aboutsummaryrefslogtreecommitdiffstats
path: root/src/nwc-bg.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/nwc-bg.js')
-rw-r--r--src/nwc-bg.js61
1 files changed, 59 insertions, 2 deletions
diff --git a/src/nwc-bg.js b/src/nwc-bg.js
index 3349126..ab083d9 100644
--- a/src/nwc-bg.js
+++ b/src/nwc-bg.js
@@ -57,6 +57,62 @@ export async function saveBg(walletKey, rec) {
export async function clearBg(walletKey) {
return tx('readwrite', (s) => { s.delete(walletKey); });
}
+
+// The worker's diary: what it did while the app was closed — each push, the
+// steps of a payment, every notification shown or refused. A phone stops a
+// worker without a trace, and the diary's last line says where it got to.
+// Settings → Notifications → Copy hands it to whoever is looking into it.
+// Appended line by line (a worker that dies mid-push keeps what it wrote),
+// one writer at a time, a few hundred lines kept.
+const LOG_KEY = '__log';
+const LOG_MAX = 400;
+let logChain = Promise.resolve();
+export function appendBgLog(line) {
+ logChain = logChain.then(() => tx('readwrite', async (s) => {
+ const cur = (await get(s, LOG_KEY)) || [];
+ cur.push(line);
+ s.put(cur.length > LOG_MAX ? cur.slice(-LOG_MAX) : cur, LOG_KEY);
+ })).catch(() => {});
+ return logChain;
+}
+export async function readBgLog() {
+ try { return (await tx('readonly', (s) => get(s, LOG_KEY))) || []; } catch { return []; }
+}
+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() {
@@ -67,7 +123,8 @@ export async function allBgs() {
cur.onsuccess = () => {
const c = cur.result;
if (!c) return res(out);
- out.push({ walletKey: c.key, rec: c.value });
+ // the worker's diary shares the store under a reserved key
+ if (!(typeof c.key === 'string' && c.key.startsWith('__'))) out.push({ walletKey: c.key, rec: c.value });
c.continue();
};
cur.onerror = () => rej(cur.error);
@@ -119,7 +176,7 @@ export function buildBg({ ark, mgrState, keyFor, connections, offer, autowithdra
// worker counts what the app spent; a revoked one rides along flagged, to
// be refused rather than ignored.
connections: (connections || []).map((c) => ({
- id: c.id, servicePk: c.servicePk, serviceSk: c.serviceSk, clientPk: c.clientPk,
+ id: c.id, name: c.name || '', servicePk: c.servicePk, serviceSk: c.serviceSk, clientPk: c.clientPk,
maxSat: c.maxSat, dailySat: c.dailySat, period: c.period || 'day',
spentDate: c.spentDate || null, spentToday: c.spentToday || 0,
revoked: !!c.revoked,