Presence
List connections in a segment and observe presence notifications, which are not a complete user roster.
Before you start: complete Messaging. Use its channel and chat handles in web/app.js, with credentials that grant read access to chat. Register listeners before you connect. Request a listing only after the channel is connected.
Presence describes connections, not unique people. A user with three tabs can appear three times, each with its own connectionId.
1. Register a listener and hold presence interest
Add this setup before the Connect button handler:
const stopPresence = chat.onPresence((event) => {
console.log(
event.joined ? "Joined" : "Left",
event.tokenReference,
event.connectionId
);
});
const presenceSubscription = chat.subscribePresence();
onPresence() registers a local callback. subscribePresence() requests notifications. Keep both until this part of your application is finished. event.joined is a boolean, and event.timestamp is a bigint. The identifiers are strings.
A presence subscription does not join the segment. It does not give you messages, and your connection does not show in the presence of that segment because of it. To receive the messages of the segment, also call subscribe(). A publish does not join the segment. A connection that only publishes does not show in the presence of that segment.
The server sends presence events as follows:
- A connection does not get the join event that it causes. A different connection of the same token gets it.
- When a connection unsubscribes from a segment, watchers get a leave event with the same
connectionIdas the join. The connection stays connected. - On
default, a connection causes a join when it connects and a leave when it closes. Thedefaultpresence list includes all connections of the channel. - After a reconnect, watchers get a leave for the previous
connectionIdand a join for the new one. The SDK subscribes again to the presence that you hold withsubscribePresence().
2. Fetch a snapshot
After you connect, request the first page. Use ordinary JavaScript numbers for page and perPage:
try {
const result = await chat.presenceList({ page: 1, perPage: 25 });
console.log("Connections:", result.total);
console.log("Page:", result.currentPage);
for (const connection of result.connections) {
console.log(connection.tokenReference, connection.connectionId);
}
} catch (error) {
console.error("Presence query failed:", error.name, error.message);
}
A query does not require a held presence-event subscription, but it does require an established connection and read permission.
Read the result fields by their types:
| Field | JavaScript type | Meaning |
|---|---|---|
total | number | Reported connection count |
perPage | number | Page size reported by the server |
currentPage | number | Current page number |
from, to | number | Range reported for this page |
connections | array | Connection records, not unique users |
connections[i].timestamp | bigint | Exact timestamp on that connection record |
You can compare counts and page figures, add them, or pass them to Math functions directly. They do not need Number() or BigInt() conversion. A record's timestamp is different. Keep its bigint value, or use a decimal string when you serialize it to JSON.
With both Quickstart tabs connected, the listing includes two connections. They have the same demo identity and different connection IDs.
3. Paginate and handle failures
Both request fields must be integer numbers: page from 1 to 2,147,483,647 and perPage from 1 to 100. The SDK rejects strings, bigint values, fractions, and out-of-range values with ConfigurationError. It does not convert or clamp them.
To inspect a small segment across multiple pages, run this in a connected handler instead of the one-page query above:
async function listConnections(segment) {
const connections = [];
let page = 1;
const perPage = 25;
while (true) {
const result = await segment.presenceList({ page, perPage });
connections.push(...result.connections);
if (result.connections.length === 0 || result.to >= result.total) break;
if (page === 2_147_483_647) break;
page += 1;
}
return connections;
}
try {
const connections = await listConnections(chat);
console.log("Records fetched:", connections.length);
} catch (error) {
console.error("Presence listing failed:", error.name, error.message);
}
Only one query can be in flight per channel, even if the queries target different segments. Await the first query before you start another.
A server refusal or failure rejects the relevant presenceList() directly, not also through onError. Timeout and cancellation free the query slot and do not close the connection. The SDK ignores a late response.
The default query timeout is ten seconds. Use presenceQueryTimeoutMs to change it, to at most 15 minutes. The SDK does not retry queries automatically. A query does not go into the publish queue. When the socket writer is full, or when the SDK pauses after a rate limit, the query rejects with Backpressure. If the connection closes during a query, the query rejects with Transport. Issue a new query after recovery. See Reconnection and Recovery.
Past the last page, connections can be empty with from > to. Do not interpret that as a protocol error.
Listings aggregate across the cluster, but pages are not an atomic snapshot. Connections can join or leave between requests, so a multi-page result can omit or repeat records. Presence events go to watchers on all server nodes of the channel. But a connection that joined before you subscribed, or while you were disconnected, does not cause an event for you. Refresh listings when current state matters, and do not treat presence as a durable user roster.
4. Release presence resources
presenceSubscription.cancel();
stopPresence();
Close the channel when its owner no longer needs it. A connection to default has automatic message membership, but still needs subscribePresence() to request presence events.
When you close one demo connection, a later listing no longer includes it. When you cancel your presence subscription, notifications stop and the channel stays open.
Next: Errors and troubleshooting.