1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
// One request, one answerer per origin.
//
// Every open tab of the same wallet (and its service worker) sees every NWC
// request. Without exclusion two hidden tabs on one laptop both paid a zap:
// the loser's "payment already in progress" error reached the client half a
// second before the winner's preimage, and the zap showed as failed while
// the sats had gone. Web Locks are shared by every same-origin context, so
// the first to claim a request's lock answers it and the rest stay quiet.
// Where locks are unavailable (tests, ancient engines) the caller just runs.
// The worker's wallet work, one push at a time. Every push handler builds
// its own manager over the one shared mirror, and two of them driving the
// same send at once would sign it twice (a client's retry lands seconds
// after its lookup). A waiting push keeps its waitUntil and reads the mirror
// fresh once it gets the lock.
// The Web Lock alone didn't hold on iOS (two workers at once, neither
// waited), so the lease in the worker's store (nwc-bg.js) is taken inside it.
export async function withWorkLock(fn, { lease = null } = {}) {
const locks = typeof navigator !== 'undefined' && navigator.locks;
const run = async () => {
if (!lease) return fn(0);
const l = await lease();
try { return await fn(l.waited); } finally { await l.release(); }
};
if (!locks) return run();
return locks.request('nwc-bg-work', run);
}
export async function withRequestLock(id, fn, held = () => undefined) {
const locks = typeof navigator !== 'undefined' && navigator.locks;
if (!locks || !id) return fn();
return locks.request(`nwc-req:${id}`, { ifAvailable: true }, (lock) => (lock ? fn() : held()));
}
|