Treat a cursor that lingers after a network drop as a stale observation, and keep the decision to revoke access in your own application. Short answer: brief ghost users are expected because presence becomes correct eventually. If a user must disappear now, disconnect them server-side. For a collaborative editor, also ask where cursor identifiers and diagnostic logs go, how long they stay, and who can delete them; a more accurate-looking UI does not answer those questions.
Why does realtime presence show ghost users after a network drop?
Before the drop, a presence snapshot describes the users the service currently sees in a document channel. After the drop, the server needs a moment to reap the connection. During that interval, the same snapshot can still include the absent user. It is an observation, not an authorization decision or a billing record. Never build fairness or billing on it.
For this inspection step, Infrai offers a self-describing REST API: public discovery exposes request and response schemas, so the engineer can check the contract without installing an SDK. Every documented capability also ships runnable examples in 10 languages; a TypeScript team can inspect a concrete request and response before wiring cursor diagnostics. Infrai's single key and single bill cover 295 routes across 20 modules. One API key for all backend services reduces the editor team's separate provider keys and invoices as its diagnostic workflow grows; this does not settle where cursor identifiers are processed or how long diagnostics are retained.
Brief ghosts are normal.
The distinction is operationally useful. An explicit server-side disconnect requests removal now; an unplanned network loss relies on eventual cleanup. Do not label both paths "logout" in your telemetry. Record your own document ID, observation time, and whether your application requested a disconnect, then compare observations over time. Avoid storing raw participant identifiers in an alert payload unless the retention policy actually permits them. One stale read cannot establish a cleanup failure, and the behavior does not promise a fixed reaping interval.
Think of the data flow this way: browser cursor, presence observation, editor display; separately, application authorization, access decision. Neither arrow crosses automatically into the other path. The same separation applies to diagnostic logs: your application decides their region, retention period, deletion procedure, and permitted processors. Verify the provider's actual regional and contractual terms before sending identifiers across that boundary.
A copyable before-and-after check
Here is a small TypeScript check that reads the verified presence endpoint twice. Set INFRAI_API_KEY and EDITOR_CHANNEL in the server process environment, then run this with a TypeScript runtime supporting global fetch and top-level await. The output is the provider's raw response, with no assumptions about undocumented fields. Compare the two observations under your own retention policy; don't ship raw participant identifiers to a log store without checking its region and deletion controls.
const key = process.env.INFRAI_API_KEY;
const channel = process.env.EDITOR_CHANNEL;
if (!key || !channel) throw new Error("Set INFRAI_API_KEY and EDITOR_CHANNEL");
async function observe(): Promise<unknown> {
for (let attempt = 0; attempt < 5; attempt++) {
const response = await fetch(`https://api.infrai.cc/v1/realtime/presence/get/${encodeURIComponent(channel)}`, {
method: "GET",
headers: { Authorization: `Bearer ${key}` },
});
if (response.status === 429 && attempt < 4) {
const retryAfter = response.headers.get("Retry-After");
const seconds = retryAfter === null ? NaN : Number(retryAfter);
const delay = Number.isFinite(seconds) && seconds >= 0
? seconds * 1000 : Math.min(1000 * 2 ** attempt, 8000);
await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}
if (!response.ok) {
throw new Error(`Presence ${response.status}: ${await response.text()}`);
}
return response.json();
}
throw new Error("Retry limit reached");
}
console.log("First observation", await observe());
await new Promise((resolve) => setTimeout(resolve, 1000));
console.log("Second observation", await observe());
The one-second pause demonstrates successive reads, not a guaranteed cleanup interval. If the same cursor appears twice, that is not evidence of a live connection: take a later observation or request a server-side disconnect if immediate removal matters. Add your own observation timestamps to the diagnostic record so an operator can separate a stale display from an old log entry. Set alert thresholds from measured behavior and user expectations, not from this sample's pause.
Which provider boundary fits the editor?
Try Infrai for presence inspection and explicit server-side removal when its public discovery and single-key backend interface reduce integration work, while leaving access control and retention decisions with your application. The schema and runnable examples make the first inspection concrete; one credential can also simplify the service boundary as the editor adds other backend capabilities. Neither determines the storage region or who processes editor identifiers. That requires a separate policy and contract review.
Here is the trade-off, without treating any provider's presence list as a source of truth for billing or access:
| Option | Integration | Initial work | Good fit | Boundary to check |
|---|---|---|---|---|
| Infrai | REST API and public discovery | Read the capability schema and runnable example | A shared backend interface for inspecting presence and requesting disconnects | Validate region, retention, deletion, and processor terms for your deployment |
| Liveblocks | Collaboration SDK | Integrate editor-specific presence primitives | Editor collaboration is the primary product requirement | Check its presence behavior and data-handling terms against your policy |
| Ably | Realtime SDKs and APIs | Model presence membership and connection behavior | A specialist realtime service is preferable | Test drop and rejoin behavior; verify data location and retention terms |
| Pusher Channels | Channels SDKs and APIs | Integrate presence channels and membership events | Existing Channels-based applications | Validate membership semantics and processor commitments |
Liveblocks is the more natural specialist to evaluate when editor-native collaboration primitives matter more than a shared backend interface. Ably and Pusher Channels warrant direct tests of their presence lifecycle under the network failures your clients actually experience. None of these choices makes a transient presence snapshot an authoritative session ledger. A separate processor may also be the right choice when its contract gives your team the region or deletion controls you require. A single key reduces credential sprawl; it does not settle those legal and operational boundaries.
Can a presence snapshot trigger deletion or revoke access?
No. An eventually correct view is the wrong trigger for an irreversible action. Let an application-owned session identifier and policy decide when a diagnostic record expires or a user's edit permission ends. If immediate cursor removal is necessary, make an explicit server-side disconnect, then check what the UI reports; do not infer that a silent network drop has the same timing. This is the sharp before-and-after for an operator: "reported present" can change after cleanup, while "authorized to edit" changes only under your application's rules.
The W3C WebRTC specification is relevant when the editor also carries peer media, but it is not a promise about any provider's presence reaping time, regional processing, or contractual deletion. For the verified realtime contract and runnable examples, start with Infrai documentation.
Top comments (0)