Users can link one or more phone numbers to their Hermes identity by publishing a kind:30078 claim event whose d-tag is a SHA-256 hash of the E.164 number. The number itself never leaves the device. Add-contact and message-send now also accept a +phone input directly — the client hashes it and queries relays for the matching Hermes identity. - lib/hermes.js: normalisePhone(str) strips separators + a leading 00 into E.164, rejects anything that doesn't fit +[country][7-15 digits]. hashPhone(str) → base64url-truncated-to-32 SHA-256 digest, used as the d-tag suffix under PHONE_DTAG_PREFIX. buildPhoneClaimEvent / buildPhoneRetractEvent emit the signed kind:30078 / NIP-09 kind:5. - main.js: _hermesAPI.getPhone / setPhone / clearPhone / findByPhones. Plaintext phone persists in the identity meta (locally only) so the user can see what's linked and retract it; event ids are kept so a retract can reference the exact wire event. hermesResolveTo takes a phone-shaped input and does the discovery lookup transparently, so contactsAdd and send accept phones without a new entry point. - panel.html: Settings → Phone discovery (link / unlink + privacy note), add-contact placeholder now lists +phone alongside the other forms. Privacy stance stated in the UI: "anyone who already knows your number can find you — same tradeoff as number-based messengers." No PSI tricks yet; plain hash. Users who don't want it just don't link, which is the default. Future slice can add a pepper if operators want to narrow the brute-force window.
83 lines
3.6 KiB
JavaScript
83 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",
|
|
"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)");
|
|
},
|
|
};
|