Skip to content
Moku
@moku-labs/room pages

session

Default engine. Room code, QR, roster, role, and the record that survives a stage reload.

On this page

What it does

Wired by default. session owns who is in the room: the code, the join URL, the QR, the roster and this device's role. The facades call it. Call it directly for self, leave, rejoin and recoveryPhase, which the facades do not expose.

How it works

createRoom mints the code with crypto.getRandomValues, connects the transport as the active side and returns without waiting. joinRoom connects as the passive side and resolves when the channel to the host opens. When a phone's channel opens, the stage adds it to the roster, broadcasts the roster on the Wire and emits room:peer-joined. Phones mirror that roster.

For what happens on a reload, see Failures and reloads.

Config

pluginConfigs.session.

OptionTypeDefaultDescription
joinUrlBasestring""The start of the join URL. Empty means location.origin.
generateQrbooleantruefalse makes qr() return null.
maxControllersnumber8Phones allowed, without the host. Over the cap the stage closes the channel.
snapshotDebounceMsnumber500Delay of the IndexedDB write of the reload record.
reconnectTimeoutMsnumber10000How long a join waits, and how long a phone waits for a lost stage.
intentBufferMaxnumber256Cap of the session's reconnect buffer.
intentBufferMaxAgeMsnumber8000Maximum age of a buffered intent.
storageKeyPrefixstring"moku.room"Prefix of the keys in localStorage.
codeLengthnumber6Characters in a new code. Use 8 on a public hub.

API

Mounted at app.session, reachable with ctx.require(sessionPlugin).

MethodSignatureDescription
createRoom() => RoomDescriptorHost entry. Throws when a room is active.
qr() => Promise<QrMatrix | null>The QR matrix of the join URL.
joinRoom(code: string) => Promise<JoinResult>Phone entry. Resolves { ok: true, selfId } or { ok: false, reason }.
leave() => Promise<void>Closes the transport and resets the role. No-op outside a room.
rejoin() => Promise<JoinResult>Joins the stored code again. Throws when there is none.
roster() => readonly RosterEntry[]Phones, oldest first. A copy.
self() => SelfInfo{ selfId, role, roomCode }. Role is host, controller or none.
hostId() => PeerIdThe stage's id. "" before the channel is up.
persistSnapshot(snapshot: Snapshot, sSeq: number) => voidSaves the reload record. sync calls it. No-op on a phone.
recoveryPhase() => RecoveryPhasestable, host-absent, verifying, reconciling or degraded.

Events

Emits

EventPayloadWhen
room:peer-joined{ peerId }Stage only. A phone entered the roster.
room:peer-left{ peerId }Stage only. The heartbeat declared a phone dead.
room:host-reconnecting{}Stage only. The page loaded with a saved room.

Listens to

None.

Depends on

transport.

Example

src/seats.ts
import { app } from "./stage";

await app.start();

const { role } = app.session.self();
if (role === "none") app.session.createRoom();

for (const player of app.session.roster()) {
  console.log(player.id, player.joinedAt);
}

await app.session.leave();
  • A failed join keeps the role. role stays controller, and the next joinRoom returns unreachable. Call leave() first.
  • full never comes. See Joining a room.
  • reconnectToken is empty in this version. Nothing fills it. A phone that reloads joins with a new id, as a new player. Key your game state on your own player id if a seat must survive.
  • leave leaves the reload record behind. See Failures and reloads.