Presence Tracking

Know who's online in real-time. Track user presence across your project and handle join/leave events.

What is Presence?

Presence tracking allows you to see which actors (users, devices, or servers) are currently connected to your project. This is essential for features like:

  • Online/offline indicators in chat apps
  • Showing who's viewing a document
  • Live user counts
  • Typing indicators
Room-scoped: Presence is tracked at the room level. Actors in a room can only see each other's presence. For observing presence across multiple rooms, use Lobbies.

Setting Presence

Set your presence after connecting. Presence data can include any custom fields you need:

import { NoLag } from '@nolag/js-sdk'

const client = NoLag('your_access_token')
await client.connect()

// Join a room and set presence
const room = client.setApp('chat').setRoom('general')

room.setPresence({
  username: 'Alice',
  status: 'online',
  avatar: 'https://example.com/alice.jpg'
})

// Listen for presence events in this room
room.on('presence:join', (actor) => {
  console.log(`${actor.presence.username} joined`)
})

room.on('presence:leave', (actor) => {
  console.log(`${actor.presence.username} left`)
})

room.on('presence:update', (actor) => {
  console.log(`${actor.presence.username} updated status to ${actor.presence.status}`)
})

Presence Events

Listen for these events to track when actors come online, go offline, or update their status:

  • presence:join - An actor connected and set their presence
  • presence:leave - An actor disconnected
  • presence:update - An actor updated their presence data

Getting Current Presence

Get a list of all actors currently online:

// Get all online actors in this room from local cache
const actors = room.getPresence()
console.log('Users in room:', Object.keys(actors).length)

Object.entries(actors).forEach(([actorId, actor]) => {
  console.log(`- ${actor.presence.username} (${actorId})`)
})

// Fetch fresh presence list from server
const freshList = await room.fetchPresence()
console.log('Server says online:', freshList.length)

Local Cache vs Server Fetch

  • getPresence() - Returns from local cache (fast, synchronous)
  • fetchPresence() - Fetches fresh data from server (async, guaranteed fresh)

The local cache is automatically updated when you receive presence events.

Updating Presence

Update your presence data at any time by calling setPresence() again:

// Update your presence data (e.g., status change)
room.setPresence({
  username: 'Alice',
  status: 'away',
  lastActive: Date.now()
})

// Presence is automatically re-sent on reconnect

Typing Indicators

A common use case is showing typing indicators. Use presence updates with a debounce:

// For typing indicators, update presence with typing status
let typingTimeout: NodeJS.Timeout

function sendTyping() {
  room.setPresence({
    username: 'Alice',
    status: 'online',
    isTyping: true
  })

  // Clear typing after 3 seconds of inactivity
  clearTimeout(typingTimeout)
  typingTimeout = setTimeout(() => {
    room.setPresence({
      username: 'Alice',
      status: 'online',
      isTyping: false
    })
  }, 3000)
}

// Listen for typing from others
room.on('presence:update', (actor) => {
  if (actor.presence.isTyping) {
    showTypingIndicator(actor.presence.username)
  } else {
    hideTypingIndicator(actor.presence.username)
  }
})

Actor Presence Structure

Each actor presence object contains:

interface ActorPresence {
  actorTokenId: string    // Unique actor identifier
  actorType: 'device' | 'user' | 'server'
  presence: {             // Your custom presence data
    username?: string
    status?: string
    // ... any other fields you set
  }
  joinedAt?: number       // Timestamp when actor connected
}

Best Practices

  • Keep presence data small - Only include necessary information (username, status, avatar URL)
  • Use debouncing - For typing indicators, debounce updates to avoid flooding
  • Handle reconnections - Presence is automatically re-sent on reconnect
  • Consider privacy - Let users opt out of presence tracking if needed
  • Use fetchPresence() sparingly - The local cache is usually sufficient

Next Steps