onelobby 0.5.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 (109) hide show
  1. package/README.md +2 -2
  2. package/blueprint.schema.json +8 -1
  3. package/dist/blueprint.d.ts +4 -0
  4. package/dist/blueprint.js +5 -0
  5. package/dist/blueprint.js.map +1 -1
  6. package/dist/codec.d.ts +23 -0
  7. package/dist/codec.js +28 -0
  8. package/dist/codec.js.map +1 -1
  9. package/dist/direct/browser-host.d.ts +46 -0
  10. package/dist/direct/browser-host.js +93 -0
  11. package/dist/direct/browser-host.js.map +1 -0
  12. package/dist/direct/hub-host.d.ts +34 -23
  13. package/dist/direct/hub-host.js +75 -59
  14. package/dist/direct/hub-host.js.map +1 -1
  15. package/dist/direct/hub-local.d.ts +22 -0
  16. package/dist/direct/hub-local.js +46 -0
  17. package/dist/direct/hub-local.js.map +1 -0
  18. package/dist/direct/hub.d.ts +39 -10
  19. package/dist/direct/hub.js +134 -24
  20. package/dist/direct/hub.js.map +1 -1
  21. package/dist/direct/index.d.ts +36 -3
  22. package/dist/direct/index.js +146 -20
  23. package/dist/direct/index.js.map +1 -1
  24. package/dist/direct/metrics.d.ts +12 -0
  25. package/dist/direct/metrics.js +45 -0
  26. package/dist/direct/metrics.js.map +1 -0
  27. package/dist/direct/portmap.d.ts +9 -2
  28. package/dist/direct/portmap.js +7 -2
  29. package/dist/direct/portmap.js.map +1 -1
  30. package/dist/direct/sha1.d.ts +16 -0
  31. package/dist/direct/sha1.js +111 -0
  32. package/dist/direct/sha1.js.map +1 -0
  33. package/dist/direct-address.d.ts +2 -2
  34. package/dist/direct-address.js.map +1 -1
  35. package/dist/friendly.d.ts +24 -0
  36. package/dist/friendly.js +30 -0
  37. package/dist/friendly.js.map +1 -1
  38. package/dist/index.d.ts +8 -4
  39. package/dist/index.js +6 -4
  40. package/dist/index.js.map +1 -1
  41. package/dist/internals.d.ts +9 -0
  42. package/dist/internals.js +9 -0
  43. package/dist/internals.js.map +1 -1
  44. package/dist/join-code-kind.d.ts +10 -0
  45. package/dist/join-code-kind.js +10 -0
  46. package/dist/join-code-kind.js.map +1 -0
  47. package/dist/joincode.d.ts +10 -1
  48. package/dist/joincode.js +23 -3
  49. package/dist/joincode.js.map +1 -1
  50. package/dist/lobby.d.ts +94 -5
  51. package/dist/lobby.js +177 -18
  52. package/dist/lobby.js.map +1 -1
  53. package/dist/lockstep.d.ts +8 -0
  54. package/dist/lockstep.js +23 -2
  55. package/dist/lockstep.js.map +1 -1
  56. package/dist/nostr/crypto.d.ts +20 -0
  57. package/dist/nostr/crypto.js +78 -0
  58. package/dist/nostr/crypto.js.map +1 -0
  59. package/dist/nostr/host.d.ts +66 -0
  60. package/dist/nostr/host.js +196 -0
  61. package/dist/nostr/host.js.map +1 -0
  62. package/dist/nostr/limits.d.ts +33 -0
  63. package/dist/nostr/limits.js +38 -0
  64. package/dist/nostr/limits.js.map +1 -0
  65. package/dist/nostr/link.d.ts +70 -0
  66. package/dist/nostr/link.js +180 -0
  67. package/dist/nostr/link.js.map +1 -0
  68. package/dist/nostr/relays.d.ts +79 -0
  69. package/dist/nostr/relays.js +325 -0
  70. package/dist/nostr/relays.js.map +1 -0
  71. package/dist/nostr/room.d.ts +43 -0
  72. package/dist/nostr/room.js +104 -0
  73. package/dist/nostr/room.js.map +1 -0
  74. package/dist/nostr/secp256k1.d.ts +10 -0
  75. package/dist/nostr/secp256k1.js +171 -0
  76. package/dist/nostr/secp256k1.js.map +1 -0
  77. package/dist/nostr/socket.d.ts +48 -0
  78. package/dist/nostr/socket.js +141 -0
  79. package/dist/nostr/socket.js.map +1 -0
  80. package/dist/onelobby.bundle.js +146 -0
  81. package/dist/onelobby.bundle.js.map +7 -0
  82. package/dist/onelobby.bundle.mjs +11 -5
  83. package/dist/onelobby.bundle.mjs.map +4 -4
  84. package/dist/onelobby.d.ts +31 -3
  85. package/dist/onelobby.global.js +2673 -60
  86. package/dist/onelobby.global.js.map +4 -4
  87. package/dist/onelobby.global.min.js +11 -5
  88. package/dist/onelobby.global.min.js.map +4 -4
  89. package/dist/onelobby.js +77 -14
  90. package/dist/onelobby.js.map +1 -1
  91. package/dist/pause.d.ts +85 -0
  92. package/dist/pause.js +160 -0
  93. package/dist/pause.js.map +1 -0
  94. package/dist/signaling.d.ts +2 -0
  95. package/dist/signaling.js.map +1 -1
  96. package/dist/socket.d.ts +15 -6
  97. package/dist/socket.js.map +1 -1
  98. package/dist/testing.d.ts +2 -0
  99. package/dist/testing.js +18 -2
  100. package/dist/testing.js.map +1 -1
  101. package/dist/transport.d.ts +5 -1
  102. package/dist/transport.js.map +1 -1
  103. package/dist/webrtc.d.ts +6 -0
  104. package/dist/webrtc.js +27 -3
  105. package/dist/webrtc.js.map +1 -1
  106. package/package.json +1 -1
  107. package/dist/deprecated.d.ts +0 -18
  108. package/dist/deprecated.js +0 -11
  109. 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\";\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 a blueprint (onelobby.json)\n BadConfig: \"bad_config\",\n VoiceDisabled: \"voice_disabled\",\n ChatDisabled: \"chat_disabled\",\n ListingDisabled: \"listing_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 listing_disabled: { message: \"This game has no public lobby list.\", hint: \"Join with a code or a link from the host.\" },\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/** 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
@@ -17,6 +17,8 @@ export type { LobbySocket, LobbySocketOptions } from "./socket.js";
17
17
  export { Lockstep } from "./lockstep.js";
18
18
  export type { Frame, DesyncInfo, LockstepEvents } from "./lockstep.js";
