Skip to main content

Documentation Index

Fetch the complete documentation index at: https://openturn.io/docs/llms.txt

Use this file to discover all available pages before exploring further.

Browser-targeted. Owns the single protocol that connects a game iframe to the shell that hosts it: the URL-fragment handshake, the postMessage message set, and the shell-controls registry that lets the host render save / load / reset / lobby chrome around the game from a manifest-driven catalog.

Install

bun add @openturn/bridge

The wire

A host mounts a game iframe with a #openturn-bridge=<base64-json> fragment. Inside the iframe, the game side decodes it with createGameBridge(); the host side uses createBridgeHost() to manage the iframe lifecycle. Both sides talk exclusively via namespaced postMessage messages defined in this package.

Game side (inside the iframe)

createGameBridge(options?)

Resolve the bridge for this iframe. Reads the fragment from the URL (override with options.readInit) and returns the handle.
const bridge = createGameBridge();
// bridge.init            → decoded BridgeInit (roomID, userID, scope, ...)
// bridge.token           → live room token (reflects the latest refresh)
// bridge.connection      → HostedConnectionDescriptor (game scope only)
// bridge.shellControl    → BridgeShellControlChannel; call .on(listener)
// bridge.lifecycle       → BridgeLifecycle; call .on("pause" | "resume" | "close", listener)
// bridge.refreshToken(): Promise<string>
Throws BridgeUnavailableError if the fragment is missing.

GameBridge

The game-side handle. Exposes the decoded init, the live token getter, a HostedConnectionDescriptor for the WebSocket client, a shell-control event channel, a lifecycle event emitter, and a token refresher. Read room/user/scope/websocketURL off bridge.init.* — prior builds mirrored those fields onto the bridge directly; that mirror was removed in favor of a single source of truth. Additional methods:
  • bridge.lifecycle.on(event, listener) — subscribe to host-driven "pause" | "resume" | "close" lifecycle events. Returns an unsubscribe.
  • bridge.shellControl.on(listener) — subscribe to shell-control activations from the host (reset, returnToLobby, …). Each click fires phase: "before" then phase: "after". See BridgeShellControlChannel below.
  • bridge.registerBatchSource(source: BatchSourceHandle | null) — register an initial-snapshot + ongoing-batch source so the host can request a live inspector stream. Most recent registration wins; pass null to clear.
  • bridge.allowBatchStreaming(allow: boolean) — author-side opt-out. When false, host-initiated stream requests resolve to "denied-by-game". Defaults to true.
  • bridge.setMatchActive(active: boolean) — announce whether a match is currently live. The shell uses this to enable/disable match-only controls (e.g. Reset). Coalesces duplicates.
  • bridge.getRoomToken() — alias of refreshToken() that plugs into transport clients expecting a getRoomToken() function.
  • bridge.dispose() — release the message listener and clear shell-control listeners.

CreateGameBridgeOptions

interface CreateGameBridgeOptions {
  readInit?: () => BridgeInit | null;          // default: fragment decode
  parent?: Pick<Window, "postMessage"> | null;  // default: window.parent
  refreshSkewSeconds?: number;                  // default: 15
}

BatchSourceHandle<TInitial, TBatch>

interface BatchSourceHandle<TInitial, TBatch> {
  getInitialSnapshot(): TInitial;
  subscribe(listener: (batch: TBatch) => void): () => void;
}

BridgeShellControlChannel / BridgeShellControlEvent

Shell controls are host-driven, not game-advertised: the deployment manifest declares which controls render, the host adapter implements them, and the bridge notifies the game around each invocation so it can react (e.g. clear local UI on reset).
interface BridgeShellControlChannel {
  on(listener: (event: BridgeShellControlEvent) => void): () => void;
}

interface BridgeShellControlEvent {
  control: BridgeShellControl;          // open string; narrow with isKnownShellControl
  phase: BridgeShellControlPhase;       // "before" | "after"
}

type BridgeShellControlPhase = "before" | "after";
const off = bridge.shellControl.on(({ control, phase }) => {
  if (control === "reset" && phase === "before") clearLocalState();
  if (control === "returnToLobby" && phase === "after") closeAnyOverlays();
});
// later: off()
Each host-side click fires phase: "before" immediately before the host runs the corresponding adapter method, and phase: "after" once it settles. The wire control is intentionally an open string — narrow it with isKnownShellControl before switching so unknown ids from a newer host are handled explicitly.

BridgeLifecycle / BridgeLifecycleEvent

type BridgeLifecycleEvent = "pause" | "resume" | "close";

interface BridgeLifecycle {
  on(event: BridgeLifecycleEvent, listener: () => void): () => void;
}

const offPause = bridge.lifecycle.on("pause", () => pauseTimers());
The host emits pause/resume when the iframe leaves and re-enters the foreground; close fires before the host detaches the iframe.

Host side (in the shell)

createBridgeHost(options)

