Files
skills/docs/networking/network/websocket-gateway/src/presence/presence.md
2026-05-29 08:20:56 +01:00

3.1 KiB

PresenceTracker

(no submodules)

Purpose

Per-gateway-instance presence map tracking which clients are online, away, busy, or offline. The gateway uses one tracker shared across rooms — presence is a property of the connection, not the room — and exposes it to listeners via the typed EventBusLike events. The tracker takes an injectable Clock so unit + property tests can drive deterministic heartbeat timeouts without vi.useFakeTimers().

Features

  • track / untrack / getPresence for the lifecycle hooks.
  • heartbeat(clientId) — the gateway's per-frame keepalive update; auto-promotes offline back to online.
  • updateStatus(clientId, status) for explicit caller-driven transitions (away / busy).
  • updateState(clientId, state) — merges arbitrary state into the per-client custom payload.
  • detectTimedOut() — sweep used by the gateway's heartbeat interval to mark clients offline once they exceed clientTimeoutMs.
  • listActive() / listAll() for read-side fan-out to listeners.

Usage: Basic Example

import { PresenceTracker } from "@suaveplan/websocket-gateway";

const tracker = new PresenceTracker(90_000);
tracker.track("alice");
tracker.heartbeat("alice");

Usage: Advanced Example

import { PresenceTracker } from "@suaveplan/websocket-gateway";

let now = 1_000;
const tracker = new PresenceTracker(5_000, () => now);
tracker.track("alice");
now = 10_000;
expect(tracker.detectTimedOut()).toEqual(["alice"]);
tracker.heartbeat("alice"); // back to online
tracker.updateState("alice", { typing: true });
tracker.updateStatus("alice", "busy");

API Reference

  • new PresenceTracker(timeoutMs: number, clock?: Clock) — clock defaults to Date.now. Implementing Clock lets you fake time.
  • track(clientId, initialState?) — registers a new client with status: "online".
  • untrack(clientId): boolean — removes a client; returns true if present.
  • heartbeat(clientId): PresenceInfo | null — bumps lastSeen, auto-promotes offline → online.
  • updateStatus(clientId, status): PresenceInfo | null — caller-driven status set.
  • updateState(clientId, state): PresenceInfo | null — merges custom state.
  • getPresence(clientId): PresenceInfo | null — returns a defensive copy.
  • listActive() / listAll() — defensive copies of non-offline / all entries.
  • detectTimedOut(): string[] — mark every client past timeoutMs as offline and return their ids.
  • isTracked(clientId) / clear().

Implementation Notes

The tracker holds presence state in-process; clustering across multiple gateway instances is out of scope and is the future job of a websocket-gateway-store-* adapter. detectTimedOut is O(n) over the tracked set — n is bounded by the concurrent-client cap and the sweep runs at the configurable heartbeatIntervalMs, so the cost stays predictable. Mutation methods return defensive copies of PresenceInfo so the caller cannot accidentally mutate internal state by holding a reference; the internal records mutate in-place for performance.