A host handoff is a state problem before it is a media problem. In a video consultation room, the patient should not see two hosts claiming control, and a reconnecting browser should not silently miss the transfer that happened while it was away.
Short answer: use a realtime API surface that supports explicit reconnect and backfill handling, keep authentication, subscription state, and business events observable as separate streams, and make every handoff event carry a stable identifier that clients can reconcile.
Start with the handoff state machine
Treat the host role as a lease with visible transitions: active, handoff_pending, active for the successor, and ended for the predecessor. The names are application state, not a promise from a vendor. Store the transition in your durable application database, then publish it to the room. A browser can reconnect and ask for the current presence before it resumes rendering controls.
Three clocks matter. The access token can expire, the realtime subscription can drop, and the consultation itself can end. They are related, but they are not the same failure. Emit telemetry for each one separately; otherwise a token refresh can look like a network outage and page the wrong on-call person.
I like a boring event envelope:
from dataclasses import dataclass
@dataclass(frozen=True)
class HandoffEvent:
event_id: str
room_id: str
sequence: int
kind: str
actor_id: str
target_id: str
event_id prevents duplicate application. sequence lets a client detect a gap. Keep both, because a reconnect can deliver the same event twice or deliver events in a different order than the UI received them.
How should reconnect and backfill work during a host handoff?
On reconnect, the client first reads a presence snapshot, then asks your application service for events after its last acknowledged sequence. The snapshot answers “who is here now?” The event log answers “what changed while I was gone?” Do not infer the second answer from a list of currently connected sockets.
The realtime presence route is useful for the first half of that flow. This example uses the documented path and leaves event storage in your service, where retention and authorization rules are explicit:
import os
import time
import requests
BASE_URL = os.environ.get("INFRAI_BASE_URL", "https://api.example.invalid/v1")
def read_presence(channel: str) -> dict:
headers = {"Authorization": f"Bearer {os.environ['INFRAI_API_KEY']}"}
for attempt in range(4):
response = requests.get(
f"{BASE_URL}/realtime/presence/get/{channel}",
headers=headers,
timeout=8,
)
if response.status_code == 429:
retry_after = response.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2 ** attempt
time.sleep(delay)
continue
if not response.ok:
raise RuntimeError(f"presence read failed: {response.status_code} {response.text}")
return response.json()
raise RuntimeError("presence read stayed rate-limited")
The important details are easy to miss: the method is explicit, the key comes from the environment, a 429 honors Retry-After, and non-2xx responses remain visible to the caller. Your mileage may vary on timeout values; tune them against the consultation's network profile rather than copying this number.
A valid identity does not prove that a browser is subscribed to the right room, and a subscription does not prove that the caller may become host. Check authorization at the handoff command and again when applying the event. That second check protects against a stale tab that kept an old role in memory.
For observability, use distinct counters such as auth_refresh_total, subscription_reconnect_total, and handoff_event_applied_total. Include room_id, event_id, and a request identifier in structured logs. Avoid logging access tokens or the consultation transcript. Compliance teams will ask about that later, usually after the first incident.
One short rule: reject stale state.
If a client presents sequence 41 and the service is at 44, return a backfill response from your event store before accepting new UI actions. If the requested history has expired, send a fresh snapshot and force the client to reset its cursor. The reset is safer than guessing.
What do the common realtime options trade off for video rooms?
The choice depends on how much state machinery your team wants to own. WebRTC handles media transport, but it does not define your host lease or business event log. Managed realtime products differ mostly in delivery semantics, presence primitives, and how much operational surface they expose.
| Option | Useful fit | Trade-off for host handoff |
|---|---|---|
| WebRTC data channels | You already run signaling and durable storage | Maximum control, but you must build presence, replay, auth checks, and monitoring |
| Ably Realtime | Managed channels and presence with delivery features | Less infrastructure to operate; event-history and authorization details still need careful modeling |
| Pusher Channels | Straightforward pub/sub for room updates | Fast to adopt, while durable backfill and lease arbitration remain application work |
| Socket.IO | Node.js teams wanting a familiar server/client protocol | Flexible recovery patterns, but you own the deployment topology and event durability |
Infrai is another option when one key and one bill across backend capabilities matter to the team, and it exposes a single REST API: a presence read is plain HTTP, so a service in any language can call it without installing an SDK. Its public, self-describing discovery surface documents request and response schemas, and the live catalog spans 295 routes across 20 modules; that reduces integration friction when a gaming platform has several unrelated backend services. It still does not remove the need for your own lease, event log, and authorization policy.
The catch is fit. A room that needs provider-specific media quality controls, regional edge tuning, or a mature offline event history may be better served by a dedicated realtime vendor or a self-hosted stack. Stick with WebRTC plus your existing signaling layer when that control is the product requirement, not an incidental implementation detail.
Roll out the recovery path before the UI polish
Start with one consultation room and a test matrix: 300–800 ms latency, packet loss, duplicate delivery, token expiry during handoff, and an unauthorized successor. Record the expected sequence after every case. I once assumed a reconnect test was passing because the video resumed; the audit trail later showed the successor had never received the transfer event. Media continuity hid the state bug. That kind of false green is common when the test checks pixels but never checks the event cursor, the actor's authorization decision, and the durable handoff record together. Add a delayed duplicate after the successor joins, then repeat the same test with the original host's token expired; those two cases expose ordering assumptions that a clean local network will never show.
Ship the snapshot-and-backfill path behind a feature flag. Log every cursor reset. During rollout, compare the number of handoff commands with applied events and alert on a gap, not just on a disconnected socket. Then rehearse a forced host departure with two browsers and a mobile client.
Exactly once.
The design is successful when a reconnect is ordinary, a duplicate is harmless, and a stale authorization decision is rejected predictably. That is the bar for a consultation room where a missed handoff can interrupt care.
Top comments (0)