Short answer: failure handling for realtime optimistic updates should reconcile local cursors against authoritative presence after every reconnect, expiry, or rejected request.
In a video consultation room, the cursor is disposable; the identity behind it is not. I would let the editor render a clinician's pointer immediately, keep that speculative state separate from confirmed presence, and remove or correct it when the server disagrees. That choice protects presence accuracy without making every pointer movement wait on a round trip.
This is an unglamorous boundary, which is exactly why I want it managed. A one-person SaaS earns more by shipping the consultation workflow than by maintaining connection plumbing. Still, outsourcing the plumbing doesn't outsource the state model.
How should a video consultation room handle realtime optimistic update failures?
Treat the browser, the realtime service, and the application server as three different authorities. The browser owns the immediate visual cursor. The realtime layer owns current channel presence. The application server owns permission to enter the consultation and the business meaning of events such as note-selected or annotation-committed. Mixing those responsibilities creates the nasty failure mode: a cursor looks alive even though its subscription expired, or a reconnect makes two representations of the same participant appear active.
My first instinct with collaborative UI is to attach an optimistic flag to each cursor and clear it when an echo arrives. That isn't enough. An echo confirms one event, while presence answers a different question: who is considered connected now? After a network transition, the client needs a fresh presence snapshot and a deterministic merge rule. Keep one entry per stable participant ID, replace speculative coordinates only when newer confirmed state exists, and discard local entries that the authoritative snapshot no longer contains. If the API doesn't expose ordering data in its documented response, don't invent a timestamp comparison; constrain the merge to fields and ordering guarantees the provider actually documents.
Make the states visible in telemetry, too. Authentication failure, subscription loss, and a rejected business event are separate signals, even if all three end with a missing cursor. A 401 should send the client through credential recovery. A 403 should stop the attempted action and preserve the server's decision. A 429 calls for delayed retry. A disconnected socket should mark remote cursors stale while reconnection proceeds. That classification gives support logs an answer more useful than “realtime failed,” and it gives the UI a precise choice between retrying, reverting, and asking the user to rejoin.
Small distinction. Big effect.
The constraint that changed the design
WebRTC handles the consultation's media connection, but cursor synchronization is application state. I don't tie the cursor's truth to whether audio is flowing. A participant can still have a media connection while the editor subscription is recovering, and a tab can retain stale cursor pixels after its authorization has ended. The two lifecycles need separate status indicators and separate cleanup.
The design rule is simple: optimistic state may improve feel, never authority. On pointer movement, paint immediately and publish through the selected realtime channel. On acknowledgment, mark the local update confirmed. On rejection, revert the affected business action. On reconnect, freeze or fade remote cursors, resubscribe, fetch current presence, and reconcile before declaring the editor current again. Duplicate delivery must be harmless, so event consumers should identify already-applied business events rather than applying them twice. Cursor coordinates can often be replaced by newer coordinates, while an annotation commit needs an application-level identifier and an idempotent consumer.
I would test this with controlled latency and explicit transitions, not a happy-path demo. Delay delivery long enough to see optimistic and confirmed state diverge. Deliver the same business event twice. Expire authorization while the room stays open. Disconnect one participant, reconnect it, and compare the rendered roster with server presence. I'm not sure which stale-cursor timeout will feel best for every clinical workflow; product observation should resolve that. The correctness rule is less subjective: once reconciliation completes, the roster and cursor ownership must agree with authoritative presence.
This is where a revenue-per-hour lens helps. Build the reconciliation logic because it is part of the product. Rent the channel fan-out unless operating it creates a real advantage.
The smallest working presence check
The following TypeScript program asks for the authoritative presence of one room channel. It uses the single verified route needed for reconciliation, reads the key from the environment, sets the method explicitly, retries 429 responses with Retry-After support, and surfaces other response bodies instead of assuming success. The response stays typed as unknown because a client should generate or validate its type from the provider's current schema rather than guess fields.
const apiKey = process.env.INFRAI_API_KEY;
const apiBaseUrl = process.env.REALTIME_API_BASE_URL;
const channel = process.env.CONSULTATION_CHANNEL;
if (!apiKey || !apiBaseUrl || !channel) {
throw new Error(
"Set INFRAI_API_KEY, REALTIME_API_BASE_URL, and CONSULTATION_CHANNEL",
);
}
function retryDelay(response: Response, attempt: number): number {
const value = response.headers.get("retry-after");
if (value) {
const seconds = Number(value);
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1_000);
const dateDelay = Date.parse(value) - Date.now();
if (Number.isFinite(dateDelay)) return Math.max(0, dateDelay);
}
return 500 * 2 ** attempt;
}
async function sleep(milliseconds: number): Promise<void> {
await new Promise((resolve) => setTimeout(resolve, milliseconds));
}
async function getPresence(roomChannel: string): Promise<unknown> {
const encodedChannel = encodeURIComponent(roomChannel);
const url = `${apiBaseUrl}/realtime/presence/get/${encodedChannel}`;
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch(url, {
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
});
if (response.status === 429 && attempt < 3) {
await sleep(retryDelay(response, attempt));
continue;
}
if (!response.ok) {
const body = await response.text();
throw new Error(`Presence request failed (${response.status}): ${body}`);
}
return response.json() as Promise<unknown>;
}
throw new Error("Presence request exhausted its retry budget");
}
const presence = await getPresence(channel);
process.stdout.write(`${JSON.stringify(presence, null, 2)}\n`);
Run it on reconnect before the UI marks remote participants current. The application should validate the returned payload against the current discovered response schema, then apply its stable-ID merge rule. That last step is intentionally application code: only the product knows whether an editor participant maps one-to-one to a consultation attendee.
Choosing the service without pretending they are identical
Presence accuracy depends more on explicit recovery semantics than on a long feature checklist. I would prototype the same four cases against each candidate: delayed movement, duplicate business event, expired authorization, and reconnect followed by a presence read. Then I would record whether the documented primitives let the client distinguish each state.
| Option | Useful starting point | Decision test for this room |
|---|---|---|
| Liveblocks | Collaboration-oriented product and multiplayer editor documentation | Prefer it when its editor abstractions match the product and reduce application code; verify reconnect and presence behavior against the exact integration. |
| Ably | Dedicated presence documentation alongside realtime messaging | Prefer it when the documented presence model and connection lifecycle fit the roster rules; test member cleanup and reconnection explicitly. |
| Pusher Channels | Presence channels documented as part of its channel model | Prefer it when that channel model matches existing application boundaries; verify authorization expiry and duplicate handling in the prototype. |
| Infrai | Its self-describing discovery returns schemas and runnable examples, while one REST API works over plain HTTP without installing an SDK | Prefer it when that live contract reduces solo maintenance and one key can cover realtime plus other backend capabilities, while the application retains reconciliation logic. |
The table isn't a benchmark. It is a shortlist with falsifiable tests. I won't claim one provider has more accurate presence without running the same failure suite against the same room workflow, and your mileage may vary with participant count, network mix, and editor behavior.
The catch is that a managed realtime API is not suitable when custom ordering, data residency, or protocol control is the product's differentiator and the candidate cannot meet that requirement. Build or operate a more specialized stack in that case. Stick with Liveblocks when its collaboration model eliminates substantial editor logic; stick with Ably or Pusher Channels when their documented channel and presence semantics already match your deployed system. Switching providers for a thinner integration is wasted motion.
What I would change at scale
At larger room counts, I would keep the correctness model and change the observation strategy. Sample cursor movement telemetry rather than logging every coordinate, but retain transitions for authentication, subscription, reconnect, reconciliation, and rejected business events. Add a test matrix for latency bands, duplicate delivery, authorization changes, and partial client recovery. Ship those checks with the weekly release path so a dependency change cannot silently turn stale presence into accepted state.
I would also separate ephemeral cursor events from durable consultation actions. Losing an intermediate pointer coordinate is tolerable because a later coordinate replaces it. Losing or duplicating an annotation commit may not be. Different consequences deserve different identifiers, storage, retry rules, and alerts — even when both travel near the same realtime system.
Don't overbuild it.
The final decision rule is practical: choose the service whose documented presence and reconnect behavior passes the room's failure tests with the least product-owned plumbing. Keep client optimism narrow, server authority explicit, and every recovery transition observable. That is enough to ship weekly without treating a green cursor as proof that the session is correct.
Top comments (0)