DEV Community

BramwellVance7953
BramwellVance7953

Posted on

Node.js Express Presence: Scoped Access and Reconnect Recovery for Property Tours

Short answer: for an Express endpoint that answers who is online in a document, issue a room-scoped credential, take an authorized roster snapshot, and then reconcile membership changes over a live connection. In a property-management workflow where an agent opens a video room alongside a shared inspection document, reconnect must trigger a fresh snapshot or a replay from a known cursor. A socket reconnect alone cannot tell you who joined while the client was away. The trade-off is extra recovery state versus a roster that can be audited against an explicit point in time.

What does a reconnect erase?

Consider a bounded failure drill, not a claim about a particular production incident: an agent opens the inspection document for unit 4B, joins its tour room, then loses connectivity while a tenant leaves and a maintenance coordinator joins. The agent's browser still displays the tenant as online. I would treat that as an SLO question about roster freshness, not as proof that the tenant remains in the call. The invariant is narrower: a displayed roster describes a specific authorized scope and a specific observed revision; once its connection drops, the client must mark that view stale until it has reconciled.

Stale is a state, not a person.

There are three separate identities here. The authenticated person is authorized by the application. A connection is an ephemeral transport instance. A room member is a server-observed, time-bounded projection of one or more connections. Conflating these leads to duplicate names across tabs and to a departed person lingering after a network break. Presence also cannot certify attention or actual camera participation. WebRTC defines peer connection behavior; it does not define an application-wide roster for your inspection document. If the agent opens a second tab, decide whether the roster lists two connections or one person before writing any counting query. If a mobile browser suspends its connection during the tour, avoid interpreting that transport event as a deliberate departure from the property workflow. A UI can show a recovering state while the server settles its membership view, but it must not imply that a previously observed member is currently connected merely because the last displayed array still contains their name.

The page may contain a document editor and a video room, but neither scope should inherit the other's access by accident. The credential should bind the authenticated subject to the property, document and room identifiers, with an expiry and explicit permissions. The Express application must check authorization before handing out a roster or a credential. A room identifier supplied by a browser is a lookup key, not permission.

Check both scopes.

Snapshot, replay, or reset?

I would choose a reconciliation contract before choosing a broker. A snapshot returns members and a monotonically increasing revision for one room; each membership event carries a subsequent revision. On connection, establish a subscription with an ordered cursor, obtain a snapshot at a known revision, and apply only later events. The server must define how the subscription and snapshot overlap, because fetching the snapshot first and subscribing afterward leaves a gap. If an event arrives during the snapshot, buffer it and discard revisions already represented by the snapshot.

That gap matters.

On reconnect, request events after the last applied revision. If that cursor is older than the retained log, or if the server cannot prove continuity, replace the local roster with a new snapshot. Do not silently skip to the newest event. This rule is about membership, not durable document edits: document changes need their own persistence and conflict-resolution contract. An ordered roster event stream by itself cannot repair missing document operations.

The fallback is a full read.

Recovery model Operational consequence When it fits
Fresh snapshot on every reconnect Simple correctness boundary; snapshot reads grow with reconnect volume Small rooms and short rosters
Bounded event replay with snapshot fallback Requires retention, cursor validation and gap handling Larger rooms or frequent interruptions
Periodic polling alone Bounded by poll interval; no immediate notification Interfaces that tolerate stale status

For capacity planning, measure room cardinality, reconnect rate, snapshot size, event-log retention and how often recovery falls back to a full read. The buy-versus-build decision turns on who owns cursor ordering, expiry and on-call diagnosis. A managed stream can remove some infrastructure work, but it does not decide your application's authorization scope or what an "online" label means. A self-hosted stream gives control over retention while also putting replay continuity and operations on your team. Neither changes the invariant.

How can Express get document presence and show who is online?

Keep the Express handler thin: authenticate the caller, authorize their access to the property document and tour room, then call a presence service for a versioned roster or issue a short-lived credential limited to that scope. The following Go fragment specifies the service-side recovery contract; it is not an Express drop-in or a claim about a vendor SDK. The cursor belongs to the room, and a failed continuity check returns a replacement snapshot rather than a partial roster.

package presence

import "context"

type Member struct {
    UserID string
    Role   string
}

type Snapshot struct {
    Revision uint64
    Members  []Member
}

type Event struct {
    Revision uint64
    Member   Member
    Joined   bool
}

type Store interface {
    Snapshot(context.Context, string) (Snapshot, error)
    After(context.Context, string, uint64) ([]Event, bool, error)
}

func Recover(ctx context.Context, store Store, room string, cursor uint64) (Snapshot, []Event, error) {
    events, continuous, err := store.After(ctx, room, cursor)
    if err != nil {
        return Snapshot{}, nil, err
    }
    if continuous {
        return Snapshot{Revision: cursor}, events, nil
    }
    snapshot, err := store.Snapshot(ctx, room)
    return snapshot, nil, err
}
Enter fullscreen mode Exit fullscreen mode

The caller still has to authorize room before calling Recover, order and deduplicate events by revision, and reject a replay whose events do not start immediately after the cursor. The store's continuous result must mean that no intervening revision has expired, not merely that it found at least one event. If an implementation cannot provide that guarantee, always return a full snapshot after reconnect. Briefly mark the roster as reconnecting while this happens; stale names must not be presented as current.

No continuity, no replay.

How would I test the failure, and when is this too much?

Test the boundary with two tabs for one user, concurrent joins during the initial snapshot, a disconnect spanning a leave and join, an expired cursor, a credential scoped to another property's room, and a revoked user's reconnect. Assert the final member set, revision continuity and rejection behavior, rather than only checking that a socket opens. Record reconnect-to-fresh-roster latency against a chosen SLO and track snapshot fallbacks, authorization denials and gaps by room without logging credential contents. A deploy that changes membership event format needs a migration or an explicit forced snapshot for older clients.

Not every editor needs a retained event log. When rooms are small, membership is advisory, and fresh snapshots are cheap at the expected reconnect rate, the simpler snapshot-on-reconnect contract is often preferable. Keep document persistence separate, scope every read and credential, and make stale status visible. That is enough to answer "who is online" honestly without claiming that a lost connection preserved knowledge it never received.

References

Sources

Top comments (0)