GameLobby

Attributes

auto-ready
boolean Automatically call ready() when a queue match is found
url .url
string | null
reconnect .reconnect
boolean

Methods

.define(tag, registry)
.createRoom()
Create a private room. After a handoff this reconnects first.
.joinRoom(code)
Join a room by code. After a handoff this reconnects first.
.leaveQueue()
Leave the matchmaking queue.
.leaveRoom()
Leave the current private room. The signalling protocol has no leave message, so this drops the WebSocket and reconnects with a new player identity, which the server sees as the player leaving.
.joinQueue(preferences)
Enter the matchmaking queue with optional preference strings. The intent survives until a match starts signalling or `leaveQueue()` is called: a dropped socket or a cancelled match re-enters the queue on its own.
.setPreference(preference)
Set a preference string (e.g. chosen role, faction).
.ready()
Mark self as ready within a room.
.unready()
Mark self as not ready within a room.
.handoff()
Release the signalling socket once the peer connection is up. The server drops this player from the room without telling the others, and closes the room when the last player hands off. Called by ``; only valid during signalling.
.reportResult(opponent, outcome)
Report a match result. Reconnects first if the socket was handed off; the report carries the match token from `start_signalling`.
.subscribe(context, callback)

Events

game-lobby-connected GameLobbyConnectedEvent
Connected and assigned a player ID
game-lobby-room GameLobbyRoomEvent
Room created or joined
game-lobby-match GameLobbyMatchEvent
Match found in queue
game-lobby-queue GameLobbyQueueEvent
Queue position updated
game-lobby-player GameLobbyPlayerEvent
Player joined/left/readied in room
game-lobby-start GameLobbyStartEvent
Signalling phase beginning
game-lobby-error GameLobbyErrorEvent
Server error received

Custom States

StateDescription
:state(connecting)Connecting to server
:state(connected)WebSocket open, no room or queue
:state(in-room)In a room, waiting for ready
:state(in-queue)In matchmaking queue
:state(signalling)WebRTC signalling in progress
:state(handed-off)Peers connected, socket released until a result is reported
:state(disconnected)WebSocket closed

Manages a WebSocket connection to a signalling server. Handles matchmaking queue, private rooms, player ready states, and relays SDP/ICE messages to <game-peer-connection> for WebRTC establishment.

Pushes lobby state into shell stats on every transition, making it available to <game-signal> and when-* conditions throughout the shell tree.

Attributes

auto-ready
boolean Automatically call ready() when a queue match is found
url .url
string | null
reconnect .reconnect
boolean
url
string — WebSocket URL of the signalling server (without the /ws/{game-id} path, which is appended automatically). Defaults to https://signalling.htmlgamekit.dev. Override to point at your own server. No connection is made until the parent shell has a game-id.
reconnect
boolean — When present, automatically reconnects after 2 seconds if the WebSocket closes unexpectedly. Omit for single-attempt connections.
auto-ready
boolean — When present, calls .ready() automatically as soon as a queue match is found, so queued players skip a manual accept step. Has no effect on the private room flow, where readiness is the player's decision.

Instance Methods

.joinQueue(preferences?)
Enter the matchmaking queue. The server pairs players in the same queue (same shell game-id). Fires game-lobby-queue when confirmed; game-lobby-start when a match is found and signalling begins.

Parameters:

  • preferencesstring[] — Optional preference strings (e.g. ["role:healer"]) sent to the server for match filtering.
lobby.joinQueue();
lobby.joinQueue(["difficulty:hard"]);
.leaveQueue()
Leave the matchmaking queue before a match is found. The element returns to :state(connected) immediately rather than waiting for the server, so a queue overlay selected with when-eq-lobby-state="in-queue" closes at once.
.leaveRoom()
Leave the current private room. The signalling protocol has no leave message, so this closes the WebSocket and reconnects with a fresh player identity — the server sees the player leave, and the room code and player count stats are cleared. Called automatically when the shell quits (shell.quit() / --quit) while in a room or during signalling, so the other player receives game-lobby-player with action "left". After a handoff the room no longer exists, so quitting reconnects with the same identity instead.
.handoff()
Release the signalling socket once the peer connection is up. Sends handoff so the server drops this player from the room without telling the others, then closes the WebSocket and moves to :state(handed-off). No reconnect is scheduled: the next createRoom(), joinRoom(), joinQueue(), reportResult() or shell quit reconnects with the same player ID and carries on. Called by <game-peer-connection> once both peers have finished ICE gathering; ignored unless the lobby is in signalling.
.createRoom()
Create a private room. Fires game-lobby-room with the room code on success. Share the code (via <game-signal key="room-code">) with another player so they can join.
.joinRoom(code)
Join a private room by code. Fires game-lobby-room on both the host and the joiner.

Parameters:

  • codestring — Room code (case-insensitive; the server normalises it).
