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, chat, and ctx values, 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 connections appears three times, each with its own ConnectionID.
1. Register a listener and hold presence interest
stopPresence := chat.OnPresence(func(event celeris.PresenceEvent) {
fmt.Println("Joined:", event.Joined, event.TokenReference, event.ConnectionID)
})
presenceSubscription, err := chat.SubscribePresence()
if err != nil {
log.Fatal(err)
}
OnPresence registers a local listener. SubscribePresence requests notifications. Notifications arrive only while you hold a presence subscription. Keep both until this part of your application is finished. event.Joined is false when the connection left. event.Timestamp is an int64 value in Unix milliseconds.
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:
page, err := chat.PresenceList(ctx, 1, 25)
if err != nil {
log.Println("Presence query failed:", err)
return
}
fmt.Println("Connections:", page.Total)
fmt.Println("Page:", page.CurrentPage)
for _, connection := range page.Connections {
fmt.Println(connection.TokenReference, connection.ConnectionID)
}
A query requires an established connection and read permission, but no held presence subscription.
Read the result fields by their types:
| Field | Go type | Meaning |
|---|---|---|
Total | int32 | Reported connection count |
PerPage | int32 | Page size reported by the server |
CurrentPage | int32 | Current page number |
From, To | int32 | Range reported for this page |
Connections | []PresenceConnection | Connection records, not unique users |
Connections[i].Timestamp | int64 | Unix milliseconds for that connection |
3. Paginate and handle failures
page must be at least 1, and perPage must be from 1 to 100. Other values return ErrConfiguration. The SDK does not convert or clamp them.
func listConnections(ctx context.Context, segment *celeris.Segment) ([]celeris.PresenceConnection, error) {
var connections []celeris.PresenceConnection
for pageNumber := int32(1); ; pageNumber++ {
page, err := segment.PresenceList(ctx, pageNumber, 25)
if err != nil {
return nil, err
}
connections = append(connections, page.Connections...)
if len(page.Connections) == 0 || page.To >= page.Total {
return connections, nil
}
}
}
The goroutine that runs listeners also routes a query's reply, so PresenceList inside a listener always times out. To refresh a listing when presence changes, query from a goroutine of your own:
stopRefresh := chat.OnPresence(func(celeris.PresenceEvent) {
go func() {
connections, err := listConnections(ctx, chat)
if err != nil {
log.Println("Presence query failed:", err)
return
}
fmt.Println("Records fetched:", len(connections))
}()
})
Only one query can be in flight per channel, even across segments. A second query returns ErrOperationInProgress, so serialize refreshes that can overlap. A query also returns ErrBackpressure while the writer is full or paused after a rate limit.
A server refusal returns a *celeris.ServerError from PresenceList directly, with SubType "PRES_LIST". The refusal does not also reach OnError. The default query deadline (PresenceQueryTimeout) is 10 seconds. A timeout (ErrTimeout) or a canceled ctx frees the query slot and does not close the connection. The SDK ignores a late reply. The SDK does not retry queries automatically. A connection loss during a query returns ErrTransport. Issue a new query after recovery. While the channel is not connected, a query returns ErrNotConnected. See Reconnection and Recovery.
Past the last page, Connections is empty, and 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()
stopRefresh()
stopPresence()
Close the channel when its owner no longer needs it. The default segment has automatic message membership, but it still needs SubscribePresence() to request presence events.
Next: Errors and troubleshooting.