onelobby 0.6.0 → 0.7.0

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.
Files changed (45) hide show
  1. package/README.md +2 -2
  2. package/dist/direct/hub.d.ts +2 -0
  3. package/dist/direct/hub.js +20 -1
  4. package/dist/direct/hub.js.map +1 -1
  5. package/dist/friendly.d.ts +1 -0
  6. package/dist/friendly.js +2 -0
  7. package/dist/friendly.js.map +1 -1
  8. package/dist/index.d.ts +0 -2
  9. package/dist/index.js +0 -2
  10. package/dist/index.js.map +1 -1
  11. package/dist/lobby.d.ts +29 -1
  12. package/dist/lobby.js +26 -6
  13. package/dist/lobby.js.map +1 -1
  14. package/dist/nostr/limits.d.ts +2 -0
  15. package/dist/nostr/limits.js +2 -0
  16. package/dist/nostr/limits.js.map +1 -1
  17. package/dist/nostr/relays.js +11 -1
  18. package/dist/nostr/relays.js.map +1 -1
  19. package/dist/onelobby.bundle.js +10 -10
  20. package/dist/onelobby.bundle.js.map +4 -4
  21. package/dist/onelobby.bundle.mjs +10 -10
  22. package/dist/onelobby.bundle.mjs.map +4 -4
  23. package/dist/onelobby.d.ts +1 -1
  24. package/dist/onelobby.global.js +79 -19
  25. package/dist/onelobby.global.js.map +3 -3
  26. package/dist/onelobby.global.min.js +10 -10
  27. package/dist/onelobby.global.min.js.map +4 -4
  28. package/dist/onelobby.js +10 -1
  29. package/dist/onelobby.js.map +1 -1
  30. package/dist/signaling.d.ts +2 -0
  31. package/dist/signaling.js.map +1 -1
  32. package/dist/socket.d.ts +15 -6
  33. package/dist/socket.js.map +1 -1
  34. package/dist/testing.d.ts +2 -0
  35. package/dist/testing.js +18 -2
  36. package/dist/testing.js.map +1 -1
  37. package/dist/transport.d.ts +2 -0
  38. package/dist/transport.js.map +1 -1
  39. package/dist/webrtc.d.ts +5 -0
  40. package/dist/webrtc.js +21 -2
  41. package/dist/webrtc.js.map +1 -1
  42. package/package.json +1 -1
  43. package/dist/deprecated.d.ts +0 -18
  44. package/dist/deprecated.js +0 -11
  45. package/dist/deprecated.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"friendly.js","sourceRoot":"","sources":["../src/friendly.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Player-facing helpers: plain-language error text and shareable join links.\n * Nothing here talks to the network; it is safe to call from any UI code.\n */\nimport { formatCode, normalizeCode } from \"./crockford.js\";\nimport type { IpCodeKind, JoinCodeKind } from \"./join-code-kind.js\";\n\nexport interface FriendlyError {\n /** Stable machine code (the OneLobbyError code, or \"unknown\"). */\n code: string;\n /** One short sentence a player can read. */\n message: string;\n /** What the player can do about it, when there is something. */\n hint?: string;\n}\n\ntype Entry = { message: string; hint?: string };\n\n/**\n * Every `code` a `OneLobbyError` can carry. Compare against these instead of string literals; C# has the shared ones in\n * `LobbyErrorCodes`. `kicked` with `data.reason === \"denied\"` is a refused join request; `rate_limited` with\n * `data.reason === \"max_lobbies\"` is the per-address lobby limit.\n */\nexport const ErrorCodes = {\n // from the lobby server or a direct host's hub\n UnknownCode: \"unknown_code\",\n BadCode: \"bad_code\",\n LobbyFull: \"lobby_full\",\n VersionMismatch: \"version_mismatch\",\n InGame: \"in_game\",\n Kicked: \"kicked\",\n BadPassword: \"bad_password\",\n NotHost: \"not_host\",\n RateLimited: \"rate_limited\",\n ServerFull: \"server_full\",\n GameNotAllowed: \"game_not_allowed\",\n BadRequest: \"bad_request\",\n // from this client\n ServerClosed: \"server_closed\",\n IceFailed: \"ice_failed\",\n Timeout: \"timeout\",\n DirectUnreachable: \"direct_unreachable\",\n BadAddress: \"bad_address\",\n PortInUse: \"port_in_use\",\n ConnectFailed: \"connect_failed\",\n Closed: \"closed\",\n ServerLost: \"server_lost\",\n InvalidToken: \"invalid_token\",\n NoServer: \"no_server\",\n // from the shared pause (lobby.pause)\n NotInGame: \"not_in_game\",\n NotAllowed: \"not_allowed\",\n // from a blueprint (onelobby.json)\n BadConfig: \"bad_config\",\n VoiceDisabled: \"voice_disabled\",\n ChatDisabled: \"chat_disabled\",\n PauseDisabled: \"pause_disabled\",\n ListingDisabled: \"listing_disabled\",\n NostrDisabled: \"nostr_disabled\",\n IdentityAnonymous: \"identity_anonymous\",\n} as const;\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\nconst TEXT: Record<string, Entry> = {\n // server errors (spec/signaling.md)\n 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.\" },\n bad_code: { message: \"That doesn't look like a lobby code.\", hint: \"Codes are 6 letters and numbers, like 7HQ 4K2.\" },\n lobby_full: { message: \"That lobby is full.\", hint: \"Ask the host to make room, or start your own lobby.\" },\n 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.\" },\n in_game: { message: \"That game has already started.\", hint: \"Ask the host to let people join mid-game, or wait for the next round.\" },\n kicked: { message: \"The host removed you from the lobby.\" },\n denied: { message: \"The host didn't let you in this time.\", hint: \"Ask the host to approve you, then try again.\" },\n not_host: { message: \"Only the host can do that.\" },\n rate_limited: { message: \"Slow down a little: too many tries in a short time.\", hint: \"Wait a moment, then try again.\" },\n max_lobbies: { message: \"You already have the most lobbies open that this server allows.\", hint: \"Close one of your other lobbies, then try again.\" },\n server_full: { message: \"The game server is full right now.\", hint: \"Try again in a few minutes.\" },\n 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.\" },\n 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.\" },\n // client errors\n connect_failed: { message: \"Couldn't reach the game server.\", hint: \"Check your internet connection, then try again.\" },\n closed: { message: \"Lost the connection to the game server.\", hint: \"Check your internet connection, then try again.\" },\n 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.\" },\n invalid_token: { message: \"Your old seat in that lobby is no longer available.\", hint: \"Join again with the lobby code.\" },\n 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.\" },\n no_server: { message: \"This game has no lobby server address yet.\", hint: \"Enter one in the multiplayer settings, or host from your own computer with a join code.\" },\n timeout: { message: \"The connection took too long.\", hint: \"Check your internet connection, then try again.\" },\n voice_disabled: { message: \"Voice chat is turned off in this game.\" },\n chat_disabled: { message: \"Text chat is turned off in this game.\" },\n pause_disabled: { message: \"Pausing is turned off in this game.\" },\n not_in_game: { message: \"You can only pause while a game is running.\", hint: \"Start the game first.\" },\n not_allowed: { message: \"You can't do that right now.\", hint: \"The player who paused, or the host, can resume.\" },\n listing_disabled: { message: \"This game has no public lobby list.\", hint: \"Join with a code or a link from the host.\" },\n direct_unreachable: { message: \"Couldn't reach the host.\", hint: \"Check the code, and that the host is still online. A host in a browser needs its tab open and visible.\" },\n nostr_disabled: { message: \"This game can't be hosted from a browser.\", hint: \"Host from the desktop version, or let the game's owner know.\" },\n identity_anonymous: { message: \"This game doesn't use player names.\", hint: \"Everyone shows up as Player 1, Player 2, and so on.\" },\n bad_config: { message: \"The game's multiplayer settings file is not valid.\", hint: \"Let the game's owner know; the details are in the error message.\" },\n left: { message: \"You left the lobby.\" },\n unknown: { message: \"Something went wrong.\", hint: \"Try again. If it keeps happening, reload the page.\" },\n};\n\nconst CODE_RE = /^[a-z][a-z0-9_]*$/;\n\nfunction codeOf(err: unknown): { code: string | null; data?: Record<string, unknown> } {\n if (typeof err === \"string\") {\n const m = /^([a-z][a-z0-9_]*)(?::|$)/.exec(err.trim());\n return { code: m ? m[1] : null };\n }\n if (err && typeof err === \"object\") {\n const o = err as { code?: unknown; data?: unknown; message?: unknown; name?: unknown };\n const data = o.data && typeof o.data === \"object\" ? (o.data as Record<string, unknown>) : undefined;\n if (typeof o.code === \"string\" && CODE_RE.test(o.code)) return { code: o.code, data };\n if (typeof o.message === \"string\") {\n const m = /^([a-z][a-z0-9_]*):/.exec(o.message);\n if (m && TEXT[m[1]]) return { code: m[1], data };\n // browser network failures (fetch/WebSocket) surface as TypeError / NetworkError\n if (o.name === \"NetworkError\" || /network|failed to fetch|websocket/i.test(o.message)) return { code: \"connect_failed\" };\n if (o.name === \"TimeoutError\" || /timed? ?out/i.test(o.message)) return { code: \"timeout\" };\n }\n }\n return { code: null };\n}\n\n/**\n * Plain-language, player-facing text for any error the kit (or its server) can produce.\n * Never returns a stack trace or raw technical message; unknown errors get a generic friendly line.\n */\nexport function describeError(err: unknown): FriendlyError {\n const { code, data } = codeOf(err);\n if (!code) return { code: \"unknown\", ...TEXT.unknown };\n let key = code;\n if (code === \"kicked\" && data?.reason === \"denied\") key = \"denied\";\n if (code === \"rate_limited\" && data?.reason === \"max_lobbies\") key = \"max_lobbies\";\n const e = TEXT[key];\n if (!e) return { code, ...TEXT.unknown };\n const out: FriendlyError = { code, message: e.message };\n if (e.hint) out.hint = e.hint;\n if (code === \"rate_limited\" && typeof data?.retryAfterMs === \"number\" && key === \"rate_limited\") {\n const s = Math.max(1, Math.ceil(data.retryAfterMs / 1000));\n out.hint = `Wait ${s} second${s === 1 ? \"\" : \"s\"}, then try again.`;\n }\n return out;\n}\n\nfunction currentHref(): string | undefined {\n try {\n return typeof location !== \"undefined\" ? location.href : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * A link that opens the game straight into lobby `code`, e.g. `https://game.example/?join=7HQ-4K2`.\n * `base` defaults to the current page; any existing `param` is replaced. Throws on an invalid code.\n */\nexport function shareUrl(code: string, base?: string, param = \"join\"): string {\n const norm = normalizeCode(code);\n if (!norm) throw new Error(`\"${code}\" is not a valid lobby code`);\n const b = base ?? currentHref();\n if (!b) throw new Error(\"shareUrl needs a base URL outside the browser\");\n const u = new URL(b);\n u.searchParams.delete(param);\n // URLSearchParams would encode the display space as \"+\": use a hyphen, which normalizeCode also accepts\n u.searchParams.set(param, formatCode(norm).replace(\" \", \"-\"));\n return u.toString();\n}\n\n/** What a direct host answers at `GET /joininfo` (onelobby/direct). */\nexport interface JoinInfo {\n joinCode: string;\n joinCodeKind: JoinCodeKind;\n /** The IP code (address and port); equals `joinCode` when Nostr is off. */\n directCode: string;\n directCodeKind: IpCodeKind;\n /** `http://<public or LAN address>:<port>/` when the host serves pages; else null. */\n joinUrl: string | null;\n port: number;\n}\n\n/**\n * For a page served by a direct host (`hostDirect` with `http`): the host's current join code and share link, so a\n * player who opened the page at a LAN or localhost address still shares the public one. `base` defaults to the page's\n * origin. Null when the host has no code yet or cannot be reached. Ask again after a while: the code turns \"internet\"\n * when the router answers a later port-mapping retry.\n */\nexport async function directJoinInfo(base?: string): Promise<JoinInfo | null> {\n const b = base ?? currentHref();\n if (!b) return null;\n try {\n const res = await fetch(new URL(\"/joininfo\", b), { cache: \"no-store\" });\n return res.ok ? ((await res.json()) as JoinInfo) : null;\n } catch {\n return null;\n }\n}\n\n/** The lobby code carried by a share link (see shareUrl), or null when there is none or it is not a valid code. */\nexport function codeFromUrl(url?: string, param = \"join\"): string | null {\n const href = url ?? currentHref();\n if (!href) return null;\n let u: URL;\n try {\n u = new URL(href, \"http://localhost/\");\n } catch {\n return null;\n }\n const v = u.searchParams.get(param) ?? new URLSearchParams(u.hash.replace(/^#/, \"\")).get(param);\n return v ? normalizeCode(v) : null;\n}\n"]}