19
19
  export { Chat, CHAT_MAX_BYTES } from "./chat.js";
20
+ export { Pause } from "./pause.js";
21
+ export type { PauseState, PauseEvents, PauseMode, PauseRefusal } from "./pause.js";
20
22
  export type { ChatMessage, ChatEvents } from "./chat.js";
21
23
  export { Voice } from "./voice.js";
22
24
  export type { VoiceEvents, VoiceDeps } from "./voice.js";
@@ -31,7 +33,9 @@ export { OneLobbyError } from "./signaling.js";
31
33
  export type { LobbySummary, LobbyState, PeerInfo } from "./signaling.js";
32
34
  export type { PeerLink, LinkFactory, SocketFactory, SocketLike, IceServer, ChannelName } from "./transport.js";
33
35
  export type { Clock } from "./clock.js";
34
- export { describeError, shareUrl, codeFromUrl, ErrorCodes } from "./friendly.js";
35
- export type { FriendlyError, ErrorCode } from "./friendly.js";
36
- export { Keepalive, KeepaliveError } from "./deprecated.js";
37
- export type { KeepaliveOptions, KeepaliveEvents } from "./deprecated.js";
36
+ export { describeError, shareUrl, codeFromUrl, directJoinInfo, ErrorCodes } from "./friendly.js";
37
+ export type { FriendlyError, ErrorCode, JoinInfo } from "./friendly.js";
38
+ export { checkNostrRelays, DEFAULT_NOSTR_RELAYS, DEFAULT_NOSTR_STUN } from "./nostr/relays.js";
39
+ export type { RelayCheck, RelayConnect } from "./nostr/relays.js";
40
+ export { encodeRoomCode } from "./joincode.js";
41
+ export type { BrowserHostOptions, VisibilityDoc } from "./direct/browser-host.js";
package/dist/index.js CHANGED
@@ -13,15 +13,17 @@ export { MIN_PEER_TIMEOUT_MS } from "./lobby.js";
13
13
  export { closeCodeFor } from "./socket.js";
14
14
  export { Lockstep } from "./lockstep.js";
15
15
  export { Chat, CHAT_MAX_BYTES } from "./chat.js";
16
+ export { Pause } from "./pause.js";
16
17
  export { Voice } from "./voice.js";
17
18
  export { webCodecsOpusAvailable } from "./voice-browser.js";
18
19
  export { normalizeCode, formatCode } from "./crockford.js";
19
- // Direct IP (docs/direct-ip.md): kit.joinDirect(code); the code is the host's address in disguise. Hosting is in
20
+ // Direct IP (docs/design/direct-ip.md): kit.joinDirect(code); the code is the host's address in disguise. Hosting is in
20
21
  // onelobby/direct (Node). webRtcLinkFactoryFor gives Node joiners a WebRTC stack (node-datachannel/polyfill).
21
22
  export { decodeJoinCode, looksLikeJoinCode } from "./joincode.js";
22
23
  export { webRtcLinkFactoryFor } from "./webrtc.js";
23
24
  export { OneLobbyError } from "./signaling.js";
24
- export { describeError, shareUrl, codeFromUrl, ErrorCodes } from "./friendly.js";
25
- // Pre-rename names, kept for one version so existing games keep working.
26
- export { Keepalive, KeepaliveError } from "./deprecated.js";
25
+ export { describeError, shareUrl, codeFromUrl, directJoinInfo, ErrorCodes } from "./friendly.js";
26
+ // Nostr signaling for direct hosts (spec/nostr-signaling.md): relay probe for picking hosting.nostrRelays.
27
+ export { checkNostrRelays, DEFAULT_NOSTR_RELAYS, DEFAULT_NOSTR_STUN } from "./nostr/relays.js";
28
+ export { encodeRoomCode } from "./joincode.js";
27
29
  //# sourceMappingURL=index.js.map
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;AAEjD,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,iHAAiH;AACjH,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,UAAU,EAAE,MAAM,eAAe,CAAC;AAEjF,yEAAyE;AACzE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,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 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/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, ErrorCodes } from \"./friendly.js\";\nexport type { FriendlyError, ErrorCode } 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"]}
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"]}
@@ -7,6 +7,8 @@
7
7
  export * from "./index.js";
8
8
  export { MAX_DESYNCS, HOST_SILENCE_MS } from "./lockstep.js";
9
9
  export { ChatLimiter, sanitizeChat, CHAT_HISTORY_LINES } from "./chat.js";
10
+ export { PAUSE_MIN_REQUEST_GAP_MS } from "./pause.js";
11
+ export { PAUSE_FLAG_PAUSER_LEFT, PAUSE_REFUSE } from "./codec.js";
10
12
  export { JitterBuffer, VoiceGate, VoicePacketizer, VoiceRelayPolicy, FRAME_SAMPLES, SAMPLE_RATE, MAX_SPEAKERS, } from "./voice-core.js";
11
13
  export type { VoicePacket } from "./voice-core.js";
12
14
  export { webCodecsOpus, WorkletMic, WebAudioSink } from "./voice-browser.js";
