onelobby 0.5.0 → 0.6.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 (99) 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 +37 -10
  19. package/dist/direct/hub.js +114 -23
  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 +23 -0
  36. package/dist/friendly.js +28 -0
  37. package/dist/friendly.js.map +1 -1
  38. package/dist/index.d.ts +8 -2
  39. package/dist/index.js +6 -2
  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 +65 -4
  51. package/dist/lobby.js +151 -12
  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 +31 -0
  63. package/dist/nostr/limits.js +36 -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 +315 -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 +2599 -46
  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 +68 -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/transport.d.ts +3 -1
  95. package/dist/transport.js.map +1 -1
  96. package/dist/webrtc.d.ts +1 -0
  97. package/dist/webrtc.js +6 -1
  98. package/dist/webrtc.js.map +1 -1
  99. package/package.json +1 -1
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,11 @@ 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 { describeError, shareUrl, codeFromUrl, directJoinInfo, ErrorCodes } from "./friendly.js";
37
+ export type { FriendlyError, ErrorCode, JoinInfo } from "./friendly.js";
36
38
  export { Keepalive, KeepaliveError } from "./deprecated.js";
37
39
  export type { KeepaliveOptions, KeepaliveEvents } from "./deprecated.js";
40
+ export { checkNostrRelays, DEFAULT_NOSTR_RELAYS, DEFAULT_NOSTR_STUN } from "./nostr/relays.js";
41
+ export type { RelayCheck, RelayConnect } from "./nostr/relays.js";
42
+ export { encodeRoomCode } from "./joincode.js";
43
+ export type { BrowserHostOptions, VisibilityDoc } from "./direct/browser-host.js";
package/dist/index.js CHANGED
@@ -13,15 +13,19 @@ 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
+ export { describeError, shareUrl, codeFromUrl, directJoinInfo, ErrorCodes } from "./friendly.js";
25
26
  // Pre-rename names, kept for one version so existing games keep working.
26
27
  export { Keepalive, KeepaliveError } from "./deprecated.js";
28
+ // Nostr signaling for direct hosts (spec/nostr-signaling.md): relay probe for picking hosting.nostrRelays.
29
+ export { checkNostrRelays, DEFAULT_NOSTR_RELAYS, DEFAULT_NOSTR_STUN } from "./nostr/relays.js";
30
+ export { encodeRoomCode } from "./joincode.js";
27
31
  //# 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,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"]}
@@ -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":"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","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,6 +35,13 @@ 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];
36
47
  phase: [Phase];
@@ -40,6 +51,16 @@ export type LobbyEvents = {
40
51
  /** The signaling server went away. The game continues on the data channels (s.9). */
41
52
  serverLost: [];
42
53
  metaChanged: [Record<string, string>];
54
+ /**
55
+ * Direct-IP host only: the join code changed after `hostDirect` resolved, e.g. the router answered a later port-mapping
56
+ * retry and the code went from "same_network" to "internet". Show the new code (and `joinUrl`).
57
+ */
58
+ joinCodeChanged: [code: string, kind: JoinCodeKind];
59
+ /**
60
+ * Browser host only (`kit.hostNostr`): the host tab went hidden (`true`) or visible again (`false`). Browsers throttle
61
+ * timers in hidden tabs, so joiners may see lag or drop; ask the host to keep the tab visible.
62
+ */
63
+ hostHiddenChanged: [hidden: boolean];
43
64
  };
