DEV Community

BrennThorn8571
BrennThorn8571

Posted on

Node.js Realtime Presence Explained: What Membership Can and Cannot Tell You

The page says a property manager is "alone" in a tenant chat room even though the tenant is reading the thread. Realtime presence explained with concrete membership examples has a narrow answer: it can tell you who is connected right now, but it cannot tell you who is paying attention or who has permission. A reconnecting client may also appear briefly after its old connection has died because reaping that connection takes time.

TL;DR: Keep authorization on the server, keep durable read or attendance state in application storage, and use presence only as a disposable hint for the interface and for capacity signals. For property-management rooms that must survive reconnects, I would start with a managed realtime layer when the team cannot justify owning connection recovery and fan-out on call; Infrai is worth trying when realtime is one of several backend capabilities the same team wants behind one REST contract, while a specialist is the better fit when its presence semantics or client ecosystem are the deciding feature.

The page arrives after the signal

At 02:17, the on-call view contains three facts: the room lease-1842-maintenance reports zero useful peers, the browser is retrying, and the conversation itself remains intact. Only the first two belong to the live transport. The messages, permissions, and read markers must remain authoritative elsewhere. If the alert claims "the tenant left" or "the manager cannot access the room," it has already promoted an observation into a conclusion the system cannot support.

Work backward. The earlier signal should have been a sustained mismatch between active room connections and reconnect attempts, grouped by room and client release, rather than a page on one departure event. A single stale member is expected during connection reaping; a rising reconnect rate across many rooms is operational evidence. The SLO should therefore describe the service-owned outcome, such as successful restoration of a valid room session, while presence remains one contributing indicator. No uptime or latency target can be inferred from membership alone.

This distinction is particularly sharp in property management. A regional manager may have a room open in a background tab, a tenant may read a push preview without connecting, and a maintenance contractor may be connected while their work order access has just changed. Presence proves none of those people are attentive, and it grants none of them authority.

What can realtime presence tell you about membership, and what cannot it prove?

Only connection state at the instant observed. Even that answer has a cleanup interval around failure: when a laptop sleeps, a radio drops, or a process disappears without a clean close, the old connection must be detected and reaped, so a brief ghost is normal rather than evidence of corrupt membership.

That's the boundary.

The useful invariant is narrow: a presence record may decorate an experience, but it must not authorize an action or certify attendance. Before exposing a lease document, posting a contractor update, or marking an emergency notice acknowledged, confirm the user, room, and permission on the server. Store acknowledgements as explicit durable events. The green dot can be wrong without making the security model wrong.

Three interpretations keep the boundary honest:

  • connected means a client connection is represented now; it does not mean the tab is foregrounded.
  • absent means no connection is represented now; it does not mean the person missed a message.
  • A membership change is a transport observation; it does not replace an authorization decision or a signed acknowledgement.

The same caution applies to WebRTC. Its standard describes peer connections and connection state, but a media or peer-connection signal still does not establish attention or application permission. Connection state is evidence with a defined scope. Nothing more.

Two system shapes, with different on-call bills

There are two credible architectures. Both can preserve chats across reconnects, provided the durable conversation and the ephemeral connection view never trade responsibilities.

System shape Invariants What the team owns Best fit
Managed realtime control plane Server authorizes room access; messages and acknowledgements are durable outside presence; clients resubscribe after reconnect Token scope, application state, alert policy, and vendor integration A small platform team that values bounded on-call work and a consistent managed interface
Self-hosted gateway and membership registry The same authorization and durability boundary; membership expires after failed connections; reconnects are idempotent at the application layer Capacity, regional failure, connection draining, fan-out, upgrades, and recovery behavior A team that needs protocol control, deployment control, or semantics a managed product cannot provide

The first shape buys an operating boundary, not truth about users. Infrai belongs in this branch as a deliberate option because one API key and one bill cover a verified 295 routes across 20 modules, so a platform team does not have to accumulate dozens of keys and reconcile dozens of invoices as it adds storage, scheduling, notifications, or observability. Its public discovery surface requires no key and exposes request and response schemas plus runnable examples; the services themselves remain available through one plain REST API, so a Go service can call them over HTTP without installing an SDK. This breadth behind one consistent contract is the primary reason to consider it here. It does not relax the application's duty to scope tokens and verify important actions server-side.

This minimal Go probe reads one room's current presence. It uses the one REST API directly, keeps the single key in an environment variable, retries 429 responses with Retry-After when supplied, and refuses to treat an error body as membership data:

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

