onelobby 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/onelobby.js CHANGED
@@ -6,10 +6,10 @@ import { OneLobbyError, PROTO_ID, SignalingClient, } from "./signaling.js";
6
6
  import { defaultSocketFactory, } from "./transport.js";
7
7
  import { WebAudioSink, WorkletMic, webCodecsOpus, webCodecsOpusAvailable } from "./voice-browser.js";
8
8
  import { webRtcLinkFactory } from "./webrtc.js";
9
- import { decodeJoinCode, looksLikeJoinCode } from "./joincode.js";
9
+ import { decodeJoinCode, encodeJoinCode, looksLikeJoinCode } from "./joincode.js";
10
10
  import { DEFAULT_DIRECT_PORT, parseDirectAddress } from "./direct-address.js";
11
11
  export const LIB_NAME = "onelobby";
12
- export const LIB_VERSION = "0.2.0";
12
+ export const LIB_VERSION = "0.2.1";
13
13
  export const DEFAULT_READY_TIMEOUT_MS = 15_000;
14
14
  function hasMic() {
15
15
  return typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && typeof AudioWorkletNode !== "undefined";
@@ -283,7 +283,10 @@ export class OneLobby extends Emitter {
283
283
  const { sig, welcome } = await this.connect(addr.url, this.socketFactory, opts.connectTimeoutMs ?? 5000);
284
284
  try {
285
285
  const state = await sig.request({ t: "join", ...(opts.password ? { password: opts.password } : {}) }, "lobby");
286
- return this.makeLobby(sig, welcome, state, { links });
286
+ const lobby = this.makeLobby(sig, welcome, state, { links });
287
+ // a joiner's joinCode is what it joined with (normalized), so a HUD can show it on both sides
288
+ lobby.joinCode = joinedWith(addr);
289
+ return lobby;
287
290
  }
288
291
  catch (e) {
289
292
  sig.close();
@@ -334,4 +337,15 @@ export class OneLobby extends Emitter {
334
337
  }
335
338
  }
336
339
  }
340
+ /** The join code for an IPv4 address (a host name or IPv6 has none: its `host:port` instead). */
341
+ function joinedWith(addr) {
342
+ if (addr.isIPv6)
343
+ return addr.display;
344
+ try {
345
+ return encodeJoinCode(addr.host, addr.port);
346
+ }
347
+ catch {
348
+ return addr.display;
349
+ }
350
+ }
337
351
  //# sourceMappingURL=onelobby.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"onelobby.js","sourceRoot":"","sources":["../src/onelobby.ts"],"names":[],"mappings":"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","sourcesContent":["import { systemClock, type Clock } from \"./clock.js\";\nimport { Emitter } from \"./emitter.js\";\nimport { normalizeCode } from \"./crockford.js\";\nimport { Lobby, type RateLimit } from \"./lobby.js\";\nimport {\n OneLobbyError,\n PROTO_ID,\n SignalingClient,\n type LobbyState,\n type LobbySummary,\n type WelcomeMsg,\n} from \"./signaling.js\";\nimport {\n defaultSocketFactory,\n type LinkFactory,\n type SocketFactory,\n} from \"./transport.js\";\nimport type { VoiceCodec, FrameSource, AudioSink } from \"./voice-types.js\";\nimport { WebAudioSink, WorkletMic, webCodecsOpus, webCodecsOpusAvailable } from \"./voice-browser.js\";\nimport { webRtcLinkFactory } from \"./webrtc.js\";\nimport { decodeJoinCode, looksLikeJoinCode } from \"./joincode.js\";\nimport { DEFAULT_DIRECT_PORT, parseDirectAddress } from \"./direct-address.js\";\n\nexport const LIB_NAME = \"onelobby\";\nexport const LIB_VERSION = \"0.2.0\";\n\n/** Where `rememberSeat` keeps the resume token: the subset of the Web Storage API it uses. */\nexport interface SeatStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\n/** Seams for tests, adapters and non-browser runtimes. Every default is the browser implementation. */\nexport interface AdvancedOptions {\n socketFactory?: SocketFactory;\n linkFactory?: LinkFactory;\n clock?: Clock;\n /** Wall clock in unix ms, stamps chat lines. */\n wallClock?: () => number;\n /** Opus codec; defaults to WebCodecs when available. Provide a WASM-backed one elsewhere. */\n voiceCodec?: VoiceCodec | null;\n createMic?: (() => FrameSource) | null;\n createSink?: (() => AudioSink) | null;\n /** Storage for `rememberSeat`; defaults to `sessionStorage` when the page has one. null disables it. */\n storage?: SeatStorage | null;\n}\n\nexport interface OneLobbyOptions {\n gameId: string;\n netVersion: string;\n /** `ws://` / `wss://` URL of a onelobby-server. */\n lobbyServer: string;\n mode?: \"lockstep\" | \"raw\";\n tickRate?: number;\n /** Default: tickRate (once a second). */\n hashEveryTicks?: number;\n /** The player's name. Empty (the default): other players see \"Player N\", N being the seat number. */\n displayName?: string;\n relayOnly?: boolean;\n /**\n * Joiners give up on reaching the host after this many ms: `lobby.ready` rejects with code `ice_failed`, the kit\n * emits `error`, and the lobby is left. Half the time goes to a relay-only retry when the server offers TURN.\n * Default 15000; 0 waits forever.\n */\n readyTimeoutMs?: number;\n /**\n * Treat a peer as gone after this many ms of silence. Joiners ping the host every 2 s and the host answers, so the\n * host drops a silent joiner (`peerLeft`, \"timeout\") and a joiner handles a silent host as lost (a direct lobby\n * ends with \"host_left\"). Default 0 (off): a silently dead peer then surfaces when its link or signaling socket\n * times out, within about 30 s. Values under 6000 are raised to 6000.\n */\n peerTimeoutMs?: number;\n /**\n * Keep this tab's seat across a page reload (sessionStorage): `join(code)` and `resume()` take the seat back\n * instead of joining as a new player. Cleared when the session ends. Default true where sessionStorage exists.\n */\n rememberSeat?: boolean;\n /** Test and runtime seams (see AdvancedOptions). */\n advanced?: AdvancedOptions;\n\n /** @deprecated use `advanced.socketFactory` */\n socketFactory?: SocketFactory;\n /** @deprecated use `advanced.linkFactory` */\n linkFactory?: LinkFactory;\n /** @deprecated use `advanced.clock` */\n clock?: Clock;\n /** @deprecated use `advanced.wallClock` */\n wallClock?: () => number;\n /** @deprecated use `advanced.voiceCodec` */\n voiceCodec?: VoiceCodec | null;\n /** @deprecated use `advanced.createMic` */\n createMic?: (() => FrameSource) | null;\n /** @deprecated use `advanced.createSink` */\n createSink?: (() => AudioSink) | null;\n}\n\nexport interface HostOptions {\n name: string;\n maxPlayers: number;\n /**\n * \"unlisted\" (default): anyone with the code or link can join, but the lobby is not in `list()`.\n * \"public\": also listed. \"private\": the host approves each join.\n */\n visibility?: \"public\" | \"unlisted\" | \"private\";\n lateJoin?: boolean;\n meta?: Record<string, string>;\n /** Per-joiner cap on incoming raw messages, e.g. `{ messages: 60, perMs: 1000 }`; the excess is dropped before `message` fires or is relayed. */\n rateLimit?: RateLimit;\n}\n\nexport interface ListOptions {\n meta?: Record<string, string>;\n limit?: number;\n cursor?: string;\n}\n\nexport interface JoinDirectOptions {\n /** The host's password, if it set one; a wrong one fails with `bad_password`. */\n password?: string;\n /** Port for an address typed without one (default 47810, docs/direct-ip.md s.2). */\n defaultPort?: number;\n /** No answer on the host's TCP port within this long fails with `direct_unreachable` (default 5000). */\n connectTimeoutMs?: number;\n}\n\n/** @internal How onelobby/direct makes a host lobby: its own socket and link factory, and direct mode. */\nexport interface DirectLobbyHooks {\n socketFactory: SocketFactory;\n /** Wraps the kit's own link factory (port pin, no ICE servers). */\n wrapLinks: (inner: LinkFactory) => LinkFactory;\n}\n\nexport type OneLobbyEvents = { error: [Error] };\n\nexport const DEFAULT_READY_TIMEOUT_MS = 15_000;\n\nfunction hasMic(): boolean {\n return typeof navigator !== \"undefined\" && !!navigator.mediaDevices?.getUserMedia && typeof AudioWorkletNode !== \"undefined\";\n}\n\nfunction browserStorage(): SeatStorage | null {\n try {\n const s = (globalThis as { sessionStorage?: SeatStorage }).sessionStorage;\n return s ?? null;\n } catch {\n return null; // storage access can throw (sandboxed iframe, blocked cookies)\n }\n}\n\ninterface Seat {\n code: string;\n token: string;\n server: string;\n}\n\n/**\n * Lobbies alive in this page, by resume token. A remembered seat that is still in use here (two kits in one tab,\n * e.g. a local two-player demo) must not be taken over; after a reload this set starts empty.\n */\nconst liveSeats = new Set<Lobby>();\nfunction seatInUse(token: string): boolean {\n for (const l of liveSeats) if (l.resumeToken === token) return true;\n return false;\n}\n\n/** Entry point (DESIGN s.11.2). */\nexport class OneLobby extends Emitter<OneLobbyEvents> {\n readonly options: Required<Pick<OneLobbyOptions, \"gameId\" | \"netVersion\" | \"lobbyServer\" | \"mode\" | \"tickRate\" | \"hashEveryTicks\" | \"displayName\" | \"relayOnly\" | \"readyTimeoutMs\" | \"peerTimeoutMs\">>;\n private socketFactory: SocketFactory;\n private linkFactory: LinkFactory;\n private clock: Clock;\n private wall: () => number;\n private voiceCodec: VoiceCodec | null;\n private createMic: (() => FrameSource) | null;\n private createSink: (() => AudioSink) | null;\n private storage: SeatStorage | null;\n\n constructor(o: OneLobbyOptions) {\n super();\n const tickRate = o.tickRate ?? 20;\n this.options = {\n gameId: o.gameId,\n netVersion: o.netVersion,\n lobbyServer: o.lobbyServer,\n mode: o.mode ?? \"lockstep\",\n tickRate,\n hashEveryTicks: o.hashEveryTicks ?? tickRate,\n displayName: (o.displayName ?? \"\").trim(),\n relayOnly: o.relayOnly ?? false,\n readyTimeoutMs: o.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS,\n peerTimeoutMs: o.peerTimeoutMs ?? 0,\n };\n const a = o.advanced ?? {};\n const pick = <K extends keyof AdvancedOptions & keyof OneLobbyOptions>(k: K) => (a[k] !== undefined ? a[k] : o[k]);\n this.socketFactory = pick(\"socketFactory\") ?? defaultSocketFactory;\n this.linkFactory = pick(\"linkFactory\") ?? webRtcLinkFactory;\n this.clock = pick(\"clock\") ?? systemClock;\n this.wall = pick(\"wallClock\") ?? (() => Date.now());\n const codec = pick(\"voiceCodec\");\n this.voiceCodec = codec !== undefined ? codec : webCodecsOpusAvailable() ? webCodecsOpus : null;\n const mic = pick(\"createMic\");\n this.createMic = mic !== undefined ? mic : hasMic() ? () => new WorkletMic() : null;\n const sink = pick(\"createSink\");\n this.createSink = sink !== undefined ? sink : typeof AudioContext !== \"undefined\" ? () => new WebAudioSink() : null;\n this.storage = o.rememberSeat === false ? null : a.storage !== undefined ? a.storage : browserStorage();\n }\n\n // ---- remembered seat -------------------------------------------------------------------------------------------\n\n private get seatKey(): string {\n return `onelobby.seat.${this.options.gameId}`;\n }\n\n private loadSeat(): Seat | null {\n if (!this.storage) return null;\n try {\n const raw = this.storage.getItem(this.seatKey);\n if (!raw) return null;\n const s = JSON.parse(raw) as Partial<Seat>;\n if (typeof s.code !== \"string\" || typeof s.token !== \"string\" || s.server !== this.options.lobbyServer) return null;\n return s as Seat;\n } catch {\n return null;\n }\n }\n\n private saveSeat(code: string, token: string): void {\n if (!this.storage || !token) return;\n try {\n this.storage.setItem(this.seatKey, JSON.stringify({ code, token, server: this.options.lobbyServer } satisfies Seat));\n } catch {\n /* quota or blocked storage: the seat just is not remembered */\n }\n }\n\n private clearSeat(code?: string): void {\n if (!this.storage) return;\n try {\n if (code !== undefined) {\n const s = this.loadSeat();\n if (s && s.code !== code) return;\n }\n this.storage.removeItem(this.seatKey);\n } catch {\n /* ignore */\n }\n }\n\n /** The lobby code of the seat this tab can take back (rememberSeat), or null. */\n get rememberedCode(): string | null {\n return this.loadSeat()?.code ?? null;\n }\n\n /**\n * Take back the seat remembered from before a page reload (host or joiner). Resolves null when there is none\n * or the server no longer holds it (the seat is forgotten then).\n */\n async resume(): Promise<Lobby | null> {\n const seat = this.loadSeat();\n if (!seat || seatInUse(seat.token)) return null;\n try {\n return await this.rejoin(seat.token);\n } catch (e) {\n if (e instanceof OneLobbyError && e.code === \"invalid_token\") {\n this.clearSeat();\n return null;\n }\n throw e;\n }\n }\n\n // ---- connecting ------------------------------------------------------------------------------------------------\n\n private helloMsg(resumeToken?: string): Record<string, unknown> {\n return {\n t: \"hello\",\n proto: PROTO_ID,\n gameId: this.options.gameId,\n netVersion: this.options.netVersion,\n client: { lib: LIB_NAME, ver: LIB_VERSION, platform: typeof navigator !== \"undefined\" ? \"browser\" : \"node\" },\n name: this.options.displayName.slice(0, 24),\n ...(resumeToken ? { resumeToken } : {}),\n };\n }\n\n private async connect(url = this.options.lobbyServer, factory = this.socketFactory, timeoutMs = 0): Promise<{ sig: SignalingClient; welcome: WelcomeMsg }> {\n const sig = new SignalingClient(url, factory, this.clock);\n if (timeoutMs > 0) {\n let cancel = () => {};\n const timeout = new Promise<never>((_, reject) => {\n cancel = this.clock.setTimeout(() => reject(new OneLobbyError(\"direct_unreachable\", `no answer from ${url} within ${timeoutMs} ms`)), timeoutMs);\n });\n try {\n await Promise.race([sig.connect(), timeout]);\n } catch (e) {\n sig.close();\n if (e instanceof OneLobbyError && e.code === \"connect_failed\") throw new OneLobbyError(\"direct_unreachable\", `no answer from ${url}`);\n throw e;\n } finally {\n cancel();\n }\n } else await sig.connect();\n try {\n const welcome = await sig.request<WelcomeMsg & { t: string }>(this.helloMsg(), \"welcome\");\n return { sig, welcome };\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n private makeLobby(sig: SignalingClient, welcome: WelcomeMsg, state: LobbyState, direct?: { links: LinkFactory }, rateLimit?: RateLimit): Lobby {\n const lobby = new Lobby({\n signaling: sig,\n state,\n ice: welcome.ice ?? [],\n links: direct?.links ?? this.linkFactory,\n direct: !!direct,\n clock: this.clock,\n wall: this.wall,\n hashEveryTicks: this.options.hashEveryTicks,\n relayOnly: state.relayOnly ?? this.options.relayOnly,\n displayName: this.options.displayName,\n voice: { codec: this.voiceCodec, createSource: this.createMic, createSink: this.createSink },\n readyTimeoutMs: this.options.readyTimeoutMs,\n peerTimeoutMs: this.options.peerTimeoutMs,\n rateLimit,\n });\n lobby.errorSink = (e) => this.emit(\"error\", e);\n if (this.storage && !direct) {\n liveSeats.add(lobby);\n this.saveSeat(lobby.code, lobby.resumeToken);\n lobby.seatSink = (token) => this.saveSeat(lobby.code, token);\n lobby.on(\"sessionEnded\", () => {\n liveSeats.delete(lobby);\n this.clearSeat(lobby.code);\n });\n }\n return lobby;\n }\n\n async host(h: HostOptions): Promise<Lobby> {\n const { sig, welcome } = await this.connect();\n try {\n const state = await sig.request<LobbyState>(\n {\n t: \"create\",\n name: h.name,\n maxPlayers: h.maxPlayers,\n visibility: h.visibility ?? \"unlisted\",\n mode: this.options.mode,\n tickRate: this.options.tickRate,\n lateJoin: h.lateJoin ?? true,\n meta: h.meta ?? {},\n ...(this.options.relayOnly ? { relayOnly: true } : {}),\n },\n \"lobby\",\n );\n return this.makeLobby(sig, welcome, state, undefined, h.rateLimit);\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n async join(code: string): Promise<Lobby> {\n const norm = normalizeCode(code);\n if (!norm) throw new OneLobbyError(\"bad_code\", `\"${code}\" is not a valid join code`);\n const seat = this.loadSeat();\n if (seat && seat.code === norm && !seatInUse(seat.token)) {\n // Same tab, same lobby (a page reload): take the old seat back instead of joining as a new player.\n try {\n return await this.rejoin(seat.token);\n } catch (e) {\n this.clearSeat();\n if (!(e instanceof OneLobbyError && e.code === \"invalid_token\")) throw e;\n }\n }\n const { sig, welcome } = await this.connect();\n try {\n const state = await sig.request<LobbyState>({ t: \"join\", code: norm }, \"lobby\");\n return this.makeLobby(sig, welcome, state);\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n /**\n * Join a direct-IP host (docs/direct-ip.md): a join code from the host's screen (`6B01-RGFE-P2B`, any case, spaces\n * or hyphens) or, advanced, an address (`ip`, `ip:port`, `[v6]:port`). No lobby server is involved: the code is\n * the host's address. A mistyped code fails at once with `bad_code`; nobody listening is `direct_unreachable`.\n * The address is also given to ICE as the host's UDP candidate. When the host leaves, `sessionEnded(\"host_left\")`.\n */\n async joinDirect(codeOrAddress: string, opts: JoinDirectOptions = {}): Promise<Lobby> {\n let input = codeOrAddress.trim();\n if (looksLikeJoinCode(input)) {\n const d = decodeJoinCode(input);\n if (\"error\" in d)\n throw new OneLobbyError(d.error, d.error === \"unknown_version\" ? `\"${input}\" is a join code from a newer version of the game` : `\"${input}\" is not a valid join code: check for a typo`);\n input = `${d.ip}:${d.port}`;\n }\n const addr = parseDirectAddress(input, opts.defaultPort ?? DEFAULT_DIRECT_PORT);\n if (!addr) throw new OneLobbyError(\"bad_address\", `\"${codeOrAddress}\" is not a join code or an address (try 6B01-RGFE-P2B or 192.168.1.20:47810)`);\n const inner = this.linkFactory;\n const links: LinkFactory = { create: (o) => inner.create({ ...o, iceServers: [], extraRemoteCandidates: [addr.candidate] }) };\n const { sig, welcome } = await this.connect(addr.url, this.socketFactory, opts.connectTimeoutMs ?? 5000);\n try {\n const state = await sig.request<LobbyState>({ t: \"join\", ...(opts.password ? { password: opts.password } : {}) }, \"lobby\");\n return this.makeLobby(sig, welcome, state, { links });\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n /** @internal onelobby/direct's host: create the one lobby on the embedded hub, in direct mode. */\n async hostDirectWith(h: HostOptions, hooks: DirectLobbyHooks): Promise<Lobby> {\n const { sig, welcome } = await this.connect(\"onelobby-direct://host/\", hooks.socketFactory);\n try {\n const state = await sig.request<LobbyState>(\n { t: \"create\", name: h.name, maxPlayers: h.maxPlayers, visibility: \"unlisted\", mode: this.options.mode, tickRate: this.options.tickRate,\n lateJoin: h.lateJoin ?? true, meta: h.meta ?? {} },\n \"lobby\",\n );\n return this.makeLobby(sig, welcome, state, { links: hooks.wrapLinks(this.linkFactory) }, h.rateLimit);\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n async rejoin(resumeToken: string): Promise<Lobby> {\n const sig = new SignalingClient(this.options.lobbyServer, this.socketFactory, this.clock);\n await sig.connect();\n try {\n // The server answers hello(resumeToken) with welcome then the full lobby state; listen for both up front.\n const lobbyP = sig.expect<LobbyState>(\"lobby\");\n const welcomeP = sig.request<WelcomeMsg & { t: string }>(this.helloMsg(resumeToken), \"welcome\");\n lobbyP.catch(() => {}); // settled below or by close()\n const welcome = await welcomeP;\n // spec: an unknown or expired token is ignored -> plain welcome (no peerId), and no lobby follows\n if (welcome.peerId === undefined) throw new OneLobbyError(\"invalid_token\", \"resume token unknown or expired\");\n const state = await lobbyP;\n return this.makeLobby(sig, welcome, state);\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n async list(opts: ListOptions = {}): Promise<LobbySummary[]> {\n const { sig } = await this.connect();\n try {\n const res = await sig.request<{ t: string; items: LobbySummary[] }>(\n { t: \"list\", visibility: \"public\", meta: opts.meta ?? {}, limit: Math.min(100, opts.limit ?? 50), ...(opts.cursor ? { cursor: opts.cursor } : {}) },\n \"lobbies\",\n );\n return res.items ?? [];\n } finally {\n sig.close();\n }\n }\n}\n"]}
1
+ {"version":3,"file":"onelobby.js","sourceRoot":"","sources":["../src/onelobby.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAc,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAkB,MAAM,YAAY,CAAC;AACnD,OAAO,EACL,aAAa,EACb,QAAQ,EACR,eAAe,GAIhB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,oBAAoB,GAGrB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AACrG,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9E,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAC;AACnC,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC;AA+GnC,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC;AAE/C,SAAS,MAAM;IACb,OAAO,OAAO,SAAS,KAAK,WAAW,IAAI,CAAC,CAAC,SAAS,CAAC,YAAY,EAAE,YAAY,IAAI,OAAO,gBAAgB,KAAK,WAAW,CAAC;AAC/H,CAAC;AAED,SAAS,cAAc;IACrB,IAAI,CAAC;QACH,MAAM,CAAC,GAAI,UAA+C,CAAC,cAAc,CAAC;QAC1E,OAAO,CAAC,IAAI,IAAI,CAAC;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,+DAA+D;IAC9E,CAAC;AACH,CAAC;AAQD;;;GAGG;AACH,MAAM,SAAS,GAAG,IAAI,GAAG,EAAS,CAAC;AACnC,SAAS,SAAS,CAAC,KAAa;IAC9B,KAAK,MAAM,CAAC,IAAI,SAAS;QAAE,IAAI,CAAC,CAAC,WAAW,KAAK,KAAK;YAAE,OAAO,IAAI,CAAC;IACpE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,mCAAmC;AACnC,MAAM,OAAO,QAAS,SAAQ,OAAuB;IAC1C,OAAO,CAAuL;IAC/L,aAAa,CAAgB;IAC7B,WAAW,CAAc;IACzB,KAAK,CAAQ;IACb,IAAI,CAAe;IACnB,UAAU,CAAoB;IAC9B,SAAS,CAA6B;IACtC,UAAU,CAA2B;IACrC,OAAO,CAAqB;IAEpC,YAAY,CAAkB;QAC5B,KAAK,EAAE,CAAC;QACR,MAAM,QAAQ,GAAG,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC;QAClC,IAAI,CAAC,OAAO,GAAG;YACb,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,UAAU;YAC1B,QAAQ;YACR,cAAc,EAAE,CAAC,CAAC,cAAc,IAAI,QAAQ;YAC5C,WAAW,EAAE,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;YACzC,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,KAAK;YAC/B,cAAc,EAAE,CAAC,CAAC,cAAc,IAAI,wBAAwB;YAC5D,aAAa,EAAE,CAAC,CAAC,aAAa,IAAI,CAAC;SACpC,CAAC;QACF,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,CAA0D,CAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACnH,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,oBAAoB,CAAC;QACnE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,iBAAiB,CAAC;QAC5D,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,WAAW,CAAC;QAC1C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QACpD,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC;QACjC,IAAI,CAAC,UAAU,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,sBAAsB,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC;QAChG,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC;QAC9B,IAAI,CAAC,SAAS,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,UAAU,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACpF,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC;QAChC,IAAI,CAAC,UAAU,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,YAAY,KAAK,WAAW,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACpH,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,YAAY,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC;IAC1G,CAAC;IAED,mHAAmH;IAEnH,IAAY,OAAO;QACjB,OAAO,iBAAiB,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;IAChD,CAAC;IAEO,QAAQ;QACd,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC/B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC/C,IAAI,CAAC,GAAG;gBAAE,OAAO,IAAI,CAAC;YACtB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;YAC3C,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,OAAO,CAAC,WAAW;gBAAE,OAAO,IAAI,CAAC;YACpH,OAAO,CAAS,CAAC;QACnB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAEO,QAAQ,CAAC,IAAY,EAAE,KAAa;QAC1C,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK;YAAE,OAAO;QACpC,IAAI,CAAC;YACH,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW,EAAiB,CAAC,CAAC,CAAC;QACvH,CAAC;QAAC,MAAM,CAAC;YACP,+DAA+D;QACjE,CAAC;IACH,CAAC;IAEO,SAAS,CAAC,IAAa;QAC7B,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAI,CAAC;YACH,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACvB,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAC1B,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI;oBAAE,OAAO;YACnC,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,YAAY;QACd,CAAC;IACH,CAAC;IAED,iFAAiF;IACjF,IAAI,cAAc;QAChB,OAAO,IAAI,CAAC,QAAQ,EAAE,EAAE,IAAI,IAAI,IAAI,CAAC;IACvC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAM;QACV,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAChD,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,YAAY,aAAa,IAAI,CAAC,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;gBAC7D,IAAI,CAAC,SAAS,EAAE,CAAC;gBACjB,OAAO,IAAI,CAAC;YACd,CAAC;YACD,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC;IAED,mHAAmH;IAE3G,QAAQ,CAAC,WAAoB;QACnC,OAAO;YACL,CAAC,EAAE,OAAO;YACV,KAAK,EAAE,QAAQ;YACf,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM;YAC3B,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU;YACnC,MAAM,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,SAAS,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,EAAE;YAC5G,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;YAC3C,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxC,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO,GAAG,IAAI,CAAC,aAAa,EAAE,SAAS,GAAG,CAAC;QAC/F,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1D,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;YAClB,IAAI,MAAM,GAAG,GAAG,EAAE,GAAE,CAAC,CAAC;YACtB,MAAM,OAAO,GAAG,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;gBAC/C,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,aAAa,CAAC,oBAAoB,EAAE,kBAAkB,GAAG,WAAW,SAAS,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;YACnJ,CAAC,CAAC,CAAC;YACH,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;YAC/C,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,GAAG,CAAC,KAAK,EAAE,CAAC;gBACZ,IAAI,CAAC,YAAY,aAAa,IAAI,CAAC,CAAC,IAAI,KAAK,gBAAgB;oBAAE,MAAM,IAAI,aAAa,CAAC,oBAAoB,EAAE,kBAAkB,GAAG,EAAE,CAAC,CAAC;gBACtI,MAAM,CAAC,CAAC;YACV,CAAC;oBAAS,CAAC;gBACT,MAAM,EAAE,CAAC;YACX,CAAC;QACH,CAAC;;YAAM,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,OAAO,CAA6B,IAAI,CAAC,QAAQ,EAAE,EAAE,SAAS,CAAC,CAAC;YAC1F,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;QAC1B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,GAAG,CAAC,KAAK,EAAE,CAAC;YACZ,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC;IAEO,SAAS,CAAC,GAAoB,EAAE,OAAmB,EAAE,KAAiB,EAAE,MAA+B,EAAE,SAAqB;QACpI,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC;YACtB,SAAS,EAAE,GAAG;YACd,KAAK;YACL,GAAG,EAAE,OAAO,CAAC,GAAG,IAAI,EAAE;YACtB,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,IAAI,CAAC,WAAW;YACxC,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,cAAc,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc;YAC3C,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS;YACpD,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW;YACrC,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,YAAY,EAAE,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE;YAC5F,cAAc,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc;YAC3C,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa;YACzC,SAAS;SACV,CAAC,CAAC;QACH,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC/C,IAAI,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;YAC5B,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACrB,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;YAC7C,KAAK,CAAC,QAAQ,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAC7D,KAAK,CAAC,EAAE,CAAC,cAAc,EAAE,GAAG,EAAE;gBAC5B,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACxB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC7B,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,CAAc;QACvB,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAC9C,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,OAAO,CAC7B;gBACE,CAAC,EAAE,QAAQ;gBACX,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,UAAU,EAAE,CAAC,CAAC,UAAU;gBACxB,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,UAAU;gBACtC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI;gBACvB,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ;gBAC/B,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,IAAI;gBAC5B,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE;gBAClB,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACvD,EACD,OAAO,CACR,CAAC;YACF,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC;QACrE,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,GAAG,CAAC,KAAK,EAAE,CAAC;YACZ,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAY;QACrB,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,aAAa,CAAC,UAAU,EAAE,IAAI,IAAI,4BAA4B,CAAC,CAAC;QACrF,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC7B,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACzD,mGAAmG;YACnG,IAAI,CAAC;gBACH,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACvC,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,IAAI,CAAC,SAAS,EAAE,CAAC;gBACjB,IAAI,CAAC,CAAC,CAAC,YAAY,aAAa,IAAI,CAAC,CAAC,IAAI,KAAK,eAAe,CAAC;oBAAE,MAAM,CAAC,CAAC;YAC3E,CAAC;QACH,CAAC;QACD,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAC9C,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,OAAO,CAAa,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;YAChF,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,GAAG,CAAC,KAAK,EAAE,CAAC;YACZ,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,UAAU,CAAC,aAAqB,EAAE,OAA0B,EAAE;QAClE,IAAI,KAAK,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC;QACjC,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7B,MAAM,CAAC,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;YAChC,IAAI,OAAO,IAAI,CAAC;gBACd,MAAM,IAAI,aAAa,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,KAAK,iBAAiB,CAAC,CAAC,CAAC,IAAI,KAAK,mDAAmD,CAAC,CAAC,CAAC,IAAI,KAAK,8CAA8C,CAAC,CAAC;YAC3L,KAAK,GAAG,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9B,CAAC;QACD,MAAM,IAAI,GAAG,kBAAkB,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,IAAI,mBAAmB,CAAC,CAAC;QAChF,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,aAAa,CAAC,aAAa,EAAE,IAAI,aAAa,8EAA8E,CAAC,CAAC;QACnJ,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC;QAC/B,MAAM,KAAK,GAAgB,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,qBAAqB,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC;QAC9H,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,CAAC;QACzG,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,OAAO,CAAa,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;YAC3H,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YAC7D,8FAA8F;YAC9F,KAAK,CAAC,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;YAClC,OAAO,KAAK,CAAC;QACf,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,GAAG,CAAC,KAAK,EAAE,CAAC;YACZ,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC;IAED,kGAAkG;IAClG,KAAK,CAAC,cAAc,CAAC,CAAc,EAAE,KAAuB;QAC1D,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,yBAAyB,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;QAC5F,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,OAAO,CAC7B,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ;gBACrI,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,EACpD,OAAO,CACR,CAAC;YACF,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC;QACxG,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,GAAG,CAAC,KAAK,EAAE,CAAC;YACZ,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,WAAmB;QAC9B,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1F,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;QACpB,IAAI,CAAC;YACH,0GAA0G;YAC1G,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAa,OAAO,CAAC,CAAC;YAC/C,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAA6B,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,CAAC,CAAC;YAChG,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAAC,8BAA8B;YACtD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC;YAC/B,kGAAkG;YAClG,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;gBAAE,MAAM,IAAI,aAAa,CAAC,eAAe,EAAE,iCAAiC,CAAC,CAAC;YAC9G,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC;YAC3B,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,GAAG,CAAC,KAAK,EAAE,CAAC;YACZ,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAoB,EAAE;QAC/B,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,OAAO,CAC3B,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EACnJ,SAAS,CACV,CAAC;YACF,OAAO,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;QACzB,CAAC;gBAAS,CAAC;YACT,GAAG,CAAC,KAAK,EAAE,CAAC;QACd,CAAC;IACH,CAAC;CACF;AAED,iGAAiG;AACjG,SAAS,UAAU,CAAC,IAAsE;IACxF,IAAI,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC;IACrC,IAAI,CAAC;QACH,OAAO,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;AACH,CAAC","sourcesContent":["import { systemClock, type Clock } from \"./clock.js\";\nimport { Emitter } from \"./emitter.js\";\nimport { normalizeCode } from \"./crockford.js\";\nimport { Lobby, type RateLimit } from \"./lobby.js\";\nimport {\n OneLobbyError,\n PROTO_ID,\n SignalingClient,\n type LobbyState,\n type LobbySummary,\n type WelcomeMsg,\n} from \"./signaling.js\";\nimport {\n defaultSocketFactory,\n type LinkFactory,\n type SocketFactory,\n} from \"./transport.js\";\nimport type { VoiceCodec, FrameSource, AudioSink } from \"./voice-types.js\";\nimport { WebAudioSink, WorkletMic, webCodecsOpus, webCodecsOpusAvailable } from \"./voice-browser.js\";\nimport { webRtcLinkFactory } from \"./webrtc.js\";\nimport { decodeJoinCode, encodeJoinCode, looksLikeJoinCode } from \"./joincode.js\";\nimport { DEFAULT_DIRECT_PORT, parseDirectAddress } from \"./direct-address.js\";\n\nexport const LIB_NAME = \"onelobby\";\nexport const LIB_VERSION = \"0.2.1\";\n\n/** Where `rememberSeat` keeps the resume token: the subset of the Web Storage API it uses. */\nexport interface SeatStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\n/** Seams for tests, adapters and non-browser runtimes. Every default is the browser implementation. */\nexport interface AdvancedOptions {\n socketFactory?: SocketFactory;\n linkFactory?: LinkFactory;\n clock?: Clock;\n /** Wall clock in unix ms, stamps chat lines. */\n wallClock?: () => number;\n /** Opus codec; defaults to WebCodecs when available. Provide a WASM-backed one elsewhere. */\n voiceCodec?: VoiceCodec | null;\n createMic?: (() => FrameSource) | null;\n createSink?: (() => AudioSink) | null;\n /** Storage for `rememberSeat`; defaults to `sessionStorage` when the page has one. null disables it. */\n storage?: SeatStorage | null;\n}\n\nexport interface OneLobbyOptions {\n gameId: string;\n netVersion: string;\n /** `ws://` / `wss://` URL of a onelobby-server. */\n lobbyServer: string;\n mode?: \"lockstep\" | \"raw\";\n tickRate?: number;\n /** Default: tickRate (once a second). */\n hashEveryTicks?: number;\n /** The player's name. Empty (the default): other players see \"Player N\", N being the seat number. */\n displayName?: string;\n relayOnly?: boolean;\n /**\n * Joiners give up on reaching the host after this many ms: `lobby.ready` rejects with code `ice_failed`, the kit\n * emits `error`, and the lobby is left. Half the time goes to a relay-only retry when the server offers TURN.\n * Default 15000; 0 waits forever.\n */\n readyTimeoutMs?: number;\n /**\n * Treat a peer as gone after this many ms of silence. Joiners ping the host every 2 s and the host answers, so the\n * host drops a silent joiner (`peerLeft`, \"timeout\") and a joiner handles a silent host as lost (a direct lobby\n * ends with \"host_left\"). Default 0 (off): a silently dead peer then surfaces when its link or signaling socket\n * times out, within about 30 s. Values under 6000 are raised to 6000.\n */\n peerTimeoutMs?: number;\n /**\n * Keep this tab's seat across a page reload (sessionStorage): `join(code)` and `resume()` take the seat back\n * instead of joining as a new player. Cleared when the session ends. Default true where sessionStorage exists.\n */\n rememberSeat?: boolean;\n /** Test and runtime seams (see AdvancedOptions). */\n advanced?: AdvancedOptions;\n\n /** @deprecated use `advanced.socketFactory` */\n socketFactory?: SocketFactory;\n /** @deprecated use `advanced.linkFactory` */\n linkFactory?: LinkFactory;\n /** @deprecated use `advanced.clock` */\n clock?: Clock;\n /** @deprecated use `advanced.wallClock` */\n wallClock?: () => number;\n /** @deprecated use `advanced.voiceCodec` */\n voiceCodec?: VoiceCodec | null;\n /** @deprecated use `advanced.createMic` */\n createMic?: (() => FrameSource) | null;\n /** @deprecated use `advanced.createSink` */\n createSink?: (() => AudioSink) | null;\n}\n\nexport interface HostOptions {\n name: string;\n maxPlayers: number;\n /**\n * \"unlisted\" (default): anyone with the code or link can join, but the lobby is not in `list()`.\n * \"public\": also listed. \"private\": the host approves each join.\n */\n visibility?: \"public\" | \"unlisted\" | \"private\";\n lateJoin?: boolean;\n meta?: Record<string, string>;\n /** Per-joiner cap on incoming raw messages, e.g. `{ messages: 60, perMs: 1000 }`; the excess is dropped before `message` fires or is relayed. */\n rateLimit?: RateLimit;\n}\n\nexport interface ListOptions {\n meta?: Record<string, string>;\n limit?: number;\n cursor?: string;\n}\n\nexport interface JoinDirectOptions {\n /** The host's password, if it set one; a wrong one fails with `bad_password`. */\n password?: string;\n /** Port for an address typed without one (default 47810, docs/direct-ip.md s.2). */\n defaultPort?: number;\n /** No answer on the host's TCP port within this long fails with `direct_unreachable` (default 5000). */\n connectTimeoutMs?: number;\n}\n\n/** @internal How onelobby/direct makes a host lobby: its own socket and link factory, and direct mode. */\nexport interface DirectLobbyHooks {\n socketFactory: SocketFactory;\n /** Wraps the kit's own link factory (port pin, no ICE servers). */\n wrapLinks: (inner: LinkFactory) => LinkFactory;\n}\n\nexport type OneLobbyEvents = { error: [Error] };\n\nexport const DEFAULT_READY_TIMEOUT_MS = 15_000;\n\nfunction hasMic(): boolean {\n return typeof navigator !== \"undefined\" && !!navigator.mediaDevices?.getUserMedia && typeof AudioWorkletNode !== \"undefined\";\n}\n\nfunction browserStorage(): SeatStorage | null {\n try {\n const s = (globalThis as { sessionStorage?: SeatStorage }).sessionStorage;\n return s ?? null;\n } catch {\n return null; // storage access can throw (sandboxed iframe, blocked cookies)\n }\n}\n\ninterface Seat {\n code: string;\n token: string;\n server: string;\n}\n\n/**\n * Lobbies alive in this page, by resume token. A remembered seat that is still in use here (two kits in one tab,\n * e.g. a local two-player demo) must not be taken over; after a reload this set starts empty.\n */\nconst liveSeats = new Set<Lobby>();\nfunction seatInUse(token: string): boolean {\n for (const l of liveSeats) if (l.resumeToken === token) return true;\n return false;\n}\n\n/** Entry point (DESIGN s.11.2). */\nexport class OneLobby extends Emitter<OneLobbyEvents> {\n readonly options: Required<Pick<OneLobbyOptions, \"gameId\" | \"netVersion\" | \"lobbyServer\" | \"mode\" | \"tickRate\" | \"hashEveryTicks\" | \"displayName\" | \"relayOnly\" | \"readyTimeoutMs\" | \"peerTimeoutMs\">>;\n private socketFactory: SocketFactory;\n private linkFactory: LinkFactory;\n private clock: Clock;\n private wall: () => number;\n private voiceCodec: VoiceCodec | null;\n private createMic: (() => FrameSource) | null;\n private createSink: (() => AudioSink) | null;\n private storage: SeatStorage | null;\n\n constructor(o: OneLobbyOptions) {\n super();\n const tickRate = o.tickRate ?? 20;\n this.options = {\n gameId: o.gameId,\n netVersion: o.netVersion,\n lobbyServer: o.lobbyServer,\n mode: o.mode ?? \"lockstep\",\n tickRate,\n hashEveryTicks: o.hashEveryTicks ?? tickRate,\n displayName: (o.displayName ?? \"\").trim(),\n relayOnly: o.relayOnly ?? false,\n readyTimeoutMs: o.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS,\n peerTimeoutMs: o.peerTimeoutMs ?? 0,\n };\n const a = o.advanced ?? {};\n const pick = <K extends keyof AdvancedOptions & keyof OneLobbyOptions>(k: K) => (a[k] !== undefined ? a[k] : o[k]);\n this.socketFactory = pick(\"socketFactory\") ?? defaultSocketFactory;\n this.linkFactory = pick(\"linkFactory\") ?? webRtcLinkFactory;\n this.clock = pick(\"clock\") ?? systemClock;\n this.wall = pick(\"wallClock\") ?? (() => Date.now());\n const codec = pick(\"voiceCodec\");\n this.voiceCodec = codec !== undefined ? codec : webCodecsOpusAvailable() ? webCodecsOpus : null;\n const mic = pick(\"createMic\");\n this.createMic = mic !== undefined ? mic : hasMic() ? () => new WorkletMic() : null;\n const sink = pick(\"createSink\");\n this.createSink = sink !== undefined ? sink : typeof AudioContext !== \"undefined\" ? () => new WebAudioSink() : null;\n this.storage = o.rememberSeat === false ? null : a.storage !== undefined ? a.storage : browserStorage();\n }\n\n // ---- remembered seat -------------------------------------------------------------------------------------------\n\n private get seatKey(): string {\n return `onelobby.seat.${this.options.gameId}`;\n }\n\n private loadSeat(): Seat | null {\n if (!this.storage) return null;\n try {\n const raw = this.storage.getItem(this.seatKey);\n if (!raw) return null;\n const s = JSON.parse(raw) as Partial<Seat>;\n if (typeof s.code !== \"string\" || typeof s.token !== \"string\" || s.server !== this.options.lobbyServer) return null;\n return s as Seat;\n } catch {\n return null;\n }\n }\n\n private saveSeat(code: string, token: string): void {\n if (!this.storage || !token) return;\n try {\n this.storage.setItem(this.seatKey, JSON.stringify({ code, token, server: this.options.lobbyServer } satisfies Seat));\n } catch {\n /* quota or blocked storage: the seat just is not remembered */\n }\n }\n\n private clearSeat(code?: string): void {\n if (!this.storage) return;\n try {\n if (code !== undefined) {\n const s = this.loadSeat();\n if (s && s.code !== code) return;\n }\n this.storage.removeItem(this.seatKey);\n } catch {\n /* ignore */\n }\n }\n\n /** The lobby code of the seat this tab can take back (rememberSeat), or null. */\n get rememberedCode(): string | null {\n return this.loadSeat()?.code ?? null;\n }\n\n /**\n * Take back the seat remembered from before a page reload (host or joiner). Resolves null when there is none\n * or the server no longer holds it (the seat is forgotten then).\n */\n async resume(): Promise<Lobby | null> {\n const seat = this.loadSeat();\n if (!seat || seatInUse(seat.token)) return null;\n try {\n return await this.rejoin(seat.token);\n } catch (e) {\n if (e instanceof OneLobbyError && e.code === \"invalid_token\") {\n this.clearSeat();\n return null;\n }\n throw e;\n }\n }\n\n // ---- connecting ------------------------------------------------------------------------------------------------\n\n private helloMsg(resumeToken?: string): Record<string, unknown> {\n return {\n t: \"hello\",\n proto: PROTO_ID,\n gameId: this.options.gameId,\n netVersion: this.options.netVersion,\n client: { lib: LIB_NAME, ver: LIB_VERSION, platform: typeof navigator !== \"undefined\" ? \"browser\" : \"node\" },\n name: this.options.displayName.slice(0, 24),\n ...(resumeToken ? { resumeToken } : {}),\n };\n }\n\n private async connect(url = this.options.lobbyServer, factory = this.socketFactory, timeoutMs = 0): Promise<{ sig: SignalingClient; welcome: WelcomeMsg }> {\n const sig = new SignalingClient(url, factory, this.clock);\n if (timeoutMs > 0) {\n let cancel = () => {};\n const timeout = new Promise<never>((_, reject) => {\n cancel = this.clock.setTimeout(() => reject(new OneLobbyError(\"direct_unreachable\", `no answer from ${url} within ${timeoutMs} ms`)), timeoutMs);\n });\n try {\n await Promise.race([sig.connect(), timeout]);\n } catch (e) {\n sig.close();\n if (e instanceof OneLobbyError && e.code === \"connect_failed\") throw new OneLobbyError(\"direct_unreachable\", `no answer from ${url}`);\n throw e;\n } finally {\n cancel();\n }\n } else await sig.connect();\n try {\n const welcome = await sig.request<WelcomeMsg & { t: string }>(this.helloMsg(), \"welcome\");\n return { sig, welcome };\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n private makeLobby(sig: SignalingClient, welcome: WelcomeMsg, state: LobbyState, direct?: { links: LinkFactory }, rateLimit?: RateLimit): Lobby {\n const lobby = new Lobby({\n signaling: sig,\n state,\n ice: welcome.ice ?? [],\n links: direct?.links ?? this.linkFactory,\n direct: !!direct,\n clock: this.clock,\n wall: this.wall,\n hashEveryTicks: this.options.hashEveryTicks,\n relayOnly: state.relayOnly ?? this.options.relayOnly,\n displayName: this.options.displayName,\n voice: { codec: this.voiceCodec, createSource: this.createMic, createSink: this.createSink },\n readyTimeoutMs: this.options.readyTimeoutMs,\n peerTimeoutMs: this.options.peerTimeoutMs,\n rateLimit,\n });\n lobby.errorSink = (e) => this.emit(\"error\", e);\n if (this.storage && !direct) {\n liveSeats.add(lobby);\n this.saveSeat(lobby.code, lobby.resumeToken);\n lobby.seatSink = (token) => this.saveSeat(lobby.code, token);\n lobby.on(\"sessionEnded\", () => {\n liveSeats.delete(lobby);\n this.clearSeat(lobby.code);\n });\n }\n return lobby;\n }\n\n async host(h: HostOptions): Promise<Lobby> {\n const { sig, welcome } = await this.connect();\n try {\n const state = await sig.request<LobbyState>(\n {\n t: \"create\",\n name: h.name,\n maxPlayers: h.maxPlayers,\n visibility: h.visibility ?? \"unlisted\",\n mode: this.options.mode,\n tickRate: this.options.tickRate,\n lateJoin: h.lateJoin ?? true,\n meta: h.meta ?? {},\n ...(this.options.relayOnly ? { relayOnly: true } : {}),\n },\n \"lobby\",\n );\n return this.makeLobby(sig, welcome, state, undefined, h.rateLimit);\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n async join(code: string): Promise<Lobby> {\n const norm = normalizeCode(code);\n if (!norm) throw new OneLobbyError(\"bad_code\", `\"${code}\" is not a valid join code`);\n const seat = this.loadSeat();\n if (seat && seat.code === norm && !seatInUse(seat.token)) {\n // Same tab, same lobby (a page reload): take the old seat back instead of joining as a new player.\n try {\n return await this.rejoin(seat.token);\n } catch (e) {\n this.clearSeat();\n if (!(e instanceof OneLobbyError && e.code === \"invalid_token\")) throw e;\n }\n }\n const { sig, welcome } = await this.connect();\n try {\n const state = await sig.request<LobbyState>({ t: \"join\", code: norm }, \"lobby\");\n return this.makeLobby(sig, welcome, state);\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n /**\n * Join a direct-IP host (docs/direct-ip.md): a join code from the host's screen (`6B01-RGFE-P2B`, any case, spaces\n * or hyphens) or, advanced, an address (`ip`, `ip:port`, `[v6]:port`). No lobby server is involved: the code is\n * the host's address. A mistyped code fails at once with `bad_code`; nobody listening is `direct_unreachable`.\n * The address is also given to ICE as the host's UDP candidate. When the host leaves, `sessionEnded(\"host_left\")`.\n */\n async joinDirect(codeOrAddress: string, opts: JoinDirectOptions = {}): Promise<Lobby> {\n let input = codeOrAddress.trim();\n if (looksLikeJoinCode(input)) {\n const d = decodeJoinCode(input);\n if (\"error\" in d)\n throw new OneLobbyError(d.error, d.error === \"unknown_version\" ? `\"${input}\" is a join code from a newer version of the game` : `\"${input}\" is not a valid join code: check for a typo`);\n input = `${d.ip}:${d.port}`;\n }\n const addr = parseDirectAddress(input, opts.defaultPort ?? DEFAULT_DIRECT_PORT);\n if (!addr) throw new OneLobbyError(\"bad_address\", `\"${codeOrAddress}\" is not a join code or an address (try 6B01-RGFE-P2B or 192.168.1.20:47810)`);\n const inner = this.linkFactory;\n const links: LinkFactory = { create: (o) => inner.create({ ...o, iceServers: [], extraRemoteCandidates: [addr.candidate] }) };\n const { sig, welcome } = await this.connect(addr.url, this.socketFactory, opts.connectTimeoutMs ?? 5000);\n try {\n const state = await sig.request<LobbyState>({ t: \"join\", ...(opts.password ? { password: opts.password } : {}) }, \"lobby\");\n const lobby = this.makeLobby(sig, welcome, state, { links });\n // a joiner's joinCode is what it joined with (normalized), so a HUD can show it on both sides\n lobby.joinCode = joinedWith(addr);\n return lobby;\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n /** @internal onelobby/direct's host: create the one lobby on the embedded hub, in direct mode. */\n async hostDirectWith(h: HostOptions, hooks: DirectLobbyHooks): Promise<Lobby> {\n const { sig, welcome } = await this.connect(\"onelobby-direct://host/\", hooks.socketFactory);\n try {\n const state = await sig.request<LobbyState>(\n { t: \"create\", name: h.name, maxPlayers: h.maxPlayers, visibility: \"unlisted\", mode: this.options.mode, tickRate: this.options.tickRate,\n lateJoin: h.lateJoin ?? true, meta: h.meta ?? {} },\n \"lobby\",\n );\n return this.makeLobby(sig, welcome, state, { links: hooks.wrapLinks(this.linkFactory) }, h.rateLimit);\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n async rejoin(resumeToken: string): Promise<Lobby> {\n const sig = new SignalingClient(this.options.lobbyServer, this.socketFactory, this.clock);\n await sig.connect();\n try {\n // The server answers hello(resumeToken) with welcome then the full lobby state; listen for both up front.\n const lobbyP = sig.expect<LobbyState>(\"lobby\");\n const welcomeP = sig.request<WelcomeMsg & { t: string }>(this.helloMsg(resumeToken), \"welcome\");\n lobbyP.catch(() => {}); // settled below or by close()\n const welcome = await welcomeP;\n // spec: an unknown or expired token is ignored -> plain welcome (no peerId), and no lobby follows\n if (welcome.peerId === undefined) throw new OneLobbyError(\"invalid_token\", \"resume token unknown or expired\");\n const state = await lobbyP;\n return this.makeLobby(sig, welcome, state);\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n async list(opts: ListOptions = {}): Promise<LobbySummary[]> {\n const { sig } = await this.connect();\n try {\n const res = await sig.request<{ t: string; items: LobbySummary[] }>(\n { t: \"list\", visibility: \"public\", meta: opts.meta ?? {}, limit: Math.min(100, opts.limit ?? 50), ...(opts.cursor ? { cursor: opts.cursor } : {}) },\n \"lobbies\",\n );\n return res.items ?? [];\n } finally {\n sig.close();\n }\n }\n}\n\n/** The join code for an IPv4 address (a host name or IPv6 has none: its `host:port` instead). */\nfunction joinedWith(addr: { host: string; port: number; isIPv6: boolean; display: string }): string {\n if (addr.isIPv6) return addr.display;\n try {\n return encodeJoinCode(addr.host, addr.port);\n } catch {\n return addr.display;\n }\n}\n"]}
package/dist/socket.d.ts CHANGED
@@ -14,9 +14,14 @@ export interface LobbySocket {
14
14
  send(data: string | Uint8Array): void;
15
15
  /**
16
16
  * Closes this socket locally and fires `onclose` with `code` (default 1000). On a joiner it also leaves the lobby.
17
- * On the host it only stops this connection's events: to remove the player too, call `lobby.kick(conn.peerId)`.
17
+ * On the host it only stops this connection's events: to end the player's connection too (its socket gets your code
18
+ * and reason), call `lobby.disconnect(conn.peerId, code, reason)`; `lobby.kick()` also bans it from the lobby.
18
19
  */
19
20
  close(code?: number, reason?: string): void;
21
+ /**
22
+ * Fires once, on a later task than the one that created the socket (like a real WebSocket). Assigned after it already
23
+ * fired (the socket came out of an `await` chain)? It runs for the new handler once; `readyState === 1` also tells.
24
+ */
20
25
  onopen: ((ev: {
21
26
  type: "open";
22
27
  }) => void) | null;
@@ -25,7 +30,7 @@ export interface LobbySocket {
25
30
  type: "message";
26
31
  data: string | Uint8Array;
27
32
  }) => void) | null;
28
- /** `reason` is the lobby's reason ("host_left", "timeout", "kicked", ...) unless `close()` gave one. */
33
+ /** `reason` is the lobby's reason ("host_left", "timeout", "kicked", "disconnected", ...) unless `close()` or the host gave one. */
29
34
  onclose: ((ev: {
30
35
  type: "close";
31
36
  code: number;
@@ -44,13 +49,14 @@ export interface LobbySocketOptions {
44
49
  channel?: "reliable" | "fast";
45
50
  }
46
51
  /**
47
- * Close codes: 1000 a normal leave or `close()`, 1001 the other end went away (`host_left`, the host closing, a
48
- * player who left), 4000 kicked, 1006 anything abnormal (`timeout`, `ice_failed`, `server_lost`, ...).
52
+ * Close codes: 1000 a normal leave, `close()` or the host's `disconnect()`, 1001 the other end went away (`host_left`,
53
+ * the host closing, a player who left), 4000 kicked, 1006 anything abnormal (`timeout`, `ice_failed`, `server_lost`,
54
+ * ...). A code and reason the host passed to `kick()` / `disconnect()` win over these.
49
55
  */
50
56
  export declare function closeCodeFor(reason: string): number;
51
57
  /**
52
58
  * Joiner: the link to the host as a socket. OPEN once `lobby.ready` resolves (at once if it already has), with
53
- * `onopen` one microtask later;
59
+ * `onopen` on a later task;
54
60
  * closed when the session ends, or with 1001 "host_changed" when a server-backed lobby migrates to a new host.
55
61
  */
56
62
  export declare function joinerSocket(lobby: Lobby, o?: LobbySocketOptions): LobbySocket;
package/dist/socket.js CHANGED
@@ -1,9 +1,10 @@
1
1
  /**
2
- * Close codes: 1000 a normal leave or `close()`, 1001 the other end went away (`host_left`, the host closing, a
3
- * player who left), 4000 kicked, 1006 anything abnormal (`timeout`, `ice_failed`, `server_lost`, ...).
2
+ * Close codes: 1000 a normal leave, `close()` or the host's `disconnect()`, 1001 the other end went away (`host_left`,
3
+ * the host closing, a player who left), 4000 kicked, 1006 anything abnormal (`timeout`, `ice_failed`, `server_lost`,
4
+ * ...). A code and reason the host passed to `kick()` / `disconnect()` win over these.
4
5
  */
5
6
  export function closeCodeFor(reason) {
6
- if (reason === "left")
7
+ if (reason === "left" || reason === "disconnected")
7
8
  return 1000;
8
9
  if (reason === "host_left" || reason === "closed")
9
10
  return 1001;
@@ -11,6 +12,8 @@ export function closeCodeFor(reason) {
11
12
  return 4000;
12
13
  return 1006;
13
14
  }
15
+ /** A later task, like a real WebSocket's events: setImmediate in Node, setTimeout(0) in browsers. */
16
+ const nextTask = globalThis.setImmediate ?? ((f) => void setTimeout(f, 0));
14
17
  const enc = new TextEncoder();
15
18
  const dec = new TextDecoder();
16
19
  class PeerSocket {
@@ -20,7 +23,9 @@ class PeerSocket {
20
23
  onLocalClose;
21
24
  readyState = 0;
22
25
  bufferedAmount = 0;
23
- onopen = null;
26
+ openHandler = null;
27
+ /** OPEN, but `onopen` has not run yet for any handler. */
28
+ openPending = false;
24
29
  onmessage = null;
25
30
  onclose = null;
26
31
  onerror = null;
@@ -43,15 +48,34 @@ class PeerSocket {
43
48
  this.closed(code, reason);
44
49
  this.onLocalClose();
45
50
  }
46
- /** @internal OPEN now; `onopen` now, or one microtask later so a handler assigned right after creation still runs. */
51
+ get onopen() {
52
+ return this.openHandler;
53
+ }
54
+ set onopen(h) {
55
+ this.openHandler = h;
56
+ // assigned after the open event went by with no handler: deliver it once, still on a later task
57
+ if (h && this.openPending)
58
+ nextTask(() => this.fireOpen());
59
+ }
60
+ /**
61
+ * @internal OPEN now; `onopen` now, or on a later task (like a real WebSocket) so a handler assigned
62
+ * after any number of awaits in the same task still runs.
63
+ */
47
64
  open(later = false) {
48
65
  if (this.readyState !== 0)
49
66
  return;
50
67
  this.readyState = 1;
68
+ this.openPending = true;
51
69
  if (!later)
52
- this.onopen?.({ type: "open" });
70
+ this.fireOpen();
53
71
  else
54
- queueMicrotask(() => this.readyState === 1 && this.onopen?.({ type: "open" }));
72
+ nextTask(() => this.fireOpen());
73
+ }
74
+ fireOpen() {
75
+ if (this.readyState !== 1 || !this.openPending || !this.openHandler)
76
+ return;
77
+ this.openPending = false;
78
+ this.openHandler({ type: "open" });
55
79
  }
56
80
  /** @internal */
57
81
  deliver(bytes) {
@@ -69,7 +93,7 @@ class PeerSocket {
69
93
  }
70
94
  /**
71
95
  * Joiner: the link to the host as a socket. OPEN once `lobby.ready` resolves (at once if it already has), with
72
- * `onopen` one microtask later;
96
+ * `onopen` on a later task;
73
97
  * closed when the session ends, or with 1001 "host_changed" when a server-backed lobby migrates to a new host.
74
98
  */
75
99
  export function joinerSocket(lobby, o = {}) {
@@ -82,7 +106,7 @@ export function joinerSocket(lobby, o = {}) {
82
106
  if (from === lobby.hostPeerId)
83
107
  s.deliver(bytes);
84
108
  }),
85
- lobby.on("sessionEnded", (reason) => s.closed(closeCodeFor(reason), reason)),
109
+ lobby.on("sessionEnded", (reason, close) => s.closed(close?.code ?? closeCodeFor(reason), close?.reason ?? reason)),
86
110
  // The socket's far end is the host it opened to; after a migration that server is gone (direct lobbies end instead).
87
111
  lobby.on("hostChanged", () => s.closed(1001, "host_changed")),
88
112
  ];
@@ -126,7 +150,7 @@ export function hostConnections(lobby, handler, o = {}) {
126
150
  const offs = [
127
151
  lobby.on("peerReady", (p) => void ensure(p.peerId)),
128
152
  lobby.on("message", (from, bytes) => ensure(from)?.deliver(bytes)),
129
- lobby.on("peerLeft", (p, reason) => conns.get(p.peerId)?.closed(reason === "left" ? 1001 : closeCodeFor(reason), reason)),
153
+ lobby.on("peerLeft", (p, reason, close) => conns.get(p.peerId)?.closed(close?.code ?? (reason === "left" ? 1001 : closeCodeFor(reason)), close?.reason ?? reason)),
130
154
  lobby.on("sessionEnded", (reason) => {
131
155
  for (const c of [...conns.values()])
132
156
  c.closed(reason === "left" ? 1001 : closeCodeFor(reason), reason);
@@ -1 +1 @@
1
- {"version":3,"file":"socket.js","sourceRoot":"","sources":["../src/socket.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Lobby, Peer } from \"./lobby.js\";\n\n/**\n * A WebSocket-shaped view of one peer link, so code written against `WebSocket` runs on a lobby unchanged:\n * `lobby.asWebSocket()` on a joiner (the link to the host), `lobby.onConnection(conn => ...)` on the host (one per\n * joiner). Not a real WebSocket: no URL, no subprotocols, no `addEventListener`; `bufferedAmount` is always 0.\n */\nexport interface LobbySocket {\n /** The peer at the other end (the host for a joiner's socket, the joiner for a host's connection). */\n readonly peerId: number;\n /** 0 CONNECTING (joiner, before `lobby.ready`), 1 OPEN, 3 CLOSED (there is no CLOSING). */\n readonly readyState: 0 | 1 | 3;\n readonly bufferedAmount: 0;\n /** A string is sent as UTF-8; bytes as they are. Ignored unless OPEN. */\n send(data: string | Uint8Array): void;\n /**\n * Closes this socket locally and fires `onclose` with `code` (default 1000). On a joiner it also leaves the lobby.\n * On the host it only stops this connection's events: to remove the player too, call `lobby.kick(conn.peerId)`.\n */\n close(code?: number, reason?: string): void;\n onopen: ((ev: { type: \"open\" }) => void) | null;\n /** `data` is a string (UTF-8 decoded), or the bytes with `{ binary: true }`. */\n onmessage: ((ev: { type: \"message\"; data: string | Uint8Array }) => void) | null;\n /** `reason` is the lobby's reason (\"host_left\", \"timeout\", \"kicked\", ...) unless `close()` gave one. */\n onclose: ((ev: { type: \"close\"; code: number; reason: string; wasClean: boolean }) => void) | null;\n /** Never called (errors surface as `onclose` with code 1006); present so WebSocket code that assigns it works. */\n onerror: ((ev: { type: \"error\" }) => void) | null;\n}\n\nexport interface LobbySocketOptions {\n /** Deliver `onmessage` data as bytes (Uint8Array) instead of UTF-8 text. Default false. */\n binary?: boolean;\n /** Channel for `send` (default \"reliable\"). \"fast\" is unordered and may drop, like UDP. */\n channel?: \"reliable\" | \"fast\";\n}\n\n/**\n * Close codes: 1000 a normal leave or `close()`, 1001 the other end went away (`host_left`, the host closing, a\n * player who left), 4000 kicked, 1006 anything abnormal (`timeout`, `ice_failed`, `server_lost`, ...).\n */\nexport function closeCodeFor(reason: string): number {\n if (reason === \"left\") return 1000;\n if (reason === \"host_left\" || reason === \"closed\") return 1001;\n if (reason === \"kicked\") return 4000;\n return 1006;\n}\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\nclass PeerSocket implements LobbySocket {\n readyState: 0 | 1 | 3 = 0;\n readonly bufferedAmount = 0 as const;\n onopen: LobbySocket[\"onopen\"] = null;\n onmessage: LobbySocket[\"onmessage\"] = null;\n onclose: LobbySocket[\"onclose\"] = null;\n onerror: LobbySocket[\"onerror\"] = null;\n /** Unsubscribes from the lobby; set by the factory. */\n detach: () => void = () => {};\n\n constructor(\n readonly peerId: number,\n private readonly out: (bytes: Uint8Array) => void,\n private readonly binary: boolean,\n private readonly onLocalClose: () => void,\n ) {}\n\n send(data: string | Uint8Array): void {\n if (this.readyState !== 1) return;\n this.out(typeof data === \"string\" ? enc.encode(data) : data);\n }\n\n close(code = 1000, reason = \"left\"): void {\n if (this.readyState === 3) return;\n this.closed(code, reason);\n this.onLocalClose();\n }\n\n /** @internal OPEN now; `onopen` now, or one microtask later so a handler assigned right after creation still runs. */\n open(later = false): void {\n if (this.readyState !== 0) return;\n this.readyState = 1;\n if (!later) this.onopen?.({ type: \"open\" });\n else queueMicrotask(() => this.readyState === 1 && this.onopen?.({ type: \"open\" }));\n }\n\n /** @internal */\n deliver(bytes: Uint8Array): void {\n if (this.readyState === 1) this.onmessage?.({ type: \"message\", data: this.binary ? bytes : dec.decode(bytes) });\n }\n\n /** @internal */\n closed(code: number, reason: string): void {\n if (this.readyState === 3) return;\n this.readyState = 3;\n this.detach();\n this.onclose?.({ type: \"close\", code, reason, wasClean: code === 1000 || code === 1001 });\n }\n}\n\n/**\n * Joiner: the link to the host as a socket. OPEN once `lobby.ready` resolves (at once if it already has), with\n * `onopen` one microtask later;\n * closed when the session ends, or with 1001 \"host_changed\" when a server-backed lobby migrates to a new host.\n */\nexport function joinerSocket(lobby: Lobby, o: LobbySocketOptions = {}): LobbySocket {\n if (lobby.isHost) throw new Error(\"asWebSocket() is for joiners; the host uses lobby.onConnection()\");\n const channel = o.channel ?? \"reliable\";\n const s = new PeerSocket(lobby.hostPeerId, (b) => lobby.send(lobby.hostPeerId, b, { channel }), !!o.binary, () => void lobby.leave());\n const offs = [\n lobby.on(\"message\", (from, bytes) => {\n if (from === lobby.hostPeerId) s.deliver(bytes);\n }),\n lobby.on(\"sessionEnded\", (reason) => s.closed(closeCodeFor(reason), reason)),\n // The socket's far end is the host it opened to; after a migration that server is gone (direct lobbies end instead).\n lobby.on(\"hostChanged\", () => s.closed(1001, \"host_changed\")),\n ];\n s.detach = () => offs.forEach((f) => f());\n // Already connected (made after `await lobby.ready`): OPEN at once, so a send() right away goes out.\n if (lobby.connectedPeers.includes(lobby.hostPeerId)) s.open(true);\n else lobby.ready.then(() => s.open(true), () => {});\n return s;\n}\n\n/**\n * Host: calls `handler(conn)` once per joiner, as soon as its link is ready (and right away for joiners already\n * connected). A message from a joiner the handler has not seen yet creates its connection first, so nothing is lost.\n * `conn.onclose` fires when that peer leaves (code from closeCodeFor) or the host's session ends. Returns a function\n * that stops new connections (existing ones keep working).\n */\nexport function hostConnections(lobby: Lobby, handler: (conn: LobbySocket) => void, o: LobbySocketOptions = {}): () => void {\n if (!lobby.isHost) throw new Error(\"onConnection() is for the host; a joiner uses lobby.asWebSocket()\");\n const channel = o.channel ?? \"reliable\";\n const conns = new Map<number, PeerSocket>();\n let stopped = false;\n const ensure = (peerId: number): PeerSocket | undefined => {\n if (peerId === lobby.localPeerId) return undefined;\n let c = conns.get(peerId);\n if (c || stopped) return c;\n c = new PeerSocket(peerId, (b) => lobby.send(peerId, b, { channel }), !!o.binary, () => {});\n const conn = c;\n conn.detach = () => {\n if (conns.get(peerId) === conn) conns.delete(peerId);\n };\n conns.set(peerId, conn);\n conn.open();\n handler(conn);\n return conn;\n };\n const offs = [\n lobby.on(\"peerReady\", (p: Peer) => void ensure(p.peerId)),\n lobby.on(\"message\", (from, bytes) => ensure(from)?.deliver(bytes)),\n lobby.on(\"peerLeft\", (p: Peer, reason) => conns.get(p.peerId)?.closed(reason === \"left\" ? 1001 : closeCodeFor(reason), reason)),\n lobby.on(\"sessionEnded\", (reason) => {\n for (const c of [...conns.values()]) c.closed(reason === \"left\" ? 1001 : closeCodeFor(reason), reason);\n offs.forEach((f) => f());\n }),\n ];\n for (const id of lobby.connectedPeers) ensure(id);\n return () => {\n stopped = true;\n };\n}\n"]}
1
+ {"version":3,"file":"socket.js","sourceRoot":"","sources":["../src/socket.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CloseInfo, Lobby, Peer } from \"./lobby.js\";\n\n/**\n * A WebSocket-shaped view of one peer link, so code written against `WebSocket` runs on a lobby unchanged:\n * `lobby.asWebSocket()` on a joiner (the link to the host), `lobby.onConnection(conn => ...)` on the host (one per\n * joiner). Not a real WebSocket: no URL, no subprotocols, no `addEventListener`; `bufferedAmount` is always 0.\n */\nexport interface LobbySocket {\n /** The peer at the other end (the host for a joiner's socket, the joiner for a host's connection). */\n readonly peerId: number;\n /** 0 CONNECTING (joiner, before `lobby.ready`), 1 OPEN, 3 CLOSED (there is no CLOSING). */\n readonly readyState: 0 | 1 | 3;\n readonly bufferedAmount: 0;\n /** A string is sent as UTF-8; bytes as they are. Ignored unless OPEN. */\n send(data: string | Uint8Array): void;\n /**\n * Closes this socket locally and fires `onclose` with `code` (default 1000). On a joiner it also leaves the lobby.\n * On the host it only stops this connection's events: to end the player's connection too (its socket gets your code\n * and reason), call `lobby.disconnect(conn.peerId, code, reason)`; `lobby.kick()` also bans it from the lobby.\n */\n close(code?: number, reason?: string): void;\n /**\n * Fires once, on a later task than the one that created the socket (like a real WebSocket). Assigned after it already\n * fired (the socket came out of an `await` chain)? It runs for the new handler once; `readyState === 1` also tells.\n */\n onopen: ((ev: { type: \"open\" }) => void) | null;\n /** `data` is a string (UTF-8 decoded), or the bytes with `{ binary: true }`. */\n onmessage: ((ev: { type: \"message\"; data: string | Uint8Array }) => void) | null;\n /** `reason` is the lobby's reason (\"host_left\", \"timeout\", \"kicked\", \"disconnected\", ...) unless `close()` or the host gave one. */\n onclose: ((ev: { type: \"close\"; code: number; reason: string; wasClean: boolean }) => void) | null;\n /** Never called (errors surface as `onclose` with code 1006); present so WebSocket code that assigns it works. */\n onerror: ((ev: { type: \"error\" }) => void) | null;\n}\n\nexport interface LobbySocketOptions {\n /** Deliver `onmessage` data as bytes (Uint8Array) instead of UTF-8 text. Default false. */\n binary?: boolean;\n /** Channel for `send` (default \"reliable\"). \"fast\" is unordered and may drop, like UDP. */\n channel?: \"reliable\" | \"fast\";\n}\n\n/**\n * Close codes: 1000 a normal leave, `close()` or the host's `disconnect()`, 1001 the other end went away (`host_left`,\n * the host closing, a player who left), 4000 kicked, 1006 anything abnormal (`timeout`, `ice_failed`, `server_lost`,\n * ...). A code and reason the host passed to `kick()` / `disconnect()` win over these.\n */\nexport function closeCodeFor(reason: string): number {\n if (reason === \"left\" || reason === \"disconnected\") return 1000;\n if (reason === \"host_left\" || reason === \"closed\") return 1001;\n if (reason === \"kicked\") return 4000;\n return 1006;\n}\n\n/** A later task, like a real WebSocket's events: setImmediate in Node, setTimeout(0) in browsers. */\nconst nextTask: (f: () => void) => void =\n (globalThis as { setImmediate?: (f: () => void) => void }).setImmediate ?? ((f) => void setTimeout(f, 0));\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\nclass PeerSocket implements LobbySocket {\n readyState: 0 | 1 | 3 = 0;\n readonly bufferedAmount = 0 as const;\n private openHandler: LobbySocket[\"onopen\"] = null;\n /** OPEN, but `onopen` has not run yet for any handler. */\n private openPending = false;\n onmessage: LobbySocket[\"onmessage\"] = null;\n onclose: LobbySocket[\"onclose\"] = null;\n onerror: LobbySocket[\"onerror\"] = null;\n /** Unsubscribes from the lobby; set by the factory. */\n detach: () => void = () => {};\n\n constructor(\n readonly peerId: number,\n private readonly out: (bytes: Uint8Array) => void,\n private readonly binary: boolean,\n private readonly onLocalClose: () => void,\n ) {}\n\n send(data: string | Uint8Array): void {\n if (this.readyState !== 1) return;\n this.out(typeof data === \"string\" ? enc.encode(data) : data);\n }\n\n close(code = 1000, reason = \"left\"): void {\n if (this.readyState === 3) return;\n this.closed(code, reason);\n this.onLocalClose();\n }\n\n get onopen(): LobbySocket[\"onopen\"] {\n return this.openHandler;\n }\n\n set onopen(h: LobbySocket[\"onopen\"]) {\n this.openHandler = h;\n // assigned after the open event went by with no handler: deliver it once, still on a later task\n if (h && this.openPending) nextTask(() => this.fireOpen());\n }\n\n /**\n * @internal OPEN now; `onopen` now, or on a later task (like a real WebSocket) so a handler assigned\n * after any number of awaits in the same task still runs.\n */\n open(later = false): void {\n if (this.readyState !== 0) return;\n this.readyState = 1;\n this.openPending = true;\n if (!later) this.fireOpen();\n else nextTask(() => this.fireOpen());\n }\n\n private fireOpen(): void {\n if (this.readyState !== 1 || !this.openPending || !this.openHandler) return;\n this.openPending = false;\n this.openHandler({ type: \"open\" });\n }\n\n /** @internal */\n deliver(bytes: Uint8Array): void {\n if (this.readyState === 1) this.onmessage?.({ type: \"message\", data: this.binary ? bytes : dec.decode(bytes) });\n }\n\n /** @internal */\n closed(code: number, reason: string): void {\n if (this.readyState === 3) return;\n this.readyState = 3;\n this.detach();\n this.onclose?.({ type: \"close\", code, reason, wasClean: code === 1000 || code === 1001 });\n }\n}\n\n/**\n * Joiner: the link to the host as a socket. OPEN once `lobby.ready` resolves (at once if it already has), with\n * `onopen` on a later task;\n * closed when the session ends, or with 1001 \"host_changed\" when a server-backed lobby migrates to a new host.\n */\nexport function joinerSocket(lobby: Lobby, o: LobbySocketOptions = {}): LobbySocket {\n if (lobby.isHost) throw new Error(\"asWebSocket() is for joiners; the host uses lobby.onConnection()\");\n const channel = o.channel ?? \"reliable\";\n const s = new PeerSocket(lobby.hostPeerId, (b) => lobby.send(lobby.hostPeerId, b, { channel }), !!o.binary, () => void lobby.leave());\n const offs = [\n lobby.on(\"message\", (from, bytes) => {\n if (from === lobby.hostPeerId) s.deliver(bytes);\n }),\n lobby.on(\"sessionEnded\", (reason, close?: CloseInfo) => s.closed(close?.code ?? closeCodeFor(reason), close?.reason ?? reason)),\n // The socket's far end is the host it opened to; after a migration that server is gone (direct lobbies end instead).\n lobby.on(\"hostChanged\", () => s.closed(1001, \"host_changed\")),\n ];\n s.detach = () => offs.forEach((f) => f());\n // Already connected (made after `await lobby.ready`): OPEN at once, so a send() right away goes out.\n if (lobby.connectedPeers.includes(lobby.hostPeerId)) s.open(true);\n else lobby.ready.then(() => s.open(true), () => {});\n return s;\n}\n\n/**\n * Host: calls `handler(conn)` once per joiner, as soon as its link is ready (and right away for joiners already\n * connected). A message from a joiner the handler has not seen yet creates its connection first, so nothing is lost.\n * `conn.onclose` fires when that peer leaves (code from closeCodeFor) or the host's session ends. Returns a function\n * that stops new connections (existing ones keep working).\n */\nexport function hostConnections(lobby: Lobby, handler: (conn: LobbySocket) => void, o: LobbySocketOptions = {}): () => void {\n if (!lobby.isHost) throw new Error(\"onConnection() is for the host; a joiner uses lobby.asWebSocket()\");\n const channel = o.channel ?? \"reliable\";\n const conns = new Map<number, PeerSocket>();\n let stopped = false;\n const ensure = (peerId: number): PeerSocket | undefined => {\n if (peerId === lobby.localPeerId) return undefined;\n let c = conns.get(peerId);\n if (c || stopped) return c;\n c = new PeerSocket(peerId, (b) => lobby.send(peerId, b, { channel }), !!o.binary, () => {});\n const conn = c;\n conn.detach = () => {\n if (conns.get(peerId) === conn) conns.delete(peerId);\n };\n conns.set(peerId, conn);\n conn.open();\n handler(conn);\n return conn;\n };\n const offs = [\n lobby.on(\"peerReady\", (p: Peer) => void ensure(p.peerId)),\n lobby.on(\"message\", (from, bytes) => ensure(from)?.deliver(bytes)),\n lobby.on(\"peerLeft\", (p: Peer, reason, close?: CloseInfo) =>\n conns.get(p.peerId)?.closed(close?.code ?? (reason === \"left\" ? 1001 : closeCodeFor(reason)), close?.reason ?? reason),\n ),\n lobby.on(\"sessionEnded\", (reason) => {\n for (const c of [...conns.values()]) c.closed(reason === \"left\" ? 1001 : closeCodeFor(reason), reason);\n offs.forEach((f) => f());\n }),\n ];\n for (const id of lobby.connectedPeers) ensure(id);\n return () => {\n stopped = true;\n };\n}\n"]}
package/dist/testing.js CHANGED
@@ -409,10 +409,20 @@ export class MemorySocket {
409
409
  const victim = l.peers.get(Number(m.peerId));
410
410
  if (!victim || victim === this.peer)
411
411
  return this.err("bad_request");
412
- l.kicked.add(victim.peerId);
412
+ // ban:false disconnects without the ban; closeCode / closeReason go to the removed peer (spec/signaling.md)
413
+ const ban = m.ban !== false;
414
+ const data = {};
415
+ if (!ban)
416
+ data.ban = false;
417
+ if (typeof m.closeCode === "number")
418
+ data.closeCode = m.closeCode;
419
+ if (typeof m.closeReason === "string")
420
+ data.closeReason = m.closeReason;
421
+ if (ban)
422
+ l.kicked.add(victim.peerId);
413
423
  l.peers.delete(victim.peerId);
414
- victim.sock?.push({ t: "error", code: "kicked", msg: "kicked" });
415
- this.broadcast(l, { t: "peer_left", peerId: victim.peerId, reason: "kicked" });
424
+ victim.sock?.push({ t: "error", code: "kicked", msg: "kicked", ...(Object.keys(data).length ? { data } : {}) });
425
+ this.broadcast(l, { t: "peer_left", peerId: victim.peerId, reason: ban ? "kicked" : "disconnected" });
416
426
  return;
417
427
  }
418
428
  case "host_changed": {
@@ -1 +1 @@
1
- {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAqBnE,uGAAuG;AACvG,MAAM,OAAO,WAAW;IACd,CAAC,GAAG,CAAC,CAAC;IACN,GAAG,GAAG,CAAC,CAAC;IACR,MAAM,GAAY,EAAE,CAAC;IAE7B,GAAG;QACD,OAAO,IAAI,CAAC,CAAC,CAAC;IAChB,CAAC;IAED,WAAW,CAAC,EAAc,EAAE,EAAU;QACpC,MAAM,KAAK,GAAU,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;QACtF,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,OAAO,GAAG,EAAE;YACV,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;QACpB,CAAC,CAAC;IACJ,CAAC;IAED,UAAU,CAAC,EAAc,EAAE,EAAU;QACnC,MAAM,KAAK,GAAU,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC;QAC9D,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,OAAO,GAAG,EAAE;YACV,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;QACpB,CAAC,CAAC;IACJ,CAAC;IAED,4GAA4G;IAC5G,OAAO,CAAC,EAAU;QAChB,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;QACxB,SAAS,CAAC;YACR,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACjD,IAAI,IAAuB,CAAC;YAC5B,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM;gBAAE,IAAI,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;oBAAE,IAAI,GAAG,CAAC,CAAC;YAC9H,IAAI,CAAC,IAAI;gBAAE,MAAM;YACjB,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;YACnC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC;gBACtB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACxB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;YACnB,CAAC;YACD,IAAI,CAAC,EAAE,EAAE,CAAC;QACZ,CAAC;QACD,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC;IACf,CAAC;IAED,iGAAiG;IACjG,KAAK,CAAC,GAAG,CAAC,EAAU,EAAE,IAAI,GAAG,EAAE;QAC7B,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,MAAM,MAAM,EAAE,CAAC;QACf,OAAO,IAAI,GAAG,CAAC,EAAE,CAAC;YAChB,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC/B,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAChB,IAAI,IAAI,CAAC,CAAC;YACV,MAAM,MAAM,EAAE,CAAC;QACjB,CAAC;IACH,CAAC;CACF;AAED,0EAA0E;AAC1E,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,MAAM,GAAG,CAAC;IACrC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE;QAAE,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;IACzD,2FAA2F;IAC3F,MAAM,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,CAAE,UAAyD,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AACrI,CAAC;AAED,sHAAsH;AAEtH,IAAI,UAAU,GAAG,CAAC,CAAC;AAEnB;;;GAGG;AACH,MAAM,OAAO,aAAa;IAChB,MAAM,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC/C,oEAAoE;IAC3D,KAAK,GAAiB,EAAE,CAAC;IAClC,+HAA+H;IACtH,OAAO,GAAgB,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;IAEjF,gBAAgB;IAChB,QAAQ,CAAC,KAAa,EAAE,IAAgB;QACtC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC7B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IACD,gBAAgB;IAChB,MAAM,CAAC,KAAa;QAClB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAED,4GAA4G;IAC5G,SAAS,CAAC,KAAa;QACrB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK;YAAE,IAAI,CAAC,CAAC,KAAK,KAAK,KAAK;gBAAE,CAAC,CAAC,KAAK,EAAE,CAAC;IAC/D,CAAC;IAED,oFAAoF;IACpF,UAAU,CAAC,KAAa;QACtB,OAAO;YACL,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBACf,MAAM,CAAC,GAAG,IAAI,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBACrC,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC;gBAChB,OAAO,CAAC,CAAC;YACX,CAAC;SACF,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,UAAU;IAiBD;IAhBX,QAAQ,CAAS;IAC1B,MAAM,GAAG,KAAK,CAAC;IACf,KAAK,GAAG,EAAE,CAAC;IACX,QAAQ,GAAyB,IAAI,CAAC;IACtC,MAAM,GAAuB,IAAI,CAAC;IAClC,OAAO,GAAwB,IAAI,CAAC;IACpC,SAAS,GAA0B,IAAI,CAAC;IACxC,MAAM,GAAuB,IAAI,CAAC;IAClC,8CAA8C;IACrC,IAAI,GAAgC,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IAChF,2EAA2E;IAC3E,SAAS,GAAG,KAAK,CAAC;IACV,IAAI,GAAsB,IAAI,CAAC;IAC/B,MAAM,GAAG,KAAK,CAAC;IACf,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,CAAC;IAEnC,YAAoB,GAAkB,EAAE,IAAiB;QAArC,QAAG,GAAH,GAAG,CAAe;QACpC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAChC,CAAC;IAED,UAAU;QACR,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACjC,cAAc,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IAClF,CAAC;IAED,YAAY,CAAC,CAAgB;QAC3B,IAAI,CAAC,CAAC,GAAG,EAAE,IAAI,KAAK,OAAO,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC3C,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC1B,IAAI,CAAC,OAAO;gBAAE,OAAO;YACrB,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC;YACpB,cAAc,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;YACjF,cAAc,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;YAC3C,cAAc,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,gBAAgB;IAChB,MAAM,CAAC,QAAoB;QACzB,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC;QACrB,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAEO,IAAI;QACV,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACvC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QAChB,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,CAAC,OAAoB,EAAE,KAAiB;QAC1C,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC;QACnC,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO;QAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACvB,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;QAC3B,cAAc,CAAC,GAAG,EAAE;YAClB,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM;gBAAE,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC3F,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACvB,cAAc,CAAC,GAAG,EAAE;YAClB,IAAI,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,CAAC;YACzB,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM;gBAAE,IAAI,CAAC,eAAe,EAAE,CAAC;QACnD,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,eAAe;QACrB,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,OAAO,EAAE,CAAC,eAAe,CAAC,CAAC;IAClC,CAAC;CACF;AAiCD,+GAA+G;AAC/G,MAAM,OAAO,YAAY;IACd,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC7C,0DAA0D;IACjD,OAAO,GAAmB,EAAE,CAAC;IAC7B,aAAa,GAAkB,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;IACrD,CAAC,GAAG,CAAC,CAAC;IACd,UAAU,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,wBAAwB,CAAC,EAAE,CAAC,CAAC;IACpD,mDAAmD;IAC1C,QAAQ,GAAW,EAAE,CAAC;IAE/B,OAAO;QACL,MAAM,CAAC,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACrB,OAAO,CAAC,CAAC;IACX,CAAC;IAED,gBAAgB;IAChB,OAAO;QACL,IAAI,CAAC,GAAG,UAAU,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;QACrC,IAAI,CAAC,GAAG,EAAE,CAAC;QACX,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3B,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;YAC7C,CAAC,IAAI,kBAAkB,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC;CACF;AAED,MAAM,OAAO,YAAY;IAYH;IAXpB,UAAU,GAAG,CAAC,CAAC;IACf,MAAM,GAAyB,IAAI,CAAC;IACpC,SAAS,GAA4B,IAAI,CAAC;IAC1C,OAAO,GAA0B,IAAI,CAAC;IACtC,OAAO,GAA0B,IAAI,CAAC;IAE9B,KAAK,GAAgB,IAAI,CAAC;IAC1B,KAAK,GAAkB,IAAI,CAAC;IAC5B,IAAI,GAAiB,IAAI,CAAC;IAC1B,MAAM,GAAG,KAAK,CAAC;IAEvB,YAAoB,GAAiB;QAAjB,QAAG,GAAH,GAAG,CAAc;QACnC,cAAc,CAAC,GAAG,EAAE;YAClB,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;YACpB,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,kBAAkB;IAClB,IAAI,CAAC,IAAY;QACf,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAS,CAAC;QACnC,cAAc,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC;IACD,KAAK;QACH,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;QACpB,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAC3B,cAAc,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,sCAAsC;IACtC,IAAI,CAAC,CAAO;QACV,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,cAAc,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACtE,CAAC;IAEO,GAAG,CAAC,IAAY,EAAE,GAAG,GAAG,EAAE,EAAE,IAAW;QAC7C,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAClE,CAAC;IAEO,QAAQ,CAAC,CAAS,EAAE,CAAQ;QAClC,OAAO;YACL,CAAC,EAAE,OAAO;YACV,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,GAAG,EAAE,CAAC,CAAC,MAAM;YACb,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;YACnG,WAAW,EAAE,CAAC,CAAC,KAAK;YACpB,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,UAAU,EAAE,CAAC,CAAC,UAAU;SACzB,CAAC;IACJ,CAAC;IAEO,SAAS,CAAC,CAAS,EAAE,CAAO,EAAE,MAAe;QACnD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE;YAAE,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM;gBAAE,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7E,CAAC;IAEO,UAAU,CAAC,MAAc;QAC/B,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;QACrB,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;QACpB,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;YAAE,OAAO;QACrB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC;QACd,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACzB,IAAI,MAAM,KAAK,SAAS;YAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,uBAAuB;QAC7E,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACvB,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAChC,OAAO;QACT,CAAC;QACD,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAChE,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;YAC1B,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YACvC,CAAC,CAAC,KAAK,EAAE,CAAC;YACV,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QAC7E,CAAC;IACH,CAAC;IAEO,MAAM,CAAC,CAAO;QACpB,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1B,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,CAAC,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC1D,IAAI,CAAC,KAAK,OAAO,EAAE,CAAC;YAClB,IAAI,IAAI,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;YAC/D,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;YACf,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;YACnE,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC1C,MAAM,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC/B,IAAI,CAAC,EAAE;oBAAE,SAAS;gBAClB,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACvB,EAAE,CAAC,IAAI,GAAG,IAAI,CAAC;gBACf,EAAE,CAAC,KAAK,GAAG,GAAG,GAAG,GAAG,CAAC;gBACrB,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;gBAC3B,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;gBACf,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;gBACf,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;gBAC5I,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;gBAChC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;YAC/H,CAAC;YACD,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QAClI,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QAC/D,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACzC,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACjD,QAAQ,CAAC,EAAE,CAAC;YACV,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACd,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;gBAChC,MAAM,CAAC,GAAU,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,IAAI,IAAI,EAAE,CAAC;gBACxH,MAAM,CAAC,GAAW;oBAChB,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;oBACvH,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAG,CAAC,CAAC,IAA+B,IAAI,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC;oBACxH,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAE;iBAC5G,CAAC;gBACF,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;gBAC9B,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;gBACf,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;gBACd,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACxC,CAAC;YACD,KAAK,MAAM,CAAC,CAAC,CAAC;gBACZ,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC3C,IAAI,CAAC,IAAI;oBAAE,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;gBACtD,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBACrC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM;oBAAE,OAAO,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;gBAC/D,IAAI,CAAC,CAAC,UAAU,KAAK,UAAU;oBAAE,OAAO,IAAI,CAAC,GAAG,CAAC,kBAAkB,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC,UAAU,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC,CAAC;gBACvI,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC,QAAQ;oBAAE,OAAO,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACrE,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,UAAU;oBAAE,OAAO,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;gBAChE,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;gBACpB,MAAM,CAAC,GAAU,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,IAAI,IAAI,EAAE,EAAE,EAAE,CAAC;gBAC7H,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;gBACnB,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;gBACf,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;gBACd,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBAC/B,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;YAC/G,CAAC;YACD,KAAK,MAAM,CAAC,CAAC,CAAC;gBACZ,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;qBACzC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC,UAAU,KAAK,UAAU,IAAI,CAAC,CAAC,UAAU,KAAK,QAAQ,CAAC;qBAC9F,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;oBACX,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,IAAI,EAAE;oBACnH,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;iBAC3G,CAAC,CAAC,CAAC;gBACN,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;YAC5C,CAAC;YACD,KAAK,OAAO;gBACV,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;YACjC,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACd,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;gBACrB,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;oBAAE,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;gBACrD,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBACrC,IAAI,CAAC,EAAE;oBAAE,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;gBACxD,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YACpI,CAAC;YACD,KAAK,MAAM,CAAC;YACZ,KAAK,OAAO,CAAC,CAAC,CAAC;gBACb,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;gBACrB,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;oBAAE,OAAO;gBAC7B,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,IAAI,CAAC,MAAM;oBAAE,OAAO,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gBAC/D,IAAI,CAAC,KAAK,MAAM;oBAAE,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAA8B,CAAC;;oBACvD,CAAC,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;gBAC/B,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE;oBAAE,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBACpE,OAAO;YACT,CAAC;YACD,KAAK,MAAM,CAAC,CAAC,CAAC;gBACZ,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;gBACrB,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;oBAAE,OAAO;gBAC7B,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,IAAI,CAAC,MAAM;oBAAE,OAAO,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gBAC/D,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;gBAC7C,IAAI,CAAC,MAAM,IAAI,MAAM,KAAK,IAAI,CAAC,IAAI;oBAAE,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;gBACpE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC5B,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC9B,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;gBACjE,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;gBAC/E,OAAO;YACT,CAAC;YACD,KAAK,cAAc,CAAC,CAAC,CAAC;gBACpB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;gBACrB,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;oBAAE,OAAO;gBAC7B,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC;oBACvE,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;oBAC5B,CAAC,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;oBAC1B,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;gBAC7E,CAAC;gBACD,OAAO;YACT,CAAC;YACD;gBACE,OAAO;QACX,CAAC;IACH,CAAC;CACF;AAED,0GAA0G;AAC1G,MAAM,UAAU,UAAU,CAAC,CAAa;IACtC,CAAC,CAAC,KAAK,EAAE,CAAC;AACZ,CAAC","sourcesContent":["/**\n * Test doubles: a manual clock, an in-memory lobby server speaking the lk1 protocol (spec/signaling.md, the parts the\n * client uses) and an in-memory peer network. Exported as `onelobby/testing`.\n */\nimport type { Clock } from \"./clock.js\";\nimport { CROCKFORD_ALPHABET, normalizeCode } from \"./crockford.js\";\nimport type {\n ChannelName,\n LinkFactory,\n LinkOptions,\n PeerLink,\n SignalPayload,\n SocketFactory,\n SocketLike,\n} from \"./transport.js\";\n\n// ---- clock ------------------------------------------------------------------------------------------------------\n\ninterface Timer {\n at: number;\n seq: number;\n fn: () => void;\n every?: number;\n dead?: boolean;\n}\n\n/** Deterministic clock: time only moves through `advance()`. Timers fire in (time, creation) order. */\nexport class ManualClock implements Clock {\n private t = 0;\n private seq = 0;\n private timers: Timer[] = [];\n\n now(): number {\n return this.t;\n }\n\n setInterval(fn: () => void, ms: number): () => void {\n const timer: Timer = { at: this.t + ms, seq: this.seq++, fn, every: Math.max(1, ms) };\n this.timers.push(timer);\n return () => {\n timer.dead = true;\n };\n }\n\n setTimeout(fn: () => void, ms: number): () => void {\n const timer: Timer = { at: this.t + ms, seq: this.seq++, fn };\n this.timers.push(timer);\n return () => {\n timer.dead = true;\n };\n }\n\n /** Advance time, firing due timers one at a time (microtasks flush between them when awaited via `run`). */\n advance(ms: number): void {\n const end = this.t + ms;\n for (;;) {\n this.timers = this.timers.filter((x) => !x.dead);\n let next: Timer | undefined;\n for (const x of this.timers) if (x.at <= end && (!next || x.at < next.at || (x.at === next.at && x.seq < next.seq))) next = x;\n if (!next) break;\n this.t = Math.max(this.t, next.at);\n if (next.every) {\n next.at += next.every;\n next.seq = this.seq++;\n } else {\n next.dead = true;\n }\n next.fn();\n }\n this.t = end;\n }\n\n /** Advance in `step` ms slices, letting promises and queued microtasks settle between slices. */\n async run(ms: number, step = 10): Promise<void> {\n let left = ms;\n await settle();\n while (left > 0) {\n const d = Math.min(step, left);\n this.advance(d);\n left -= d;\n await settle();\n }\n }\n}\n\n/** Let all pending microtasks (and chained promise continuations) run. */\nexport async function settle(rounds = 8): Promise<void> {\n for (let i = 0; i < rounds; i++) await Promise.resolve();\n // A macrotask boundary guarantees the whole microtask queue (delivery chains) has drained.\n await new Promise<void>((r) => ((globalThis as { setImmediate?: (f: () => void) => void }).setImmediate?.(r) ?? setTimeout(r, 0)));\n}\n\n// ---- in-memory peer network ---------------------------------------------------------------------------------------\n\nlet linkSerial = 0;\n\n/**\n * Pairs `PeerLink`s through the signaling path like WebRTC would: the offerer's SDP string names it, the answerer\n * resolves that name, and both open one microtask later. Messages are delivered FIFO, one microtask later.\n */\nexport class MemoryNetwork {\n private offers = new Map<string, MemoryLink>();\n /** Every link ever created (for assertions and fault injection). */\n readonly links: MemoryLink[] = [];\n /** Set to drop all traffic in one direction pair for fault injection: key `${from}->${to}` is not tracked; use link.mute(). */\n readonly factory: LinkFactory = { create: (opts) => new MemoryLink(this, opts) };\n\n /** @internal */\n register(offer: string, link: MemoryLink): void {\n this.offers.set(offer, link);\n this.links.push(link);\n }\n /** @internal */\n lookup(offer: string): MemoryLink | undefined {\n return this.offers.get(offer);\n }\n\n /** Kill every link whose far end is this identity-tagged factory owner (set `tag` via MemoryLink.owner). */\n killOwner(owner: string): void {\n for (const l of this.links) if (l.owner === owner) l.close();\n }\n\n /** Per-owner factory so tests can tell whose link is whose and cut one peer off. */\n factoryFor(owner: string): LinkFactory {\n return {\n create: (opts) => {\n const l = new MemoryLink(this, opts);\n l.owner = owner;\n return l;\n },\n };\n }\n}\n\nexport class MemoryLink implements PeerLink {\n readonly remoteId: number;\n isOpen = false;\n owner = \"\";\n onSignal: PeerLink[\"onSignal\"] = null;\n onOpen: PeerLink[\"onOpen\"] = null;\n onClose: PeerLink[\"onClose\"] = null;\n onMessage: PeerLink[\"onMessage\"] = null;\n onPath: PeerLink[\"onPath\"] = null;\n /** Bytes sent per channel, for assertions. */\n readonly sent: Record<ChannelName, number> = { reliable: 0, fast: 0, voice: 0 };\n /** While true, outgoing messages vanish (a frozen or partitioned peer). */\n blackhole = false;\n private peer: MemoryLink | null = null;\n private closed = false;\n private id = `mem-${++linkSerial}`;\n\n constructor(private net: MemoryNetwork, opts: LinkOptions) {\n this.remoteId = opts.remoteId;\n }\n\n startOffer(): void {\n this.net.register(this.id, this);\n queueMicrotask(() => this.onSignal?.({ sdp: { type: \"offer\", sdp: this.id } }));\n }\n\n handleSignal(s: SignalPayload): void {\n if (s.sdp?.type === \"offer\") {\n const offerer = this.net.lookup(s.sdp.sdp);\n this.net.links.push(this);\n if (!offerer) return;\n this.peer = offerer;\n queueMicrotask(() => this.onSignal?.({ sdp: { type: \"answer\", sdp: this.id } }));\n queueMicrotask(() => offerer.accept(this));\n queueMicrotask(() => this.open());\n }\n }\n\n /** @internal */\n accept(answerer: MemoryLink): void {\n this.peer = answerer;\n this.open();\n }\n\n private open(): void {\n if (this.closed || this.isOpen) return;\n this.isOpen = true;\n this.onOpen?.();\n this.onPath?.(\"direct\");\n }\n\n send(channel: ChannelName, bytes: Uint8Array): void {\n if (!this.isOpen || this.closed) return;\n this.sent[channel] += bytes.length;\n if (this.blackhole) return;\n const peer = this.peer;\n const copy = bytes.slice();\n queueMicrotask(() => {\n if (peer && peer.isOpen && !peer.closed && !this.closed) peer.onMessage?.(channel, copy);\n });\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n this.isOpen = false;\n const peer = this.peer;\n queueMicrotask(() => {\n this.onClose?.(\"closed\");\n if (peer && !peer.closed) peer.closeFromRemote();\n });\n }\n\n private closeFromRemote(): void {\n if (this.closed) return;\n this.closed = true;\n this.isOpen = false;\n this.onClose?.(\"remote closed\");\n }\n}\n\n// ---- in-memory lobby server ---------------------------------------------------------------------------------------\n\ntype Json = Record<string, unknown>;\n\ninterface MPeer {\n peerId: number;\n name: string;\n joinedAt: number;\n sock: MemorySocket | null;\n token: string;\n}\ninterface MLobby {\n code: string;\n gameId: string;\n netVersion: string;\n name: string;\n hostId: number;\n epoch: number;\n mode: string;\n tickRate: number;\n lateJoin: boolean;\n phase: string;\n meta: Record<string, string>;\n visibility: string;\n maxPlayers: number;\n peers: Map<number, MPeer>;\n next: number;\n kicked: Set<number>;\n reserved: Map<string, MPeer>;\n}\n\n/** Minimal onelobby-server (lk1) for client tests. Not a conformance target: stream A owns the real server. */\nexport class MemoryServer {\n readonly lobbies = new Map<string, MLobby>();\n /** Every socket ever opened (to drop one from a test). */\n readonly sockets: MemorySocket[] = [];\n readonly socketFactory: SocketFactory = () => this.connect();\n private n = 0;\n iceServers = [{ urls: [\"stun:stun.example:3478\"] }];\n /** Log of every client message, for assertions. */\n readonly received: Json[] = [];\n\n connect(): MemorySocket {\n const s = new MemorySocket(this);\n this.sockets.push(s);\n return s;\n }\n\n /** @internal */\n newCode(): string {\n let x = 0x9e3779b1 * (++this.n + 17);\n let s = \"\";\n for (let i = 0; i < 6; i++) {\n x = (Math.imul(x, 1103515245) + 12345) >>> 0;\n s += CROCKFORD_ALPHABET[(x >>> 16) & 31];\n }\n return this.lobbies.has(s) ? this.newCode() : s;\n }\n}\n\nexport class MemorySocket implements SocketLike {\n readyState = 0;\n onopen: SocketLike[\"onopen\"] = null;\n onmessage: SocketLike[\"onmessage\"] = null;\n onclose: SocketLike[\"onclose\"] = null;\n onerror: SocketLike[\"onerror\"] = null;\n\n private hello: Json | null = null;\n private lobby: MLobby | null = null;\n private peer: MPeer | null = null;\n private closed = false;\n\n constructor(private srv: MemoryServer) {\n queueMicrotask(() => {\n this.readyState = 1;\n this.onopen?.();\n });\n }\n\n // ---- socket API\n send(data: string): void {\n if (this.closed) return;\n const m = JSON.parse(data) as Json;\n queueMicrotask(() => this.handle(m));\n }\n close(): void {\n if (this.closed) return;\n this.closed = true;\n this.readyState = 3;\n this.leaveLobby(\"timeout\");\n queueMicrotask(() => this.onclose?.());\n }\n\n /** @internal server -> this client */\n push(m: Json): void {\n if (this.closed) return;\n queueMicrotask(() => this.onmessage?.({ data: JSON.stringify(m) }));\n }\n\n private err(code: string, msg = \"\", data?: Json) {\n this.push({ t: \"error\", code, msg, ...(data ? { data } : {}) });\n }\n\n private lobbyMsg(l: MLobby, p: MPeer): Json {\n return {\n t: \"lobby\",\n code: l.code,\n gameId: l.gameId,\n netVersion: l.netVersion,\n hostId: l.hostId,\n you: p.peerId,\n mode: l.mode,\n tickRate: l.tickRate,\n lateJoin: l.lateJoin,\n phase: l.phase,\n meta: l.meta,\n peers: [...l.peers.values()].map((q) => ({ peerId: q.peerId, name: q.name, joinedAt: q.joinedAt })),\n resumeToken: p.token,\n name: l.name,\n maxPlayers: l.maxPlayers,\n visibility: l.visibility,\n };\n }\n\n private broadcast(l: MLobby, m: Json, except?: number) {\n for (const q of l.peers.values()) if (q.peerId !== except) q.sock?.push(m);\n }\n\n private leaveLobby(reason: string) {\n const l = this.lobby;\n const p = this.peer;\n if (!l || !p) return;\n this.lobby = null;\n this.peer = null;\n p.sock = null;\n l.peers.delete(p.peerId);\n if (reason === \"timeout\") l.reserved.set(p.token, p); // slot kept for resume\n if (l.peers.size === 0) {\n this.srv.lobbies.delete(l.code);\n return;\n }\n this.broadcast(l, { t: \"peer_left\", peerId: p.peerId, reason });\n if (l.hostId === p.peerId) {\n l.hostId = Math.min(...l.peers.keys());\n l.epoch++;\n this.broadcast(l, { t: \"host_changed\", hostId: l.hostId, epoch: l.epoch });\n }\n }\n\n private handle(m: Json) {\n if (this.closed) return;\n this.srv.received.push(m);\n const t = String(m.t);\n if (t === \"ping\") return this.push({ t: \"pong\", n: m.n });\n if (t === \"hello\") {\n if (this.hello) return this.err(\"bad_request\", \"second hello\");\n this.hello = m;\n const tok = typeof m.resumeToken === \"string\" ? m.resumeToken : \"\";\n for (const l of this.srv.lobbies.values()) {\n const rp = l.reserved.get(tok);\n if (!rp) continue;\n l.reserved.delete(tok);\n rp.sock = this;\n rp.token = tok + \"+\";\n l.peers.set(rp.peerId, rp);\n this.lobby = l;\n this.peer = rp;\n this.push({ t: \"welcome\", peerId: rp.peerId, ice: this.srv.iceServers, serverTime: Date.now(), limits: { maxMsg: 16384, maxPlayers: 64 } });\n this.push(this.lobbyMsg(l, rp));\n return this.broadcast(l, { t: \"peer_joined\", peer: { peerId: rp.peerId, name: rp.name, joinedAt: rp.joinedAt } }, rp.peerId);\n }\n return this.push({ t: \"welcome\", ice: this.srv.iceServers, serverTime: Date.now(), limits: { maxMsg: 16384, maxPlayers: 64 } });\n }\n if (!this.hello) return this.err(\"bad_request\", \"hello first\");\n const gameId = String(this.hello.gameId);\n const netVersion = String(this.hello.netVersion);\n switch (t) {\n case \"create\": {\n const code = this.srv.newCode();\n const p: MPeer = { peerId: 1, name: String(this.hello.name), joinedAt: Date.now(), sock: this, token: `tok-${code}-1` };\n const l: MLobby = {\n code, gameId, netVersion, name: String(m.name), hostId: 1, epoch: 0, mode: String(m.mode), tickRate: Number(m.tickRate),\n lateJoin: !!m.lateJoin, phase: \"lobby\", meta: (m.meta as Record<string, string>) ?? {}, visibility: String(m.visibility),\n maxPlayers: Number(m.maxPlayers), peers: new Map([[1, p]]), next: 2, kicked: new Set(), reserved: new Map(),\n };\n this.srv.lobbies.set(code, l);\n this.lobby = l;\n this.peer = p;\n return this.push(this.lobbyMsg(l, p));\n }\n case \"join\": {\n const code = normalizeCode(String(m.code));\n if (!code) return this.err(\"bad_request\", \"bad code\");\n const l = this.srv.lobbies.get(code);\n if (!l || l.gameId !== gameId) return this.err(\"unknown_code\");\n if (l.netVersion !== netVersion) return this.err(\"version_mismatch\", \"\", { hostNetVersion: l.netVersion, yourNetVersion: netVersion });\n if (l.phase === \"in_game\" && !l.lateJoin) return this.err(\"in_game\");\n if (l.peers.size >= l.maxPlayers) return this.err(\"lobby_full\");\n const id = l.next++;\n const p: MPeer = { peerId: id, name: String(this.hello.name), joinedAt: Date.now(), sock: this, token: `tok-${code}-${id}` };\n l.peers.set(id, p);\n this.lobby = l;\n this.peer = p;\n this.push(this.lobbyMsg(l, p));\n return this.broadcast(l, { t: \"peer_joined\", peer: { peerId: id, name: p.name, joinedAt: p.joinedAt } }, id);\n }\n case \"list\": {\n const items = [...this.srv.lobbies.values()]\n .filter((l) => l.gameId === gameId && l.netVersion === netVersion && l.visibility === \"public\")\n .map((l) => ({\n code: l.code, gameId: l.gameId, netVersion: l.netVersion, name: l.name, hostName: l.peers.get(l.hostId)?.name ?? \"\",\n players: l.peers.size, maxPlayers: l.maxPlayers, phase: l.phase, meta: l.meta, region: \"mem\", createdAt: 0,\n }));\n return this.push({ t: \"lobbies\", items });\n }\n case \"leave\":\n return this.leaveLobby(\"left\");\n case \"signal\": {\n const l = this.lobby;\n if (!l || !this.peer) return this.err(\"bad_request\");\n const to = l.peers.get(Number(m.to));\n if (!to) return this.err(\"bad_request\", \"no such peer\");\n return to.sock?.push({ t: \"signal\", from: this.peer.peerId, ...(m.sdp ? { sdp: m.sdp } : {}), ...(m.ice ? { ice: m.ice } : {}) });\n }\n case \"meta\":\n case \"phase\": {\n const l = this.lobby;\n if (!l || !this.peer) return;\n if (l.hostId !== this.peer.peerId) return this.err(\"not_host\");\n if (t === \"meta\") l.meta = m.meta as Record<string, string>;\n else l.phase = String(m.phase);\n for (const q of l.peers.values()) q.sock?.push(this.lobbyMsg(l, q));\n return;\n }\n case \"kick\": {\n const l = this.lobby;\n if (!l || !this.peer) return;\n if (l.hostId !== this.peer.peerId) return this.err(\"not_host\");\n const victim = l.peers.get(Number(m.peerId));\n if (!victim || victim === this.peer) return this.err(\"bad_request\");\n l.kicked.add(victim.peerId);\n l.peers.delete(victim.peerId);\n victim.sock?.push({ t: \"error\", code: \"kicked\", msg: \"kicked\" });\n this.broadcast(l, { t: \"peer_left\", peerId: victim.peerId, reason: \"kicked\" });\n return;\n }\n case \"host_changed\": {\n const l = this.lobby;\n if (!l || !this.peer) return;\n if (Number(m.hostId) === this.peer.peerId && Number(m.epoch) > l.epoch) {\n l.hostId = this.peer.peerId;\n l.epoch = Number(m.epoch);\n this.broadcast(l, { t: \"host_changed\", hostId: l.hostId, epoch: l.epoch });\n }\n return;\n }\n default:\n return;\n }\n }\n}\n\n/** Convenience: kill a socket as if the network dropped (server sees a timeout, others get peer_left). */\nexport function dropSocket(s: SocketLike): void {\n s.close();\n}\n"]}
1
+ {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Test doubles: a manual clock, an in-memory lobby server speaking the lk1 protocol (spec/signaling.md, the parts the\n * client uses) and an in-memory peer network. Exported as `onelobby/testing`.\n */\nimport type { Clock } from \"./clock.js\";\nimport { CROCKFORD_ALPHABET, normalizeCode } from \"./crockford.js\";\nimport type {\n ChannelName,\n LinkFactory,\n LinkOptions,\n PeerLink,\n SignalPayload,\n SocketFactory,\n SocketLike,\n} from \"./transport.js\";\n\n// ---- clock ------------------------------------------------------------------------------------------------------\n\ninterface Timer {\n at: number;\n seq: number;\n fn: () => void;\n every?: number;\n dead?: boolean;\n}\n\n/** Deterministic clock: time only moves through `advance()`. Timers fire in (time, creation) order. */\nexport class ManualClock implements Clock {\n private t = 0;\n private seq = 0;\n private timers: Timer[] = [];\n\n now(): number {\n return this.t;\n }\n\n setInterval(fn: () => void, ms: number): () => void {\n const timer: Timer = { at: this.t + ms, seq: this.seq++, fn, every: Math.max(1, ms) };\n this.timers.push(timer);\n return () => {\n timer.dead = true;\n };\n }\n\n setTimeout(fn: () => void, ms: number): () => void {\n const timer: Timer = { at: this.t + ms, seq: this.seq++, fn };\n this.timers.push(timer);\n return () => {\n timer.dead = true;\n };\n }\n\n /** Advance time, firing due timers one at a time (microtasks flush between them when awaited via `run`). */\n advance(ms: number): void {\n const end = this.t + ms;\n for (;;) {\n this.timers = this.timers.filter((x) => !x.dead);\n let next: Timer | undefined;\n for (const x of this.timers) if (x.at <= end && (!next || x.at < next.at || (x.at === next.at && x.seq < next.seq))) next = x;\n if (!next) break;\n this.t = Math.max(this.t, next.at);\n if (next.every) {\n next.at += next.every;\n next.seq = this.seq++;\n } else {\n next.dead = true;\n }\n next.fn();\n }\n this.t = end;\n }\n\n /** Advance in `step` ms slices, letting promises and queued microtasks settle between slices. */\n async run(ms: number, step = 10): Promise<void> {\n let left = ms;\n await settle();\n while (left > 0) {\n const d = Math.min(step, left);\n this.advance(d);\n left -= d;\n await settle();\n }\n }\n}\n\n/** Let all pending microtasks (and chained promise continuations) run. */\nexport async function settle(rounds = 8): Promise<void> {\n for (let i = 0; i < rounds; i++) await Promise.resolve();\n // A macrotask boundary guarantees the whole microtask queue (delivery chains) has drained.\n await new Promise<void>((r) => ((globalThis as { setImmediate?: (f: () => void) => void }).setImmediate?.(r) ?? setTimeout(r, 0)));}\n\n// ---- in-memory peer network ---------------------------------------------------------------------------------------\n\nlet linkSerial = 0;\n\n/**\n * Pairs `PeerLink`s through the signaling path like WebRTC would: the offerer's SDP string names it, the answerer\n * resolves that name, and both open one microtask later. Messages are delivered FIFO, one microtask later.\n */\nexport class MemoryNetwork {\n private offers = new Map<string, MemoryLink>();\n /** Every link ever created (for assertions and fault injection). */\n readonly links: MemoryLink[] = [];\n /** Set to drop all traffic in one direction pair for fault injection: key `${from}->${to}` is not tracked; use link.mute(). */\n readonly factory: LinkFactory = { create: (opts) => new MemoryLink(this, opts) };\n\n /** @internal */\n register(offer: string, link: MemoryLink): void {\n this.offers.set(offer, link);\n this.links.push(link);\n }\n /** @internal */\n lookup(offer: string): MemoryLink | undefined {\n return this.offers.get(offer);\n }\n\n /** Kill every link whose far end is this identity-tagged factory owner (set `tag` via MemoryLink.owner). */\n killOwner(owner: string): void {\n for (const l of this.links) if (l.owner === owner) l.close();\n }\n\n /** Per-owner factory so tests can tell whose link is whose and cut one peer off. */\n factoryFor(owner: string): LinkFactory {\n return {\n create: (opts) => {\n const l = new MemoryLink(this, opts);\n l.owner = owner;\n return l;\n },\n };\n }\n}\n\nexport class MemoryLink implements PeerLink {\n readonly remoteId: number;\n isOpen = false;\n owner = \"\";\n onSignal: PeerLink[\"onSignal\"] = null;\n onOpen: PeerLink[\"onOpen\"] = null;\n onClose: PeerLink[\"onClose\"] = null;\n onMessage: PeerLink[\"onMessage\"] = null;\n onPath: PeerLink[\"onPath\"] = null;\n /** Bytes sent per channel, for assertions. */\n readonly sent: Record<ChannelName, number> = { reliable: 0, fast: 0, voice: 0 };\n /** While true, outgoing messages vanish (a frozen or partitioned peer). */\n blackhole = false;\n private peer: MemoryLink | null = null;\n private closed = false;\n private id = `mem-${++linkSerial}`;\n\n constructor(private net: MemoryNetwork, opts: LinkOptions) {\n this.remoteId = opts.remoteId;\n }\n\n startOffer(): void {\n this.net.register(this.id, this);\n queueMicrotask(() => this.onSignal?.({ sdp: { type: \"offer\", sdp: this.id } }));\n }\n\n handleSignal(s: SignalPayload): void {\n if (s.sdp?.type === \"offer\") {\n const offerer = this.net.lookup(s.sdp.sdp);\n this.net.links.push(this);\n if (!offerer) return;\n this.peer = offerer;\n queueMicrotask(() => this.onSignal?.({ sdp: { type: \"answer\", sdp: this.id } }));\n queueMicrotask(() => offerer.accept(this));\n queueMicrotask(() => this.open());\n }\n }\n\n /** @internal */\n accept(answerer: MemoryLink): void {\n this.peer = answerer;\n this.open();\n }\n\n private open(): void {\n if (this.closed || this.isOpen) return;\n this.isOpen = true;\n this.onOpen?.();\n this.onPath?.(\"direct\");\n }\n\n send(channel: ChannelName, bytes: Uint8Array): void {\n if (!this.isOpen || this.closed) return;\n this.sent[channel] += bytes.length;\n if (this.blackhole) return;\n const peer = this.peer;\n const copy = bytes.slice();\n queueMicrotask(() => {\n if (peer && peer.isOpen && !peer.closed && !this.closed) peer.onMessage?.(channel, copy);\n });\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n this.isOpen = false;\n const peer = this.peer;\n queueMicrotask(() => {\n this.onClose?.(\"closed\");\n if (peer && !peer.closed) peer.closeFromRemote();\n });\n }\n\n private closeFromRemote(): void {\n if (this.closed) return;\n this.closed = true;\n this.isOpen = false;\n this.onClose?.(\"remote closed\");\n }\n}\n\n// ---- in-memory lobby server ---------------------------------------------------------------------------------------\n\ntype Json = Record<string, unknown>;\n\ninterface MPeer {\n peerId: number;\n name: string;\n joinedAt: number;\n sock: MemorySocket | null;\n token: string;\n}\ninterface MLobby {\n code: string;\n gameId: string;\n netVersion: string;\n name: string;\n hostId: number;\n epoch: number;\n mode: string;\n tickRate: number;\n lateJoin: boolean;\n phase: string;\n meta: Record<string, string>;\n visibility: string;\n maxPlayers: number;\n peers: Map<number, MPeer>;\n next: number;\n kicked: Set<number>;\n reserved: Map<string, MPeer>;\n}\n\n/** Minimal onelobby-server (lk1) for client tests. Not a conformance target: stream A owns the real server. */\nexport class MemoryServer {\n readonly lobbies = new Map<string, MLobby>();\n /** Every socket ever opened (to drop one from a test). */\n readonly sockets: MemorySocket[] = [];\n readonly socketFactory: SocketFactory = () => this.connect();\n private n = 0;\n iceServers = [{ urls: [\"stun:stun.example:3478\"] }];\n /** Log of every client message, for assertions. */\n readonly received: Json[] = [];\n\n connect(): MemorySocket {\n const s = new MemorySocket(this);\n this.sockets.push(s);\n return s;\n }\n\n /** @internal */\n newCode(): string {\n let x = 0x9e3779b1 * (++this.n + 17);\n let s = \"\";\n for (let i = 0; i < 6; i++) {\n x = (Math.imul(x, 1103515245) + 12345) >>> 0;\n s += CROCKFORD_ALPHABET[(x >>> 16) & 31];\n }\n return this.lobbies.has(s) ? this.newCode() : s;\n }\n}\n\nexport class MemorySocket implements SocketLike {\n readyState = 0;\n onopen: SocketLike[\"onopen\"] = null;\n onmessage: SocketLike[\"onmessage\"] = null;\n onclose: SocketLike[\"onclose\"] = null;\n onerror: SocketLike[\"onerror\"] = null;\n\n private hello: Json | null = null;\n private lobby: MLobby | null = null;\n private peer: MPeer | null = null;\n private closed = false;\n\n constructor(private srv: MemoryServer) {\n queueMicrotask(() => {\n this.readyState = 1;\n this.onopen?.();\n });\n }\n\n // ---- socket API\n send(data: string): void {\n if (this.closed) return;\n const m = JSON.parse(data) as Json;\n queueMicrotask(() => this.handle(m));\n }\n close(): void {\n if (this.closed) return;\n this.closed = true;\n this.readyState = 3;\n this.leaveLobby(\"timeout\");\n queueMicrotask(() => this.onclose?.());\n }\n\n /** @internal server -> this client */\n push(m: Json): void {\n if (this.closed) return;\n queueMicrotask(() => this.onmessage?.({ data: JSON.stringify(m) }));\n }\n\n private err(code: string, msg = \"\", data?: Json) {\n this.push({ t: \"error\", code, msg, ...(data ? { data } : {}) });\n }\n\n private lobbyMsg(l: MLobby, p: MPeer): Json {\n return {\n t: \"lobby\",\n code: l.code,\n gameId: l.gameId,\n netVersion: l.netVersion,\n hostId: l.hostId,\n you: p.peerId,\n mode: l.mode,\n tickRate: l.tickRate,\n lateJoin: l.lateJoin,\n phase: l.phase,\n meta: l.meta,\n peers: [...l.peers.values()].map((q) => ({ peerId: q.peerId, name: q.name, joinedAt: q.joinedAt })),\n resumeToken: p.token,\n name: l.name,\n maxPlayers: l.maxPlayers,\n visibility: l.visibility,\n };\n }\n\n private broadcast(l: MLobby, m: Json, except?: number) {\n for (const q of l.peers.values()) if (q.peerId !== except) q.sock?.push(m);\n }\n\n private leaveLobby(reason: string) {\n const l = this.lobby;\n const p = this.peer;\n if (!l || !p) return;\n this.lobby = null;\n this.peer = null;\n p.sock = null;\n l.peers.delete(p.peerId);\n if (reason === \"timeout\") l.reserved.set(p.token, p); // slot kept for resume\n if (l.peers.size === 0) {\n this.srv.lobbies.delete(l.code);\n return;\n }\n this.broadcast(l, { t: \"peer_left\", peerId: p.peerId, reason });\n if (l.hostId === p.peerId) {\n l.hostId = Math.min(...l.peers.keys());\n l.epoch++;\n this.broadcast(l, { t: \"host_changed\", hostId: l.hostId, epoch: l.epoch });\n }\n }\n\n private handle(m: Json) {\n if (this.closed) return;\n this.srv.received.push(m);\n const t = String(m.t);\n if (t === \"ping\") return this.push({ t: \"pong\", n: m.n });\n if (t === \"hello\") {\n if (this.hello) return this.err(\"bad_request\", \"second hello\");\n this.hello = m;\n const tok = typeof m.resumeToken === \"string\" ? m.resumeToken : \"\";\n for (const l of this.srv.lobbies.values()) {\n const rp = l.reserved.get(tok);\n if (!rp) continue;\n l.reserved.delete(tok);\n rp.sock = this;\n rp.token = tok + \"+\";\n l.peers.set(rp.peerId, rp);\n this.lobby = l;\n this.peer = rp;\n this.push({ t: \"welcome\", peerId: rp.peerId, ice: this.srv.iceServers, serverTime: Date.now(), limits: { maxMsg: 16384, maxPlayers: 64 } });\n this.push(this.lobbyMsg(l, rp));\n return this.broadcast(l, { t: \"peer_joined\", peer: { peerId: rp.peerId, name: rp.name, joinedAt: rp.joinedAt } }, rp.peerId);\n }\n return this.push({ t: \"welcome\", ice: this.srv.iceServers, serverTime: Date.now(), limits: { maxMsg: 16384, maxPlayers: 64 } });\n }\n if (!this.hello) return this.err(\"bad_request\", \"hello first\");\n const gameId = String(this.hello.gameId);\n const netVersion = String(this.hello.netVersion);\n switch (t) {\n case \"create\": {\n const code = this.srv.newCode();\n const p: MPeer = { peerId: 1, name: String(this.hello.name), joinedAt: Date.now(), sock: this, token: `tok-${code}-1` };\n const l: MLobby = {\n code, gameId, netVersion, name: String(m.name), hostId: 1, epoch: 0, mode: String(m.mode), tickRate: Number(m.tickRate),\n lateJoin: !!m.lateJoin, phase: \"lobby\", meta: (m.meta as Record<string, string>) ?? {}, visibility: String(m.visibility),\n maxPlayers: Number(m.maxPlayers), peers: new Map([[1, p]]), next: 2, kicked: new Set(), reserved: new Map(),\n };\n this.srv.lobbies.set(code, l);\n this.lobby = l;\n this.peer = p;\n return this.push(this.lobbyMsg(l, p));\n }\n case \"join\": {\n const code = normalizeCode(String(m.code));\n if (!code) return this.err(\"bad_request\", \"bad code\");\n const l = this.srv.lobbies.get(code);\n if (!l || l.gameId !== gameId) return this.err(\"unknown_code\");\n if (l.netVersion !== netVersion) return this.err(\"version_mismatch\", \"\", { hostNetVersion: l.netVersion, yourNetVersion: netVersion });\n if (l.phase === \"in_game\" && !l.lateJoin) return this.err(\"in_game\");\n if (l.peers.size >= l.maxPlayers) return this.err(\"lobby_full\");\n const id = l.next++;\n const p: MPeer = { peerId: id, name: String(this.hello.name), joinedAt: Date.now(), sock: this, token: `tok-${code}-${id}` };\n l.peers.set(id, p);\n this.lobby = l;\n this.peer = p;\n this.push(this.lobbyMsg(l, p));\n return this.broadcast(l, { t: \"peer_joined\", peer: { peerId: id, name: p.name, joinedAt: p.joinedAt } }, id);\n }\n case \"list\": {\n const items = [...this.srv.lobbies.values()]\n .filter((l) => l.gameId === gameId && l.netVersion === netVersion && l.visibility === \"public\")\n .map((l) => ({\n code: l.code, gameId: l.gameId, netVersion: l.netVersion, name: l.name, hostName: l.peers.get(l.hostId)?.name ?? \"\",\n players: l.peers.size, maxPlayers: l.maxPlayers, phase: l.phase, meta: l.meta, region: \"mem\", createdAt: 0,\n }));\n return this.push({ t: \"lobbies\", items });\n }\n case \"leave\":\n return this.leaveLobby(\"left\");\n case \"signal\": {\n const l = this.lobby;\n if (!l || !this.peer) return this.err(\"bad_request\");\n const to = l.peers.get(Number(m.to));\n if (!to) return this.err(\"bad_request\", \"no such peer\");\n return to.sock?.push({ t: \"signal\", from: this.peer.peerId, ...(m.sdp ? { sdp: m.sdp } : {}), ...(m.ice ? { ice: m.ice } : {}) });\n }\n case \"meta\":\n case \"phase\": {\n const l = this.lobby;\n if (!l || !this.peer) return;\n if (l.hostId !== this.peer.peerId) return this.err(\"not_host\");\n if (t === \"meta\") l.meta = m.meta as Record<string, string>;\n else l.phase = String(m.phase);\n for (const q of l.peers.values()) q.sock?.push(this.lobbyMsg(l, q));\n return;\n }\n case \"kick\": {\n const l = this.lobby;\n if (!l || !this.peer) return;\n if (l.hostId !== this.peer.peerId) return this.err(\"not_host\");\n const victim = l.peers.get(Number(m.peerId));\n if (!victim || victim === this.peer) return this.err(\"bad_request\");\n // ban:false disconnects without the ban; closeCode / closeReason go to the removed peer (spec/signaling.md)\n const ban = m.ban !== false;\n const data: Json = {};\n if (!ban) data.ban = false;\n if (typeof m.closeCode === \"number\") data.closeCode = m.closeCode;\n if (typeof m.closeReason === \"string\") data.closeReason = m.closeReason;\n if (ban) l.kicked.add(victim.peerId);\n l.peers.delete(victim.peerId);\n victim.sock?.push({ t: \"error\", code: \"kicked\", msg: \"kicked\", ...(Object.keys(data).length ? { data } : {}) });\n this.broadcast(l, { t: \"peer_left\", peerId: victim.peerId, reason: ban ? \"kicked\" : \"disconnected\" });\n return;\n }\n case \"host_changed\": {\n const l = this.lobby;\n if (!l || !this.peer) return;\n if (Number(m.hostId) === this.peer.peerId && Number(m.epoch) > l.epoch) {\n l.hostId = this.peer.peerId;\n l.epoch = Number(m.epoch);\n this.broadcast(l, { t: \"host_changed\", hostId: l.hostId, epoch: l.epoch });\n }\n return;\n }\n default:\n return;\n }\n }\n}\n\n/** Convenience: kill a socket as if the network dropped (server sees a timeout, others get peer_left). */\nexport function dropSocket(s: SocketLike): void {\n s.close();\n}\n"]}