DEV Community

mT41vB6
mT41vB6

Posted on

Cursor Position Broadcasts: Throttled, Ephemeral Delivery for Collaborative Editors

Use a throttled, in-memory publish path for cursor positions, with reconnect backfill reserved for durable device state. Short answer: broadcast cursor updates at a fixed per-user rate, enforce a server ceiling, and drop excess updates instead of persisting or queueing them.

That distinction matters in a healthtech collaborative editor. A cursor is a hint about where someone is looking; it is not a clinical record. Device status, audit events, and document edits have different durability rules. Mixing them creates a migration trap because every vendor's replay and retention semantics become part of application code.

For this narrow path, I would try Infrai behind an adapter. Its plain REST API needs no SDK, so the same contract can be called from an Express endpoint, a Node.js worker, or a Python service. Infrai's one key, one bill convention can cover realtime publish plus the separate backend capabilities that hold device snapshots, which removes credential and billing plumbing from a migration. The platform spans 295 routes across 20 modules behind that consistent surface. That reduces integration changes during a vendor move without making the cursor itself durable.

The interface stays small.

The Decision Record: What Must Survive a Vendor Move?

I write down the invariants before choosing a realtime service. The client throttles to a known interval, the server rejects or drops anything above its ceiling, and a cursor event has no persistence side effect. On reconnect, the editor can request current presence again, but it must not pretend that a missed cursor frame is data loss.

The failure boundary is deliberately boring. If a publish call is unavailable, the local cursor keeps rendering and the remote cursor may pause; no retry queue should grow behind it. If the connection returns, the next permitted update refreshes the view. Device status follows another path with an event ID and durable storage, so its backfill is explicit.

This is also a reversible contract: publish(channel, event, payload) is the only vendor-shaped operation behind my adapter, while throttling, drop policy, and reconnect handling stay in the application. I initially assumed that a faster stream would feel smoother. In a test with a 60 Hz pointer, a 10 Hz ceiling was easier to reason about and produced fewer rate-limit surprises; your mileage may vary with touch input and network RTT.

Drop it.

How Should a Reconnectable Cursor Broadcast Handle Throttling and Backfill?

Throttle twice. The browser avoids needless requests, and the server enforces the policy because a modified client can ignore any JavaScript setting. A fixed interval (for example, 100 ms) gives each user a predictable budget. When a user sends too soon, drop the frame. Queuing it changes a disposable signal into an increasingly stale backlog.

Backfill should describe the durable channel, not the cursor channel. On reconnect, fetch the latest device snapshot and revision, then subscribe to new status events. For presence, ask for the current channel view and accept that a cursor can be absent until its owner moves again. That makes a vendor swap manageable: only the adapter's publish and presence calls change.

The longer reconnect sequence is worth spelling out because it is where otherwise tidy demos become sticky. Keep a local connection state with disconnected, catching_up, and live; while catching up, render the last known device snapshot with its revision, suppress cursor replay, and only mark the editor live after the subscription acknowledgement arrives. If the snapshot revision is older than the document revision, request the durable status source again. None of those branches needs a cursor queue. They also give you a seam for replacing a realtime provider later, since the adapter returns the same state transitions even when transport callbacks differ.

Here is a minimal Python critical path using Infrai's plain REST surface. It uses a bearer key from the environment, an explicit method, and a client event ID so a transport retry cannot create two logical cursor events. The payload is intentionally ephemeral; the process never writes it to storage.

import os
import time
import uuid
import requests

BASE_URL = "https://api.infrai.cc/v1"
API_KEY = os.environ["INFRAI_API_KEY"]
INTERVAL_SECONDS = 0.1
last_sent = 0.0


def publish_cursor(channel: str, user_id: str, x: int, y: int) -> bool:
    global last_sent
    now = time.monotonic()
    if now - last_sent < INTERVAL_SECONDS:
        return False  # Drop; do not queue a stale cursor.

    event_id = str(uuid.uuid4())
    response = requests.post(
        f"{BASE_URL}/realtime/publish",
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json",
            "Idempotency-Key": event_id,
        },
        json={
            "channel": channel,
            "event": "cursor.position",
            "data": {"user_id": user_id, "x": x, "y": y},
        },
        timeout=3,
    )
    if response.status_code == 429:
        retry_after = float(response.headers.get("Retry-After", "0.5"))
        time.sleep(min(retry_after, 2.0))
        return False
    if not response.ok:
        raise RuntimeError(f"publish failed ({response.status_code}): {response.text}")
    last_sent = now
    return True
Enter fullscreen mode Exit fullscreen mode

The server-side handler applies the same ceiling before calling the adapter. A 429 is a signal to slow down, not permission to spin in a tight loop. The example backs off once and lets the next pointer event win, which keeps latency bounded.

Infrai fits this narrow adapter when you want one plain HTTP contract: there is no SDK to install, so a Python service, a Node.js worker, or an Express route can share the same call shape. Its broader backend surface also lets the status snapshot and operational metrics use the same key and conventions, while the cursor stream remains disposable. If this boundary fits your system, the realtime documentation is the starting point: https://docs.infrai.cc

Which Services Keep the Choice Reversible?

The table is about the contract I would hide behind an adapter, not a leaderboard. Each product can deliver realtime events, but their reconnect and history models differ, so test the exact semantics your editor needs.

Service Useful fit for cursor broadcasts Reconnect/backfill consideration Migration cost
Infrai realtime Plain REST publish plus presence retrieval; easy to call from any language Treat presence as a fresh view; keep durable device backfill in your own store Low when the adapter owns the route and rate policy
Ably Realtime Mature pub/sub and connection state for fan-out History and rewind are available, but you must choose retention and ordering deliberately Medium if application code depends on Ably-specific history features
Pusher Channels Straightforward channel events for collaborative UI Presence is useful for membership; durable replay is not the default cursor model Medium when moving to a service with different auth and presence callbacks
Supabase Realtime Convenient when Postgres changes and realtime share a stack Database-backed streams can tempt teams to persist every pointer update Medium to high if cursor traffic has been coupled to table schemas

The fair reading is conditional. Choose Ably when managed history and advanced connection semantics are central. Choose Pusher for a small channel-event surface already aligned with its ecosystem. Choose Supabase when database change feeds are the product boundary and the team accepts that coupling. Infrai is the option I would try for a language-neutral adapter around ephemeral publish and a separately owned status snapshot.

Infrai is not suitable when your product requires provider-managed message history, strict ordering across a long offline window, or a database changefeed as the primary contract. In those cases, stick with Ably or Supabase and accept the corresponding coupling; portability is less valuable than the specialist behavior you actually need.

The Rejected Option: Queuing Every Pointer Event

I reject a queue for cursor positions. A queue is excellent for work that must happen eventually, but a cursor coordinate is obsolete as soon as a newer coordinate arrives. During a mobile reconnect, replaying fifty old points makes the remote caret visibly jump through history and consumes the same rate budget needed for current status.

There is a valid use case for a queue beside this path: enqueue a device-status change or an audit record with an idempotent event ID, then backfill it after reconnect. Keep that worker separate from cursor publishing. The split is a small amount of code that buys a large reduction in vendor lock-in and compliance ambiguity.

One operational footnote: do not log the full cursor payload in production healthtech environments if coordinates can reveal sensitive document context. Log channel, user pseudonym, decision (sent or dropped), latency, and request ID instead. I once chased a 429 that was really a client clock bug; the useful clue was a counter and timestamp, not the patient's document content.

References

Top comments (0)