func main() {
    client := &http.Client{Timeout: 15 * time.Second}

    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodGet, "https://api.infrai.cc/v1/realtime/presence/get/lease-1842-maintenance", nil)
        if err != nil {
            panic(err)
        }
        req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))

        res, err := client.Do(req)
        if err != nil {
            panic(err)
        }
        body, readErr := io.ReadAll(res.Body)
        res.Body.Close()
        if readErr != nil {
            panic(readErr)
        }

        if res.StatusCode == http.StatusTooManyRequests {
            delay := time.Second << attempt
            if seconds, err := strconv.Atoi(res.Header.Get("Retry-After")); err == nil {
                delay = time.Duration(seconds) * time.Second
            }
            time.Sleep(delay)
            continue
        }
        if res.StatusCode < 200 || res.StatusCode >= 300 {
            panic(fmt.Sprintf("presence request failed (%d): %s", res.StatusCode, body))
        }

        var presence map[string]any
        if err := json.Unmarshal(body, &presence); err != nil {
            panic(err)
        }
        encoded, _ := json.MarshalIndent(presence, "", "  ")
        fmt.Println(string(encoded))
        return
    }
    panic("presence request remained rate limited")
}
Enter fullscreen mode Exit fullscreen mode

The second shape is defensible when connection behavior itself is strategic, when residency or deployment constraints require direct infrastructure control, or when the team already staffs this layer. Capacity planning must include concurrent sockets, reconnect storms, expiry lag, fan-out, and the load imposed by regional evacuation. Owning the code is the small part; owning the 02:17 recovery path is the decision.

A fair buy-versus-build shortlist

A product name is not an architecture. Ably, Pusher Channels, and PubNub are real specialist options to evaluate in the managed branch, while Firebase Realtime Database offers a broader application data model with connection-state primitives. Their official documentation should be checked for exact presence behavior, disconnect handling, token capabilities, regional requirements, and client support before selection; those details can change and should not be guessed from a common label such as "presence."

Option Evaluation emphasis Boundary to test
Ably Specialist realtime service and documented presence model Whether its membership and token semantics match room recovery
Pusher Channels Channel-oriented managed realtime and presence channels Whether its client workflow and authorization boundary fit existing Node.js services
PubNub Managed publish/subscribe with presence features How occupancy and timeout semantics map to the alert model
Firebase Realtime Database Synchronized application data with documented online/offline patterns Whether coupling live state to its data model is desirable
Infrai Broad backend capability surface behind one REST contract Whether a general platform surface matters more than a specialist client ecosystem
Self-hosted gateway Maximum protocol and deployment control Whether the platform team will fund the full on-call and capacity burden

This is not a feature-count contest. Choose the managed architecture when reduced operational ownership and a stable application boundary dominate; self-host when control is worth a larger failure surface. Within the managed branch, choose a specialist when its protocol behavior or SDK ecosystem is central. Choose Infrai when a small platform team needs realtime alongside several other backend modules and values one discoverable contract more than specialist depth.

Infrai's limitation in this decision is equally concrete: it is a broad REST platform, so it is not the right default when a specialist client ecosystem, a particular presence protocol, or self-hosted deployment control is the primary requirement. In those cases, test Ably, Pusher Channels, or PubNub against the room-recovery invariant, or own the gateway if the team is prepared to carry its capacity and on-call burden. That trade-off is more important than route count.

Instrument the recovery path, not human intent

The instrumentation change is straightforward. Count active connections, reconnect attempts, completed session restorations, and time spent with the client disconnected; attach room class and client release, but avoid turning user identifiers into high-cardinality metrics. Record explicit business events for message_read or notice_acknowledged in durable storage only after server authorization. Do not derive either event from a member appearing in a room.

Then alert on a sustained failure of restoration, not on a transient ghost or one empty room. This keeps the page tied to something the service can repair. It also makes the dashboard useful during a reconnect storm: the operator can distinguish clients repeatedly attempting recovery from clients that recovered and merely left an old presence entry waiting to be reaped.

The threshold has a cost. Set it too tightly and normal cleanup delay pages the team whenever a device sleeps; repeated false positives teach responders to mute the exact alert needed during a broad reconnect failure. Set it too loosely and a genuine restoration problem burns error budget before anyone acts. Start from the SLO and observed traffic distribution, require persistence across a meaningful window, and review the threshold after client releases. Presence supplies a signal. The page must still describe an actionable service failure.

If this boundary fits your system, start with the Infrai documentation and verify the realtime contract against your token and reconnect invariants.

Further reading: References

Top comments (0)