aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
diff options
context:
space:
mode:
Diffstat (limited to 'src/features')
-rw-r--r--src/features/ark.js39
-rw-r--r--src/features/nwc.js14
2 files changed, 48 insertions, 5 deletions
diff --git a/src/features/ark.js b/src/features/ark.js
index f5ba9d8..075cf4b 100644
--- a/src/features/ark.js
+++ b/src/features/ark.js
@@ -1052,10 +1052,29 @@ export function arkFeature(ctx) {
return v;
};
}
- async function writeBgMirror(connections, offer, prefs) {
+ // The worker can now pay while this app is alive behind another one (a
+ // phone with Coinos in the background: the page is frozen or throttled,
+ // the worker is not). Its change coin and its spent coins then exist only
+ // in the mirror — so the mirror is never written over the worker's hand:
+ // one writer at a time (the lock the worker holds for a whole push), and
+ // whatever the worker did since we last looked is folded in first.
+ let bgAbsorbedAt = 0; // the worker stamp (rec.workerAt) this app has folded in
+ const withBgLock = (fn) => (typeof navigator !== 'undefined' && navigator.locks
+ ? navigator.locks.request('nwc-bg-work', fn) : fn());
+ const bgBusy = (mgr) => !!(mgr && mgr._lnDriving && mgr._lnDriving.size);
+ async function writeBgMirror(connections, offer, prefs) { return withBgLock(() => writeBgMirrorLocked(connections, offer, prefs)); }
+ async function writeBgMirrorLocked(connections, offer, prefs) {
const cfg = getArkConfig();
if (!cfg || !ark || !ark.info || !ark.state) return;
- const prev = await loadBg(wallet._cacheKey());
+ let prev = await loadBg(wallet._cacheKey());
+ if (prev && prev.v >= 3 && prev.mgr && (prev.workerAt || 0) > bgAbsorbedAt) {
+ // a payment of ours in flight holds the very records a merge replaces:
+ // leave the worker's state standing and write at the next call
+ if (bgBusy(ark)) return;
+ await mergeBgWorkerState(ark).catch((e) => console.warn('ark: worker merge failed', e && e.message));
+ if ((prev.workerAt || 0) > bgAbsorbedAt) return; // not folded in: don't write over it
+ prev = await loadBg(wallet._cacheKey());
+ }
if (connections === undefined) connections = (prev && prev.connections) || [];
if (offer === undefined) offer = (prev && prev.offer) || null;
if (prefs === undefined) prefs = (prev && prev.prefs) || undefined;
@@ -1068,7 +1087,7 @@ export function arkFeature(ctx) {
offer,
autowithdraw: await awMirror(prev),
});
- if (prev) { rec.spends = prev.spends; rec.pendingPays = prev.pendingPays; } // the worker's log survives rewrites
+ if (prev) { rec.spends = prev.spends; rec.pendingPays = prev.pendingPays; if (prev.workerAt) rec.workerAt = prev.workerAt; } // the worker's log survives rewrites
if (prefs) rec.prefs = prefs; // notification choices the worker honours
await saveBg(wallet._cacheKey(), rec);
await disarmSiblingRecords(wallet._cacheKey(), rec).catch(() => {});
@@ -1088,6 +1107,7 @@ export function arkFeature(ctx) {
}
if (!rec.mgr) return;
bootPhase('ark-merge');
+ const stamp = rec.workerAt || 0;
const merged = mergeArkStates(mgr.state, rec.mgr);
// the worker's view of OUR vtxos is newer for anything it spent
const workerState = new Map((rec.mgr.vtxos || []).map((v) => [v.id, v.state]));
@@ -1097,6 +1117,7 @@ export function arkFeature(ctx) {
merged.nextKeyIndex = Math.max(merged.nextKeyIndex || 1, rec.mgr.nextKeyIndex || 1);
mgr.state = merged;
mgr._save();
+ if (mgr === ark) bgAbsorbedAt = Math.max(bgAbsorbedAt, stamp);
bootPhase('ark-merged');
await mgr.reconcile().catch(() => {});
bootPhase('ark-reconciled');
@@ -4166,6 +4187,18 @@ export function arkFeature(ctx) {
// nwc.js calls this (with its connections) whenever the mirror should
// refresh: on enable, on connection changes, after payments, on a timer.
async arkBgWrite(connections, offer, prefs) { return writeBgMirror(connections, offer, prefs); },
+ // Back on screen: what the worker paid while this app sat behind another
+ // one is folded in now, not at the next start. true when there was any.
+ async arkBgAbsorb() {
+ if (!ark || !ark.state) return false;
+ return withBgLock(async () => {
+ const rec = await loadBg(wallet._cacheKey());
+ if (!rec || rec.v < 3 || !rec.mgr || (rec.workerAt || 0) <= bgAbsorbedAt || bgBusy(ark)) return false;
+ await mergeBgWorkerState(ark);
+ render();
+ return true;
+ });
+ },
async arkBgSpendableSat() {
const rec = await loadBg(wallet._cacheKey());
return ((rec && rec.mgr && rec.mgr.vtxos) || [])
diff --git a/src/features/nwc.js b/src/features/nwc.js
index dc87388..9768740 100644
--- a/src/features/nwc.js
+++ b/src/features/nwc.js
@@ -388,7 +388,10 @@ export function nwcFeature(ctx) {
return !!(prior && prior.length);
};
if (age > 5 && await lookBack()) return dlog('nwc: skip — request already answered elsewhere', ev.id.slice(0, 8));
- if (mobile && age <= 5) await new Promise((r) => setTimeout(r, 1000));
+ // (a phone with this app on screen is as awake as any computer: only
+ // one behind another app holds back — every zap answered by an open
+ // phone used to wait this second for nobody)
+ if (mobile && age <= 5 && typeof document !== 'undefined' && document.hidden) await new Promise((r) => setTimeout(r, 1000));
claimed = await claim(ev.id);
if (claimed === false) return dlog('nwc: standing down — another device claimed', ev.id.slice(0, 8));
if (claimed === null && age <= 5) {
@@ -981,7 +984,14 @@ export function nwcFeature(ctx) {
if (typeof window !== 'undefined') {
window.addEventListener('online', () => listen());
document.addEventListener('visibilitychange', () => {
- if (document.visibilityState === 'visible' && conns().length) { listen(); refreshRegistration(); }
+ if (document.visibilityState === 'visible' && conns().length) {
+ listen(); refreshRegistration();
+ // the worker may have paid while this app sat behind another one
+ Promise.resolve(hook('arkBgAbsorb')).then((had) => { if (had) return reconcileBg(); }).catch(() => {});
+ }
+ // going behind another app: the worker answers from the mirror now, so
+ // it gets this moment's coins, not the last minute-and-a-half's
+ if (document.visibilityState === 'hidden' && conns().length) writeBg();
});
}
}