theseus/messages-preload.js
Local Dev a3b20a2446 Hermes MVP: threaded chat UI, contacts, persistent history, --messages flag
Builds on the previous Hermes sessions (NIP-17 crypto + chipnet hermes.bch
+ Theseus Messages window) to turn the flat inbox into something usable.

main.js (hermes block):
  - Persistent inbox per identity at
    <userData>/hermes/<pkHex>/inbox.jsonl (append-only JSONL, replayed on
    init). Read-state at <userData>/hermes/<pkHex>/read.json so unread
    badges survive restarts.
  - Address book at <userData>/hermes/contacts.json: list / add / update /
    remove. hermes-contacts-add resolves .bch name OR npub OR hex pk,
    canonicalises, writes.
  - Reverse-resolve consults contacts first, chain second: a user-curated
    nickname wins over the on-chain np lookup.
  - hermes-send also records the outgoing message to the inbox so thread
    view shows both sides (only when at least one relay accepted).
  - Inbox messages carry direction + peerPkHex + peerName so the renderer
    can group by thread without re-doing the pairing logic.
  - --messages CLI flag opens the panel at launch (first instance via
    whenReady, second-instance via handleLaunch) so the dev harness and
    power users don't need Ctrl+Shift+M.
  - Dev-only auto-init: THESEUS_HERMES_DEV_MNEMONIC env, gated on
    THESEUS_USER_DATA so it refuses to touch a production profile.
  - HERMES_INBOX_LIMIT raised 200 -> 2000 now that history actually sticks.

messages-preload.js:
  - Adds hermes.contacts.{list, add, update, remove} and
    hermes.markThreadRead; status + onMessage/onStatus unchanged.

messages.html:
  - Replaces the flat inbox with a two-column layout: contact list (search
    + unread badges + last-message preview) on the left, selected thread
    on the right with sender bubbles, timestamps, inline Enter-to-send
    compose, remove-contact affordance.
  - .bch suffix stripped from displayed names (TLD is noise in chat UI;
    other TLDs stay as disambiguation).
  - Mark-thread-read fires on open; vault-bind path kept intact.
2026-10-20 23:21:34 +03:00

38 lines
1.7 KiB
JavaScript

// Renderer bridge for the Messages panel.
// All hermes-* IPC returns { ok, ... } | { ok: false, err }.
const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("hermes", {
// Identity + relay state
status: () => ipcRenderer.invoke("hermes-status"),
canUseVault: () => ipcRenderer.invoke("hermes-can-use-vault"),
init: (mnemonic) => ipcRenderer.invoke("hermes-init", { mnemonic }),
initFromVault: () => ipcRenderer.invoke("hermes-init", { useVault: true }),
close: () => ipcRenderer.invoke("hermes-close"),
// Messages
inbox: () => ipcRenderer.invoke("hermes-inbox"),
send: (to, text) => ipcRenderer.invoke("hermes-send", { to, text }),
markThreadRead: (peerPkHex, upToCreatedAt) =>
ipcRenderer.invoke("hermes-thread-mark-read", { peerPkHex, upToCreatedAt }),
// Contacts (address book)
contacts: {
list: () => ipcRenderer.invoke("hermes-contacts-list"),
add: (name, nickname) => ipcRenderer.invoke("hermes-contacts-add", { name, nickname }),
update: (pkHex, patch) => ipcRenderer.invoke("hermes-contacts-update", { pkHex, patch }),
remove: (pkHex) => ipcRenderer.invoke("hermes-contacts-remove", { pkHex }),
},
// Pushes: new decrypted message / relay status change.
onMessage: (cb) => {
const wrapped = (_e, msg) => cb(msg);
ipcRenderer.on("hermes-message", wrapped);
return () => ipcRenderer.removeListener("hermes-message", wrapped);
},
onStatus: (cb) => {
const wrapped = (_e, s) => cb(s);
ipcRenderer.on("hermes-status-update", wrapped);
return () => ipcRenderer.removeListener("hermes-status-update", wrapped);
},
});