theseus/bundled-addons/hermes/index.js
Local Dev bbe5d3d298 hermes: cloud-wrapped backup (opt-in B) + mobile site polish
Users can now sign in with name + password on a device that has never seen the
account by opting into a cloud backup at account creation. The seed phrase is
locally re-wrapped with scrypt + AES-GCM keyed by the password and published
as a NIP-78 kind:30078 event under d-tag `hermes.backup.v1/<sha256(name)>`.
Relays only ever see opaque ciphertext; the password is the only decryption
key, so backup strength is password strength.

Signin tries the local wrap first (fast, no network) and falls back to the
cloud copy if the device hasn't been used before. Settings → Backup now has
"Back up to cloud" / "Re-upload" / "Remove cloud backup" controls that reuse
the Reveal-seed password field as the key.

Site: added @media breakpoints to shared.css so the header collapses cleanly
on narrow screens (icon-only brand under 380px, hidden "Sign in" label under
640px), the hero mark drops to 84px, h1 scales down, and buttons shrink to
touch sizes. Also fixed a stale reference to the removed `mnemonicBox` id in
the Create-account toggle that was throwing a TypeError on mobile.
2026-10-22 03:46:45 +03:00

84 lines
3.6 KiB
JavaScript

// Hermes bundled add-on — thin proxy to the host's panel API.
//
// 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.
//
// 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", "signOut", "getBackup", "hasBackup",
"publishCloudBackup", "fetchCloudBackup", "hasCloudBackup", "removeCloudBackup",
"getProfile", "updateProfile",
"getPhone", "setPhone", "clearPhone", "findByPhones",
"inbox", "send", "markThreadRead",
"contactsList", "contactsAdd", "contactsUpdate", "contactsRemove",
];
module.exports = {
activate(api) {
api.registerSidebarPanel({
id: "main",
title: "Hermes",
page: "panel.html",
// Dock on the LEFT strip (alongside mail/pithos/telegram etc.), not
// the right-hand add-on dock. The host checks `side` on both the
// manifest's panels[] entry and the registerSidebarPanel call —
// 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", () => {
ipcMain.emit("hermes-open-request");
return { ok: true };
});
// 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)");
},
};