summaryrefslogtreecommitdiffstats
path: root/src/nwc-lock.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-14 08:53:35 -0700
committerAdam Soltys <[email protected]>2026-09-14 08:53:35 -0700
commit6394de43d4e596aaeddd2b262bb7a595679d8fbc (patch)
tree391f79322f788955985ad149d02e60111aa6ea42 /src/nwc-lock.js
parent4131e5ee24c4891caffdd8f4ca903ee973d104f7 (diff)
downloadcoinosv3-6394de43d4e596aaeddd2b262bb7a595679d8fbc.tar.gz
coinosv3-6394de43d4e596aaeddd2b262bb7a595679d8fbc.zip
A zap answered by two tabs no longer reports as failed.
Every open tab of the same wallet sees every wallet-connect request. Two hidden tabs on one laptop both tried to pay the same zap: the ASP refused the second with "payment already in progress", and that error reached Amethyst half a second before the first tab's preimage. A Web Lock per request now lets one tab (or the worker) answer while the rest stay quiet, and a device that does meet "already in progress" waits for the sibling payment's outcome — relaying its preimage only if the payer never speaks — instead of shouting a failure over sats that went through. The losing side also stops writing a failed row into history for it. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01QYJfZ4A1TDCEY4Foq3CApR
Diffstat (limited to 'src/nwc-lock.js')
-rw-r--r--src/nwc-lock.js14
1 files changed, 14 insertions, 0 deletions
diff --git a/src/nwc-lock.js b/src/nwc-lock.js
new file mode 100644
index 0000000..c4fec08
--- /dev/null
+++ b/src/nwc-lock.js
@@ -0,0 +1,14 @@
+// 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.
+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()));
+}