onelobby 0.3.1 → 0.4.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/chunking.d.ts +4 -0
- package/dist/chunking.js +23 -2
- package/dist/chunking.js.map +1 -1
- package/dist/deflate.d.ts +2 -1
- package/dist/deflate.js +10 -4
- package/dist/deflate.js.map +1 -1
- package/dist/direct/hub-host.d.ts +16 -0
- package/dist/direct/hub-host.js +45 -1
- package/dist/direct/hub-host.js.map +1 -1
- package/dist/direct/hub.d.ts +3 -0
- package/dist/direct/hub.js +32 -6
- package/dist/direct/hub.js.map +1 -1
- package/dist/direct/index.d.ts +7 -1
- package/dist/direct/index.js +10 -3
- package/dist/direct/index.js.map +1 -1
- package/dist/friendly.d.ts +31 -0
- package/dist/friendly.js +32 -0
- package/dist/friendly.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lockstep.d.ts +5 -0
- package/dist/lockstep.js +17 -2
- package/dist/lockstep.js.map +1 -1
- package/dist/onelobby.bundle.mjs +5 -5
- package/dist/onelobby.bundle.mjs.map +3 -3
- package/dist/onelobby.d.ts +11 -1
- package/dist/onelobby.global.js +113 -28
- package/dist/onelobby.global.js.map +2 -2
- package/dist/onelobby.global.min.js +5 -5
- package/dist/onelobby.global.min.js.map +3 -3
- package/dist/onelobby.js +46 -24
- package/dist/onelobby.js.map +1 -1
- package/package.json +1 -1
package/dist/onelobby.js
CHANGED
|
@@ -9,7 +9,7 @@ import { webRtcLinkFactory } from "./webrtc.js";
|
|
|
9
9
|
import { decodeJoinCode, encodeJoinCode, looksLikeJoinCode } from "./joincode.js";
|
|
10
10
|
import { DEFAULT_DIRECT_PORT, parseDirectAddress } from "./direct-address.js";
|
|
11
11
|
export const LIB_NAME = "onelobby";
|
|
12
|
-
export const LIB_VERSION = "0.
|
|
12
|
+
export const LIB_VERSION = "0.4.1";
|
|
13
13
|
export const DEFAULT_READY_TIMEOUT_MS = 15_000;
|
|
14
14
|
function hasMic() {
|
|
15
15
|
return typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && typeof AudioWorkletNode !== "undefined";
|
|
@@ -155,6 +155,18 @@ export class OneLobby extends Emitter {
|
|
|
155
155
|
};
|
|
156
156
|
}
|
|
157
157
|
async connect(url = this.options.lobbyServer, factory = this.socketFactory, timeoutMs = 0) {
|
|
158
|
+
const sig = await this.open(url, factory, timeoutMs);
|
|
159
|
+
try {
|
|
160
|
+
const welcome = await sig.request(this.helloMsg(), "welcome");
|
|
161
|
+
return { sig, welcome };
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
sig.close();
|
|
165
|
+
throw e;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/** The signaling socket only (no hello yet); with a timeout (direct IP), no answer is `direct_unreachable`. */
|
|
169
|
+
async open(url, factory, timeoutMs) {
|
|
158
170
|
const sig = new SignalingClient(url, factory, this.clock);
|
|
159
171
|
if (timeoutMs > 0) {
|
|
160
172
|
let cancel = () => { };
|
|
@@ -176,14 +188,7 @@ export class OneLobby extends Emitter {
|
|
|
176
188
|
}
|
|
177
189
|
else
|
|
178
190
|
await sig.connect();
|
|
179
|
-
|
|
180
|
-
const welcome = await sig.request(this.helloMsg(), "welcome");
|
|
181
|
-
return { sig, welcome };
|
|
182
|
-
}
|
|
183
|
-
catch (e) {
|
|
184
|
-
sig.close();
|
|
185
|
-
throw e;
|
|
186
|
-
}
|
|
191
|
+
return sig;
|
|
187
192
|
}
|
|
188
193
|
makeLobby(sig, welcome, state, direct, rateLimit) {
|
|
189
194
|
const lobby = new Lobby({
|
|
@@ -268,18 +273,7 @@ export class OneLobby extends Emitter {
|
|
|
268
273
|
* The address is also given to ICE as the host's UDP candidate. When the host leaves, `sessionEnded("host_left")`.
|
|
269
274
|
*/
|
|
270
275
|
async joinDirect(codeOrAddress, opts = {}) {
|
|
271
|
-
|
|
272
|
-
if (looksLikeJoinCode(input)) {
|
|
273
|
-
const d = decodeJoinCode(input);
|
|
274
|
-
if ("error" in d)
|
|
275
|
-
throw new OneLobbyError(d.error, d.error === "unknown_version" ? `"${input}" is a join code from a newer version of the game` : `"${input}" is not a valid join code: check for a typo`);
|
|
276
|
-
input = `${d.ip}:${d.port}`;
|
|
277
|
-
}
|
|
278
|
-
const addr = parseDirectAddress(input, opts.defaultPort ?? DEFAULT_DIRECT_PORT);
|
|
279
|
-
if (!addr)
|
|
280
|
-
throw new OneLobbyError("bad_address", `"${codeOrAddress}" is not a join code or an address (try 6B01-RGFE-P2B or 192.168.1.20:47810)`);
|
|
281
|
-
const inner = this.linkFactory;
|
|
282
|
-
const links = { create: (o) => inner.create({ ...o, iceServers: [], extraRemoteCandidates: [addr.candidate] }) };
|
|
276
|
+
const { addr, links } = this.directTarget(codeOrAddress, opts);
|
|
283
277
|
const { sig, welcome } = await this.connect(addr.url, this.socketFactory, opts.connectTimeoutMs ?? 5000);
|
|
284
278
|
try {
|
|
285
279
|
const state = await sig.request({ t: "join", ...(opts.password ? { password: opts.password } : {}) }, "lobby");
|
|
@@ -293,6 +287,32 @@ export class OneLobby extends Emitter {
|
|
|
293
287
|
throw e;
|
|
294
288
|
}
|
|
295
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Take a direct-IP seat back after a drop or a restart: the code (or address) joined before and that lobby's
|
|
292
|
+
* `resumeToken` (save it after every join; it changes on each resume). The host keeps a dropped seat for 60 s and the
|
|
293
|
+
* same peer id comes back. Rejects with `invalid_token` when the seat is gone: offer `joinDirect` then.
|
|
294
|
+
*/
|
|
295
|
+
async rejoinDirect(codeOrAddress, resumeToken, opts = {}) {
|
|
296
|
+
const { addr, links } = this.directTarget(codeOrAddress, opts);
|
|
297
|
+
const lobby = await this.resumeAt(resumeToken, addr.url, opts.connectTimeoutMs ?? 5000, { links });
|
|
298
|
+
lobby.joinCode = joinedWith(addr);
|
|
299
|
+
return lobby;
|
|
300
|
+
}
|
|
301
|
+
directTarget(codeOrAddress, opts) {
|
|
302
|
+
let input = codeOrAddress.trim();
|
|
303
|
+
if (looksLikeJoinCode(input)) {
|
|
304
|
+
const d = decodeJoinCode(input);
|
|
305
|
+
if ("error" in d)
|
|
306
|
+
throw new OneLobbyError(d.error, d.error === "unknown_version" ? `"${input}" is a join code from a newer version of the game` : `"${input}" is not a valid join code: check for a typo`);
|
|
307
|
+
input = `${d.ip}:${d.port}`;
|
|
308
|
+
}
|
|
309
|
+
const addr = parseDirectAddress(input, opts.defaultPort ?? DEFAULT_DIRECT_PORT);
|
|
310
|
+
if (!addr)
|
|
311
|
+
throw new OneLobbyError("bad_address", `"${codeOrAddress}" is not a join code or an address (try 6B01-RGFE-P2B or 192.168.1.20:47810)`);
|
|
312
|
+
const inner = this.linkFactory;
|
|
313
|
+
const links = { create: (o) => inner.create({ ...o, iceServers: [], extraRemoteCandidates: [addr.candidate] }) };
|
|
314
|
+
return { addr, links };
|
|
315
|
+
}
|
|
296
316
|
/** @internal onelobby/direct's host: create the one lobby on the embedded hub, in direct mode. */
|
|
297
317
|
async hostDirectWith(h, hooks) {
|
|
298
318
|
const { sig, welcome } = await this.connect("onelobby-direct://host/", hooks.socketFactory);
|
|
@@ -307,8 +327,10 @@ export class OneLobby extends Emitter {
|
|
|
307
327
|
}
|
|
308
328
|
}
|
|
309
329
|
async rejoin(resumeToken) {
|
|
310
|
-
|
|
311
|
-
|
|
330
|
+
return this.resumeAt(resumeToken, this.options.lobbyServer, 0);
|
|
331
|
+
}
|
|
332
|
+
async resumeAt(resumeToken, url, timeoutMs, direct) {
|
|
333
|
+
const sig = await this.open(url, this.socketFactory, timeoutMs);
|
|
312
334
|
try {
|
|
313
335
|
// The server answers hello(resumeToken) with welcome then the full lobby state; listen for both up front.
|
|
314
336
|
const lobbyP = sig.expect("lobby");
|
|
@@ -319,7 +341,7 @@ export class OneLobby extends Emitter {
|
|
|
319
341
|
if (welcome.peerId === undefined)
|
|
320
342
|
throw new OneLobbyError("invalid_token", "resume token unknown or expired");
|
|
321
343
|
const state = await lobbyP;
|
|
322
|
-
return this.makeLobby(sig, welcome, state);
|
|
344
|
+
return this.makeLobby(sig, welcome, state, direct);
|
|
323
345
|
}
|
|
324
346
|
catch (e) {
|
|
325
347
|
sig.close();
|
package/dist/onelobby.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"onelobby.js","sourceRoot":"","sources":["../src/onelobby.ts"],"names":[],"mappings":"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","sourcesContent":["import { systemClock, type Clock } from \"./clock.js\";\nimport { Emitter } from \"./emitter.js\";\nimport { normalizeCode } from \"./crockford.js\";\nimport { Lobby, type RateLimit } from \"./lobby.js\";\nimport {\n OneLobbyError,\n PROTO_ID,\n SignalingClient,\n type LobbyState,\n type LobbySummary,\n type WelcomeMsg,\n} from \"./signaling.js\";\nimport {\n defaultSocketFactory,\n type LinkFactory,\n type SocketFactory,\n} from \"./transport.js\";\nimport type { VoiceCodec, FrameSource, AudioSink } from \"./voice-types.js\";\nimport { WebAudioSink, WorkletMic, webCodecsOpus, webCodecsOpusAvailable } from \"./voice-browser.js\";\nimport { webRtcLinkFactory } from \"./webrtc.js\";\nimport { decodeJoinCode, encodeJoinCode, looksLikeJoinCode } from \"./joincode.js\";\nimport { DEFAULT_DIRECT_PORT, parseDirectAddress } from \"./direct-address.js\";\n\nexport const LIB_NAME = \"onelobby\";\nexport const LIB_VERSION = \"0.3.1\";\n\n/** Where `rememberSeat` keeps the resume token: the subset of the Web Storage API it uses. */\nexport interface SeatStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\n/** Seams for tests, adapters and non-browser runtimes. Every default is the browser implementation. */\nexport interface AdvancedOptions {\n socketFactory?: SocketFactory;\n linkFactory?: LinkFactory;\n clock?: Clock;\n /** Wall clock in unix ms, stamps chat lines. */\n wallClock?: () => number;\n /** Opus codec; defaults to WebCodecs when available. Provide a WASM-backed one elsewhere. */\n voiceCodec?: VoiceCodec | null;\n createMic?: (() => FrameSource) | null;\n createSink?: (() => AudioSink) | null;\n /** Storage for `rememberSeat`; defaults to `sessionStorage` when the page has one. null disables it. */\n storage?: SeatStorage | null;\n}\n\nexport interface OneLobbyOptions {\n gameId: string;\n netVersion: string;\n /** `ws://` / `wss://` URL of a onelobby-server. */\n lobbyServer: string;\n mode?: \"lockstep\" | \"raw\";\n tickRate?: number;\n /** Default: tickRate (once a second). */\n hashEveryTicks?: number;\n /** The player's name. Empty (the default): other players see \"Player N\", N being the seat number. */\n displayName?: string;\n relayOnly?: boolean;\n /**\n * Joiners give up on reaching the host after this many ms: `lobby.ready` rejects with code `ice_failed`, the kit\n * emits `error`, and the lobby is left. Half the time goes to a relay-only retry when the server offers TURN.\n * Default 15000; 0 waits forever.\n */\n readyTimeoutMs?: number;\n /**\n * Treat a peer as gone after this many ms of silence. Joiners ping the host every 2 s and the host answers, so the\n * host drops a silent joiner (`peerLeft`, \"timeout\") and a joiner handles a silent host as lost (a direct lobby\n * ends with \"host_left\"). Default 0 (off): a silently dead peer then surfaces when its link or signaling socket\n * times out, within about 30 s. Values under 6000 are raised to 6000.\n */\n peerTimeoutMs?: number;\n /**\n * Keep this tab's seat across a page reload (sessionStorage): `join(code)` and `resume()` take the seat back\n * instead of joining as a new player. Cleared when the session ends. Default true where sessionStorage exists.\n */\n rememberSeat?: boolean;\n /** Test and runtime seams (see AdvancedOptions). */\n advanced?: AdvancedOptions;\n\n /** @deprecated use `advanced.socketFactory` */\n socketFactory?: SocketFactory;\n /** @deprecated use `advanced.linkFactory` */\n linkFactory?: LinkFactory;\n /** @deprecated use `advanced.clock` */\n clock?: Clock;\n /** @deprecated use `advanced.wallClock` */\n wallClock?: () => number;\n /** @deprecated use `advanced.voiceCodec` */\n voiceCodec?: VoiceCodec | null;\n /** @deprecated use `advanced.createMic` */\n createMic?: (() => FrameSource) | null;\n /** @deprecated use `advanced.createSink` */\n createSink?: (() => AudioSink) | null;\n}\n\nexport interface HostOptions {\n name: string;\n maxPlayers: number;\n /**\n * \"unlisted\" (default): anyone with the code or link can join, but the lobby is not in `list()`.\n * \"public\": also listed. \"private\": the host approves each join.\n */\n visibility?: \"public\" | \"unlisted\" | \"private\";\n lateJoin?: boolean;\n meta?: Record<string, string>;\n /** Per-joiner cap on incoming raw messages, e.g. `{ messages: 60, perMs: 1000 }`; the excess is dropped before `message` fires or is relayed. */\n rateLimit?: RateLimit;\n}\n\nexport interface ListOptions {\n meta?: Record<string, string>;\n limit?: number;\n cursor?: string;\n}\n\nexport interface JoinDirectOptions {\n /** The host's password, if it set one; a wrong one fails with `bad_password`. */\n password?: string;\n /** Port for an address typed without one (default 47810, docs/direct-ip.md s.2). */\n defaultPort?: number;\n /** No answer on the host's TCP port within this long fails with `direct_unreachable` (default 5000). */\n connectTimeoutMs?: number;\n}\n\n/** @internal How onelobby/direct makes a host lobby: its own socket and link factory, and direct mode. */\nexport interface DirectLobbyHooks {\n socketFactory: SocketFactory;\n /** Wraps the kit's own link factory (port pin, no ICE servers). */\n wrapLinks: (inner: LinkFactory) => LinkFactory;\n}\n\nexport type OneLobbyEvents = { error: [Error] };\n\nexport const DEFAULT_READY_TIMEOUT_MS = 15_000;\n\nfunction hasMic(): boolean {\n return typeof navigator !== \"undefined\" && !!navigator.mediaDevices?.getUserMedia && typeof AudioWorkletNode !== \"undefined\";\n}\n\nfunction browserStorage(): SeatStorage | null {\n try {\n const s = (globalThis as { sessionStorage?: SeatStorage }).sessionStorage;\n return s ?? null;\n } catch {\n return null; // storage access can throw (sandboxed iframe, blocked cookies)\n }\n}\n\ninterface Seat {\n code: string;\n token: string;\n server: string;\n}\n\n/**\n * Lobbies alive in this page, by resume token. A remembered seat that is still in use here (two kits in one tab,\n * e.g. a local two-player demo) must not be taken over; after a reload this set starts empty.\n */\nconst liveSeats = new Set<Lobby>();\nfunction seatInUse(token: string): boolean {\n for (const l of liveSeats) if (l.resumeToken === token) return true;\n return false;\n}\n\n/** Entry point (DESIGN s.11.2). */\nexport class OneLobby extends Emitter<OneLobbyEvents> {\n readonly options: Required<Pick<OneLobbyOptions, \"gameId\" | \"netVersion\" | \"lobbyServer\" | \"mode\" | \"tickRate\" | \"hashEveryTicks\" | \"displayName\" | \"relayOnly\" | \"readyTimeoutMs\" | \"peerTimeoutMs\">>;\n private socketFactory: SocketFactory;\n private linkFactory: LinkFactory;\n private clock: Clock;\n private wall: () => number;\n private voiceCodec: VoiceCodec | null;\n private createMic: (() => FrameSource) | null;\n private createSink: (() => AudioSink) | null;\n private storage: SeatStorage | null;\n\n constructor(o: OneLobbyOptions) {\n super();\n const tickRate = o.tickRate ?? 20;\n this.options = {\n gameId: o.gameId,\n netVersion: o.netVersion,\n lobbyServer: o.lobbyServer,\n mode: o.mode ?? \"lockstep\",\n tickRate,\n hashEveryTicks: o.hashEveryTicks ?? tickRate,\n displayName: (o.displayName ?? \"\").trim(),\n relayOnly: o.relayOnly ?? false,\n readyTimeoutMs: o.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS,\n peerTimeoutMs: o.peerTimeoutMs ?? 0,\n };\n const a = o.advanced ?? {};\n const pick = <K extends keyof AdvancedOptions & keyof OneLobbyOptions>(k: K) => (a[k] !== undefined ? a[k] : o[k]);\n this.socketFactory = pick(\"socketFactory\") ?? defaultSocketFactory;\n this.linkFactory = pick(\"linkFactory\") ?? webRtcLinkFactory;\n this.clock = pick(\"clock\") ?? systemClock;\n this.wall = pick(\"wallClock\") ?? (() => Date.now());\n const codec = pick(\"voiceCodec\");\n this.voiceCodec = codec !== undefined ? codec : webCodecsOpusAvailable() ? webCodecsOpus : null;\n const mic = pick(\"createMic\");\n this.createMic = mic !== undefined ? mic : hasMic() ? () => new WorkletMic() : null;\n const sink = pick(\"createSink\");\n this.createSink = sink !== undefined ? sink : typeof AudioContext !== \"undefined\" ? () => new WebAudioSink() : null;\n this.storage = o.rememberSeat === false ? null : a.storage !== undefined ? a.storage : browserStorage();\n }\n\n // ---- remembered seat -------------------------------------------------------------------------------------------\n\n private get seatKey(): string {\n return `onelobby.seat.${this.options.gameId}`;\n }\n\n private loadSeat(): Seat | null {\n if (!this.storage) return null;\n try {\n const raw = this.storage.getItem(this.seatKey);\n if (!raw) return null;\n const s = JSON.parse(raw) as Partial<Seat>;\n if (typeof s.code !== \"string\" || typeof s.token !== \"string\" || s.server !== this.options.lobbyServer) return null;\n return s as Seat;\n } catch {\n return null;\n }\n }\n\n private saveSeat(code: string, token: string): void {\n if (!this.storage || !token) return;\n try {\n this.storage.setItem(this.seatKey, JSON.stringify({ code, token, server: this.options.lobbyServer } satisfies Seat));\n } catch {\n /* quota or blocked storage: the seat just is not remembered */\n }\n }\n\n private clearSeat(code?: string): void {\n if (!this.storage) return;\n try {\n if (code !== undefined) {\n const s = this.loadSeat();\n if (s && s.code !== code) return;\n }\n this.storage.removeItem(this.seatKey);\n } catch {\n /* ignore */\n }\n }\n\n /** The lobby code of the seat this tab can take back (rememberSeat), or null. */\n get rememberedCode(): string | null {\n return this.loadSeat()?.code ?? null;\n }\n\n /**\n * Take back the seat remembered from before a page reload (host or joiner). Resolves null when there is none\n * or the server no longer holds it (the seat is forgotten then).\n */\n async resume(): Promise<Lobby | null> {\n const seat = this.loadSeat();\n if (!seat || seatInUse(seat.token)) return null;\n try {\n return await this.rejoin(seat.token);\n } catch (e) {\n if (e instanceof OneLobbyError && e.code === \"invalid_token\") {\n this.clearSeat();\n return null;\n }\n throw e;\n }\n }\n\n // ---- connecting ------------------------------------------------------------------------------------------------\n\n private helloMsg(resumeToken?: string): Record<string, unknown> {\n return {\n t: \"hello\",\n proto: PROTO_ID,\n gameId: this.options.gameId,\n netVersion: this.options.netVersion,\n client: { lib: LIB_NAME, ver: LIB_VERSION, platform: typeof navigator !== \"undefined\" ? \"browser\" : \"node\" },\n name: this.options.displayName.slice(0, 24),\n ...(resumeToken ? { resumeToken } : {}),\n };\n }\n\n private async connect(url = this.options.lobbyServer, factory = this.socketFactory, timeoutMs = 0): Promise<{ sig: SignalingClient; welcome: WelcomeMsg }> {\n const sig = new SignalingClient(url, factory, this.clock);\n if (timeoutMs > 0) {\n let cancel = () => {};\n const timeout = new Promise<never>((_, reject) => {\n cancel = this.clock.setTimeout(() => reject(new OneLobbyError(\"direct_unreachable\", `no answer from ${url} within ${timeoutMs} ms`)), timeoutMs);\n });\n try {\n await Promise.race([sig.connect(), timeout]);\n } catch (e) {\n sig.close();\n if (e instanceof OneLobbyError && e.code === \"connect_failed\") throw new OneLobbyError(\"direct_unreachable\", `no answer from ${url}`);\n throw e;\n } finally {\n cancel();\n }\n } else await sig.connect();\n try {\n const welcome = await sig.request<WelcomeMsg & { t: string }>(this.helloMsg(), \"welcome\");\n return { sig, welcome };\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n private makeLobby(sig: SignalingClient, welcome: WelcomeMsg, state: LobbyState, direct?: { links: LinkFactory }, rateLimit?: RateLimit): Lobby {\n const lobby = new Lobby({\n signaling: sig,\n state,\n ice: welcome.ice ?? [],\n links: direct?.links ?? this.linkFactory,\n direct: !!direct,\n clock: this.clock,\n wall: this.wall,\n hashEveryTicks: this.options.hashEveryTicks,\n relayOnly: state.relayOnly ?? this.options.relayOnly,\n displayName: this.options.displayName,\n voice: { codec: this.voiceCodec, createSource: this.createMic, createSink: this.createSink },\n readyTimeoutMs: this.options.readyTimeoutMs,\n peerTimeoutMs: this.options.peerTimeoutMs,\n rateLimit,\n });\n lobby.errorSink = (e) => this.emit(\"error\", e);\n if (this.storage && !direct) {\n liveSeats.add(lobby);\n this.saveSeat(lobby.code, lobby.resumeToken);\n lobby.seatSink = (token) => this.saveSeat(lobby.code, token);\n lobby.on(\"sessionEnded\", () => {\n liveSeats.delete(lobby);\n this.clearSeat(lobby.code);\n });\n }\n return lobby;\n }\n\n async host(h: HostOptions): Promise<Lobby> {\n const { sig, welcome } = await this.connect();\n try {\n const state = await sig.request<LobbyState>(\n {\n t: \"create\",\n name: h.name,\n maxPlayers: h.maxPlayers,\n visibility: h.visibility ?? \"unlisted\",\n mode: this.options.mode,\n tickRate: this.options.tickRate,\n lateJoin: h.lateJoin ?? true,\n meta: h.meta ?? {},\n ...(this.options.relayOnly ? { relayOnly: true } : {}),\n },\n \"lobby\",\n );\n return this.makeLobby(sig, welcome, state, undefined, h.rateLimit);\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n async join(code: string): Promise<Lobby> {\n const norm = normalizeCode(code);\n if (!norm) throw new OneLobbyError(\"bad_code\", `\"${code}\" is not a valid join code`);\n const seat = this.loadSeat();\n if (seat && seat.code === norm && !seatInUse(seat.token)) {\n // Same tab, same lobby (a page reload): take the old seat back instead of joining as a new player.\n try {\n return await this.rejoin(seat.token);\n } catch (e) {\n this.clearSeat();\n if (!(e instanceof OneLobbyError && e.code === \"invalid_token\")) throw e;\n }\n }\n const { sig, welcome } = await this.connect();\n try {\n const state = await sig.request<LobbyState>({ t: \"join\", code: norm }, \"lobby\");\n return this.makeLobby(sig, welcome, state);\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n /**\n * Join a direct-IP host (docs/direct-ip.md): a join code from the host's screen (`6B01-RGFE-P2B`, any case, spaces\n * or hyphens) or, advanced, an address (`ip`, `ip:port`, `[v6]:port`). No lobby server is involved: the code is\n * the host's address. A mistyped code fails at once with `bad_code`; nobody listening is `direct_unreachable`.\n * The address is also given to ICE as the host's UDP candidate. When the host leaves, `sessionEnded(\"host_left\")`.\n */\n async joinDirect(codeOrAddress: string, opts: JoinDirectOptions = {}): Promise<Lobby> {\n let input = codeOrAddress.trim();\n if (looksLikeJoinCode(input)) {\n const d = decodeJoinCode(input);\n if (\"error\" in d)\n throw new OneLobbyError(d.error, d.error === \"unknown_version\" ? `\"${input}\" is a join code from a newer version of the game` : `\"${input}\" is not a valid join code: check for a typo`);\n input = `${d.ip}:${d.port}`;\n }\n const addr = parseDirectAddress(input, opts.defaultPort ?? DEFAULT_DIRECT_PORT);\n if (!addr) throw new OneLobbyError(\"bad_address\", `\"${codeOrAddress}\" is not a join code or an address (try 6B01-RGFE-P2B or 192.168.1.20:47810)`);\n const inner = this.linkFactory;\n const links: LinkFactory = { create: (o) => inner.create({ ...o, iceServers: [], extraRemoteCandidates: [addr.candidate] }) };\n const { sig, welcome } = await this.connect(addr.url, this.socketFactory, opts.connectTimeoutMs ?? 5000);\n try {\n const state = await sig.request<LobbyState>({ t: \"join\", ...(opts.password ? { password: opts.password } : {}) }, \"lobby\");\n const lobby = this.makeLobby(sig, welcome, state, { links });\n // a joiner's joinCode is what it joined with (normalized), so a HUD can show it on both sides\n lobby.joinCode = joinedWith(addr);\n return lobby;\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n /** @internal onelobby/direct's host: create the one lobby on the embedded hub, in direct mode. */\n async hostDirectWith(h: HostOptions, hooks: DirectLobbyHooks): Promise<Lobby> {\n const { sig, welcome } = await this.connect(\"onelobby-direct://host/\", hooks.socketFactory);\n try {\n const state = await sig.request<LobbyState>(\n { t: \"create\", name: h.name, maxPlayers: h.maxPlayers, visibility: \"unlisted\", mode: this.options.mode, tickRate: this.options.tickRate,\n lateJoin: h.lateJoin ?? true, meta: h.meta ?? {} },\n \"lobby\",\n );\n return this.makeLobby(sig, welcome, state, { links: hooks.wrapLinks(this.linkFactory) }, h.rateLimit);\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n async rejoin(resumeToken: string): Promise<Lobby> {\n const sig = new SignalingClient(this.options.lobbyServer, this.socketFactory, this.clock);\n await sig.connect();\n try {\n // The server answers hello(resumeToken) with welcome then the full lobby state; listen for both up front.\n const lobbyP = sig.expect<LobbyState>(\"lobby\");\n const welcomeP = sig.request<WelcomeMsg & { t: string }>(this.helloMsg(resumeToken), \"welcome\");\n lobbyP.catch(() => {}); // settled below or by close()\n const welcome = await welcomeP;\n // spec: an unknown or expired token is ignored -> plain welcome (no peerId), and no lobby follows\n if (welcome.peerId === undefined) throw new OneLobbyError(\"invalid_token\", \"resume token unknown or expired\");\n const state = await lobbyP;\n return this.makeLobby(sig, welcome, state);\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n async list(opts: ListOptions = {}): Promise<LobbySummary[]> {\n const { sig } = await this.connect();\n try {\n const res = await sig.request<{ t: string; items: LobbySummary[] }>(\n { t: \"list\", visibility: \"public\", meta: opts.meta ?? {}, limit: Math.min(100, opts.limit ?? 50), ...(opts.cursor ? { cursor: opts.cursor } : {}) },\n \"lobbies\",\n );\n return res.items ?? [];\n } finally {\n sig.close();\n }\n }\n}\n\n/** The join code for an IPv4 address (a host name or IPv6 has none: its `host:port` instead). */\nfunction joinedWith(addr: { host: string; port: number; isIPv6: boolean; display: string }): string {\n if (addr.isIPv6) return addr.display;\n try {\n return encodeJoinCode(addr.host, addr.port);\n } catch {\n return addr.display;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"onelobby.js","sourceRoot":"","sources":["../src/onelobby.ts"],"names":[],"mappings":"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","sourcesContent":["import { systemClock, type Clock } from \"./clock.js\";\nimport { Emitter } from \"./emitter.js\";\nimport { normalizeCode } from \"./crockford.js\";\nimport { Lobby, type RateLimit } from \"./lobby.js\";\nimport {\n OneLobbyError,\n PROTO_ID,\n SignalingClient,\n type LobbyState,\n type LobbySummary,\n type WelcomeMsg,\n} from \"./signaling.js\";\nimport {\n defaultSocketFactory,\n type LinkFactory,\n type SocketFactory,\n} from \"./transport.js\";\nimport type { VoiceCodec, FrameSource, AudioSink } from \"./voice-types.js\";\nimport { WebAudioSink, WorkletMic, webCodecsOpus, webCodecsOpusAvailable } from \"./voice-browser.js\";\nimport { webRtcLinkFactory } from \"./webrtc.js\";\nimport { decodeJoinCode, encodeJoinCode, looksLikeJoinCode } from \"./joincode.js\";\nimport { DEFAULT_DIRECT_PORT, parseDirectAddress } from \"./direct-address.js\";\n\nexport const LIB_NAME = \"onelobby\";\nexport const LIB_VERSION = \"0.4.1\";\n\n/** Where `rememberSeat` keeps the resume token: the subset of the Web Storage API it uses. */\nexport interface SeatStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\n/** Seams for tests, adapters and non-browser runtimes. Every default is the browser implementation. */\nexport interface AdvancedOptions {\n socketFactory?: SocketFactory;\n linkFactory?: LinkFactory;\n clock?: Clock;\n /** Wall clock in unix ms, stamps chat lines. */\n wallClock?: () => number;\n /** Opus codec; defaults to WebCodecs when available. Provide a WASM-backed one elsewhere. */\n voiceCodec?: VoiceCodec | null;\n createMic?: (() => FrameSource) | null;\n createSink?: (() => AudioSink) | null;\n /** Storage for `rememberSeat`; defaults to `sessionStorage` when the page has one. null disables it. */\n storage?: SeatStorage | null;\n}\n\nexport interface OneLobbyOptions {\n gameId: string;\n netVersion: string;\n /** `ws://` / `wss://` URL of a onelobby-server. */\n lobbyServer: string;\n mode?: \"lockstep\" | \"raw\";\n tickRate?: number;\n /** Default: tickRate (once a second). */\n hashEveryTicks?: number;\n /** The player's name. Empty (the default): other players see \"Player N\", N being the seat number. */\n displayName?: string;\n relayOnly?: boolean;\n /**\n * Joiners give up on reaching the host after this many ms: `lobby.ready` rejects with code `ice_failed`, the kit\n * emits `error`, and the lobby is left. Half the time goes to a relay-only retry when the server offers TURN.\n * Default 15000; 0 waits forever.\n */\n readyTimeoutMs?: number;\n /**\n * Treat a peer as gone after this many ms of silence. Joiners ping the host every 2 s and the host answers, so the\n * host drops a silent joiner (`peerLeft`, \"timeout\") and a joiner handles a silent host as lost (a direct lobby\n * ends with \"host_left\"). Default 0 (off): a silently dead peer then surfaces when its link or signaling socket\n * times out, within about 30 s. Values under 6000 are raised to 6000.\n */\n peerTimeoutMs?: number;\n /**\n * Keep this tab's seat across a page reload (sessionStorage): `join(code)` and `resume()` take the seat back\n * instead of joining as a new player. Cleared when the session ends. Default true where sessionStorage exists.\n */\n rememberSeat?: boolean;\n /** Test and runtime seams (see AdvancedOptions). */\n advanced?: AdvancedOptions;\n\n /** @deprecated use `advanced.socketFactory` */\n socketFactory?: SocketFactory;\n /** @deprecated use `advanced.linkFactory` */\n linkFactory?: LinkFactory;\n /** @deprecated use `advanced.clock` */\n clock?: Clock;\n /** @deprecated use `advanced.wallClock` */\n wallClock?: () => number;\n /** @deprecated use `advanced.voiceCodec` */\n voiceCodec?: VoiceCodec | null;\n /** @deprecated use `advanced.createMic` */\n createMic?: (() => FrameSource) | null;\n /** @deprecated use `advanced.createSink` */\n createSink?: (() => AudioSink) | null;\n}\n\nexport interface HostOptions {\n name: string;\n maxPlayers: number;\n /**\n * \"unlisted\" (default): anyone with the code or link can join, but the lobby is not in `list()`.\n * \"public\": also listed. \"private\": the host approves each join.\n */\n visibility?: \"public\" | \"unlisted\" | \"private\";\n lateJoin?: boolean;\n meta?: Record<string, string>;\n /** Per-joiner cap on incoming raw messages, e.g. `{ messages: 60, perMs: 1000 }`; the excess is dropped before `message` fires or is relayed. */\n rateLimit?: RateLimit;\n}\n\nexport interface ListOptions {\n meta?: Record<string, string>;\n limit?: number;\n cursor?: string;\n}\n\nexport interface JoinDirectOptions {\n /** The host's password, if it set one; a wrong one fails with `bad_password`. */\n password?: string;\n /** Port for an address typed without one (default 47810, docs/direct-ip.md s.2). */\n defaultPort?: number;\n /** No answer on the host's TCP port within this long fails with `direct_unreachable` (default 5000). */\n connectTimeoutMs?: number;\n}\n\n/** @internal How onelobby/direct makes a host lobby: its own socket and link factory, and direct mode. */\nexport interface DirectLobbyHooks {\n socketFactory: SocketFactory;\n /** Wraps the kit's own link factory (port pin, no ICE servers). */\n wrapLinks: (inner: LinkFactory) => LinkFactory;\n}\n\nexport type OneLobbyEvents = { error: [Error] };\n\nexport const DEFAULT_READY_TIMEOUT_MS = 15_000;\n\nfunction hasMic(): boolean {\n return typeof navigator !== \"undefined\" && !!navigator.mediaDevices?.getUserMedia && typeof AudioWorkletNode !== \"undefined\";\n}\n\nfunction browserStorage(): SeatStorage | null {\n try {\n const s = (globalThis as { sessionStorage?: SeatStorage }).sessionStorage;\n return s ?? null;\n } catch {\n return null; // storage access can throw (sandboxed iframe, blocked cookies)\n }\n}\n\ninterface Seat {\n code: string;\n token: string;\n server: string;\n}\n\n/**\n * Lobbies alive in this page, by resume token. A remembered seat that is still in use here (two kits in one tab,\n * e.g. a local two-player demo) must not be taken over; after a reload this set starts empty.\n */\nconst liveSeats = new Set<Lobby>();\nfunction seatInUse(token: string): boolean {\n for (const l of liveSeats) if (l.resumeToken === token) return true;\n return false;\n}\n\n/** Entry point (DESIGN s.11.2). */\nexport class OneLobby extends Emitter<OneLobbyEvents> {\n readonly options: Required<Pick<OneLobbyOptions, \"gameId\" | \"netVersion\" | \"lobbyServer\" | \"mode\" | \"tickRate\" | \"hashEveryTicks\" | \"displayName\" | \"relayOnly\" | \"readyTimeoutMs\" | \"peerTimeoutMs\">>;\n private socketFactory: SocketFactory;\n private linkFactory: LinkFactory;\n private clock: Clock;\n private wall: () => number;\n private voiceCodec: VoiceCodec | null;\n private createMic: (() => FrameSource) | null;\n private createSink: (() => AudioSink) | null;\n private storage: SeatStorage | null;\n\n constructor(o: OneLobbyOptions) {\n super();\n const tickRate = o.tickRate ?? 20;\n this.options = {\n gameId: o.gameId,\n netVersion: o.netVersion,\n lobbyServer: o.lobbyServer,\n mode: o.mode ?? \"lockstep\",\n tickRate,\n hashEveryTicks: o.hashEveryTicks ?? tickRate,\n displayName: (o.displayName ?? \"\").trim(),\n relayOnly: o.relayOnly ?? false,\n readyTimeoutMs: o.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS,\n peerTimeoutMs: o.peerTimeoutMs ?? 0,\n };\n const a = o.advanced ?? {};\n const pick = <K extends keyof AdvancedOptions & keyof OneLobbyOptions>(k: K) => (a[k] !== undefined ? a[k] : o[k]);\n this.socketFactory = pick(\"socketFactory\") ?? defaultSocketFactory;\n this.linkFactory = pick(\"linkFactory\") ?? webRtcLinkFactory;\n this.clock = pick(\"clock\") ?? systemClock;\n this.wall = pick(\"wallClock\") ?? (() => Date.now());\n const codec = pick(\"voiceCodec\");\n this.voiceCodec = codec !== undefined ? codec : webCodecsOpusAvailable() ? webCodecsOpus : null;\n const mic = pick(\"createMic\");\n this.createMic = mic !== undefined ? mic : hasMic() ? () => new WorkletMic() : null;\n const sink = pick(\"createSink\");\n this.createSink = sink !== undefined ? sink : typeof AudioContext !== \"undefined\" ? () => new WebAudioSink() : null;\n this.storage = o.rememberSeat === false ? null : a.storage !== undefined ? a.storage : browserStorage();\n }\n\n // ---- remembered seat -------------------------------------------------------------------------------------------\n\n private get seatKey(): string {\n return `onelobby.seat.${this.options.gameId}`;\n }\n\n private loadSeat(): Seat | null {\n if (!this.storage) return null;\n try {\n const raw = this.storage.getItem(this.seatKey);\n if (!raw) return null;\n const s = JSON.parse(raw) as Partial<Seat>;\n if (typeof s.code !== \"string\" || typeof s.token !== \"string\" || s.server !== this.options.lobbyServer) return null;\n return s as Seat;\n } catch {\n return null;\n }\n }\n\n private saveSeat(code: string, token: string): void {\n if (!this.storage || !token) return;\n try {\n this.storage.setItem(this.seatKey, JSON.stringify({ code, token, server: this.options.lobbyServer } satisfies Seat));\n } catch {\n /* quota or blocked storage: the seat just is not remembered */\n }\n }\n\n private clearSeat(code?: string): void {\n if (!this.storage) return;\n try {\n if (code !== undefined) {\n const s = this.loadSeat();\n if (s && s.code !== code) return;\n }\n this.storage.removeItem(this.seatKey);\n } catch {\n /* ignore */\n }\n }\n\n /** The lobby code of the seat this tab can take back (rememberSeat), or null. */\n get rememberedCode(): string | null {\n return this.loadSeat()?.code ?? null;\n }\n\n /**\n * Take back the seat remembered from before a page reload (host or joiner). Resolves null when there is none\n * or the server no longer holds it (the seat is forgotten then).\n */\n async resume(): Promise<Lobby | null> {\n const seat = this.loadSeat();\n if (!seat || seatInUse(seat.token)) return null;\n try {\n return await this.rejoin(seat.token);\n } catch (e) {\n if (e instanceof OneLobbyError && e.code === \"invalid_token\") {\n this.clearSeat();\n return null;\n }\n throw e;\n }\n }\n\n // ---- connecting ------------------------------------------------------------------------------------------------\n\n private helloMsg(resumeToken?: string): Record<string, unknown> {\n return {\n t: \"hello\",\n proto: PROTO_ID,\n gameId: this.options.gameId,\n netVersion: this.options.netVersion,\n client: { lib: LIB_NAME, ver: LIB_VERSION, platform: typeof navigator !== \"undefined\" ? \"browser\" : \"node\" },\n name: this.options.displayName.slice(0, 24),\n ...(resumeToken ? { resumeToken } : {}),\n };\n }\n\n private async connect(url = this.options.lobbyServer, factory = this.socketFactory, timeoutMs = 0): Promise<{ sig: SignalingClient; welcome: WelcomeMsg }> {\n const sig = await this.open(url, factory, timeoutMs);\n try {\n const welcome = await sig.request<WelcomeMsg & { t: string }>(this.helloMsg(), \"welcome\");\n return { sig, welcome };\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n /** The signaling socket only (no hello yet); with a timeout (direct IP), no answer is `direct_unreachable`. */\n private async open(url: string, factory: SocketFactory, timeoutMs: number): Promise<SignalingClient> {\n const sig = new SignalingClient(url, factory, this.clock);\n if (timeoutMs > 0) {\n let cancel = () => {};\n const timeout = new Promise<never>((_, reject) => {\n cancel = this.clock.setTimeout(() => reject(new OneLobbyError(\"direct_unreachable\", `no answer from ${url} within ${timeoutMs} ms`)), timeoutMs);\n });\n try {\n await Promise.race([sig.connect(), timeout]);\n } catch (e) {\n sig.close();\n if (e instanceof OneLobbyError && e.code === \"connect_failed\") throw new OneLobbyError(\"direct_unreachable\", `no answer from ${url}`);\n throw e;\n } finally {\n cancel();\n }\n } else await sig.connect();\n return sig;\n }\n\n private makeLobby(sig: SignalingClient, welcome: WelcomeMsg, state: LobbyState, direct?: { links: LinkFactory }, rateLimit?: RateLimit): Lobby {\n const lobby = new Lobby({\n signaling: sig,\n state,\n ice: welcome.ice ?? [],\n links: direct?.links ?? this.linkFactory,\n direct: !!direct,\n clock: this.clock,\n wall: this.wall,\n hashEveryTicks: this.options.hashEveryTicks,\n relayOnly: state.relayOnly ?? this.options.relayOnly,\n displayName: this.options.displayName,\n voice: { codec: this.voiceCodec, createSource: this.createMic, createSink: this.createSink },\n readyTimeoutMs: this.options.readyTimeoutMs,\n peerTimeoutMs: this.options.peerTimeoutMs,\n rateLimit,\n });\n lobby.errorSink = (e) => this.emit(\"error\", e);\n if (this.storage && !direct) {\n liveSeats.add(lobby);\n this.saveSeat(lobby.code, lobby.resumeToken);\n lobby.seatSink = (token) => this.saveSeat(lobby.code, token);\n lobby.on(\"sessionEnded\", () => {\n liveSeats.delete(lobby);\n this.clearSeat(lobby.code);\n });\n }\n return lobby;\n }\n\n async host(h: HostOptions): Promise<Lobby> {\n const { sig, welcome } = await this.connect();\n try {\n const state = await sig.request<LobbyState>(\n {\n t: \"create\",\n name: h.name,\n maxPlayers: h.maxPlayers,\n visibility: h.visibility ?? \"unlisted\",\n mode: this.options.mode,\n tickRate: this.options.tickRate,\n lateJoin: h.lateJoin ?? true,\n meta: h.meta ?? {},\n ...(this.options.relayOnly ? { relayOnly: true } : {}),\n },\n \"lobby\",\n );\n return this.makeLobby(sig, welcome, state, undefined, h.rateLimit);\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n async join(code: string): Promise<Lobby> {\n const norm = normalizeCode(code);\n if (!norm) throw new OneLobbyError(\"bad_code\", `\"${code}\" is not a valid join code`);\n const seat = this.loadSeat();\n if (seat && seat.code === norm && !seatInUse(seat.token)) {\n // Same tab, same lobby (a page reload): take the old seat back instead of joining as a new player.\n try {\n return await this.rejoin(seat.token);\n } catch (e) {\n this.clearSeat();\n if (!(e instanceof OneLobbyError && e.code === \"invalid_token\")) throw e;\n }\n }\n const { sig, welcome } = await this.connect();\n try {\n const state = await sig.request<LobbyState>({ t: \"join\", code: norm }, \"lobby\");\n return this.makeLobby(sig, welcome, state);\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n /**\n * Join a direct-IP host (docs/direct-ip.md): a join code from the host's screen (`6B01-RGFE-P2B`, any case, spaces\n * or hyphens) or, advanced, an address (`ip`, `ip:port`, `[v6]:port`). No lobby server is involved: the code is\n * the host's address. A mistyped code fails at once with `bad_code`; nobody listening is `direct_unreachable`.\n * The address is also given to ICE as the host's UDP candidate. When the host leaves, `sessionEnded(\"host_left\")`.\n */\n async joinDirect(codeOrAddress: string, opts: JoinDirectOptions = {}): Promise<Lobby> {\n const { addr, links } = this.directTarget(codeOrAddress, opts);\n const { sig, welcome } = await this.connect(addr.url, this.socketFactory, opts.connectTimeoutMs ?? 5000);\n try {\n const state = await sig.request<LobbyState>({ t: \"join\", ...(opts.password ? { password: opts.password } : {}) }, \"lobby\");\n const lobby = this.makeLobby(sig, welcome, state, { links });\n // a joiner's joinCode is what it joined with (normalized), so a HUD can show it on both sides\n lobby.joinCode = joinedWith(addr);\n return lobby;\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n /**\n * Take a direct-IP seat back after a drop or a restart: the code (or address) joined before and that lobby's\n * `resumeToken` (save it after every join; it changes on each resume). The host keeps a dropped seat for 60 s and the\n * same peer id comes back. Rejects with `invalid_token` when the seat is gone: offer `joinDirect` then.\n */\n async rejoinDirect(codeOrAddress: string, resumeToken: string, opts: JoinDirectOptions = {}): Promise<Lobby> {\n const { addr, links } = this.directTarget(codeOrAddress, opts);\n const lobby = await this.resumeAt(resumeToken, addr.url, opts.connectTimeoutMs ?? 5000, { links });\n lobby.joinCode = joinedWith(addr);\n return lobby;\n }\n\n private directTarget(codeOrAddress: string, opts: JoinDirectOptions): { addr: NonNullable<ReturnType<typeof parseDirectAddress>>; links: LinkFactory } {\n let input = codeOrAddress.trim();\n if (looksLikeJoinCode(input)) {\n const d = decodeJoinCode(input);\n if (\"error\" in d)\n throw new OneLobbyError(d.error, d.error === \"unknown_version\" ? `\"${input}\" is a join code from a newer version of the game` : `\"${input}\" is not a valid join code: check for a typo`);\n input = `${d.ip}:${d.port}`;\n }\n const addr = parseDirectAddress(input, opts.defaultPort ?? DEFAULT_DIRECT_PORT);\n if (!addr) throw new OneLobbyError(\"bad_address\", `\"${codeOrAddress}\" is not a join code or an address (try 6B01-RGFE-P2B or 192.168.1.20:47810)`);\n const inner = this.linkFactory;\n const links: LinkFactory = { create: (o) => inner.create({ ...o, iceServers: [], extraRemoteCandidates: [addr.candidate] }) };\n return { addr, links };\n }\n\n /** @internal onelobby/direct's host: create the one lobby on the embedded hub, in direct mode. */\n async hostDirectWith(h: HostOptions, hooks: DirectLobbyHooks): Promise<Lobby> {\n const { sig, welcome } = await this.connect(\"onelobby-direct://host/\", hooks.socketFactory);\n try {\n const state = await sig.request<LobbyState>(\n { t: \"create\", name: h.name, maxPlayers: h.maxPlayers, visibility: \"unlisted\", mode: this.options.mode, tickRate: this.options.tickRate,\n lateJoin: h.lateJoin ?? true, meta: h.meta ?? {} },\n \"lobby\",\n );\n return this.makeLobby(sig, welcome, state, { links: hooks.wrapLinks(this.linkFactory) }, h.rateLimit);\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n async rejoin(resumeToken: string): Promise<Lobby> {\n return this.resumeAt(resumeToken, this.options.lobbyServer, 0);\n }\n\n private async resumeAt(resumeToken: string, url: string, timeoutMs: number, direct?: { links: LinkFactory }): Promise<Lobby> {\n const sig = await this.open(url, this.socketFactory, timeoutMs);\n try {\n // The server answers hello(resumeToken) with welcome then the full lobby state; listen for both up front.\n const lobbyP = sig.expect<LobbyState>(\"lobby\");\n const welcomeP = sig.request<WelcomeMsg & { t: string }>(this.helloMsg(resumeToken), \"welcome\");\n lobbyP.catch(() => {}); // settled below or by close()\n const welcome = await welcomeP;\n // spec: an unknown or expired token is ignored -> plain welcome (no peerId), and no lobby follows\n if (welcome.peerId === undefined) throw new OneLobbyError(\"invalid_token\", \"resume token unknown or expired\");\n const state = await lobbyP;\n return this.makeLobby(sig, welcome, state, direct);\n } catch (e) {\n sig.close();\n throw e;\n }\n }\n\n async list(opts: ListOptions = {}): Promise<LobbySummary[]> {\n const { sig } = await this.connect();\n try {\n const res = await sig.request<{ t: string; items: LobbySummary[] }>(\n { t: \"list\", visibility: \"public\", meta: opts.meta ?? {}, limit: Math.min(100, opts.limit ?? 50), ...(opts.cursor ? { cursor: opts.cursor } : {}) },\n \"lobbies\",\n );\n return res.items ?? [];\n } finally {\n sig.close();\n }\n }\n}\n\n/** The join code for an IPv4 address (a host name or IPv6 has none: its `host:port` instead). */\nfunction joinedWith(addr: { host: string; port: number; isIPv6: boolean; display: string }): string {\n if (addr.isIPv6) return addr.display;\n try {\n return encodeJoinCode(addr.host, addr.port);\n } catch {\n return addr.display;\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "onelobby",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Multiplayer for browser games: host/join lobbies by short code, peer-to-peer WebRTC, lockstep sync, text and voice chat. Zero runtime dependencies; works from a plain <script> tag.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"multiplayer",
|