aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ark/store.js20
-rw-r--r--src/features/ark.js23
2 files changed, 41 insertions, 2 deletions
diff --git a/src/ark/store.js b/src/ark/store.js
index b1caa94..24d8f55 100644
--- a/src/ark/store.js
+++ b/src/ark/store.js
@@ -139,6 +139,7 @@ export class ArkStore {
}
}
this.ready = true;
+ this._parsed = null; // peeks now read the opened store
if (migrated.length) {
this.flush().then(() => { for (const k of migrated) { try { ls.removeItem(k); } catch {} } }).catch(() => {});
}
@@ -153,13 +154,32 @@ export class ArkStore {
const v = this.mem.get(key);
return v ? clone(v) : null;
}
+ // The stored object itself, not a copy — for readers that only look (the
+ // render path). get() deep-copies, and a big wallet's state is ~1 MB: on a
+ // Pixel 9 each copy took ~220 ms and the first render asked five to seven
+ // times before the manager connected, 1.4 s of every launch. Never mutate
+ // what this returns. Before the store opens (or on localStorage) the parse
+ // is kept until the key is next written.
+ peek(key) {
+ if (!this.ready || this.lsMode) {
+ const hit = this._parsed && this._parsed.get(key);
+ if (hit !== undefined) return hit;
+ let v = null;
+ try { const raw = this.ls().getItem(key); v = raw ? JSON.parse(raw) : null; } catch { v = null; }
+ (this._parsed ||= new Map()).set(key, v);
+ return v;
+ }
+ return this.mem.get(key) || null;
+ }
set(key, obj) {
+ if (this._parsed) this._parsed.delete(key);
if (!this.ready || this.lsMode) { try { this.ls().setItem(key, JSON.stringify(obj)); } catch {} return; }
this.mem.set(key, obj);
this.dirty.add(key);
this._schedule();
}
remove(key) {
+ if (this._parsed) this._parsed.delete(key);
if (!this.ready || this.lsMode) { try { this.ls().removeItem(key); } catch {} return; }
this.mem.delete(key);
this.dirty.add(key);
diff --git a/src/features/ark.js b/src/features/ark.js
index 5d3b293..f5ba9d8 100644
--- a/src/features/ark.js
+++ b/src/features/ark.js
@@ -77,11 +77,15 @@ if (typeof document !== 'undefined') {
document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'hidden') flushAll(); });
}
+// A read-only look at the stored state (stub wallets in tests have only loadArkState).
+const peekArk = (wallet, url) => (wallet.peekArkState ? wallet.peekArkState(url) : wallet.loadArkState(url));
+
// Wallet storage/key helpers for this feature, installed onto the core
// wallet instance so a build without the feature ships none of it.
export function installArkWallet(wallet) {
if (wallet.loadArkState) return; // already installed
const store = arkStore(); // IndexedDB-backed, memory-fronted; opens now
+ const peekOwnerOk = new WeakSet(); // stored states whose owner check passed
Object.assign(wallet, {
_arkStore: store,
// ---- Ark support -------------------------------------------------------
@@ -133,6 +137,21 @@ export function installArkWallet(wallet) {
return st;
} catch { return null; }
},
+ // loadArkState for readers that only look (see ArkStore.peek): the stored
+ // object, no copy — never mutate it. Misfiled state goes through
+ // loadArkState's repair; each object's owner is decoded once.
+ peekArkState(url) {
+ try {
+ const st = store.peek(this._arkKey(url));
+ if (!st) return null;
+ if (!peekOwnerOk.has(st)) {
+ const owner = this._arkVtxoOwner(st);
+ if (owner && st.serverPubkey && owner !== st.serverPubkey) return this.loadArkState(url);
+ peekOwnerOk.add(st);
+ }
+ return st;
+ } catch { return null; }
+ },
// Offer up pre-namespacing state, but only to the ASP that actually
// cosigned it. Its owner is read from the state's own stamp, or failing
// that decoded straight out of a vtxo — the vtxo wire format carries the
@@ -554,7 +573,7 @@ export function arkFeature(ctx) {
function arkWanted() {
const cfg = getArkConfig();
- const st = cfg && wallet.loadArkState(cfg.ark);
+ const st = cfg && peekArk(wallet, cfg.ark);
return !!(st && ((st.vtxos || []).length || (st.movements || []).length
|| (st.actions || []).some((a) => !['done', 'failed'].includes(a.step))));
}
@@ -797,7 +816,7 @@ export function arkFeature(ctx) {
// (send/exit) still requires the seed and is gated separately.
const cfg = getArkConfig();
if (!cfg) return null;
- return wallet.loadArkState(cfg.ark);
+ return peekArk(wallet, cfg.ark);
}
// Which receives actually arrived over Lightning? A payment to the user's