Presence

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

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

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:

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

Untrack (go offline)

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

Complete presence example

import { useAerostack } from '@aerostack/react'
import { useEffect, useState, useRef } from 'react'
 
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 }}
        >
          <span className="w-2 h-2 rounded-full bg-white/80" />
          {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 for a complete, interactive demo with an event log.