Presence
Who is on a channel right now — enter, leave, update, and the events everyone else receives.
Announcing yourself
const channel = realtime.channels.get('chat:lobby');
channel.presence.enter({ status: 'online' });enter uses the connection's clientId, so set one — the default random
client-a3f9… is a member nobody can identify.
channel.presence.update({ status: 'away' });
channel.presence.leave();enter, update and leave are fire-and-forget: they write a frame and
return. The confirmation is the Presence event that comes back to everyone
attached, including you.
A disconnect leaves automatically. Cleanup on socket close unregisters the connection and fans out the leave, so a closed laptop does not linger as a member forever.
Watching the channel
channel.presence.subscribe((member, action) => {
console.log(member.clientId, action);
});action is enter, leave or update. To narrow it:
channel.presence.subscribe(['enter', 'leave'], (member, action) => {
console.log(member.clientId, action);
});That filter is client-side, deliberately. Presence volume is per member, not per
message, and a subscriber that only wants enter still needs the connection
attached for the others — otherwise get() drifts out of date.
Who is here
const members = await channel.presence.get();Each member is:
{
clientId: 'alice',
connectionId: 'conn-…',
data: { status: 'online' },
updatedAt: 1710000000000,
}This asks the server, over the socket that is already open and already
authenticated — it is not a REST call and it does not need the presence
capability twice.
There is also a local map maintained from the Presence frames this client has
received: enter and update upsert by clientId, leave deletes. It only
reflects what arrived on this connection since it attached, so it is a cache of
the events you saw, not a snapshot of the channel. When you want the truth, ask
the server.
Attach without entering
A client can watch presence without being a member of it:
const channel = realtime.channels.get('chat:lobby');
channel.presence.subscribe((member, action) => { /* … */ });Subscribing attaches the channel, which is the gate for presence delivery. You receive everyone else's enters and leaves and appear in nobody's member list — which is what a dashboard wants.
Presence history
The enter / leave / update log is available over REST:
const events = await channel.presence.history({ limit: 100 });See history.
Capabilities
Presence operations need the presence capability on that channel. A token
scoped to chat:* => ['subscribe'] can read messages there and will be refused
on enter with a 403.
Note that a presence-only channel is still a channel: if you use the
presence:chat:room-7 convention, grant presence on that name, not on
chat:room-7.
Across nodes
Live presence events are held in each node's memory and forwarded to interested peers, so a subscriber on another node sees them. The REST snapshot instead reads a shared store written by the origin node — which means it is accurate across a cluster only when a shared store (Postgres) is configured. On a single node, both paths agree either way.