Aegis is most of the remaining launch cost: ~165 ms of synchronous
activation, then ~610 ms of main-thread work loading its crypto and
WizardConnect deps. It was left on startup because another session
owns bundled-addons/aegis. NOTE-aegis-on-demand.md says exactly what it
needs, measured against 0.27.1:
- addon.json: "activation": "on-demand", plus panels [{id:"main",
title:"Wallet", page:"panel.html"}].
- activate() must return a promise that resolves once deps are loaded
and, only if the vault is already unlocked, wallets are mounted.
vault.derive never rejects on a locked vault, so awaiting
mountAllWallets there would stall every first call for the full 5 s
host wait.
- Persisted WizardConnect pairings (wc/<walletId>/uris) need a relay
listener: call api.startAtLaunch(true) while any exist, false when
none are left.
- Bridge calls, panel open, wiz:// links and Settings calls already wake
it. The dapp globals are injected from the manifest before it runs.
GOTCHAS: an enabled add-on may not be running, so code in main reaches
add-ons through dispatch() rather than checking isActive()/hasHandler().
7.1 KiB
Theseus gotchas
Non-obvious things sessions keep re-deriving. If you found this file because something broke in a way that felt spooky, the answer is probably here.
build.files in package.json is EXPLICIT
electron-builder ships only what's listed in build.files. Every runtime-
loaded HTML/preload/module MUST be there. A missing entry causes the
loadFile("foo.html") in a WebContentsView to silently open blank in the
packaged app — visually indistinguishable from "the button does nothing".
npm start (dev) never catches this because it reads from the source tree
directly. Only a real npm run dist + install exposes it.
Every time you add a new WebContentsView or loadFile, add its HTML and
preload to the files array. Sanity check after building:
node -e "const b = require('fs').readFileSync('dist-public/win-unpacked/resources/app.asar'); for (const n of ['engine-picker.html','popover.html','downloads.html','engine-picker-preload.js','popover-preload.js','downloads-preload.js']) console.log(n, b.indexOf(Buffer.from(n)) >= 0 ? 'OK' : 'MISSING');"
This has bit us once (search-picker dropdown, shipped 6bbccf9c, blank
picker). Do not let it bite twice.
Building — admin terminal the first time
npm run dist needs an admin terminal on the machine's first Theseus
build so electron-builder's winCodeSign package can extract its
darwin/ symlinks (requires SeCreateSymbolicLink). Subsequent builds
reuse the cache under $LOCALAPPDATA/electron-builder/Cache/winCodeSign/
and don't need admin.
If DNS is misbehaving on the machine, pin these hosts in
C:\Windows\System32\drivers\etc\hosts before building so electron-builder
can fetch: github.com, codeload.github.com,
objects.githubusercontent.com, release-assets.githubusercontent.com.
Strip them after.
Reproducible builds
Set both before npm run dist:
SOURCE_DATE_EPOCH— freezes timestamps so a rebuild with no content changes produces the same hash. Bump per release, not per rebuild.CSC_IDENTITY_AUTO_DISCOVERY=false— stops electron-builder searching for a signing cert (we don't sign; SECURITY.md rule 2).
Native <select> popup theme
Chromium's native <select> popup uses the page's color-scheme. When it's
"light dark" (both accepted), Chromium picks by the OS scheme — so a
dark-themed app on a light OS shows a light popup and vice versa.
Fix, currently in settings.html:
:root { color-scheme: dark; }
@media (prefers-color-scheme: light) { :root { color-scheme: light; } }
nativeTheme.themeSource (driven by settings.theme) sets
prefers-color-scheme, so this stays in sync automatically. Do not remove.
Chromium also respects select option { background; color } in the popup
on Windows — a belt-and-braces override alongside color-scheme.
A preload belongs to the WebContents, not the page
A view's preload runs for every document that view ever loads. Navigating
a Settings tab to a website used to hand that site window.cfg, which
covers the vault (pwGet), settings and extension installs. Two rules
follow:
- Settings and add-on-file tabs never load anything else.
navigateTabandwill-navigateopen the target in a fresh tab instead. - A new IPC channel exposed through
settings-preload.jsmust be added toSETTINGS_ONLY(orSETTINGS_SHAREDif the toolbar'spreload.jscalls it too) in main.js. The wrapper aroundipcMain.handleonly checks the sender for the channels in those sets. Any other channel is callable by whatever page ends up in the view.
The same applies to home-preload.js, which is in every tab. Handlers
behind it check isHomePageSender / isErrorPageSender, which compare
the sender against the exact shipped file:// URL.
An enabled add-on is not necessarily running
An add-on whose addon.json says "activation": "on-demand" is listed at
launch but not started. Its declared panels, toolbar-menu,
context-menu-items and page-inject bridge are live anyway, and
AddonHost.dispatch() starts it on the first call (see ensureActive in
addons-host.js). So in main:
- Reach an add-on through
addonHost.dispatch(...), never by checkingisActive()/hasHandler()first. Both are false for a waiting add-on, and the call would never wake it (routeWizUrishows the pattern for when you really need to check). - "Enabled" in the snapshot means running or waiting;
runningtells them apart. - Settings › Performance › "Start extensions when first used" off makes every add-on start at launch again, which is the quickest way to rule this out when an add-on "does nothing".
The resolver in Theseus is resolver-web.mjs, not .js
Packaged builds ship Argus/src/lib/resolver-web.js as resolver-web.mjs
(see extraResources in package.json). Renamed because
resources/ has no adjacent package.json so a .js gets treated as
CommonJS and its exports fail. Dev reads the engine copy directly (Argus
is type: module so .js works there).
The resolver's electrum WS directIP fallback
main.js passes directIP: true to resolveHost so the engine
can dial the chipnet electrum servers by pinned IP if system DNS is dead.
Do not remove — this is what keeps .bch resolution working when the
adapter DNS is broken (a real failure mode we've hit).
Publishing new hashes end-to-end
- Build (see above).
- Update
../site/releases-manifest.jsonAND../site/tools/index.htmlAND../site/releases/index.htmlwith the fresh SHA-256s + release date. All three must match. - Deploy:
scp dist-public/*.exe ../site/releases-manifest.json silentmode:/opt/silent-mode/dl/ cd ../Argus && node src/lib/sia-upload.js ../site bns/silentmode - On-chain (wallet spend, user's action):
releases.silentmode.bchcurrently publishes{"u":"https://dl.silentmode.st/releases-manifest.json"}— a compacturecord because on-chain hashes wouldn't fit the 200-byte OP_RETURN. Only re-publish if the URL changes or if we ever put full hashes on-chain.
Sia upload from this machine fails without a hosts pin
node src/lib/sia-upload.js fails with getaddrinfo EAI_FAIL coinspectrum.duckdns.org unless the host is pinned in
C:\Windows\System32\drivers\etc\hosts:
195.184.247.106 coinspectrum.duckdns.org. This is a DNS quirk of the
machine, not a code bug — related to the strict-resolver behavior the
Ariadne daemon's EDNS fix addresses.
Deploy directory quirks
/opt/silent-mode/dl/is served asdl.silentmode.st— installers + manifest live here.silentmode.stproxies to Sia (bns/silentmode/) — the HTML pages live there.- The
bnsgateway does NOT auto-index directories.silentmode.st/apps/is a 404 (NoSuchKey); users must hit/apps/index.html. Fix upstream inbnsd.jsif it ever matters.
Firefox needs a restart after Ariadne CA install/rotation
Firefox only reads root CAs at startup. The Ariadne installer offers a
-CloseFirefox task by default when Firefox is running so this doesn't
bite. Chrome/Edge pick up the new root immediately.