2026-07-29 13:54:34 +02:00
|
|
|
|
const { contextBridge, ipcRenderer } = require("electron");
|
|
|
|
|
|
contextBridge.exposeInMainWorld("cfg", {
|
|
|
|
|
|
get: () => ipcRenderer.invoke("settings-get"),
|
|
|
|
|
|
set: (key, value) => ipcRenderer.invoke("settings-set", key, value),
|
2026-07-30 19:29:32 +02:00
|
|
|
|
engines: () => ipcRenderer.invoke("search-engines"),
|
2026-07-30 20:58:45 +02:00
|
|
|
|
addEngine: (eng) => ipcRenderer.invoke("add-engine", eng),
|
|
|
|
|
|
removeEngine: (id) => ipcRenderer.invoke("remove-engine", id),
|
2026-07-30 22:55:52 +02:00
|
|
|
|
setEngineEnabled: (id, on) => ipcRenderer.invoke("set-engine-enabled", id, on),
|
2026-07-30 23:26:00 +02:00
|
|
|
|
setEngineOrder: (ids) => ipcRenderer.invoke("set-engine-order", ids),
|
2026-08-06 01:41:31 +02:00
|
|
|
|
removeFromList: (id) => ipcRenderer.invoke("remove-from-list", id),
|
Snapshot in-progress work: Ariadne mobile, Theseus password manager, Hephaestus
Several concurrent workstreams committed together as a checkpoint:
- Ariadne mobile resolver — BchFetcher/Bns/MainActivity resolution logic,
AndroidManifest + build.ps1
- Theseus password manager — settings.html/chrome.html/settings-preload.js UI +
main.js wiring + package.json resource; Argus password-vault.js, record-picker.js
(+ tests) and resolver-web.d.ts
- Hephaestus — new BCH-wallet OIDC auth-proxy + Forgejo docker-compose and
restic/S3 scripts (secrets referenced via env only; Hephaestus/.env is gitignored)
- Argus public-gateway.mjs updates
- Docs — root README, Email README/RUNBOOK, VPS access runbooks (Checkers/Deviant),
site/hermes, WebsiteDev registry + faster-blocks, Failures/ AAAA-mangle writeup,
Decentralized Storage map, coordination notes
- .gitignore — exclude /.keys/ and Hephaestus/.env
2026-08-14 23:17:18 +02:00
|
|
|
|
// Storage: wipe browsing data on demand. Pass any subset of
|
|
|
|
|
|
// { cookies, cache, storage, history }.
|
|
|
|
|
|
clearBrowsingData: (opts) => ipcRenderer.invoke("clear-browsing-data", opts),
|
|
|
|
|
|
// Password vault. All calls return { ok, ... } | { ok: false, err }.
|
|
|
|
|
|
// Renderers never see the seed / vault key / master password past setup/
|
|
|
|
|
|
// unlock; get() returns plaintext only in explicit response to a user click.
|
|
|
|
|
|
pwStatus: () => ipcRenderer.invoke("password-status"),
|
|
|
|
|
|
pwSetup: (masterPassword, seedSource) => ipcRenderer.invoke("password-setup", { masterPassword, seedSource }),
|
|
|
|
|
|
pwUnlock: (masterPassword) => ipcRenderer.invoke("password-unlock", masterPassword),
|
|
|
|
|
|
pwLock: () => ipcRenderer.invoke("password-lock"),
|
|
|
|
|
|
pwList: () => ipcRenderer.invoke("password-list"),
|
|
|
|
|
|
pwGet: (id) => ipcRenderer.invoke("password-get", id),
|
|
|
|
|
|
pwAdd: (entry) => ipcRenderer.invoke("password-add", entry),
|
|
|
|
|
|
pwUpdate: (id, patch) => ipcRenderer.invoke("password-update", id, patch),
|
|
|
|
|
|
pwRemove: (id) => ipcRenderer.invoke("password-remove", id),
|
|
|
|
|
|
pwGenerate: (spec) => ipcRenderer.invoke("password-generate", spec),
|
Theseus: quick-unlock PIN for the vault, shared with extensions
The vault re-locks on every restart and only the master password opened
it, so every extension that needs it (Aegis, now Pithos) either asked for
the master password itself or grew its own PIN. Theseus now owns one:
- Settings > Passwords sets, changes or removes a 6-digit PIN. The PIN
wraps the master password (PBKDF2-SHA256, 600k iterations, AES-256-GCM)
and the result is sealed with the OS keystore (safeStorage: DPAPI /
Keychain / libsecret), so a copied vault-pin.json cannot be brute-forced
elsewhere. Every unlock still ends at the master password.
- Three wrong PINs in a row require the master password. The strike count
lives in the same file, so a restart does not reset it; a successful
master-password unlock does. A PIN whose password no longer opens the
vault (password changed) is dropped.
- unlock.html is Theseus's own prompt, over the whole window: PIN pad, or
the master password. Extensions call api.vault.requestUnlock({ reason })
(vault-derive capability) and get { ok } back; what the user typed never
reaches them. Settings' locked screen offers "Unlock with PIN" through
the same prompt.
2026-10-03 20:33:26 +02:00
|
|
|
|
// Quick-unlock PIN. pinSet proves the master password in main before
|
|
|
|
|
|
// wrapping it; pinUnlock opens Theseus's own PIN / password prompt.
|
|
|
|
|
|
pinStatus: () => ipcRenderer.invoke("vault-pin-status"),
|
|
|
|
|
|
pinSet: (pin, masterPassword) => ipcRenderer.invoke("vault-pin-set", { pin, masterPassword }),
|
|
|
|
|
|
pinClear: () => ipcRenderer.invoke("vault-pin-clear"),
|
|
|
|
|
|
pinUnlock: () => ipcRenderer.invoke("vault-pin-unlock"),
|
2026-08-06 01:41:31 +02:00
|
|
|
|
// Main asks settings to jump to a specific sidebar section (e.g. from the
|
|
|
|
|
|
// engine picker's "Search settings…" click). Emits the section id string.
|
|
|
|
|
|
onFocusSection: (cb) => ipcRenderer.on("focus-section", (_e, section) => cb(section)),
|
2026-08-02 11:39:00 +02:00
|
|
|
|
// Collision-mode: BCNR/ICANN policy + per-name/per-TLD overrides
|
|
|
|
|
|
collisionState: () => ipcRenderer.invoke("collision-state"),
|
|
|
|
|
|
setCollisionPolicy: (p) => ipcRenderer.invoke("collision-set-policy", p),
|
|
|
|
|
|
resetCollisions: () => ipcRenderer.invoke("collision-reset"),
|
Theseus: add-on framework MVP + Notepad reference add-on
New subsystem for extending Theseus with folders on disk. Each add-on
lives at <userData>/addons/<id>/ with an addon.json manifest and a
CommonJS entry that exports activate(api). Nothing about a private
add-on ships in the public installer - drop the folder, restart, it's
live. Bundled reference add-ons ride in the packaged app under
resources/bundled-addons/ and are seeded into <userData>/addons/ on
first boot; the framework treats seeded and drop-in add-ons the same.
Files:
- addons-host.js Loader + api.registerSidebarPanel() + per-
addon storage on <userData>/addons-data/.
Kept at the CommonJS-scoped top level (lib/
is ESM-scoped via its own package.json).
- sidebar-preload.js Runs in every sidebar panel. Exposes
window.silentmode.storage.{get,set,all} +
onVisibility. Main-side handlers derive the
add-on id from the sender file:// URL, so a
panel can only touch its own store.
- bundled-addons/notepad/ Reference add-on: addon.json, index.js,
note.html. Autosaving textarea with char /
word count.
main.js:
- Extension point: sidebar-panel. One right-anchored WebContentsView
(SIDEBAR_W=340) hosts the current panel; layout() shrinks the tab
views by the sidebar width when visible. First registered panel
wins for MVP; picker for multiple panels lands later.
- initAddons() at app.whenReady(): seedBundledAddons, then
AddonHost.discoverAndActivate.
- IPC surface: sidebar-toggle / sidebar-open / sidebar-close /
sidebar-state, addons-list / addons-set-enabled / addons-reveal /
addons-open-dir / addons-reload, and origin-gated
addon-storage-get/set/all.
- Settings gains `disabledAddons: []` — off-toggled ids persist and
the loader honours them without a restart (discoverAndActivate
runs again on toggle).
chrome.html: toolbar sidebar-toggle button, hidden until at least one
add-on has registered a sidebar panel.
settings.html: new "Add-ons" section under privacy. Lists installed
add-ons with icon / name / version / description / capabilities;
per-add-on enable/disable toggle + Show folder button; page-level
Reload and Open add-ons folder buttons; warning note about the trust
model.
package.json: build.files gains sidebar-preload.js + addons-host.js.
extraResources gains bundled-addons/ so the packaged app carries the
reference notepad for the first-boot seed.
Verified: `npm start` boots, addons-host discovers the notepad,
activates it, registers one sidebar panel. Log confirms
"1 installed, 1 enabled, 1 sidebar panels". Actual sidebar rendering
+ notepad UI need clicked-through validation on a real install.
Not shipped yet - deploy still blocked on the fail2ban VPS SSH ban.
Ships as 0.2.0 once SSH clears (this is a new subsystem, not a fix).
2026-08-31 13:51:08 +02:00
|
|
|
|
// Add-ons management (Settings > Add-ons tab).
|
|
|
|
|
|
listAddons: () => ipcRenderer.invoke("addons-list"),
|
|
|
|
|
|
setAddonEnabled: (id, enabled) => ipcRenderer.invoke("addons-set-enabled", id, !!enabled),
|
|
|
|
|
|
revealAddon: (folder) => ipcRenderer.invoke("addons-reveal", folder),
|
feat(theseus/settings): Extensions as compact rows with a detail view
The Extensions list was a stack of tall cards — description, author,
capabilities and buttons on every one — so seven add-ons filled the
page before the user found the toggle. Rows are now one line each,
Firefox-style: icon, name, built-in badge, version, a short update
status, the on/off switch and a ⋯ menu, grouped Enabled / Disabled /
Failed to load. Clicking a row opens the detail view in place: back
arrow, description, update status, author, version, type, folder with
Show folder, and a Permissions block that explains each declared
capability in plain words. The ⋯ menu (and right-click) offers Turn
on/off, Details, Show folder and, for non-bundled extensions, Remove —
a new addons-remove IPC that deletes the folder under the extensions
directory, refuses bundled add-ons (they would only be reseeded), and
clears the dock prefs it left behind.
2026-09-22 20:43:50 +02:00
|
|
|
|
// Delete a non-bundled extension's folder (Settings › Extensions › ⋯ › Remove).
|
|
|
|
|
|
removeAddon: (id) => ipcRenderer.invoke("addons-remove", id),
|
2026-09-30 02:16:11 +02:00
|
|
|
|
openAddonsDir: () => ipcRenderer.invoke("addons-open-dir"),
|
|
|
|
|
|
reloadAddons: () => ipcRenderer.invoke("addons-reload"),
|
|
|
|
|
|
// Add-on update flow. checkAddonUpdates hits the release manifest and
|
|
|
|
|
|
// stages any newer signed version; listStagedAddonUpdates reports what's
|
|
|
|
|
|
// waiting; applyStagedAddons promotes staged → active and reactivates the
|
|
|
|
|
|
// addon host so the new bytes load without a full Theseus restart.
|
|
|
|
|
|
// Community extensions from theseus.x/extensions: the catalog (with what
|
|
|
|
|
|
// is installed already) and a verified install/update of one entry.
|
|
|
|
|
|
communityCatalog: () => ipcRenderer.invoke("addons-community-catalog"),
|
|
|
|
|
|
installCommunity: (id) => ipcRenderer.invoke("addons-install-community", id),
|
|
|
|
|
|
checkAddonUpdates: () => ipcRenderer.invoke("addons-check-updates"),
|
|
|
|
|
|
listStagedAddonUpdates: () => ipcRenderer.invoke("addons-list-staged"),
|
fix(theseus): extension updates re-check, announce and install in place
Updates published after launch never showed: the add-on channel was
checked once, 30 s after boot, and staged copies only applied on the
next launch with nothing telling the user. On 2026-09-22 Aegis 0.8.3
and VPN 0.1.3 landed minutes after the app's only check and stayed
invisible through manual scans made earlier and a restart made before
they were published.
Now: the check repeats every 4 hours; every check (boot, timer, manual,
add-on-driven) reports what is staged to the chrome, which shows a chip
for staged extensions; clicking it, or the "Update to vX" button that
appears on the extension's row and detail in Settings, promotes the
staged folder over the installed one and rebuilds the add-on host, so
the new version runs without a restart. Plug-ins (Aegis) are excluded
from the chip and the hot swap — a wallet updates from its own panel
and applies on the next launch.
Also from the same review: the new-tab button follows the last tab and
parks after the scroll arrow only when the strip overflows; Tor sits
left of the Aegis chip; plug-ins no longer appear in Settings ›
Extensions (they have Plug-ins); the extension detail view has a
labelled Back button, a close button and a Check now button; the
promotion helper returns what it promoted and accepts a filter.
2026-09-22 21:25:33 +02:00
|
|
|
|
applyStagedAddons: (id) => ipcRenderer.invoke("addons-apply-staged", typeof id === "string" ? id : undefined),
|
2026-09-27 20:37:30 +02:00
|
|
|
|
// Settings › Performance › Protections: talk to an add-on's own message
|
|
|
|
|
|
// handlers (Shield, Cookie Pop-ups) and open its panel for the details.
|
|
|
|
|
|
addonInvoke: (id, msg, payload) => ipcRenderer.invoke("addon-invoke", String(id || ""), String(msg || ""), payload),
|
|
|
|
|
|
openPanel: (panelId) => ipcRenderer.invoke("settings-open-panel", String(panelId || "")),
|
2026-09-27 22:01:28 +02:00
|
|
|
|
// Which page is showing (the address bar follows), Tor state + toggle for Privacy › Network.
|
|
|
|
|
|
reportSection: (slug) => ipcRenderer.invoke("settings-section", String(slug || "")),
|
|
|
|
|
|
torState: () => ipcRenderer.invoke("tor-state"),
|
|
|
|
|
|
toggleTor: () => ipcRenderer.invoke("toggle-tor"),
|
2026-09-30 02:16:11 +02:00
|
|
|
|
// OS locale — used by the Website-language row to label "Automatic (OS: …)".
|
|
|
|
|
|
systemLocale: () => ipcRenderer.invoke("system-locale"),
|
|
|
|
|
|
// Live settings updates — the toolbar chip, this page's Website-language
|
|
|
|
|
|
// row, and the Anti-fingerprinting Language row all edit the same setting;
|
|
|
|
|
|
// any of them writing pushes a "settings-update" the others react to.
|
|
|
|
|
|
onSettingsUpdate: (cb) => ipcRenderer.on("settings-update", (_e, d) => cb(d)),
|
|
|
|
|
|
// Ariadne's Thread plug-in (system-wide resolver). The state getter returns
|
|
|
|
|
|
// { state:"running"|"stopped"|"not-installed", installedVersion, bundledVersion,
|
|
|
|
|
|
// canUpdate, hasUninstaller }; the mutators prompt UAC for admin.
|
|
|
|
|
|
ariadneState: () => ipcRenderer.invoke("ariadne-state"),
|
|
|
|
|
|
ariadneToggle: (on) => ipcRenderer.invoke("ariadne-toggle", !!on),
|
|
|
|
|
|
ariadneInstall: () => ipcRenderer.invoke("ariadne-install"),
|
|
|
|
|
|
ariadneUpdate: () => ipcRenderer.invoke("ariadne-update"),
|
|
|
|
|
|
ariadneUninstall: () => ipcRenderer.invoke("ariadne-uninstall"),
|
feat(theseus/ariadne): settings panel — policy + per-source toggles + status report
Ariadne 0.1.13 exposed /api/status and per-source enable flags in
policy.json. Theseus's Plug-ins > Ariadne's Thread sub-page now wires those
into a full UI, no daemon restart, no UAC.
Added to the plugins-ariadne sub-page (after Status, before Remove):
Collision policy -- radio group (BCNR-first / ICANN-first) writes
C:\ProgramData\Ariadne\policy.json.policy; hot-reloaded
by the daemon within 5 s.
Sources -- 3-column grid, one row per source (snapshotHttps,
electrumWss, perQueryLookup, diskCache, localApi):
enable checkbox + last-state summary
(last success / last error / hit-miss counters /
disk-cache size+mtime). Toggle writes
policy.json.sources.<name>.enabled and re-polls after
the 5-s hot-reload tick so the state text catches up.
Status report -- <pre> JSON dump of GET http://127.0.0.1/api/status
with Copy report + Refresh report buttons. This is
the paste-me-into-support artefact for any diagnosis.
IPC wiring:
main.js
ariadne-get-status -> GET http://127.0.0.1/api/status ({ok, status|error})
ariadne-get-policy -> read C:\ProgramData\Ariadne\policy.json (or {})
ariadne-set-policy -> merge {policy}, write back (validates enum)
ariadne-set-source -> merge {sources.<name>.enabled}, write back
(validates against the known 5 names)
settings-preload.js
ariadneGetStatus, ariadneGetPolicy, ariadneSetPolicy, ariadneSetSource
All four handlers write policy.json as the local user; no UAC. Works because
install.ps1 grants BUILTIN\Users Modify on the file (0.1.7+).
Sub-page auto-refreshes state every time it opens (listens on the existing
'section' custom event dispatched by showSection).
Not building/shipping Theseus here -- this rides the next Theseus release.
Panel gracefully handles: daemon down (shows 'Daemon unreachable' with a
pointer to the Status toggle), localApi disabled (daemon returns 503, panel
shows the error), missing policy.json (all sources default to true).
2026-10-01 00:51:50 +02:00
|
|
|
|
// Local BNS daemon settings + status (0.1.13+). All read/write against
|
|
|
|
|
|
// C:\ProgramData\Ariadne\policy.json (user-writable ACL) and the daemon's
|
|
|
|
|
|
// own /api/status endpoint on 127.0.0.1. No UAC required for any of these.
|
|
|
|
|
|
ariadneGetStatus: () => ipcRenderer.invoke("ariadne-get-status"),
|
|
|
|
|
|
ariadneGetPolicy: () => ipcRenderer.invoke("ariadne-get-policy"),
|
|
|
|
|
|
ariadneSetPolicy: (policy) => ipcRenderer.invoke("ariadne-set-policy", String(policy || "")),
|
|
|
|
|
|
ariadneSetSource: (name, enabled) => ipcRenderer.invoke("ariadne-set-source", String(name || ""), !!enabled),
|
2026-09-30 02:16:11 +02:00
|
|
|
|
// Manual "Check for updates" — un-dismisses any existing chip and re-
|
|
|
|
|
|
// fetches the release manifest. Returns { updateAvailable, currentVersion }.
|
|
|
|
|
|
recheckUpdate: () => ipcRenderer.invoke("recheck-update"),
|
|
|
|
|
|
appVersion: () => ipcRenderer.invoke("app-version"),
|
|
|
|
|
|
restartApp: () => ipcRenderer.invoke("app-restart"),
|
2026-07-29 13:54:34 +02:00
|
|
|
|
});
|