onelobby 0.3.0 → 0.3.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/README.md +2 -2
- package/dist/direct/hub-host.d.ts +3 -1
- package/dist/direct/hub-host.js +6 -4
- package/dist/direct/hub-host.js.map +1 -1
- package/dist/direct/index.js +11 -0
- package/dist/direct/index.js.map +1 -1
- package/dist/onelobby.bundle.mjs +2 -2
- package/dist/onelobby.bundle.mjs.map +1 -1
- package/dist/onelobby.d.ts +1 -1
- package/dist/onelobby.global.js +2 -2
- package/dist/onelobby.global.js.map +1 -1
- package/dist/onelobby.global.min.js +2 -2
- package/dist/onelobby.global.min.js.map +1 -1
- package/dist/onelobby.js +1 -1
- package/dist/onelobby.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ You need a OneLobby lobby server (the WebSocket URL, like `wss://lobby.example.c
|
|
|
9
9
|
## Use it from a `<script>` tag (no build tools)
|
|
10
10
|
|
|
11
11
|
```html
|
|
12
|
-
<script src="https://cdn.jsdelivr.net/npm/onelobby@0.3.
|
|
12
|
+
<script src="https://cdn.jsdelivr.net/npm/onelobby@0.3.1/dist/onelobby.global.min.js"></script>
|
|
13
13
|
<script>
|
|
14
14
|
const kit = new OneLobby.OneLobby({
|
|
15
15
|
gameId: "com.you.yourgame", // any unique name for your game
|
|
@@ -51,7 +51,7 @@ const lobby = await kit.host({ name: "My game", maxPlayers: 4 });
|
|
|
51
51
|
console.log("Tell your friend this code:", formatCode(lobby.code));
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
Without a bundler: `import { OneLobby } from "https://cdn.jsdelivr.net/npm/onelobby@0.3.
|
|
54
|
+
Without a bundler: `import { OneLobby } from "https://cdn.jsdelivr.net/npm/onelobby@0.3.1/dist/onelobby.bundle.mjs"`.
|
|
55
55
|
|
|
56
56
|
## Made for players
|
|
57
57
|
|
|
@@ -54,8 +54,10 @@ export declare class DirectHubHost {
|
|
|
54
54
|
connectLocal(): SocketLike;
|
|
55
55
|
/** Asks the home router to forward TCP and UDP `port` here (UPnP, then NAT-PMP; docs/direct-ip.md s.2a). */
|
|
56
56
|
openPort(options?: PortMapperOptions): Promise<PortMapResult>;
|
|
57
|
-
/** Stops listening, closes every joiner socket and removes the router mapping. */
|
|
57
|
+
/** Stops listening, closes every joiner socket and removes the router mapping. Every call gets the same promise. */
|
|
58
58
|
close(): Promise<void>;
|
|
59
|
+
private closing?;
|
|
60
|
+
private closeOnce;
|
|
59
61
|
private accept;
|
|
60
62
|
/** RFC 6455 s.4.2: answers 101 with Sec-WebSocket-Accept, or 200 for /healthz, or 400/426. */
|
|
61
63
|
private handshake;
|
package/dist/direct/hub-host.js
CHANGED
|
@@ -114,10 +114,12 @@ export class DirectHubHost {
|
|
|
114
114
|
this.portMapper = new PortMapper(this.port, options);
|
|
115
115
|
return this.portMapper.open();
|
|
116
116
|
}
|
|
117
|
-
/** Stops listening, closes every joiner socket and removes the router mapping. */
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
/** Stops listening, closes every joiner socket and removes the router mapping. Every call gets the same promise. */
|
|
118
|
+
close() {
|
|
119
|
+
return (this.closing ??= this.closeOnce());
|
|
120
|
+
}
|
|
121
|
+
closing;
|
|
122
|
+
async closeOnce() {
|
|
121
123
|
this.closed = true;
|
|
122
124
|
clearInterval(this.ticker);
|
|
123
125
|
const unmap = this.portMapper?.close();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hub-host.js","sourceRoot":"","sources":["../../src/direct/hub-host.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference types=\"node\" />\nimport { createHash } from \"node:crypto\";\nimport { createSocket } from \"node:dgram\";\nimport { createServer, type Server, type Socket } from \"node:net\";\nimport type { SocketLike } from \"../transport.js\";\nimport { SignalingHub, directHubOptions, type HubDeps, type HubOptions, type Session } from \"./hub.js\";\nimport { PortMapper, type PortMapperOptions, type PortMapResult } from \"./portmap.js\";\n\nconst WS_GUID = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\";\nconst MAX_HEADER_BYTES = 8192;\nconst HANDSHAKE_TIMEOUT_MS = 5000;\nconst STATUS_TEXT: Record<number, string> = { 200: \"OK\", 301: \"Moved Permanently\", 302: \"Found\", 404: \"Not Found\", 500: \"Internal Server Error\" };\n\n/**\n * Rejects (EADDRINUSE) when another program holds IPv4 TCP or UDP `port`. Windows lets the dual-stack listener bind\n * over a program on 0.0.0.0 (every IPv4 joiner would then reach that program), and the peer links need UDP `port`\n * too, so both are tried and released before the real listen.\n */\nasync function probeIPv4(port: number): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const s = createServer();\n s.once(\"error\", reject);\n s.listen({ port, host: \"0.0.0.0\", exclusive: true }, () => s.close(() => resolve()));\n });\n await new Promise<void>((resolve, reject) => {\n const u = createSocket({ type: \"udp4\" });\n u.once(\"error\", (e) => {\n u.close();\n reject(e);\n });\n u.bind({ port, address: \"0.0.0.0\", exclusive: true }, () => u.close(() => resolve()));\n });\n}\n\nexport class PortInUseError extends Error {\n readonly code = \"port_in_use\";\n constructor(port: number, detail: string) {\n super(`port_in_use: cannot listen on port ${port} (${detail}): is another copy of the game hosting?`);\n }\n}\n\n/**\n * Runs a {@link SignalingHub} inside a Node process and listens for joiners' signaling WebSockets on TCP `port`: the\n * Node twin of C#'s `DirectHubHost` (docs/direct-ip.md s.2, s.5 step 3). A plain `net` server with a minimal HTTP/1.1\n * upgrade and RFC 6455 framing (text, close, ping/pong, fragments; client frames must be masked), so the package keeps\n * zero runtime dependencies. Any path upgrades; `GET /healthz` answers `ok`. The host's own client talks to the hub in\n * memory through {@link connectLocal}. `hub.tick()` runs once a second on an unref'd timer.\n */\n/** One plain-HTTP answer from the host's port, e.g. a browser join page. */\nexport interface HttpReply {\n /** Default 200. */\n status?: number;\n /** Content-Type, e.g. \"text/html; charset=utf-8\". */\n type: string;\n body: string | Uint8Array;\n}\n\n/** Serves plain `GET` requests on the direct port (path without the query); null answers 404. */\nexport type HttpHandler = (path: string) => HttpReply | null;\n\nexport class DirectHubHost {\n readonly hub: SignalingHub;\n /**\n * Plain `GET`s that are not WebSocket upgrades go here, so the host can serve its own join page: a browser opening\n * `http://<host address>:<port>/` gets a page that joins with `kit.joinDirect(location.host)`. Null (default): 426.\n */\n http: HttpHandler | null = null;\n /** The bound TCP port (the requested one, or the one the OS picked for 0). */\n readonly port: number;\n portMapper: PortMapper | null = null;\n private readonly server: Server;\n private readonly sockets = new Set<Socket>();\n private readonly ticker: ReturnType<typeof setInterval>;\n private readonly maxMessageBytes: number;\n private closed = false;\n\n private constructor(server: Server, port: number, hub: SignalingHub, maxMessageBytes: number) {\n this.server = server;\n this.port = port;\n this.hub = hub;\n this.maxMessageBytes = maxMessageBytes;\n this.ticker = setInterval(() => this.hub.tick(), 1000);\n this.ticker.unref?.();\n server.on(\"connection\", (sock) => this.accept(sock));\n }\n\n /**\n * Binds TCP `port` on every interface (IPv6 dual-stack where available). Hub options default to\n * {@link directHubOptions} (one lobby, no codes, no ICE servers). Rejects with {@link PortInUseError}.\n */\n static async listen(port: number, options?: Partial<HubOptions>, deps?: HubDeps & { host?: string }): Promise<DirectHubHost> {\n const opts = { ...directHubOptions(8), ...options };\n const server = createServer({ noDelay: true });\n const tryListen = (host: string, ipv6Only?: boolean) =>\n new Promise<void>((resolve, reject) => {\n const onError = (e: NodeJS.ErrnoException) => reject(e);\n server.once(\"error\", onError);\n server.listen({ port, host, ipv6Only }, () => {\n server.off(\"error\", onError);\n resolve();\n });\n });\n try {\n if (deps?.host) await tryListen(deps.host);\n else {\n if (port !== 0) await probeIPv4(port);\n try {\n await tryListen(\"::\", false);\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"EADDRINUSE\") throw e;\n await tryListen(\"0.0.0.0\"); // no IPv6 on this machine\n }\n }\n } catch (e) {\n throw new PortInUseError(port, (e as NodeJS.ErrnoException).code ?? String(e));\n }\n const addr = server.address();\n const bound = typeof addr === \"object\" && addr ? addr.port : port;\n return new DirectHubHost(server, bound, new SignalingHub(opts, deps), opts.maxMessageBytes);\n }\n\n /** Joiner sockets currently open. */\n get connectionCount(): number {\n return this.sockets.size;\n }\n\n /**\n * The host's own signaling socket: a WebSocket look-alike wired straight into the hub (no network). Pass it to the\n * kit as `advanced.socketFactory: () => host.connectLocal()`.\n */\n connectLocal(): SocketLike {\n return new LocalHubSocket(this.hub);\n }\n\n /** Asks the home router to forward TCP and UDP `port` here (UPnP, then NAT-PMP; docs/direct-ip.md s.2a). */\n openPort(options?: PortMapperOptions): Promise<PortMapResult> {\n if (this.portMapper) throw new Error(\"the port is already being opened\");\n this.portMapper = new PortMapper(this.port, options);\n return this.portMapper.open();\n }\n\n /** Stops listening, closes every joiner socket and removes the router mapping. */\n async close(): Promise<void> {\n if (this.closed) return;\n this.closed = true;\n clearInterval(this.ticker);\n const unmap = this.portMapper?.close();\n for (const s of this.sockets) s.end(closeFrame(1001, \"host left\"));\n await new Promise<void>((resolve) => this.server.close(() => resolve()));\n setTimeout(() => { for (const s of this.sockets) s.destroy(); }, 300).unref?.();\n await unmap;\n }\n\n // ---------------------------------------------------------------- one TCP connection\n\n private accept(sock: Socket): void {\n if (this.closed) {\n sock.destroy();\n return;\n }\n this.sockets.add(sock);\n sock.on(\"close\", () => this.sockets.delete(sock));\n sock.on(\"error\", () => {});\n const ip = (sock.remoteAddress ?? \"\").replace(/^::ffff:/, \"\");\n let head = Buffer.alloc(0);\n const timer = setTimeout(() => sock.destroy(), HANDSHAKE_TIMEOUT_MS);\n const onData = (chunk: Buffer) => {\n head = Buffer.concat([head, chunk]);\n const end = head.indexOf(\"\\r\\n\\r\\n\");\n if (end < 0) {\n if (head.length > MAX_HEADER_BYTES) sock.destroy();\n return;\n }\n clearTimeout(timer);\n sock.off(\"data\", onData);\n const rest = head.subarray(end + 4);\n if (!this.handshake(sock, head.subarray(0, end).toString(\"latin1\"))) return;\n this.run(sock, ip, rest);\n };\n sock.on(\"data\", onData);\n }\n\n /** RFC 6455 s.4.2: answers 101 with Sec-WebSocket-Accept, or 200 for /healthz, or 400/426. */\n private handshake(sock: Socket, head: string): boolean {\n const [requestLine, ...lines] = head.split(\"\\r\\n\");\n const headers = new Map<string, string>();\n for (const l of lines) {\n const i = l.indexOf(\":\");\n if (i > 0) headers.set(l.slice(0, i).trim().toLowerCase(), l.slice(i + 1).trim());\n }\n const [method, path] = requestLine.split(\" \");\n const reply = (status: string, body: string) =>\n sock.end(`HTTP/1.1 ${status}\\r\\nContent-Type: text/plain\\r\\nContent-Length: ${Buffer.byteLength(body)}\\r\\nConnection: close\\r\\n\\r\\n${body}`);\n if (method === \"GET\" && path === \"/healthz\") {\n reply(\"200 OK\", \"ok\");\n return false;\n }\n const key = headers.get(\"sec-websocket-key\");\n const upgrade = /websocket/i.test(headers.get(\"upgrade\") ?? \"\");\n if (method === \"GET\" && !upgrade && this.http && path) {\n let page: HttpReply | null = null;\n try {\n page = this.http(path.split(\"?\")[0]);\n } catch {\n page = { status: 500, type: \"text/plain\", body: \"error\" };\n }\n const p = page ?? { status: 404, type: \"text/plain\", body: \"not found\" };\n const body = typeof p.body === \"string\" ? Buffer.from(p.body, \"utf8\") : Buffer.from(p.body);\n const status = p.status ?? 200;\n sock.write(`HTTP/1.1 ${status} ${STATUS_TEXT[status] ?? \"OK\"}\\r\\nContent-Type: ${p.type}\\r\\n` +\n `Content-Length: ${body.length}\\r\\nCache-Control: no-store\\r\\nX-Content-Type-Options: nosniff\\r\\nConnection: close\\r\\n\\r\\n`);\n sock.end(body);\n return false;\n }\n if (method !== \"GET\" || !upgrade || !key) {\n reply(\"426 Upgrade Required\", \"this port speaks WebSocket (OneLobby direct host)\");\n return false;\n }\n const accept = createHash(\"sha1\").update(key + WS_GUID).digest(\"base64\");\n sock.write(`HTTP/1.1 101 Switching Protocols\\r\\nUpgrade: websocket\\r\\nConnection: Upgrade\\r\\nSec-WebSocket-Accept: ${accept}\\r\\n\\r\\n`);\n return true;\n }\n\n private run(sock: Socket, ip: string, initial: Buffer): void {\n let open = true;\n const sendText = (text: string) => {\n if (open && !sock.destroyed) sock.write(frame(0x1, Buffer.from(text, \"utf8\")));\n };\n const shut = (code: number, reason: string) => {\n if (!open) return;\n open = false;\n if (!sock.destroyed) sock.end(closeFrame(code, reason));\n };\n let session: Session | null = null;\n session = this.hub.connect(ip, sendText, () => shut(1000, \"closed by the hub\"));\n if (!session) return;\n const s = session;\n const reader = new FrameReader(this.maxMessageBytes + 1024, {\n text: (t) => this.hub.receive(s, t),\n ping: (p) => !sock.destroyed && sock.write(frame(0xa, p)),\n close: () => {\n shut(1000, \"\");\n this.hub.disconnect(s);\n },\n fail: (code, why) => {\n shut(code, why);\n this.hub.disconnect(s);\n },\n });\n sock.on(\"data\", (b: Buffer) => reader.push(b));\n sock.on(\"close\", () => {\n open = false;\n this.hub.disconnect(s);\n });\n if (initial.length) reader.push(initial);\n }\n}\n\n// ------------------------------------------------------------------------------------------------ RFC 6455 framing\n\n/** One unmasked server frame (FIN set). */\nfunction frame(opcode: number, payload: Buffer): Buffer {\n const n = payload.length;\n const head = n < 126 ? Buffer.from([0x80 | opcode, n]) : n < 65536 ? Buffer.alloc(4) : Buffer.alloc(10);\n if (n >= 126) {\n head[0] = 0x80 | opcode;\n if (n < 65536) {\n head[1] = 126;\n head.writeUInt16BE(n, 2);\n } else {\n head[1] = 127;\n head.writeBigUInt64BE(BigInt(n), 2);\n }\n }\n return Buffer.concat([head, payload]);\n}\n\nfunction closeFrame(code: number, reason: string): Buffer {\n const r = Buffer.from(reason.slice(0, 100), \"utf8\");\n const p = Buffer.alloc(2 + r.length);\n p.writeUInt16BE(code, 0);\n r.copy(p, 2);\n return frame(0x8, p);\n}\n\ninterface FrameSink {\n text(t: string): void;\n ping(payload: Buffer): void;\n close(): void;\n fail(code: number, why: string): void;\n}\n\n/** Incremental client-frame parser: masked frames only, text messages (fragmented or not), control frames between. */\nclass FrameReader {\n private buf: Buffer = Buffer.alloc(0);\n private parts: Buffer[] = [];\n private partBytes = 0;\n private inMessage = false;\n private binary = false;\n private done = false;\n private readonly utf8 = new TextDecoder(\"utf-8\", { fatal: true });\n\n constructor(private readonly maxMessage: number, private readonly sink: FrameSink) {}\n\n push(chunk: Buffer): void {\n if (this.done) return;\n this.buf = this.buf.length ? Buffer.concat([this.buf, chunk]) : chunk;\n while (!this.done) {\n if (this.buf.length < 2) return;\n const b0 = this.buf[0];\n const b1 = this.buf[1];\n const fin = (b0 & 0x80) !== 0;\n const opcode = b0 & 0x0f;\n if (b0 & 0x70) return this.fail(1002, \"reserved bits set\");\n if (!(b1 & 0x80)) return this.fail(1002, \"client frames must be masked\");\n let len = b1 & 0x7f;\n let off = 2;\n if (len === 126) {\n if (this.buf.length < 4) return;\n len = this.buf.readUInt16BE(2);\n off = 4;\n } else if (len === 127) {\n if (this.buf.length < 10) return;\n const big = this.buf.readBigUInt64BE(2);\n if (big > BigInt(this.maxMessage)) return this.fail(1009, \"message too big\");\n len = Number(big);\n off = 10;\n }\n if (len > this.maxMessage) return this.fail(1009, \"message too big\");\n if (this.buf.length < off + 4 + len) return;\n const mask = this.buf.subarray(off, off + 4);\n const payload = Buffer.from(this.buf.subarray(off + 4, off + 4 + len));\n for (let i = 0; i < payload.length; i++) payload[i] ^= mask[i & 3];\n this.buf = this.buf.subarray(off + 4 + len);\n if (opcode >= 0x8) {\n if (!fin || len > 125) return this.fail(1002, \"bad control frame\");\n if (opcode === 0x8) {\n this.done = true;\n this.sink.close();\n return;\n }\n if (opcode === 0x9) this.sink.ping(payload);\n continue; // pong or unknown control: ignore\n }\n if (opcode === 0x1 || opcode === 0x2) {\n if (this.inMessage) return this.fail(1002, \"new message inside a fragmented one\");\n this.inMessage = true;\n this.binary = opcode === 0x2; // spec/signaling.md: binary frames are ignored\n } else if (opcode !== 0x0 || !this.inMessage) return this.fail(1002, \"unexpected continuation\");\n this.partBytes += payload.length;\n if (this.partBytes > this.maxMessage) return this.fail(1009, \"message too big\");\n if (!this.binary) this.parts.push(payload);\n if (fin) {\n const all = this.parts.length === 1 ? this.parts[0] : Buffer.concat(this.parts);\n const skip = this.binary;\n this.parts = [];\n this.partBytes = 0;\n this.inMessage = false;\n this.binary = false;\n if (skip) continue;\n let text: string;\n try {\n text = this.utf8.decode(all);\n } catch {\n return this.fail(1007, \"invalid UTF-8\");\n }\n this.sink.text(text);\n }\n }\n }\n\n private fail(code: number, why: string): void {\n this.done = true;\n this.sink.fail(code, why);\n }\n}\n\n// ------------------------------------------------------------------------------------------------ in-memory socket\n\n/**\n * The direct-IP host's own signaling socket (C#'s `HubSignalingSocket`): sends go straight into the hub; the hub's\n * replies are delivered on a microtask, so callbacks never run inside the hub.\n */\nexport class LocalHubSocket implements SocketLike {\n /** Address the hub sees for the host itself (rate limits and kicks are per address). */\n static readonly ADDRESS = \"local\";\n readyState = 0;\n onopen: SocketLike[\"onopen\"] = null;\n onmessage: SocketLike[\"onmessage\"] = null;\n onclose: SocketLike[\"onclose\"] = null;\n onerror: SocketLike[\"onerror\"] = null;\n private session: Session | null;\n\n constructor(private readonly hub: SignalingHub) {\n this.session = hub.connect(LocalHubSocket.ADDRESS, (text) => queueMicrotask(() => this.readyState === 1 && this.onmessage?.({ data: text })), () =>\n this.finish(),\n );\n queueMicrotask(() => {\n if (!this.session) {\n this.finish();\n return;\n }\n this.readyState = 1;\n this.onopen?.();\n });\n }\n\n send(data: string): void {\n if (this.readyState === 1 && this.session) this.hub.receive(this.session, data);\n }\n\n close(): void {\n const s = this.session;\n const wasOpen = this.readyState === 1;\n this.finish();\n if (wasOpen && s) this.hub.disconnect(s);\n }\n\n private finish(): void {\n if (this.readyState === 3) return;\n this.readyState = 3;\n queueMicrotask(() => this.onclose?.());\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"hub-host.js","sourceRoot":"","sources":["../../src/direct/hub-host.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference types=\"node\" />\nimport { createHash } from \"node:crypto\";\nimport { createSocket } from \"node:dgram\";\nimport { createServer, type Server, type Socket } from \"node:net\";\nimport type { SocketLike } from \"../transport.js\";\nimport { SignalingHub, directHubOptions, type HubDeps, type HubOptions, type Session } from \"./hub.js\";\nimport { PortMapper, type PortMapperOptions, type PortMapResult } from \"./portmap.js\";\n\nconst WS_GUID = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\";\nconst MAX_HEADER_BYTES = 8192;\nconst HANDSHAKE_TIMEOUT_MS = 5000;\nconst STATUS_TEXT: Record<number, string> = { 200: \"OK\", 301: \"Moved Permanently\", 302: \"Found\", 404: \"Not Found\", 500: \"Internal Server Error\" };\n\n/**\n * Rejects (EADDRINUSE) when another program holds IPv4 TCP or UDP `port`. Windows lets the dual-stack listener bind\n * over a program on 0.0.0.0 (every IPv4 joiner would then reach that program), and the peer links need UDP `port`\n * too, so both are tried and released before the real listen.\n */\nasync function probeIPv4(port: number): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const s = createServer();\n s.once(\"error\", reject);\n s.listen({ port, host: \"0.0.0.0\", exclusive: true }, () => s.close(() => resolve()));\n });\n await new Promise<void>((resolve, reject) => {\n const u = createSocket({ type: \"udp4\" });\n u.once(\"error\", (e) => {\n u.close();\n reject(e);\n });\n u.bind({ port, address: \"0.0.0.0\", exclusive: true }, () => u.close(() => resolve()));\n });\n}\n\nexport class PortInUseError extends Error {\n readonly code = \"port_in_use\";\n constructor(port: number, detail: string) {\n super(`port_in_use: cannot listen on port ${port} (${detail}): is another copy of the game hosting?`);\n }\n}\n\n/**\n * Runs a {@link SignalingHub} inside a Node process and listens for joiners' signaling WebSockets on TCP `port`: the\n * Node twin of C#'s `DirectHubHost` (docs/direct-ip.md s.2, s.5 step 3). A plain `net` server with a minimal HTTP/1.1\n * upgrade and RFC 6455 framing (text, close, ping/pong, fragments; client frames must be masked), so the package keeps\n * zero runtime dependencies. Any path upgrades; `GET /healthz` answers `ok`. The host's own client talks to the hub in\n * memory through {@link connectLocal}. `hub.tick()` runs once a second on an unref'd timer.\n */\n/** One plain-HTTP answer from the host's port, e.g. a browser join page. */\nexport interface HttpReply {\n /** Default 200. */\n status?: number;\n /** Content-Type, e.g. \"text/html; charset=utf-8\". */\n type: string;\n body: string | Uint8Array;\n}\n\n/** Serves plain `GET` requests on the direct port (path without the query); null answers 404. */\nexport type HttpHandler = (path: string) => HttpReply | null;\n\nexport class DirectHubHost {\n readonly hub: SignalingHub;\n /**\n * Plain `GET`s that are not WebSocket upgrades go here, so the host can serve its own join page: a browser opening\n * `http://<host address>:<port>/` gets a page that joins with `kit.joinDirect(location.host)`. Null (default): 426.\n */\n http: HttpHandler | null = null;\n /** The bound TCP port (the requested one, or the one the OS picked for 0). */\n readonly port: number;\n portMapper: PortMapper | null = null;\n private readonly server: Server;\n private readonly sockets = new Set<Socket>();\n private readonly ticker: ReturnType<typeof setInterval>;\n private readonly maxMessageBytes: number;\n private closed = false;\n\n private constructor(server: Server, port: number, hub: SignalingHub, maxMessageBytes: number) {\n this.server = server;\n this.port = port;\n this.hub = hub;\n this.maxMessageBytes = maxMessageBytes;\n this.ticker = setInterval(() => this.hub.tick(), 1000);\n this.ticker.unref?.();\n server.on(\"connection\", (sock) => this.accept(sock));\n }\n\n /**\n * Binds TCP `port` on every interface (IPv6 dual-stack where available). Hub options default to\n * {@link directHubOptions} (one lobby, no codes, no ICE servers). Rejects with {@link PortInUseError}.\n */\n static async listen(port: number, options?: Partial<HubOptions>, deps?: HubDeps & { host?: string }): Promise<DirectHubHost> {\n const opts = { ...directHubOptions(8), ...options };\n const server = createServer({ noDelay: true });\n const tryListen = (host: string, ipv6Only?: boolean) =>\n new Promise<void>((resolve, reject) => {\n const onError = (e: NodeJS.ErrnoException) => reject(e);\n server.once(\"error\", onError);\n server.listen({ port, host, ipv6Only }, () => {\n server.off(\"error\", onError);\n resolve();\n });\n });\n try {\n if (deps?.host) await tryListen(deps.host);\n else {\n if (port !== 0) await probeIPv4(port);\n try {\n await tryListen(\"::\", false);\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"EADDRINUSE\") throw e;\n await tryListen(\"0.0.0.0\"); // no IPv6 on this machine\n }\n }\n } catch (e) {\n throw new PortInUseError(port, (e as NodeJS.ErrnoException).code ?? String(e));\n }\n const addr = server.address();\n const bound = typeof addr === \"object\" && addr ? addr.port : port;\n return new DirectHubHost(server, bound, new SignalingHub(opts, deps), opts.maxMessageBytes);\n }\n\n /** Joiner sockets currently open. */\n get connectionCount(): number {\n return this.sockets.size;\n }\n\n /**\n * The host's own signaling socket: a WebSocket look-alike wired straight into the hub (no network). Pass it to the\n * kit as `advanced.socketFactory: () => host.connectLocal()`.\n */\n connectLocal(): SocketLike {\n return new LocalHubSocket(this.hub);\n }\n\n /** Asks the home router to forward TCP and UDP `port` here (UPnP, then NAT-PMP; docs/direct-ip.md s.2a). */\n openPort(options?: PortMapperOptions): Promise<PortMapResult> {\n if (this.portMapper) throw new Error(\"the port is already being opened\");\n this.portMapper = new PortMapper(this.port, options);\n return this.portMapper.open();\n }\n\n /** Stops listening, closes every joiner socket and removes the router mapping. Every call gets the same promise. */\n close(): Promise<void> {\n return (this.closing ??= this.closeOnce());\n }\n\n private closing?: Promise<void>;\n\n private async closeOnce(): Promise<void> {\n this.closed = true;\n clearInterval(this.ticker);\n const unmap = this.portMapper?.close();\n for (const s of this.sockets) s.end(closeFrame(1001, \"host left\"));\n await new Promise<void>((resolve) => this.server.close(() => resolve()));\n setTimeout(() => { for (const s of this.sockets) s.destroy(); }, 300).unref?.();\n await unmap;\n }\n\n // ---------------------------------------------------------------- one TCP connection\n\n private accept(sock: Socket): void {\n if (this.closed) {\n sock.destroy();\n return;\n }\n this.sockets.add(sock);\n sock.on(\"close\", () => this.sockets.delete(sock));\n sock.on(\"error\", () => {});\n const ip = (sock.remoteAddress ?? \"\").replace(/^::ffff:/, \"\");\n let head = Buffer.alloc(0);\n const timer = setTimeout(() => sock.destroy(), HANDSHAKE_TIMEOUT_MS);\n const onData = (chunk: Buffer) => {\n head = Buffer.concat([head, chunk]);\n const end = head.indexOf(\"\\r\\n\\r\\n\");\n if (end < 0) {\n if (head.length > MAX_HEADER_BYTES) sock.destroy();\n return;\n }\n clearTimeout(timer);\n sock.off(\"data\", onData);\n const rest = head.subarray(end + 4);\n if (!this.handshake(sock, head.subarray(0, end).toString(\"latin1\"))) return;\n this.run(sock, ip, rest);\n };\n sock.on(\"data\", onData);\n }\n\n /** RFC 6455 s.4.2: answers 101 with Sec-WebSocket-Accept, or 200 for /healthz, or 400/426. */\n private handshake(sock: Socket, head: string): boolean {\n const [requestLine, ...lines] = head.split(\"\\r\\n\");\n const headers = new Map<string, string>();\n for (const l of lines) {\n const i = l.indexOf(\":\");\n if (i > 0) headers.set(l.slice(0, i).trim().toLowerCase(), l.slice(i + 1).trim());\n }\n const [method, path] = requestLine.split(\" \");\n const reply = (status: string, body: string) =>\n sock.end(`HTTP/1.1 ${status}\\r\\nContent-Type: text/plain\\r\\nContent-Length: ${Buffer.byteLength(body)}\\r\\nConnection: close\\r\\n\\r\\n${body}`);\n if (method === \"GET\" && path === \"/healthz\") {\n reply(\"200 OK\", \"ok\");\n return false;\n }\n const key = headers.get(\"sec-websocket-key\");\n const upgrade = /websocket/i.test(headers.get(\"upgrade\") ?? \"\");\n if (method === \"GET\" && !upgrade && this.http && path) {\n let page: HttpReply | null = null;\n try {\n page = this.http(path.split(\"?\")[0]);\n } catch {\n page = { status: 500, type: \"text/plain\", body: \"error\" };\n }\n const p = page ?? { status: 404, type: \"text/plain\", body: \"not found\" };\n const body = typeof p.body === \"string\" ? Buffer.from(p.body, \"utf8\") : Buffer.from(p.body);\n const status = p.status ?? 200;\n sock.write(`HTTP/1.1 ${status} ${STATUS_TEXT[status] ?? \"OK\"}\\r\\nContent-Type: ${p.type}\\r\\n` +\n `Content-Length: ${body.length}\\r\\nCache-Control: no-store\\r\\nX-Content-Type-Options: nosniff\\r\\nConnection: close\\r\\n\\r\\n`);\n sock.end(body);\n return false;\n }\n if (method !== \"GET\" || !upgrade || !key) {\n reply(\"426 Upgrade Required\", \"this port speaks WebSocket (OneLobby direct host)\");\n return false;\n }\n const accept = createHash(\"sha1\").update(key + WS_GUID).digest(\"base64\");\n sock.write(`HTTP/1.1 101 Switching Protocols\\r\\nUpgrade: websocket\\r\\nConnection: Upgrade\\r\\nSec-WebSocket-Accept: ${accept}\\r\\n\\r\\n`);\n return true;\n }\n\n private run(sock: Socket, ip: string, initial: Buffer): void {\n let open = true;\n const sendText = (text: string) => {\n if (open && !sock.destroyed) sock.write(frame(0x1, Buffer.from(text, \"utf8\")));\n };\n const shut = (code: number, reason: string) => {\n if (!open) return;\n open = false;\n if (!sock.destroyed) sock.end(closeFrame(code, reason));\n };\n let session: Session | null = null;\n session = this.hub.connect(ip, sendText, () => shut(1000, \"closed by the hub\"));\n if (!session) return;\n const s = session;\n const reader = new FrameReader(this.maxMessageBytes + 1024, {\n text: (t) => this.hub.receive(s, t),\n ping: (p) => !sock.destroyed && sock.write(frame(0xa, p)),\n close: () => {\n shut(1000, \"\");\n this.hub.disconnect(s);\n },\n fail: (code, why) => {\n shut(code, why);\n this.hub.disconnect(s);\n },\n });\n sock.on(\"data\", (b: Buffer) => reader.push(b));\n sock.on(\"close\", () => {\n open = false;\n this.hub.disconnect(s);\n });\n if (initial.length) reader.push(initial);\n }\n}\n\n// ------------------------------------------------------------------------------------------------ RFC 6455 framing\n\n/** One unmasked server frame (FIN set). */\nfunction frame(opcode: number, payload: Buffer): Buffer {\n const n = payload.length;\n const head = n < 126 ? Buffer.from([0x80 | opcode, n]) : n < 65536 ? Buffer.alloc(4) : Buffer.alloc(10);\n if (n >= 126) {\n head[0] = 0x80 | opcode;\n if (n < 65536) {\n head[1] = 126;\n head.writeUInt16BE(n, 2);\n } else {\n head[1] = 127;\n head.writeBigUInt64BE(BigInt(n), 2);\n }\n }\n return Buffer.concat([head, payload]);\n}\n\nfunction closeFrame(code: number, reason: string): Buffer {\n const r = Buffer.from(reason.slice(0, 100), \"utf8\");\n const p = Buffer.alloc(2 + r.length);\n p.writeUInt16BE(code, 0);\n r.copy(p, 2);\n return frame(0x8, p);\n}\n\ninterface FrameSink {\n text(t: string): void;\n ping(payload: Buffer): void;\n close(): void;\n fail(code: number, why: string): void;\n}\n\n/** Incremental client-frame parser: masked frames only, text messages (fragmented or not), control frames between. */\nclass FrameReader {\n private buf: Buffer = Buffer.alloc(0);\n private parts: Buffer[] = [];\n private partBytes = 0;\n private inMessage = false;\n private binary = false;\n private done = false;\n private readonly utf8 = new TextDecoder(\"utf-8\", { fatal: true });\n\n constructor(private readonly maxMessage: number, private readonly sink: FrameSink) {}\n\n push(chunk: Buffer): void {\n if (this.done) return;\n this.buf = this.buf.length ? Buffer.concat([this.buf, chunk]) : chunk;\n while (!this.done) {\n if (this.buf.length < 2) return;\n const b0 = this.buf[0];\n const b1 = this.buf[1];\n const fin = (b0 & 0x80) !== 0;\n const opcode = b0 & 0x0f;\n if (b0 & 0x70) return this.fail(1002, \"reserved bits set\");\n if (!(b1 & 0x80)) return this.fail(1002, \"client frames must be masked\");\n let len = b1 & 0x7f;\n let off = 2;\n if (len === 126) {\n if (this.buf.length < 4) return;\n len = this.buf.readUInt16BE(2);\n off = 4;\n } else if (len === 127) {\n if (this.buf.length < 10) return;\n const big = this.buf.readBigUInt64BE(2);\n if (big > BigInt(this.maxMessage)) return this.fail(1009, \"message too big\");\n len = Number(big);\n off = 10;\n }\n if (len > this.maxMessage) return this.fail(1009, \"message too big\");\n if (this.buf.length < off + 4 + len) return;\n const mask = this.buf.subarray(off, off + 4);\n const payload = Buffer.from(this.buf.subarray(off + 4, off + 4 + len));\n for (let i = 0; i < payload.length; i++) payload[i] ^= mask[i & 3];\n this.buf = this.buf.subarray(off + 4 + len);\n if (opcode >= 0x8) {\n if (!fin || len > 125) return this.fail(1002, \"bad control frame\");\n if (opcode === 0x8) {\n this.done = true;\n this.sink.close();\n return;\n }\n if (opcode === 0x9) this.sink.ping(payload);\n continue; // pong or unknown control: ignore\n }\n if (opcode === 0x1 || opcode === 0x2) {\n if (this.inMessage) return this.fail(1002, \"new message inside a fragmented one\");\n this.inMessage = true;\n this.binary = opcode === 0x2; // spec/signaling.md: binary frames are ignored\n } else if (opcode !== 0x0 || !this.inMessage) return this.fail(1002, \"unexpected continuation\");\n this.partBytes += payload.length;\n if (this.partBytes > this.maxMessage) return this.fail(1009, \"message too big\");\n if (!this.binary) this.parts.push(payload);\n if (fin) {\n const all = this.parts.length === 1 ? this.parts[0] : Buffer.concat(this.parts);\n const skip = this.binary;\n this.parts = [];\n this.partBytes = 0;\n this.inMessage = false;\n this.binary = false;\n if (skip) continue;\n let text: string;\n try {\n text = this.utf8.decode(all);\n } catch {\n return this.fail(1007, \"invalid UTF-8\");\n }\n this.sink.text(text);\n }\n }\n }\n\n private fail(code: number, why: string): void {\n this.done = true;\n this.sink.fail(code, why);\n }\n}\n\n// ------------------------------------------------------------------------------------------------ in-memory socket\n\n/**\n * The direct-IP host's own signaling socket (C#'s `HubSignalingSocket`): sends go straight into the hub; the hub's\n * replies are delivered on a microtask, so callbacks never run inside the hub.\n */\nexport class LocalHubSocket implements SocketLike {\n /** Address the hub sees for the host itself (rate limits and kicks are per address). */\n static readonly ADDRESS = \"local\";\n readyState = 0;\n onopen: SocketLike[\"onopen\"] = null;\n onmessage: SocketLike[\"onmessage\"] = null;\n onclose: SocketLike[\"onclose\"] = null;\n onerror: SocketLike[\"onerror\"] = null;\n private session: Session | null;\n\n constructor(private readonly hub: SignalingHub) {\n this.session = hub.connect(LocalHubSocket.ADDRESS, (text) => queueMicrotask(() => this.readyState === 1 && this.onmessage?.({ data: text })), () =>\n this.finish(),\n );\n queueMicrotask(() => {\n if (!this.session) {\n this.finish();\n return;\n }\n this.readyState = 1;\n this.onopen?.();\n });\n }\n\n send(data: string): void {\n if (this.readyState === 1 && this.session) this.hub.receive(this.session, data);\n }\n\n close(): void {\n const s = this.session;\n const wasOpen = this.readyState === 1;\n this.finish();\n if (wasOpen && s) this.hub.disconnect(s);\n }\n\n private finish(): void {\n if (this.readyState === 3) return;\n this.readyState = 3;\n queueMicrotask(() => this.onclose?.());\n }\n}\n"]}
|
package/dist/direct/index.js
CHANGED
|
@@ -58,6 +58,17 @@ export async function hostDirect(kit, o = {}) {
|
|
|
58
58
|
throw e;
|
|
59
59
|
}
|
|
60
60
|
lobby.on("sessionEnded", () => void hub.close());
|
|
61
|
+
// leave() resolves once the router mapping is deleted, so a host that exits right after it does not leave the port
|
|
62
|
+
// forwarded until the lease runs out (2 h, or never on routers that only grant permanent leases)
|
|
63
|
+
const leave = lobby.leave.bind(lobby);
|
|
64
|
+
lobby.leave = async () => {
|
|
65
|
+
try {
|
|
66
|
+
await leave();
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
await hub.close();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
61
72
|
const map = await mapping;
|
|
62
73
|
lobby.directAddresses = localAddresses(hub.port);
|
|
63
74
|
lobby.portMapping = map ? map.detail : "off";
|
package/dist/direct/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/direct/index.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference types=\"node\" />\n/**\n * `onelobby/direct` (Node only): host a direct-IP lobby from a Node process, with no server of anyone's\n * (docs/direct-ip.md). The process runs the signaling hub, listens on TCP `port`, binds its peer links to UDP `port`,\n * asks the router to forward both (UPnP, then NAT-PMP) and hands out a join code. Joiners use `kit.joinDirect(code)`\n * from `onelobby` (Node or browser) or `JoinDirectAsync` in C#; the wire protocol is the same, so they mix.\n *\n * WebRTC in Node needs an RTCPeerConnection: `npm install node-datachannel`, then\n * `advanced: { linkFactory: webRtcLinkFactoryFor(RTCPeerConnection) }` with\n * `import { RTCPeerConnection } from \"node-datachannel/polyfill\"`. Its libdatachannel honours the port pin.\n */\nimport type { HostOptions, OneLobby } from \"../onelobby.js\";\nimport type { Lobby, RateLimit } from \"../lobby.js\";\nimport type { LinkFactory } from \"../transport.js\";\nimport { encodeJoinCode } from \"../joincode.js\";\nimport { DEFAULT_DIRECT_PORT } from \"../direct-address.js\";\nimport { DirectHubHost, PortInUseError, type HttpHandler } from \"./hub-host.js\";\nimport { directHubOptions } from \"./hub.js\";\nimport { isPublicIPv4, lanIPv4, localAddresses, type PortMapResult, type PortMapperOptions } from \"./portmap.js\";\nimport { OneLobbyError } from \"../signaling.js\";\n\nexport { DirectHubHost, LocalHubSocket, PortInUseError } from \"./hub-host.js\";\nexport type { HttpHandler, HttpReply } from \"./hub-host.js\";\nexport { SignalingHub, Session, RateWindow, sanitize, defaultHubOptions, directHubOptions, PROTO } from \"./hub.js\";\nexport type { HubOptions, HubDeps } from \"./hub.js\";\nexport { PortMapper, isPublicIPv4, lanIPv4, localAddresses } from \"./portmap.js\";\nexport type { PortMapResult, PortMapperOptions, PortMapMethod } from \"./portmap.js\";\nexport { webRtcLinkFactoryFor } from \"../webrtc.js\";\n\nexport interface DirectHostOptions {\n /**\n * TCP and UDP port to listen on (default 47810). When it is busy (another program holds it) the next free one of the\n * following `fallbackPorts` is used; the join code carries the port, so friends never type it.\n */\n port?: number;\n /** How many ports after `port` to try when it is busy; default 9 with `port` left out, else 0. */\n fallbackPorts?: number;\n /** Optional; joiners must send the same one or get `bad_password`. */\n password?: string;\n maxPlayers?: number;\n /** Lobby name; default \"<displayName>'s game\". */\n name?: string;\n lateJoin?: boolean;\n meta?: Record<string, string>;\n /** Ask the router to forward the port so the code works from the internet (s.2a). The host-menu toggle; default on. */\n openPort?: boolean;\n /** Test seam and knobs for the router request (description defaults to \"OneLobby <gameId>\"). */\n portMapping?: PortMapperOptions;\n /**\n * Serves plain-HTTP `GET`s on the same port, typically a join page for browsers (recipe s.4b). The page joins with\n * `kit.joinDirect(location.host)`; the lobby's `joinUrl` is then the link to share.\n */\n http?: HttpHandler;\n /** Per-joiner cap on incoming raw messages, e.g. `{ messages: 60, perMs: 1000 }`; the excess is dropped. */\n rateLimit?: RateLimit;\n}\n\n/** A direct host lobby: the normal Lobby, plus the hub behind it. Leaving or ending the lobby stops the hub. */\nexport type DirectLobby = Lobby & {\n directHub: DirectHubHost;\n /** With the `http` option: `http://<the code's address>:<port>/`, the link a browser opens to join. Else null. */\n joinUrl: string | null;\n};\n\n/**\n * Hosts a direct-IP lobby (docs/direct-ip.md s.3): starts the hub and listener on `port`, opens the router port\n * (unless `openPort: false`), connects `kit` to the hub in memory and creates the lobby. Resolves with the lobby;\n * show `lobby.joinCode` with `lobby.joinCodeKind` (\"internet\" or \"same_network\"). Rejects with `port_in_use`.\n * When the lobby ends (leave, or the process exits) the listener closes and the router mapping is removed; joiners\n * get `sessionEnded(\"host_left\")`.\n */\n/** With `port` left out, how many ports after the default `hostDirect` tries when it is busy. */\nexport const DEFAULT_FALLBACK_PORTS = 9;\n\nexport async function hostDirect(kit: OneLobby, o: DirectHostOptions = {}): Promise<DirectLobby> {\n const port = o.port ?? DEFAULT_DIRECT_PORT;\n // busy (another program holds it): try the next fallbackPorts; the join code carries the port\n const last = port + Math.max(0, o.fallbackPorts ?? (o.port === undefined ? DEFAULT_FALLBACK_PORTS : 0));\n const maxPlayers = o.maxPlayers ?? 4;\n let hub: DirectHubHost | undefined;\n for (let p = port; !hub; p++) {\n try {\n hub = await DirectHubHost.listen(p, directHubOptions(maxPlayers, o.password));\n } catch (e) {\n if (!(e instanceof PortInUseError)) throw e;\n if (p < last) continue;\n const range = last > port ? `ports ${port}-${last}` : `port ${port}`;\n throw new OneLobbyError(\"port_in_use\", `cannot listen on ${range}: is another copy of the game hosting?`);\n }\n }\n hub.http = o.http ?? null;\n // The router request runs alongside lobby creation; it takes up to a few seconds when nothing answers.\n const mapping: Promise<PortMapResult | null> =\n o.openPort === false\n ? Promise.resolve(null)\n : hub.openPort({ description: `OneLobby ${kit.options.gameId}`, ...o.portMapping }).catch((e: Error) => ({\n mapped: false, method: \"none\" as const, externalIPv4: null, localIPv4: null, detail: e.message,\n }));\n const wrapLinks = (inner: LinkFactory): LinkFactory => ({\n create: (lo) => inner.create({ ...lo, iceServers: [], portRange: { begin: hub.port, end: hub.port } }),\n });\n let lobby: Lobby;\n try {\n const name = o.name ?? `${kit.options.displayName || \"Player\"}'s game`;\n const h: HostOptions = { name, maxPlayers, lateJoin: o.lateJoin ?? true, meta: o.meta, rateLimit: o.rateLimit };\n lobby = await kit.hostDirectWith(h, { socketFactory: () => hub.connectLocal(), wrapLinks });\n } catch (e) {\n await mapping;\n await hub.close();\n throw e;\n }\n lobby.on(\"sessionEnded\", () => void hub.close());\n const map = await mapping;\n lobby.directAddresses = localAddresses(hub.port);\n lobby.portMapping = map ? map.detail : \"off\";\n let codeIp: string | null = null;\n if (map?.mapped && isPublicIPv4(map.externalIPv4)) {\n codeIp = map.externalIPv4!;\n lobby.joinCode = encodeJoinCode(codeIp, hub.port);\n lobby.joinCodeKind = \"internet\";\n } else {\n codeIp = map?.localIPv4 ?? (await lanIPv4());\n if (codeIp) {\n lobby.joinCode = encodeJoinCode(codeIp, hub.port);\n lobby.joinCodeKind = \"same_network\";\n }\n }\n const joinUrl = o.http && codeIp ? `http://${codeIp}:${hub.port}/` : null;\n return Object.assign(lobby, { directHub: hub, joinUrl });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/direct/index.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference types=\"node\" />\n/**\n * `onelobby/direct` (Node only): host a direct-IP lobby from a Node process, with no server of anyone's\n * (docs/direct-ip.md). The process runs the signaling hub, listens on TCP `port`, binds its peer links to UDP `port`,\n * asks the router to forward both (UPnP, then NAT-PMP) and hands out a join code. Joiners use `kit.joinDirect(code)`\n * from `onelobby` (Node or browser) or `JoinDirectAsync` in C#; the wire protocol is the same, so they mix.\n *\n * WebRTC in Node needs an RTCPeerConnection: `npm install node-datachannel`, then\n * `advanced: { linkFactory: webRtcLinkFactoryFor(RTCPeerConnection) }` with\n * `import { RTCPeerConnection } from \"node-datachannel/polyfill\"`. Its libdatachannel honours the port pin.\n */\nimport type { HostOptions, OneLobby } from \"../onelobby.js\";\nimport type { Lobby, RateLimit } from \"../lobby.js\";\nimport type { LinkFactory } from \"../transport.js\";\nimport { encodeJoinCode } from \"../joincode.js\";\nimport { DEFAULT_DIRECT_PORT } from \"../direct-address.js\";\nimport { DirectHubHost, PortInUseError, type HttpHandler } from \"./hub-host.js\";\nimport { directHubOptions } from \"./hub.js\";\nimport { isPublicIPv4, lanIPv4, localAddresses, type PortMapResult, type PortMapperOptions } from \"./portmap.js\";\nimport { OneLobbyError } from \"../signaling.js\";\n\nexport { DirectHubHost, LocalHubSocket, PortInUseError } from \"./hub-host.js\";\nexport type { HttpHandler, HttpReply } from \"./hub-host.js\";\nexport { SignalingHub, Session, RateWindow, sanitize, defaultHubOptions, directHubOptions, PROTO } from \"./hub.js\";\nexport type { HubOptions, HubDeps } from \"./hub.js\";\nexport { PortMapper, isPublicIPv4, lanIPv4, localAddresses } from \"./portmap.js\";\nexport type { PortMapResult, PortMapperOptions, PortMapMethod } from \"./portmap.js\";\nexport { webRtcLinkFactoryFor } from \"../webrtc.js\";\n\nexport interface DirectHostOptions {\n /**\n * TCP and UDP port to listen on (default 47810). When it is busy (another program holds it) the next free one of the\n * following `fallbackPorts` is used; the join code carries the port, so friends never type it.\n */\n port?: number;\n /** How many ports after `port` to try when it is busy; default 9 with `port` left out, else 0. */\n fallbackPorts?: number;\n /** Optional; joiners must send the same one or get `bad_password`. */\n password?: string;\n maxPlayers?: number;\n /** Lobby name; default \"<displayName>'s game\". */\n name?: string;\n lateJoin?: boolean;\n meta?: Record<string, string>;\n /** Ask the router to forward the port so the code works from the internet (s.2a). The host-menu toggle; default on. */\n openPort?: boolean;\n /** Test seam and knobs for the router request (description defaults to \"OneLobby <gameId>\"). */\n portMapping?: PortMapperOptions;\n /**\n * Serves plain-HTTP `GET`s on the same port, typically a join page for browsers (recipe s.4b). The page joins with\n * `kit.joinDirect(location.host)`; the lobby's `joinUrl` is then the link to share.\n */\n http?: HttpHandler;\n /** Per-joiner cap on incoming raw messages, e.g. `{ messages: 60, perMs: 1000 }`; the excess is dropped. */\n rateLimit?: RateLimit;\n}\n\n/** A direct host lobby: the normal Lobby, plus the hub behind it. Leaving or ending the lobby stops the hub. */\nexport type DirectLobby = Lobby & {\n directHub: DirectHubHost;\n /** With the `http` option: `http://<the code's address>:<port>/`, the link a browser opens to join. Else null. */\n joinUrl: string | null;\n};\n\n/**\n * Hosts a direct-IP lobby (docs/direct-ip.md s.3): starts the hub and listener on `port`, opens the router port\n * (unless `openPort: false`), connects `kit` to the hub in memory and creates the lobby. Resolves with the lobby;\n * show `lobby.joinCode` with `lobby.joinCodeKind` (\"internet\" or \"same_network\"). Rejects with `port_in_use`.\n * When the lobby ends (leave, or the process exits) the listener closes and the router mapping is removed; joiners\n * get `sessionEnded(\"host_left\")`.\n */\n/** With `port` left out, how many ports after the default `hostDirect` tries when it is busy. */\nexport const DEFAULT_FALLBACK_PORTS = 9;\n\nexport async function hostDirect(kit: OneLobby, o: DirectHostOptions = {}): Promise<DirectLobby> {\n const port = o.port ?? DEFAULT_DIRECT_PORT;\n // busy (another program holds it): try the next fallbackPorts; the join code carries the port\n const last = port + Math.max(0, o.fallbackPorts ?? (o.port === undefined ? DEFAULT_FALLBACK_PORTS : 0));\n const maxPlayers = o.maxPlayers ?? 4;\n let hub: DirectHubHost | undefined;\n for (let p = port; !hub; p++) {\n try {\n hub = await DirectHubHost.listen(p, directHubOptions(maxPlayers, o.password));\n } catch (e) {\n if (!(e instanceof PortInUseError)) throw e;\n if (p < last) continue;\n const range = last > port ? `ports ${port}-${last}` : `port ${port}`;\n throw new OneLobbyError(\"port_in_use\", `cannot listen on ${range}: is another copy of the game hosting?`);\n }\n }\n hub.http = o.http ?? null;\n // The router request runs alongside lobby creation; it takes up to a few seconds when nothing answers.\n const mapping: Promise<PortMapResult | null> =\n o.openPort === false\n ? Promise.resolve(null)\n : hub.openPort({ description: `OneLobby ${kit.options.gameId}`, ...o.portMapping }).catch((e: Error) => ({\n mapped: false, method: \"none\" as const, externalIPv4: null, localIPv4: null, detail: e.message,\n }));\n const wrapLinks = (inner: LinkFactory): LinkFactory => ({\n create: (lo) => inner.create({ ...lo, iceServers: [], portRange: { begin: hub.port, end: hub.port } }),\n });\n let lobby: Lobby;\n try {\n const name = o.name ?? `${kit.options.displayName || \"Player\"}'s game`;\n const h: HostOptions = { name, maxPlayers, lateJoin: o.lateJoin ?? true, meta: o.meta, rateLimit: o.rateLimit };\n lobby = await kit.hostDirectWith(h, { socketFactory: () => hub.connectLocal(), wrapLinks });\n } catch (e) {\n await mapping;\n await hub.close();\n throw e;\n }\n lobby.on(\"sessionEnded\", () => void hub.close());\n // leave() resolves once the router mapping is deleted, so a host that exits right after it does not leave the port\n // forwarded until the lease runs out (2 h, or never on routers that only grant permanent leases)\n const leave = lobby.leave.bind(lobby);\n lobby.leave = async () => {\n try {\n await leave();\n } finally {\n await hub.close();\n }\n };\n const map = await mapping;\n lobby.directAddresses = localAddresses(hub.port);\n lobby.portMapping = map ? map.detail : \"off\";\n let codeIp: string | null = null;\n if (map?.mapped && isPublicIPv4(map.externalIPv4)) {\n codeIp = map.externalIPv4!;\n lobby.joinCode = encodeJoinCode(codeIp, hub.port);\n lobby.joinCodeKind = \"internet\";\n } else {\n codeIp = map?.localIPv4 ?? (await lanIPv4());\n if (codeIp) {\n lobby.joinCode = encodeJoinCode(codeIp, hub.port);\n lobby.joinCodeKind = \"same_network\";\n }\n }\n const joinUrl = o.http && codeIp ? `http://${codeIp}:${hub.port}/` : null;\n return Object.assign(lobby, { directHub: hub, joinUrl });\n}\n"]}
|
package/dist/onelobby.bundle.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! onelobby 0.3.
|
|
1
|
+
/*! onelobby 0.3.1 | MIT license */
|
|
2
2
|
function Ge(r,e){if(typeof Worker>"u"||typeof Blob>"u"||typeof URL>"u"||!URL.createObjectURL)return null;try{let t="let h=0;onmessage=e=>{if(e.data&&e.data.start){h=setInterval(()=>postMessage(0),e.data.ms)}else{clearInterval(h);close()}}",s=URL.createObjectURL(new Blob([t],{type:"text/javascript"})),i=new Worker(s);return URL.revokeObjectURL(s),i.onmessage=()=>r(),i.postMessage({start:!0,ms:e}),()=>{i.postMessage({stop:!0}),i.terminate()}}catch{return null}}var he={now:()=>typeof performance<"u"?performance.now():Date.now(),setInterval(r,e){let t=Ge(r,e);if(t)return t;let s=setInterval(r,e);return()=>clearInterval(s)},setTimeout(r,e){let t=setTimeout(r,e);return()=>clearTimeout(t)}};var p=class{handlers=new Map;on(e,t){let s=this.handlers.get(e);return s||this.handlers.set(e,s=[]),s.push(t),()=>this.off(e,t)}off(e,t){let s=this.handlers.get(e);if(!s)return;let i=s.indexOf(t);i>=0&&s.splice(i,1)}emit(e,...t){let s=this.handlers.get(e);if(s)for(let i of[...s])try{i(...t)}catch(n){queueMicrotask(()=>{throw n})}}};var k="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function Xe(r){return r.replace(/[a-z]/g,e=>e.toUpperCase())}function m(r){let e=Xe(r).replace(/[ \-]/g,"").replace(/[IL]/g,"1").replace(/O/g,"0");if(e.length!==6)return null;for(let t of e)if(k.indexOf(t)<0)return null;return e}function y(r){let e=m(r)??r.toUpperCase();return e.length===6?`${e.slice(0,3)} ${e.slice(3)}`:e}var J={hi:1,welcome_dc:2,cmd:16,frame:17,hash:18,desync:19,snapshot_req:20,snapshot:21,chunk:22,raw:32,ping:48,pong:49,host_claim:64,voice:80,chat:96,chat_history:97,chat_reject:98},pe=1,fe=2,f=class extends Error{constructor(t,s=""){super(s?`${t}: ${s}`:t);this.code=t;this.name="CodecError"}code},de=new TextEncoder,Ze=new TextDecoder("utf-8",{fatal:!0});function ue(r){try{return Ze.decode(r)}catch{throw new f("bad_utf8")}}var W=class{buf;dv;pos=0;constructor(e){this.buf=new Uint8Array(e),this.dv=new DataView(this.buf.buffer)}ensure(e){if(this.pos+e<=this.buf.length)return;let t=new Uint8Array(Math.max(this.buf.length*2,this.pos+e));t.set(this.buf),this.buf=t,this.dv=new DataView(t.buffer)}u8(e){this.ensure(1),this.dv.setUint8(this.pos,e),this.pos+=1}u16(e){this.ensure(2),this.dv.setUint16(this.pos,e,!0),this.pos+=2}u32(e){this.ensure(4),this.dv.setUint32(this.pos,e>>>0,!0),this.pos+=4}i64(e){this.ensure(8),this.dv.setBigInt64(this.pos,BigInt(e),!0),this.pos+=8}u64(e){this.ensure(8),this.dv.setBigUint64(this.pos,BigInt.asUintN(64,e),!0),this.pos+=8}bytes(e){this.ensure(e.length),this.buf.set(e,this.pos),this.pos+=e.length}done(){return this.buf.slice(0,this.pos)}},z=class{constructor(e){this.buf=e;this.dv=new DataView(e.buffer,e.byteOffset,e.byteLength)}buf;dv;pos=0;need(e){if(this.pos+e>this.buf.length)throw new f("too_short","truncated frame")}u8(){this.need(1);let e=this.dv.getUint8(this.pos);return this.pos+=1,e}u16(){this.need(2);let e=this.dv.getUint16(this.pos,!0);return this.pos+=2,e}u32(){this.need(4);let e=this.dv.getUint32(this.pos,!0);return this.pos+=4,e}i64(){this.need(8);let e=this.dv.getBigInt64(this.pos,!0);return this.pos+=8,Number(e)}u64(){this.need(8);let e=this.dv.getBigUint64(this.pos,!0);return this.pos+=8,e}bytes(e){this.need(e);let t=this.buf.slice(this.pos,this.pos+e);return this.pos+=e,t}rest(){let e=this.buf.slice(this.pos);return this.pos=this.buf.length,e}};function v(r,e=0,t=!1){let s=new W(64);switch(s.u8(J[r.type]),s.u16(e),r.type){case"hi":s.u8(r.proto),s.u16(r.peerId),s.i64(r.lastTick);break;case"welcome_dc":s.i64(r.tick),s.u8(r.tickRate),s.u8(r.mode),s.u16(r.hostId);break;case"cmd":s.u32(r.seq),s.u16(r.bytes.length),s.bytes(r.bytes);break;case"frame":s.i64(r.tick),s.u16(r.commands.length);for(let n of r.commands)s.u16(n.peerId),s.u16(n.bytes.length),s.bytes(n.bytes);break;case"hash":s.i64(r.tick),s.u64(r.hash);break;case"desync":s.i64(r.tick),s.u64(r.hostHash),s.u64(r.yourHash);break;case"snapshot_req":s.i64(r.sinceTick);break;case"snapshot":s.i64(r.tick),s.u32(r.total),s.u8(r.flags);break;case"chunk":s.u32(r.id),s.u16(r.index),s.u16(r.count),s.bytes(r.bytes);break;case"raw":s.bytes(r.bytes);break;case"ping":case"pong":s.u32(r.n),s.u32(r.t);break;case"host_claim":s.u16(r.epoch),s.u16(r.hostId),s.i64(r.tick);break;case"voice":s.u16(r.seq),s.u8(r.flags),s.u8(r.frames.length);for(let n of r.frames)s.u16(n.length),s.bytes(n);break;case"chat":{let n=de.encode(r.text);s.u32(r.id),s.i64(r.ts),s.u16(n.length),s.bytes(n);break}case"chat_history":s.u16(r.lines.length);for(let n of r.lines){let o=de.encode(n.text);s.u16(n.peerId),s.u32(n.id),s.i64(n.ts),s.u16(o.length),s.bytes(o)}break;case"chat_reject":s.u32(r.id),s.u8(r.reason);break}let i=s.done();if(!t&&i.length>16384)throw new f("too_long",`${i.length} > 16384`);return i}var Qe=new Map(Object.keys(J).map(r=>[J[r],r]));function me(r,e=!1){let t=e?67108864:16384;if(r.length>t)throw new f("too_long",`${r.length} > ${t}`);if(r.length<3)throw new f("too_short","header");let s=new z(r),i=Qe.get(s.u8()),n=s.u16();if(!i)return null;let o;switch(i){case"hi":o={type:i,proto:s.u8(),peerId:s.u16(),lastTick:s.i64()};break;case"welcome_dc":o={type:i,tick:s.i64(),tickRate:s.u8(),mode:s.u8(),hostId:s.u16()};break;case"cmd":{let a=s.u32();o={type:i,seq:a,bytes:s.bytes(s.u16())};break}case"frame":{let a=s.i64(),c=s.u16(),l=[];for(let h=0;h<c;h++){let u=s.u16();l.push({peerId:u,bytes:s.bytes(s.u16())})}o={type:i,tick:a,commands:l};break}case"hash":o={type:i,tick:s.i64(),hash:s.u64()};break;case"desync":o={type:i,tick:s.i64(),hostHash:s.u64(),yourHash:s.u64()};break;case"snapshot_req":o={type:i,sinceTick:s.i64()};break;case"snapshot":o={type:i,tick:s.i64(),total:s.u32(),flags:s.u8()};break;case"chunk":o={type:i,id:s.u32(),index:s.u16(),count:s.u16(),bytes:s.rest()};break;case"raw":o={type:i,bytes:s.rest()};break;case"ping":case"pong":o={type:i,n:s.u32(),t:s.u32()};break;case"host_claim":o={type:i,epoch:s.u16(),hostId:s.u16(),tick:s.i64()};break;case"voice":{let a=s.u16(),c=s.u8(),l=s.u8(),h=[];for(let u=0;u<l;u++)h.push(s.bytes(s.u16()));o={type:i,seq:a,flags:c,frames:h};break}case"chat":{let a=s.u32(),c=s.i64();o={type:i,id:a,ts:c,text:ue(s.bytes(s.u16()))};break}case"chat_history":{let a=s.u16(),c=[];for(let l=0;l<a;l++){let h=s.u16(),u=s.u32(),Ye=s.i64();c.push({peerId:h,id:u,ts:Ye,text:ue(s.bytes(s.u16()))})}o={type:i,lines:c};break}case"chat_reject":o={type:i,id:s.u32(),reason:s.u8()};break}if(s.pos!==r.length)throw new f("trailing",`${r.length-s.pos} extra bytes`);return{route:n,msg:o}}var E={rate:1,length:2,mutedByHost:3};function Y(r){return r===1?"rate":r===2?"length":r===3?"muted_by_host":"unknown"}var G=256,be=3,et=5e3,tt=5,L=100,st=/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g;function it(r){return r.replace(st,"")}function ye(r){return new TextEncoder().encode(r).length}var X=class{buckets=new Map;refillMs=et/tt;tryTake(e,t){let s=this.buckets.get(e);return s||this.buckets.set(e,s={tokens:be,at:t}),s.tokens=Math.min(be,s.tokens+(t-s.at)/this.refillMs),s.at=t,s.tokens<1?!1:(s.tokens-=1,!0)}forget(e){this.buckets.delete(e)}},w=class extends p{constructor(t,s,i){super();this.hub=t;this.wall=s;this.isLocallyMuted=i}hub;wall;isLocallyMuted;filter=t=>t;lines=[];limiter=new X;hostMuted=new Set;localId=0;stampId=0;get history(){return this.lines}send(t){let s=++this.localId>>>0;return ye(t)>G*4?(queueMicrotask(()=>this.emit("rejected",s,"length")),s):(this.hub.isHost?this.hostProcess(this.hub.localPeerId,s,t):this.hub.sendToHost({type:"chat",id:s,ts:0,text:t}),s)}hostMute(t,s){if(!this.hub.isHost)throw new Error("only the host can mute chat");s?this.hostMuted.add(t):this.hostMuted.delete(t)}isHostMuted(t){return this.hostMuted.has(t)}onPeerLeft(t){this.limiter.forget(t),this.hostMuted.delete(t)}sendHistory(t){let s=this.lines.slice(-L).map(i=>({...i}));this.hub.sendTo(t,{type:"chat_history",lines:s},0)}handle(t,s,i){switch(i.type){case"chat":this.hub.isHost?this.hostProcess(t,i.id,i.text):this.deliver({peerId:s,id:i.id,ts:i.ts,text:i.text});break;case"chat_history":if(!this.hub.isHost){this.lines=[];for(let n of i.lines.slice(-L))this.push({...n,text:this.safeFilter(n.text)});this.emit("history",this.lines)}break;case"chat_reject":this.emit("rejected",i.id,Y(i.reason));break}}hostProcess(t,s,i){let n=l=>{t===this.hub.localPeerId?this.emit("rejected",s,Y(l)):this.hub.sendTo(t,{type:"chat_reject",id:s,reason:l},0)};if(this.hostMuted.has(t))return n(E.mutedByHost);if(ye(i)>G)return n(E.length);if(!this.limiter.tryTake(t,this.hub.clock.now()))return n(E.rate);let o=it(i);if(o.length===0)return;let a={peerId:t,id:++this.stampId>>>0,ts:this.wall(),text:o},c={type:"chat",id:a.id,ts:a.ts,text:a.text};for(let l of this.hub.connectedPeers())this.hub.sendTo(l,c,t);this.deliver(a)}deliver(t){let s={...t,text:this.safeFilter(t.text)};this.push(s),!this.isLocallyMuted(t.peerId)&&this.emit("message",s)}push(t){this.isLocallyMuted(t.peerId)||(this.lines.push(t),this.lines.length>L&&this.lines.splice(0,this.lines.length-L))}safeFilter(t){try{return this.filter(t)}catch{return t}}};var Z=16e3;function M(r,e,t=Z){let s=Math.max(1,Math.ceil(e.length/t));if(s>65535)throw new RangeError("payload needs more than 65535 chunks");let i=[];for(let n=0;n<s;n++)i.push({type:"chunk",id:r,index:n,count:s,bytes:e.subarray(n*t,Math.min(e.length,(n+1)*t))});return i}var _=class{parts=new Map;push(e){if(e.count===0||e.index>=e.count)return null;let t=this.parts.get(e.id);if((!t||t.count!==e.count)&&(t={count:e.count,got:0,parts:new Array(e.count),bytes:0},this.parts.set(e.id,t)),t.parts[e.index])return null;if(t.parts[e.index]=e.bytes,t.got++,t.bytes+=e.bytes.length,t.bytes>67108864)throw this.parts.delete(e.id),new f("too_long",`chunk set over ${67108864} bytes`);if(t.got<t.count)return null;this.parts.delete(e.id);let s=new Uint8Array(t.bytes),i=0;for(let n of t.parts)s.set(n,i),i+=n.length;return s}get pending(){return this.parts.size}clear(){this.parts.clear()}};function ke(){return typeof CompressionStream<"u"&&typeof DecompressionStream<"u"}async function ve(r,e){let t=e.writable.getWriter();t.write(r).then(()=>t.close());let s=[],i=e.readable.getReader(),n=0;for(;;){let{done:c,value:l}=await i.read();if(c)break;s.push(l),n+=l.length}let o=new Uint8Array(n),a=0;for(let c of s)o.set(c,a),a+=c.length;return o}function we(r){return ve(r,new CompressionStream("deflate"))}function xe(r){return ve(r,new DecompressionStream("deflate"))}var rt=3,ot=3,at=3e3,ct=1e4,lt=1024,x=class extends p{constructor(t,s){super();this.hub=t;this.hashEveryTicks=Math.max(1,s.hashEveryTicks),this.historyMax=s.historyFrames??Math.max(60,t.tickRate*30),this.lastFrameRx=t.clock.now()}hub;snapshot=null;restore=null;hashEveryTicks;chunkSize=Z;historyMax;queue=[];history=[];lastFrameTick=-1;consumedTick=0;cmdSeq=1;awaitingSnapshot=!1;lastFrameRx=0;lastSnapshotReq=-1/0;restoreFailures=0;hosting=!1;_started=!1;pending=[];ready=new Set;snapshotting=new Set;cancelTicker=null;baseTime=0;emitted=0;ownHashes=new Map;peerHashes=new Map;desyncs=new Map;lastHashRx=new Map;staleUpTo=new Map;pendingSnapshot=null;get started(){return this._started}get framesBuffered(){return this.queue.length}get catchingUp(){return this.queue.length>2}get currentTick(){return this.consumedTick}get lastTick(){return Math.max(0,this.lastFrameTick)}get hiTick(){return this.lastFrameTick}submit(t){this.hosting?this.pending.push({peerId:this.hub.localPeerId,bytes:t.slice()}):this.hub.sendToHost({type:"cmd",seq:this.cmdSeq++>>>0,bytes:t})}nextFrame(){let t=this.queue.shift()??null;return t&&(this.consumedTick=t.tick),t}reportHash(t,s){if(!(this.awaitingSnapshot||t<=0||t%this.hashEveryTicks!==0))if(this.hosting){this.ownHashes.set(t,s),this.prune(this.ownHashes);let i=this.peerHashes.get(t);if(i){this.peerHashes.delete(t);for(let[n,o]of[...i].sort((a,c)=>a[0]-c[0]))this.compare(n,t,s,o)}}else this.hub.sendToHost({type:"hash",tick:t,hash:s})}hostIdle(){this.hosting=!0,this.lastFrameTick<0&&(this.lastFrameTick=0)}hostStart(){this.hostIdle(),this._started=!0,this.emitted=0,this.baseTime=this.hub.clock.now(),this.cancelTicker?.();let t=1e3/this.hub.tickRate;this.cancelTicker=this.hub.clock.setInterval(()=>this.pump(t),Math.max(1,Math.floor(t/2)))}becomeHost(){this.pending=[],this.ready.clear(),this.snapshotting.clear(),this.ownHashes.clear(),this.peerHashes.clear(),this.desyncs.clear(),this.awaitingSnapshot=!1,this.hostIdle(),this._started&&this.hostStart()}becomeJoiner(){this.hosting=!1,this.cancelTicker?.(),this.cancelTicker=null,this.pending=[],this.ready.clear(),this.lastFrameRx=this.hub.clock.now()}markStarted(){this._started||(this._started=!0,this.emit("started"))}stop(){this.cancelTicker?.(),this.cancelTicker=null}onPeerLeft(t){this.ready.delete(t),this.snapshotting.delete(t),this.desyncs.delete(t),this.lastHashRx.delete(t),this.staleUpTo.delete(t);for(let s of this.peerHashes.values())s.delete(t);this.pending=this.pending.filter(s=>s.peerId!==t)}pump(t){if(!this.hosting)return;let s=this.hub.clock.now(),i=Math.floor((s-this.baseTime+1)/t)-this.emitted;i>10&&(this.baseTime=s-this.emitted*t,i=1);for(let n=0;n<i;n++)this.emitFrame()}emitFrame(){let t=this.pending.splice(0).sort((n,o)=>n.peerId-o.peerId),s={tick:this.lastFrameTick+1,commands:t};this.emitted++,this.lastFrameTick=s.tick,this.record(s),this.queue.push(s);let i={type:"frame",tick:s.tick,commands:t};for(let n of this.ready)this.hub.sendTo(n,i,0);this.checkPeerSilence()}record(t){this.history.push(t),this.history.length>this.historyMax&&this.history.splice(0,this.history.length-this.historyMax)}checkPeerSilence(){if(this.ownHashes.size===0)return;let t=this.hub.clock.now(),s=Math.max(ct,this.hashEveryTicks/this.hub.tickRate*1e3*3);for(let i of[...this.ready]){let n=this.lastHashRx.get(i)??t;t-n>s&&this.hub.dropPeer(i,"timeout")}}handle(t,s){switch(s.type){case"cmd":this.hosting&&this.pending.push({peerId:t,bytes:s.bytes});break;case"frame":this.hosting||this.onFrame(s.tick,s.commands);break;case"hash":this.hosting&&this.onPeerHash(t,s.tick,s.hash);break;case"desync":this.hosting||(this.awaitingSnapshot=!0,this.queue=[],this.emit("desync",{tick:s.tick,hostHash:s.hostHash,yourHash:s.yourHash}));break;case"snapshot_req":this.hosting&&this.sendSnapshot(t);break;case"snapshot":this.hosting||(this.markStarted(),this.pendingSnapshot={tick:s.tick,total:s.total,flags:s.flags});break}}get snapshotPending(){return this.pendingSnapshot!==null}onSnapshotPayload(t){let s=this.pendingSnapshot;if(this.pendingSnapshot=null,!!s){if(t.length!==s.total){this.awaitingSnapshot=!1,this.requestSnapshot();return}this.applySnapshot(s,t)}}onFrame(t,s){if(this.lastFrameRx=this.hub.clock.now(),this.markStarted(),this.awaitingSnapshot)return;let i=Math.max(0,this.lastFrameTick)+1;if(t<i)return;if(t>i){this.requestSnapshot();return}let n={tick:t,commands:s};this.lastFrameTick=t,this.record(n),this.queue.push(n),this.queue.length>this.hub.tickRate*3&&this.requestSnapshot()}requestSnapshot(){let t=this.hub.clock.now();this.awaitingSnapshot&&t-this.lastSnapshotReq<2e3||(this.lastSnapshotReq=t,this.awaitingSnapshot=!0,this.queue=[],this.hub.sendToHost({type:"snapshot_req",sinceTick:this.lastFrameTick}))}pokeHost(){this.hub.sendToHost({type:"snapshot_req",sinceTick:this.lastFrameTick})}async applySnapshot(t,s){try{let i=s;if(t.flags&1&&(i=await xe(s)),!this.restore)throw new Error("lockstep.restore is not set; cannot apply a snapshot");this.restore(t.tick,i),this.queue=[],this.history=[],this.lastFrameTick=t.tick,this.consumedTick=t.tick,this.awaitingSnapshot=!1,this.restoreFailures=0,this.markStarted(),this.lastFrameRx=this.hub.clock.now(),this.emit("restored",t.tick)}catch(i){this.hub.reportError(i instanceof Error?i:new Error(String(i))),++this.restoreFailures<=ot?(this.awaitingSnapshot=!1,this.lastSnapshotReq=-1/0,this.requestSnapshot()):this.hub.reportError(new Error(`snapshot restore failed ${this.restoreFailures} times; giving up`))}}onPeerHi(t,s){this.lastHashRx.set(t,this.hub.clock.now());let i=this.lastFrameTick,n=this.history.length?this.history[0].tick:i+1;if(s<0){this._started&&i>0?this.sendSnapshot(t):this.ready.add(t);return}if(s<=i&&s+1>=n){for(let o of this.history)o.tick>s&&this.hub.sendTo(t,{type:"frame",tick:o.tick,commands:o.commands},0);this.ready.add(t)}else this.sendSnapshot(t)}onPeerHash(t,s,i){if(this.lastHashRx.set(t,this.hub.clock.now()),this.isStale(t,s))return;let n=this.ownHashes.get(s);if(n===void 0){let o=this.peerHashes.get(s);o||this.peerHashes.set(s,o=new Map),o.set(t,i),this.prune(this.peerHashes);return}this.compare(t,s,n,i)}compare(t,s,i,n){if(i===n||this.isStale(t,s)||!this.ready.has(t)||this.snapshotting.has(t))return;let o=(this.desyncs.get(t)??0)+1;if(this.desyncs.set(t,o),o>=rt){this.hub.dropPeer(t,"desync");return}this.hub.sendTo(t,{type:"desync",tick:s,hostHash:i,yourHash:n},0),this.sendSnapshot(t,s)}isStale(t,s){let i=this.staleUpTo.get(t);return!!i&&s>i.from&&s<=i.to}async sendSnapshot(t,s=1/0){if(!this.snapshotting.has(t)){if(!this.snapshot){this.hub.reportError(new Error("lockstep.snapshot is not set; cannot serve a snapshot"));return}this.snapshotting.add(t),this.ready.delete(t);try{let i=this.consumedTick,n=this.snapshot(),o=n,a=0;if(n.length>=lt&&ke()&&(o=await we(n),a=1),!this.snapshotting.has(t))return;this.hub.sendTo(t,{type:"snapshot",tick:i,total:o.length,flags:a},0);for(let c of M(this.hub.nextChunkId(),o,this.chunkSize))this.hub.sendTo(t,c,0);for(let c of this.history)c.tick>i&&this.hub.sendTo(t,{type:"frame",tick:c.tick,commands:c.commands},0);s!==1/0?this.staleUpTo.set(t,{from:s,to:this.lastFrameTick}):this.staleUpTo.delete(t);for(let c of this.peerHashes.values())c.delete(t);this.ready.add(t),this.lastHashRx.set(t,this.hub.clock.now())}catch(i){this.hub.reportError(i instanceof Error?i:new Error(String(i)))}finally{this.snapshotting.delete(t)}}}prune(t){if(t.size<=64)return;let s=[...t.keys()].sort((i,n)=>i-n);for(let i of s.slice(0,s.length-64))t.delete(i)}get msSinceFrame(){return this.hub.clock.now()-this.lastFrameRx}hostSilent(){return!this.hosting&&this._started&&this.msSinceFrame>at}touch(){this.lastFrameRx=this.hub.clock.now()}hiMessage(t){return{type:"hi",proto:1,peerId:t,lastTick:this.lastFrameTick}}get resyncCount(){return this.desyncs}};function Se(r,e){let t=null;for(let s of r)s!==e&&(t===null||s<t)&&(t=s);return t}function ht(r,e){let t=r-e&65535;return t!==0&&t<32768}function Pe(r,e){return r.epoch!==e.epoch?ht(r.epoch,e.epoch):r.hostId<e.hostId}var d=class extends Error{constructor(t,s,i){super(s?`${t}: ${s}`:t);this.code=t;this.data=i;this.name="OneLobbyError"}code;data},Ce="lk1",dt=1e4,ut=3e4,S=class{constructor(e,t,s){this.url=e;this.factory=t;this.clock=s}url;factory;clock;socket=null;waiters=[];lastRx=0;cancelPing=null;pingN=0;closed=!1;handler=null;backlog=[];onClose=null;get onMessage(){return this.handler}set onMessage(e){for(this.handler=e;e&&this.handler===e&&this.backlog.length;)e(this.backlog.shift())}connect(){return new Promise((e,t)=>{let s;try{s=this.factory(this.url)}catch(i){t(new d("connect_failed",String(i)));return}this.socket=s,s.onopen=()=>{this.lastRx=this.clock.now(),this.cancelPing=this.clock.setInterval(()=>this.tick(),dt),e()},s.onerror=()=>{this.lastRx||t(new d("connect_failed",`cannot reach ${this.url}`))},s.onmessage=i=>{this.lastRx=this.clock.now();let n;try{n=JSON.parse(String(i.data))}catch{return}if(!n||typeof n.t!="string")return;if(n.t==="ping"){this.send({t:"pong",n:n.n});return}if(n.t==="pong")return;let o=this.waiters.findIndex(a=>a.types.includes(n.t)||n.t==="error");if(o>=0){let[a]=this.waiters.splice(o,1);n.t==="error"?a.reject(pt(n)):a.resolve(n);return}this.handler?this.handler(n):this.backlog.length<256&&this.backlog.push(n)},s.onclose=()=>{this.lastRx||t(new d("connect_failed",`cannot reach ${this.url}`)),this.shutdown()}})}tick(){if(this.clock.now()-this.lastRx>ut){this.socket?.close(4e3,"silence"),this.shutdown();return}this.send({t:"ping",n:++this.pingN})}shutdown(){if(this.closed)return;this.closed=!0,this.cancelPing?.();let e=new d("closed","signaling connection closed");for(let t of this.waiters.splice(0))t.reject(e);this.onClose?.()}get isOpen(){return!this.closed&&this.socket!==null&&this.socket.readyState===1}send(e){this.isOpen&&this.socket.send(JSON.stringify(e))}expect(...e){return this.closed?Promise.reject(new d("closed","signaling connection closed")):new Promise((t,s)=>{this.waiters.push({types:e,resolve:t,reject:s})})}async request(e,...t){let s=this.expect(...t);return this.send(e),s}close(){this.socket?.close(1e3,"bye"),this.shutdown()}};function pt(r){return new d(String(r.code??"error"),r.msg===void 0?void 0:String(r.msg),r.data)}var R=class{constructor(e=2){this.framesPerPacket=e}framesPerPacket;seq=0;buf=[];push(e){return this.buf.push(e),this.buf.length<this.framesPerPacket?null:this.take(0)}packet(e,t){let s={seq:this.seq,flags:t,frames:e};return this.seq=this.seq+1&65535,s}endBurst(){return this.take(1)}get buffered(){return this.buf.length}take(e){let t={seq:this.seq,flags:e,frames:this.buf};return this.seq=this.seq+1&65535,this.buf=[],t}};function Te(r){if(r.length===0)return 0;let e=0;for(let t=0;t<r.length;t++)e+=r[t]*r[t];return Math.sqrt(e/r.length)}var A=class{mode="pushToTalk";ptt=!1;vadThreshold=.02;lastLoud=-1/0;open=!1;update(e,t){let s;this.mode==="pushToTalk"?s=this.ptt:(e>=this.vadThreshold&&(this.lastLoud=t),s=t-this.lastLoud<=300);let i=s===this.open?null:s?"open":"close";return this.open=s,{send:s,edge:i}}get isOpen(){return this.open}};function Q(r,e){let t=r-e&65535;return t>=32768?t-65536:t}var I=class{constructor(e=60,t=2){this.framesPerPacket=t;this.prefillFrames=Math.max(1,Math.round(e/20))}framesPerPacket;packets=new Map;expected=null;ready=[];playing=!1;endSeq=null;waitTicks=0;prefillFrames;push(e){if(this.expected===null)this.expected=e.seq;else if(Q(e.seq,this.expected)<0)return;this.packets.set(e.seq,e),e.flags&1?this.endSeq=e.seq:this.endSeq!==null&&Q(e.seq,this.endSeq)>0&&(this.endSeq=null)}get ended(){return this.endSeq!==null}bufferedFrames(){let e=this.ready.length;for(let t of this.packets.values())e+=t.frames.length;return e}newerThanExpected(){for(let e of this.packets.keys())if(this.expected!==null&&Q(e,this.expected)>0)return!0;return!1}pull(){if(this.expected===null)return{kind:"idle"};if(!this.playing)if(this.bufferedFrames()>=this.prefillFrames||this.ended&&this.packets.size>0)this.playing=!0;else return{kind:"wait"};this.ready.length===0&&this.refill();let e=this.ready.shift();return e?(this.waitTicks=0,e.length===0?{kind:"plc"}:{kind:"frame",opus:e}):this.ended&&this.packets.size===0?(this.reset(),{kind:"idle"}):++this.waitTicks>12?(this.reset(),{kind:"idle"}):{kind:"wait"}}refill(){if(this.expected===null)return;let e=this.packets.get(this.expected);if(e){this.packets.delete(this.expected),this.expected=this.expected+1&65535;for(let t of e.frames)this.ready.push(t);return}if(this.newerThanExpected()){this.expected=this.expected+1&65535;for(let t=0;t<this.framesPerPacket;t++)this.ready.push(new Uint8Array(0))}}reset(){this.packets.clear(),this.ready=[],this.expected=null,this.playing=!1,this.endSeq=null,this.waitTicks=0}get active(){return this.expected!==null}},O=class{speakers=new Map;windows=new Map;burstTimeoutMs=500;accept(e,t,s,i){if(t>1024)return!1;let n=this.windows.get(e);for(n||this.windows.set(e,n=[]);n.length&&i-n[0]>=1e3;)n.shift();if(n.length>=30)return!1;for(let[o,a]of this.speakers)i-a>this.burstTimeoutMs&&this.speakers.delete(o);return!this.speakers.has(e)&&this.speakers.size>=8?!1:(n.push(i),s&1?this.speakers.delete(e):this.speakers.set(e,i),!0)}get activeSpeakers(){return this.speakers.size}forget(e){this.speakers.delete(e),this.windows.delete(e)}};var ft=300,P=class extends p{constructor(t,s){super();this.hub=t;this.deps=s}hub;deps;mode="pushToTalk";muted=!1;inputLevel=0;bitrate=24e3;_enabled=!1;gate=new A;packetizer=new R;relay=new O;speakers=new Map;peerMuted=new Set;gains=new Map;encoder=null;source=null;sink=null;cancelPlayout=null;endTimer=null;sending=!1;get enabled(){return this._enabled}set enabled(t){t!==this._enabled&&(this._enabled=t,t?this.start():this.stop())}pushToTalk(t){this.gate.ptt=t}isMutedPeer(t){return this.peerMuted.has(t)}setMuted(t,s){s?this.peerMuted.add(t):this.peerMuted.delete(t)}setVolume(t,s){this.gains.set(t,Math.min(2,Math.max(0,s)))}volumeOf(t){return this.gains.get(t)??1}async start(){try{if(!this.deps.codec)throw new Error("no Opus codec available: supply one through the voice.codec option (WASM fallback)");if(!this.deps.createSink)throw new Error("no audio output available");this.sink=this.deps.createSink(),this.cancelPlayout=this.hub.clock.setInterval(()=>this.playoutTick(),20),this.deps.createSource&&(this.encoder=this.deps.codec.createEncoder({bitrate:this.bitrate},t=>this.onEncoded(t)),this.source=this.deps.createSource(),await this.source.start(t=>this.onCapturedFrame(t,this.hub.clock.now())))}catch(t){this._enabled=!1,this.stop(),this.emit("error",{message:t instanceof Error?t.message:String(t),cause:t})}}stop(){this.sending&&this.closeBurst(),this.source?.stop(),this.source=null,this.encoder?.close(),this.encoder=null,this.cancelPlayout?.(),this.cancelPlayout=null,this.sink?.close(),this.sink=null;for(let[t,s]of this.speakers)s.decoder?.close(),s.talking&&this.emit("speaking",t,!1);this.speakers.clear()}onCapturedFrame(t,s){let i=Te(t);if(this.inputLevel=i,!this._enabled||this.muted){this.sending&&this.closeBurst();return}let n=this.gate.update(i,s);n.send?(this.sending=!0,this.endTimer?.(),this.endTimer=null,this.encoder?.encode(t)):n.edge==="close"&&this.sending&&this.closeBurst()}closeBurst(){this.sending=!1,this.endTimer?.(),this.endTimer=this.hub.clock.setTimeout(()=>{this.endTimer=null,this.sendPacket(this.packetizer.endBurst())},40)}sendEncoded(t,s=!1){this.sendPacket(this.packetizer.packet(t,s?1:0))}onEncoded(t){let s=this.packetizer.push(t);s&&this.sendPacket(s)}sendPacket(t){let s={type:"voice",seq:t.seq,flags:t.flags,frames:t.frames};if(this.hub.isHost)for(let i of this.hub.connectedPeers())this.hub.sendTo(i,s,this.hub.localPeerId,"voice");else this.hub.sendToHost(s,0,"voice")}handle(t,s,i){if(i.type!=="voice")return;let n=i.frames.reduce((c,l)=>c+l.length+2,0)+7,o=this.hub.clock.now(),a=s;if(this.hub.isHost){if(a=t,!this.relay.accept(t,n,i.flags,o))return;for(let c of this.hub.connectedPeers())c!==t&&this.hub.sendTo(c,i,t,"voice")}this.emit("packet",a,i.seq,i.flags,i.frames),!(!this._enabled||a===this.hub.localPeerId||this.peerMuted.has(a))&&this.receivePacket(a,{seq:i.seq,flags:i.flags,frames:i.frames},o)}receivePacket(t,s,i){let n=this.speakers.get(t);n||(n={jitter:new I,decoder:null,talking:!1,lastHeard:i},this.speakers.set(t,n)),n.lastHeard=i,n.jitter.push(s),!n.talking&&s.frames.length>0&&(n.talking=!0,this.emit("speaking",t,!0))}playoutTick(){let t=this.hub.clock.now();for(let[s,i]of this.speakers){let n=i.jitter.pull();n.kind==="frame"||n.kind==="plc"?(!i.decoder&&this.deps.codec&&(i.decoder=this.deps.codec.createDecoder(o=>this.sink?.play(s,o,this.volumeOf(s)))),i.decoder?.decode(n.kind==="frame"?n.opus:null)):n.kind==="idle"&&i.talking?(i.talking=!1,this.emit("speaking",s,!1)):i.talking&&t-i.lastHeard>ft+100&&!i.jitter.active&&(i.talking=!1,this.emit("speaking",s,!1))}}onPeerLeft(t){this.relay.forget(t);let s=this.speakers.get(t);s&&(s.decoder?.close(),s.talking&&this.emit("speaking",t,!1),this.speakers.delete(t))}close(){this._enabled=!1,this.stop(),this.endTimer?.()}static END=1};function H(r){return r==="left"||r==="disconnected"?1e3:r==="host_left"||r==="closed"?1001:r==="kicked"?4e3:1006}var Le=globalThis.setImmediate??(r=>{setTimeout(r,0)}),mt=new TextEncoder,bt=new TextDecoder,F=class{constructor(e,t,s,i){this.peerId=e;this.out=t;this.binary=s;this.onLocalClose=i}peerId;out;binary;onLocalClose;readyState=0;bufferedAmount=0;openHandler=null;openPending=!1;onmessage=null;onclose=null;onerror=null;detach=()=>{};send(e){this.readyState===1&&this.out(typeof e=="string"?mt.encode(e):e)}close(e=1e3,t="left"){this.readyState!==3&&(this.closed(e,t),this.onLocalClose())}get onopen(){return this.openHandler}set onopen(e){this.openHandler=e,e&&this.openPending&&Le(()=>this.fireOpen())}open(e=!1){this.readyState===0&&(this.readyState=1,this.openPending=!0,e?Le(()=>this.fireOpen()):this.fireOpen())}fireOpen(){this.readyState!==1||!this.openPending||!this.openHandler||(this.openPending=!1,this.openHandler({type:"open"}))}deliver(e){this.readyState===1&&this.onmessage?.({type:"message",data:this.binary?e:bt.decode(e)})}closed(e,t){this.readyState!==3&&(this.readyState=3,this.detach(),this.onclose?.({type:"close",code:e,reason:t,wasClean:e===1e3||e===1001}))}};function _e(r,e={}){if(r.isHost)throw new Error("asWebSocket() is for joiners; the host uses lobby.onConnection()");let t=e.channel??"reliable",s=new F(r.hostPeerId,n=>r.send(r.hostPeerId,n,{channel:t}),!!e.binary,()=>{r.leave()}),i=[r.on("message",(n,o)=>{n===r.hostPeerId&&s.deliver(o)}),r.on("sessionEnded",(n,o)=>s.closed(o?.code??H(n),o?.reason??n)),r.on("hostChanged",()=>s.closed(1001,"host_changed"))];return s.detach=()=>i.forEach(n=>n()),r.connectedPeers.includes(r.hostPeerId)?s.open(!0):r.ready.then(()=>s.open(!0),()=>{}),s}function Me(r,e,t={}){if(!r.isHost)throw new Error("onConnection() is for the host; a joiner uses lobby.asWebSocket()");let s=t.channel??"reliable",i=new Map,n=!1,o=c=>{if(c===r.localPeerId)return;let l=i.get(c);if(l||n)return l;l=new F(c,u=>r.send(c,u,{channel:s}),!!t.binary,()=>{});let h=l;return h.detach=()=>{i.get(c)===h&&i.delete(c)},i.set(c,h),h.open(),e(h),h},a=[r.on("peerReady",c=>{o(c.peerId)}),r.on("message",(c,l)=>o(c)?.deliver(l)),r.on("peerLeft",(c,l,h)=>i.get(c.peerId)?.closed(h?.code??(l==="left"?1001:H(l)),h?.reason??l)),r.on("sessionEnded",c=>{for(let l of[...i.values()])l.closed(c==="left"?1001:H(c),c);a.forEach(l=>l())})];for(let c of r.connectedPeers)o(c);return()=>{n=!0}}var Re=6e3,gt=3e3;function U(r){let e=typeof r.name=="string"?r.name.trim():"";return e&&e!=="Player"?r:{...r,name:`Player ${r.peerId}`}}function kt(r){return r.some(e=>(Array.isArray(e.urls)?e.urls:[e.urls]).some(t=>/^turns?:/i.test(String(t))))}var vt=250,wt=2e3,C=class extends p{code;mode;tickRate;lockstep;chat;voice;localPeerId;hostPeerId;phase;meta;resumeToken;rtt=0;joinCode="";joinCodeKind="none";directAddresses=[];portMapping="";signaling;cfg;roster=new Map;links=new Map;earlyIce=new Map;connected=new Set;hiSentTo=new Set;epoch=0;ended=!1;serverUp=!0;cancelWatchdog=null;cancelPing=null;pingN=0;chunkId=1;reassemblers=new Map;hub;outbox=new Map;markReady=()=>{};failReady=()=>{};isReady=!1;cancelReadyTimer=null;relayRetry=!1;lastHeard=new Map;lingering=new Set;rate=new Map;ready=new Promise((e,t)=>{this.markReady=()=>{this.isReady||(this.isReady=!0,this.cancelReadyTimer?.(),this.cancelReadyTimer=null,e())},this.failReady=t});constructor(e){super(),this.cfg=e,this.signaling=e.signaling;let t=e.state;this.code=t.code,this.mode=t.mode,this.tickRate=t.tickRate,this.localPeerId=t.you,this.hostPeerId=t.hostId,this.phase=t.phase,this.meta={...t.meta},this.resumeToken=t.resumeToken;for(let i of t.peers)this.roster.set(i.peerId,U(i));this.ready.catch(()=>{});let s=this;this.hub={get clock(){return s.cfg.clock},get localPeerId(){return s.localPeerId},get hostPeerId(){return s.hostPeerId},get isHost(){return s.isHost},get tickRate(){return s.tickRate},connectedPeers:()=>s.connectedPeerIds(),sendTo:(i,n,o,a)=>s.sendTo(i,n,o??0,a),sendToHost:(i,n,o)=>{s.isHost||s.sendTo(s.hostPeerId,i,n??0,o)},dropPeer:(i,n)=>s.dropPeer(i,n),reportError:i=>s.reportError(i),nextChunkId:()=>s.chunkId++>>>0},this.lockstep=new x(this.hub,{hashEveryTicks:e.hashEveryTicks}),this.chat=new w(this.hub,e.wall,i=>this.voice.isMutedPeer(i)),this.voice=new P(this.hub,e.voice),this.lockstep.on("started",()=>this.setPhase("in_game")),this.lockstep.on("error",i=>this.reportError(i)),this.signaling.onMessage=i=>this.onSignaling(i),this.signaling.onClose=()=>this.onServerClosed(),this.isHost?this.markReady():this.armReadyTimer(),this.isHost&&this.mode==="lockstep"&&(this.phase==="in_game"?this.lockstep.hostStart():this.lockstep.hostIdle()),this.cancelWatchdog=e.clock.setInterval(()=>this.watchdog(),vt),this.cancelPing=e.clock.setInterval(()=>this.pingHost(),wt)}get isHost(){return this.localPeerId===this.hostPeerId}get peers(){return[...this.roster.values()].sort((e,t)=>e.peerId-t.peerId)}peer(e){return this.roster.get(e)}get connectedPeers(){return this.connectedPeerIds()}asWebSocket(e){return _e(this,e)}onConnection(e,t){return Me(this,e,t)}set snapshotChunkSize(e){this.lockstep.chunkSize=e}get hostEpoch(){return this.epoch}_offer(e){return this.offerTo(e),this.links.get(e)}_answer(e){return this.makeLink(e)}_begin(){}async start(){if(!this.isHost)throw new Error("only the host can start the game");this.phase!=="in_game"&&(this.signaling.send({t:"phase",phase:"in_game"}),this.setPhase("in_game"),this.mode==="lockstep"&&this.lockstep.hostStart())}async setMeta(e){if(!this.isHost)throw new Error("only the host can set metadata");this.meta={...e},this.signaling.send({t:"meta",meta:e}),this.emit("metaChanged",this.meta)}async kick(e,t={}){if(!this.isHost)throw new Error("only the host can kick");let{ban:s=!0,code:i,reason:n}=t;if(i!==void 0&&!(i===1e3||Number.isInteger(i)&&i>=3e3&&i<=4999))throw new RangeError("close code must be 1000 or 3000-4999");if(n!==void 0&&n.length>120)throw new RangeError("close reason must be at most 120 characters");let o={t:"kick",peerId:e};s||(o.ban=!1),i!==void 0&&(o.closeCode=i),n!==void 0&&(o.closeReason=n),this.signaling.send(o);let a=i!==void 0||n!==void 0?{code:i,reason:n}:void 0;this.removePeer(e,s?"kicked":"disconnected",a,!0)}disconnect(e,t,s){return this.kick(e,{ban:!1,code:t,reason:s})}async approve(e,t){if(!this.isHost)throw new Error("only the host can approve joins");this.signaling.send({t:"approve",peerId:e,ok:t})}async leave(){this.ended||(this.signaling.send({t:"leave"}),this.teardown("left"))}send(e,t,s={}){let i=s.channel??"reliable";this.isHost?this.sendTo(e,{type:"raw",bytes:t},this.localPeerId,i):this.sendTo(this.hostPeerId,{type:"raw",bytes:t},e,i)}broadcast(e,t={}){let s=t.channel??"reliable";if(this.isHost)for(let i of this.roster.keys())i!==this.localPeerId&&this.sendTo(i,{type:"raw",bytes:e},this.localPeerId,s);else this.sendTo(this.hostPeerId,{type:"raw",bytes:e},0,s)}channelFor(e){return e.type==="voice"?"voice":e.type==="ping"||e.type==="pong"?"fast":"reliable"}sendTo(e,t,s,i){let n=this.links.get(e),o=i??this.channelFor(t),a=t.type==="raw"||t.type==="chat"||t.type==="cmd";if(!(!!n?.isOpen&&(!a||!this.isHost||this.connected.has(e)))){let l=this.roster.has(e)||e===this.hostPeerId;if(a&&o==="reliable"&&l&&!this.ended){let h=this.outbox.get(e);h||this.outbox.set(e,h=[]),h.length>=1e3&&h.shift(),h.push({msg:t,route:s,channel:i})}return}if(n)try{let l=o,h;try{h=v(t,s)}catch(u){if(!(u instanceof f)||u.code!=="too_long")throw u;h=v(t,s,!0)}if(h.length>16384)for(let u of M(this.chunkId++>>>0,h))n.send(l,v(u,s));else n.send(l,h)}catch(l){this.reportError(l instanceof Error?l:new Error(String(l)))}}connectedPeerIds(){return this.isHost?[...this.connected].filter(t=>this.links.get(t)?.isOpen).sort((t,s)=>t-s):this.links.get(this.hostPeerId)?.isOpen?[this.hostPeerId]:[]}reportError(e){this.errorSink?.(e)}errorSink=null;seatSink=null;armReadyTimer(){let e=this.cfg.readyTimeoutMs??0;if(!(e>0))return;let t=!this.cfg.relayOnly&&kt(this.cfg.ice),s=t?Math.ceil(e/2):e;this.cancelReadyTimer=this.cfg.clock.setTimeout(()=>{this.isReady||this.ended||(t?this.retryRelayed(e-s):this.giveUpConnecting())},s)}retryRelayed(e){this.relayRetry=!0,this.makeLink(this.hostPeerId,!0).startOffer(),this.cancelReadyTimer=this.cfg.clock.setTimeout(()=>{!this.isReady&&!this.ended&&this.giveUpConnecting()},e)}giveUpConnecting(){let e=new d("ice_failed",`could not open a connection to the host${this.relayRetry?" (direct or relayed)":""}`);this.failReady(e),this.reportError(e),!this.ended&&(this.signaling.send({t:"leave"}),this.teardown("ice_failed"))}makeLink(e,t=this.cfg.relayOnly){let s=this.links.get(e);s&&(s.onClose=null,s.onMessage=null,s.close());let i=this.cfg.links.create({remoteId:e,iceServers:this.cfg.ice,relayOnly:t});return this.links.set(e,i),i.onSignal=n=>this.signaling.send({t:"signal",to:e,...n}),i.onOpen=()=>this.onLinkOpen(e,i),i.onClose=n=>this.onLinkClosed(e,i,n),i.onMessage=(n,o)=>this.onData(e,n,o),i.onPath=n=>this.signaling.send({t:"path",path:n}),i}offerTo(e){this.makeLink(e).startOffer()}onLinkOpen(e,t){if(this.isHost){this.epoch>0&&this.sendTo(e,{type:"host_claim",epoch:this.epoch,hostId:this.localPeerId,tick:this.lockstep.lastTick},0);return}e===this.hostPeerId&&this.sendHi(t)}sendHi(e){this.hiSentTo.has(e)||(this.hiSentTo.add(e),this.lockstep.touch(),e.send("reliable",v(this.lockstep.hiMessage(this.localPeerId))),this.markReady(),this.emit("ready"),this.emit("peerReady",this.roster.get(e.remoteId)??{peerId:e.remoteId,name:""}),this.flush(e.remoteId))}onLinkClosed(e,t,s){if(this.links.get(e)===t&&(this.links.delete(e),this.hiSentTo.delete(t),!this.ended)){if(this.isHost)this.connected.delete(e),this.lockstep.onPeerLeft(e),this.voice.onPeerLeft(e),!this.serverUp&&this.roster.has(e)&&this.removePeer(e,"timeout");else if(e===this.hostPeerId){if(!this.isReady&&this.cancelReadyTimer)return;this.handleHostLost("link closed")}}}onData(e,t,s,i=!1){let n;try{n=me(s,i)}catch(c){this.reportError(c instanceof Error?c:new Error(String(c)));return}if(!n)return;i||this.lastHeard.set(e,this.cfg.clock.now());let{msg:o,route:a}=n;switch(o.type){case"hi":this.isHost&&this.onHi(e,o.lastTick);break;case"welcome_dc":this.isHost||(o.hostId!==this.hostPeerId&&this.setHost(o.hostId),this.lockstep.touch());break;case"cmd":case"frame":case"hash":case"desync":case"snapshot_req":case"snapshot":(e===this.hostPeerId||this.isHost)&&this.lockstep.handle(e,o);break;case"chunk":{if(i)break;let c=this.reassemblers.get(e);c||this.reassemblers.set(e,c=new _);let l;try{l=c.push(o)}catch(h){this.reportError(h instanceof Error?h:new Error(String(h)));break}if(!l)break;this.lockstep.snapshotPending&&e===this.hostPeerId?this.lockstep.onSnapshotPayload(l):this.onData(e,t,l,!0);break}case"chat":case"chat_history":case"chat_reject":this.chat.handle(e,a,o);break;case"voice":t==="voice"&&this.voice.handle(e,a,o);break;case"raw":this.onRaw(e,a,o.bytes,t);break;case"ping":this.sendTo(e,{type:"pong",n:o.n,t:o.t},0,"fast");break;case"pong":this.rtt=Math.max(0,(this.cfg.clock.now()>>>0)-o.t);break;case"host_claim":this.onHostClaim(e,o.epoch,o.hostId);break}}onHi(e,t){this.connected.add(e),this.sendTo(e,{type:"welcome_dc",tick:this.mode==="lockstep"?this.lockstep.currentTick:0,tickRate:this.tickRate,mode:this.mode==="lockstep"?pe:fe,hostId:this.localPeerId},0),this.chat.sendHistory(e),this.mode==="lockstep"&&this.lockstep.onPeerHi(e,t),this.emit("peerReady",this.roster.get(e)??{peerId:e,name:""}),this.flush(e)}flush(e){let t=this.outbox.get(e);if(t){this.outbox.delete(e);for(let s of t)this.sendTo(e,s.msg,s.route,s.channel)}}onRaw(e,t,s,i){if(!this.isHost){this.emit("message",t,s,i);return}if(this.allowRaw(e))if((t===0||t===this.localPeerId)&&this.emit("message",e,s,i),t===0)for(let n of this.connectedPeerIds())n!==e&&this.sendTo(n,{type:"raw",bytes:s},e,i);else t!==this.localPeerId&&this.sendTo(t,{type:"raw",bytes:s},e,i)}allowRaw(e){let t=this.cfg.rateLimit;if(!t||!(t.messages>0)||!(t.perMs>0))return!0;let s=this.cfg.clock.now(),i=this.rate.get(e);return(!i||s-i.start>=t.perMs)&&this.rate.set(e,i={start:s,n:0}),++i.n<=t.messages}setHost(e){e!==this.hostPeerId&&(this.hostPeerId=e,this.emit("hostChanged",e))}handleHostLost(e){if(this.isHost||this.ended)return;if(this.cfg.direct){this.teardown("host_left");return}let t=this.hostPeerId,s=this.roster.get(t);s&&(this.roster.delete(t),this.emit("peerLeft",s,e==="link closed"?"timeout":e));let i=this.links.get(t);i&&(i.onClose=null,i.onMessage=null,i.close(),this.links.delete(t),this.hiSentTo.delete(i)),(Se([...this.roster.keys(),this.localPeerId],t)??this.localPeerId)===this.localPeerId&&this.becomeHost()}becomeHost(){this.epoch=this.epoch+1&65535,this.hostPeerId=this.localPeerId,this.markReady(),this.connected.clear(),this.hiSentTo.clear(),this.lastHeard.clear(),this.lockstep.becomeHost(),this.emit("hostChanged",this.localPeerId),this.serverUp&&this.signaling.send({t:"host_changed",hostId:this.localPeerId,epoch:this.epoch});for(let e of this.roster.keys())e!==this.localPeerId&&this.offerTo(e)}onHostClaim(e,t,s){let i={epoch:this.epoch,hostId:this.hostPeerId},n={epoch:t,hostId:s};if(s===this.hostPeerId&&t===this.epoch&&!this.isHost){let c=this.links.get(e);c&&this.sendHi(c);return}if(!Pe(n,i))return;let o=this.isHost;if(this.epoch=t,o){for(let[c,l]of this.links)c!==e&&(l.onClose=null,l.onMessage=null,l.close(),this.links.delete(c));this.connected.clear(),this.lockstep.becomeJoiner()}this.setHost(s);let a=this.links.get(e);a&&e===s&&this.sendHi(a)}onSignaling(e){switch(e.t){case"peer_joined":{if(!e.peer||this.roster.has(e.peer.peerId))return;let t=U(e.peer);this.roster.set(t.peerId,t),this.emit("peerJoined",t),this.isHost&&this.offerTo(t.peerId);break}case"peer_left":this.removePeer(Number(e.peerId),String(e.reason??"left"));break;case"host_changed":{if(this.cfg.direct){this.isHost||this.teardown("host_left");break}let t=Number(e.hostId);if(typeof e.epoch=="number"&&e.epoch>this.epoch&&(this.epoch=e.epoch&65535),t===this.hostPeerId)break;if(t===this.localPeerId&&!this.isHost){let s=this.hostPeerId;this.roster.delete(s),this.becomeHost()}else this.setHost(t);break}case"join_request":this.emit("joinRequested",U(e.peer));break;case"lobby":{let t=e;this.meta={...t.meta},t.resumeToken&&t.resumeToken!==this.resumeToken&&(this.resumeToken=t.resumeToken,this.seatSink?.(this.resumeToken)),this.roster=new Map(t.peers.map(s=>[s.peerId,U(s)])),t.hostId!==this.hostPeerId&&!this.isHost&&this.setHost(t.hostId),this.setPhase(t.phase);break}case"meta":this.meta={...e.meta},this.emit("metaChanged",this.meta);break;case"phase":this.setPhase(e.phase);break;case"signal":this.onSignal(Number(e.from),e);break;case"error":if(e.code==="kicked"){let t=e.data??{},s={};typeof t.closeCode=="number"&&(s.code=t.closeCode),typeof t.closeReason=="string"&&(s.reason=t.closeReason),this.teardown(t.ban===!1?"disconnected":"kicked",s.code!==void 0||s.reason!==void 0?s:void 0)}else this.reportError(new d(String(e.code),String(e.msg??""),e.data));break}}async onSignal(e,t){let s=this.links.get(e),i={};if(t.sdp&&(i.sdp=t.sdp),t.ice&&(i.ice=t.ice),t.sdp?.type==="offer"){s=this.makeLink(e);let n=this.earlyIce.get(e)??[];this.earlyIce.delete(e);let o=s.handleSignal(i);for(let a of n)s.handleSignal(a);await o;return}if(!s){if(!i.ice||this.ended||!this.roster.has(e))return;let n=this.earlyIce.get(e)??[];n.length<64&&n.push(i),this.earlyIce.set(e,n);return}await s.handleSignal(i)}removePeer(e,t,s,i=!1){let n=this.roster.get(e);if(e===this.localPeerId){this.teardown(t==="kicked"?"kicked":t);return}n&&this.roster.delete(e),this.earlyIce.delete(e);let o=this.links.get(e);if(o&&e!==this.hostPeerId)if(o.onMessage=null,this.links.delete(e),this.hiSentTo.delete(o),i){this.lingering.add(o);let a=()=>{this.lingering.delete(o)&&(c(),o.onClose=null,o.close())},c=this.cfg.clock.setTimeout(a,gt);o.onClose=a}else o.onClose=null,o.close();this.connected.delete(e),this.outbox.delete(e),this.lastHeard.delete(e),this.rate.delete(e),this.lockstep.onPeerLeft(e),this.chat.onPeerLeft(e),this.voice.onPeerLeft(e),n&&this.emit("peerLeft",n,t,s),e===this.hostPeerId&&!this.isHost&&this.handleHostLost(t)}dropPeer(e,t){this.signaling.send({t:"kick",peerId:e,ban:!1,closeReason:t}),this.removePeer(e,t,void 0,!0)}setPhase(e){e!=="lobby"&&e!=="in_game"||this.phase!==e&&(this.phase=e,e==="in_game"&&!this.lockstep.started&&this.lockstep.markStarted(),this.emit("phase",e))}onServerClosed(){if(!this.ended){if(this.cfg.direct&&!this.isHost){this.teardown("host_left");return}this.serverUp=!1,this.emit("serverLost"),this.links.size===0&&this.roster.size<=1&&this.teardown("server_lost")}}watchdog(){if(this.ended)return;let e=this.cfg.peerTimeoutMs??0;e>0&&this.dropSilent(Math.max(e,Re)),!(this.ended||this.isHost)&&this.mode==="lockstep"&&this.lockstep.hostSilent()&&(this.lockstep.touch(),this.lockstep.pokeHost(),this.handleHostLost("timeout"))}dropSilent(e){let t=this.cfg.clock.now();if(this.isHost)for(let s of[...this.connected]){let i=this.lastHeard.get(s);i!==void 0&&t-i>e&&this.removePeer(s,"timeout")}else if(this.isReady){let s=this.lastHeard.get(this.hostPeerId);s!==void 0&&t-s>e&&(this.lastHeard.delete(this.hostPeerId),this.handleHostLost("timeout"))}}pingHost(){this.ended||this.isHost||this.sendTo(this.hostPeerId,{type:"ping",n:++this.pingN,t:this.cfg.clock.now()>>>0},0,"fast")}teardown(e,t){if(!this.ended){if(this.ended=!0,this.cancelReadyTimer?.(),!this.isReady){let s=e==="ice_failed"||e==="kicked"?e:"closed";this.failReady(new d(s,`the lobby ended before connecting (${e})`))}this.cancelWatchdog?.(),this.cancelPing?.(),this.lockstep.stop(),this.voice.close();for(let s of[...this.links.values(),...this.lingering])s.onClose=null,s.onMessage=null,s.close();this.links.clear(),this.earlyIce.clear(),this.lingering.clear(),this.signaling.onClose=null,this.signaling.onMessage=null,this.signaling.close(),this.emit("sessionEnded",e,t)}}get linkCount(){return this.links.size}};var j={reliable:0,fast:1,voice:2},Ae=r=>new WebSocket(r);function se(){return typeof AudioEncoder<"u"&&typeof AudioDecoder<"u"&&typeof AudioData<"u"}var Ie={createEncoder(r,e){let t=0,s=new AudioEncoder({output:i=>{let n=new Uint8Array(i.byteLength);i.copyTo(n),e(n)},error:i=>console.error("onelobby: AudioEncoder",i)});return s.configure({codec:"opus",sampleRate:48e3,numberOfChannels:1,bitrate:r.bitrate,opus:{frameDuration:2e4,application:"voip",useinbandfec:!0,usedtx:!1,packetlossperc:10}}),{encode(i){let n=new AudioData({format:"f32",sampleRate:48e3,numberOfFrames:i.length,numberOfChannels:1,timestamp:t,data:i});t+=2e4,s.encode(n),n.close()},close(){s.state!=="closed"&&s.close()}}},createDecoder(r){let e=0,t=new AudioDecoder({output:s=>{let i=new Float32Array(s.numberOfFrames);s.copyTo(i,{planeIndex:0,format:"f32-planar"}),s.close(),r(i)},error:s=>console.error("onelobby: AudioDecoder",s)});return t.configure({codec:"opus",sampleRate:48e3,numberOfChannels:1}),{decode(s){if(s===null){r(new Float32Array(960));return}t.decode(new EncodedAudioChunk({type:"key",timestamp:e,data:s})),e+=2e4},close(){t.state!=="closed"&&t.close()}}}},xt=`
|
|
3
3
|
class OneLobbyCapture extends AudioWorkletProcessor {
|
|
4
4
|
constructor() { super(); this.buf = new Float32Array(${960}); this.n = 0; }
|
|
@@ -17,7 +17,7 @@ class OneLobbyCapture extends AudioWorkletProcessor {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
registerProcessor('onelobby-capture', OneLobbyCapture);
|
|
20
|
-
`,N=class{ctx=null;stream=null;async start(e){this.stream=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0}}),this.ctx=new AudioContext({sampleRate:48e3});let t=URL.createObjectURL(new Blob([xt],{type:"text/javascript"}));try{await this.ctx.audioWorklet.addModule(t)}finally{URL.revokeObjectURL(t)}let s=this.ctx.createMediaStreamSource(this.stream),i=new AudioWorkletNode(this.ctx,"onelobby-capture",{numberOfInputs:1,numberOfOutputs:0});i.port.onmessage=n=>e(n.data),s.connect(i),this.ctx.state==="suspended"&&await this.ctx.resume()}stop(){for(let e of this.stream?.getTracks()??[])e.stop();this.ctx?.close(),this.stream=null,this.ctx=null}},D=class{ctx=new AudioContext({sampleRate:48e3});next=new Map;constructor(){this.ctx.resume()}play(e,t,s){if(t.length===0)return;let i=this.ctx.createBuffer(1,t.length,48e3);i.copyToChannel(t,0);let n=this.ctx.createBufferSource();n.buffer=i;let o=this.ctx.createGain();o.gain.value=s,n.connect(o).connect(this.ctx.destination);let a=this.ctx.currentTime,c=this.next.get(e)??0;c<a+.01&&(c=a+.02),n.start(c),this.next.set(e,c+t.length/48e3)}close(){this.ctx.close()}};var St={reliable:{negotiated:!0,id:j.reliable,ordered:!0},fast:{negotiated:!0,id:j.fast,ordered:!1,maxRetransmits:0},voice:{negotiated:!0,id:j.voice,ordered:!1,maxRetransmits:0}},q=class{remoteId;isOpen=!1;onSignal=null;onOpen=null;onClose=null;onMessage=null;onPath=null;pc;channels={};pendingIce=[];haveRemote=!1;closed=!1;opened=0;early=[];extra;constructor(e,t=globalThis.RTCPeerConnection){this.remoteId=e.remoteId,this.extra=e.extraRemoteCandidates??[];let s={iceServers:e.iceServers,iceTransportPolicy:e.relayOnly?"relay":"all"};e.portRange&&(s.portRangeBegin=e.portRange.begin,s.portRangeEnd=e.portRange.end,e.portRange.begin===e.portRange.end&&(s.enableIceUdpMux=!0)),this.pc=new t(s),this.pc.onicecandidate=i=>{i.candidate&&this.onSignal?.({ice:i.candidate.toJSON()})},this.pc.onconnectionstatechange=()=>{let i=this.pc.connectionState;(i==="failed"||i==="closed"||i==="disconnected")&&(i==="disconnected"?setTimeout(()=>this.pc.connectionState==="disconnected"&&this.fail("disconnected"),4e3):this.fail(i))}}openChannels(){if(!this.channels.reliable)for(let e of["reliable","fast","voice"]){let t=this.pc.createDataChannel(e,St[e]);t.binaryType="arraybuffer",t.onopen=()=>{if(++this.opened===3&&!this.closed){this.isOpen=!0,this.onOpen?.();for(let[s,i]of this.early.splice(0))this.closed||this.onMessage?.(s,i);this.reportPath()}},t.onclose=()=>this.fail(`channel ${e} closed`),t.onmessage=s=>{let i=s.data,n=i instanceof ArrayBuffer?new Uint8Array(i):ArrayBuffer.isView(i)?new Uint8Array(i.buffer,i.byteOffset,i.byteLength):new Uint8Array(i);if(!this.isOpen){this.early.length<1e3&&this.early.push([e,n]);return}this.onMessage?.(e,n)},this.channels[e]=t}}startOffer(){(async()=>{try{this.openChannels();let e=await this.pc.createOffer();await this.pc.setLocalDescription(e),this.onSignal?.({sdp:{type:"offer",sdp:e.sdp??""}})}catch(e){this.fail(`offer failed: ${String(e)}`)}})()}async handleSignal(e){try{if(e.sdp){await this.pc.setRemoteDescription({type:e.sdp.type,sdp:e.sdp.sdp}),this.haveRemote=!0;for(let t of this.pendingIce.splice(0))await this.pc.addIceCandidate(t);if(this.extra.length){let t=/^a=mid:(\S+)/m.exec(e.sdp.sdp)?.[1]??"0";for(let s of this.extra.splice(0))await this.pc.addIceCandidate({candidate:s,sdpMid:t,sdpMLineIndex:0})}if(e.sdp.type==="offer"){this.openChannels();let t=await this.pc.createAnswer();await this.pc.setLocalDescription(t),this.onSignal?.({sdp:{type:"answer",sdp:t.sdp??""}})}}e.ice&&(this.haveRemote?await this.pc.addIceCandidate(e.ice):this.pendingIce.push(e.ice))}catch(t){this.fail(`signal failed: ${String(t)}`)}}async reportPath(e=0){if(!(this.closed||e>10)){try{let t=await this.pc.getStats(),s=new Map;t.forEach(a=>s.set(a.id,a));let i;t.forEach(a=>{a.type==="transport"&&a.selectedCandidatePairId&&(i=s.get(a.selectedCandidatePairId))}),i||t.forEach(a=>{a.type==="candidate-pair"&&(a.selected||a.nominated&&a.state==="succeeded")&&(i=a)});let n=i&&s.get(i.localCandidateId),o=i&&s.get(i.remoteCandidateId);if(n&&o){this.onPath?.(n.candidateType==="relay"||o.candidateType==="relay"?"relayed":"direct");return}}catch{}setTimeout(()=>{this.reportPath(e+1)},300)}}send(e,t){let s=this.channels[e];s&&s.readyState==="open"&&s.send(t)}close(){this.fail("closed locally")}fail(e){if(this.closed)return;this.closed=!0;let t=this.isOpen;this.isOpen=!1;try{this.pc.close()}catch{}this.onClose?.(e)}},Oe={create:r=>new q(r)};function Pt(r){return{create:e=>new q(e,r)}}var He=11;function Fe(r,e){let t=0;for(let s=e-1;s>=0;s--){let i=Number(r>>BigInt(s)&1n),n=t>>4&1;t=t<<1&31,n^i&&(t^=5)}return t}function Ct(r){let e=r.split(".");if(e.length!==4)return null;let t=0;for(let s of e){if(!/^\d{1,3}$/.test(s)||Number(s)>255)return null;t=t*256+Number(s)}return t}function Ue(r,e){let t=Ct(r);if(t===null)throw new Error(`not an IPv4 address: ${r}`);if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`bad port ${e}`);let s=BigInt(t)<<16n|BigInt(e),i=s<<5n|BigInt(Fe(s,50)),n="";for(let o=0;o<He;o++)n=k[Number(i&31n)]+n,i>>=5n;return`${n.slice(0,4)}-${n.slice(4,8)}-${n.slice(8)}`}function je(r){let e=r.replace(/[ \-]/g,"").replace(/[a-z]/g,t=>t.toUpperCase()).replace(/[IL]/g,"1").replace(/O/g,"0");if(e.length!==He)return null;for(let t of e)if(k.indexOf(t)<0)return null;return e}function ie(r){let e=je(r);if(!e)return{error:"bad_code"};let t=0n;for(let o of e)t=t<<5n|BigInt(k.indexOf(o));let s=t>>5n;if(Fe(s,50)!==Number(t&31n))return{error:"bad_code"};if(s>>48n!==0n)return{error:"unknown_version"};let i=Number(s&0xffffn);if(i===0)return{error:"bad_code"};let n=Number(s>>16n&0xffffffffn);return{ip:[n>>>24,n>>>16&255,n>>>8&255,n&255].join("."),port:i}}function ne(r){return!/[.:]/.test(r)&&je(r)!==null}var Tt=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/,Et=/^(?=.{1,253}$)([A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/;function Ne(r){let e=Tt.exec(r);return!!e&&e.slice(1).every(t=>Number(t)<=255)}function Lt(r){if(!/^[0-9A-Fa-f:.]+$/.test(r)||r.split("::").length>2||!r.includes(":"))return!1;let e=r.split(":").pop()??"",t=r.split(":").filter((s,i,n)=>s!==""||i>0&&i<n.length-1);return e.includes(".")&&!Ne(e)?!1:t.every(s=>s===""||s.includes(".")||/^[0-9A-Fa-f]{1,4}$/.test(s))}function De(r,e=47810){let t=r.trim();if(/^ws:\/\//i.test(t)&&(t=t.slice(5)),t=t.replace(/\/+$/,""),!t)return null;let s,i=null;if(t[0]==="["){let c=t.indexOf("]");if(c<0)return null;s=t.slice(1,c);let l=t.slice(c+1);if(l){if(l[0]!==":")return null;i=l.slice(1)}}else{let c=t.indexOf(":");c>=0&&c===t.lastIndexOf(":")?(s=t.slice(0,c),i=t.slice(c+1)):s=t}let n=e;if(i!==null&&(!/^\d{1,5}$/.test(i)||(n=Number(i),n<1||n>65535)))return null;let o=Lt(s);if(!o&&!Ne(s)&&!Et.test(s))return null;let a=(o?`[${s}]`:s)+`:${n}`;return{host:s,port:n,isIPv6:o,display:a,url:`ws://${a}/`,candidate:`candidate:1 1 UDP 2122317823 ${s} ${n} typ host`}}var $e="onelobby",Be="0.3.0",Ve=15e3;function Mt(){return typeof navigator<"u"&&!!navigator.mediaDevices?.getUserMedia&&typeof AudioWorkletNode<"u"}function Rt(){try{return globalThis.sessionStorage??null}catch{return null}}var re=new Set;function qe(r){for(let e of re)if(e.resumeToken===r)return!0;return!1}var b=class extends p{options;socketFactory;linkFactory;clock;wall;voiceCodec;createMic;createSink;storage;constructor(e){super();let t=e.tickRate??20;this.options={gameId:e.gameId,netVersion:e.netVersion,lobbyServer:e.lobbyServer,mode:e.mode??"lockstep",tickRate:t,hashEveryTicks:e.hashEveryTicks??t,displayName:(e.displayName??"").trim(),relayOnly:e.relayOnly??!1,readyTimeoutMs:e.readyTimeoutMs??Ve,peerTimeoutMs:e.peerTimeoutMs??0};let s=e.advanced??{},i=c=>s[c]!==void 0?s[c]:e[c];this.socketFactory=i("socketFactory")??Ae,this.linkFactory=i("linkFactory")??Oe,this.clock=i("clock")??he,this.wall=i("wallClock")??(()=>Date.now());let n=i("voiceCodec");this.voiceCodec=n!==void 0?n:se()?Ie:null;let o=i("createMic");this.createMic=o!==void 0?o:Mt()?()=>new N:null;let a=i("createSink");this.createSink=a!==void 0?a:typeof AudioContext<"u"?()=>new D:null,this.storage=e.rememberSeat===!1?null:s.storage!==void 0?s.storage:Rt()}get seatKey(){return`onelobby.seat.${this.options.gameId}`}loadSeat(){if(!this.storage)return null;try{let e=this.storage.getItem(this.seatKey);if(!e)return null;let t=JSON.parse(e);return typeof t.code!="string"||typeof t.token!="string"||t.server!==this.options.lobbyServer?null:t}catch{return null}}saveSeat(e,t){if(!(!this.storage||!t))try{this.storage.setItem(this.seatKey,JSON.stringify({code:e,token:t,server:this.options.lobbyServer}))}catch{}}clearSeat(e){if(this.storage)try{if(e!==void 0){let t=this.loadSeat();if(t&&t.code!==e)return}this.storage.removeItem(this.seatKey)}catch{}}get rememberedCode(){return this.loadSeat()?.code??null}async resume(){let e=this.loadSeat();if(!e||qe(e.token))return null;try{return await this.rejoin(e.token)}catch(t){if(t instanceof d&&t.code==="invalid_token")return this.clearSeat(),null;throw t}}helloMsg(e){return{t:"hello",proto:Ce,gameId:this.options.gameId,netVersion:this.options.netVersion,client:{lib:$e,ver:Be,platform:typeof navigator<"u"?"browser":"node"},name:this.options.displayName.slice(0,24),...e?{resumeToken:e}:{}}}async connect(e=this.options.lobbyServer,t=this.socketFactory,s=0){let i=new S(e,t,this.clock);if(s>0){let n=()=>{},o=new Promise((a,c)=>{n=this.clock.setTimeout(()=>c(new d("direct_unreachable",`no answer from ${e} within ${s} ms`)),s)});try{await Promise.race([i.connect(),o])}catch(a){throw i.close(),a instanceof d&&a.code==="connect_failed"?new d("direct_unreachable",`no answer from ${e}`):a}finally{n()}}else await i.connect();try{let n=await i.request(this.helloMsg(),"welcome");return{sig:i,welcome:n}}catch(n){throw i.close(),n}}makeLobby(e,t,s,i,n){let o=new C({signaling:e,state:s,ice:t.ice??[],links:i?.links??this.linkFactory,direct:!!i,clock:this.clock,wall:this.wall,hashEveryTicks:this.options.hashEveryTicks,relayOnly:s.relayOnly??this.options.relayOnly,displayName:this.options.displayName,voice:{codec:this.voiceCodec,createSource:this.createMic,createSink:this.createSink},readyTimeoutMs:this.options.readyTimeoutMs,peerTimeoutMs:this.options.peerTimeoutMs,rateLimit:n});return o.errorSink=a=>this.emit("error",a),this.storage&&!i&&(re.add(o),this.saveSeat(o.code,o.resumeToken),o.seatSink=a=>this.saveSeat(o.code,a),o.on("sessionEnded",()=>{re.delete(o),this.clearSeat(o.code)})),o}async host(e){let{sig:t,welcome:s}=await this.connect();try{let i=await t.request({t:"create",name:e.name,maxPlayers:e.maxPlayers,visibility:e.visibility??"unlisted",mode:this.options.mode,tickRate:this.options.tickRate,lateJoin:e.lateJoin??!0,meta:e.meta??{},...this.options.relayOnly?{relayOnly:!0}:{}},"lobby");return this.makeLobby(t,s,i,void 0,e.rateLimit)}catch(i){throw t.close(),i}}async join(e){let t=m(e);if(!t)throw new d("bad_code",`"${e}" is not a valid join code`);let s=this.loadSeat();if(s&&s.code===t&&!qe(s.token))try{return await this.rejoin(s.token)}catch(o){if(this.clearSeat(),!(o instanceof d&&o.code==="invalid_token"))throw o}let{sig:i,welcome:n}=await this.connect();try{let o=await i.request({t:"join",code:t},"lobby");return this.makeLobby(i,n,o)}catch(o){throw i.close(),o}}async joinDirect(e,t={}){let s=e.trim();if(ne(s)){let l=ie(s);if("error"in l)throw new d(l.error,l.error==="unknown_version"?`"${s}" is a join code from a newer version of the game`:`"${s}" is not a valid join code: check for a typo`);s=`${l.ip}:${l.port}`}let i=De(s,t.defaultPort??47810);if(!i)throw new d("bad_address",`"${e}" is not a join code or an address (try 6B01-RGFE-P2B or 192.168.1.20:47810)`);let n=this.linkFactory,o={create:l=>n.create({...l,iceServers:[],extraRemoteCandidates:[i.candidate]})},{sig:a,welcome:c}=await this.connect(i.url,this.socketFactory,t.connectTimeoutMs??5e3);try{let l=await a.request({t:"join",...t.password?{password:t.password}:{}},"lobby"),h=this.makeLobby(a,c,l,{links:o});return h.joinCode=At(i),h}catch(l){throw a.close(),l}}async hostDirectWith(e,t){let{sig:s,welcome:i}=await this.connect("onelobby-direct://host/",t.socketFactory);try{let n=await s.request({t:"create",name:e.name,maxPlayers:e.maxPlayers,visibility:"unlisted",mode:this.options.mode,tickRate:this.options.tickRate,lateJoin:e.lateJoin??!0,meta:e.meta??{}},"lobby");return this.makeLobby(s,i,n,{links:t.wrapLinks(this.linkFactory)},e.rateLimit)}catch(n){throw s.close(),n}}async rejoin(e){let t=new S(this.options.lobbyServer,this.socketFactory,this.clock);await t.connect();try{let s=t.expect("lobby"),i=t.request(this.helloMsg(e),"welcome");s.catch(()=>{});let n=await i;if(n.peerId===void 0)throw new d("invalid_token","resume token unknown or expired");let o=await s;return this.makeLobby(t,n,o)}catch(s){throw t.close(),s}}async list(e={}){let{sig:t}=await this.connect();try{return(await t.request({t:"list",visibility:"public",meta:e.meta??{},limit:Math.min(100,e.limit??50),...e.cursor?{cursor:e.cursor}:{}},"lobbies")).items??[]}finally{t.close()}}};function At(r){if(r.isIPv6)return r.display;try{return Ue(r.host,r.port)}catch{return r.display}}var $={unknown_code:{message:"We couldn't find a lobby with that code.",hint:"Check the code with the host. Codes stop working once everyone leaves."},bad_code:{message:"That doesn't look like a lobby code.",hint:"Codes are 6 letters and numbers, like 7HQ 4K2."},lobby_full:{message:"That lobby is full.",hint:"Ask the host to make room, or start your own lobby."},version_mismatch:{message:"You and the host are running different versions of the game.",hint:"Reload the page (or update the game) on both sides, then try again."},in_game:{message:"That game has already started.",hint:"Ask the host to let people join mid-game, or wait for the next round."},kicked:{message:"The host removed you from the lobby."},denied:{message:"The host didn't let you in this time.",hint:"Ask the host to approve you, then try again."},not_host:{message:"Only the host can do that."},rate_limited:{message:"Slow down a little: too many tries in a short time.",hint:"Wait a moment, then try again."},max_lobbies:{message:"You already have the most lobbies open that this server allows.",hint:"Close one of your other lobbies, then try again."},server_full:{message:"The game server is full right now.",hint:"Try again in a few minutes."},game_not_allowed:{message:"This game server doesn't host this game.",hint:"The game's server setting may be wrong; let the game's owner know."},bad_request:{message:"The game sent something the server didn't understand.",hint:"Reload the page and try again. If it keeps happening, the game may need an update."},connect_failed:{message:"Couldn't reach the game server.",hint:"Check your internet connection, then try again."},closed:{message:"Lost the connection to the game server.",hint:"Check your internet connection, then try again."},server_lost:{message:"Lost the connection to the game server.",hint:"Your game may keep going; new players can't join until it's back."},invalid_token:{message:"Your old seat in that lobby is no longer available.",hint:"Join again with the lobby code."},ice_failed:{message:"Couldn't connect to the other players.",hint:"Some networks (school, work, public Wi-Fi) block game connections. Try another network or a phone hotspot."},timeout:{message:"The connection took too long.",hint:"Check your internet connection, then try again."},left:{message:"You left the lobby."},unknown:{message:"Something went wrong.",hint:"Try again. If it keeps happening, reload the page."}},It=/^[a-z][a-z0-9_]*$/;function Ot(r){if(typeof r=="string"){let e=/^([a-z][a-z0-9_]*)(?::|$)/.exec(r.trim());return{code:e?e[1]:null}}if(r&&typeof r=="object"){let e=r,t=e.data&&typeof e.data=="object"?e.data:void 0;if(typeof e.code=="string"&&It.test(e.code))return{code:e.code,data:t};if(typeof e.message=="string"){let s=/^([a-z][a-z0-9_]*):/.exec(e.message);if(s&&$[s[1]])return{code:s[1],data:t};if(e.name==="NetworkError"||/network|failed to fetch|websocket/i.test(e.message))return{code:"connect_failed"};if(e.name==="TimeoutError"||/timed? ?out/i.test(e.message))return{code:"timeout"}}}return{code:null}}function oe(r){let{code:e,data:t}=Ot(r);if(!e)return{code:"unknown",...$.unknown};let s=e;e==="kicked"&&t?.reason==="denied"&&(s="denied"),e==="rate_limited"&&t?.reason==="max_lobbies"&&(s="max_lobbies");let i=$[s];if(!i)return{code:e,...$.unknown};let n={code:e,message:i.message};if(i.hint&&(n.hint=i.hint),e==="rate_limited"&&typeof t?.retryAfterMs=="number"&&s==="rate_limited"){let o=Math.max(1,Math.ceil(t.retryAfterMs/1e3));n.hint=`Wait ${o} second${o===1?"":"s"}, then try again.`}return n}function Ke(){try{return typeof location<"u"?location.href:void 0}catch{return}}function ae(r,e,t="join"){let s=m(r);if(!s)throw new Error(`"${r}" is not a valid lobby code`);let i=e??Ke();if(!i)throw new Error("shareUrl needs a base URL outside the browser");let n=new URL(i);return n.searchParams.delete(t),n.searchParams.set(t,y(s).replace(" ","-")),n.toString()}function ce(r,e="join"){let t=r??Ke();if(!t)return null;let s;try{s=new URL(t,"http://localhost/")}catch{return null}let i=s.searchParams.get(e)??new URLSearchParams(s.hash.replace(/^#/,"")).get(e);return i?m(i):null}var Ht=b,Ft=d;var Ut=new TextEncoder,jt=new TextDecoder,B=r=>Ut.encode(JSON.stringify(r)),V=class{constructor(e){this.c=e}c;values=new Map;get(e){return this.values.get(e)}set(e,t){this.c._sharedOp({k:"set",key:String(e),v:t===void 0?null:t})}add(e,t=1){this.c._sharedOp({k:"add",key:String(e),n:Number(t)||0})}all(){return Object.fromEntries(this.values)}keys(){return[...this.values.keys()]}},T=class{constructor(e){this.opts=e}opts;state="idle";lobby=null;error=null;linking=!1;name="";shared=new V(this);onChange=()=>{};onEvent=()=>{};lastAction=null;unsub=[];gen=0;seq=0;get isHost(){return!!this.lobby&&this.lobby.isHost}get code(){return this.lobby?.code??null}get players(){let e=this.lobby;if(!e)return[];let t=e.peers.map(s=>this.toPlayer(s));return t.some(s=>s.isMe)||t.push({id:e.localPeerId,name:this.name||`Player ${e.localPeerId}`,isHost:e.isHost,isMe:!0}),t.sort((s,i)=>s.id-i.id)}get statusText(){switch(this.state){case"idle":return"Host a game, or type a code to join one.";case"connecting":return this.lastAction?.kind==="join"?"Joining\u2026":this.lastAction?.kind==="resume"?"Getting your seat back\u2026":"Creating a game\u2026";case"error":return this.error?.message??"Something went wrong.";case"lobby":{if(this.linking)return`Connecting to ${this.hostName}\u2026`;if(this.isHost){let e=this.players.length;return e<=1?"Waiting for players\u2026 Share the code with your friends.":`${e} players here. Press Start when everyone's in.`}return`Connected! Waiting for ${this.hostName} to start.`}case"playing":return this.linking?`Connecting to ${this.hostName}\u2026`:"Game on!"}}get hostName(){let e=this.lobby;return e&&e.peer(e.hostPeerId)?.name||"the host"}toPlayer(e,t){let s=this.lobby,i=e?.peerId??t??0;return{id:i,name:e?.name||i===s.localPeerId&&this.name||`Player ${i}`,isHost:i===s.hostPeerId,isMe:i===s.localPeerId}}host(e){return this.lastAction={kind:"host"},this.run(e,t=>t.host({name:this.opts.lobbyName??"game",maxPlayers:typeof this.opts.maxPlayers=="function"?this.opts.maxPlayers():this.opts.maxPlayers,visibility:"unlisted",lateJoin:!0}))}join(e,t){let s=m(e);return this.lastAction={kind:"join",code:e},s?this.run(t,i=>i.join(s)):(this.fail(new d("bad_code")),Promise.resolve())}async resume(e){let t=this.opts.makeKit(e);return!t.resume||!t.rememberedCode?!1:(this.lastAction={kind:"resume"},await this.run(e,()=>t.resume(),!0),!!this.lobby)}retry(){let e=this.lastAction;return!e||e.kind==="resume"?(this.reset(),Promise.resolve()):e.kind==="host"?this.host(this.name):this.join(e.code,this.name)}async start(){!this.lobby||!this.isHost||this.state==="playing"||await this.lobby.start()}async leave(){let e=this.lobby;this.detach(),await e?.leave(),this.reset()}reset(){this.detach(),this.state="idle",this.error=null,this.onChange()}send(e){this.lobby&&this.lobby.broadcast(B({k:"m",d:e===void 0?null:e}))}async run(e,t,s=!1){this.detach();let i=++this.gen;this.name=(e||"").trim().slice(0,24),this.state="connecting",this.error=null,this.onChange();let n;try{n=await t(this.opts.makeKit(this.name))}catch(o){if(i!==this.gen)return;s?this.reset():this.fail(o);return}if(i!==this.gen){n?.leave();return}n?this.attach(n):this.reset()}attach(e){this.lobby=e,e.isHost||this.shared.values.clear(),this.seq=0,this.state=e.phase==="in_game"?"playing":"lobby",this.linking=!e.isHost;let t=this.unsub;t.push(e.on("ready",()=>this.linked())),t.push(e.on("peerJoined",s=>{this.onEvent("player-joined",{player:this.toPlayer(s)}),this.onChange()})),t.push(e.on("peerLeft",(s,i)=>{this.onEvent("player-left",{player:this.toPlayer(s),reason:i}),this.onChange()})),t.push(e.on("peerReady",s=>{e.isHost&&s.peerId!==e.localPeerId&&e.send(s.peerId,B({k:"SS",all:this.shared.all(),q:this.seq}))})),t.push(e.on("hostChanged",()=>this.onChange())),t.push(e.on("phase",s=>{s==="in_game"&&this.state!=="playing"&&(this.state="playing",this.onChange(),this.onEvent("start",{}))})),t.push(e.on("message",(s,i)=>this.onRaw(s,i))),t.push(e.on("sessionEnded",s=>{this.lobby===e&&s!=="left"&&this.fail(new d(s))})),e.ready.then(()=>{this.lobby===e&&this.linked()},()=>{}),this.onChange(),this.state==="playing"&&!this.linking&&this.onEvent("start",{})}linked(){this.linking&&(this.linking=!1,this.onChange(),this.state==="playing"&&this.onEvent("start",{}))}detach(){this.gen++;for(let e of this.unsub.splice(0))e();this.lobby=null,this.linking=!1}fail(e){let t=this.lobby;this.detach(),t&&t.leave(),this.state="error",this.error=oe(e),this.onChange(),this.onEvent("error",this.error)}playerById(e){let t=this.lobby;return!t||!e?null:this.toPlayer(t.peer(e),e)}onRaw(e,t){let s;try{s=JSON.parse(jt.decode(t))}catch{return}if(!s||typeof s!="object")return;let i=this.lobby;switch(s.k){case"m":this.onEvent("message",{from:this.playerById(e),data:s.d});break;case"set":case"add":i.isHost&&this.applyHost(s,e);break;case"S":!i.isHost&&e===i.hostPeerId&&!(s.q<=this.seq)&&(this.seq=s.q,this.applyValue(s.key,s.v,s.by));break;case"SS":if(!i.isHost&&e===i.hostPeerId&&s.all&&typeof s.all=="object"&&!(s.q<this.seq)){this.seq=s.q;for(let n of[...this.shared.values.keys()])n in s.all||this.applyValue(n,null,0);for(let[n,o]of Object.entries(s.all))this.applyValue(n,o,0)}break}}_sharedOp(e){let t=this.lobby;if(!t){let s=e.k==="set"?e.v:(Number(this.shared.values.get(e.key))||0)+e.n;this.applyValue(e.key,s,0);return}t.isHost?this.applyHost(e,t.localPeerId):t.send(t.hostPeerId,B(e))}applyHost(e,t){let s=this.lobby;if(typeof e.key!="string")return;let i=e.k==="set"?e.v:(Number(this.shared.values.get(e.key))||0)+(Number(e.n)||0);this.applyValue(e.key,i,t),s.broadcast(B({k:"S",key:e.key,v:i,by:t,q:++this.seq}))}applyValue(e,t,s){t===null?this.shared.values.delete(e):this.shared.values.set(e,t),this.onEvent("shared-change",{key:e,value:t,from:this.playerById(s)})}};var Je=`
|
|
20
|
+
`,N=class{ctx=null;stream=null;async start(e){this.stream=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0}}),this.ctx=new AudioContext({sampleRate:48e3});let t=URL.createObjectURL(new Blob([xt],{type:"text/javascript"}));try{await this.ctx.audioWorklet.addModule(t)}finally{URL.revokeObjectURL(t)}let s=this.ctx.createMediaStreamSource(this.stream),i=new AudioWorkletNode(this.ctx,"onelobby-capture",{numberOfInputs:1,numberOfOutputs:0});i.port.onmessage=n=>e(n.data),s.connect(i),this.ctx.state==="suspended"&&await this.ctx.resume()}stop(){for(let e of this.stream?.getTracks()??[])e.stop();this.ctx?.close(),this.stream=null,this.ctx=null}},D=class{ctx=new AudioContext({sampleRate:48e3});next=new Map;constructor(){this.ctx.resume()}play(e,t,s){if(t.length===0)return;let i=this.ctx.createBuffer(1,t.length,48e3);i.copyToChannel(t,0);let n=this.ctx.createBufferSource();n.buffer=i;let o=this.ctx.createGain();o.gain.value=s,n.connect(o).connect(this.ctx.destination);let a=this.ctx.currentTime,c=this.next.get(e)??0;c<a+.01&&(c=a+.02),n.start(c),this.next.set(e,c+t.length/48e3)}close(){this.ctx.close()}};var St={reliable:{negotiated:!0,id:j.reliable,ordered:!0},fast:{negotiated:!0,id:j.fast,ordered:!1,maxRetransmits:0},voice:{negotiated:!0,id:j.voice,ordered:!1,maxRetransmits:0}},q=class{remoteId;isOpen=!1;onSignal=null;onOpen=null;onClose=null;onMessage=null;onPath=null;pc;channels={};pendingIce=[];haveRemote=!1;closed=!1;opened=0;early=[];extra;constructor(e,t=globalThis.RTCPeerConnection){this.remoteId=e.remoteId,this.extra=e.extraRemoteCandidates??[];let s={iceServers:e.iceServers,iceTransportPolicy:e.relayOnly?"relay":"all"};e.portRange&&(s.portRangeBegin=e.portRange.begin,s.portRangeEnd=e.portRange.end,e.portRange.begin===e.portRange.end&&(s.enableIceUdpMux=!0)),this.pc=new t(s),this.pc.onicecandidate=i=>{i.candidate&&this.onSignal?.({ice:i.candidate.toJSON()})},this.pc.onconnectionstatechange=()=>{let i=this.pc.connectionState;(i==="failed"||i==="closed"||i==="disconnected")&&(i==="disconnected"?setTimeout(()=>this.pc.connectionState==="disconnected"&&this.fail("disconnected"),4e3):this.fail(i))}}openChannels(){if(!this.channels.reliable)for(let e of["reliable","fast","voice"]){let t=this.pc.createDataChannel(e,St[e]);t.binaryType="arraybuffer",t.onopen=()=>{if(++this.opened===3&&!this.closed){this.isOpen=!0,this.onOpen?.();for(let[s,i]of this.early.splice(0))this.closed||this.onMessage?.(s,i);this.reportPath()}},t.onclose=()=>this.fail(`channel ${e} closed`),t.onmessage=s=>{let i=s.data,n=i instanceof ArrayBuffer?new Uint8Array(i):ArrayBuffer.isView(i)?new Uint8Array(i.buffer,i.byteOffset,i.byteLength):new Uint8Array(i);if(!this.isOpen){this.early.length<1e3&&this.early.push([e,n]);return}this.onMessage?.(e,n)},this.channels[e]=t}}startOffer(){(async()=>{try{this.openChannels();let e=await this.pc.createOffer();await this.pc.setLocalDescription(e),this.onSignal?.({sdp:{type:"offer",sdp:e.sdp??""}})}catch(e){this.fail(`offer failed: ${String(e)}`)}})()}async handleSignal(e){try{if(e.sdp){await this.pc.setRemoteDescription({type:e.sdp.type,sdp:e.sdp.sdp}),this.haveRemote=!0;for(let t of this.pendingIce.splice(0))await this.pc.addIceCandidate(t);if(this.extra.length){let t=/^a=mid:(\S+)/m.exec(e.sdp.sdp)?.[1]??"0";for(let s of this.extra.splice(0))await this.pc.addIceCandidate({candidate:s,sdpMid:t,sdpMLineIndex:0})}if(e.sdp.type==="offer"){this.openChannels();let t=await this.pc.createAnswer();await this.pc.setLocalDescription(t),this.onSignal?.({sdp:{type:"answer",sdp:t.sdp??""}})}}e.ice&&(this.haveRemote?await this.pc.addIceCandidate(e.ice):this.pendingIce.push(e.ice))}catch(t){this.fail(`signal failed: ${String(t)}`)}}async reportPath(e=0){if(!(this.closed||e>10)){try{let t=await this.pc.getStats(),s=new Map;t.forEach(a=>s.set(a.id,a));let i;t.forEach(a=>{a.type==="transport"&&a.selectedCandidatePairId&&(i=s.get(a.selectedCandidatePairId))}),i||t.forEach(a=>{a.type==="candidate-pair"&&(a.selected||a.nominated&&a.state==="succeeded")&&(i=a)});let n=i&&s.get(i.localCandidateId),o=i&&s.get(i.remoteCandidateId);if(n&&o){this.onPath?.(n.candidateType==="relay"||o.candidateType==="relay"?"relayed":"direct");return}}catch{}setTimeout(()=>{this.reportPath(e+1)},300)}}send(e,t){let s=this.channels[e];s&&s.readyState==="open"&&s.send(t)}close(){this.fail("closed locally")}fail(e){if(this.closed)return;this.closed=!0;let t=this.isOpen;this.isOpen=!1;try{this.pc.close()}catch{}this.onClose?.(e)}},Oe={create:r=>new q(r)};function Pt(r){return{create:e=>new q(e,r)}}var He=11;function Fe(r,e){let t=0;for(let s=e-1;s>=0;s--){let i=Number(r>>BigInt(s)&1n),n=t>>4&1;t=t<<1&31,n^i&&(t^=5)}return t}function Ct(r){let e=r.split(".");if(e.length!==4)return null;let t=0;for(let s of e){if(!/^\d{1,3}$/.test(s)||Number(s)>255)return null;t=t*256+Number(s)}return t}function Ue(r,e){let t=Ct(r);if(t===null)throw new Error(`not an IPv4 address: ${r}`);if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`bad port ${e}`);let s=BigInt(t)<<16n|BigInt(e),i=s<<5n|BigInt(Fe(s,50)),n="";for(let o=0;o<He;o++)n=k[Number(i&31n)]+n,i>>=5n;return`${n.slice(0,4)}-${n.slice(4,8)}-${n.slice(8)}`}function je(r){let e=r.replace(/[ \-]/g,"").replace(/[a-z]/g,t=>t.toUpperCase()).replace(/[IL]/g,"1").replace(/O/g,"0");if(e.length!==He)return null;for(let t of e)if(k.indexOf(t)<0)return null;return e}function ie(r){let e=je(r);if(!e)return{error:"bad_code"};let t=0n;for(let o of e)t=t<<5n|BigInt(k.indexOf(o));let s=t>>5n;if(Fe(s,50)!==Number(t&31n))return{error:"bad_code"};if(s>>48n!==0n)return{error:"unknown_version"};let i=Number(s&0xffffn);if(i===0)return{error:"bad_code"};let n=Number(s>>16n&0xffffffffn);return{ip:[n>>>24,n>>>16&255,n>>>8&255,n&255].join("."),port:i}}function ne(r){return!/[.:]/.test(r)&&je(r)!==null}var Tt=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/,Et=/^(?=.{1,253}$)([A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/;function Ne(r){let e=Tt.exec(r);return!!e&&e.slice(1).every(t=>Number(t)<=255)}function Lt(r){if(!/^[0-9A-Fa-f:.]+$/.test(r)||r.split("::").length>2||!r.includes(":"))return!1;let e=r.split(":").pop()??"",t=r.split(":").filter((s,i,n)=>s!==""||i>0&&i<n.length-1);return e.includes(".")&&!Ne(e)?!1:t.every(s=>s===""||s.includes(".")||/^[0-9A-Fa-f]{1,4}$/.test(s))}function De(r,e=47810){let t=r.trim();if(/^ws:\/\//i.test(t)&&(t=t.slice(5)),t=t.replace(/\/+$/,""),!t)return null;let s,i=null;if(t[0]==="["){let c=t.indexOf("]");if(c<0)return null;s=t.slice(1,c);let l=t.slice(c+1);if(l){if(l[0]!==":")return null;i=l.slice(1)}}else{let c=t.indexOf(":");c>=0&&c===t.lastIndexOf(":")?(s=t.slice(0,c),i=t.slice(c+1)):s=t}let n=e;if(i!==null&&(!/^\d{1,5}$/.test(i)||(n=Number(i),n<1||n>65535)))return null;let o=Lt(s);if(!o&&!Ne(s)&&!Et.test(s))return null;let a=(o?`[${s}]`:s)+`:${n}`;return{host:s,port:n,isIPv6:o,display:a,url:`ws://${a}/`,candidate:`candidate:1 1 UDP 2122317823 ${s} ${n} typ host`}}var $e="onelobby",Be="0.3.1",Ve=15e3;function Mt(){return typeof navigator<"u"&&!!navigator.mediaDevices?.getUserMedia&&typeof AudioWorkletNode<"u"}function Rt(){try{return globalThis.sessionStorage??null}catch{return null}}var re=new Set;function qe(r){for(let e of re)if(e.resumeToken===r)return!0;return!1}var b=class extends p{options;socketFactory;linkFactory;clock;wall;voiceCodec;createMic;createSink;storage;constructor(e){super();let t=e.tickRate??20;this.options={gameId:e.gameId,netVersion:e.netVersion,lobbyServer:e.lobbyServer,mode:e.mode??"lockstep",tickRate:t,hashEveryTicks:e.hashEveryTicks??t,displayName:(e.displayName??"").trim(),relayOnly:e.relayOnly??!1,readyTimeoutMs:e.readyTimeoutMs??Ve,peerTimeoutMs:e.peerTimeoutMs??0};let s=e.advanced??{},i=c=>s[c]!==void 0?s[c]:e[c];this.socketFactory=i("socketFactory")??Ae,this.linkFactory=i("linkFactory")??Oe,this.clock=i("clock")??he,this.wall=i("wallClock")??(()=>Date.now());let n=i("voiceCodec");this.voiceCodec=n!==void 0?n:se()?Ie:null;let o=i("createMic");this.createMic=o!==void 0?o:Mt()?()=>new N:null;let a=i("createSink");this.createSink=a!==void 0?a:typeof AudioContext<"u"?()=>new D:null,this.storage=e.rememberSeat===!1?null:s.storage!==void 0?s.storage:Rt()}get seatKey(){return`onelobby.seat.${this.options.gameId}`}loadSeat(){if(!this.storage)return null;try{let e=this.storage.getItem(this.seatKey);if(!e)return null;let t=JSON.parse(e);return typeof t.code!="string"||typeof t.token!="string"||t.server!==this.options.lobbyServer?null:t}catch{return null}}saveSeat(e,t){if(!(!this.storage||!t))try{this.storage.setItem(this.seatKey,JSON.stringify({code:e,token:t,server:this.options.lobbyServer}))}catch{}}clearSeat(e){if(this.storage)try{if(e!==void 0){let t=this.loadSeat();if(t&&t.code!==e)return}this.storage.removeItem(this.seatKey)}catch{}}get rememberedCode(){return this.loadSeat()?.code??null}async resume(){let e=this.loadSeat();if(!e||qe(e.token))return null;try{return await this.rejoin(e.token)}catch(t){if(t instanceof d&&t.code==="invalid_token")return this.clearSeat(),null;throw t}}helloMsg(e){return{t:"hello",proto:Ce,gameId:this.options.gameId,netVersion:this.options.netVersion,client:{lib:$e,ver:Be,platform:typeof navigator<"u"?"browser":"node"},name:this.options.displayName.slice(0,24),...e?{resumeToken:e}:{}}}async connect(e=this.options.lobbyServer,t=this.socketFactory,s=0){let i=new S(e,t,this.clock);if(s>0){let n=()=>{},o=new Promise((a,c)=>{n=this.clock.setTimeout(()=>c(new d("direct_unreachable",`no answer from ${e} within ${s} ms`)),s)});try{await Promise.race([i.connect(),o])}catch(a){throw i.close(),a instanceof d&&a.code==="connect_failed"?new d("direct_unreachable",`no answer from ${e}`):a}finally{n()}}else await i.connect();try{let n=await i.request(this.helloMsg(),"welcome");return{sig:i,welcome:n}}catch(n){throw i.close(),n}}makeLobby(e,t,s,i,n){let o=new C({signaling:e,state:s,ice:t.ice??[],links:i?.links??this.linkFactory,direct:!!i,clock:this.clock,wall:this.wall,hashEveryTicks:this.options.hashEveryTicks,relayOnly:s.relayOnly??this.options.relayOnly,displayName:this.options.displayName,voice:{codec:this.voiceCodec,createSource:this.createMic,createSink:this.createSink},readyTimeoutMs:this.options.readyTimeoutMs,peerTimeoutMs:this.options.peerTimeoutMs,rateLimit:n});return o.errorSink=a=>this.emit("error",a),this.storage&&!i&&(re.add(o),this.saveSeat(o.code,o.resumeToken),o.seatSink=a=>this.saveSeat(o.code,a),o.on("sessionEnded",()=>{re.delete(o),this.clearSeat(o.code)})),o}async host(e){let{sig:t,welcome:s}=await this.connect();try{let i=await t.request({t:"create",name:e.name,maxPlayers:e.maxPlayers,visibility:e.visibility??"unlisted",mode:this.options.mode,tickRate:this.options.tickRate,lateJoin:e.lateJoin??!0,meta:e.meta??{},...this.options.relayOnly?{relayOnly:!0}:{}},"lobby");return this.makeLobby(t,s,i,void 0,e.rateLimit)}catch(i){throw t.close(),i}}async join(e){let t=m(e);if(!t)throw new d("bad_code",`"${e}" is not a valid join code`);let s=this.loadSeat();if(s&&s.code===t&&!qe(s.token))try{return await this.rejoin(s.token)}catch(o){if(this.clearSeat(),!(o instanceof d&&o.code==="invalid_token"))throw o}let{sig:i,welcome:n}=await this.connect();try{let o=await i.request({t:"join",code:t},"lobby");return this.makeLobby(i,n,o)}catch(o){throw i.close(),o}}async joinDirect(e,t={}){let s=e.trim();if(ne(s)){let l=ie(s);if("error"in l)throw new d(l.error,l.error==="unknown_version"?`"${s}" is a join code from a newer version of the game`:`"${s}" is not a valid join code: check for a typo`);s=`${l.ip}:${l.port}`}let i=De(s,t.defaultPort??47810);if(!i)throw new d("bad_address",`"${e}" is not a join code or an address (try 6B01-RGFE-P2B or 192.168.1.20:47810)`);let n=this.linkFactory,o={create:l=>n.create({...l,iceServers:[],extraRemoteCandidates:[i.candidate]})},{sig:a,welcome:c}=await this.connect(i.url,this.socketFactory,t.connectTimeoutMs??5e3);try{let l=await a.request({t:"join",...t.password?{password:t.password}:{}},"lobby"),h=this.makeLobby(a,c,l,{links:o});return h.joinCode=At(i),h}catch(l){throw a.close(),l}}async hostDirectWith(e,t){let{sig:s,welcome:i}=await this.connect("onelobby-direct://host/",t.socketFactory);try{let n=await s.request({t:"create",name:e.name,maxPlayers:e.maxPlayers,visibility:"unlisted",mode:this.options.mode,tickRate:this.options.tickRate,lateJoin:e.lateJoin??!0,meta:e.meta??{}},"lobby");return this.makeLobby(s,i,n,{links:t.wrapLinks(this.linkFactory)},e.rateLimit)}catch(n){throw s.close(),n}}async rejoin(e){let t=new S(this.options.lobbyServer,this.socketFactory,this.clock);await t.connect();try{let s=t.expect("lobby"),i=t.request(this.helloMsg(e),"welcome");s.catch(()=>{});let n=await i;if(n.peerId===void 0)throw new d("invalid_token","resume token unknown or expired");let o=await s;return this.makeLobby(t,n,o)}catch(s){throw t.close(),s}}async list(e={}){let{sig:t}=await this.connect();try{return(await t.request({t:"list",visibility:"public",meta:e.meta??{},limit:Math.min(100,e.limit??50),...e.cursor?{cursor:e.cursor}:{}},"lobbies")).items??[]}finally{t.close()}}};function At(r){if(r.isIPv6)return r.display;try{return Ue(r.host,r.port)}catch{return r.display}}var $={unknown_code:{message:"We couldn't find a lobby with that code.",hint:"Check the code with the host. Codes stop working once everyone leaves."},bad_code:{message:"That doesn't look like a lobby code.",hint:"Codes are 6 letters and numbers, like 7HQ 4K2."},lobby_full:{message:"That lobby is full.",hint:"Ask the host to make room, or start your own lobby."},version_mismatch:{message:"You and the host are running different versions of the game.",hint:"Reload the page (or update the game) on both sides, then try again."},in_game:{message:"That game has already started.",hint:"Ask the host to let people join mid-game, or wait for the next round."},kicked:{message:"The host removed you from the lobby."},denied:{message:"The host didn't let you in this time.",hint:"Ask the host to approve you, then try again."},not_host:{message:"Only the host can do that."},rate_limited:{message:"Slow down a little: too many tries in a short time.",hint:"Wait a moment, then try again."},max_lobbies:{message:"You already have the most lobbies open that this server allows.",hint:"Close one of your other lobbies, then try again."},server_full:{message:"The game server is full right now.",hint:"Try again in a few minutes."},game_not_allowed:{message:"This game server doesn't host this game.",hint:"The game's server setting may be wrong; let the game's owner know."},bad_request:{message:"The game sent something the server didn't understand.",hint:"Reload the page and try again. If it keeps happening, the game may need an update."},connect_failed:{message:"Couldn't reach the game server.",hint:"Check your internet connection, then try again."},closed:{message:"Lost the connection to the game server.",hint:"Check your internet connection, then try again."},server_lost:{message:"Lost the connection to the game server.",hint:"Your game may keep going; new players can't join until it's back."},invalid_token:{message:"Your old seat in that lobby is no longer available.",hint:"Join again with the lobby code."},ice_failed:{message:"Couldn't connect to the other players.",hint:"Some networks (school, work, public Wi-Fi) block game connections. Try another network or a phone hotspot."},timeout:{message:"The connection took too long.",hint:"Check your internet connection, then try again."},left:{message:"You left the lobby."},unknown:{message:"Something went wrong.",hint:"Try again. If it keeps happening, reload the page."}},It=/^[a-z][a-z0-9_]*$/;function Ot(r){if(typeof r=="string"){let e=/^([a-z][a-z0-9_]*)(?::|$)/.exec(r.trim());return{code:e?e[1]:null}}if(r&&typeof r=="object"){let e=r,t=e.data&&typeof e.data=="object"?e.data:void 0;if(typeof e.code=="string"&&It.test(e.code))return{code:e.code,data:t};if(typeof e.message=="string"){let s=/^([a-z][a-z0-9_]*):/.exec(e.message);if(s&&$[s[1]])return{code:s[1],data:t};if(e.name==="NetworkError"||/network|failed to fetch|websocket/i.test(e.message))return{code:"connect_failed"};if(e.name==="TimeoutError"||/timed? ?out/i.test(e.message))return{code:"timeout"}}}return{code:null}}function oe(r){let{code:e,data:t}=Ot(r);if(!e)return{code:"unknown",...$.unknown};let s=e;e==="kicked"&&t?.reason==="denied"&&(s="denied"),e==="rate_limited"&&t?.reason==="max_lobbies"&&(s="max_lobbies");let i=$[s];if(!i)return{code:e,...$.unknown};let n={code:e,message:i.message};if(i.hint&&(n.hint=i.hint),e==="rate_limited"&&typeof t?.retryAfterMs=="number"&&s==="rate_limited"){let o=Math.max(1,Math.ceil(t.retryAfterMs/1e3));n.hint=`Wait ${o} second${o===1?"":"s"}, then try again.`}return n}function Ke(){try{return typeof location<"u"?location.href:void 0}catch{return}}function ae(r,e,t="join"){let s=m(r);if(!s)throw new Error(`"${r}" is not a valid lobby code`);let i=e??Ke();if(!i)throw new Error("shareUrl needs a base URL outside the browser");let n=new URL(i);return n.searchParams.delete(t),n.searchParams.set(t,y(s).replace(" ","-")),n.toString()}function ce(r,e="join"){let t=r??Ke();if(!t)return null;let s;try{s=new URL(t,"http://localhost/")}catch{return null}let i=s.searchParams.get(e)??new URLSearchParams(s.hash.replace(/^#/,"")).get(e);return i?m(i):null}var Ht=b,Ft=d;var Ut=new TextEncoder,jt=new TextDecoder,B=r=>Ut.encode(JSON.stringify(r)),V=class{constructor(e){this.c=e}c;values=new Map;get(e){return this.values.get(e)}set(e,t){this.c._sharedOp({k:"set",key:String(e),v:t===void 0?null:t})}add(e,t=1){this.c._sharedOp({k:"add",key:String(e),n:Number(t)||0})}all(){return Object.fromEntries(this.values)}keys(){return[...this.values.keys()]}},T=class{constructor(e){this.opts=e}opts;state="idle";lobby=null;error=null;linking=!1;name="";shared=new V(this);onChange=()=>{};onEvent=()=>{};lastAction=null;unsub=[];gen=0;seq=0;get isHost(){return!!this.lobby&&this.lobby.isHost}get code(){return this.lobby?.code??null}get players(){let e=this.lobby;if(!e)return[];let t=e.peers.map(s=>this.toPlayer(s));return t.some(s=>s.isMe)||t.push({id:e.localPeerId,name:this.name||`Player ${e.localPeerId}`,isHost:e.isHost,isMe:!0}),t.sort((s,i)=>s.id-i.id)}get statusText(){switch(this.state){case"idle":return"Host a game, or type a code to join one.";case"connecting":return this.lastAction?.kind==="join"?"Joining\u2026":this.lastAction?.kind==="resume"?"Getting your seat back\u2026":"Creating a game\u2026";case"error":return this.error?.message??"Something went wrong.";case"lobby":{if(this.linking)return`Connecting to ${this.hostName}\u2026`;if(this.isHost){let e=this.players.length;return e<=1?"Waiting for players\u2026 Share the code with your friends.":`${e} players here. Press Start when everyone's in.`}return`Connected! Waiting for ${this.hostName} to start.`}case"playing":return this.linking?`Connecting to ${this.hostName}\u2026`:"Game on!"}}get hostName(){let e=this.lobby;return e&&e.peer(e.hostPeerId)?.name||"the host"}toPlayer(e,t){let s=this.lobby,i=e?.peerId??t??0;return{id:i,name:e?.name||i===s.localPeerId&&this.name||`Player ${i}`,isHost:i===s.hostPeerId,isMe:i===s.localPeerId}}host(e){return this.lastAction={kind:"host"},this.run(e,t=>t.host({name:this.opts.lobbyName??"game",maxPlayers:typeof this.opts.maxPlayers=="function"?this.opts.maxPlayers():this.opts.maxPlayers,visibility:"unlisted",lateJoin:!0}))}join(e,t){let s=m(e);return this.lastAction={kind:"join",code:e},s?this.run(t,i=>i.join(s)):(this.fail(new d("bad_code")),Promise.resolve())}async resume(e){let t=this.opts.makeKit(e);return!t.resume||!t.rememberedCode?!1:(this.lastAction={kind:"resume"},await this.run(e,()=>t.resume(),!0),!!this.lobby)}retry(){let e=this.lastAction;return!e||e.kind==="resume"?(this.reset(),Promise.resolve()):e.kind==="host"?this.host(this.name):this.join(e.code,this.name)}async start(){!this.lobby||!this.isHost||this.state==="playing"||await this.lobby.start()}async leave(){let e=this.lobby;this.detach(),await e?.leave(),this.reset()}reset(){this.detach(),this.state="idle",this.error=null,this.onChange()}send(e){this.lobby&&this.lobby.broadcast(B({k:"m",d:e===void 0?null:e}))}async run(e,t,s=!1){this.detach();let i=++this.gen;this.name=(e||"").trim().slice(0,24),this.state="connecting",this.error=null,this.onChange();let n;try{n=await t(this.opts.makeKit(this.name))}catch(o){if(i!==this.gen)return;s?this.reset():this.fail(o);return}if(i!==this.gen){n?.leave();return}n?this.attach(n):this.reset()}attach(e){this.lobby=e,e.isHost||this.shared.values.clear(),this.seq=0,this.state=e.phase==="in_game"?"playing":"lobby",this.linking=!e.isHost;let t=this.unsub;t.push(e.on("ready",()=>this.linked())),t.push(e.on("peerJoined",s=>{this.onEvent("player-joined",{player:this.toPlayer(s)}),this.onChange()})),t.push(e.on("peerLeft",(s,i)=>{this.onEvent("player-left",{player:this.toPlayer(s),reason:i}),this.onChange()})),t.push(e.on("peerReady",s=>{e.isHost&&s.peerId!==e.localPeerId&&e.send(s.peerId,B({k:"SS",all:this.shared.all(),q:this.seq}))})),t.push(e.on("hostChanged",()=>this.onChange())),t.push(e.on("phase",s=>{s==="in_game"&&this.state!=="playing"&&(this.state="playing",this.onChange(),this.onEvent("start",{}))})),t.push(e.on("message",(s,i)=>this.onRaw(s,i))),t.push(e.on("sessionEnded",s=>{this.lobby===e&&s!=="left"&&this.fail(new d(s))})),e.ready.then(()=>{this.lobby===e&&this.linked()},()=>{}),this.onChange(),this.state==="playing"&&!this.linking&&this.onEvent("start",{})}linked(){this.linking&&(this.linking=!1,this.onChange(),this.state==="playing"&&this.onEvent("start",{}))}detach(){this.gen++;for(let e of this.unsub.splice(0))e();this.lobby=null,this.linking=!1}fail(e){let t=this.lobby;this.detach(),t&&t.leave(),this.state="error",this.error=oe(e),this.onChange(),this.onEvent("error",this.error)}playerById(e){let t=this.lobby;return!t||!e?null:this.toPlayer(t.peer(e),e)}onRaw(e,t){let s;try{s=JSON.parse(jt.decode(t))}catch{return}if(!s||typeof s!="object")return;let i=this.lobby;switch(s.k){case"m":this.onEvent("message",{from:this.playerById(e),data:s.d});break;case"set":case"add":i.isHost&&this.applyHost(s,e);break;case"S":!i.isHost&&e===i.hostPeerId&&!(s.q<=this.seq)&&(this.seq=s.q,this.applyValue(s.key,s.v,s.by));break;case"SS":if(!i.isHost&&e===i.hostPeerId&&s.all&&typeof s.all=="object"&&!(s.q<this.seq)){this.seq=s.q;for(let n of[...this.shared.values.keys()])n in s.all||this.applyValue(n,null,0);for(let[n,o]of Object.entries(s.all))this.applyValue(n,o,0)}break}}_sharedOp(e){let t=this.lobby;if(!t){let s=e.k==="set"?e.v:(Number(this.shared.values.get(e.key))||0)+e.n;this.applyValue(e.key,s,0);return}t.isHost?this.applyHost(e,t.localPeerId):t.send(t.hostPeerId,B(e))}applyHost(e,t){let s=this.lobby;if(typeof e.key!="string")return;let i=e.k==="set"?e.v:(Number(this.shared.values.get(e.key))||0)+(Number(e.n)||0);this.applyValue(e.key,i,t),s.broadcast(B({k:"S",key:e.key,v:i,by:t,q:++this.seq}))}applyValue(e,t,s){t===null?this.shared.values.delete(e):this.shared.values.set(e,t),this.onEvent("shared-change",{key:e,value:t,from:this.playerById(s)})}};var Je=`
|
|
21
21
|
:host {
|
|
22
22
|
--onelobby-bg: #ffffff;
|
|
23
23
|
--onelobby-fg: #1b1f24;
|