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, 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 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:

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:

FieldGo typeMeaning
Totalint32Reported connection count
PerPageint32Page size reported by the server
CurrentPageint32Current page number
From, Toint32Range reported for this page
Connections[]PresenceConnectionConnection records, not unique users
Connections[i].Timestampint64Unix 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.

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.