feat(backend): resume protocol with missed ephemeral-event replay#278
Open
Olorunfemi20 wants to merge 1 commit into
Open
feat(backend): resume protocol with missed ephemeral-event replay#278Olorunfemi20 wants to merge 1 commit into
Olorunfemi20 wants to merge 1 commit into
Conversation
|
@Olorunfemi20 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
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.
Summary
Adds a resume protocol so a reconnecting device can recover the lightweight, non-durable events it missed while offline, then fall back to a full envelope sync for durable messages.
Ephemeral events (read/delivery receipts, presence changes, system notices) are appended to a short-lived Redis stream as they are emitted live. On reconnect the client sends
resume { lastEventId }; the gateway replays everything recorded after that id and then tells the client to run a message/envelope sync. Durable chat messages live in Postgres and are deliberately never written to the stream, keeping the resume path cheap and bounded.closes #200
Design
services/resumeStream.tsowns the stream. A per-user Redis stream (resume:events:<userId>) is trimmed withMAXLEN ~ 500and expires after 300s of inactivity, so it stays small and self-cleaning. Redis stream ids are monotonic and unique, which makes them the naturallastEventIdclients persist.(<lastEventId>), so re-issuingresumewith an advanced cursor never re-delivers an event the client already saw. Combined with stable per-event ids (clients can dedupe), replay is idempotent.lastEventIdand resumes independently, without the server having to fan ephemeral events into a separate stream per device. This avoids extra bookkeeping while delivering exactly the "each device resumes from its own position" behaviour.resume_completealways carriessyncRequired: true.Changes
services/resumeStream.ts(new):recordEphemeralEvent,publishEphemeral(best-effort fan-out, no-op when Redis is down or the recipient list is empty),readMissedEvents(exclusive range + tolerant payload parsing), pluseventStreamKeyand the TTL/MAXLEN constants.socket/messaging.ts:resumehandler — replays missed events asephemeral_replay { id, type, data }, then emitsresume_complete { lastEventId, syncRequired: true }. Falls back to a sync-only completion when Redis is unavailable.message_readnow also records theread_receiptto each member's resume stream (guarded so the member lookup is skipped entirely when Redis is unavailable).index.ts: presence changes on connect/disconnect are recorded to co-members' resume streams via a smallrecordPresenceForCoMembershelper, so members offline at the moment of the change replay it on reconnect.Acceptance criteria
resumereplays receipts and presence recorded while the device was offline.resume_completeinstructs the client to run a full envelope sync.Testing
src/__tests__/resumeStream.test.ts: stream key namespacing, capped append + TTL refresh + returned id, recipient de-duplication, no-op when Redis is null / recipients empty, full vs exclusive-cursor reads, and malformed-payload tolerance.src/__tests__/resume.socket.test.ts: replay of missed events with sync signal, exclusive-cursor idempotency (nothing missed leaves the cursor unchanged), and missing-lastEventIdhandled as a full replay.tsc --noEmitclean, ESLint clean (no new warnings), Prettier clean.