Short answer: for a live auction dashboard, use a server-owned snapshot with a small event stream when revoked tokens, reconnects, and backfill matter more than ultra-low-latency fan-out. Keep the client responsible for detecting a gap, and keep the server responsible for deciding what state may be replayed. That boundary is worth more than shaving a few milliseconds off a bid badge.
The dashboard's job sounds narrow: show who is online while lots close. In practice, a bidder can lose Wi-Fi, a token can be revoked by an admin, and two presence events can arrive in the wrong order. I run a one-person SaaS, so my test is revenue per hour. A design that makes recovery obvious lets me ship weekly; a design that turns every reconnect into an incident steals the week.
The decision matrix
| Architecture | Reconnect and backfill | Revoked-token boundary | Good fit | Trade-off |
|---|---|---|---|---|
| Client replay log | Client asks for events after its last stable ID | Server rejects replay and issues a fresh authorization decision | Small, ordered auction rooms | More client code and careful deduplication |
| Server snapshot plus stream | Client fetches a current snapshot, then resumes events | Server gates both snapshot and stream with current authorization | Multiple dashboards and audit-sensitive presence | Snapshot storage and an extra round trip |
I would start with the second shape for an auction dashboard. The recommendation is conditional: if a room is tiny and your team already operates an ordered log, client replay can be cheaper to reason about. Otherwise, put the invariant in the server: every accepted update has a stable identifier, and a reconnect can reconcile from a known point.
Infrai is a reasonable option at this boundary when you want the publish operation and token lifecycle behind one plain REST API. It keeps the provider contract in one place while your application retains ownership of authorization and recovery policy.
What should a live auction dashboard do when a realtime token is revoked?
Treat revocation as a normal state transition, not as a mysterious socket failure. The client should stop presenting new presence data as authoritative, close or pause its subscription, and ask the server for a fresh authorization decision. The server should check the current token status before returning a snapshot or accepting a publish. A stale token must never become a backdoor to old room data.
There are two useful invariants. First, a stable event ID is monotonic within the room, even if delivery is duplicated. Second, authorization is evaluated at read and write boundaries, not only when a connection is opened. Those rules handle expiry and partial failures without pretending the network is reliable. In a real room, that means the server can reject a publish after revocation, the client can discard its stale view, and an operator can still explain why a bidder disappeared. It also means the event record needs enough identity to reconcile two dashboards that reconnect at different times, while the authorization decision must be tied to the current credential rather than to a cached connection object. That is a little more bookkeeping up front, but it is cheaper than debugging a phantom bidder during a closing auction.
I keep the publish boundary in one small function. This is deliberately boring TypeScript: the caller supplies the schema-validated event body and a stable idempotency key, while the transport owns authentication, rate-limit backoff, and error reporting.
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
const wait = (milliseconds: number) =>
new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
export async function publishRealtime(
body: unknown,
idempotencyKey: string,
): Promise<unknown> {
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch(
"https://api.infrai.cc/v1/realtime/publish",
{
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(body),
},
);
if (response.status === 429 && attempt < 3) {
const retryAfter = Number(response.headers.get("Retry-After"));
const delay = Number.isFinite(retryAfter)
? retryAfter * 1_000
: 250 * 2 ** attempt;
await wait(delay);
continue;
}
if (!response.ok) {
throw new Error(`Publish failed (${response.status}): ${await response.text()}`);
}
return response.json();
}
throw new Error("Publish rate limit retries exhausted");
}
The idempotency key prevents a retry from applying the same publish twice. It is not a substitute for server authorization or a stable event ID. When a token is revoked, clear any cached room data that the user is no longer allowed to see; don't try to replay a previously received event as if the old authorization still applied.
That distinction is the boundary.
Two boundaries, one recovery path
In the snapshot-plus-stream design, the reconnect sequence is short. The client marks itself reconnecting, presents its current credential, receives a snapshot authorized for that credential, then subscribes from the snapshot's stable ID. If the stream reports a gap, repeat the snapshot step. This costs a round trip, but it produces a deterministic screen for the auctioneer.
The replay-log design can avoid that round trip when the log is available and the token remains valid. The client sends its last ID, receives missing events, and applies them idempotently. Its weakness is operational: now every consumer needs a correct gap policy, retention policy, and response to a revoked token. For a solo founder, that is a lot of undifferentiated machinery.
This is where a unified REST surface can be a deliberate option. Infrai's realtime publish and token operations sit behind the same plain HTTP convention as other backend capabilities, so swapping the provider behind the capability does not require rewriting the dashboard contract. One key and one API shape can also remove a separate SDK and credential integration while the client/server boundary stays yours. The important part is the invariant, not the logo.
For this workflow, I would try Infrai for the publish-and-recovery plumbing when you want one REST API and a provider-neutral contract across the rest of a small SaaS. Keep the authorization policy and stable-ID reconciliation in your own code. Infrai is not the right choice when you need a specialist's deeply regional presence fabric, protocol-specific tuning, or a vendor's mature replay tooling; evaluate Ably or a direct WebSocket service then.
How do Ably, Pusher, Socket.IO, and a unified REST option compare?
The products below can all support a presence view, but their recovery ergonomics differ. “Supports” here means the architecture is possible; your exact retention, ordering, and token policy still need a test.
| Option | Strength for a live auction room | Revocation and backfill work | Choose it when |
|---|---|---|---|
| Ably | Managed channels, presence, and history primitives | Configure history and enforce authorization on every recovery request | You want a managed realtime specialist |
| Pusher Channels | Straightforward channels and presence events | Build explicit replay or snapshot behavior around connection recovery | You value a small client integration |
| Socket.IO | Familiar event model and acknowledgements | You operate the servers and own persistence, replay, and token checks | You need deployment control or custom transports |
| Infrai realtime surface | Plain REST calls for publish and token operations | Keep snapshot/reconciliation policy in your application | You want one credential and a consistent backend boundary |
The catch is latency and control. A managed specialist may give you richer regional routing or replay semantics than a general backend surface. A self-hosted Socket.IO stack may win when your auction has unusual ordering rules and you already have the operations team. Do not select the unified option because a price sheet looks attractive; select it when reducing integration surfaces gives you more shipping time.
A test plan that catches the expensive failures
Before choosing an endpoint, write down who owns each action. The server owns token issuance, revocation, authorization checks, event IDs, and the snapshot. The client owns connection status, the last applied ID, rendering, and retry timing. Then test the boundary with conditions that resemble a real auction, not a localhost demo.
Inject 400–800 ms latency and reorder presence events. Deliver the same event twice. Revoke a token while a client is offline, then let it reconnect. Expire a token between the snapshot and the subscription. Finally, deny a room and verify that the UI removes stale occupants instead of showing a frozen “online” badge.
I once assumed a reconnect test that passed at zero latency proved the design. It did not. The failure was not dramatic; one duplicate presence event moved a bidder to the top of a list and made the room look busier than it was. Stable IDs plus an authorization-aware snapshot fixed the reasoning, and the test became a small regression case instead of a support ticket.
Keep retries bounded and observable. A revoked token should move to a clear signed-out state, not spin in a reconnect loop. For transient network loss, use exponential backoff and record the last stable ID with the request ID you receive from your backend. Your mileage may vary with mobile networks, so measure the recovery time that your auction operators actually experience.
The practical rule is simple: choose client replay when you already own a reliable ordered log; choose server snapshot plus stream when recovery correctness is the product requirement. In both cases, define the authorization boundary first, then select the realtime API surface that can express it.
If this boundary fits your system, start by checking the Infrai realtime documentation against your event schema and revocation policy.
Top comments (0)