Lobbies

Observe presence across multiple rooms from one subscription. A lobby is how a dashboard, dispatcher or monitor sees who is present in many rooms without joining each one.

Read the limits before you build on lobbies. In kraken v0.9.0 the set of rooms a lobby covers comes from the subscriber's own grants rather than from your lobby definition, and kraken only remembers that set for five minutes on the node where the subscribe happened. Lobbies work for a single-node dashboard that re-subscribes regularly; they are not yet a dependable cross-room view.

What is a Lobby?

A lobby groups rooms for presence observation. Actors in a room only see each other; a lobby subscriber sees presence from the lobby's rooms, with each event tagged by the room it came from.

Room trip-123  ->  driver + rider
Room trip-456  ->  driver + rider
Room trip-789  ->  driver + rider

Lobby active-trips  ->  a dispatch dashboard sees everyone present in those rooms

Lobbies are read-only. Nobody publishes to a lobby or sets presence on one; actors set presence on their room, and kraken forwards it to the lobby's subscribers.

Use Cases

  • Ride-sharing dashboard: monitor every active trip and its driver's position
  • Support center: see which support conversations have someone in them
  • Game matchmaking: show player counts across game rooms
  • IoT monitoring: track which devices are connected across sites

Setting Presence on a Room

Participants set presence on their own room with room.setPresence(), exactly as in Presence. kraken forwards it to the lobby. Presence set without a room is never broadcast and never reaches a lobby.

import { NoLag } from "@nolag/js-sdk";

const client = NoLag(driverToken, { url: "ws://localhost:8080/ws" });
const trip = client.setApp("rides").setRoom("trip-123");

client.on("connect", () => {
  trip.setPresence({ role: "driver", name: "John Smith", location: { lat: 37.77, lng: -122.41 } });
});

await client.connect();

// Update location while driving
setInterval(() => {
  trip.setPresence({ role: "driver", name: "John Smith", location: getCurrentLocation() });
}, 5000);

Subscribing to a Lobby

Subscribe to receive a snapshot of current presence, followed by live events. The lobby is addressed by its slug.

import { NoLag } from "@nolag/js-sdk";

const client = NoLag(dashboardToken, { url: "ws://localhost:8080/ws" });
await client.connect();

const lobby = client.setApp("rides").setLobby("active-trips");

// Live events carry the room they came from
lobby.on("presence:join", (event) => addToMap(event.roomId, event.actorId, event.data));
lobby.on("presence:update", (event) => updateOnMap(event.roomId, event.actorId, event.data));
lobby.on("presence:leave", (event) => removeFromMap(event.roomId, event.actorId));

// subscribe() resolves with a snapshot. The first snapshot can come back
// empty (see the limits below), so read the current state again.
await lobby.subscribe();
const snapshot = await lobby.fetchPresence();

// snapshot: { roomSlug: { actorId: { actorTokenId, presence, joinedAt } } }
for (const [roomSlug, actors] of Object.entries(snapshot)) {
  for (const [actorId, entry] of Object.entries(actors)) {
    addToMap(roomSlug, actorId, entry.presence);
  }
}

To stop observing, call lobby.unsubscribe() (Unsubscribe() in Go).

Lobby Presence Events

Live events include the room, so a dashboard can group actors by room:

interface LobbyPresenceEvent {
  lobbyId: string; // The lobby slug
  roomId: string; // The room the actor is in, as a slug
  actorId: string; // The actor's id
  data: PresenceData; // The actor's presence payload (empty on leave)
}

The snapshot is shaped differently from the events: each actor maps to an entry holding actorTokenId, presence and joinedAt, not to the presence payload itself. Read entry.presence for the payload.

Defining a Lobby and Granting Access

There is no separate lobby permission. An actor may subscribe to the lobbies its grants list, and a refused subscribe arrives as not_authorized on the client's error event (the JavaScript lobby.subscribe() then rejects after its 10-second timeout).

With @nolag/core

Lobbies belong to an app in the project document, and list room slugs from the same app:

{
  "slug": "rides",
  "name": "Rides",
  "topics": ["status"],
  "rooms": [
    { "slug": "trip-123", "name": "Trip 123" },
    { "slug": "trip-456", "name": "Trip 456" }
  ],
  "lobbies": [
    { "slug": "active-trips", "name": "Active trips", "rooms": ["trip-123", "trip-456"] }
  ]
}

A lobby is visible to any actor that can reach at least one of its rooms. A rider who can reach trip-123 can therefore subscribe to active-trips too.

With kraken's static auth file

List the lobby on the token, with a slug and an id:

"dev-token-dispatch": {
  "actorTokenId": "dispatch",
  "projectId": "rides",
  "actorType": "user",
  "allowedTopics": [
    { "pattern": "rides/trip-123/#", "permission": "subscribe", "app_id": "rides", "room_slug": "trip-123", "room_id": "rides-trip-123" },
    { "pattern": "rides/trip-456/#", "permission": "subscribe", "app_id": "rides", "room_slug": "trip-456", "room_id": "rides-trip-456" }
  ],
  "allowedLobbies": [{ "lobby_slug": "active-trips", "lobby_id": "rides-active-trips" }]
}

Current Limits

These follow from how kraken v0.9.0 maps lobbies to rooms, and apply with either auth backend:

  • The subscriber's grants decide the rooms. When an actor subscribes to a lobby, kraken treats every room in that actor's own grants that carries a room id as part of the lobby, across all of its apps. With @nolag/core, the lobby's rooms list decides who may subscribe, not which rooms are watched. With the static file, the dispatcher above watches exactly the rooms its rules name.
  • Five-minute memory. kraken remembers that room mapping for five minutes after the most recent subscribe to the lobby. After that, live lobby events stop and the presence list comes back empty, until someone subscribes to the lobby again. A dashboard that must stay live can call lobby.subscribe() again on a timer shorter than five minutes.
  • The first snapshot can be empty. The snapshot returned by subscribe() can be read before the mapping is stored. Call fetchPresence() after subscribing, as in the examples above.
  • Shared mapping. The mapping is shared by every subscriber of the lobby on that node, so subscribers whose grants differ can see presence from each other's rooms, and a room maps to the lobby most recently subscribed over it.
  • Single node. The mapping lives on the kraken node that handled the subscribe. In a cluster, presence set by actors connected to other nodes does not reach the lobby.
  • Not isolated by access scope. Like room presence, lobby presence is not separated by scope.

Keep presence payloads free of anything one participant should not see about another, and keep sensitive rooms out of any lobby. If you need a dependable cross-room view today, have participants also publish their status to a topic the dashboard subscribes to, for example rides/dispatch/status.

Room vs Lobby Presence

FeatureRoom PresenceLobby Presence
ScopeSingle roomRooms in the subscriber's grants
Can set presenceYesNo (read-only)
Events include the roomNo (implicit)Yes
Use caseParticipants in a sessionDashboards, monitoring
Who can observeActors with presence in the roomActors granted the lobby

Next Steps