Presence
Who is on a channel right now, over HTTP — and how that differs from the live view.
The snapshot
GET /api/channels/:channel/presenceCapability: 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.
source | where it came from |
|---|---|
store | the shared presence table — every node's members |
memory | the 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.