const host = createBridgeHost({
  bundleURL: "https://cdn.example/deployments/abc/index.html",
  init,                 // BridgeInit built server-side from the room record
  refreshToken,         // async callback that mints a fresh room token
  expectOrigin,         // iframe origin; "*" for local dev only
});
// <iframe src={host.src} />
// host.emitShellControl(id, "before" | "after") — notify the game around each adapter call
// host.pause() / host.resume() / host.close() — lifecycle

BridgeHost

Shell-side handle with:
  • src: string — iframe src with the fragment baked in.
  • matchActive: boolean — current match-active state. Defaults to init.scope === "game"; updated via match-state-changed events.
  • emitShellControl(control, phase) — notify the game that a shell control was activated. Call once with "before" immediately before invoking the corresponding adapter method, and once with "after" afterwards.
  • pause() / resume() / close() — lifecycle control.
  • on(event, listener) — subscribe to BridgeHostEvents. Returns an unsubscribe function.
  • requestBatchStream(timeoutMs?) — ask the game to start streaming initial snapshot + batches. Resolves to a BatchStreamStatus ("allowed" | "denied-by-game" | "no-source").
  • stopBatchStream() — ask the game to stop streaming. Safe to call when idle.
  • onBatch(listener) — subscribe to streamed batches. Immediately replays the last-seen snapshot/batch. Returns an unsubscribe function.
  • dispose() — release the window listener and clear pending stream requests.

BridgeHostEvent / BridgeHostEventMap

The events a BridgeHost emits via host.on(...):
type BridgeHostEventMap = {
  ready: { origin: string };
  "lifecycle-close": Record<string, never>;
  "match-state-changed": { matchActive: boolean };
};
type BridgeHostEvent = keyof BridgeHostEventMap;

BridgeHostTokenContext / BridgeHostTokenRefreshResult

Passed to / returned from BridgeHostOptions.refreshToken:
interface BridgeHostTokenContext {
  roomID: string;
  userID: string;
  scope: BridgeScope;
}

interface BridgeHostTokenRefreshResult {
  token: string;
  tokenExpiresAt?: number;
}

Batch-stream types

type BatchStreamStatus = "allowed" | "denied-by-game" | "no-source";

interface BatchStreamPayload<TInitial, TBatch> {
  initialSnapshot: TInitial | null;
  lastBatch: TBatch | null;
}

type BatchStreamListener<TInitial, TBatch> = (
  payload: BatchStreamPayload<TInitial, TBatch>,
) => void;

Schema

BridgeInit

The decoded fragment payload. Carries roomID, userID, scope, token, tokenExpiresAt, websocketURL, playerID?, the player range (minPlayers, maxPlayers, targetCapacity), and host info (isHost, hostUserID). Zod-validated on both sides. targetCapacity is the room’s effective seat count (host-mutable in [minPlayers, maxPlayers]); maxPlayers equals the manifest’s players.length. For fixed-size games the three numbers are equal.

BridgeMessage

Discriminated union of all postMessage payloads: ready, token-refresh-request / token-refresh-response, shell-control, lifecycle-pause / lifecycle-resume / lifecycle-close, batch-stream-start / batch-stream-stop / batch-stream-response, initial-snapshot / batch-applied, and match-state (all under the openturn:bridge:* namespace).

BRIDGE_FRAGMENT_KEY

The URL hash key under which BridgeInit is stored: openturn-bridge.

BridgeScope

"lobby" | "game" — matches RoomTokenClaims["scope"]. Determines whether the bridge exposes a connection descriptor.

BridgeShellControl

The wire type for a shell-control id. Intentionally an open string (validated as min(1).max(64)) so a newer host can introduce new ids without bumping the schema or breaking older games. Use isKnownShellControl to narrow it to the canonical OpenturnShellControl union before switching.
type BridgeShellControl = string;
type BridgeShellControlPhase = "before" | "after";

Shell-controls registry

Single source of truth binding each OpenturnShellControl id (declared in @openturn/manifest) to its runtime metadata. The registry is what the shell, the manifest schema, and the runtime gating helper all derive from. Games never register shell controls — the manifest decides which controls render and the host adapter implements them.

SHELL_CONTROLS

const SHELL_CONTROLS: Readonly<Record<OpenturnShellControl, ShellControlMeta>> = {
  save:             { adapterMethod: "saveCurrentRoom",    label: "Save",              placement: "toolbar-trail" },
  load:             { adapterMethod: "createRoomFromSave", label: "Load",              placement: "toolbar-trail" },
  reset:            { adapterMethod: "resetRoom",          label: "Reset",             placement: "toolbar-trail", requiresMatchActive: true },
  returnToLobby:    { adapterMethod: "returnToLobby",      label: "Back to lobby",     placement: "toolbar-trail", requiresMatchActive: true },
  copyInvite:       { adapterMethod: null,                 label: "Copy Invite",       placement: "toolbar-lead" },
  publicRooms:      { adapterMethod: "listPublicRooms",    label: "Open public rooms", placement: "lobby-section" },
  visibilityToggle: { adapterMethod: "setVisibility",      label: "Visibility",        placement: "toolbar-lead" },
};

