LobbyLab client SDK (@lobbylab/client)

LobbyLab is a hosted multiplayer backend. Your game keeps its own UI, engine and domain; players join LobbyLab rooms over WebSockets through @lobbylab/client, and LobbyLab keeps the rooms, invite codes, matchmaking and shared game state. Game rules that run on LobbyLab's servers come later. Request an alpha key at https://lobbylab.gg/developers.

Status: preview. @lobbylab/client and @lobbylab/node are not on npm yet; both arrive with the alpha, and the API below may change before then. Do not add them to a project yet. To connect today from any language, use the wire protocol (the protocol doc).

What the SDK does for you

Connect

import { LobbyLab } from "@lobbylab/client";

const lobby = new LobbyLab({
  key: "llpk_...",             // publishable key: safe in a browser, checked against your allowed origins
  name: "Maya",                // display name for an anonymous player
  // or: authEndpoint: "/api/lobbylab-token", for players from your own login
});

Keys: a publishable key (llpk_...) for clients and a secret key (llsk_...) for your backend only. A secret key sent from a browser is refused with secret_key_in_browser.

Rooms

const room = await lobby.joinOrCreate("default");   // quick join: the fullest open public room of this type, or a new one
const byCode = await lobby.join("KXPTQM");          // a 6-letter room code a friend shared
const mine = await lobby.create({ maxPlayers: 4 }); // a new room; share mine.code
const open = await lobby.listRooms({ type: "default" });

A room type (set once for your project, for example default or race) decides whether rooms are turn-based or live (20 Hz), how many players they hold, whether they are public, and who may write where.

Shared state

Every player sees a RoomView:

interface RoomView {
  doc: Json;                      // the shared document everyone reads
  players: Record<string, Json>;  // one public slot per player, by memberId
  private: Json | null;           // this player's private data only
  host: string | null;            // the host's memberId
  meta: Record<string, Json>;     // public room metadata, at most 2 KB
  locked: boolean;
  version: number;                // goes up by one on every accepted change
}
room.state.on("change", (view, info) => render(view));
await room.state.set("doc/board/4", "x");            // resolves when the server accepts it
await room.state.inc("doc/score/red", 1);
await room.state.push("doc/log", { move: "e4" }, { max: 50 });
await room.state.transact(
  [{ op: "set", path: "doc/turn", value: "blue" }],
  { guards: [{ path: "doc/turn", equals: "red" }] },  // refused with guard_failed if someone moved first
);
room.state.me.set({ x: 12, y: 40 });                  // your own slot; in live rooms it is your input each tick

Paths start with doc/, players/<memberId>/, private/<memberId>/ or meta/. Up to 16 operations go in one change, applied in order, all or none. A refused change rejects with a code such as forbidden_path, guard_failed, version_conflict or too_large.

Messages

room.send("emote", { kind: "wave" });                       // to everyone
room.send("hint", { cell: 3 }, { to: ["m_2"] });            // to chosen players ("all", "others", "host", or member ids)
room.on("message:emote", ({ from, data }) => showEmote(from, data));

A message's data is at most 4 KB.

Members, the host and the room's end

room.members.on("change", () => drawRoster(room.members.list()));
if (room.isHost) await room.lock(true);                      // the host can also kick, set metadata, hand over the host role and close
room.on("reconnecting", () => showBanner("Reconnecting"));
room.on("closed", ({ reason }) => showEnd(reason));          // host_closed, empty, time_limit, host_left, ...
await room.leave();

The room lifecycle, limits and close reasons are in the rooms doc; every error code has a page at https://lobbylab.gg/docs/errors.

Your backend: @lobbylab/node

import { LobbyLabServer } from "@lobbylab/node";
const lobbylab = new LobbyLabServer({ secretKey: process.env.LOBBYLAB_SECRET_KEY });
const { token } = await lobbylab.tokens.create({ userId: user.id, name: user.displayName });

Use it to mint player tokens for your own signed-in players, create reserved rooms, list rooms and read usage. It uses only fetch and Web Crypto, so it also runs in Workers, Vercel Edge, Deno and Bun.

Until the SDK ships

Keep your game's networking behind one small module (connect, send, and a callback when the state changes), so moving to LobbyLab later changes one file. The planned REST routes are in the API reference: https://lobbylab.gg/docs/api.