1
+ {"version":3,"file":"friendly.js","sourceRoot":"","sources":["../src/friendly.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Player-facing helpers: plain-language error text and shareable join links.\n * Nothing here talks to the network; it is safe to call from any UI code.\n */\nimport { formatCode, normalizeCode } from \"./crockford.js\";\nimport type { IpCodeKind, JoinCodeKind } from \"./join-code-kind.js\";\n\nexport interface FriendlyError {\n /** Stable machine code (the OneLobbyError code, or \"unknown\"). */\n code: string;\n /** One short sentence a player can read. */\n message: string;\n /** What the player can do about it, when there is something. */\n hint?: string;\n}\n\ntype Entry = { message: string; hint?: string };\n\n/**\n * Every `code` a `OneLobbyError` can carry. Compare against these instead of string literals; C# has the shared ones in\n * `LobbyErrorCodes`. `kicked` with `data.reason === \"denied\"` is a refused join request; `rate_limited` with\n * `data.reason === \"max_lobbies\"` is the per-address lobby limit.\n */\nexport const ErrorCodes = {\n // from the lobby server or a direct host's hub\n UnknownCode: \"unknown_code\",\n BadCode: \"bad_code\",\n LobbyFull: \"lobby_full\",\n VersionMismatch: \"version_mismatch\",\n InGame: \"in_game\",\n Kicked: \"kicked\",\n BadPassword: \"bad_password\",\n NotHost: \"not_host\",\n RateLimited: \"rate_limited\",\n ServerFull: \"server_full\",\n GameNotAllowed: \"game_not_allowed\",\n BadRequest: \"bad_request\",\n // from this client\n ServerClosed: \"server_closed\",\n IceFailed: \"ice_failed\",\n Timeout: \"timeout\",\n DirectUnreachable: \"direct_unreachable\",\n BadAddress: \"bad_address\",\n PortInUse: \"port_in_use\",\n Unsupported: \"unsupported\",\n ConnectFailed: \"connect_failed\",\n Closed: \"closed\",\n ServerLost: \"server_lost\",\n InvalidToken: \"invalid_token\",\n NoServer: \"no_server\",\n // from the shared pause (lobby.pause)\n NotInGame: \"not_in_game\",\n NotAllowed: \"not_allowed\",\n // from a blueprint (onelobby.json)\n BadConfig: \"bad_config\",\n VoiceDisabled: \"voice_disabled\",\n ChatDisabled: \"chat_disabled\",\n PauseDisabled: \"pause_disabled\",\n ListingDisabled: \"listing_disabled\",\n NostrDisabled: \"nostr_disabled\",\n IdentityAnonymous: \"identity_anonymous\",\n} as const;\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\nconst TEXT: Record<string, Entry> = {\n // server errors (spec/signaling.md)\n 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.\" },\n bad_code: { message: \"That doesn't look like a lobby code.\", hint: \"Codes are 6 letters and numbers, like 7HQ 4K2.\" },\n lobby_full: { message: \"That lobby is full.\", hint: \"Ask the host to make room, or start your own lobby.\" },\n 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.\" },\n in_game: { message: \"That game has already started.\", hint: \"Ask the host to let people join mid-game, or wait for the next round.\" },\n kicked: { message: \"The host removed you from the lobby.\" },\n denied: { message: \"The host didn't let you in this time.\", hint: \"Ask the host to approve you, then try again.\" },\n not_host: { message: \"Only the host can do that.\" },\n rate_limited: { message: \"Slow down a little: too many tries in a short time.\", hint: \"Wait a moment, then try again.\" },\n max_lobbies: { message: \"You already have the most lobbies open that this server allows.\", hint: \"Close one of your other lobbies, then try again.\" },\n server_full: { message: \"The game server is full right now.\", hint: \"Try again in a few minutes.\" },\n 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.\" },\n 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.\" },\n // client errors\n connect_failed: { message: \"Couldn't reach the game server.\", hint: \"Check your internet connection, then try again.\" },\n closed: { message: \"Lost the connection to the game server.\", hint: \"Check your internet connection, then try again.\" },\n 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.\" },\n invalid_token: { message: \"Your old seat in that lobby is no longer available.\", hint: \"Join again with the lobby code.\" },\n 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.\" },\n no_server: { message: \"This game has no lobby server address yet.\", hint: \"Enter one in the multiplayer settings, or host from your own computer with a join code.\" },\n timeout: { message: \"The connection took too long.\", hint: \"Check your internet connection, then try again.\" },\n unsupported: { message: \"The host's game is too old for that.\", hint: \"Ask the host to update the game.\" },\n voice_disabled: { message: \"Voice chat is turned off in this game.\" },\n chat_disabled: { message: \"Text chat is turned off in this game.\" },\n pause_disabled: { message: \"Pausing is turned off in this game.\" },\n not_in_game: { message: \"You can only pause while a game is running.\", hint: \"Start the game first.\" },\n not_allowed: { message: \"You can't do that right now.\", hint: \"The player who paused, or the host, can resume.\" },\n listing_disabled: { message: \"This game has no public lobby list.\", hint: \"Join with a code or a link from the host.\" },\n direct_unreachable: { message: \"Couldn't reach the host.\", hint: \"Check the code, and that the host is still online. A host in a browser needs its tab open and visible.\" },\n nostr_disabled: { message: \"This game can't be hosted from a browser.\", hint: \"Host from the desktop version, or let the game's owner know.\" },\n identity_anonymous: { message: \"This game doesn't use player names.\", hint: \"Everyone shows up as Player 1, Player 2, and so on.\" },\n bad_config: { message: \"The game's multiplayer settings file is not valid.\", hint: \"Let the game's owner know; the details are in the error message.\" },\n left: { message: \"You left the lobby.\" },\n unknown: { message: \"Something went wrong.\", hint: \"Try again. If it keeps happening, reload the page.\" },\n};\n\nconst CODE_RE = /^[a-z][a-z0-9_]*$/;\n\nfunction codeOf(err: unknown): { code: string | null; data?: Record<string, unknown> } {\n if (typeof err === \"string\") {\n const m = /^([a-z][a-z0-9_]*)(?::|$)/.exec(err.trim());\n return { code: m ? m[1] : null };\n }\n if (err && typeof err === \"object\") {\n const o = err as { code?: unknown; data?: unknown; message?: unknown; name?: unknown };\n const data = o.data && typeof o.data === \"object\" ? (o.data as Record<string, unknown>) : undefined;\n if (typeof o.code === \"string\" && CODE_RE.test(o.code)) return { code: o.code, data };\n if (typeof o.message === \"string\") {\n const m = /^([a-z][a-z0-9_]*):/.exec(o.message);\n if (m && TEXT[m[1]]) return { code: m[1], data };\n // browser network failures (fetch/WebSocket) surface as TypeError / NetworkError\n if (o.name === \"NetworkError\" || /network|failed to fetch|websocket/i.test(o.message)) return { code: \"connect_failed\" };\n if (o.name === \"TimeoutError\" || /timed? ?out/i.test(o.message)) return { code: \"timeout\" };\n }\n }\n return { code: null };\n}\n\n/**\n * Plain-language, player-facing text for any error the kit (or its server) can produce.\n * Never returns a stack trace or raw technical message; unknown errors get a generic friendly line.\n */\nexport function describeError(err: unknown): FriendlyError {\n const { code, data } = codeOf(err);\n if (!code) return { code: \"unknown\", ...TEXT.unknown };\n let key = code;\n if (code === \"kicked\" && data?.reason === \"denied\") key = \"denied\";\n if (code === \"rate_limited\" && data?.reason === \"max_lobbies\") key = \"max_lobbies\";\n const e = TEXT[key];\n if (!e) return { code, ...TEXT.unknown };\n const out: FriendlyError = { code, message: e.message };\n if (e.hint) out.hint = e.hint;\n if (code === \"rate_limited\" && typeof data?.retryAfterMs === \"number\" && key === \"rate_limited\") {\n const s = Math.max(1, Math.ceil(data.retryAfterMs / 1000));\n out.hint = `Wait ${s} second${s === 1 ? \"\" : \"s\"}, then try again.`;\n }\n return out;\n}\n\nfunction currentHref(): string | undefined {\n try {\n return typeof location !== \"undefined\" ? location.href : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * A link that opens the game straight into lobby `code`, e.g. `https://game.example/?join=7HQ-4K2`.\n * `base` defaults to the current page; any existing `param` is replaced. Throws on an invalid code.\n */\nexport function shareUrl(code: string, base?: string, param = \"join\"): string {\n const norm = normalizeCode(code);\n if (!norm) throw new Error(`\"${code}\" is not a valid lobby code`);\n const b = base ?? currentHref();\n if (!b) throw new Error(\"shareUrl needs a base URL outside the browser\");\n const u = new URL(b);\n u.searchParams.delete(param);\n // URLSearchParams would encode the display space as \"+\": use a hyphen, which normalizeCode also accepts\n u.searchParams.set(param, formatCode(norm).replace(\" \", \"-\"));\n return u.toString();\n}\n\n/** What a direct host answers at `GET /joininfo` (onelobby/direct). */\nexport interface JoinInfo {\n joinCode: string;\n joinCodeKind: JoinCodeKind;\n /** The IP code (address and port); equals `joinCode` when Nostr is off. */\n directCode: string;\n directCodeKind: IpCodeKind;\n /** `http://<public or LAN address>:<port>/` when the host serves pages; else null. */\n joinUrl: string | null;\n port: number;\n}\n\n/**\n * For a page served by a direct host (`hostDirect` with `http`): the host's current join code and share link, so a\n * player who opened the page at a LAN or localhost address still shares the public one. `base` defaults to the page's\n * origin. Null when the host has no code yet or cannot be reached. Ask again after a while: the code turns \"internet\"\n * when the router answers a later port-mapping retry.\n */\nexport async function directJoinInfo(base?: string): Promise<JoinInfo | null> {\n const b = base ?? currentHref();\n if (!b) return null;\n try {\n const res = await fetch(new URL(\"/joininfo\", b), { cache: \"no-store\" });\n return res.ok ? ((await res.json()) as JoinInfo) : null;\n } catch {\n return null;\n }\n}\n\n/** The lobby code carried by a share link (see shareUrl), or null when there is none or it is not a valid code. */\nexport function codeFromUrl(url?: string, param = \"join\"): string | null {\n const href = url ?? currentHref();\n if (!href) return null;\n let u: URL;\n try {\n u = new URL(href, \"http://localhost/\");\n } catch {\n return null;\n }\n const v = u.searchParams.get(param) ?? new URLSearchParams(u.hash.replace(/^#/, \"\")).get(param);\n return v ? normalizeCode(v) : null;\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -35,8 +35,6 @@ export type { PeerLink, LinkFactory, SocketFactory, SocketLike, IceServer, Chann
35
35
  export type { Clock } from "./clock.js";
36
36
  export { describeError, shareUrl, codeFromUrl, directJoinInfo, ErrorCodes } from "./friendly.js";
37
37
  export type { FriendlyError, ErrorCode, JoinInfo } from "./friendly.js";
38
- export { Keepalive, KeepaliveError } from "./deprecated.js";
39
- export type { KeepaliveOptions, KeepaliveEvents } from "./deprecated.js";
40
38
  export { checkNostrRelays, DEFAULT_NOSTR_RELAYS, DEFAULT_NOSTR_STUN } from "./nostr/relays.js";
41
39
  export type { RelayCheck, RelayConnect } from "./nostr/relays.js";
42
40
  export { encodeRoomCode } from "./joincode.js";
package/dist/index.js CHANGED
@@ -23,8 +23,6 @@ export { decodeJoinCode, looksLikeJoinCode } from "./joincode.js";
23
23
  export { webRtcLinkFactoryFor } from "./webrtc.js";
24
24
  export { OneLobbyError } from "./signaling.js";
25
25
  export { describeError, shareUrl, codeFromUrl, directJoinInfo, ErrorCodes } from "./friendly.js";
26
- // Pre-rename names, kept for one version so existing games keep working.
27
- export { Keepalive, KeepaliveError } from "./deprecated.js";
28
26
  // Nostr signaling for direct hosts (spec/nostr-signaling.md): relay probe for picking hosting.nostrRelays.
29
27
  export { checkNostrRelays, DEFAULT_NOSTR_RELAYS, DEFAULT_NOSTR_STUN } from "./nostr/relays.js";
30
28
  export { encodeRoomCode } from "./joincode.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAEzD,0GAA0G;AAC1G,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEjF,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,+GAA+G;AAC/G,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAInC,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC3D,wHAAwH;AACxH,8GAA8G;AAC9G,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElE,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAK/C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEjG,yEAAyE;AACzE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE5D,2GAA2G;AAC3G,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE/F,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC","sourcesContent":["/**\n * Public API of `onelobby`: create a `OneLobby`, `host()` / `join(code)` / `list()` lobbies, then use the\n * `Lobby`'s lockstep, raw messages, chat and voice. Lower-level pieces (codec, chunking, migration, jitter buffer,\n * WebRTC/signaling classes, clocks, the gridpaint demo game) live in `onelobby/internals`.\n */\nexport { OneLobby, LIB_NAME, LIB_VERSION } from \"./onelobby.js\";\nexport type { OneLobbyOptions, FromConfigOverrides, HostOptions, ListOptions, OneLobbyEvents, JoinDirectOptions } from \"./onelobby.js\";\nexport { DEFAULT_READY_TIMEOUT_MS } from \"./onelobby.js\";\nexport type { AdvancedOptions, SeatStorage } from \"./onelobby.js\";\n// Blueprint v1: onelobby.json (docs/design/blueprint.md). OneLobby.fromConfig(json) builds a kit from it.\nexport { parseBlueprint, defineBlueprint, BlueprintError } from \"./blueprint.js\";\nexport type { Blueprint, BlueprintInput, BlueprintEngine } from \"./blueprint.js\";\nexport { Lobby } from \"./lobby.js\";\nexport type { LobbyEvents, LobbyConfig, Peer, Phase, RateLimit } from \"./lobby.js\";\nexport { MIN_PEER_TIMEOUT_MS } from \"./lobby.js\";\n// WebSocket-shaped sockets over a lobby: lobby.asWebSocket() (joiner), lobby.onConnection(conn => ...) (host).\nexport { closeCodeFor } from \"./socket.js\";\nexport type { LobbySocket, LobbySocketOptions } from \"./socket.js\";\nexport { Lockstep } from \"./lockstep.js\";\nexport type { Frame, DesyncInfo, LockstepEvents } from \"./lockstep.js\";\nexport { Chat, CHAT_MAX_BYTES } from \"./chat.js\";\nexport { Pause } from \"./pause.js\";\nexport type { PauseState, PauseEvents, PauseMode, PauseRefusal } from \"./pause.js\";\nexport type { ChatMessage, ChatEvents } from \"./chat.js\";\nexport { Voice } from \"./voice.js\";\nexport type { VoiceEvents, VoiceDeps } from \"./voice.js\";\nexport type { VoiceMode } from \"./voice-core.js\";\nexport type { VoiceCodec, VoiceEncoderHandle, VoiceDecoderHandle, FrameSource, AudioSink, VoiceError } from \"./voice-types.js\";\nexport { webCodecsOpusAvailable } from \"./voice-browser.js\";\nexport { normalizeCode, formatCode } from \"./crockford.js\";\n// Direct IP (docs/design/direct-ip.md): kit.joinDirect(code); the code is the host's address in disguise. Hosting is in\n// onelobby/direct (Node). webRtcLinkFactoryFor gives Node joiners a WebRTC stack (node-datachannel/polyfill).\nexport { decodeJoinCode, looksLikeJoinCode } from \"./joincode.js\";\nexport type { DecodedJoinCode, JoinCodeError } from \"./joincode.js\";\nexport { webRtcLinkFactoryFor } from \"./webrtc.js\";\nexport { OneLobbyError } from \"./signaling.js\";\nexport type { LobbySummary, LobbyState, PeerInfo } from \"./signaling.js\";\n// Types for the seams in OneLobbyOptions (ChannelName also tags raw messages).\nexport type { PeerLink, LinkFactory, SocketFactory, SocketLike, IceServer, ChannelName } from \"./transport.js\";\nexport type { Clock } from \"./clock.js\";\nexport { describeError, shareUrl, codeFromUrl, directJoinInfo, ErrorCodes } from \"./friendly.js\";\nexport type { FriendlyError, ErrorCode, JoinInfo } from \"./friendly.js\";\n// Pre-rename names, kept for one version so existing games keep working.\nexport { Keepalive, KeepaliveError } from \"./deprecated.js\";\nexport type { KeepaliveOptions, KeepaliveEvents } from \"./deprecated.js\";\n// Nostr signaling for direct hosts (spec/nostr-signaling.md): relay probe for picking hosting.nostrRelays.\nexport { checkNostrRelays, DEFAULT_NOSTR_RELAYS, DEFAULT_NOSTR_STUN } from \"./nostr/relays.js\";\nexport type { RelayCheck, RelayConnect } from \"./nostr/relays.js\";\nexport { encodeRoomCode } from \"./joincode.js\";\n// Browser host (no Node process, no port): kit.hostNostr(options).\nexport type { BrowserHostOptions, VisibilityDoc } from \"./direct/browser-host.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAEzD,0GAA0G;AAC1G,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEjF,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,+GAA+G;AAC/G,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAInC,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC3D,wHAAwH;AACxH,8GAA8G;AAC9G,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElE,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAK/C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEjG,2GAA2G;AAC3G,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE/F,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC","sourcesContent":["/**\n * Public API of `onelobby`: create a `OneLobby`, `host()` / `join(code)` / `list()` lobbies, then use the\n * `Lobby`'s lockstep, raw messages, chat and voice. Lower-level pieces (codec, chunking, migration, jitter buffer,\n * WebRTC/signaling classes, clocks, the gridpaint demo game) live in `onelobby/internals`.\n */\nexport { OneLobby, LIB_NAME, LIB_VERSION } from \"./onelobby.js\";\nexport type { OneLobbyOptions, FromConfigOverrides, HostOptions, ListOptions, OneLobbyEvents, JoinDirectOptions } from \"./onelobby.js\";\nexport { DEFAULT_READY_TIMEOUT_MS } from \"./onelobby.js\";\nexport type { AdvancedOptions, SeatStorage } from \"./onelobby.js\";\n// Blueprint v1: onelobby.json (docs/design/blueprint.md). OneLobby.fromConfig(json) builds a kit from it.\nexport { parseBlueprint, defineBlueprint, BlueprintError } from \"./blueprint.js\";\nexport type { Blueprint, BlueprintInput, BlueprintEngine } from \"./blueprint.js\";\nexport { Lobby } from \"./lobby.js\";\nexport type { LobbyEvents, LobbyConfig, Peer, Phase, RateLimit } from \"./lobby.js\";\nexport { MIN_PEER_TIMEOUT_MS } from \"./lobby.js\";\n// WebSocket-shaped sockets over a lobby: lobby.asWebSocket() (joiner), lobby.onConnection(conn => ...) (host).\nexport { closeCodeFor } from \"./socket.js\";\nexport type { LobbySocket, LobbySocketOptions } from \"./socket.js\";\nexport { Lockstep } from \"./lockstep.js\";\nexport type { Frame, DesyncInfo, LockstepEvents } from \"./lockstep.js\";\nexport { Chat, CHAT_MAX_BYTES } from \"./chat.js\";\nexport { Pause } from \"./pause.js\";\nexport type { PauseState, PauseEvents, PauseMode, PauseRefusal } from \"./pause.js\";\nexport type { ChatMessage, ChatEvents } from \"./chat.js\";\nexport { Voice } from \"./voice.js\";\nexport type { VoiceEvents, VoiceDeps } from \"./voice.js\";\nexport type { VoiceMode } from \"./voice-core.js\";\nexport type { VoiceCodec, VoiceEncoderHandle, VoiceDecoderHandle, FrameSource, AudioSink, VoiceError } from \"./voice-types.js\";\nexport { webCodecsOpusAvailable } from \"./voice-browser.js\";\nexport { normalizeCode, formatCode } from \"./crockford.js\";\n// Direct IP (docs/design/direct-ip.md): kit.joinDirect(code); the code is the host's address in disguise. Hosting is in\n// onelobby/direct (Node). webRtcLinkFactoryFor gives Node joiners a WebRTC stack (node-datachannel/polyfill).\nexport { decodeJoinCode, looksLikeJoinCode } from \"./joincode.js\";\nexport type { DecodedJoinCode, JoinCodeError } from \"./joincode.js\";\nexport { webRtcLinkFactoryFor } from \"./webrtc.js\";\nexport { OneLobbyError } from \"./signaling.js\";\nexport type { LobbySummary, LobbyState, PeerInfo } from \"./signaling.js\";\n// Types for the seams in OneLobbyOptions (ChannelName also tags raw messages).\nexport type { PeerLink, LinkFactory, SocketFactory, SocketLike, IceServer, ChannelName } from \"./transport.js\";\nexport type { Clock } from \"./clock.js\";\nexport { describeError, shareUrl, codeFromUrl, directJoinInfo, ErrorCodes } from \"./friendly.js\";\nexport type { FriendlyError, ErrorCode, JoinInfo } from \"./friendly.js\";\n// Nostr signaling for direct hosts (spec/nostr-signaling.md): relay probe for picking hosting.nostrRelays.\nexport { checkNostrRelays, DEFAULT_NOSTR_RELAYS, DEFAULT_NOSTR_STUN } from \"./nostr/relays.js\";\nexport type { RelayCheck, RelayConnect } from \"./nostr/relays.js\";\nexport { encodeRoomCode } from \"./joincode.js\";\n// Browser host (no Node process, no port): kit.hostNostr(options).\nexport type { BrowserHostOptions, VisibilityDoc } from \"./direct/browser-host.js\";\n"]}
package/dist/lobby.d.ts CHANGED
@@ -44,6 +44,8 @@ export type LobbyEvents = {
44
44
  peerFailed: [Peer, reason: string];
45
45
  joinRequested: [Peer];
46
46
  hostChanged: [hostId: number];
47
+ /** A member (you included) changed its display name mid-session (`rename`): the peer with its new name, and the old name. */
48
+ peerRenamed: [Peer, oldName: string];
47
49
  phase: [Phase];
48
50
  message: [from: number, bytes: Uint8Array, channel: ChannelName];
49
51
  /** `close` is the code and reason the host gave when it removed this player (`kick()` / `disconnect()`). */
@@ -66,6 +68,8 @@ export interface LobbyConfig {
66
68
  signaling: SignalingClient;
67
69
  state: LobbyState;
68
70
  ice: IceServer[];
71
+ /** `welcome.features` of the hub (e.g. "set_name"); absent = none. */
72
+ features?: string[];
69
73
  links: LinkFactory;
70
74
  clock: Clock;
71
75
  wall: () => number;
@@ -202,8 +206,23 @@ export declare class Lobby extends Emitter<LobbyEvents> {
202
206
  * Joiner: the link to the host as a WebSocket-shaped object (text by default), so WebSocket client code runs
203
207
  * unchanged. It opens when `ready` resolves and closes when the session ends (`host_left` gives code 1001).
204
208
  */
209
+ asWebSocket(opts: LobbySocketOptions & {
210
+ binary: true;
211
+ }): LobbySocket<Uint8Array>;
212
+ asWebSocket(opts?: LobbySocketOptions & {
213
+ binary?: false;
214
+ }): LobbySocket<string>;
205
215
  asWebSocket(opts?: LobbySocketOptions): LobbySocket;
206
- /** Host: `handler(conn)` once per joiner (a WebSocket-shaped connection), the server-side mirror of asWebSocket(). */
216
+ /**
217
+ * Host: `handler(conn)` once per joiner (a WebSocket-shaped connection), the server-side mirror of asWebSocket().
218
+ * With `{ binary: true }` every `conn.onmessage` gets a Uint8Array.
219
+ */
220
+ onConnection(handler: (conn: LobbySocket<Uint8Array>) => void, opts: LobbySocketOptions & {
221
+ binary: true;
222
+ }): () => void;
223
+ onConnection(handler: (conn: LobbySocket<string>) => void, opts?: LobbySocketOptions & {
224
+ binary?: false;
225
+ }): () => void;
207
226
  onConnection(handler: (conn: LobbySocket) => void, opts?: LobbySocketOptions): () => void;
208
227
  set snapshotChunkSize(n: number);
209
228
  /** Current migration epoch (0 until the first host change). */
@@ -215,6 +234,13 @@ export declare class Lobby extends Emitter<LobbyEvents> {
215
234
  /** @internal Called by OneLobby right after construction: the host waits for joiners, a joiner waits for the host's offer. */
216
235
  _begin(): void;
217
236
  start(): Promise<void>;
237
+ /**
238
+ * Changes your display name mid-session. The hub sanitises it (as at hello, at most 24 characters) and every member,
239
+ * you included, gets `peerRenamed`. Names need not be unique. At most one rename per second; a refusal
240
+ * (`bad_request` for an empty name, `rate_limited`) arrives on the kit's `error` event. Throws `unsupported` when
241
+ * the hub is too old to rename.
242
+ */
243
+ rename(name: string): Promise<void>;
218
244
  setMeta(meta: Record<string, string>): Promise<void>;
219
245
  /**
220
246
  * Host: removes a player. By default it is a ban: the hub refuses that player's address for the rest of this lobby
@@ -257,6 +283,8 @@ export declare class Lobby extends Emitter<LobbyEvents> {
257
283
  private offerTo;
258
284
  private onLinkOpen;
259
285
  private sendHi;
286
+ /** Why and how the joiner's last link to the host ended (shown when the join gives up). */
287
+ private lastHostLinkEnd;
260
288
  private onLinkClosed;
261
289
  private onData;
262
290
  private onHi;
package/dist/lobby.js CHANGED
@@ -204,14 +204,9 @@ export class Lobby extends Emitter {
204
204
  get connectedPeers() {
205
205
  return this.connectedPeerIds();
206
206
  }
207
- /**
208
- * Joiner: the link to the host as a WebSocket-shaped object (text by default), so WebSocket client code runs
209
- * unchanged. It opens when `ready` resolves and closes when the session ends (`host_left` gives code 1001).
210
- */
211
207
  asWebSocket(opts) {
212
208
  return joinerSocket(this, opts);
213
209
  }
214
- /** Host: `handler(conn)` once per joiner (a WebSocket-shaped connection), the server-side mirror of asWebSocket(). */
215
210
  onConnection(handler, opts) {
216
211
  return hostConnections(this, handler, opts);
217
212
  }
@@ -246,6 +241,17 @@ export class Lobby extends Emitter {
246
241
  if (this.mode === "lockstep")
247
242
  this.lockstep.hostStart();
248
243
  }
244
+ /**
245
+ * Changes your display name mid-session. The hub sanitises it (as at hello, at most 24 characters) and every member,
246
+ * you included, gets `peerRenamed`. Names need not be unique. At most one rename per second; a refusal
247
+ * (`bad_request` for an empty name, `rate_limited`) arrives on the kit's `error` event. Throws `unsupported` when
248
+ * the hub is too old to rename.
249
+ */
250
+ async rename(name) {
251
+ if (!(this.cfg.features ?? []).includes("set_name"))
252
+ throw new OneLobbyError("unsupported", "this lobby's hub cannot rename players (update it)");
253
+ this.signaling.send({ t: "set_name", name: String(name ?? "") });
254
+ }
249
255
  async setMeta(meta) {
250
256
  if (!this.isHost)
251
257
  throw new Error("only the host can set metadata");
@@ -410,7 +416,11 @@ export class Lobby extends Emitter {
410
416
  }, budget);
411
417
  }
412
418
  giveUpConnecting() {
413
- const err = new OneLobbyError("ice_failed", `could not open a connection to the host${this.relayRetry ? " (direct or relayed)" : ""}`);
419
+ // what the host link saw (states, signals, timings), so a test log of a failed connect says where it stalled
420
+ const hostLink = this.links.get(this.hostPeerId);
421
+ const diagnostics = hostLink ? hostLink.diagnostics?.() : (this.lastHostLinkEnd ?? "no offer from the host ever arrived");
422
+ // the trace holds ICE candidates (local addresses): keep it in data, out of the message a game may show
423
+ const err = new OneLobbyError("ice_failed", `could not open a connection to the host${this.relayRetry ? " (direct or relayed)" : ""}`, diagnostics ? { diagnostics } : undefined);
414
424
  this.failReady(err);
415
425
  this.reportError(err);
416
426
  if (this.ended)
@@ -465,9 +475,13 @@ export class Lobby extends Emitter {
465
475
  this.emit("peerReady", this.roster.get(link.remoteId) ?? { peerId: link.remoteId, name: "" });
466
476
  this.flush(link.remoteId);
467
477
  }
478
+ /** Why and how the joiner's last link to the host ended (shown when the join gives up). */
479
+ lastHostLinkEnd = null;
468
480
  onLinkClosed(peerId, link, _reason) {
469
481
  if (this.links.get(peerId) !== link)
470
482
  return; // replaced
483
+ if (!this.isHost && peerId === this.hostPeerId)
484
+ this.lastHostLinkEnd = `link ended (${_reason}): ${link.diagnostics?.() ?? ""}`;
471
485
  this.links.delete(peerId);
472
486
  this.hiSentTo.delete(link);
473
487
  if (this.ended)
@@ -786,7 +800,13 @@ export class Lobby extends Emitter {
786
800
  this.resumeToken = st.resumeToken;
787
801
  this.seatSink?.(this.resumeToken);
788
802
  }
803
+ const before = this.roster;
789
804
  this.roster = new Map(st.peers.map((p) => [p.peerId, seatName(p)]));
805
+ for (const [id, p] of this.roster) {
806
+ const old = before.get(id);
807
+ if (old && old.name !== p.name)
808
+ this.emit("peerRenamed", p, old.name);
809
+ }
790
810
  if (st.hostId !== this.hostPeerId && !this.isHost)
791
811
  this.setHost(st.hostId);
792
812
  this.setPhase(st.phase);