44
65
  export interface LobbyConfig {
45
66
  signaling: SignalingClient;
@@ -59,7 +80,7 @@ export interface LobbyConfig {
59
80
  */
60
81
  readyTimeoutMs?: number;
61
82
  /**
62
- * Direct IP (docs/direct-ip.md): the hub lives in the host, so there is no migration. A joiner that loses the host
83
+ * 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
84
  * (link, peer_left, a host_changed from the hub as it shuts down, or the signaling socket) ends with "host_left".
64
85
  */
65
86
  direct?: boolean;
@@ -91,31 +112,55 @@ export declare class Lobby extends Emitter<LobbyEvents> {
91
112
  readonly tickRate: number;
92
113
  readonly lockstep: Lockstep;
93
114
  readonly chat: Chat;
115
+ /** The shared co-op pause (docs/recipes/pause.md). */
116
+ readonly pause: Pause;
94
117
  readonly voice: Voice;
95
118
  localPeerId: number;
96
119
  hostPeerId: number;
97
120
  phase: Phase;
98
121
  meta: Record<string, string>;
122
+ /** The lobby's name as the server keeps it (host()'s `name`, sanitised); "" if the server sends none. */
123
+ name: string;
99
124
  resumeToken: string;
100
125
  /** Last measured round trip to the host in ms (joiners), or 0. */
101
126
  rtt: number;
102
127
  /**
103
- * Direct-IP host only (onelobby/direct): the code friends paste to join, e.g. "6B01-RGFE-P2B" (docs/direct-ip.md
128
+ * Direct-IP host only (onelobby/direct): the code friends paste to join, e.g. "6B01-RGFE-P2B" (docs/design/direct-ip.md
104
129
  * s.2a). Show it with joinCodeKind. Empty when the machine has no IPv4 address, and on joiners.
105
130
  */
106
131
  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";
132
+ /**
133
+ * "nostr": a version 1 code that works through public Nostr relays (spec/nostr-signaling.md; the default for a direct
134
+ * host). "internet": the router forwarded the port and the code carries its public address; "same_network": the LAN
135
+ * address. "none": no code.
136
+ */
137
+ joinCodeKind: JoinCodeKind;
138
+ /**
139
+ * Direct-IP host only: the IP code (address and port, version 0), with `directCodeKind` "internet" or "same_network".
140
+ * Equal to `joinCode` when Nostr is off; otherwise the code to hand out for LAN play or when the router port is open.
141
+ */
142
+ directCode: string;
143
+ directCodeKind: IpCodeKind;
109
144
  /** Direct-IP host only: this machine's addresses with the port, for an "Advanced" fold-out. */
110
145
  directAddresses: string[];
111
146
  /** Direct-IP host only: how the router port-open went ("UPnP", "no UPnP gateway answered; ...", "off"). */
112
147
  portMapping: string;
148
+ /** Direct-IP host only: the one startup line (bound port, join code, port mapping); see `formatHostLog` in onelobby/direct. */
149
+ directHostLog: string;
150
+ /** Browser host only (`kit.hostNostr`): true while the host tab is hidden (see the `hostHiddenChanged` event). */
151
+ hostHidden: boolean;
152
+ /** @internal kit.hostNostr: the tab's visibility changed. */
153
+ setHostHidden(hidden: boolean): void;
154
+ /** @internal onelobby/direct: fires `joinCodeChanged` with the current code. */
155
+ notifyJoinCodeChanged(): void;
113
156
  private signaling;
114
157
  private cfg;
115
158
  private roster;
116
159
  private links;
117
160
  /** ICE from a peer that has no link yet: its candidates can overtake its offer (node-datachannel). */
118
161
  private earlyIce;
162
+ /** Offers from roster members that arrived while the host link was open (see offerVerdict), by sender. */
163
+ private heldOffers;
119
164
  private connected;
120
165
  private hiSentTo;
121
166
  private epoch;
@@ -137,6 +182,8 @@ export declare class Lobby extends Emitter<LobbyEvents> {
137
182
  private lastHeard;
138
183
  /** Host: links of kicked / disconnected peers, kept open briefly (removePeer `linger`). */
139
184
  private readonly lingering;
185
+ /** Host: joiners that joined but are not ready yet, with the cancel of their readyTimeoutMs timer (peerFailed). */
186
+ private notReady;
140
187
  /** Host: per-peer fixed window for rateLimit. */
141
188
  private rate;
142
189
  /**
@@ -203,6 +250,8 @@ export declare class Lobby extends Emitter<LobbyEvents> {
203
250
  /** The direct attempt stalled: dial the host ourselves through the TURN relay (a host answers any offer). */
204
251
  private retryRelayed;
205
252
  private giveUpConnecting;
253
+ /** @internal A direct host's hub learns where each joiner's link comes from (Nostr kick bans the address too). */
254
+ linkAddressSink: ((peerId: number, ip: string) => void) | null;
206
255
  private makeLink;
207
256
  /** Host side: offer to a joiner. */
208
257
  private offerTo;
@@ -217,10 +266,20 @@ export declare class Lobby extends Emitter<LobbyEvents> {
217
266
  private setHost;
218
267
  /** The host is gone (link closed, no frames for 3 s, or the server said so): run the s.9 rule. */
219
268
  private handleHostLost;
269
+ /** Offers that came from roster members while the host link was still open: now the host is lost, answer them. */
270
+ private replayHeldOffers;
220
271
  private becomeHost;
221
272
  private onHostClaim;
222
273
  private onSignaling;
223
274
  private onSignal;
275
+ /**
276
+ * Whether an SDP offer from `from` may be answered. The host (or a joiner's host) always may. Direct lobbies have no
277
+ * migration, so nobody else. In a server lobby a roster member may dial us when we are the host (a claim race decides
278
+ * who wins) or when our own link to the host is gone (the migration window); while that link is open the offer is
279
+ * held, to be answered if the host turns out to be lost a moment later.
280
+ */
281
+ private offerVerdict;
282
+ private acceptOffer;
224
283
  /**
225
284
  * `linger` (host kick / disconnect): stop using the peer's link at once but leave it open until that peer closes it
226
285
  * or KICK_LINGER_MS pass. The hub tells the peer over its WebSocket; closing the data link first would make it look
@@ -235,6 +294,8 @@ export declare class Lobby extends Emitter<LobbyEvents> {
235
294
  /** peerTimeoutMs: the host drops joiners it has not heard from; a joiner handles a silent host as lost. */
236
295
  private dropSilent;
237
296
  private pingHost;
297
+ /** Host: report a joiner that is not ready `readyTimeoutMs` after joining as peerFailed("timeout"), once. */
298
+ private watchReady;
238
299
  private teardown;
239
300
  /** Total in-flight links (diagnostics and tests). */
240
301
  get linkCount(): number;