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
Client-level: Presence events (presence:join, presence:leave, presence:update) are tracked at the client level, not room-scoped. Use client.on() to listen for these events. For observing presence across 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()

// Set presence at the client level
client.setPresence({
  username: 'Alice',
  status: 'online',
  avatar: 'https://example.com/alice.jpg'
})

// Presence events are client-level, not room-scoped
client.on('presence:join', (actor) => {
  console.log(`${actor.presence.username} joined`)
})

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

client.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 present. Note that getPresence() returns all actors from the local cache (not room-scoped):

// Get all present actors from local cache (client-level)
const actors = client.getPresence()
console.log('Users online:', actors.length)

actors.forEach((actor) => {
  console.log(`- ${actor.presence.username} (${actor.actorTokenId})`)
})

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

Local Cache vs Server Fetch

  • client.getPresence() - Returns all actors from local cache (fast, synchronous)
  • client.getPresence(actorId) - Returns a specific actor's presence from cache
  • client.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)
client.setPresence({
  username: 'Alice',
  status: 'away',
  lastActive: Date.now()
})

// Client-level 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() {
  client.setPresence({
    username: 'Alice',
    status: 'online',
    isTyping: true
  })

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

// Listen for typing from others (client-level event)
client.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 - Client-level 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