aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/sync.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-18 23:12:18 -0700
committerAdam Soltys <[email protected]>2026-09-18 23:12:18 -0700
commit703ee6e1281e72ec00f65768d7de7a015c242abb (patch)
tree8a643d0f1ad6d6e49db210aec45341a7b119e5ab /src/features/sync.js
parent9e0f9528cd6726836386658c4f38ba8a42bea3ac (diff)
downloadcoinosv3-703ee6e1281e72ec00f65768d7de7a015c242abb.tar.gz
coinosv3-703ee6e1281e72ec00f65768d7de7a015c242abb.zip
Accounts from the header, snapshot-first Spending restore, name ownership grants.
Header buttons always leave the current feature screen; an account with no remembered nostr identity opens Accounts instead of nothing. Logout confirms in place. A scan finishing after logout or an account switch no longer drops the new session into offline mode (stateGeneration guards in sync + ark). Spending restores from the snapshot first. A migration grant names the identity that owns an address; its manager may register it without becoming that identity (names/ownership.js). Browser tests for account access, profile retry, spending restore, thread reply focus, custom-emoji reactions and registry ownership. Co-Authored-By: Claude Fable 5.1 <[email protected]>
Diffstat (limited to 'src/features/sync.js')
-rw-r--r--src/features/sync.js47
1 files changed, 36 insertions, 11 deletions
diff --git a/src/features/sync.js b/src/features/sync.js
index 415717f..ed22322 100644
--- a/src/features/sync.js
+++ b/src/features/sync.js
@@ -247,8 +247,25 @@ export function installSyncWallet(wallet, { outbox = syncOutbox() } = {}) {
},
});
+ // Account activation, Spending connect and the live subscription all need
+ // the same initial restore. Share it so history is merged newest-first
+ // before either live source starts delivering an old backlog piecemeal.
+ const restore = wallet.syncFromNostr.bind(wallet);
+ let restoring = null;
+ wallet.syncFromNostr = () => {
+ const pk = wallet.nostr.pk, net = wallet.netName;
+ if (restoring && restoring.pk === pk && restoring.net === net) return restoring.promise;
+ const run = { pk, net, promise: null };
+ run.promise = restore().finally(() => { if (restoring === run) restoring = null; });
+ restoring = run;
+ wallet.nostrRestoreReady ||= run.promise;
+ return run.promise;
+ };
+
// identity follows the open wallet
wallet.registerLoadHook(() => {
+ restoring = null;
+ wallet.nostrRestoreReady = null;
if (wallet.mnemonic) wallet.nostr.load(wallet.mnemonic, wallet.passphrase, wallet.accountIndex || 0);
else wallet.nostr.unload();
outbox?.wake();
@@ -305,24 +322,32 @@ export function installSyncWallet(wallet, { outbox = syncOutbox() } = {}) {
// vtxos union) applies live; full snapshots stay a load-time affair. With
// per-device slots no device overwrites another's, so this only ever adds.
let stateUnsub = null;
+ let stateGeneration = 0;
wallet.registerRealtimeHook({
start: () => {
const sync = getSyncConfig();
if (!sync.enabled || !wallet.nostr.pk) return;
wallet.nostr.setRelays(sync.relays);
if (stateUnsub) { try { stateUnsub(); } catch {} }
- stateUnsub = wallet.nostr.subscribeStates((v, dtag) => {
- if (!v.netName || v.netName !== wallet.netName) return;
- if (!isOurDtag(dtag, wallet.netName)) return;
- if (isOwnDeviceDtag(dtag, wallet.netName) && (v.savedAt || 0) === (wallet._savedAt || 0)) return; // our own echo
- if (!v.blob) { wallet._mergeSnapshotExtensions(v); return; }
- const pk = wallet.nostr.pk;
- resolveStates([{ state: v, dtag }]).then((r) => {
- if (r[0] && wallet.nostr.pk === pk && wallet.netName === r[0].state.netName) wallet._mergeSnapshotExtensions(r[0].state);
- }).catch(() => {});
- });
+ stateUnsub = null;
+ const generation = ++stateGeneration;
+ const identity = wallet.nostr.pk, network = wallet.netName;
+ Promise.resolve(wallet.nostrRestoreReady || wallet.syncFromNostr()).then(() => {
+ if (generation !== stateGeneration || wallet.nostr.pk !== identity || wallet.netName !== network) return;
+ stateUnsub = wallet.nostr.subscribeStates((v, dtag) => {
+ if (generation !== stateGeneration || wallet.nostr.pk !== identity) return;
+ if (!v.netName || v.netName !== wallet.netName) return;
+ if (!isOurDtag(dtag, wallet.netName)) return;
+ if (isOwnDeviceDtag(dtag, wallet.netName) && (v.savedAt || 0) === (wallet._savedAt || 0)) return; // our own echo
+ if (!v.blob) { wallet._mergeSnapshotExtensions(v); return; }
+ const pk = wallet.nostr.pk;
+ resolveStates([{ state: v, dtag }]).then((r) => {
+ if (generation === stateGeneration && r[0] && wallet.nostr.pk === pk && wallet.netName === r[0].state.netName) wallet._mergeSnapshotExtensions(r[0].state);
+ }).catch(() => {});
+ });
+ }).catch(() => {});
},
- stop: () => { if (stateUnsub) { try { stateUnsub(); } catch {} stateUnsub = null; } },
+ stop: () => { stateGeneration++; if (stateUnsub) { try { stateUnsub(); } catch {} stateUnsub = null; } },
});
// Capture and sign changed domains NOW, under the saving wallet's identity.