Lobbies

Observe presence across multiple rooms. Build dashboards that monitor all active sessions, trips, games, or conversations in real-time.

What is a Lobby?

A Lobby is a group of rooms that share presence visibility. While actors in a room can only see each other, a lobby observer can see presence from all rooms in that lobby.

// The Hotel Metaphor
//
// Room 101 (trip-123)  -> Driver + Rider (private conversation)
// Room 102 (trip-456)  -> Driver + Rider (private conversation)
// Room 103 (trip-789)  -> Driver + Rider (private conversation)
//
// Lobby (active-trips) -> Dashboard sees everyone who's "checked in"
//
// Actors in rooms can only see each other within that room.
// The dashboard in the lobby can see presence from ALL rooms.
Read-only observation: Lobbies are for observing presence only. Actors cannot publish messages or set presence directly on a lobby - they must do so on a room.

Use Cases

  • Ride-sharing dashboard - Monitor all active trips, see driver locations across the city
  • Support center - View all active support conversations, see agent availability
  • Game matchmaking - Observe all game lobbies, show player counts
  • IoT monitoring - Track all connected devices across multiple facilities
  • Trading floor - Monitor all active trading sessions

How It Works

  1. Actors set presence on rooms - Drivers, riders, players, etc. join their specific rooms
  2. Rooms are added to lobbies - Via REST API when a session starts
  3. Presence auto-propagates - When presence is set on a room, it automatically propagates to all lobbies containing that room
  4. Dashboard subscribes to lobby - Receives aggregated presence from all rooms
Limit: A room can belong to a maximum of 10 lobbies. This bounds the fan-out when presence is updated.

Setting Presence on a Room

Actors set presence on their room. This presence automatically propagates to any lobbies the room belongs to:

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

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

// Driver joins trip room and sets presence
const trip = client.setApp('rides').setRoom('trip-123')

trip.setPresence({
  role: 'driver',
  name: 'John Smith',
  vehicle: 'Toyota Camry',
  location: { lat: 37.77, lng: -122.41 }
})

// Update location as driving
setInterval(() => {
  trip.setPresence({
    role: 'driver',
    name: 'John Smith',
    vehicle: 'Toyota Camry',
    location: getCurrentLocation()
  })
}, 5000)

// Listen for rider presence in the same room
trip.on('presence:join', (actor) => {
  console.log(`Rider ${actor.presence.name} joined the trip`)
})

trip.on('presence:update', (actor) => {
  if (actor.presence.role === 'rider') {
    console.log('Rider location:', actor.presence.location)
  }
})

Subscribing to a Lobby

Subscribe to a lobby to receive presence from all rooms. You'll get a snapshot of current presence when you subscribe, plus live updates as actors join, leave, or update:

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

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

// Subscribe to a lobby to observe all trips
const lobby = client.setApp('rides').setLobby('active-trips')

// Subscribe returns a snapshot of current presence
const snapshot = await lobby.subscribe()
console.log('Current trips:', Object.keys(snapshot))

// snapshot structure: { roomId -> { actorId -> presenceData } }
// {
//   "trip-123": {
//     "driver-1": { role: "driver", name: "John", location: {...} },
//     "rider-5": { role: "rider", name: "Alice" }
//   },
//   "trip-456": {
//     "driver-2": { role: "driver", name: "Mike", location: {...} }
//   }
// }

// Build initial map
const presenceMap = new Map()
for (const [roomId, actors] of Object.entries(snapshot)) {
  for (const [actorId, data] of Object.entries(actors)) {
    addToMap(roomId, actorId, data)
  }
}

Lobby Presence Events

Lobby presence events include the roomId so you know which room the presence came from. This lets you group actors by room on your dashboard:

// Listen for presence events (includes room context)
lobby.on('presence:join', (event) => {
  // event: { lobbyId, roomId, actorId, data }
  console.log(`${event.actorId} joined room ${event.roomId}`)
  addToMap(event.roomId, event.actorId, event.data)
})

lobby.on('presence:update', (event) => {
  console.log(`${event.actorId} in room ${event.roomId} updated`)
  updateOnMap(event.roomId, event.actorId, event.data)
})

lobby.on('presence:leave', (event) => {
  console.log(`${event.actorId} left room ${event.roomId}`)
  removeFromMap(event.roomId, event.actorId)
})

// Fetch fresh presence at any time
const freshPresence = await lobby.fetchPresence()

