# Presence

> Track who is online, what they are doing, and when they leave. Presence state is cleaned up automatically on WebSocket disconnect.

Track who is online on a channel, what they're doing, and when they leave. Aerostack automatically cleans up presence state when a user disconnects — no server code required.

## How it works

Call `channel.track(state)` with any JSON object describing the user. All other subscribers on the same channel receive `presence:join`, `presence:update`, and `presence:leave` events.

When a user disconnects, Aerostack waits 90 seconds (to allow reconnection), then broadcasts `presence:leave` for all their tracked channels.

## Track presence

```ts
const channel = realtime.channel('canvas/room-1')

channel.track({
  userId: user.id,
  name: user.name,
  color: '#FF5733',
  cursor: { x: 120, y: 340 },
})
channel.subscribe()
```

## Listen for presence events

```ts
channel
  .on('presence:join', ({ data }) => {
    // data: { userId, name, color, cursor, ... }
    setPeers(prev => [...prev, data])
  })
  .on('presence:update', ({ data }) => {
    setPeers(prev => prev.map(p => p.userId === data.userId ? data : p))
  })
  .on('presence:leave', ({ data }) => {
    setPeers(prev => prev.filter(p => p.userId !== data.userId))
  })
```

## Update presence state

Call `track()` again at any time to update your state — all subscribers receive a `presence:update` event:

```ts
// Update cursor position on mousemove
document.addEventListener('mousemove', ({ clientX, clientY }) => {
  channel.track({ ...myState, cursor: { x: clientX, y: clientY } })
})
```

## Untrack (go offline)

```ts
channel.untrack()
// Broadcasts presence:leave to all subscribers immediately
```

## Complete presence example

```tsx

const COLORS = ['#FF5733', '#3498DB', '#2ECC71', '#F39C12', '#9B59B6']

function LivePresence({ roomId, userId, userName }) {
  const { realtime } = useAerostack()
  const [peers, setPeers] = useState([])
  const color = useRef(COLORS[Math.floor(Math.random() * COLORS.length)])

  useEffect(() => {
    const channel = realtime.channel(`room/${roomId}`)

    channel
      .on('presence:join', ({ data }) => {
        setPeers(prev => {
          if (prev.find(p => p.userId === data.userId)) return prev
          return [...prev, data]
        })
      })
      .on('presence:update', ({ data }) => {
        setPeers(prev => prev.map(p => p.userId === data.userId ? { ...p, ...data } : p))
      })
      .on('presence:leave', ({ data }) => {
        setPeers(prev => prev.filter(p => p.userId !== data.userId))
      })
      .subscribe()

    // Announce your presence
    channel.track({ userId, name: userName, color: color.current, status: 'online' })

    return () => {
      channel.untrack()
      channel.unsubscribe()
    }
  }, [realtime, roomId, userId, userName])

  return (
    <div className="flex gap-2 flex-wrap">
      {peers.map(peer => (
        <div
          key={peer.userId}
          className="flex items-center gap-2 px-3 py-1.5 rounded-full text-white text-sm"
          style={{ background: peer.color }}
        >
          
          {peer.name}
        </div>
      ))}
    </div>
  )
}
```

Presence state is not persisted — it exists only in memory. When the server restarts, all presence is cleared.

See the [Live Presence example](/examples/live-presence) for a complete, interactive demo with an event log.
