diff --git a/collision-preload.js b/collision-preload.js new file mode 100644 index 0000000..e0c97a8 --- /dev/null +++ b/collision-preload.js @@ -0,0 +1,7 @@ +// Minimal, sandboxed IPC bridge for the "Open with…" collision prompt window. +// Only exposes the exact call the page needs: send the user's choice back to +// the main process (which awaits it via ipcMain.once). +const { contextBridge, ipcRenderer } = require("electron"); +contextBridge.exposeInMainWorld("collisionApi", { + choose(host, payload) { ipcRenderer.send(`collision-choose:${host}`, payload); }, +}); diff --git a/collision.html b/collision.html new file mode 100644 index 0000000..e129565 --- /dev/null +++ b/collision.html @@ -0,0 +1,94 @@ + + + + + Open with… + + + + +

exists in two registries

+
Choose which one to open. You can change this later in Settings.
+ +
+ + +
+ +
+
+
+
+
+ +
+ +
+ + + + diff --git a/main.js b/main.js index b13be3e..c7fe59e 100644 --- a/main.js +++ b/main.js @@ -4,7 +4,7 @@ // h, Sia s3, direct ip, redirect u). Tabs, nav controls, a search box, a home // page, and optional Tor onion routing. No system daemon; the app is the trust // boundary. -const { app, BrowserWindow, WebContentsView, ipcMain, protocol, session, Menu, clipboard, nativeTheme } = require("electron"); +const { app, BrowserWindow, WebContentsView, ipcMain, protocol, session, Menu, clipboard, nativeTheme, shell } = require("electron"); const path = require("path"); const http = require("http"); const https = require("https"); @@ -21,27 +21,29 @@ const RES_DIR = app.isPackaged ? process.resourcesPath : __dirname; const RESOLVER = app.isPackaged ? path.join(RES_DIR, "resolver-web.mjs") : path.join(__dirname, "..", "Argus", "src", "lib", "resolver-web.js"); -// Built-in search engines. Users can also add their own (settings.customEngines, +// Built-in engines. Users can also add their own (settings.customEngines, // each { id, name, url } where the url contains "%s" for the query). // Catalog of built-in engines (users pick which to enable + can add their own). // fav = the domain to load a real favicon from; sym = emoji fallback. +// kind = "search" (traditional search engines) or "llm" (AI answer engines). +// The picker + settings render each kind in its own section; kind stays a +// display-only grouping (URL routing is the same for both). const SEARCH_ENGINES = { - duckduckgo: { name: "DuckDuckGo", sym: "πŸ¦†", fav: "duckduckgo.com", url: (q) => "https://duckduckgo.com/?q=" + encodeURIComponent(q) }, - google: { name: "Google", sym: "πŸ”΅", fav: "www.google.com", url: (q) => "https://www.google.com/search?q=" + encodeURIComponent(q) }, - brave: { name: "Brave", sym: "🦁", fav: "search.brave.com", url: (q) => "https://search.brave.com/search?q=" + encodeURIComponent(q) }, - bing: { name: "Bing", sym: "πŸ”Ž", fav: "www.bing.com", url: (q) => "https://www.bing.com/search?q=" + encodeURIComponent(q) }, - startpage: { name: "Startpage", sym: "πŸ›‘οΈ", fav: "www.startpage.com", url: (q) => "https://www.startpage.com/sp/search?query=" + encodeURIComponent(q) }, - yandex: { name: "Yandex", sym: "πŸ”΄", fav: "yandex.com", url: (q) => "https://yandex.com/search/?text=" + encodeURIComponent(q) }, - ecosia: { name: "Ecosia", sym: "🌱", fav: "www.ecosia.org", url: (q) => "https://www.ecosia.org/search?q=" + encodeURIComponent(q) }, - mojeek: { name: "Mojeek", sym: "🧭", fav: "www.mojeek.com", url: (q) => "https://www.mojeek.com/search?q=" + encodeURIComponent(q) }, - searxng: { name: "SearXNG", sym: "🧩", fav: "searx.be", url: (q) => "https://searx.be/search?q=" + encodeURIComponent(q) }, - wikipedia: { name: "Wikipedia", sym: "πŸ“–", fav: "en.wikipedia.org", url: (q) => "https://en.wikipedia.org/wiki/Special:Search?search=" + encodeURIComponent(q) }, - perplexity: { name: "Perplexity", sym: "🧠", fav: "www.perplexity.ai", url: (q) => "https://www.perplexity.ai/search?q=" + encodeURIComponent(q) }, - // AI / LLM answer engines (accept a URL query and answer it directly). - chatgpt: { name: "ChatGPT", sym: "πŸ€–", fav: "chatgpt.com", url: (q) => "https://chatgpt.com/?q=" + encodeURIComponent(q) }, - claude: { name: "Claude", sym: "✳️", fav: "claude.ai", url: (q) => "https://claude.ai/new?q=" + encodeURIComponent(q) }, - phind: { name: "Phind", sym: "πŸ§‘β€πŸ’»", fav: "www.phind.com", url: (q) => "https://www.phind.com/search?q=" + encodeURIComponent(q) }, - you: { name: "You.com", sym: "🟣", fav: "you.com", url: (q) => "https://you.com/search?q=" + encodeURIComponent(q) + "&tbm=youchat" }, + duckduckgo: { kind: "search", name: "DuckDuckGo", sym: "πŸ¦†", fav: "duckduckgo.com", url: (q) => "https://duckduckgo.com/?q=" + encodeURIComponent(q) }, + google: { kind: "search", name: "Google", sym: "πŸ”΅", fav: "www.google.com", url: (q) => "https://www.google.com/search?q=" + encodeURIComponent(q) }, + brave: { kind: "search", name: "Brave", sym: "🦁", fav: "search.brave.com", url: (q) => "https://search.brave.com/search?q=" + encodeURIComponent(q) }, + bing: { kind: "search", name: "Bing", sym: "πŸ”Ž", fav: "www.bing.com", url: (q) => "https://www.bing.com/search?q=" + encodeURIComponent(q) }, + startpage: { kind: "search", name: "Startpage", sym: "πŸ›‘οΈ", fav: "www.startpage.com", url: (q) => "https://www.startpage.com/sp/search?query=" + encodeURIComponent(q) }, + yandex: { kind: "search", name: "Yandex", sym: "πŸ”΄", fav: "yandex.com", url: (q) => "https://yandex.com/search/?text=" + encodeURIComponent(q) }, + ecosia: { kind: "search", name: "Ecosia", sym: "🌱", fav: "www.ecosia.org", url: (q) => "https://www.ecosia.org/search?q=" + encodeURIComponent(q) }, + mojeek: { kind: "search", name: "Mojeek", sym: "🧭", fav: "www.mojeek.com", url: (q) => "https://www.mojeek.com/search?q=" + encodeURIComponent(q) }, + searxng: { kind: "search", name: "SearXNG", sym: "🧩", fav: "searx.be", url: (q) => "https://searx.be/search?q=" + encodeURIComponent(q) }, + wikipedia: { kind: "search", name: "Wikipedia", sym: "πŸ“–", fav: "en.wikipedia.org", url: (q) => "https://en.wikipedia.org/wiki/Special:Search?search=" + encodeURIComponent(q) }, + // AI / LLM answer engines that ANSWER the URL query without requiring a login. + // ChatGPT / Claude / You.com's youchat all bounce to sign-in before running + // ?q=, so they'd fail silently as a "search engine" β€” omitted deliberately. + perplexity: { kind: "llm", name: "Perplexity", sym: "🧠", fav: "www.perplexity.ai", url: (q) => "https://www.perplexity.ai/search?q=" + encodeURIComponent(q) }, + phind: { kind: "llm", name: "Phind", sym: "πŸ§‘β€πŸ’»", fav: "www.phind.com", url: (q) => "https://www.phind.com/search?q=" + encodeURIComponent(q) }, }; // Engines enabled by default (shown in the toolbar dropdown). The rest are in the // catalog and can be turned on from Settings. Custom + detected engines are always on. @@ -55,9 +57,9 @@ function isEnabled(id) { return (settings.enabledEngines || DEFAULT_ENABLED).inc // enabledEngines; custom engines are always enabled. function allEngines() { const list = Object.entries(SEARCH_ENGINES).map(([id, e]) => - ({ id, name: e.name, sym: e.sym, favicon: faviconUrl(e.fav), builtin: true, enabled: isEnabled(id) })); + ({ id, name: e.name, sym: e.sym, favicon: faviconUrl(e.fav), kind: e.kind || "search", builtin: true, enabled: isEnabled(id) })); for (const c of settings.customEngines || []) - list.push({ id: c.id, name: c.name, sym: c.sym || "πŸ”", favicon: customFavicon(c.url), builtin: false, enabled: true }); + list.push({ id: c.id, name: c.name, sym: c.sym || "πŸ”", favicon: customFavicon(c.url), kind: c.kind || "search", builtin: false, enabled: true }); // Apply the user's custom order; ids not in engineOrder keep their natural order (stable sort). const order = settings.engineOrder || []; return list.slice().sort((a, b) => { @@ -518,7 +520,7 @@ async function ensureIndex(force = false) { if (!force && sharedIndex && Date.now() - indexBuiltAt < INDEX_TTL) return sharedIndex; if (indexBuilding) return indexBuilding; // dedupe concurrent builds indexBuilding = buildIndex({ WebSocket: currentWS(), directIP: true, electrum: electrumPool }) - .then((idx) => { sharedIndex = idx; indexBuiltAt = Date.now(); return idx; }) + .then((idx) => { sharedIndex = idx; indexBuiltAt = Date.now(); refreshBcnrTlds(idx); return idx; }) .finally(() => { indexBuilding = null; }); // If we have a stale index, don't block on the rebuild β€” serve stale, refresh async. return (sharedIndex && !force) ? sharedIndex : indexBuilding; @@ -584,6 +586,13 @@ const POP_W = 360; let popH = 210; // popH is updated to fit the popover's conte // engine favicons, like Firefox β€” a native Just this once + + + + diff --git a/preload.js b/preload.js index 636cddc..5a9d413 100644 --- a/preload.js +++ b/preload.js @@ -29,4 +29,12 @@ contextBridge.exposeInMainWorld("theseus", { onTor: (cb) => ipcRenderer.on("tor", (_e, d) => cb(d)), onTabs: (cb) => ipcRenderer.on("tabs", (_e, d) => cb(d)), onBcnrOffer: (cb) => ipcRenderer.on("bcnr-offer", (_e, d) => cb(d)), + // Collision-mode (BCNR ↔ ICANN) live switcher for the active tab + collisionSwitch: (arg) => ipcRenderer.invoke("collision-switch", arg), + collisionState: () => ipcRenderer.invoke("collision-state"), + // Downloads β€” the toolbar button subscribes to `downloads` to update its + // badge, and toggleDownloads opens/closes the floating panel. + getDownloads: () => ipcRenderer.invoke("downloads-get"), + toggleDownloads: (rect) => ipcRenderer.invoke("toggle-downloads", rect), + onDownloads: (cb) => ipcRenderer.on("downloads", (_e, d) => cb(d)), }); diff --git a/settings-preload.js b/settings-preload.js index 162a7e6..3f361db 100644 --- a/settings-preload.js +++ b/settings-preload.js @@ -7,4 +7,8 @@ contextBridge.exposeInMainWorld("cfg", { removeEngine: (id) => ipcRenderer.invoke("remove-engine", id), setEngineEnabled: (id, on) => ipcRenderer.invoke("set-engine-enabled", id, on), setEngineOrder: (ids) => ipcRenderer.invoke("set-engine-order", ids), + // 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"), }); diff --git a/settings.html b/settings.html index 1a60e4e..923d526 100644 --- a/settings.html +++ b/settings.html @@ -2,7 +2,7 @@ Theseus β€” Settings