// Unsubscribe when done
lobby.unsubscribe()

Lobby Presence Event Structure

interface LobbyPresenceEvent {
  lobbyId: string    // The lobby this event is from
  roomId: string     // Which room the actor is in
  actorId: string    // The actor's token ID
  data: {            // The actor's presence data
    role?: string
    name?: string
    location?: { lat: number, lng: number }
    // ... any custom fields
  }
}

Full Dashboard Example

Here's a complete example of a trip monitoring dashboard:

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

// Dashboard that shows all active trips on a map
class TripDashboard {
  private client: NoLag
  private presence: Map<string, Map<string, any>> = new Map()

  async connect() {
    this.client = NoLag('dashboard_token')
    await this.client.connect()

    const lobby = this.client.setApp('rides').setLobby('active-trips')

    // Get initial snapshot
    const snapshot = await lobby.subscribe()
    this.initializeFromSnapshot(snapshot)

    // Listen for live updates
    lobby.on('presence:join', (e) => this.handleJoin(e))
    lobby.on('presence:update', (e) => this.handleUpdate(e))
    lobby.on('presence:leave', (e) => this.handleLeave(e))
  }

  private initializeFromSnapshot(snapshot: LobbyPresenceState) {
    for (const [roomId, actors] of Object.entries(snapshot)) {
      const roomMap = new Map()
      for (const [actorId, data] of Object.entries(actors)) {
        roomMap.set(actorId, data)
        this.addMarkerToMap(roomId, actorId, data)
      }
      this.presence.set(roomId, roomMap)
    }
  }

  private handleJoin(event: LobbyPresenceEvent) {
    if (!this.presence.has(event.roomId)) {
      this.presence.set(event.roomId, new Map())
    }
    this.presence.get(event.roomId)!.set(event.actorId, event.data)
    this.addMarkerToMap(event.roomId, event.actorId, event.data)
  }

  private handleUpdate(event: LobbyPresenceEvent) {
    this.presence.get(event.roomId)?.set(event.actorId, event.data)
    this.updateMarkerOnMap(event.roomId, event.actorId, event.data)
  }

  private handleLeave(event: LobbyPresenceEvent) {
    this.presence.get(event.roomId)?.delete(event.actorId)
    this.removeMarkerFromMap(event.roomId, event.actorId)
  }

  // Map rendering methods...
  private addMarkerToMap(roomId: string, actorId: string, data: any) { /* ... */ }
  private updateMarkerOnMap(roomId: string, actorId: string, data: any) { /* ... */ }
  private removeMarkerFromMap(roomId: string, actorId: string) { /* ... */ }
}

Managing Lobbies (REST API)

Lobbies are configured via the REST API. Typically, your backend adds rooms to lobbies when sessions start:

# Create a lobby
POST /v1/organizations/:orgId/projects/:projId/apps/:appId/lobbies
{
  "name": "Active Trips",
  "slug": "active-trips",
  "description": "All currently active trip rooms"
}

# Add a room to the lobby (when trip starts)
POST /v1/.../lobbies/:lobbyId/rooms
{
  "roomId": "trip-123-uuid"
}

# Remove room from lobby (when trip ends)
DELETE /v1/.../lobbies/:lobbyId/rooms/:roomId

# List all rooms in a lobby
GET /v1/.../lobbies/:lobbyId/rooms

Access Control

Lobby subscriptions are controlled by ACL. Typically, only admin/server actors should have permission to subscribe to lobbies:

// Actor token with lobby permissions
{
  actorType: "server",
  permissions: {
    lobbies: {
      "active-trips": ["subscribe"],
      "admin-lobby": ["subscribe"]
    }
  }
}

Best Practices

  • Use lobbies for dashboards, not clients - Regular users should use room presence; lobbies are for admin/monitoring
  • Keep presence data small - Lobby events fan out to all subscribers
  • Add rooms to lobbies on session start - Remove them when sessions end
  • Use the 10-lobby limit wisely - Group rooms into meaningful categories
  • Handle the initial snapshot - Don't rely only on live events; process the snapshot first

Room vs Lobby Presence

FeatureRoom PresenceLobby Presence
ScopeSingle roomMultiple rooms
Can set presenceYesNo (read-only)
Events include roomIdNo (implicit)Yes
Use caseParticipants in a sessionDashboards, monitoring
ACLRoom-level permissionsLobby-level permissions

Next Steps