lobby.joinRoom("ABCD1234");
.ready()
Signal that this player is ready to start. The server sends StartSignalling when all players in the room have readied up, which causes game-lobby-start to fire and `` to begin WebRTC establishment.
.unready()
Withdraw the ready signal.
.setPreference(preference)
Update this player's preference string. Other players in the room receive game-lobby-player with action "preference".

Parameters:

  • preferencestring
.reportResult(opponent, outcome)
Report a match outcome to the server for rating/ranking. Reconnects with the same player ID first if the socket was handed off, and sends the match token issued at start_signalling. Does nothing before a match has started signalling.

Parameters:

  • opponentstring — The opponent's player ID.
  • outcome"win" | "loss" | "draw"
lobby.reportResult(match.peerId, "win");

Events

game-lobby-connected
Fires when the WebSocket opens and the server assigns a player ID.
Property Type Description
playerId string Assigned player ID
game-lobby-room
Fires when a room is created (createRoom) or joined (joinRoom).
Property Type Description
code string Room code
players Array<{ id: string, preference: string|null }> Current players
game-lobby-player
Fires when a player joins, leaves, readies, unreadies, or changes preference in the current room.
Property Type Description
action "joined" | "left" | "ready" | "unready" | "preference" What happened
player { id: string, preference?: string|null } The player
game-lobby-queue
Fires when the server confirms queue entry.
Property Type Description
position number Position in the queue
game-lobby-match
Fires when a match is found (queue flow only), before signalling begins.
Property Type Description
players Array<{ id: string, preference: string|null }> Matched players
game-lobby-start
Fires when signalling begins (both queue and room flows). `` starts WebRTC establishment automatically when this fires.
Property Type Description
players Array<{ id: string, preference: string|null }> Players in the match
code string|null Room code if in a private room
game-lobby-error
Fires when the server sends an error.
Property Type Description
code string Error code
message string Human-readable message

All events bubble and compose, so you can listen at game-shell level.

Commands

Buttons drive the lobby declaratively with the native Invoker Commands API — commandfor names the lobby element, command names the action:

Command Effect
--create-room .createRoom()
--join-room .joinRoom(value)
--leave-room .leaveRoom()
--join-queue .joinQueue(value.split(/\s+/))
--leave-queue .leaveQueue()
--lobby-ready .ready()
--lobby-unready .unready()
--set-preference .setPreference(value)

--join-room, --join-queue and --set-preference read the button's value attribute; the others ignore it.

<button commandfor="lobby" command="--create-room">Create private room</button>
<button commandfor="lobby" command="--join-queue" value="ranked">
  Find a ranked game
</button>

CSS States

State When active
:state(connecting) WebSocket connect in progress
:state(connected) WebSocket open, not in room or queue
:state(in-room) In a private room
:state(in-queue) In the matchmaking queue
:state(signalling) WebRTC signalling in progress
:state(disconnected) WebSocket closed
game-lobby:state(connecting)::after {
  content: "Connecting...";
}
game-lobby:state(connected)::after {
  content: "Connected";
}

Shell Stats

The lobby writes to shell stats on every state change, making them available to <game-signal> and when-* conditions without any custom code:

Stat Type Description
lobby-state string Current state name
player-count number Players in the current room
room-code string Room code when in a room
queue-position number Queue position when waiting
player-id string This player's server-assigned ID
<p>Room: <game-signal key="room-code"></game-signal></p>
<div when-eq-lobby-state="in-queue" data-overlay>Finding a game...</div>

game-shell.start() clears stats, so the lobby republishes all of the above on the "setup" lifecycle event. Lobby state therefore stays readable during playing without any bookkeeping in your game.

Signal Access

Signal Usage
.playerId Signal.State<string|null> — current player ID, consumed by <game-peer-connection>
.startSignalling Signal.State<{ players, code }|null> — consumed by <game-peer-connection>

These are public Signal.State instances on the element instance. They are the primary interface between <game-lobby> and <game-peer-connection> — you do not need to access them directly.

Usage

<game-shell id="game" game-id="my-game" rounds="1">
  <game-lobby id="lobby" reconnect auto-ready></game-lobby>
  <game-peer-connection auto-ready></game-peer-connection>

  <div
    when-some-scene="intro"
    when-no-lobby-state="in-room in-queue signalling"
    data-overlay
  >
    <button commandfor="lobby" command="--join-queue">Find a game</button>
    <button commandfor="lobby" command="--create-room">
      Create private room
    </button>
  </div>

  <div when-some-scene="intro" when-eq-lobby-state="in-room" data-overlay>
    <p>Room: <game-signal key="room-code"></game-signal></p>
    <button commandfor="lobby" command="--lobby-ready">Ready</button>
    <button commandfor="lobby" command="--leave-room">Cancel</button>
  </div>
</game-shell>

With auto-ready on <game-peer-connection> the shell starts by itself once both peers are connected, so this whole flow needs no JavaScript.

See the Multiplayer concept page and the Noughts & Crosses tutorial for complete integration examples.