# Presence

Session state, heartbeats, and expiry.

## Track a session


```ts
const room = db.channel("editor", {
  presence: { ttl: 30, heartbeatInterval: 10 },
});

room
  .on("presence", { event: "sync" }, () => {
    console.log(room.presenceState());
  })
  .on("presence", { event: "join" }, ({ key, newPresences }) => {
    console.log("joined", key, newPresences);
  })
  .on("presence", { event: "leave" }, ({ key, leftPresences }) => {
    console.log("left", key, leftPresences);
  })
  .subscribe();

await room.track({ displayName: "Alice", page: "document-1" });
await room.untrack();
room.unsubscribe();
```

Times are seconds. Default TTL is 30 seconds. Default heartbeat interval is 10 seconds, or one third of a shorter TTL. TTL must be an integer from 2 to 3600. The heartbeat interval must be positive and at most half the TTL.

`presenceState()` returns public keys mapped to session lists. Each entry contains `pubkey`, `sessionId`, `state`, and `expiresAt`. Each channel instance has its own session ID, so two tabs with one key remain separate sessions. The result is a copy.

`track()` publishes the initial state, then sends heartbeats. Calling it again updates state. `untrack()` stops heartbeats and publishes a leave message. An older heartbeat cannot override a newer leave message. A `sync` notification follows accepted state updates, joins, and leaves. Late subscribers learn existing sessions from later heartbeats.

Changing the signing key or signing out stops local tracking. `unsubscribe()` and `db.close()` stop timers and subscriptions. Other clients remove the session after its TTL. Use `await untrack()` before unsubscribing for an immediate leave attempt.


## Delivery and privacy


- Broadcast payloads and Presence state are public. Signing proves the author; it does not limit access.
- Relays need not retain ephemeral events. The SDK does not put them in its event cache or offline write queue.
- Relay acknowledgement proves acceptance, not delivery to every subscriber.
- Presence is approximate. Disconnects, clock differences, dropped messages, or relay policies can delay joins and leaves.
- Relays must accept and deliver kind `20078` and the channel tags. This is a nostrbase wire format, not a general Presence standard.
- Negentropy recovers stored record events. It cannot recover ephemeral Broadcast or Presence history.
