aboutsummaryrefslogtreecommitdiffstats
path: root/src/nwc-bg.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-25 07:46:55 -0700
committerAdam Soltys <[email protected]>2026-10-25 07:46:55 -0700
commit43a35d0fe6abc56b6594015ae1028842c04a2c19 (patch)
treebe56c2bb8f71826df0360b0842ee1bd7fa4b3a3b /src/nwc-bg.js
parent9270bc52a33ef007d57d15b5e6aee95248c368a2 (diff)
downloadcoinosv3-43a35d0fe6abc56b6594015ae1028842c04a2c19.tar.gz
coinosv3-43a35d0fe6abc56b6594015ae1028842c04a2c19.zip
A closed wallet's NWC payments fit iOS's ten-second wake: a paid send is never marked for revocation by a second wake, lookups report outgoing sends and finish them, and pushes no longer run side by side
PullCap's night of 2026-10-24: every wake's diary stopped 8-12 s after its push (iOS stops the worker there, not at 30 s). Two wakes two seconds apart both drove one send; the second's InitiateLightningPayment was refused ("state: ln-spent") and the paid send went to 'revoking', where every later wake spent its whole budget on it, so no request was answered after midnight. - manager: an initiate refused as ln-spent follows the payment ('initiated'); a send in 'revoking' asks the ASP first (paid: settle; in flight before expiry: back to waiting; not found: revoke) - worker: 9 s budget on Apple endpoints (27 s elsewhere) for answers, earlier sends and renewals; every ASP step held to it - lookup_invoice asks every copy of the connection and drives an outgoing send in flight, answering settled with the preimage - a lease in the worker's IndexedDB store serialises pushes (the Web Lock didn't); diary notes a missing Web Locks API - the reply and the mirror save run together; stuck sends are finished before a new payment only when they hold its coins Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QjVvE4YAcK3AGwV7kZXeN4
Diffstat (limited to 'src/nwc-bg.js')
-rw-r--r--src/nwc-bg.js32
1 files changed, 32 insertions, 0 deletions
diff --git a/src/nwc-bg.js b/src/nwc-bg.js
index 2c73206..ab083d9 100644
--- a/src/nwc-bg.js
+++ b/src/nwc-bg.js
@@ -81,6 +81,38 @@ export async function readBgLog() {
export async function clearBgLog() {
return tx('readwrite', (s) => { s.delete(LOG_KEY); });
}
+// A lease on the worker's wallet work, kept in this store. Two pushes seconds
+// apart ran side by side on iOS, each in its own worker (two "worker started"
+// lines), and nothing kept them apart: both finished one send, and the
+// second, over a stale copy, marked a paid send 'revoking'. The store is
+// the one thing both see, and a readwrite transaction is atomic across them.
+// A lease outlives a stopped worker only for its ttl.
+export async function takeLease(name, { ttl = 12_000, wait = 5_000 } = {}) {
+ const key = '__lease:' + name;
+ const me = Math.random().toString(36).slice(2);
+ const start = Date.now();
+ for (;;) {
+ let got = false;
+ try {
+ got = await tx('readwrite', async (s) => {
+ const cur = await get(s, key);
+ if (cur && cur.until > Date.now() && cur.owner !== me) return false;
+ s.put({ owner: me, until: Date.now() + ttl }, key);
+ return true;
+ });
+ } catch { got = true; } // no store, no lease: run
+ if (got) break;
+ if (Date.now() - start > wait) break; // a holder that overstays: go anyway
+ await new Promise((r) => setTimeout(r, 250));
+ }
+ const waited = Date.now() - start;
+ const release = () => tx('readwrite', async (s) => {
+ const cur = await get(s, key);
+ if (cur && cur.owner === me) s.delete(key);
+ }).catch(() => {});
+ return { waited, release };
+}
+
// The service worker doesn't know wallet cache keys — it finds the right
// record by the connection's service pubkey across all of them.
export async function allBgs() {