Commit graph

9 commits

Author SHA1 Message Date
Local Dev
4cc2d53582 Theseus: start extensions on first use, Startup switches in Settings
Every enabled add-on used to be activated synchronously in initAddons(),
during app.whenReady and before the window exists. That is the largest
launch cost left (boot tracer, 2026-10-03). An add-on can now say
"activation": "on-demand" in addon.json. It is then listed at launch but
not started. Its declared surfaces stay live: "panels" (new: sidebar
panels declared up front), toolbar-menu, context-menu-items and the
page-inject bridge, whose source is read on the first matching page.
activate() runs on first real use: a panel opened, a menu or context
item picked, a message from its panel, tab or page bridge, a Settings
addon-invoke, a wiz:// link (for Aegis). All of those go through
AddonHost.dispatch(), which starts the add-on and waits for it, so no
call is dropped. Concurrent callers share one activation, and
activations run one at a time.

Startup stays the default: the host cannot tell what an older add-on
does in activate(). request-filter add-ons and add-ons that declare no
surface are forced to startup. If activate() returns a promise, calls
wait for it (at most 5 s). api.startAtLaunch(bool) lets an on-demand
add-on ask to be started at launch again (for live relay sessions).

Converted: notepad, screenshot, translate, docx-editor, pdf-editor, vpn
(none has launch-time work: no file association, no auto-connect, and
add-on file tabs are not part of the saved session). Shield and Cookie
Pop-ups stay startup: Shield owns the request filter and must see the
first request; Cookie Pop-ups costs ~6 ms and acts unasked on every
page. Aegis stays startup and untouched: another session owns it. See
NOTE-aegis-on-demand.md (next commit).

Settings › Performance › Startup:
- "Start extensions when first used" (default on). Off = all at launch;
  switching it off starts the waiting add-ons immediately.
- "Start the wallet at launch". Shown disabled with a hint until the
  installed Aegis manifest allows on-demand. It applies with no Settings
  change once Aegis opts in.
- "Preload common menus" gates prewarmOverlays().
- "Use lightest" preset.
New keys are plain SETTINGS_DEFAULTS through the existing settings-set.
No new IPC channels.

