Documentation

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 connectionId as the join. The connection stays connected.
  • On default, a connection causes a join when it connects and a leave when it closes. The default presence list includes all connections of the channel.
  • After a reconnect, watchers get a leave for the previous connectionId and a join for the new one. The SDK subscribes again to the presence that you hold with subscribePresence().

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:

FieldJavaScript typeMeaning
totalnumberReported connection count
perPagenumberPage size reported by the server
currentPagenumberCurrent page number
from, tonumberRange reported for this page
connectionsarrayConnection records, not unique users
connections[i].timestampbigintExact 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.

We use Google cookies for analytics and for advertising measurement, only if you allow them. Until you choose, Google sets no cookies. Refer to our Cookie Policy.