Theseus 0.3.60: pick your browsing language from the URL bar; per plug-in settings
A globe chip next to the URL-bar star shows the language sites see you in (Accept-Language + navigator.language) — "AUTO" while following the OS locale, the two-letter code once you pin one. Click opens a 23-language menu; the same setting has a friendly row at the top of Settings › General. Both write to the existing languageMode/languageValue and stay in sync with the Anti-fingerprinting Language row through a settings-update broadcast (settings.html and chrome.html both react live). Settings › Plug-ins is now two compact rows — one per plug-in — with the on/off toggle on the right and the update controls beside it. Clicking a plug-in's title opens its own sub-page (plugins/ariadne, plugins/aegis) with the full description and the Uninstall button, so the main list stays scannable and dangerous actions stop travelling with the everyday ones. The Ariadne toggle and its sub-page mirror the same scheduled-task state.
This commit is contained in:
parent
13e4c6997c
commit
df2b1f57ff
6 changed files with 521 additions and 180 deletions
58
chrome.html
58
chrome.html
|
|
@ -187,6 +187,20 @@
|
||||||
display: grid; place-items: center; line-height: 1; box-shadow: 0 0 0 2px var(--bg); }
|
display: grid; place-items: center; line-height: 1; box-shadow: 0 0 0 2px var(--bg); }
|
||||||
.dlbtn.done .dlbadge { background: #4fd1a5; }
|
.dlbtn.done .dlbadge { background: #4fd1a5; }
|
||||||
.dlbtn.err .dlbadge { background: #f6768a; }
|
.dlbtn.err .dlbadge { background: #f6768a; }
|
||||||
|
/* Website-language chip inside the address bar (Firefox-style, next to the
|
||||||
|
bookmark star). Two-letter uppercase code shows the language sites see us
|
||||||
|
in — Accept-Language + navigator.language. Same setting the Anti-
|
||||||
|
fingerprinting Language row edits; both stay in sync via settings-update. */
|
||||||
|
.langchip { flex: none; display: inline-flex; align-items: center; gap: 3px;
|
||||||
|
height: 24px; padding: 0 5px 0 4px; border: none; border-radius: 6px;
|
||||||
|
background: transparent; cursor: pointer; color: var(--dim);
|
||||||
|
font: 600 10.5px/1 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
||||||
|
letter-spacing: .04em; }
|
||||||
|
.langchip:hover { background: var(--line2); color: var(--ink); }
|
||||||
|
.langchip.on { color: var(--acid); }
|
||||||
|
.langchip svg { width: 15px; height: 15px; fill: none; stroke: currentColor;
|
||||||
|
stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
|
||||||
|
.langchip .lcode { min-width: 16px; text-align: center; }
|
||||||
/* indeterminate loading bar under the toolbar — collapses when idle so it
|
/* indeterminate loading bar under the toolbar — collapses when idle so it
|
||||||
doesn't take a permanent 2px strip below the address bar */
|
doesn't take a permanent 2px strip below the address bar */
|
||||||
.loadbar { height: 0; overflow: hidden; background: transparent; transition: height .12s linear; }
|
.loadbar { height: 0; overflow: hidden; background: transparent; transition: height .12s linear; }
|
||||||
|
|
@ -286,7 +300,7 @@
|
||||||
its width and pushed the registry button and the star out past the
|
its width and pushed the registry button and the star out past the
|
||||||
bar's right edge. */
|
bar's right edge. */
|
||||||
#url { flex: 1 1 0; min-width: 0; }
|
#url { flex: 1 1 0; min-width: 0; }
|
||||||
.secbadge, .pwchip, .star, .regbtn, .zoomchip { flex: none; }
|
.secbadge, .pwchip, .star, .regbtn, .zoomchip, .langchip { flex: none; }
|
||||||
/* Zoom indicator — only shown when the active tab isn't at 100 %.
|
/* Zoom indicator — only shown when the active tab isn't at 100 %.
|
||||||
Click resets (Ctrl+0); Ctrl +/- and Ctrl+wheel step the level. */
|
Click resets (Ctrl+0); Ctrl +/- and Ctrl+wheel step the level. */
|
||||||
.zoomchip { border: none; background: transparent; color: var(--mut); cursor: pointer; padding: 2px 5px;
|
.zoomchip { border: none; background: transparent; color: var(--mut); cursor: pointer; padding: 2px 5px;
|
||||||
|
|
@ -490,6 +504,13 @@
|
||||||
makes it installable (Chrome's omnibox icon). Filled once the
|
makes it installable (Chrome's omnibox icon). Filled once the
|
||||||
app is installed; click then opens it or removes it. -->
|
app is installed; click then opens it or removes it. -->
|
||||||
<button class="appchip" id="appchip" title="Install app" hidden><svg viewBox="0 0 16 16" aria-hidden="true"><rect x="2" y="2.5" width="12" height="9" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M8 5 V9.5 M6 7.8 L8 9.8 L10 7.8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M5.5 14 H10.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></button>
|
<button class="appchip" id="appchip" title="Install app" hidden><svg viewBox="0 0 16 16" aria-hidden="true"><rect x="2" y="2.5" width="12" height="9" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M8 5 V9.5 M6 7.8 L8 9.8 L10 7.8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M5.5 14 H10.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></button>
|
||||||
|
<!-- Website-language chip: click to change the language sites serve
|
||||||
|
you in. Icon = globe with meridian + equator (universal "language"
|
||||||
|
glyph); label = 2-letter code, or "AUTO" when we follow the OS. -->
|
||||||
|
<button class="langchip" id="langBtn" title="Language websites serve you in">
|
||||||
|
<svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="6"/><path d="M2 8 H14"/><path d="M8 2 C5 5 5 11 8 14 C11 11 11 5 8 2"/></svg>
|
||||||
|
<span class="lcode" id="langCode">AUTO</span>
|
||||||
|
</button>
|
||||||
<button class="star" id="star" title="Save this page">☆</button>
|
<button class="star" id="star" title="Save this page">☆</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="bardrag" id="bardrag" title="Drag to shift the address / search bar ratio"></div>
|
<div class="bardrag" id="bardrag" title="Drag to shift the address / search bar ratio"></div>
|
||||||
|
|
@ -694,6 +715,41 @@
|
||||||
T.getDownloads && T.getDownloads().then(renderDownloads);
|
T.getDownloads && T.getDownloads().then(renderDownloads);
|
||||||
T.onDownloads && T.onDownloads(renderDownloads);
|
T.onDownloads && T.onDownloads(renderDownloads);
|
||||||
|
|
||||||
|
// ---- website-language pill: shows the language sites see us in ----
|
||||||
|
// "AUTO" (2-letter tone) when following the OS locale; a two-letter
|
||||||
|
// uppercase code (e.g. "DE", "PT") when a specific language is pinned.
|
||||||
|
// Same setting the Anti-fingerprinting Language row edits; both stay in
|
||||||
|
// sync via T.onSettingsUpdate below.
|
||||||
|
const langBtn = $("langBtn"), langCode = $("langCode");
|
||||||
|
let systemLocaleCached = "en-US";
|
||||||
|
function short(tag) { return String(tag || "").split("-")[0].toUpperCase().slice(0, 2) || "??"; }
|
||||||
|
function renderLang(s) {
|
||||||
|
if (!s) return;
|
||||||
|
const auto = (s.languageMode || "show") === "show";
|
||||||
|
if (auto) {
|
||||||
|
langBtn.classList.remove("on");
|
||||||
|
langCode.textContent = "AUTO";
|
||||||
|
langBtn.title = `Website language: automatic (OS: ${systemLocaleCached}). Click to change.`;
|
||||||
|
} else {
|
||||||
|
const tag = (s.languageMode === "manual" && s.languageValue) ? s.languageValue
|
||||||
|
: (s.languageMode === "spoof" && s.languageSpoof) ? s.languageSpoof
|
||||||
|
: (s.languageMode === "hide") ? "en-US" : systemLocaleCached;
|
||||||
|
langBtn.classList.add("on");
|
||||||
|
langCode.textContent = short(tag);
|
||||||
|
langBtn.title = `Websites see you in ${tag}. Click to change.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
langBtn.onclick = () => {
|
||||||
|
const r = langBtn.getBoundingClientRect();
|
||||||
|
T.websiteLanguageMenu && T.websiteLanguageMenu({ x: Math.round(r.left), y: Math.round(r.bottom + 4) });
|
||||||
|
};
|
||||||
|
// Locale is optional (older main.js won't expose it) — render on settings
|
||||||
|
// either way so the chip works even without it.
|
||||||
|
(T.systemLocale ? T.systemLocale() : Promise.resolve("en-US"))
|
||||||
|
.then((loc) => { systemLocaleCached = loc || "en-US"; return T.getSettings(); })
|
||||||
|
.then(renderLang).catch(() => {});
|
||||||
|
T.onSettingsUpdate && T.onSettingsUpdate(renderLang);
|
||||||
|
|
||||||
// ---- extension dock ----
|
// ---- extension dock ----
|
||||||
// One toolbar button per registered addon sidebar-panel, plus a static
|
// One toolbar button per registered addon sidebar-panel, plus a static
|
||||||
// "coming soon" placeholder for Aegis (the built-in BCH wallet, in
|
// "coming soon" placeholder for Aegis (the built-in BCH wallet, in
|
||||||
|
|
|
||||||
66
main.js
66
main.js
|
|
@ -4045,6 +4045,72 @@ ipcMain.handle("tab-context-menu-popup", (e, tabId, rect) => {
|
||||||
popup.popup({ window: win, x, y });
|
popup.popup({ window: win, x, y });
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
// Website-language quick switch — a native menu anchored under the toolbar
|
||||||
|
// pill. Same setting the Anti-fingerprinting Language row edits: "auto"
|
||||||
|
// (languageMode="show", follow OS) or a specific BCP-47 tag
|
||||||
|
// (languageMode="manual", languageValue=<tag>). One truth source, two entry
|
||||||
|
// points. Applied via applyAcceptLanguage() + applyFingerprintAll() below.
|
||||||
|
const WEBSITE_LANGUAGE_QUICK = [
|
||||||
|
{ tag: "en-US", label: "English (US)" },
|
||||||
|
{ tag: "en-GB", label: "English (UK)" },
|
||||||
|
{ tag: "es-ES", label: "Español" },
|
||||||
|
{ tag: "pt-BR", label: "Português (Brasil)" },
|
||||||
|
{ tag: "fr-FR", label: "Français" },
|
||||||
|
{ tag: "de-DE", label: "Deutsch" },
|
||||||
|
{ tag: "it-IT", label: "Italiano" },
|
||||||
|
{ tag: "nl-NL", label: "Nederlands" },
|
||||||
|
{ tag: "ru-RU", label: "Русский" },
|
||||||
|
{ tag: "uk-UA", label: "Українська" },
|
||||||
|
{ tag: "pl-PL", label: "Polski" },
|
||||||
|
{ tag: "tr-TR", label: "Türkçe" },
|
||||||
|
{ tag: "ar", label: "العربية" },
|
||||||
|
{ tag: "he-IL", label: "עברית" },
|
||||||
|
{ tag: "el-GR", label: "Ελληνικά" },
|
||||||
|
{ tag: "zh-CN", label: "中文(简体)" },
|
||||||
|
{ tag: "zh-TW", label: "中文(繁體)" },
|
||||||
|
{ tag: "ja-JP", label: "日本語" },
|
||||||
|
{ tag: "ko-KR", label: "한국어" },
|
||||||
|
{ tag: "hi-IN", label: "हिन्दी" },
|
||||||
|
{ tag: "vi-VN", label: "Tiếng Việt" },
|
||||||
|
{ tag: "th-TH", label: "ไทย" },
|
||||||
|
{ tag: "id-ID", label: "Bahasa Indonesia" },
|
||||||
|
];
|
||||||
|
function setWebsiteLanguage(mode, value) {
|
||||||
|
if (mode === "show") {
|
||||||
|
settings.languageMode = "show";
|
||||||
|
} else if (mode === "manual" && value) {
|
||||||
|
settings.languageMode = "manual";
|
||||||
|
settings.languageValue = String(value);
|
||||||
|
} else return;
|
||||||
|
saveSettings();
|
||||||
|
applyFingerprintAll();
|
||||||
|
applyAcceptLanguage();
|
||||||
|
try { chrome?.webContents.send("settings-update", settings); } catch {}
|
||||||
|
}
|
||||||
|
ipcMain.handle("system-locale", () => app.getLocale() || "en-US");
|
||||||
|
ipcMain.handle("website-language-menu-popup", (e, rect) => {
|
||||||
|
if (chrome && e.sender !== chrome.webContents) throw new Error("website-language-menu-popup: untrusted sender");
|
||||||
|
const osLoc = app.getLocale() || "en-US";
|
||||||
|
const isAuto = settings.languageMode === "show";
|
||||||
|
const current = isAuto ? null : (settings.languageValue || "").toLowerCase();
|
||||||
|
const template = [
|
||||||
|
{ label: `Automatic (OS: ${osLoc})${isAuto ? " ✓" : ""}`,
|
||||||
|
click: () => setWebsiteLanguage("show") },
|
||||||
|
{ type: "separator" },
|
||||||
|
...WEBSITE_LANGUAGE_QUICK.map((L) => ({
|
||||||
|
label: `${L.label} — ${L.tag}${current === L.tag.toLowerCase() ? " ✓" : ""}`,
|
||||||
|
click: () => setWebsiteLanguage("manual", L.tag),
|
||||||
|
})),
|
||||||
|
{ type: "separator" },
|
||||||
|
{ label: "More languages…", click: () => { try { openSettingsTab("general"); } catch {} } },
|
||||||
|
];
|
||||||
|
const popup = Menu.buildFromTemplate(template);
|
||||||
|
const chromeBounds = chrome ? chrome.getBounds() : { x: 0, y: 0 };
|
||||||
|
const x = Math.max(0, Math.round(chromeBounds.x + (rect?.x || 0)));
|
||||||
|
const y = Math.max(0, Math.round(chromeBounds.y + (rect?.y || 0)));
|
||||||
|
popup.popup({ window: win, x, y });
|
||||||
|
return true;
|
||||||
|
});
|
||||||
ipcMain.handle("move-tab", (_e, id, targetId, place) => {
|
ipcMain.handle("move-tab", (_e, id, targetId, place) => {
|
||||||
const src = tabs.findIndex((t) => t.id === id);
|
const src = tabs.findIndex((t) => t.id === id);
|
||||||
const dst = tabs.findIndex((t) => t.id === targetId);
|
const dst = tabs.findIndex((t) => t.id === targetId);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "theseus-navigator",
|
"name": "theseus-navigator",
|
||||||
"version": "0.3.59",
|
"version": "0.3.60",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"description": "Theseus Navigator — a browser that follows the thread. By Silent Mode, a Deviant project.",
|
"description": "Theseus Navigator — a browser that follows the thread. By Silent Mode, a Deviant project.",
|
||||||
"author": "Silent Mode",
|
"author": "Silent Mode",
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ contextBridge.exposeInMainWorld("theseus", {
|
||||||
getSettings: () => ipcRenderer.invoke("settings-get"),
|
getSettings: () => ipcRenderer.invoke("settings-get"),
|
||||||
setSetting: (key, val) => ipcRenderer.invoke("settings-set", key, val),
|
setSetting: (key, val) => ipcRenderer.invoke("settings-set", key, val),
|
||||||
onSettingsUpdate: (cb) => ipcRenderer.on("settings-update", (_e, d) => cb(d)),
|
onSettingsUpdate: (cb) => ipcRenderer.on("settings-update", (_e, d) => cb(d)),
|
||||||
|
systemLocale: () => ipcRenderer.invoke("system-locale"),
|
||||||
|
websiteLanguageMenu: (rect) => ipcRenderer.invoke("website-language-menu-popup", rect),
|
||||||
// Find-in-page. main.js fires 'find-open' on Ctrl+F; chrome renderer
|
// Find-in-page. main.js fires 'find-open' on Ctrl+F; chrome renderer
|
||||||
// owns the bar UI and drives findInPage / stopFindInPage via these
|
// owns the bar UI and drives findInPage / stopFindInPage via these
|
||||||
// wrappers. Match count / active ordinal comes back through onFindResult.
|
// wrappers. Match count / active ordinal comes back through onFindResult.
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,42 @@ contextBridge.exposeInMainWorld("cfg", {
|
||||||
collisionState: () => ipcRenderer.invoke("collision-state"),
|
collisionState: () => ipcRenderer.invoke("collision-state"),
|
||||||
setCollisionPolicy: (p) => ipcRenderer.invoke("collision-set-policy", p),
|
setCollisionPolicy: (p) => ipcRenderer.invoke("collision-set-policy", p),
|
||||||
resetCollisions: () => ipcRenderer.invoke("collision-reset"),
|
resetCollisions: () => ipcRenderer.invoke("collision-reset"),
|
||||||
|
// 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),
|
||||||
|
// Delete a non-bundled extension's folder (Settings › Extensions › ⋯ › Remove).
|
||||||
|
removeAddon: (id) => ipcRenderer.invoke("addons-remove", id),
|
||||||
|
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"),
|
||||||
|
applyStagedAddons: (id) => ipcRenderer.invoke("addons-apply-staged", typeof id === "string" ? id : undefined),
|
||||||
|
// 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 || "")),
|
||||||
|
// 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"),
|
||||||
|
// 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"),
|
ariadneState: () => ipcRenderer.invoke("ariadne-state"),
|
||||||
ariadneToggle: (on) => ipcRenderer.invoke("ariadne-toggle", !!on),
|
ariadneToggle: (on) => ipcRenderer.invoke("ariadne-toggle", !!on),
|
||||||
ariadneInstall: () => ipcRenderer.invoke("ariadne-install"),
|
ariadneInstall: () => ipcRenderer.invoke("ariadne-install"),
|
||||||
|
|
@ -41,28 +77,4 @@ contextBridge.exposeInMainWorld("cfg", {
|
||||||
recheckUpdate: () => ipcRenderer.invoke("recheck-update"),
|
recheckUpdate: () => ipcRenderer.invoke("recheck-update"),
|
||||||
appVersion: () => ipcRenderer.invoke("app-version"),
|
appVersion: () => ipcRenderer.invoke("app-version"),
|
||||||
restartApp: () => ipcRenderer.invoke("app-restart"),
|
restartApp: () => ipcRenderer.invoke("app-restart"),
|
||||||
// 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),
|
|
||||||
// Delete a non-bundled extension's folder (Settings › Extensions › ⋯ › Remove).
|
|
||||||
removeAddon: (id) => ipcRenderer.invoke("addons-remove", id),
|
|
||||||
// Install a staged signed update now (Settings › Extensions › "Update to vX"); no id = every staged extension.
|
|
||||||
applyStagedAddons: (id) => ipcRenderer.invoke("addons-apply-staged", typeof id === "string" ? id : undefined),
|
|
||||||
// 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 || "")),
|
|
||||||
// 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"),
|
|
||||||
openAddonsDir: () => ipcRenderer.invoke("addons-open-dir"),
|
|
||||||
reloadAddons: () => ipcRenderer.invoke("addons-reload"),
|
|
||||||
// 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"),
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
417
settings.html
417
settings.html
|
|
@ -42,6 +42,16 @@
|
||||||
.srow::before{content:"";width:9px;height:9px;border-radius:50%;background:var(--dim);flex:none}
|
.srow::before{content:"";width:9px;height:9px;border-radius:50%;background:var(--dim);flex:none}
|
||||||
.srow.ok::before{background:#3ddc84}.srow.warn::before{background:#f5a524}
|
.srow.ok::before{background:#3ddc84}.srow.warn::before{background:#f5a524}
|
||||||
.row.link{cursor:pointer}.row.link:hover{border-color:rgb(from var(--acid) r g b / .4)}.row .chev{color:var(--dim);font-size:20px;line-height:1}
|
.row.link{cursor:pointer}.row.link:hover{border-color:rgb(from var(--acid) r g b / .4)}.row .chev{color:var(--dim);font-size:20px;line-height:1}
|
||||||
|
/* Plug-in main-page row: the .txt area is a click target that navigates to
|
||||||
|
the plug-in's own settings sub-page (chevron on the title); .plug-ctl on
|
||||||
|
the right hosts the update button + on/off toggle, and stops the click
|
||||||
|
from bubbling so they don't navigate. */
|
||||||
|
.plug-row .plug-open{cursor:pointer;border-radius:8px;padding:2px 6px;margin:-2px -6px;transition:background .12s}
|
||||||
|
.plug-row .plug-open:hover{background:rgba(255,255,255,.04)}
|
||||||
|
.plug-row .plug-open:focus-visible{outline:2px solid rgb(from var(--acid) r g b / .5);outline-offset:2px}
|
||||||
|
.plug-row .plug-open .chev{color:var(--dim);font-size:15px;line-height:1;margin-left:4px;opacity:.6}
|
||||||
|
.plug-row .plug-open:hover .chev{opacity:1;color:var(--acid-text)}
|
||||||
|
.plug-row .plug-ctl{display:flex;align-items:center;gap:8px;flex:none;flex-wrap:wrap;justify-content:flex-end;max-width:60%}
|
||||||
.exlist{display:flex;flex-direction:column;gap:6px;margin-bottom:14px}
|
.exlist{display:flex;flex-direction:column;gap:6px;margin-bottom:14px}
|
||||||
.exrow{display:flex;align-items:center;justify-content:space-between;gap:12px;background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:9px 14px;font:13px ui-monospace,"Cascadia Code",Consolas,monospace}
|
.exrow{display:flex;align-items:center;justify-content:space-between;gap:12px;background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:9px 14px;font:13px ui-monospace,"Cascadia Code",Consolas,monospace}
|
||||||
.exempty{color:var(--dim);font-size:13px;margin:0 0 14px}
|
.exempty{color:var(--dim);font-size:13px;margin:0 0 14px}
|
||||||
|
|
@ -241,7 +251,16 @@
|
||||||
<section id="general">
|
<section id="general">
|
||||||
<h1>General</h1>
|
<h1>General</h1>
|
||||||
<p class="lede">Changes apply immediately and are saved for next time.</p>
|
<p class="lede">Changes apply immediately and are saved for next time.</p>
|
||||||
<h2 class="sub" style="border-top:0;padding-top:0;margin-top:0">Startup</h2>
|
<h2 class="sub" style="border-top:0;padding-top:0;margin-top:0">Website language</h2>
|
||||||
|
<p class="subd">The language sites serve you in — Wikipedia, YouTube, news sites, and anything else that has a localised version. Sent as your <code>Accept-Language</code> header and <code>navigator.language</code>. <b>Automatic</b> follows your operating system; pick a language to override it. The same setting lives under <b>Privacy → Anti-fingerprinting → Language</b> for advanced modes (Spoof, Hide).</p>
|
||||||
|
<div class="row">
|
||||||
|
<div class="txt"><div class="t">Preferred language</div><div class="d" id="webLangHint">Also switchable from the globe icon in the address bar.</div></div>
|
||||||
|
<div class="ctl">
|
||||||
|
<select id="webLangPick"></select>
|
||||||
|
<input id="webLangOther" type="text" placeholder="BCP-47, e.g. cs-CZ" hidden style="width:150px">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h2 class="sub">Startup</h2>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="txt"><div class="t">Open previous windows and tabs</div><div class="d">Restore the tabs from your last session when Theseus starts.</div></div>
|
<div class="txt"><div class="t">Open previous windows and tabs</div><div class="d">Restore the tabs from your last session when Theseus starts.</div></div>
|
||||||
<label class="sw"><input type="checkbox" id="restoreSession"><span class="track"><span class="knob"></span></span></label>
|
<label class="sw"><input type="checkbox" id="restoreSession"><span class="track"><span class="knob"></span></span></label>
|
||||||
|
|
@ -309,51 +328,106 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<!-- PLUG-INS -->
|
<!-- PLUG-INS -->
|
||||||
|
<!-- Compact main page: one row per plug-in with title / status / update
|
||||||
|
controls on the left and the on/off toggle on the right. Clicking
|
||||||
|
the TITLE opens that plug-in's own settings sub-page (details +
|
||||||
|
Uninstall). Toggle and buttons stopPropagation so they don't
|
||||||
|
navigate. -->
|
||||||
<section id="plugins" hidden>
|
<section id="plugins" hidden>
|
||||||
<h1>Plug-ins</h1>
|
<h1>Plug-ins</h1>
|
||||||
<p class="lede">Silent Mode components that live alongside Theseus — the system-wide resolver and the built-in wallet. Each ships with its own install / update / on-off controls.</p>
|
<p class="lede">Silent Mode components that live alongside Theseus — each with its own on/off, updates and settings page.</p>
|
||||||
<div class="row" style="justify-content:flex-end;gap:8px"><a class="btn" style="text-decoration:none;display:inline-flex;align-items:center" href="https://theseus.x/plug-ins/" target="_blank" rel="noopener" title="Current signed versions, hashes and how plug-ins update">About plug-ins on theseus.x ↗</a></div>
|
<div class="row" style="justify-content:flex-end;gap:8px"><a class="btn" style="text-decoration:none;display:inline-flex;align-items:center" href="https://theseus.x/plug-ins/" target="_blank" rel="noopener" title="Current signed versions, hashes and how plug-ins update">About plug-ins on theseus.x ↗</a></div>
|
||||||
<div class="row" style="flex-direction:column;align-items:stretch;gap:8px">
|
|
||||||
|
<!-- Ariadne's Thread -->
|
||||||
|
<div class="row plug-row">
|
||||||
|
<div class="txt plug-open" data-go="plugins/ariadne" role="button" tabindex="0" title="Open Ariadne's Thread settings">
|
||||||
|
<div class="t">Ariadne's Thread <span class="pmuted" style="font-weight:400">— system-wide resolver</span> <span class="chev">›</span></div>
|
||||||
|
<div class="d">Resolves BCDN names for every browser on this machine — not just Theseus.</div>
|
||||||
|
<div id="ariadneStatusMain" class="pmuted" style="font-size:12.5px;margin-top:4px">checking…</div>
|
||||||
|
</div>
|
||||||
|
<div class="plug-ctl">
|
||||||
|
<button id="ariadneInstallMain" class="btn small" hidden>Install</button>
|
||||||
|
<button id="ariadneUpdateMain" class="btn small" hidden>Update</button>
|
||||||
|
<button id="ariadneCheckMain" class="btn small" title="Refresh Ariadne status">Refresh</button>
|
||||||
|
<label class="sw" id="ariadneSwWrap" title="Turn Ariadne on/off">
|
||||||
|
<input type="checkbox" id="ariadneToggleMain">
|
||||||
|
<span class="track"><span class="knob"></span></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Aegis -->
|
||||||
|
<div class="row plug-row">
|
||||||
|
<div class="txt plug-open" data-go="plugins/aegis" role="button" tabindex="0" title="Open Aegis settings">
|
||||||
|
<div class="t">Aegis <span class="pmuted" style="font-weight:400">— built-in wallet</span> <span class="chev">›</span></div>
|
||||||
|
<div class="d">Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB). Ships in the box, updates over-the-air.</div>
|
||||||
|
<div id="aegisStatusMain" class="pmuted" style="font-size:12.5px;margin-top:4px">loading…</div>
|
||||||
|
</div>
|
||||||
|
<div class="plug-ctl">
|
||||||
|
<button id="aegisCheckMain" class="btn small">Check for updates</button>
|
||||||
|
<button id="aegisApplyMain" class="btn small" hidden>Apply update</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- PLUG-INS › ARIADNE (sub-page) -->
|
||||||
|
<section id="plugins-ariadne" class="subpage" data-parent="plugins" hidden>
|
||||||
|
<div class="crumb"><a data-go="plugins">Plug-ins</a> › Ariadne's Thread</div>
|
||||||
|
<h1><button class="back" data-go="plugins" title="Back to Plug-ins" aria-label="Back">‹</button>Ariadne's Thread</h1>
|
||||||
|
<p class="lede">A local daemon that resolves BCDN names for <b>every browser on this machine</b> (Chrome, Edge, Firefox, etc.), not just Theseus. Turning it off means non-Theseus browsers stop resolving <code>.bch</code> / <code>.x</code> / other BCNR TLDs; Theseus keeps working either way, since it has its own built-in resolver.</p>
|
||||||
|
|
||||||
|
<h2 class="sub" style="border-top:0;padding-top:0;margin-top:0">Status</h2>
|
||||||
|
<div class="row">
|
||||||
<div class="txt">
|
<div class="txt">
|
||||||
<div class="t">System-wide resolver — Ariadne's Thread</div>
|
<div class="t">System-wide resolver</div>
|
||||||
<div class="d">
|
<div class="d" id="ariadneStatusSub">checking…</div>
|
||||||
A local daemon that resolves BCDN names for <b>every browser on this machine</b>
|
|
||||||
(Chrome, Edge, Firefox, etc.), not just Theseus. Turning it off means non-Theseus
|
|
||||||
browsers stop resolving <code>.bch</code> / <code>.x</code> / other BCNR TLDs;
|
|
||||||
Theseus keeps working either way, since it has its own built-in resolver.
|
|
||||||
</div>
|
</div>
|
||||||
|
<label class="sw" title="Turn Ariadne on/off">
|
||||||
|
<input type="checkbox" id="ariadneToggleSub">
|
||||||
|
<span class="track"><span class="knob"></span></span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div id="ariadneStatus" class="pmuted" style="font-size:12.5px">checking…</div>
|
<div id="ariadneMissing" class="pmuted" style="font-size:12.5px;margin:-4px 4px 8px" hidden>
|
||||||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
Ariadne's Thread isn't installed on this machine. Click <b>Install</b> below to run
|
||||||
<button id="ariadneStart" class="btn" hidden>Turn on</button>
|
the bundled installer, or grab it manually from
|
||||||
<button id="ariadneStop" class="btn" hidden>Turn off</button>
|
<a href="https://silentmode.st/tools/" target="_blank" rel="noopener">silentmode.st/tools</a>.
|
||||||
<button id="ariadneInstall" class="btn" hidden>Install</button>
|
|
||||||
<button id="ariadneUpdate" class="btn" hidden>Update</button>
|
|
||||||
<button id="ariadneUninstall" class="btn" hidden style="color:#f6768a;border-color:rgba(246,118,138,.35)">Uninstall</button>
|
|
||||||
<button id="ariadneRefresh" class="btn">Refresh</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="ariadneMissing" class="pmuted" style="font-size:12px" hidden>
|
<div class="row" style="justify-content:flex-end;gap:8px">
|
||||||
Ariadne's Thread isn't installed on this machine. Click <b>Install</b> above
|
<button id="ariadneInstallSub" class="btn" hidden>Install</button>
|
||||||
to run the bundled installer, or grab it manually from
|
<button id="ariadneUpdateSub" class="btn" hidden>Update</button>
|
||||||
<a href="https://silentmode.st/tools/">silentmode.st/tools</a>.
|
<button id="ariadneRefreshSub" class="btn">Refresh</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="row" style="flex-direction:column;align-items:stretch;gap:8px">
|
<h2 class="sub">Remove</h2>
|
||||||
|
<p class="subd">Uninstall the resolver from this machine. Theseus itself keeps working — its built-in resolver isn't affected.</p>
|
||||||
|
<div class="row">
|
||||||
<div class="txt">
|
<div class="txt">
|
||||||
<div class="t">Built-in wallet — Aegis</div>
|
<div class="t">Uninstall Ariadne's Thread</div>
|
||||||
<div class="d">
|
<div class="d">Removes the two scheduled tasks and deletes <code>C:\ProgramData\Ariadne\</code>. Prompts for admin.</div>
|
||||||
Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB) that ships in the box.
|
</div>
|
||||||
Bundled with Theseus, but updates are delivered <b>over-the-air</b>: new signed
|
<button id="ariadneUninstallSub" class="btn" hidden style="color:#f6768a;border-color:rgba(246,118,138,.35)">Uninstall</button>
|
||||||
versions land without a Theseus release. Runs entirely inside this browser —
|
</div>
|
||||||
never system-wide.
|
</section>
|
||||||
|
|
||||||
|
<!-- PLUG-INS › AEGIS (sub-page) -->
|
||||||
|
<section id="plugins-aegis" class="subpage" data-parent="plugins" hidden>
|
||||||
|
<div class="crumb"><a data-go="plugins">Plug-ins</a> › Aegis</div>
|
||||||
|
<h1><button class="back" data-go="plugins" title="Back to Plug-ins" aria-label="Back">‹</button>Aegis</h1>
|
||||||
|
<p class="lede">Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB) that ships in the box. Bundled with Theseus, but updates are delivered <b>over-the-air</b>: new signed versions land without a Theseus release. Runs entirely inside this browser — never system-wide.</p>
|
||||||
|
|
||||||
|
<h2 class="sub" style="border-top:0;padding-top:0;margin-top:0">Status</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="txt">
|
||||||
|
<div class="t">Aegis wallet</div>
|
||||||
|
<div class="d" id="aegisStatusSub">loading…</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="aegisStatus" class="pmuted" style="font-size:12.5px">Loading…</div>
|
<div class="row" style="justify-content:flex-end;gap:8px">
|
||||||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
<button id="aegisCheckSub" class="btn">Check for updates</button>
|
||||||
<button id="aegisCheck" class="btn">Check for updates</button>
|
<button id="aegisApplySub" class="btn" hidden>Apply update now</button>
|
||||||
<button id="aegisApply" class="btn" hidden>Apply update now</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="note" style="margin-top:1rem">Aegis lives in the Extensions folder alongside every other add-on; to remove it, use <a data-go="addons" style="color:var(--acid-text);cursor:pointer">Extensions</a>.</div>
|
||||||
</section>
|
</section>
|
||||||
<!-- SEARCH -->
|
<!-- SEARCH -->
|
||||||
<section id="search" hidden>
|
<section id="search" hidden>
|
||||||
|
|
@ -895,6 +969,69 @@
|
||||||
el.checked = !!s[k];
|
el.checked = !!s[k];
|
||||||
el.addEventListener("change", () => C.set(k, el.checked));
|
el.addEventListener("change", () => C.set(k, el.checked));
|
||||||
}
|
}
|
||||||
|
// ---- website language (General page) — friendly wrapper over the
|
||||||
|
// same languageMode/languageValue setting the Anti-fingerprinting Language
|
||||||
|
// row edits. "Auto" = languageMode="show" (follow OS); anything else
|
||||||
|
// switches to languageMode="manual" and pins languageValue.
|
||||||
|
const WEB_LANG_QUICK = [
|
||||||
|
{ tag: "en-US", label: "English (US)" }, { tag: "en-GB", label: "English (UK)" },
|
||||||
|
{ tag: "es-ES", label: "Español (España)" }, { tag: "es-MX", label: "Español (México)" },
|
||||||
|
{ tag: "pt-BR", label: "Português (Brasil)" }, { tag: "pt-PT", label: "Português (Portugal)" },
|
||||||
|
{ tag: "fr-FR", label: "Français" }, { tag: "de-DE", label: "Deutsch" },
|
||||||
|
{ tag: "it-IT", label: "Italiano" }, { tag: "nl-NL", label: "Nederlands" },
|
||||||
|
{ tag: "ru-RU", label: "Русский" }, { tag: "uk-UA", label: "Українська" },
|
||||||
|
{ tag: "pl-PL", label: "Polski" }, { tag: "cs-CZ", label: "Čeština" },
|
||||||
|
{ tag: "sv-SE", label: "Svenska" }, { tag: "fi-FI", label: "Suomi" },
|
||||||
|
{ tag: "tr-TR", label: "Türkçe" }, { tag: "el-GR", label: "Ελληνικά" },
|
||||||
|
{ tag: "he-IL", label: "עברית" }, { tag: "ar", label: "العربية" },
|
||||||
|
{ tag: "hi-IN", label: "हिन्दी" }, { tag: "zh-CN", label: "中文(简体)" },
|
||||||
|
{ tag: "zh-TW", label: "中文(繁體)" }, { tag: "ja-JP", label: "日本語" },
|
||||||
|
{ tag: "ko-KR", label: "한국어" }, { tag: "vi-VN", label: "Tiếng Việt" },
|
||||||
|
{ tag: "th-TH", label: "ไทย" }, { tag: "id-ID", label: "Bahasa Indonesia" },
|
||||||
|
];
|
||||||
|
(async () => {
|
||||||
|
const webLangPick = document.getElementById("webLangPick");
|
||||||
|
const webLangOther = document.getElementById("webLangOther");
|
||||||
|
const hint = document.getElementById("webLangHint");
|
||||||
|
if (!webLangPick) return;
|
||||||
|
let osLoc = "en-US";
|
||||||
|
try { osLoc = (C.systemLocale && await C.systemLocale()) || "en-US"; } catch {}
|
||||||
|
// Build options: "Automatic" first, then the quick list, then "Other…".
|
||||||
|
const opts = [`<option value="__auto__">Automatic (OS: ${osLoc})</option>`,
|
||||||
|
...WEB_LANG_QUICK.map((L) => `<option value="${L.tag}">${L.label} — ${L.tag}</option>`),
|
||||||
|
`<option value="__other__">Other…</option>`];
|
||||||
|
webLangPick.innerHTML = opts.join("");
|
||||||
|
const paint = (cur) => {
|
||||||
|
const mode = cur.languageMode || "show";
|
||||||
|
if (mode !== "manual") {
|
||||||
|
webLangPick.value = "__auto__"; webLangOther.hidden = true;
|
||||||
|
hint.innerHTML = mode === "show"
|
||||||
|
? "Following your operating system. Also switchable from the globe icon in the address bar."
|
||||||
|
: `Anti-fingerprinting is currently overriding this (mode: <b>${mode}</b>). Change it under <b>Privacy → Anti-fingerprinting</b>.`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tag = cur.languageValue || "en-US";
|
||||||
|
const known = new Set(WEB_LANG_QUICK.map((L) => L.tag));
|
||||||
|
if (known.has(tag)) { webLangPick.value = tag; webLangOther.hidden = true; }
|
||||||
|
else { webLangPick.value = "__other__"; webLangOther.hidden = false; webLangOther.value = tag; }
|
||||||
|
hint.textContent = `Websites see you in ${tag}. Also switchable from the globe icon in the address bar.`;
|
||||||
|
};
|
||||||
|
paint(s);
|
||||||
|
webLangPick.addEventListener("change", () => {
|
||||||
|
const v = webLangPick.value;
|
||||||
|
if (v === "__auto__") { C.set("languageMode", "show"); webLangOther.hidden = true; }
|
||||||
|
else if (v === "__other__") { webLangOther.hidden = false; setTimeout(() => webLangOther.focus(), 0); }
|
||||||
|
else { C.set("languageMode", "manual"); C.set("languageValue", v); webLangOther.hidden = true; }
|
||||||
|
});
|
||||||
|
webLangOther.addEventListener("change", () => {
|
||||||
|
const v = String(webLangOther.value || "").trim();
|
||||||
|
if (!v) return;
|
||||||
|
C.set("languageMode", "manual"); C.set("languageValue", v);
|
||||||
|
});
|
||||||
|
// Keep in sync when the toolbar chip or the Anti-fingerprinting row
|
||||||
|
// writes to the same underlying setting.
|
||||||
|
if (C.onSettingsUpdate) C.onSettingsUpdate((next) => paint(next));
|
||||||
|
})();
|
||||||
// search engines: default picker + custom-engine list + add form
|
// search engines: default picker + custom-engine list + add form
|
||||||
const sel = document.getElementById("searchEngine");
|
const sel = document.getElementById("searchEngine");
|
||||||
const esc = (s) => String(s || "").replace(/</g, "<");
|
const esc = (s) => String(s || "").replace(/</g, "<");
|
||||||
|
|
@ -1280,74 +1417,121 @@
|
||||||
r.addEventListener("change", () => { if (r.checked) C.set("devToolsDock", r.value); });
|
r.addEventListener("change", () => { if (r.checked) C.set("devToolsDock", r.value); });
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---- Ariadne's Thread system-wide resolver toggle -----------------------
|
// ---- Plug-ins: Ariadne's Thread + Aegis --------------------------------
|
||||||
// Ariadne runs as two elevated Scheduled Tasks ("BNS Resolver Daemon" +
|
// Two views per plug-in that share one truth source:
|
||||||
// "BNS Sia Bridge"). Toggling requires admin — main spawns an elevated
|
// Main page (#plugins) — compact row: status + update button
|
||||||
// powershell for start/stop, which prompts UAC once per action.
|
// + on/off toggle on the right; title
|
||||||
const arStatus = document.getElementById("ariadneStatus");
|
// is a link into the sub-page.
|
||||||
const arStart = document.getElementById("ariadneStart");
|
// Sub-page (#plugins-ariadne, #plugins-aegis) — full details + the
|
||||||
const arStop = document.getElementById("ariadneStop");
|
// uninstall / apply buttons.
|
||||||
const arInstall = document.getElementById("ariadneInstall");
|
// Every state-changing IPC re-runs the shared refresh, so both views
|
||||||
const arUpdate = document.getElementById("ariadneUpdate");
|
// repaint together — the toggle in the main row and in the sub-page
|
||||||
const arUninstall = document.getElementById("ariadneUninstall");
|
// reflect the same underlying scheduled-task state.
|
||||||
const arRefresh = document.getElementById("ariadneRefresh");
|
(function () {
|
||||||
const arMissing = document.getElementById("ariadneMissing");
|
// ARIADNE ————————————————————————————————————————————————————————
|
||||||
|
// Runs as two elevated scheduled tasks ("BNS Resolver Daemon" + "BNS
|
||||||
|
// Sia Bridge"). Toggling / installing / uninstalling prompts UAC.
|
||||||
|
const q = (id) => document.getElementById(id);
|
||||||
|
const arStatMain = q("ariadneStatusMain");
|
||||||
|
const arStatSub = q("ariadneStatusSub");
|
||||||
|
const arSwMain = q("ariadneToggleMain");
|
||||||
|
const arSwSub = q("ariadneToggleSub");
|
||||||
|
const arSwWrap = q("ariadneSwWrap");
|
||||||
|
const arInstallMain = q("ariadneInstallMain");
|
||||||
|
const arInstallSub = q("ariadneInstallSub");
|
||||||
|
const arUpdateMain = q("ariadneUpdateMain");
|
||||||
|
const arUpdateSub = q("ariadneUpdateSub");
|
||||||
|
const arCheckMain = q("ariadneCheckMain");
|
||||||
|
const arRefreshSub = q("ariadneRefreshSub");
|
||||||
|
const arUninstall = q("ariadneUninstallSub");
|
||||||
|
const arMissing = q("ariadneMissing");
|
||||||
|
let arBusy = false; // during install/update/toggle: freeze the switch so refreshes don't fight
|
||||||
|
|
||||||
async function refreshAriadne() {
|
async function refreshAriadne() {
|
||||||
arStatus.textContent = "checking…";
|
|
||||||
arStart.hidden = arStop.hidden = arInstall.hidden = arUpdate.hidden = arUninstall.hidden = arMissing.hidden = true;
|
|
||||||
try {
|
try {
|
||||||
const r = await C.ariadneState();
|
const r = await C.ariadneState();
|
||||||
const verSuffix = r.installedVersion ? ` (v${r.installedVersion})` : "";
|
const verSuffix = r.installedVersion ? ` (v${r.installedVersion})` : "";
|
||||||
const bundledSuffix = r.bundledVersion ? ` (bundled v${r.bundledVersion})` : "";
|
const bundledSuffix = r.bundledVersion ? ` (bundled v${r.bundledVersion})` : "";
|
||||||
|
// Main row: short one-liner.
|
||||||
if (r.state === "running") {
|
if (r.state === "running") {
|
||||||
arStatus.innerHTML = `Status: <b style="color:var(--acid-text)">running</b>${verSuffix} — every browser on this machine resolves BCDN names.`;
|
arStatMain.innerHTML = `<b style="color:var(--acid-text)">On</b>${verSuffix} — resolving names system-wide.`;
|
||||||
arStop.hidden = false; arUninstall.hidden = !r.hasUninstaller;
|
|
||||||
if (r.canUpdate) arUpdate.hidden = false;
|
|
||||||
} else if (r.state === "stopped") {
|
} else if (r.state === "stopped") {
|
||||||
arStatus.innerHTML = `Status: <b>stopped</b>${verSuffix} — only Theseus resolves BCDN names; other browsers won't.`;
|
arStatMain.innerHTML = `<b>Off</b>${verSuffix} — only Theseus resolves BCDN names.`;
|
||||||
arStart.hidden = false; arUninstall.hidden = !r.hasUninstaller;
|
|
||||||
if (r.canUpdate) arUpdate.hidden = false;
|
|
||||||
} else {
|
} else {
|
||||||
arStatus.innerHTML = `Status: <b>not installed</b> on this machine.${bundledSuffix}`;
|
arStatMain.innerHTML = `<b>Not installed</b>.${bundledSuffix}`;
|
||||||
arInstall.hidden = false;
|
}
|
||||||
arMissing.hidden = false;
|
// Sub-page row: fuller description.
|
||||||
|
if (r.state === "running") {
|
||||||
|
arStatSub.innerHTML = `<b style="color:var(--acid-text)">Running</b>${verSuffix} — every browser on this machine resolves BCDN names.`;
|
||||||
|
} else if (r.state === "stopped") {
|
||||||
|
arStatSub.innerHTML = `<b>Stopped</b>${verSuffix} — only Theseus resolves BCDN names; other browsers won't.`;
|
||||||
|
} else {
|
||||||
|
arStatSub.innerHTML = `<b>Not installed</b> on this machine.${bundledSuffix}`;
|
||||||
|
}
|
||||||
|
// Toggle state on both views. When not installed the toggle is
|
||||||
|
// disabled — user has to hit Install first.
|
||||||
|
if (!arBusy) {
|
||||||
|
const on = r.state === "running";
|
||||||
|
const installed = r.state !== "not-installed";
|
||||||
|
arSwMain.checked = arSwSub.checked = on;
|
||||||
|
arSwMain.disabled = arSwSub.disabled = !installed;
|
||||||
|
if (arSwWrap) arSwWrap.title = installed ? "Turn Ariadne on/off" : "Install Ariadne first";
|
||||||
|
}
|
||||||
|
// Install / Update buttons: install only when missing, update only
|
||||||
|
// when an update is available.
|
||||||
|
const missing = r.state === "not-installed";
|
||||||
|
arInstallMain.hidden = arInstallSub.hidden = !missing;
|
||||||
|
arUpdateMain.hidden = arUpdateSub.hidden = !(r.canUpdate && !missing);
|
||||||
|
arUninstall.hidden = !r.hasUninstaller;
|
||||||
|
arMissing.hidden = !missing;
|
||||||
|
} catch (e) {
|
||||||
|
const msg = "Status check failed: " + (e?.message || e);
|
||||||
|
arStatMain.textContent = msg; arStatSub.textContent = msg;
|
||||||
}
|
}
|
||||||
} catch (e) { arStatus.textContent = "Status check failed: " + (e?.message || e); }
|
|
||||||
}
|
}
|
||||||
// Wrap each async action so the button disables during work + always
|
|
||||||
// refreshes state after (success OR failure), so the UI never lies.
|
|
||||||
function wire(btn, action, busyLabel) {
|
function wire(btn, action, busyLabel) {
|
||||||
|
if (!btn) return;
|
||||||
btn.onclick = async () => {
|
btn.onclick = async () => {
|
||||||
const orig = btn.textContent;
|
const orig = btn.textContent;
|
||||||
btn.disabled = true; btn.textContent = busyLabel;
|
btn.disabled = true; btn.textContent = busyLabel;
|
||||||
|
arBusy = true;
|
||||||
try { await action(); } catch {}
|
try { await action(); } catch {}
|
||||||
finally { btn.disabled = false; btn.textContent = orig; refreshAriadne(); }
|
finally { arBusy = false; btn.disabled = false; btn.textContent = orig; refreshAriadne(); }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
wire(arStart, () => C.ariadneToggle(true), "Starting…");
|
// Toggle: uses whichever switch the user flipped, mirrors the other.
|
||||||
wire(arStop, () => C.ariadneToggle(false), "Stopping…");
|
async function toggle(desired) {
|
||||||
wire(arInstall, () => C.ariadneInstall(), "Installing… (accept the UAC prompt)");
|
arBusy = true;
|
||||||
wire(arUpdate, () => C.ariadneUpdate(), "Updating… (accept the UAC prompt)");
|
arSwMain.disabled = arSwSub.disabled = true;
|
||||||
|
try { await C.ariadneToggle(desired); }
|
||||||
|
catch {}
|
||||||
|
finally { arBusy = false; refreshAriadne(); }
|
||||||
|
}
|
||||||
|
arSwMain.addEventListener("change", () => toggle(arSwMain.checked));
|
||||||
|
arSwSub.addEventListener("change", () => toggle(arSwSub.checked));
|
||||||
|
wire(arInstallMain, () => C.ariadneInstall(), "Installing…");
|
||||||
|
wire(arInstallSub, () => C.ariadneInstall(), "Installing… (accept the UAC prompt)");
|
||||||
|
wire(arUpdateMain, () => C.ariadneUpdate(), "Updating…");
|
||||||
|
wire(arUpdateSub, () => C.ariadneUpdate(), "Updating… (accept the UAC prompt)");
|
||||||
wire(arUninstall, () => C.ariadneUninstall(), "Uninstalling… (accept the UAC prompt)");
|
wire(arUninstall, () => C.ariadneUninstall(), "Uninstalling… (accept the UAC prompt)");
|
||||||
arRefresh.onclick = refreshAriadne;
|
arCheckMain.onclick = refreshAriadne;
|
||||||
|
arRefreshSub.onclick = refreshAriadne;
|
||||||
refreshAriadne();
|
refreshAriadne();
|
||||||
|
|
||||||
// ---- Aegis update card --------------------------------------------------
|
// AEGIS ——————————————————————————————————————————————————————————
|
||||||
// Aegis is a bundled add-on but its updates are shipped OTA via the signed
|
// Bundled add-on shipped OTA. Same addons-check-updates IPC the
|
||||||
// add-on update endpoint, so users can get new wallet versions without
|
// Extensions page uses, filtered for the aegis id. Legacy id
|
||||||
// updating Theseus. The same addons-check-updates IPC the Extensions page
|
// "bchwallet" still matched for upgrades from pre-rename installs.
|
||||||
// uses is filtered for the aegis id so this card only speaks for Aegis.
|
const aeStatMain = q("aegisStatusMain");
|
||||||
// Legacy id "bchwallet" is also matched for upgrades from pre-rename
|
const aeStatSub = q("aegisStatusSub");
|
||||||
// installs (0.3.x); post-migration the entry lives at id="aegis".
|
const aeCheckMain = q("aegisCheckMain");
|
||||||
const aegisStatus = document.getElementById("aegisStatus");
|
const aeCheckSub = q("aegisCheckSub");
|
||||||
const aegisCheck = document.getElementById("aegisCheck");
|
const aeApplyMain = q("aegisApplyMain");
|
||||||
const aegisApply = document.getElementById("aegisApply");
|
const aeApplySub = q("aegisApplySub");
|
||||||
const isAegis = (a) => a && (a.id === "aegis" || a.id === "bchwallet");
|
const isAegis = (a) => a && (a.id === "aegis" || a.id === "bchwallet");
|
||||||
// addons-list returns {installed:[...], sidebarPanels:[...]}
|
|
||||||
// addons-check-updates returns {report, skipped, staged:[...]}
|
|
||||||
// addons-list-staged returns a flat [...]. Normalise both call sites.
|
|
||||||
async function refreshAegis() {
|
async function refreshAegis() {
|
||||||
aegisApply.hidden = true;
|
aeApplyMain.hidden = aeApplySub.hidden = true;
|
||||||
try {
|
try {
|
||||||
const [listRes, stagedRes] = await Promise.all([
|
const [listRes, stagedRes] = await Promise.all([
|
||||||
C.listAddons ? C.listAddons() : Promise.resolve({}),
|
C.listAddons ? C.listAddons() : Promise.resolve({}),
|
||||||
|
|
@ -1357,54 +1541,75 @@
|
||||||
const staged = Array.isArray(stagedRes) ? stagedRes : [];
|
const staged = Array.isArray(stagedRes) ? stagedRes : [];
|
||||||
const cur = installed.find(isAegis);
|
const cur = installed.find(isAegis);
|
||||||
const upd = staged.find(isAegis);
|
const upd = staged.find(isAegis);
|
||||||
if (!cur) { aegisStatus.textContent = "Aegis isn't installed. Reinstall Theseus to add it back, or open Settings > Extensions to load it manually."; return; }
|
if (!cur) {
|
||||||
|
const msg = "Aegis isn't installed. Reinstall Theseus to add it back, or open Extensions to load it manually.";
|
||||||
|
aeStatMain.textContent = msg; aeStatSub.textContent = msg;
|
||||||
|
return;
|
||||||
|
}
|
||||||
const curVer = cur.version || "?";
|
const curVer = cur.version || "?";
|
||||||
if (upd && upd.version) {
|
if (upd && upd.version) {
|
||||||
aegisStatus.innerHTML = `<b style="color:var(--acid-text)">v${upd.version}</b> ready — click Apply update now to switch without restarting Theseus. You're on v${curVer}.`;
|
aeStatMain.innerHTML = `<b style="color:var(--acid-text)">v${upd.version}</b> ready — you're on v${curVer}.`;
|
||||||
aegisApply.hidden = false;
|
aeStatSub.innerHTML = `<b style="color:var(--acid-text)">v${upd.version}</b> ready — click Apply update now to switch without restarting Theseus. You're on v${curVer}.`;
|
||||||
|
aeApplyMain.hidden = aeApplySub.hidden = false;
|
||||||
} else {
|
} else {
|
||||||
aegisStatus.textContent = `You're on v${curVer}. Updates arrive over-the-air; the next check runs at boot and every 6h.`;
|
aeStatMain.textContent = `On v${curVer} — updates over-the-air, checked at boot and every 6h.`;
|
||||||
|
aeStatSub.textContent = `You're on v${curVer}. Updates arrive over-the-air; the next check runs at boot and every 6h.`;
|
||||||
}
|
}
|
||||||
} catch (e) { aegisStatus.textContent = "Status check failed: " + (e?.message || e); }
|
} catch (e) {
|
||||||
|
const msg = "Status check failed: " + (e?.message || e);
|
||||||
|
aeStatMain.textContent = msg; aeStatSub.textContent = msg;
|
||||||
}
|
}
|
||||||
aegisCheck.onclick = async () => {
|
}
|
||||||
const orig = aegisCheck.textContent;
|
function wireCheck(btn) {
|
||||||
aegisCheck.disabled = true; aegisCheck.textContent = "Checking…";
|
btn.onclick = async () => {
|
||||||
aegisStatus.textContent = "Checking…";
|
const orig = btn.textContent;
|
||||||
|
btn.disabled = true; btn.textContent = "Checking…";
|
||||||
|
const noneMsg = "You're on the latest Aegis.";
|
||||||
try {
|
try {
|
||||||
const res = await (C.checkAddonUpdates ? C.checkAddonUpdates() : Promise.resolve({}));
|
const res = await (C.checkAddonUpdates ? C.checkAddonUpdates() : Promise.resolve({}));
|
||||||
const staged = (res && Array.isArray(res.staged)) ? res.staged : [];
|
const staged = (res && Array.isArray(res.staged)) ? res.staged : [];
|
||||||
const upd = staged.find(isAegis);
|
if (!staged.find(isAegis)) {
|
||||||
if (!upd) { aegisStatus.textContent = "You're on the latest Aegis."; }
|
aeStatMain.textContent = noneMsg; aeStatSub.textContent = noneMsg;
|
||||||
// refresh will populate everything else + toggle the Apply button
|
}
|
||||||
refreshAegis();
|
refreshAegis();
|
||||||
} catch (e) { aegisStatus.textContent = "Check failed: " + (e?.message || e); }
|
} catch (e) {
|
||||||
finally { aegisCheck.disabled = false; aegisCheck.textContent = orig; }
|
const msg = "Check failed: " + (e?.message || e);
|
||||||
|
aeStatMain.textContent = msg; aeStatSub.textContent = msg;
|
||||||
|
}
|
||||||
|
finally { btn.disabled = false; btn.textContent = orig; }
|
||||||
};
|
};
|
||||||
// Apply staged update without restarting Theseus. The main-process
|
}
|
||||||
// handler promotes staged → active, tears down + rebuilds the addon
|
wireCheck(aeCheckMain);
|
||||||
// host, then force-reloads the sidebar iframe so the new panel bytes
|
wireCheck(aeCheckSub);
|
||||||
// are actually mounted. Falls back to a full restart if the IPC isn't
|
// Apply staged update without restarting Theseus. Falls back to a
|
||||||
// wired (older Theseus builds).
|
// full restart when the hot-apply IPC isn't wired (older Theseus).
|
||||||
aegisApply.onclick = async () => {
|
function wireApply(btn) {
|
||||||
const orig = aegisApply.textContent;
|
btn.onclick = async () => {
|
||||||
aegisApply.disabled = true; aegisApply.textContent = "Applying…";
|
const orig = btn.textContent;
|
||||||
|
btn.disabled = true; btn.textContent = "Applying…";
|
||||||
try {
|
try {
|
||||||
if (C && C.applyStagedAddons) {
|
if (C && C.applyStagedAddons) {
|
||||||
const r = await C.applyStagedAddons();
|
const r = await C.applyStagedAddons();
|
||||||
if (r && r.ok === false) throw new Error(r.err || "apply failed");
|
if (r && r.ok === false) throw new Error(r.err || "apply failed");
|
||||||
aegisStatus.textContent = "Update applied. Reopen the Aegis panel to see the new version.";
|
const msg = "Update applied. Reopen the Aegis panel to see the new version.";
|
||||||
|
aeStatMain.textContent = msg; aeStatSub.textContent = msg;
|
||||||
refreshAegis();
|
refreshAegis();
|
||||||
} else if (C && C.restartApp) {
|
} else if (C && C.restartApp) {
|
||||||
C.restartApp();
|
C.restartApp();
|
||||||
} else {
|
} else {
|
||||||
aegisStatus.textContent = "Update staged — restart Theseus to switch.";
|
const msg = "Update staged — restart Theseus to switch.";
|
||||||
|
aeStatMain.textContent = msg; aeStatSub.textContent = msg;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
aegisStatus.textContent = "Apply failed: " + (e?.message || e) + " — restart Theseus to fall back.";
|
const msg = "Apply failed: " + (e?.message || e) + " — restart Theseus to fall back.";
|
||||||
} finally { aegisApply.disabled = false; aegisApply.textContent = orig; }
|
aeStatMain.textContent = msg; aeStatSub.textContent = msg;
|
||||||
|
} finally { btn.disabled = false; btn.textContent = orig; }
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
wireApply(aeApplyMain);
|
||||||
|
wireApply(aeApplySub);
|
||||||
refreshAegis();
|
refreshAegis();
|
||||||
|
})();
|
||||||
|
|
||||||
// ---- Manual "Check for updates" -----------------------------------------
|
// ---- Manual "Check for updates" -----------------------------------------
|
||||||
// Fires an IPC that un-dismisses any lingering session chip and hits the
|
// Fires an IPC that un-dismisses any lingering session chip and hits the
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue