feat: keep every room's history on the device and put it back on relays that forgot - #199
Merged
Merged
Conversation
A chat log given an EventArchive shows what the device kept before any relay answers, keeps every event it accepts (and its own sends), and pages back past the 30-day/500 render window with loadOlder. Archived events go through decodeChatEvent exactly as relay ones do. A session keeps the authority's rekeys and replays them at join, so a forgotten relay cannot leave a device in an old epoch. reseedCandidates decides what a relay that returned fewer events should be handed back. Claude-Session: https://claude.ai/code/session_01CG4pPCsd8pdySNvBpt8fTk
…gets chat NostrRelayPool gains query and publishTo for a single configured relay and noteKeepsChat; relay settings show 'Does not keep chat' for a relay that accepted room chat and returned none of it. Claude-Session: https://claude.ai/code/session_01CG4pPCsd8pdySNvBpt8fTk
…ful relays Original signed events are sealed per device in IndexedDB under a non-extractable AES-GCM key; records carry only HMAC handles, so the disk shows no room ids, event ids or times. Rooms open on archived history, page back when the reader reaches the top, and five seconds after joining hand each room relay that returned fewer events the originals it lacks, rate-limited and bounded, never a quiet room's chat. Forgetting this browser deletes the archive. Claude-Session: https://claude.ai/code/session_01CG4pPCsd8pdySNvBpt8fTk
Reseed compares a relay page by page with `until`, so a relay that caps its answers is read to the end, and judges nothing older than the oldest event it returned when paging stops early. `query` now says whether the relay really finished; a slow or closed read is unknown, and nothing is republished on it. Reseed writes through a new quiet `publishQuietly`: one attempt, no health marks, no reconnect, not counted as publishing. The archive-side "does not keep chat" flag and its relay settings line are gone; relay health detects that elsewhere. The archive drops the oldest events past its per-conversation cap instead of refusing new ones, and a chat log keeps an event only after the retention, duplicate and per-sender rate checks. A message first read from the archive takes its lane from the first relay copy that arrives. Archived history decodes in chunks with the page given a turn between them; a read waits only for its own conversation's pending writes; a closed or rekeyed log lets its conversation go from memory. Events said in a quiet room are marked inside the sealed record and never handed back to a relay. The header now says plainly what the device keys do and do not protect, and that records are not padded. Claude-Session: https://claude.ai/code/session_01CG4pPCsd8pdySNvBpt8fTk
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 1 (steps 1-3) of the 27 Sept durable-history plan, web and desktop. The owner's two-person room showed 425 messages on the desktop and none on Android because only one relay still held the chat and no client kept a copy.
app/src/room-archive.ts: IndexedDB archive of the ORIGINAL signed events (chat on every channel incl. control, plus the authority's rekeys), sealed with AES-GCM under a non-extractable device key; record names are keyed HMACs, AAD binds each record to its conversation. Header comment is honest about limits (keys live in the same IndexedDB on disk; sizes and per-conversation counts show).src/chat.ts,src/session.ts): archive first, then relays, through the samedecodeChatEvent/rekey path and rules.loadOlder()pages past the 30-day/500 window; decode in chunks of 40 with yields; memory released when a log closes.until, only complete answers count (queryreturns{ events, complete }), nothing older than the oldest returned counts as missing; republished unchanged viapublishQuietly(no health marks, no reconnects, not counted as publishing); bounded (500/conversation, 1,000/pass, 100 ms apart, 10 min per relay+conversation); never quiet-room events (marked inside the sealed record).Independent review found no security hole; all seven findings fixed (bc05e29). The relay "does not keep chat" flag lives in #198 instead.
Tests: new unit suites (archive, room-archive, relay-pool, chat); typecheck;
test/room-archive.spec.ts(relay forgets, device still shows history, puts it back, newcomer reads it) chromium+firefox; related specs 29/29 chromium.Merge after #196 and #198 (all touch src/relay-pool.ts). Not in scope: Android archive, catch-up from peers/Bothy, retention rule (Part 3).
🤖 Generated with Claude Code
https://claude.ai/code/session_01CG4pPCsd8pdySNvBpt8fTk