@@ -26,3 +28,10 @@ export { encodeJoinCode, decodeJoinCode, normalizeJoinCode, looksLikeJoinCode, J
26
28
  export { parseDirectAddress, DEFAULT_DIRECT_PORT } from "./direct-address.js";
27
29
  export type { DirectAddress } from "./direct-address.js";
28
30
  export { webRtcLinkFactoryFor } from "./webrtc.js";
31
+ export { NostrSocket } from "./nostr/socket.js";
32
+ export { NostrRoom } from "./nostr/room.js";
33
+ export { ReliableLink } from "./nostr/link.js";
34
+ export { RelayPool, makeEvent, NOSTR_KIND } from "./nostr/relays.js";
35
+ export { schnorrSign, schnorrVerify, schnorrPublicKey } from "./nostr/secp256k1.js";
36
+ export { deriveRoomKeys } from "./nostr/crypto.js";
37
+ export { encodeRoomCodeFor } from "./joincode.js";
package/dist/internals.js CHANGED
@@ -7,6 +7,8 @@
7
7
  export * from "./index.js";
8
8
  export { MAX_DESYNCS, HOST_SILENCE_MS } from "./lockstep.js";
9
9
  export { ChatLimiter, sanitizeChat, CHAT_HISTORY_LINES } from "./chat.js";
10
+ export { PAUSE_MIN_REQUEST_GAP_MS } from "./pause.js";
11
+ export { PAUSE_FLAG_PAUSER_LEFT, PAUSE_REFUSE } from "./codec.js";
10
12
  export { JitterBuffer, VoiceGate, VoicePacketizer, VoiceRelayPolicy, FRAME_SAMPLES, SAMPLE_RATE, MAX_SPEAKERS, } from "./voice-core.js";
11
13
  export { webCodecsOpus, WorkletMic, WebAudioSink } from "./voice-browser.js";
12
14
  export { pickNewHost, claimWins, claimWinsWrapped } from "./migration.js";
@@ -21,4 +23,11 @@ export { Gridpaint, paintCmd, colourOf, PALETTE, GRID_W, GRID_H, seedFromCode, f
21
23
  export { encodeJoinCode, decodeJoinCode, normalizeJoinCode, looksLikeJoinCode, JOIN_CODE_LENGTH } from "./joincode.js";
22
24
  export { parseDirectAddress, DEFAULT_DIRECT_PORT } from "./direct-address.js";
23
25
  export { webRtcLinkFactoryFor } from "./webrtc.js";
26
+ export { NostrSocket } from "./nostr/socket.js";
27
+ export { NostrRoom } from "./nostr/room.js";
28
+ export { ReliableLink } from "./nostr/link.js";
29
+ export { RelayPool, makeEvent, NOSTR_KIND } from "./nostr/relays.js";
30
+ export { schnorrSign, schnorrVerify, schnorrPublicKey } from "./nostr/secp256k1.js";
31
+ export { deriveRoomKeys } from "./nostr/crypto.js";
32
+ export { encodeRoomCodeFor } from "./joincode.js";
24
33
  //# sourceMappingURL=internals.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"internals.js","sourceRoot":"","sources":["../src/internals.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC1E,OAAO,EACL,YAAY,EACZ,SAAS,EACT,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,YAAY,GACb,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAE1E,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEhG,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAE/G,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACvH,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC","sourcesContent":["/**\n * Everything the package has, including the building blocks apps normally never call: wire codec, chunking, deflate,\n * host-migration rule, jitter buffer and voice packetizer, signaling and WebRTC classes, clocks, and the gridpaint demo\n * game. Exported as `onelobby/internals`. No stability promise: these can change in any release.\n * The public API is the root export (`onelobby`); it is re-exported here too.\n */\nexport * from \"./index.js\";\nexport { MAX_DESYNCS, HOST_SILENCE_MS } from \"./lockstep.js\";\nexport { ChatLimiter, sanitizeChat, CHAT_HISTORY_LINES } from \"./chat.js\";\nexport {\n JitterBuffer,\n VoiceGate,\n VoicePacketizer,\n VoiceRelayPolicy,\n FRAME_SAMPLES,\n SAMPLE_RATE,\n MAX_SPEAKERS,\n} from \"./voice-core.js\";\nexport type { VoicePacket } from \"./voice-core.js\";\nexport { webCodecsOpus, WorkletMic, WebAudioSink } from \"./voice-browser.js\";\nexport { pickNewHost, claimWins, claimWinsWrapped } from \"./migration.js\";\nexport type { Claim } from \"./migration.js\";\nexport { decodeCrockford, encodeCrockford, CROCKFORD_ALPHABET } from \"./crockford.js\";\nexport { encodeMessage, decodeMessage, CodecError, MAX_FRAME_BYTES, MsgType } from \"./codec.js\";\nexport type { Message, Decoded } from \"./codec.js\";\nexport { splitChunks, Reassembler, CHUNK_PAYLOAD } from \"./chunking.js\";\nexport { deflate, inflate, deflateAvailable } from \"./deflate.js\";\nexport { SignalingClient } from \"./signaling.js\";\nexport { WebRtcLink, webRtcLinkFactory } from \"./webrtc.js\";\nexport { systemClock } from \"./clock.js\";\nexport { Gridpaint, paintCmd, colourOf, PALETTE, GRID_W, GRID_H, seedFromCode, fnv1a64 } from \"./gridpaint.js\";\nexport type { Hub } from \"./hub.js\";\nexport { encodeJoinCode, decodeJoinCode, normalizeJoinCode, looksLikeJoinCode, JOIN_CODE_LENGTH } from \"./joincode.js\";\nexport { parseDirectAddress, DEFAULT_DIRECT_PORT } from \"./direct-address.js\";\nexport type { DirectAddress } from \"./direct-address.js\";\nexport { webRtcLinkFactoryFor } from \"./webrtc.js\";\n"]}
1
+ {"version":3,"file":"internals.js","sourceRoot":"","sources":["../src/internals.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC1E,OAAO,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAClE,OAAO,EACL,YAAY,EACZ,SAAS,EACT,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,YAAY,GACb,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAE1E,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEhG,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAE/G,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACvH,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC","sourcesContent":["/**\n * Everything the package has, including the building blocks apps normally never call: wire codec, chunking, deflate,\n * host-migration rule, jitter buffer and voice packetizer, signaling and WebRTC classes, clocks, and the gridpaint demo\n * game. Exported as `onelobby/internals`. No stability promise: these can change in any release.\n * The public API is the root export (`onelobby`); it is re-exported here too.\n */\nexport * from \"./index.js\";\nexport { MAX_DESYNCS, HOST_SILENCE_MS } from \"./lockstep.js\";\nexport { ChatLimiter, sanitizeChat, CHAT_HISTORY_LINES } from \"./chat.js\";\nexport { PAUSE_MIN_REQUEST_GAP_MS } from \"./pause.js\";\nexport { PAUSE_FLAG_PAUSER_LEFT, PAUSE_REFUSE } from \"./codec.js\";\nexport {\n JitterBuffer,\n VoiceGate,\n VoicePacketizer,\n VoiceRelayPolicy,\n FRAME_SAMPLES,\n SAMPLE_RATE,\n MAX_SPEAKERS,\n} from \"./voice-core.js\";\nexport type { VoicePacket } from \"./voice-core.js\";\nexport { webCodecsOpus, WorkletMic, WebAudioSink } from \"./voice-browser.js\";\nexport { pickNewHost, claimWins, claimWinsWrapped } from \"./migration.js\";\nexport type { Claim } from \"./migration.js\";\nexport { decodeCrockford, encodeCrockford, CROCKFORD_ALPHABET } from \"./crockford.js\";\nexport { encodeMessage, decodeMessage, CodecError, MAX_FRAME_BYTES, MsgType } from \"./codec.js\";\nexport type { Message, Decoded } from \"./codec.js\";\nexport { splitChunks, Reassembler, CHUNK_PAYLOAD } from \"./chunking.js\";\nexport { deflate, inflate, deflateAvailable } from \"./deflate.js\";\nexport { SignalingClient } from \"./signaling.js\";\nexport { WebRtcLink, webRtcLinkFactory } from \"./webrtc.js\";\nexport { systemClock } from \"./clock.js\";\nexport { Gridpaint, paintCmd, colourOf, PALETTE, GRID_W, GRID_H, seedFromCode, fnv1a64 } from \"./gridpaint.js\";\nexport type { Hub } from \"./hub.js\";\nexport { encodeJoinCode, decodeJoinCode, normalizeJoinCode, looksLikeJoinCode, JOIN_CODE_LENGTH } from \"./joincode.js\";\nexport { parseDirectAddress, DEFAULT_DIRECT_PORT } from \"./direct-address.js\";\nexport type { DirectAddress } from \"./direct-address.js\";\nexport { webRtcLinkFactoryFor } from \"./webrtc.js\";\nexport { NostrSocket } from \"./nostr/socket.js\";\nexport { NostrRoom } from \"./nostr/room.js\";\nexport { ReliableLink } from \"./nostr/link.js\";\nexport { RelayPool, makeEvent, NOSTR_KIND } from \"./nostr/relays.js\";\nexport { schnorrSign, schnorrVerify, schnorrPublicKey } from \"./nostr/secp256k1.js\";\nexport { deriveRoomKeys } from \"./nostr/crypto.js\";\nexport { encodeRoomCodeFor } from \"./joincode.js\";\n"]}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * What a direct host's join code is. One tuple for the whole client; C# has the `JoinCodeKind` enum and
3
+ * `DirectLobby.KindName`, and `spec/vectors/join-code-kinds.json` pins that both spell each kind alike.
4
+ */
5
+ export declare const JOIN_CODE_KINDS: readonly ["nostr", "internet", "same_network", "none"];
6
+ export type JoinCodeKind = (typeof JOIN_CODE_KINDS)[number];
7
+ /** An IP code (the host's address): everything but `nostr`. */
8
+ export type IpCodeKind = Exclude<JoinCodeKind, "nostr">;
9
+ /** The kind as the startup line spells it (C# `JoinCodeKind.ToString()`): `same_network` -> `SameNetwork`. */
10
+ export declare function joinCodeKindName(k: JoinCodeKind): string;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * What a direct host's join code is. One tuple for the whole client; C# has the `JoinCodeKind` enum and
3
+ * `DirectLobby.KindName`, and `spec/vectors/join-code-kinds.json` pins that both spell each kind alike.
4
+ */
5
+ export const JOIN_CODE_KINDS = ["nostr", "internet", "same_network", "none"];
6
+ /** The kind as the startup line spells it (C# `JoinCodeKind.ToString()`): `same_network` -> `SameNetwork`. */
7
+ export function joinCodeKindName(k) {
8
+ return k === "internet" ? "Internet" : k === "same_network" ? "SameNetwork" : k === "nostr" ? "Nostr" : "None";
9
+ }
10
+ //# sourceMappingURL=join-code-kind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"join-code-kind.js","sourceRoot":"","sources":["../src/join-code-kind.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,CAAU,CAAC;AAKtF,8GAA8G;AAC9G,MAAM,UAAU,gBAAgB,CAAC,CAAe;IAC9C,OAAO,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,cAAc,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;AACjH,CAAC","sourcesContent":["/**\n * What a direct host's join code is. One tuple for the whole client; C# has the `JoinCodeKind` enum and\n * `DirectLobby.KindName`, and `spec/vectors/join-code-kinds.json` pins that both spell each kind alike.\n */\nexport const JOIN_CODE_KINDS = [\"nostr\", \"internet\", \"same_network\", \"none\"] as const;\nexport type JoinCodeKind = (typeof JOIN_CODE_KINDS)[number];\n/** An IP code (the host's address): everything but `nostr`. */\nexport type IpCodeKind = Exclude<JoinCodeKind, \"nostr\">;\n\n/** The kind as the startup line spells it (C# `JoinCodeKind.ToString()`): `same_network` -> `SameNetwork`. */\nexport function joinCodeKindName(k: JoinCodeKind): string {\n return k === \"internet\" ? \"Internet\" : k === \"same_network\" ? \"SameNetwork\" : k === \"nostr\" ? \"Nostr\" : \"None\";\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `dc1` join codes (docs/direct-ip.md s.2a, spec/reference/joincode.mjs): a direct-IP host's IPv4 address and port in
2
+ * `dc1` join codes (docs/design/direct-ip.md s.2a, spec/reference/joincode.mjs): a direct-IP host's IPv4 address and port in
3
3
  * 11 Crockford characters, shown `6B01-RGFE-P2B`. The code is the address in disguise, so decoding asks nobody.
4
4
  * Bits, most significant first: version:2 (0 = IPv4) | ipv4:32 | port:16 | crc:5 (CRC-5, polynomial 0x05, over the 50
5
5
  * bits before it). One mistyped character always fails the check.
@@ -9,11 +9,20 @@ export type JoinCodeError = "bad_code" | "unknown_version";
9
9
  export type DecodedJoinCode = {
10
10
  ip: string;
11
11
  port: number;
12
+ } | {
13
+ room: string;
12
14
  } | {
13
15
  error: JoinCodeError;
14
16
  };
15
17
  /** Display form (`6B01-RGFE-P2B`) for an IPv4 address and port 1-65535. Throws on anything else. */
16
18
  export declare function encodeJoinCode(ipv4: string, port: number): string;
19
+ /**
20
+ * Version 1 code (a Nostr room, spec/nostr-signaling.md s.1): version:2 = 1 | room:48 | crc:5. `roomHex` is 12 hex
21
+ * digits. The room is only a secret both sides derive keys from.
22
+ */
23
+ export declare function encodeRoomCodeFor(roomHex: string): string;
24
+ /** A new version 1 code with a random 48-bit room from the platform's CSPRNG. */
25
+ export declare function encodeRoomCode(): string;
17
26
  /** Upper case, spaces and hyphens dropped, i/l -> 1, o -> 0; null unless 11 alphabet characters. */
18
27
  export declare function normalizeJoinCode(input: string): string | null;
19
28
  export declare function decodeJoinCode(input: string): DecodedJoinCode;
package/dist/joincode.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { CROCKFORD_ALPHABET } from "./crockford.js";
2
2
  /**
3
- * `dc1` join codes (docs/direct-ip.md s.2a, spec/reference/joincode.mjs): a direct-IP host's IPv4 address and port in
3
+ * `dc1` join codes (docs/design/direct-ip.md s.2a, spec/reference/joincode.mjs): a direct-IP host's IPv4 address and port in
4
4
  * 11 Crockford characters, shown `6B01-RGFE-P2B`. The code is the address in disguise, so decoding asks nobody.
5
5
  * Bits, most significant first: version:2 (0 = IPv4) | ipv4:32 | port:16 | crc:5 (CRC-5, polynomial 0x05, over the 50
6
6
  * bits before it). One mistyped character always fails the check.
@@ -36,7 +36,9 @@ export function encodeJoinCode(ipv4, port) {
36
36
  throw new Error(`not an IPv4 address: ${ipv4}`);
37
37
  if (!Number.isInteger(port) || port < 1 || port > 65535)
38
38
  throw new Error(`bad port ${port}`);
39
- const data = (BigInt(ip) << 16n) | BigInt(port); // version 0
39
+ return encodeData((BigInt(ip) << 16n) | BigInt(port)); // version 0
40
+ }
41
+ function encodeData(data) {
40
42
  let v = (data << 5n) | BigInt(crc5(data, 50));
41
43
  let s = "";
42
44
  for (let i = 0; i < JOIN_CODE_LENGTH; i++) {
@@ -45,6 +47,21 @@ export function encodeJoinCode(ipv4, port) {
45
47
  }
46
48
  return `${s.slice(0, 4)}-${s.slice(4, 8)}-${s.slice(8)}`;
47
49
  }
50
+ /**
51
+ * Version 1 code (a Nostr room, spec/nostr-signaling.md s.1): version:2 = 1 | room:48 | crc:5. `roomHex` is 12 hex
52
+ * digits. The room is only a secret both sides derive keys from.
53
+ */
54
+ export function encodeRoomCodeFor(roomHex) {
55
+ if (!/^[0-9a-fA-F]{12}$/.test(roomHex))
56
+ throw new Error(`room must be 12 hex digits: ${roomHex}`);
57
+ return encodeData((1n << 48n) | BigInt("0x" + roomHex));
58
+ }
59
+ /** A new version 1 code with a random 48-bit room from the platform's CSPRNG. */
60
+ export function encodeRoomCode() {
61
+ const b = new Uint8Array(6);
62
+ globalThis.crypto.getRandomValues(b);
63
+ return encodeRoomCodeFor([...b].map((x) => x.toString(16).padStart(2, "0")).join(""));
64
+ }
48
65
  /** Upper case, spaces and hyphens dropped, i/l -> 1, o -> 0; null unless 11 alphabet characters. */
49
66
  export function normalizeJoinCode(input) {
50
67
  const s = input
@@ -69,7 +86,10 @@ export function decodeJoinCode(input) {
69
86
  const data = v >> 5n;
70
87
  if (crc5(data, 50) !== Number(v & 31n))
71
88
  return { error: "bad_code" };
72
- if (data >> 48n !== 0n)
89
+ const version = data >> 48n;
90
+ if (version === 1n)
91
+ return { room: (data & 0xffffffffffffn).toString(16).padStart(12, "0") };
92
+ if (version !== 0n)
73
93
  return { error: "unknown_version" };
74
94
  const port = Number(data & 0xffffn);
75
95
  if (port === 0)
@@ -1 +1 @@
1
- {"version":3,"file":"joincode.js","sourceRoot":"","sources":["../src/joincode.ts"],"names":[],"mappings":"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","sourcesContent":["import { CROCKFORD_ALPHABET } from \"./crockford.js\";\n\n/**\n * `dc1` join codes (docs/direct-ip.md s.2a, spec/reference/joincode.mjs): a direct-IP host's IPv4 address and port in\n * 11 Crockford characters, shown `6B01-RGFE-P2B`. The code is the address in disguise, so decoding asks nobody.\n * Bits, most significant first: version:2 (0 = IPv4) | ipv4:32 | port:16 | crc:5 (CRC-5, polynomial 0x05, over the 50\n * bits before it). One mistyped character always fails the check.\n */\nexport const JOIN_CODE_LENGTH = 11;\n\nexport type JoinCodeError = \"bad_code\" | \"unknown_version\";\nexport type DecodedJoinCode = { ip: string; port: number } | { error: JoinCodeError };\n\nfunction crc5(value: bigint, bits: number): number {\n let crc = 0;\n for (let i = bits - 1; i >= 0; i--) {\n const bit = Number((value >> BigInt(i)) & 1n);\n const top = (crc >> 4) & 1;\n crc = (crc << 1) & 0x1f;\n if (top ^ bit) crc ^= 0x05;\n }\n return crc;\n}\n\nfunction parseIPv4(ip: string): number | null {\n const parts = ip.split(\".\");\n if (parts.length !== 4) return null;\n let n = 0;\n for (const p of parts) {\n if (!/^\\d{1,3}$/.test(p) || Number(p) > 255) return null;\n n = n * 256 + Number(p);\n }\n return n;\n}\n\n/** Display form (`6B01-RGFE-P2B`) for an IPv4 address and port 1-65535. Throws on anything else. */\nexport function encodeJoinCode(ipv4: string, port: number): string {\n const ip = parseIPv4(ipv4);\n if (ip === null) throw new Error(`not an IPv4 address: ${ipv4}`);\n if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`bad port ${port}`);\n const data = (BigInt(ip) << 16n) | BigInt(port); // version 0\n let v = (data << 5n) | BigInt(crc5(data, 50));\n let s = \"\";\n for (let i = 0; i < JOIN_CODE_LENGTH; i++) {\n s = CROCKFORD_ALPHABET[Number(v & 31n)] + s;\n v >>= 5n;\n }\n return `${s.slice(0, 4)}-${s.slice(4, 8)}-${s.slice(8)}`;\n}\n\n/** Upper case, spaces and hyphens dropped, i/l -> 1, o -> 0; null unless 11 alphabet characters. */\nexport function normalizeJoinCode(input: string): string | null {\n const s = input\n .replace(/[ \\-]/g, \"\")\n .replace(/[a-z]/g, (c) => c.toUpperCase())\n .replace(/[IL]/g, \"1\")\n .replace(/O/g, \"0\");\n if (s.length !== JOIN_CODE_LENGTH) return null;\n for (const c of s) if (CROCKFORD_ALPHABET.indexOf(c) < 0) return null;\n return s;\n}\n\nexport function decodeJoinCode(input: string): DecodedJoinCode {\n const s = normalizeJoinCode(input);\n if (!s) return { error: \"bad_code\" };\n let v = 0n;\n for (const c of s) v = (v << 5n) | BigInt(CROCKFORD_ALPHABET.indexOf(c));\n const data = v >> 5n;\n if (crc5(data, 50) !== Number(v & 31n)) return { error: \"bad_code\" };\n if (data >> 48n !== 0n) return { error: \"unknown_version\" };\n const port = Number(data & 0xffffn);\n if (port === 0) return { error: \"bad_code\" };\n const a = Number((data >> 16n) & 0xffffffffn);\n return { ip: [a >>> 24, (a >>> 16) & 255, (a >>> 8) & 255, a & 255].join(\".\"), port };\n}\n\n/** Shaped like a join code (no dots or colons, 11 alphabet characters): a failed decode is `bad_code`, not a host name. */\nexport function looksLikeJoinCode(input: string): boolean {\n return !/[.:]/.test(input) && normalizeJoinCode(input) !== null;\n}\n"]}
1
+ {"version":3,"file":"joincode.js","sourceRoot":"","sources":["../src/joincode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAKnC,SAAS,IAAI,CAAC,KAAa,EAAE,IAAY;IACvC,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,KAAK,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;QAC9C,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAC3B,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;QACxB,IAAI,GAAG,GAAG,GAAG;YAAE,GAAG,IAAI,IAAI,CAAC;IAC7B,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,SAAS,CAAC,EAAU;IAC3B,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC5B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG;YAAE,OAAO,IAAI,CAAC;QACzD,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC1B,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,IAAY;IACvD,MAAM,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,EAAE,KAAK,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,EAAE,CAAC,CAAC;IACjE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;IAC7F,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY;AACrE,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;IAC9C,IAAI,CAAC,GAAG,EAAE,CAAC;IACX,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,gBAAgB,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,CAAC,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;QAC5C,CAAC,KAAK,EAAE,CAAC;IACX,CAAC;IACD,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAC3D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,OAAO,EAAE,CAAC,CAAC;IAClG,OAAO,UAAU,CAAC,CAAC,EAAE,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,cAAc;IAC5B,MAAM,CAAC,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;IAC5B,UAAU,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACrC,OAAO,iBAAiB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;AACxF,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,MAAM,CAAC,GAAG,KAAK;SACZ,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;SACrB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;SACzC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACtB,IAAI,CAAC,CAAC,MAAM,KAAK,gBAAgB;QAAE,OAAO,IAAI,CAAC;IAC/C,KAAK,MAAM,CAAC,IAAI,CAAC;QAAE,IAAI,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;IACtE,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,MAAM,CAAC,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;IACrC,IAAI,CAAC,GAAG,EAAE,CAAC;IACX,KAAK,MAAM,CAAC,IAAI,CAAC;QAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACzE,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;IACrB,IAAI,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,MAAM,CAAC,CAAC,GAAG,GAAG,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;IACrE,MAAM,OAAO,GAAG,IAAI,IAAI,GAAG,CAAC;IAC5B,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,CAAC,IAAI,GAAG,eAAe,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC;IAC7F,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC;IACxD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,CAAC;IACpC,IAAI,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;IAC7C,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC;IAC9C,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;AACxF,CAAC;AAED,2HAA2H;AAC3H,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,iBAAiB,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;AAClE,CAAC","sourcesContent":["import { CROCKFORD_ALPHABET } from \"./crockford.js\";\n\n/**\n * `dc1` join codes (docs/design/direct-ip.md s.2a, spec/reference/joincode.mjs): a direct-IP host's IPv4 address and port in\n * 11 Crockford characters, shown `6B01-RGFE-P2B`. The code is the address in disguise, so decoding asks nobody.\n * Bits, most significant first: version:2 (0 = IPv4) | ipv4:32 | port:16 | crc:5 (CRC-5, polynomial 0x05, over the 50\n * bits before it). One mistyped character always fails the check.\n */\nexport const JOIN_CODE_LENGTH = 11;\n\nexport type JoinCodeError = \"bad_code\" | \"unknown_version\";\nexport type DecodedJoinCode = { ip: string; port: number } | { room: string } | { error: JoinCodeError };\n\nfunction crc5(value: bigint, bits: number): number {\n let crc = 0;\n for (let i = bits - 1; i >= 0; i--) {\n const bit = Number((value >> BigInt(i)) & 1n);\n const top = (crc >> 4) & 1;\n crc = (crc << 1) & 0x1f;\n if (top ^ bit) crc ^= 0x05;\n }\n return crc;\n}\n\nfunction parseIPv4(ip: string): number | null {\n const parts = ip.split(\".\");\n if (parts.length !== 4) return null;\n let n = 0;\n for (const p of parts) {\n if (!/^\\d{1,3}$/.test(p) || Number(p) > 255) return null;\n n = n * 256 + Number(p);\n }\n return n;\n}\n\n/** Display form (`6B01-RGFE-P2B`) for an IPv4 address and port 1-65535. Throws on anything else. */\nexport function encodeJoinCode(ipv4: string, port: number): string {\n const ip = parseIPv4(ipv4);\n if (ip === null) throw new Error(`not an IPv4 address: ${ipv4}`);\n if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`bad port ${port}`);\n return encodeData((BigInt(ip) << 16n) | BigInt(port)); // version 0\n}\n\nfunction encodeData(data: bigint): string {\n let v = (data << 5n) | BigInt(crc5(data, 50));\n let s = \"\";\n for (let i = 0; i < JOIN_CODE_LENGTH; i++) {\n s = CROCKFORD_ALPHABET[Number(v & 31n)] + s;\n v >>= 5n;\n }\n return `${s.slice(0, 4)}-${s.slice(4, 8)}-${s.slice(8)}`;\n}\n\n/**\n * Version 1 code (a Nostr room, spec/nostr-signaling.md s.1): version:2 = 1 | room:48 | crc:5. `roomHex` is 12 hex\n * digits. The room is only a secret both sides derive keys from.\n */\nexport function encodeRoomCodeFor(roomHex: string): string {\n if (!/^[0-9a-fA-F]{12}$/.test(roomHex)) throw new Error(`room must be 12 hex digits: ${roomHex}`);\n return encodeData((1n << 48n) | BigInt(\"0x\" + roomHex));\n}\n\n/** A new version 1 code with a random 48-bit room from the platform's CSPRNG. */\nexport function encodeRoomCode(): string {\n const b = new Uint8Array(6);\n globalThis.crypto.getRandomValues(b);\n return encodeRoomCodeFor([...b].map((x) => x.toString(16).padStart(2, \"0\")).join(\"\"));\n}\n\n/** Upper case, spaces and hyphens dropped, i/l -> 1, o -> 0; null unless 11 alphabet characters. */\nexport function normalizeJoinCode(input: string): string | null {\n const s = input\n .replace(/[ \\-]/g, \"\")\n .replace(/[a-z]/g, (c) => c.toUpperCase())\n .replace(/[IL]/g, \"1\")\n .replace(/O/g, \"0\");\n if (s.length !== JOIN_CODE_LENGTH) return null;\n for (const c of s) if (CROCKFORD_ALPHABET.indexOf(c) < 0) return null;\n return s;\n}\n\nexport function decodeJoinCode(input: string): DecodedJoinCode {\n const s = normalizeJoinCode(input);\n if (!s) return { error: \"bad_code\" };\n let v = 0n;\n for (const c of s) v = (v << 5n) | BigInt(CROCKFORD_ALPHABET.indexOf(c));\n const data = v >> 5n;\n if (crc5(data, 50) !== Number(v & 31n)) return { error: \"bad_code\" };\n const version = data >> 48n;\n if (version === 1n) return { room: (data & 0xffffffffffffn).toString(16).padStart(12, \"0\") };\n if (version !== 0n) return { error: \"unknown_version\" };\n const port = Number(data & 0xffffn);\n if (port === 0) return { error: \"bad_code\" };\n const a = Number((data >> 16n) & 0xffffffffn);\n return { ip: [a >>> 24, (a >>> 16) & 255, (a >>> 8) & 255, a & 255].join(\".\"), port };\n}\n\n/** Shaped like a join code (no dots or colons, 11 alphabet characters): a failed decode is `bad_code`, not a host name. */\nexport function looksLikeJoinCode(input: string): boolean {\n return !/[.:]/.test(input) && normalizeJoinCode(input) !== null;\n}\n"]}
package/dist/lobby.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { Chat } from "./chat.js";
2
+ import type { IpCodeKind, JoinCodeKind } from "./join-code-kind.js";
2
3
  import type { Clock } from "./clock.js";
3
4
  import { PROTO } from "./codec.js";
4
5
  import { Emitter } from "./emitter.js";
5
6
  import { Lockstep } from "./lockstep.js";
7
+ import { Pause } from "./pause.js";
6
8
  import { type LobbyState, type PeerInfo, type SignalingClient } from "./signaling.js";
7
9
  import type { ChannelName, IceServer, LinkFactory, PeerLink } from "./transport.js";
8
10
  import { Voice, type VoiceDeps } from "./voice.js";
@@ -23,6 +25,8 @@ export interface KickOptions {
23
25
  reason?: string;
24
26
  }
25
27
  export type Peer = PeerInfo;
28
+ /** What a direct host's `joinCode` is: see {@link Lobby.joinCodeKind}. */
29
+ export type { JoinCodeKind };
26
30
  export type LobbyEvents = {
27
31
  peerJoined: [Peer];
28
32
  /** The peer's data channels are open and the handshake is done: reliable sends reach it now (earlier ones were queued and flushed). */
@@ -31,8 +35,17 @@ export type LobbyEvents = {
31
35
  ready: [];
32
36
  /** reason "left", "kicked", "disconnected" (kick with ban false), "timeout", ...; `close` when the host gave one. */
33
37
  peerLeft: [Peer, reason: string, close?: CloseInfo];
38
+ /**
39
+ * Host only: a joiner never got its link to the host open ("a friend could not connect"). reason "timeout" when it is
40
+ * not ready `readyTimeoutMs` after joining (it may still connect: `peerReady` then fires as usual), else the
41
+ * `peerLeft` reason when it leaves before it was ready (a joiner's own `ice_failed` ends that way). Fires at most once
42
+ * per peer, just before that `peerLeft`; not for peers the host kicks or disconnects.
43
+ */
44
+ peerFailed: [Peer, reason: string];
34
45
  joinRequested: [Peer];
35
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];
36
49
  phase: [Phase];
37
50
  message: [from: number, bytes: Uint8Array, channel: ChannelName];
38
51
  /** `close` is the code and reason the host gave when it removed this player (`kick()` / `disconnect()`). */
@@ -40,11 +53,23 @@ export type LobbyEvents = {
40
53
  /** The signaling server went away. The game continues on the data channels (s.9). */
41
54
  serverLost: [];
42
55
  metaChanged: [Record<string, string>];
56
+ /**
57
+ * Direct-IP host only: the join code changed after `hostDirect` resolved, e.g. the router answered a later port-mapping
58
+ * retry and the code went from "same_network" to "internet". Show the new code (and `joinUrl`).
59
+ */
60
+ joinCodeChanged: [code: string, kind: JoinCodeKind];
61
+ /**
62
+ * Browser host only (`kit.hostNostr`): the host tab went hidden (`true`) or visible again (`false`). Browsers throttle
63
+ * timers in hidden tabs, so joiners may see lag or drop; ask the host to keep the tab visible.
64
+ */
65
+ hostHiddenChanged: [hidden: boolean];
43
66
  };
44
67
  export interface LobbyConfig {
45
68
  signaling: SignalingClient;
46
69
  state: LobbyState;
47
70
  ice: IceServer[];
71
+ /** `welcome.features` of the hub (e.g. "set_name"); absent = none. */
72
+ features?: string[];
48
73
  links: LinkFactory;
49
74
  clock: Clock;
50
75
  wall: () => number;
@@ -59,7 +84,7 @@ export interface LobbyConfig {
59
84
  */
60
85
  readyTimeoutMs?: number;
61
86
  /**
62
- * Direct IP (docs/direct-ip.md): the hub lives in the host, so there is no migration. A joiner that loses the host
87
+ * Direct IP (docs/design/direct-ip.md): the hub lives in the host, so there is no migration. A joiner that loses the host
63
88
  * (link, peer_left, a host_changed from the hub as it shuts down, or the signaling socket) ends with "host_left".
64
89
  */
65
90
  direct?: boolean;
@@ -91,31 +116,55 @@ export declare class Lobby extends Emitter<LobbyEvents> {
91
116
  readonly tickRate: number;
92
117
  readonly lockstep: Lockstep;
93
118
  readonly chat: Chat;
119
+ /** The shared co-op pause (docs/recipes/pause.md). */
120
+ readonly pause: Pause;
94
121
  readonly voice: Voice;
95
122
  localPeerId: number;
96
123
  hostPeerId: number;
97
124
  phase: Phase;
98
125
  meta: Record<string, string>;
126
+ /** The lobby's name as the server keeps it (host()'s `name`, sanitised); "" if the server sends none. */
127
+ name: string;
99
128
  resumeToken: string;
100
129
  /** Last measured round trip to the host in ms (joiners), or 0. */
101
130
  rtt: number;
102
131
  /**
103
- * Direct-IP host only (onelobby/direct): the code friends paste to join, e.g. "6B01-RGFE-P2B" (docs/direct-ip.md
132
+ * Direct-IP host only (onelobby/direct): the code friends paste to join, e.g. "6B01-RGFE-P2B" (docs/design/direct-ip.md
104
133
  * s.2a). Show it with joinCodeKind. Empty when the machine has no IPv4 address, and on joiners.
105
134
  */
106
135
  joinCode: string;
107
- /** "internet": the router forwarded the port and the code carries its public address; "same_network": the LAN address. */
108
- joinCodeKind: "internet" | "same_network" | "none";
136
+ /**
137
+ * "nostr": a version 1 code that works through public Nostr relays (spec/nostr-signaling.md; the default for a direct
138
+ * host). "internet": the router forwarded the port and the code carries its public address; "same_network": the LAN
139
+ * address. "none": no code.
140
+ */
141
+ joinCodeKind: JoinCodeKind;
142
+ /**
143
+ * Direct-IP host only: the IP code (address and port, version 0), with `directCodeKind` "internet" or "same_network".
144
+ * Equal to `joinCode` when Nostr is off; otherwise the code to hand out for LAN play or when the router port is open.
145
+ */
146
+ directCode: string;
147
+ directCodeKind: IpCodeKind;
109
148
  /** Direct-IP host only: this machine's addresses with the port, for an "Advanced" fold-out. */
110
149
  directAddresses: string[];
111
150
  /** Direct-IP host only: how the router port-open went ("UPnP", "no UPnP gateway answered; ...", "off"). */
112
151
  portMapping: string;
152
+ /** Direct-IP host only: the one startup line (bound port, join code, port mapping); see `formatHostLog` in onelobby/direct. */
153
+ directHostLog: string;
154
+ /** Browser host only (`kit.hostNostr`): true while the host tab is hidden (see the `hostHiddenChanged` event). */
155
+ hostHidden: boolean;
156
+ /** @internal kit.hostNostr: the tab's visibility changed. */
157
+ setHostHidden(hidden: boolean): void;
158
+ /** @internal onelobby/direct: fires `joinCodeChanged` with the current code. */
159
+ notifyJoinCodeChanged(): void;
113
160
  private signaling;
114
161
  private cfg;
115
162
  private roster;
116
163
  private links;
117
164
  /** ICE from a peer that has no link yet: its candidates can overtake its offer (node-datachannel). */
118
165
  private earlyIce;
166
+ /** Offers from roster members that arrived while the host link was open (see offerVerdict), by sender. */
167
+ private heldOffers;
119
168
  private connected;
120
169
  private hiSentTo;
121
170
  private epoch;
@@ -137,6 +186,8 @@ export declare class Lobby extends Emitter<LobbyEvents> {
137
186
  private lastHeard;
138
187
  /** Host: links of kicked / disconnected peers, kept open briefly (removePeer `linger`). */
139
188
  private readonly lingering;
189
+ /** Host: joiners that joined but are not ready yet, with the cancel of their readyTimeoutMs timer (peerFailed). */
190
+ private notReady;
140
191
  /** Host: per-peer fixed window for rateLimit. */
141
192
  private rate;
142
193
  /**
@@ -155,8 +206,23 @@ export declare class Lobby extends Emitter<LobbyEvents> {
155
206
  * Joiner: the link to the host as a WebSocket-shaped object (text by default), so WebSocket client code runs
156
207
  * unchanged. It opens when `ready` resolves and closes when the session ends (`host_left` gives code 1001).
157
208
  */
209
+ asWebSocket(opts: LobbySocketOptions & {
210
+ binary: true;
211
+ }): LobbySocket<Uint8Array>;
212
+ asWebSocket(opts?: LobbySocketOptions & {
213
+ binary?: false;
214
+ }): LobbySocket<string>;
158
215
  asWebSocket(opts?: LobbySocketOptions): LobbySocket;
159
- /** 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;
160
226
  onConnection(handler: (conn: LobbySocket) => void, opts?: LobbySocketOptions): () => void;
161
227
  set snapshotChunkSize(n: number);
162
228
  /** Current migration epoch (0 until the first host change). */
@@ -168,6 +234,13 @@ export declare class Lobby extends Emitter<LobbyEvents> {
168
234
  /** @internal Called by OneLobby right after construction: the host waits for joiners, a joiner waits for the host's offer. */
169
235
  _begin(): void;
170
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>;
171
244
  setMeta(meta: Record<string, string>): Promise<void>;
172
245
  /**
173
246
  * Host: removes a player. By default it is a ban: the hub refuses that player's address for the rest of this lobby
@@ -203,11 +276,15 @@ export declare class Lobby extends Emitter<LobbyEvents> {
203
276
  /** The direct attempt stalled: dial the host ourselves through the TURN relay (a host answers any offer). */
204
277
  private retryRelayed;
205
278
  private giveUpConnecting;
279
+ /** @internal A direct host's hub learns where each joiner's link comes from (Nostr kick bans the address too). */
280
+ linkAddressSink: ((peerId: number, ip: string) => void) | null;
206
281
  private makeLink;
207
282
  /** Host side: offer to a joiner. */
208
283
  private offerTo;
209
284
  private onLinkOpen;
210
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;
211
288
  private onLinkClosed;
212
289
  private onData;
213
290
  private onHi;
@@ -217,10 +294,20 @@ export declare class Lobby extends Emitter<LobbyEvents> {
217
294
  private setHost;
218
295
  /** The host is gone (link closed, no frames for 3 s, or the server said so): run the s.9 rule. */
219
296
  private handleHostLost;
297
+ /** Offers that came from roster members while the host link was still open: now the host is lost, answer them. */
298
+ private replayHeldOffers;
220
299
  private becomeHost;
221
300
  private onHostClaim;
222
301
  private onSignaling;
223
302
  private onSignal;
303
+ /**
304
+ * Whether an SDP offer from `from` may be answered. The host (or a joiner's host) always may. Direct lobbies have no
305
+ * migration, so nobody else. In a server lobby a roster member may dial us when we are the host (a claim race decides
306
+ * who wins) or when our own link to the host is gone (the migration window); while that link is open the offer is
307
+ * held, to be answered if the host turns out to be lost a moment later.
308
+ */
309
+ private offerVerdict;
310
+ private acceptOffer;
224
311
  /**
225
312
  * `linger` (host kick / disconnect): stop using the peer's link at once but leave it open until that peer closes it
226
313
  * or KICK_LINGER_MS pass. The hub tells the peer over its WebSocket; closing the data link first would make it look
@@ -235,6 +322,8 @@ export declare class Lobby extends Emitter<LobbyEvents> {
235
322
  /** peerTimeoutMs: the host drops joiners it has not heard from; a joiner handles a silent host as lost. */
236
323
  private dropSilent;
237
324
  private pingHost;
325
+ /** Host: report a joiner that is not ready `readyTimeoutMs` after joining as peerFailed("timeout"), once. */
326
+ private watchReady;
238
327
  private teardown;
239
328
  /** Total in-flight links (diagnostics and tests). */
240
329
  get linkCount(): number;