// 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. export async function withWorkLock(fn) { const locks = typeof navigator !== 'undefined' && navigator.locks; if (!locks) return fn(); return locks.request('nwc-bg-work', fn); } 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())); }