Measured: boot-trace, fresh profile, --seconds 20 so the 30 s add-on OTA
poll can't swap Aegis mid-series; warm runs 2-3 of two paired series.
- Add-on activation at launch: 121-154 ms -> 104-174 ms. The six
  converted add-ons went from 16-20 ms to 0. The rest is Shield (83-148
  ms, noisy) and Aegis (15 ms in this tree's 0.9.0).
- Toolbar painted: 1278-1584 ms -> 1282-1481 ms (within noise).
- With "Preload common menus" off: 0 overlays prewarmed, 8 processes
  instead of 11, about 50-70 MB less at 15 s.

The bundled add-on versions are not bumped. Existing profiles keep their
old addon.json, and so stay on startup activation, until those add-ons
ship with a higher version (seedBundledAddons only reseeds a strictly
newer bundle).
2026-10-03 16:05:32 +02:00
Local Dev
3cb5081bdb vpn: sing-box on both ends, modern config schema, rebuild URL from catalogue
Three bugs, all found by driving the add-on in a real Theseus and
watching the egress IP rather than reasoning about it.

1. The generated config used pre-1.11 schema. `sniff` on an inbound and
   the `block` outbound type were deprecated in sing-box 1.11 and
   REMOVED in 1.13, so 1.14.1 refused the whole file and exited 1.
   Routing is now a bare `final`; rule `action` semantics changed in
   1.12 and the explicit inbound→outbound rule was never needed.

2. xray-core 26.3.27's REALITY would not complete a handshake with a
   sing-box client — and, after ruling out keys (three derivations, a
   fresh pair used verbatim), shortIds (explicit and empty), clock skew,
   dest reachability, TLS 1.3/X25519 on the dest, and xtls-rprx-vision,
   not with a correctly configured xray client either. sing-box against
   sing-box works first try. The exits now run sing-box, which is what
   the add-on already ships to every client, so there is no longer a
   cross-implementation surface at all. Migration script included; it
   keeps the port, the SNI and the existing uuid pool and only changes
   the Reality keypair.

   Worth recording separately: xray's REALITY inbound field is `dest`,
   not sing-box's `target`. That was wrong too, independently.

3. leaseEndpoint cached the full vless URL. The Reality key and short id
   live inside that URL, so re-keying an exit left every client failing
   against a stale copy for the whole 24h lease. It now caches only the
   uuid and rebuilds the URL from the current catalogue entry, so a
   re-key takes effect as soon as the catalogue refreshes.

Verified in Theseus over CDP: baseline 80.187.100.105, tunnel up
81.31.210.65 (the sm-1 exit), off restores the baseline, and sm-3 is
correctly refused to a free-tier caller.
2026-09-27 20:58:24 +02:00
Local Dev
e31c685550 vpn: lease a credential from the key-issuer instead of expecting one in the catalogue
Found by driving the add-on in a real Theseus rather than reasoning
about it. turnOn() failed with "Silent Mode · 1 is not yet configured
(ready)" — resolveEndpoint required the catalogue entry to carry a
vless URL, but the gateway catalogue deliberately ships none, because a
vless URL is the credential and that endpoint is public. The add-on
predates the key-issuer and was never taught to ask for a lease.

It now POSTs to /api/vpn/session for any ready exit that has no URL of
its own, and caches the lease under its serverId until a minute before
expiry. Baked-in and subscription entries still use their own URL and
never hit the network.
2026-09-27 20:45:47 +02:00
Local Dev
5a17b0e866 docs(vpn): runbook for the key-issuer, and the gap it leaves open
Records what the gateway routes do, the four things that still have to
happen on the servers before the dropdown can light up (UUID pool per
inbound, the pool file, X-Forwarded-For on the vhost, deploy-from-repo),
and — deliberately at the same prominence — that expiry is bookkeeping
rather than enforcement until xray's handler API is wired per box.

Also flags the per-client byte cap as an unverified cheaper mitigation,
marked as needing a test rather than written up as though it works.
2026-09-27 19:06:58 +02:00
Local Dev
cee120cb23 vpn 0.1.3 → 0.1.4: an https:// paste imports as a subscription
The Custom box validated for a vless:// prefix and rejected anything
else, so a provider's subscription URL — the thing most people are
handed — got "paste a vless:// URL first" with no hint that the
Subscription card two sections down was what it wanted. Now an
http(s):// paste in that box is detected and routed to the subscription
importer, the placeholder says both are accepted, and the dropdown
option reads "Custom — vless:// or subscription URL".

Also renames the three bundled entries' status from "coming-soon" to
"awaiting-key-issuer". The exits exist and are running xray; what is
missing is a way to hand a client credentials without shipping a shared
secret. DESIGN.md now records why that list stays empty, since this is
the second time the shortcut looked attractive: a vless:// URL is the
credential, so writing one into the tarball (immutable, mirrored) or
onto a public Sia object (mutable but world-readable) are the same
category of mistake. Per-session minting is the fix, because then no
shared credential exists to leak.

Parser verified against plain-text, standard-base64 and url-safe
unpadded-base64 subscription bodies; an HTML error page correctly
yields zero entries instead of a JSON parse crash.
2026-09-23 23:21:51 +02:00
Local Dev
806a976647 vpn 0.1.2 → 0.1.3: subscription import + landing page mockup
Addon:
- Paste any https:// URL that returns a list of vless:// (either
  newline-separated or base64) and the extension fetches, decodes,
  parses, and adds every server to the dropdown. The full vless URL
  never leaves the panel — the addon holds it in its own storage and
  passes an opaque "sub-<hash>" id back for selection.