ShellControlMeta

interface ShellControlMeta {
  /** Adapter method that backs this control. `null` means shell-only (e.g. copyInvite). */
  readonly adapterMethod:
    | "saveCurrentRoom"
    | "createRoomFromSave"
    | "resetRoom"
    | "returnToLobby"
    | "listPublicRooms"
    | "setVisibility"
    | null;
  /** User-facing label rendered by the toolbar / lobby section. */
  readonly label: string;
  /**
   *  - `toolbar-trail`: right-side action button (Save / Load / Reset / lobby)
   *  - `toolbar-lead`:  left-side affordance (Copy Invite, visibility toggle)
   *  - `lobby-section`: full lobby card / list (start-from-save, public rooms)
   */
  readonly placement: "toolbar-trail" | "toolbar-lead" | "lobby-section";
  /** When true, the rendered button is disabled while no match is active. */
  readonly requiresMatchActive?: boolean;
}

isShellControlEnabled(adapter, id)

Resolve a single control. Returns true when the adapter implements the backing method (or the control is shell-only) AND the manifest hasn’t explicitly opted out via false. undefined in the manifest means “default-on if supported”.
function isShellControlEnabled(adapter: PlayShellAdapter, id: OpenturnShellControl): boolean;

isKnownShellControl(id)

Runtime narrowing helper. Returns true when the wire id matches one of the canonical SHELL_CONTROL_IDS.
function isKnownShellControl(id: string): id is OpenturnShellControl;

TrailShellControl

Type-level helper for compile-time exhaustiveness. Only includes the control ids whose placement is "toolbar-trail" — used by toolbar implementations to require a handler for every trail control.
type TrailShellControl = "save" | "load" | "reset" | "returnToLobby";

OpenturnShellControl

Re-exported from @openturn/manifest for convenience. The canonical union of known shell-control ids.

encodeBridgeFragment(init) / decodeBridgeFragment(hash)

Lossless round-trip between BridgeInit and the URL-fragment string.

readBridgeFragmentFromLocation()

Decodes the BridgeInit from window.location.hash (returns null when running outside a browser or when no fragment is present). This is the default readInit for createGameBridge; export it for custom bootstraps that want the same fallback.

BRIDGE_MESSAGE_NAMESPACE

Prefix used by every bridge postMessage type (openturn:bridge:*). Filter on this before schema validation when sharing a window with other postMessage traffic.

Play-shell adapter

The play shell (cloud /play and the local CLI dev shell) is a bridge-host shell that renders shell controls (save, load, reset, return-to-lobby, public rooms, …) around any embedded game. The adapter is the host-side contract that the shell calls into when those controls are activated; the bridge package owns its types so both shells can be implemented against the same surface and the shell-controls registry can gate rendering on adapter[meta.adapterMethod].

PlayShellAdapter

interface PlayShellAdapter {
  readonly meta: PlayShellAdapterMeta;     // includes shellControls config from the manifest
  createRoom(opts?): Promise<PlayRoomResult>;
  joinRoom(roomID: string): Promise<PlayRoomResult>;
  // Optional methods correspond to shell controls; see SHELL_CONTROLS for the mapping:
  saveCurrentRoom?(): Promise<SaveRoomResult>;
  createRoomFromSave?(saveID: string): Promise<PlayRoomResult>;
  resetRoom?(): Promise<RoomActionResult>;
  returnToLobby?(): Promise<RoomActionResult>;
  listPublicRooms?(): Promise<readonly PublicRoomSummary[]>;
  setVisibility?(visibility: PlayRoomVisibility): Promise<SetVisibilityResult>;
}
A control renders only when the adapter implements its backing method (or it is shell-only) and the manifest hasn’t disabled it — see isShellControlEnabled.

Adapter and room types

  • PlayShellAdapterMeta — what the adapter advertises about itself: shellControls?: OpenturnShellControlsConfig (from the manifest), invite affordances, etc.
  • PlayMultiplayerConfig — config the play shell hands the adapter at construction.
  • PlayRoomSnapshot, PlayRoomStatus, PlayRoomVisibility, PlayRoomResult — room state shapes.
  • PresenceSeat, PresenceSnapshot — per-seat presence rows.
  • PublicRoomSummary — entries returned by listPublicRooms.
  • RoomActionResult, SaveRoomResult, SetVisibilityResult — result shapes for the shell-control adapter methods.

Helpers

  • describeRoomStatus(status) — render a PlayRoomStatus as a human-readable string for shell UI.
  • extractRoomID(value) — pull the room id out of a free-form invite string or URL; returns null when no id is present.
  • snapshotToBridgeInit(snapshot) — convert a PlayRoomSnapshot into the BridgeInit baked into the iframe fragment. Shared between both play shells.

Re-exports

  • HostedConnectionDescriptor (from @openturn/client)

See also