Aegis 0.29.0: start on first use, not at every Theseus launch
Aegis was most of what was left of launch cost: its crypto and WizardConnect deps block the main thread for ~0.6 s right after the first frame. It now declares its panel and "activation": "on-demand"; the dapp bridges are still on every page from the start, and the first page call, panel open or wiz:// link starts it. activate() returns a promise the host waits on, so the call that woke Aegis finds WizardConnect and the mounted wallets. With a locked vault it does not wait for the mount (derive blocks until unlock), so the page gets the usual locked answer in ~0.7 s instead of after the host's 5 s limit. Two things only work while Aegis runs: a live WizardConnect pairing (no one else listens on its relays) and "stay unlocked" (which also opens Settings > Passwords). While either is on, Aegis asks the host to start it at launch, and withdraws the request when both are off. Pairings left behind by a removed wallet do not count. Boot trace, same profile, 3 warm runs: main thread blocked in the first 6 s 970-1040 ms -> 300-320 ms, longest block 605-667 ms -> 227-244 ms, toolbar 1.34-1.61 s -> 0.83-0.87 s.
This commit is contained in:
parent
640abb5e60
commit
ecd2c351d2
2 changed files with 59 additions and 7 deletions
10
addon.json
10
addon.json
|
|
@ -1,12 +1,20 @@
|
|||
{
|
||||
"id": "aegis",
|
||||
"name": "Aegis Wallet",
|
||||
"version": "0.28.1",
|
||||
"version": "0.29.0",
|
||||
"category": "plugin",
|
||||
"description": "Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB) derived from your Theseus vault. Dapps get window.bitcoincash and window.wizardconnect on any site; window.tronWeb / window.tronLink / window.ethereum / window.solana too. Every call needs your approval.",
|
||||
"author": "Silent Mode",
|
||||
"icon": "data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='none'%3E%3Cpolygon points='16,2 28,9 28,23 16,30 4,23 4,9' fill='%230a0a0d' stroke='%23D6FF3D' stroke-width='1.6' stroke-linejoin='round'/%3E%3Ccircle cx='16' cy='16' r='4.5' fill='none' stroke='%23D6FF3D' stroke-width='1.4'/%3E%3Ccircle cx='16' cy='16' r='1.6' fill='%23D6FF3D'/%3E%3C/svg%3E",
|
||||
"main": "index.js",
|
||||
"activation": "on-demand",
|
||||
"panels": [
|
||||
{
|
||||
"id": "main",
|
||||
"title": "Wallet",
|
||||
"page": "panel.html"
|
||||
}
|
||||
],
|
||||
"updateURL": "https://navigate.st/bns/theseus.x/extensions/aegis/updates.json",
|
||||
"capabilities": [
|
||||
"sidebar-panel",
|
||||
|
|
|
|||
56
index.js
56
index.js
|
|
@ -438,7 +438,7 @@ function coinsForPanel() {
|
|||
// host so nothing changes about durability or who owns the file; only the
|
||||
// repeated parsing of it disappears. Falls back to the host's storage
|
||||
// untouched if the api object will not accept the substitution.
|
||||
function installStorageCache(api) {
|
||||
function installStorageCache(api, onSet) {
|
||||
const raw = api.storage;
|
||||
if (!raw || typeof raw.all !== "function") return false;
|
||||
let mem = null;
|
||||
|
|
@ -454,6 +454,7 @@ function installStorageCache(api) {
|
|||
set: (key, value) => {
|
||||
load()[key] = value;
|
||||
raw.set(key, value);
|
||||
if (onSet) try { onSet(key); } catch {}
|
||||
},
|
||||
all: () => ({ ...load() }),
|
||||
};
|
||||
|
|
@ -463,6 +464,33 @@ function installStorageCache(api) {
|
|||
} catch { return false; }
|
||||
}
|
||||
|
||||
// Aegis starts on first use (addon.json "activation": "on-demand"). Two
|
||||
// things only work while it runs, so while either is on it asks the host to
|
||||
// start it at launch: a live WizardConnect pairing (nobody else listens on
|
||||
// the relays for the dapp's sign requests) and "stay unlocked" (that
|
||||
// auto-unlock also opens Settings › Passwords). Pairings of a removed wallet
|
||||
// stay in storage, so only wallets still in the list count.
|
||||
function needsLaunchStart(api) {
|
||||
const ids = new Set((readWallets(api) || []).map((w) => w.id));
|
||||
const all = api.storage.all ? api.storage.all() : {};
|
||||
for (const [k, v] of Object.entries(all)) {
|
||||
const m = /^wc\/(.+)\/uris$/.exec(k);
|
||||
if (m && ids.has(m[1]) && Array.isArray(v) && v.length) return true;
|
||||
}
|
||||
const cfg = api.storage.get("aegis/session/cfg", null);
|
||||
const blob = api.storage.get("aegis/session/enc", null);
|
||||
return !!(cfg && cfg.lockOnClose === false && blob && blob.encPwB64);
|
||||
}
|
||||
let launchStartAsked = null;
|
||||
function syncLaunchStart(api) {
|
||||
if (typeof api.startAtLaunch !== "function") return; // host predates on-demand
|
||||
const on = needsLaunchStart(api);
|
||||
if (on === launchStartAsked) return;
|
||||
launchStartAsked = on;
|
||||
try { api.startAtLaunch(on); } catch (e) { api.log("startAtLaunch:", e?.message || e); }
|
||||
}
|
||||
const LAUNCH_START_KEYS = /^(wallets$|wc\/|aegis\/session\/)/;
|
||||
|
||||
function readWallets(api) {
|
||||
const raw = api.storage.get("wallets", null);
|
||||
return Array.isArray(raw) ? raw : null;
|
||||
|
|
@ -3308,10 +3336,14 @@ function registerPageMessages(api) {
|
|||
// ---- activate ---------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
// Returns a promise that settles once Aegis can serve calls. Aegis starts
|
||||
// on first use, so the call that woke it is the first thing it sees, and
|
||||
// the host holds that call (up to 5 s) until this promise settles.
|
||||
activate(api) {
|
||||
// No explicit icon — inherit manifest.icon (branded shield data URI)
|
||||
// so the toolbar dock button renders the aegis.x brand mark instead
|
||||
// of a fallback emoji.
|
||||
// of a fallback emoji. Same id as the panel addon.json declares, which
|
||||
// this replaces.
|
||||
api.registerSidebarPanel({ id: "main", title: "Wallet", page: "panel.html" });
|
||||
// Serve reads from memory. The host's storage.get() does a readFileSync
|
||||
// plus a JSON.parse of the ENTIRE add-on store on every single call, on
|
||||
|
|
@ -3323,7 +3355,7 @@ module.exports = {
|
|||
// Safe because this process is the only writer: Aegis's panel talks to
|
||||
// the host over addon messages and never touches addon storage directly.
|
||||
// If that ever changes, this cache has to go or be invalidated.
|
||||
installStorageCache(api);
|
||||
installStorageCache(api, (key) => { if (LAUNCH_START_KEYS.test(key)) syncLaunchStart(api); });
|
||||
const c = ctx = {
|
||||
api,
|
||||
d: null,
|
||||
|
|
@ -3344,6 +3376,8 @@ module.exports = {
|
|||
migrateLegacyStorage(api);
|
||||
registerPanelMessages(api);
|
||||
registerPageMessages(api);
|
||||
launchStartAsked = null;
|
||||
syncLaunchStart(api);
|
||||
// Restore the user's opt-in choice from storage. Off by default so a
|
||||
// fresh install never hits any oracle without asking. Source can also
|
||||
// be pre-restored so a user who picked Kraken stays on Kraken.
|
||||
|
|
@ -3355,7 +3389,7 @@ module.exports = {
|
|||
// for the browser chrome to paint so the cost never delays Theseus's
|
||||
// first frame; older hosts without whenUiReady load immediately.
|
||||
const uiReady = typeof api.whenUiReady === "function" ? api.whenUiReady() : Promise.resolve();
|
||||
uiReady.then(() => loadDeps(api)).then((d) => {
|
||||
const started = uiReady.then(() => loadDeps(api)).then(async (d) => {
|
||||
if (ctx !== c) return;
|
||||
c.d = d;
|
||||
// Start the WizardConnect manager once deps are ready. Per-wallet
|
||||
|
|
@ -3380,12 +3414,22 @@ module.exports = {
|
|||
},
|
||||
});
|
||||
c.wc.onStateChange(() => emitState());
|
||||
return tryAutoUnlock(api).then(() => mountAllWallets());
|
||||
}).catch((e) => {
|
||||
await tryAutoUnlock(api);
|
||||
// vault.derive() polls until the user unlocks, so with a locked vault
|
||||
// mountAllWallets() never settles. Only wait for it when the vault is
|
||||
// open; otherwise the waking call gets the usual locked answer now
|
||||
// rather than after the host's 5 s timeout.
|
||||
const st = await api.vault.lifecycle.status().catch(() => null);
|
||||
const mounting = mountAllWallets();
|
||||
if (st && st.unlocked) await mounting;
|
||||
else mounting.catch((e) => api.log("mount:", e?.message || e));
|
||||
});
|
||||
started.catch((e) => {
|
||||
if (ctx !== c) return;
|
||||
api.log("startup failed:", e?.message);
|
||||
emitState();
|
||||
});
|
||||
return started.catch(() => {});
|
||||
},
|
||||
deactivate() {
|
||||
const c = ctx; ctx = null;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue