From 8777b1fc56601609cd96afb1bb5284bd4cb38894 Mon Sep 17 00:00:00 2001 From: Local Dev Date: Wed, 21 Oct 2026 03:00:17 +0300 Subject: [PATCH] hermes: in-panel messenger with Theseus ID signin, spread-wings mark MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sidebar panel used to be a launcher that opened the standalone Messages window; now it IS the messenger — signin screen with "Open with Theseus ID" (derives the Nostr key from the unlocked vault seed) or "Open an existing account" (12/24-word mnemonic fallback), then a contacts + thread view with a narrow-width single-column fallback. The ⧉ button still pops the standalone window out, so the Ctrl+Shift+E flow keeps working. - main.js: expose a global._hermesAPI with status/canUseVault/init/inbox/ send/markThreadRead/contacts* so the addon can proxy panel calls without granting it ipcRenderer or crossing the HERMES_ONLY sender gate. hermesEmit also fans out on an in-process __hermes_bus so the panel gets live inbox/status pushes decoupled from hermesWin's lifetime. - bundled-addons/hermes/index.js: proxies each allow-listed method onto global._hermesAPI, forwards bus events to api.emit for the panel. - panel.html: signin + app screens, mobile-style single column under 540px. - Mark updated to the spread-wings variant: envelope sized up (34×22), three tapered trails extending outside the viewBox so the wings don't bunch at the back of the envelope at any size. Applied to the addon icon (manifest base64), the panel header, both /hermes sites, and site-hermes-x's favicon + mark SVGs. --- bundled-addons/hermes/addon.json | 6 +- bundled-addons/hermes/index.js | 75 +++- bundled-addons/hermes/panel.html | 612 ++++++++++++++++++++++++++++--- main.js | 167 +++++++++ 4 files changed, 800 insertions(+), 60 deletions(-) diff --git a/bundled-addons/hermes/addon.json b/bundled-addons/hermes/addon.json index 92b8a711..2dc30d83 100644 --- a/bundled-addons/hermes/addon.json +++ b/bundled-addons/hermes/addon.json @@ -1,14 +1,14 @@ { "id": "hermes", "name": "Hermes", - "version": "0.1.0", + "version": "0.2.0", "description": "Message anyone by their .bch name. End-to-end encrypted DMs over Nostr (NIP-17), identity derived from your wallet seed.", "author": "Silent Mode", - "icon": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNjQgNjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMzhCREY4IiBzdHJva2Utd2lkdGg9IjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgdHJhbnNmb3JtPSJyb3RhdGUoLTIyIDMyIDMyKSI+PHJlY3QgeD0iMjAiIHk9IjI2IiB3aWR0aD0iMzAiIGhlaWdodD0iMTgiIHJ4PSIxLjUiLz48cGF0aCBkPSJNMjAgMjcgTDM1IDM3IEw1MCAyNyIvPjxwYXRoIGQ9Ik0yMCAzNiBMNCAzNiIvPjwvZz48L3N2Zz4K", + "icon": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNjQgNjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMzhCREY4IiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgdHJhbnNmb3JtPSJyb3RhdGUoLTIwIDMyIDMyKSI+PHJlY3QgeD0iMTgiIHk9IjI0IiB3aWR0aD0iMzQiIGhlaWdodD0iMjIiIHJ4PSIxLjUiLz48cGF0aCBkPSJNMTggMjUgTDM1IDM5IEw1MiAyNSIvPjxwYXRoIGQ9Ik0xOCAyOCBMLTQgMjQiLz48cGF0aCBkPSJNMTggMzYgTC02IDM2Ii8+PHBhdGggZD0iTTE4IDQ0IEwwIDQ2Ii8+PC9nPjwvc3ZnPg==", "main": "index.js", "activation": "on-demand", "panels": [ - { "id": "main", "title": "Hermes", "icon": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNjQgNjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMzhCREY4IiBzdHJva2Utd2lkdGg9IjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgdHJhbnNmb3JtPSJyb3RhdGUoLTIyIDMyIDMyKSI+PHJlY3QgeD0iMjAiIHk9IjI2IiB3aWR0aD0iMzAiIGhlaWdodD0iMTgiIHJ4PSIxLjUiLz48cGF0aCBkPSJNMjAgMjcgTDM1IDM3IEw1MCAyNyIvPjxwYXRoIGQ9Ik0yMCAzNiBMNCAzNiIvPjwvZz48L3N2Zz4K", "page": "panel.html", "side": "left" } + { "id": "main", "title": "Hermes", "icon": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNjQgNjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMzhCREY4IiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgdHJhbnNmb3JtPSJyb3RhdGUoLTIwIDMyIDMyKSI+PHJlY3QgeD0iMTgiIHk9IjI0IiB3aWR0aD0iMzQiIGhlaWdodD0iMjIiIHJ4PSIxLjUiLz48cGF0aCBkPSJNMTggMjUgTDM1IDM5IEw1MiAyNSIvPjxwYXRoIGQ9Ik0xOCAyOCBMLTQgMjQiLz48cGF0aCBkPSJNMTggMzYgTC02IDM2Ii8+PHBhdGggZD0iTTE4IDQ0IEwwIDQ2Ii8+PC9nPjwvc3ZnPg==", "page": "panel.html", "side": "left" } ], "capabilities": ["sidebar-panel"] } diff --git a/bundled-addons/hermes/index.js b/bundled-addons/hermes/index.js index 2d9598cb..16b736ed 100644 --- a/bundled-addons/hermes/index.js +++ b/bundled-addons/hermes/index.js @@ -1,19 +1,28 @@ -// Hermes bundled add-on — thin launcher. +// Hermes bundled add-on — thin proxy to the host's panel API. // -// Real messaging code (NIP-17 wrap/unwrap, relay subscriptions, persistent -// inbox, contacts) lives in Theseus main.js: too much raw network + crypto to -// run under the add-on sandbox. This add-on exists for ONE reason: register a -// left-strip sidebar entry so the Messages panel is discoverable without -// pressing Ctrl+Shift+E. +// All the real messaging code (NIP-17 wrap/unwrap, relay subscriptions, +// persistent inbox, contacts) lives in Theseus main.js. Too much raw network +// + crypto to run under the add-on sandbox, and the vault seed must never +// cross the host/add-on boundary. // -// The panel (panel.html) is a small chrome: mark, "Open Messages" button, -// shortcut hint. Click → forwarded through api.onMessage → an in-process -// ipcMain.emit that main.js listens for and resolves to openHermesWindow(). -// Keeping the forwarding tiny: nothing crypto-sensitive flows through this -// add-on, so a compromise of this one-handler surface can at worst open an -// otherwise-openable window. +// This file does two things: +// 1. Register the left-strip sidebar entry (so Hermes is discoverable +// without pressing Ctrl+Shift+E). +// 2. Proxy a small, allow-listed set of panel operations between the +// panel's silentmode.invoke() calls and main.js's `global._hermesAPI`. +// The panel never sees ipcRenderer — only this named handler surface. +// +// Events coming the other way (inbox updates, relay status changes) are +// fanned out via a shared ipcMain event bus `__hermes_bus`: main.js's +// hermesBusEmit publishes, this file forwards each payload to the panel. const { ipcMain } = require("electron"); +const PANEL_METHODS = [ + "status", "canUseVault", "close", "init", "inbox", "send", + "markThreadRead", "contactsList", "contactsAdd", "contactsUpdate", + "contactsRemove", +]; + module.exports = { activate(api) { api.registerSidebarPanel({ @@ -26,13 +35,47 @@ module.exports = { // declaring it in both keeps dormant vs active renders consistent. side: "left", }); + + // Legacy launcher behavior: panel.html can still ask to open the + // standalone Messages window. Kept because Ctrl+Shift+E users have + // muscle memory, and because a few flows (debug, multi-window) + // benefit from a free-floating window. api.onMessage("open-messages", () => { - // In-process emit: main.js listens on "hermes-open-request" and calls - // openHermesWindow(). Using emit (not .send) because this is a - // same-process signal, not an IPC to a renderer. ipcMain.emit("hermes-open-request"); return { ok: true }; }); - api.log("registered hermes sidebar panel"); + + // Panel API proxies. Every method listed in PANEL_METHODS invokes the + // same-named function on global._hermesAPI. The main.js side defines + // that object once hermes is initialised; before that, status/canUseVault + // are the only safe methods and will return a stub-ok. + for (const name of PANEL_METHODS) { + api.onMessage(name, async (payload) => { + const hermes = global._hermesAPI; + if (!hermes || typeof hermes[name] !== "function") { + // Host not ready. Treat status/canUseVault as "no identity yet" + // so the panel can render its signin screen without a toast. + if (name === "status") return { ok: true, ready: false }; + if (name === "canUseVault") return { ok: true, available: false }; + return { ok: false, error: "hermes host not ready" }; + } + return await hermes[name](payload); + }); + } + + // Main-side → panel event bus. hermesBusEmit in main.js fires events on + // ipcMain under __hermes_bus; forward each to the panel via api.emit so + // panel-side `silentmode.on("hermes-message", cb)` works identically to + // the ipcRenderer path used by the standalone window. + const busForward = (channel, payload) => { + try { api.emit(channel, payload); } catch {} + }; + ipcMain.on("__hermes_bus", busForward); + + api.onDeactivate && api.onDeactivate(() => { + try { ipcMain.off("__hermes_bus", busForward); } catch {} + }); + + api.log("hermes sidebar panel registered (proxy mode)"); }, }; diff --git a/bundled-addons/hermes/panel.html b/bundled-addons/hermes/panel.html index acbc4fbc..30dd0e4d 100644 --- a/bundled-addons/hermes/panel.html +++ b/bundled-addons/hermes/panel.html @@ -5,63 +5,593 @@ Hermes -