diff options
| author | Adam Soltys <[email protected]> | 2026-10-28 00:22:33 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-28 00:22:33 -0700 |
| commit | 2302a4f0e25562c4d4977990918b86d4c6baba27 (patch) | |
| tree | 7c187291cdafbfb50b8124dc3db7156ba258af79 /tools | |
| parent | f5c07454284a123070a0f0159919fc8c2de2b825 (diff) | |
| download | coinosv3-2302a4f0e25562c4d4977990918b86d4c6baba27.tar.gz coinosv3-2302a4f0e25562c4d4977990918b86d4c6baba27.zip | |
The background worker's key window is 250 keys (was 25: PullCap's hourly subscription ran out after 19 payments with the app closed, "key window exhausted"), derived once per account and memoized; 60 receive preimages (was 15). When the worker can't act on a request it still wakes the user, and now leaves a "wait, open the app" error with the notifier that goes out only if nobody answers within 20 s, so the other app hears something instead of silence and a sibling's real payment is never shouted over. A request older than its two-minute window (a push that waited at APNs) is ignored quietly instead of showing "Payment request"
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/nwc-sw-test.js | 32 |
1 files changed, 23 insertions, 9 deletions
diff --git a/tools/nwc-sw-test.js b/tools/nwc-sw-test.js index 5a15d48..376a12e 100644 --- a/tools/nwc-sw-test.js +++ b/tools/nwc-sw-test.js @@ -47,11 +47,16 @@ function makeReq(method, params = {}, scheme = 'nip44_v2', ageSec = 0) { function harness({ rec = baseRec(), recs = null, answered = false } = {}) { const published = []; + const declined = []; // wait replies left with the notifier (sent only if nobody answers) const fetchFn = async (url, opts) => { if (url.includes('/publish')) { published.push(JSON.parse(opts.body).event); return { ok: true, json: async () => ({ ok: true }) }; } + if (url.includes('/decline')) { + declined.push(JSON.parse(opts.body).event); + return { ok: true, json: async () => ({ ok: true, scheduled: true }) }; + } if (url.includes('/claim')) { return { ok: true, json: async () => ({ ok: true }) }; } @@ -73,7 +78,7 @@ function harness({ rec = baseRec(), recs = null, answered = false } = {}) { ? nip44.decrypt(ev.content, nip44.getConversationKey(clientSk, conn.servicePk)) : nip04.decrypt(clientSk, conn.servicePk, ev.content)); }; - return { deps, published, decryptReply }; + return { deps, published, declined, decryptReply }; } const ev = (e) => JSON.parse(JSON.stringify(e)); // strip verified symbol like a push payload would @@ -137,15 +142,19 @@ console.log('\n[merchant methods (BTCPay)]'); check('…one still in flight (ASP unreachable here) → outgoing, pending, inside the push budget', r2.type === 'outgoing' && r2.state === 'pending' && Date.now() - t0 < 4000, JSON.stringify(r2) + ` ${Date.now() - t0} ms`); } { - const { deps } = harness(); // a mirror without receive keys - check('make_invoice on an old mirror → wake the user', (await respondFromBg({ type: 'nwc', event: ev(makeReq('make_invoice', { amount: 21000 })) }, deps)) === false); + const { deps, declined, decryptReply } = harness(); // a mirror without receive keys + const handled = await respondFromBg({ type: 'nwc', event: ev(makeReq('make_invoice', { amount: 21000 })) }, deps); + const r = declined.length ? decryptReply(declined[0]) : null; + check('make_invoice on an old mirror → wake the user, wait reply left behind', handled === false && declined.length === 1 && r.result_type === 'make_invoice' && r.error?.code === 'OTHER', JSON.stringify(r)); } console.log('\n[guards]'); { - const { deps } = harness(); + const { deps, published, declined } = harness(); const stale = ev(makeReq('get_balance', {}, 'nip44_v2', 300)); - check('stale request refused', (await respondFromBg({ type: 'nwc', event: stale }, deps)) === false); + // a push that sat at APNs past the request window: nothing to answer, and + // no "Payment request" for it either (true = handled quietly) + check('stale request ignored quietly', (await respondFromBg({ type: 'nwc', event: stale }, deps)) === true && !published.length && !declined.length); } { const { deps } = harness(); @@ -185,16 +194,21 @@ const INV21 = 'lnbc210n1p4xuk2wpp506wkjr0xk3677nu7je9c55vq4lzlkyd0ztcq2mlvumap0z { const rec = baseRec(); rec.mgr.vtxos = []; // nothing spendable in the mirror - const { deps } = harness({ rec }); + const { deps, published, declined, decryptReply } = harness({ rec }); const handled = await respondFromBg({ type: 'nwc', event: ev(makeReq('pay_invoice', { invoice: INV21 })) }, deps); - check('empty mirror → wake the user', handled === false); + const r = declined.length ? decryptReply(declined[0]) : null; + // the wait reply is NOT published by the worker: the notifier holds it and + // sends it only if nobody answers — a sibling's payment can't be shouted over + check('empty mirror → wake the user, wait reply left with the notifier', handled === false && published.length === 0 && declined.length === 1 + && r.result_type === 'pay_invoice' && r.error?.code === 'OTHER' && /open the app/.test(r.error.message), JSON.stringify(r)); + check('…addressed to the request', declined[0].kind === 23195 && declined[0].tags.some((t) => t[0] === 'e')); } { const rec = baseRec(); delete rec.keys['5']; // change index outside the mirrored window - const { deps } = harness({ rec }); + const { deps, declined } = harness({ rec }); const handled = await respondFromBg({ type: 'nwc', event: ev(makeReq('pay_invoice', { invoice: INV21 })) }, deps); - check('exhausted key window → wake the user', handled === false); + check('exhausted key window → wake the user, wait reply left behind', handled === false && declined.length === 1); } console.log('\n[pay outcome]'); |