- Subscription CRUD on the addon side (listSubscriptions,
  addSubscription, refreshSubscription, removeSubscription). A refresh
  is a no-op inside the 6-hour TTL to avoid pounding the provider.
- Merges subscription servers with the baked-in three and gateway
  overlay by id; the dropdown groups them under one banner.

Site:
- silentmode.st/vpn landing page: three-plan grid (Free, Pro at $1/mo
  BCH, Max at $4/mo BCH), how-it-works four-step block, "the three
  servers" strip with per-tier availability, why-this-VPN cards, FAQ.
  Priced in USD, paid in BCH via the oracle at pay-time — same pattern
  as the marketplace's USD-listing covenant, no reintroduction of
  fiat/card processors.
2026-09-22 20:59:50 +02:00
Local Dev
3d955720cb vpn 0.1.1 → 0.1.2: server-list dropdown + gateway overlay
Every commercial VPN client stores its server catalog as a JSON on the
backend and lets the panel pick from a dropdown; this pulls that shape
into the extension.

- server-list.json: baked-in default the tarball ships with. Three
  Silent Mode slots (sm-1..sm-3), status "coming-soon" until the VLESS
  URLs land — the toggle stays disabled for any entry whose status is
  not "ready", so a placeholder cannot be selected by accident.
- Gateway overlay: index.js fetches
  https://navigate.st/api/vpn/servers on activation (with a 6-hour TTL
  and a "refresh" button in the panel) and merges by id — remote wins,
  new remote entries append. Cached to per-addon storage so an offline
  boot still has the last-good catalog.
- turnOn now accepts { serverId } or { vless }. Server id is resolved
  through the catalog inside the addon; the panel only sees a public
  view (label, flag, country, ready/coming-soon), never the raw URL.
- Panel: dropdown of servers + a "Custom vless://" option that reveals
  the paste box. Selection persists per-machine, refresh button forces
  a re-fetch, disabled toggle explains why in the hint area.

No behavioural change for anyone with a saved vless:// paste — that
path is now "Custom" in the dropdown and still works identically.
2026-09-22 19:56:01 +02:00
Local Dev
7724dde2ef vpn 0.1.0 → 0.1.1: pin sing-box 1.14.1 sha256s
Binaries uploaded to bns/theseus/vpn-binaries/<platform>/, served
through the gateway at navigate.st/bns/theseus.x/vpn-binaries/.
Manifest hashes match a curl-fetched copy through the gateway.
2026-09-22 00:09:18 +02:00
Local Dev
676424db87 feat(theseus/vpn): VPN extension scaffold — sing-box under our own UI
Ships the extension small (~50 KB tarball). No binaries in it — the
platform-matched sing-box is downloaded on first "Turn on" from
bns/theseus.x/vpn-binaries/<platform>/, sha256-verified against the
manifest that ships inside this operator-signed tarball, and cached
under extensions-data/vpn/bin/. Every subsequent launch re-verifies
before spawning; a mismatch redownloads rather than trusts what is on
disk.

Config generator produces a sing-box config from a vless:// URL (the
shape a 3x-UI VLESS+Reality inbound produces), plus a SOCKS5 inbound
on 127.0.0.1:<ephemeral>. api.setSessionProxy points every Theseus
request at that port while the tunnel is up; child.on("exit") clears
it if sing-box dies. Off again clears the proxy back to whatever the
browser had.

Panel is a big on/off toggle with a status pill, a paste-and-save
endpoint box, and an Advanced disclosure with "auto-on at browser
start", "re-download binary" and "clear cache". Any user with a
vless:// URL can flip it on today; the free tier and the Silent Mode
exit inbound are the server-side half, documented under DESIGN.md.

Binary manifest ships with PENDING sha256s until the binaries are
uploaded to Sia — ensureBinary refuses to activate on a platform whose
sha256 is PENDING, so a user cannot flip it on against an unverified
download.
2026-09-21 23:39:32 +02:00