Client: Presence
Track who’s online in a room, listen for join/leave events, and share ephemeral presence state (like "typing" or "away").
Need user-level presence across tabs or devices? Use the createPresence utility to group connections by user ID or any other logic.
Subscribing
Subscribe to presence in a room:
const { , } = await .(
"lobby",
() => {
if (. === "join") {
.("User joined:", .);
} else if (. === "leave") {
.("User left:", .);
} else if (. === "state") {
.("State update:", ., .);
}
}
);You’ll receive:
- A list of currently present members with metadata via
present - Real-time
"join","leave", and"state"events
The present array includes both connection IDs and their metadata:
// present structure:
[
{ id: "conn_123", metadata: { username: "alice", avatar: "..." } },
{ id: "conn_456", metadata: { username: "bob", avatar: "..." } }
]Unsubscribe when done:
await .("lobby");Join + subscribe in one step
Use joinRoom() with a callback to automatically subscribe to presence:
const { , } = await .("lobby", () => {
.();
});Both subscribePresence() and joinRoom() return the same present format with metadata included.
If you omit the callback from joinRoom(), you still get the current occupants with metadata - but you won’t be subscribed to real-time updates.
Leaving rooms
When you leave a room, any active presence subscription for that room is automatically cleaned up:
await .("lobby"); // unsubscribes from presenceYou don’t need to manually call unsubscribePresence() after leaving a room.
Publishing presence state
Send ephemeral presence state to others in the room:
await .("lobby", {
: { : "typing" },
: 8000, // optional (ms)
});Clear it manually:
await .("lobby");Automatic presence refresh
When the server sends a ping and the client responds with a pong, the server refreshes the presence TTL for all rooms that the client connection has joined.
You don’t need to manually track rooms or re-send updates for each room your client has joined.
Receiving presence states
Presence updates include state events:
const { , , } = await .(
"lobby",
() => {
if (. === "state") {
.(`${.} state:`, .);
}
}
);You’ll also get a states object with current values at the time of subscription:
{
"abc123": { status: "typing" },
"def456": { status: "away" }
}Working with member metadata
Both methods return member information including metadata:
const { , } = await .("lobby", callback);
// or
const { , } = await .("lobby");
// Display current members
.( => {
.(`${.?.username || .} is online`);
});For real-time updates in your presence callback, resolve metadata for new connections:
await .("lobby", async () => {
if (. === "join") {
const = await .(.);
.(`${?.username || .} joined`);
}
});Use cases
- Online indicators with user names and avatars
- Room occupancy lists
- Typing indicators
- Game states like “ready”, “spectating”, etc.
Tips
- Both
joinRoom()andsubscribePresence()include member metadata automatically - Unsubscribe when no longer needed to reduce overhead
- Resubscribe after reconnecting
(or use
createPresence, which handles this for you) - Use
expireAfterfor short-lived states like"typing" - Debounce frequent updates to avoid spamming the network