03 / Real-time systems
Real-time advisory co-browsing
Two browsers, two cities, one source of truth — and a mid-call refresh that has to recover rather than desync.
- Live-mirrored a 25-step advisory workflow across 52 paired screens with zero page refreshes.
- Multiplexed 7 message verbs over a single namespaced socket connection.
- Snapshot-then-delta hydration, so a late joiner or mid-call refresh recovers instantly.
- Handshake-time authentication with role-tagged connections and sender verification.
- Paired listener cleanup and StrictMode-safe idempotency guards across 55 emit sites.
- ROLE
- REAL-TIME ARCHITECTURE & IMPLEMENTATION
- TIMEFRAME
- RECENT
- STACK
- REACT 18 · TYPESCRIPT · SOCKET.IO · NODE.JS
01 / Context
An advisor and a client work through a tax engagement together while apart. The advisor drives; the client has to see exactly what the advisor sees, at the moment they see it, and act on it themselves.
Video screen-sharing answers a different question. It shows pixels, not state — the client cannot type into it, it degrades on poor connections, and it leaves no structured record of what actually happened in the workflow.
So the requirement was not a broadcast. It was a shared, live, interactive state across a 25-step workflow spanning 52 paired screens.
02 / Constraint
The obvious implementation — a channel per concern — multiplies everything that is hard about real-time. Every channel is another ordering guarantee to reason about, another authentication path, another reconnect path, and another way for two clients to drift apart.
Real sessions are also messy. Someone joins late. Someone's laptop sleeps. Someone hits refresh in the middle of step 14. Any of these can leave two screens confidently showing different truths, which in an advisory context is worse than showing an error.
React 18's StrictMode adds its own hazard: effects run twice in development, so any listener registration or bootstrap request written naively duplicates itself, and duplicated socket handlers are exactly the bug that surfaces in production rather than locally.
03 / Decisions
Collapse the transport to one namespaced Socket.IO connection and multiplex over it. Seven message verbs travel inside a discriminated-union envelope, so the protocol has one ordering guarantee, one authentication path, and one reconnect path instead of N — and TypeScript can exhaustively check every verb at the boundary.
Solve the late-joiner problem with a pull-based hydration handshake rather than by replaying history. A client asks for current state on mount, receives a full snapshot, and only then begins consuming incremental deltas. Reconnection is the same code path as first connection, which means the recovery path is exercised constantly instead of only in incidents.
Authenticate at the handshake, not after it. `autoConnect: false` plus credentials on the socket handshake means an unauthorized connection is refused before it exists; connections are role-tagged, and receivers independently verify the sender rather than trusting the payload.
Treat async lifecycle as a first-class concern: every `on` paired with its `off`, independent bootstrap I/O parallelized with `Promise.all`, in-flight requests cancellable, and idempotency guards so StrictMode's double invocation is a non-event.
04 / Outcome
The advisor and client stay in lockstep across all 52 screens and 55 emit sites, with no page refreshes and no manual resynchronization.
A mid-call refresh now recovers in a single round trip instead of silently desyncing — the failure mode that would have quietly damaged trust in the product.
The durable win is the protocol shape. One connection carrying a typed, discriminated envelope means adding the eighth verb is a small, checked change rather than a new channel with its own lifecycle to get wrong.