Presence

Who is on a channel right now, over HTTP — and how that differs from the live view.

The snapshot

GET /api/channels/:channel/presence

Capability: presence on that channel.

{
  "members": [
    {
      "clientId": "alice",
      "connectionId": "conn-…",
      "data": { "status": "online" },
      "updatedAt": 1710000000000
    }
  ],
  "source": "store"
}
curl "$BLACKEVIN/api/channels/ops/presence" -H "authorization: $AUTH"
const members = await rest.channels.get('ops').presence.get();

That source field

It tells you which view answered, and it matters.

sourcewhere it came from
storethe shared presence table — every node's members
memorythe answering node's own registry only

memory is the fallback when no shared store is configured. On a single node the two agree, so it is only a warning when there is more than one.

Two views of presence

Blackevin keeps presence in two places, on purpose.

Live events are held in each node's memory and forwarded to interested peers. That is what reaches a subscriber over the socket, and it is immediate.

The REST snapshot reads a shared store, written by the node the event originated on. That is what this endpoint returns, and it is what makes the answer correct across a cluster.

The consequence: a multi-node deployment needs a shared store (Postgres) for this endpoint to see everyone. Without one, each node answers with the members that happen to be connected to it, and labels that honestly as memory.

Not a scatter-gather

This endpoint does not ask the other nodes. It reads one store. That keeps the call O(1) in cluster size and its latency independent of the slowest peer — at the cost of being exactly as fresh as the store, rather than as fresh as the sockets.

For a live view, subscribe over the socket:

channel.presence.subscribe((member, action) => { /* … */ });

See client presence.

History

The enter / leave / update log is a separate endpoint, and checks history rather than presence — see REST history.

On this page