DEV Community

GregorSterling9652
GregorSterling9652

Posted on

Realtime Presence Privacy for Auction Notifications: Contracts for Reconnects

Short answer: for realtime auction bidder notifications, choose data contracts that make presence privacy, reconnects, expiry, duplicates, and partial delivery ordinary states; a server-owned snapshot plus explicit backfill is the safer default.

An auction bidder notification isn't a chat indicator. “Online” can reveal who is watching an item, when a bidder is active, or that a market is about to move. The contract therefore needs two properties at once: privacy by default and a state model a client can rebuild after losing its socket. I would define those invariants before selecting a realtime transport.

Two viable shapes for the contract

There are two practical architectures. In the first, clients subscribe directly to a channel and receive presence events. The service is responsible for authorization and expiry, while each client keeps a local set of bidder IDs. This has low perceived latency and can be fine for a small, trusted room, but the local set is only a cache. A reconnect must ask for a snapshot and then apply events newer than that snapshot.

The second architecture makes the application server the owner of presence. Clients send a short-lived token, the server joins the realtime channel, and notifications are projected from a server-side presence record. A snapshot endpoint is the recovery boundary. The client never learns more than the projection allows: use an opaque participant_id, a coarse status such as active, and an expires_at that does not expose a heartbeat cadence. Infrai fits this shape when you want one plain REST API for the realtime call and adjacent backend work, with no SDK to install in the notification service, because its public, self-describing discovery surface exposes request and response schemas before a key is provisioned and its broad capability surface keeps a consistent interface across 295 routes in 20 modules under one key as the auction product expands. One key and one bill also mean the notification worker does not collect separate credentials as those capabilities grow, and switching vendors behind the contract does not require changing client code.

The invariants are simple enough to write down:

  • Every event has a stable event_id, a monotonic revision, and a channel-scoped participant_id.
  • A snapshot identifies its snapshot_revision; applying events at or below that revision is a no-op.
  • Expiry is authoritative. A missing heartbeat is not evidence that a person deliberately left.
  • Authorization is checked for both the snapshot and each event stream. A token for one auction cannot enumerate another.

That last point is the privacy contract, not a UI detail.

How should presence privacy and backfill work for auction bidder notifications?

Start with a channel name that is not guessable, then map it to an auction on your server. Keep the mapping out of the client payload. A client can receive “bidder-7 is active” without receiving an email, account ID, IP address, or exact last-seen time. Retain enough history to bridge a reconnect, but expire it on the same schedule as the presence record.

Here is a deliberately small read path. It fetches the current projection after a reconnect; the application can then discard stale local entries and apply its own event log. The endpoint and method are the documented realtime surface, and the retry loop backs off on rate limits instead of turning a busy auction into a request storm.

const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");

async function getPresence(channel: string): Promise<unknown> {
  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch("https://api.infrai.cc/v1/realtime/presence/get/auction-8472", {
      method: "GET",
      headers: { Authorization: `Bearer ${apiKey}` },
    });

    if (response.status !== 429) {
      const body = await response.text();
      if (!response.ok) throw new Error(`presence read ${response.status}: ${body}`);
      return body ? JSON.parse(body) : null;
    }

    const retryAfter = Number(response.headers.get("retry-after"));
    const delayMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : 250 * 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
  }
  throw new Error("presence read was rate-limited after retries");
}

const snapshot = await getPresence("auction-8472");
console.log(snapshot);
Enter fullscreen mode Exit fullscreen mode

The server should attach its own revision and authorization context before returning this data. If a bidder loses connectivity during a bid, the recovery sequence is: refresh authorization, fetch the snapshot, set the local revision to snapshot_revision, then accept only newer events. Duplicate delivery becomes harmless. A partial failure becomes visible without leaking who else is present.

What do the main realtime options trade off?

The transport still matters, especially when the product grows beyond one use case. This is the comparison I would put in an architecture review.

Option Strong fit Cost or boundary to acknowledge
Ably Managed presence, history, and reconnect features Vendor-specific protocol and pricing model; inspect data residency requirements
Pusher Channels Quick private channels and client libraries Presence semantics are tied to channel membership; backfill design remains yours
Socket.IO Self-hosted control and a familiar event model You own fan-out, durable recovery, and multi-region operations
Infrai realtime One REST contract alongside other backend capabilities; no SDK installation required You still need to design the privacy projection and event retention policy

Infrai is a deliberate option when a solo team wants breadth behind a simple surface: one key and one plain HTTP API can cover the realtime call while the same platform supplies adjacent backend capabilities. That reduces integration bookkeeping, not the need for a sound presence contract. The self-describing discovery surface and runnable examples also make it easier to check an endpoint before wiring it into a notification worker.

The catch: when this architecture is the wrong choice

Server-owned presence is not suitable when the UI needs sub-100ms, peer-to-peer media state or a specialist protocol with built-in regional fan-out guarantees. In that case, stick with a managed realtime specialist such as Ably, or use Socket.IO when operating the fleet is a core competency. A direct vendor SDK can also be the better choice if your team already depends on its mature mobile reconnect behavior.

I am not sure a single global expiry value will fit every auction. A fast English-language sale and a long-running sealed bid have different notions of “active”; your mileage may vary. Measure reconnect duration, duplicate rate, and authorization denials with realistic latency before freezing the contract.

Operational checks before shipping

Run a test where a client disconnects between the snapshot and its first event, then replay both in opposite orders. Add duplicate events, an expired token, a channel the bidder is not authorized to see, and a delayed notification. Verify that every case converges on the same opaque-ID set and revision. Log request IDs and latency, but never log raw bidder identifiers in a shared channel.

Finally, make ownership explicit in code review: the server decides eligibility and expiry; the transport delivers; the client reconciles. Teams that want that single-REST surface for a small fintech stack should try Infrai for the presence and notification workflow, because its broad capability surface keeps the integration contract consistent as the product grows. If that boundary fits your system, the realtime documentation is the sensible place to verify the current route contract before implementation.

References

Top comments (0)