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.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
- Actors set presence on rooms - Drivers, riders, players, etc. join their specific rooms
- Rooms are added to lobbies - Via REST API when a session starts
- Presence auto-propagates - When presence is set on a room, it automatically propagates to all lobbies containing that room
- Dashboard subscribes to lobby - Receives aggregated presence from all rooms
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/roomsAccess 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
| Feature | Room Presence | Lobby Presence |
|---|---|---|
| Scope | Single room | Multiple rooms |
| Can set presence | Yes | No (read-only) |
| Events include roomId | No (implicit) | Yes |
| Use case | Participants in a session | Dashboards, monitoring |
| ACL | Room-level permissions | Lobby-level permissions |