From 43a35d0fe6abc56b6594015ae1028842c04a2c19 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sun, 25 Oct 2026 07:46:55 -0700 Subject: 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 Claude-Session: https://claude.ai/code/session_01QjVvE4YAcK3AGwV7kZXeN4 --- src/nwc-lock.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'src/nwc-lock.js') diff --git a/src/nwc-lock.js b/src/nwc-lock.js index a7b10b4..bd8586a 100644 --- a/src/nwc-lock.js +++ b/src/nwc-lock.js @@ -12,10 +12,17 @@ // 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) { +// 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; - if (!locks) return fn(); - return locks.request('nwc-bg-work', fn); + 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) { -- cgit v1.2.3