@net-mesh/browser 0.37.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 (110) hide show
  1. package/CHANGELOG.md +517 -0
  2. package/LICENSE-APACHE +190 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +666 -0
  5. package/dist/async-queue.d.ts +26 -0
  6. package/dist/async-queue.js +62 -0
  7. package/dist/credential.d.ts +70 -0
  8. package/dist/credential.js +95 -0
  9. package/dist/errors.d.ts +400 -0
  10. package/dist/errors.js +726 -0
  11. package/dist/events.d.ts +232 -0
  12. package/dist/events.js +298 -0
  13. package/dist/identity.d.ts +40 -0
  14. package/dist/identity.js +78 -0
  15. package/dist/index.bundle.js +2 -0
  16. package/dist/index.d.ts +38 -0
  17. package/dist/index.js +46 -0
  18. package/dist/leader/events.d.ts +111 -0
  19. package/dist/leader/events.js +116 -0
  20. package/dist/leader/index.d.ts +14 -0
  21. package/dist/leader/index.js +11 -0
  22. package/dist/leader/session.d.ts +539 -0
  23. package/dist/leader/session.js +770 -0
  24. package/dist/leader/wasm.d.ts +123 -0
  25. package/dist/leader/wasm.js +39 -0
  26. package/dist/lobby.d.ts +183 -0
  27. package/dist/lobby.js +504 -0
  28. package/dist/local.d.ts +80 -0
  29. package/dist/local.js +160 -0
  30. package/dist/net_leaf.d.ts +1494 -0
  31. package/dist/net_leaf.js +3753 -0
  32. package/dist/net_leaf_bg.wasm +0 -0
  33. package/dist/net_leaf_bg.wasm.d.ts +137 -0
  34. package/dist/netcode/client.d.ts +102 -0
  35. package/dist/netcode/client.js +296 -0
  36. package/dist/netcode/clock.d.ts +45 -0
  37. package/dist/netcode/clock.js +54 -0
  38. package/dist/netcode/host.d.ts +118 -0
  39. package/dist/netcode/host.js +276 -0
  40. package/dist/netcode/index.d.ts +19 -0
  41. package/dist/netcode/index.js +14 -0
  42. package/dist/netcode/interpolate.d.ts +50 -0
  43. package/dist/netcode/interpolate.js +100 -0
  44. package/dist/netcode/wire.d.ts +125 -0
  45. package/dist/netcode/wire.js +117 -0
  46. package/dist/node.d.ts +908 -0
  47. package/dist/node.js +1085 -0
  48. package/dist/org.d.ts +412 -0
  49. package/dist/org.js +567 -0
  50. package/dist/peer-driver.d.ts +117 -0
  51. package/dist/peer-driver.js +224 -0
  52. package/dist/store/assembly.d.ts +176 -0
  53. package/dist/store/assembly.js +325 -0
  54. package/dist/store/chunker.d.ts +83 -0
  55. package/dist/store/chunker.js +190 -0
  56. package/dist/store/core.d.ts +136 -0
  57. package/dist/store/core.js +477 -0
  58. package/dist/store/definition.d.ts +23 -0
  59. package/dist/store/definition.js +83 -0
  60. package/dist/store/errors.d.ts +77 -0
  61. package/dist/store/errors.js +94 -0
  62. package/dist/store/host.d.ts +247 -0
  63. package/dist/store/host.js +675 -0
  64. package/dist/store/index.d.ts +50 -0
  65. package/dist/store/index.js +40 -0
  66. package/dist/store/interest.d.ts +40 -0
  67. package/dist/store/interest.js +77 -0
  68. package/dist/store/inventory.d.ts +81 -0
  69. package/dist/store/inventory.js +140 -0
  70. package/dist/store/join.d.ts +118 -0
  71. package/dist/store/join.js +724 -0
  72. package/dist/store/json.d.ts +50 -0
  73. package/dist/store/json.js +283 -0
  74. package/dist/store/ledger.d.ts +187 -0
  75. package/dist/store/ledger.js +292 -0
  76. package/dist/store/owner.d.ts +616 -0
  77. package/dist/store/owner.js +1711 -0
  78. package/dist/store/patch.d.ts +70 -0
  79. package/dist/store/patch.js +166 -0
  80. package/dist/store/player.d.ts +65 -0
  81. package/dist/store/player.js +261 -0
  82. package/dist/store/replica.d.ts +187 -0
  83. package/dist/store/replica.js +740 -0
  84. package/dist/store/state.d.ts +42 -0
  85. package/dist/store/state.js +165 -0
  86. package/dist/store/types.d.ts +200 -0
  87. package/dist/store/types.js +25 -0
  88. package/dist/store/visibility.d.ts +118 -0
  89. package/dist/store/visibility.js +282 -0
  90. package/dist/store/wire.d.ts +309 -0
  91. package/dist/store/wire.js +834 -0
  92. package/dist/stream.d.ts +174 -0
  93. package/dist/stream.js +238 -0
  94. package/dist/three/index.d.ts +109 -0
  95. package/dist/three/index.js +130 -0
  96. package/dist/udp-probe.d.ts +159 -0
  97. package/dist/udp-probe.js +245 -0
  98. package/dist/wasm.d.ts +642 -0
  99. package/dist/wasm.js +98 -0
  100. package/dist/world/border.d.ts +84 -0
  101. package/dist/world/border.js +99 -0
  102. package/dist/world/handoff.d.ts +187 -0
  103. package/dist/world/handoff.js +222 -0
  104. package/dist/world/index.d.ts +13 -0
  105. package/dist/world/index.js +9 -0
  106. package/dist/world/link.d.ts +179 -0
  107. package/dist/world/link.js +476 -0
  108. package/dist/world/regions.d.ts +154 -0
  109. package/dist/world/regions.js +314 -0
  110. package/package.json +72 -0
@@ -0,0 +1,123 @@
1
+ /**
2
+ * The `wasm_bindgen` boundary for §8's session surface.
3
+ *
4
+ * `src/wasm.ts` describes the *direct* surface (`LeafNode`) and types
5
+ * `MeshSession` as `unknown` so the two cannot drift into two
6
+ * spellings of one contract. This file is the one place the session's
7
+ * shape is declared, in the snake_case `wasm-bindgen` emits from the
8
+ * Rust method names; every camelCase name a page sees is added by
9
+ * {@link MeshSession} one layer up.
10
+ */
11
+ import type { LeafWasmConnectOptions, LeafWasmModule, LeafWasmOrgAccess, LeafWasmOrgCallOptions, LeafWasmOrgClientStreamHandler, LeafWasmOrgDuplexHandler, LeafWasmOrgServeHandle, LeafWasmOrgServeOptions, LeafWasmOrgStreamingHandler, LeafWasmOrgUnaryHandler, LeafWasmOrgUploadCallHandle, LeafWasmOrgByteStreamHandle, LeafWasmOrgDuplexCallHandle, LeafWasmProxyStream, LeafWasmStreamOptions } from '../wasm.js';
12
+ /**
13
+ * `MeshSession.open`'s argument: everything `LeafNode.connect` reads,
14
+ * plus what leader election needs.
15
+ */
16
+ export interface LeafWasmSessionOptions extends LeafWasmConnectOptions {
17
+ /**
18
+ * Capabilities to announce, and to **re-announce** whenever this tab
19
+ * becomes the leader. They live on the session rather than on a
20
+ * method call because a new leader has to re-publish them without
21
+ * being asked.
22
+ */
23
+ capabilities?: readonly string[];
24
+ /**
25
+ * Channels this tab depends on. The leader keeps the union of every
26
+ * follower's declaration subscribed, and a new leader restores it.
27
+ */
28
+ subscriptions?: readonly string[];
29
+ /** The IndexedDB database holding the identity. Defaults to `net-mesh-leaf`. */
30
+ dbName?: string;
31
+ /**
32
+ * Override the Web Lock and `BroadcastChannel` name. Defaults to
33
+ * `net-mesh/<origin>/<identity-fingerprint>`, which is the scope
34
+ * one-node-per-origin means; overriding it is for tests that need
35
+ * two independent elections in one origin.
36
+ */
37
+ lockScope?: string;
38
+ }
39
+ /** The session object `MeshSession.open` resolves to. */
40
+ export interface LeafWasmSession {
41
+ /** `"leader"` or `"follower"`. */
42
+ role(): string;
43
+ /** The generation in force for this tab, as an exact decimal string. */
44
+ generation(): string;
45
+ /** The node's id, 16 lowercase hex digits, once one is known. */
46
+ node_id_hex(): string | undefined;
47
+ /** The identity fingerprint the lock is named after. */
48
+ fingerprint(): string;
49
+ /** The lock and channel name. */
50
+ scope(): string;
51
+ /** The measured handoff for a promoted tab; `undefined` on the first leader. */
52
+ interruption_ms(): number | undefined;
53
+ call(service: string, payload: Uint8Array, timeout_ms?: number): Promise<Uint8Array>;
54
+ call_org(service: string, payload: Uint8Array, options: LeafWasmOrgCallOptions): Promise<Uint8Array>;
55
+ call_org_streaming(service: string, payload: Uint8Array, options: LeafWasmOrgCallOptions): Promise<LeafWasmOrgByteStreamHandle>;
56
+ call_org_client_stream(service: string, options: LeafWasmOrgCallOptions): Promise<LeafWasmOrgUploadCallHandle>;
57
+ call_org_duplex(service: string, options: LeafWasmOrgCallOptions): Promise<LeafWasmOrgDuplexCallHandle>;
58
+ serve_org(service: string, access: LeafWasmOrgAccess, handler: LeafWasmOrgUnaryHandler, options: LeafWasmOrgServeOptions): LeafWasmOrgServeHandle;
59
+ serve_org_streaming(service: string, access: LeafWasmOrgAccess, handler: LeafWasmOrgStreamingHandler, options: LeafWasmOrgServeOptions): LeafWasmOrgServeHandle;
60
+ serve_org_client_stream(service: string, access: LeafWasmOrgAccess, handler: LeafWasmOrgClientStreamHandler, options: LeafWasmOrgServeOptions): LeafWasmOrgServeHandle;
61
+ serve_org_duplex(service: string, access: LeafWasmOrgAccess, handler: LeafWasmOrgDuplexHandler, options: LeafWasmOrgServeOptions): LeafWasmOrgServeHandle;
62
+ subscribe(channel: string): Promise<void>;
63
+ /**
64
+ * Release this tab's claim on a channel. The membership survives
65
+ * while another tab still declares it.
66
+ */
67
+ unsubscribe(channel: string): Promise<void>;
68
+ publish(channel: string, payload: Uint8Array): Promise<void>;
69
+ announce(capabilities: string[]): Promise<void>;
70
+ query(capability: string): Promise<string>;
71
+ /** Every counter as JSON. A promise: on a follower the node is elsewhere. */
72
+ counters_json(): Promise<string>;
73
+ /** Proxied, so a follower's `enroll()` works — one node per origin. */
74
+ enroll(): Promise<void>;
75
+ /** A promise, where `LeafNode.is_enrolled` is synchronous. */
76
+ is_enrolled(): Promise<boolean>;
77
+ signal(peer_hex: string, dialog_hex: string, kind: string, payload: Uint8Array): Promise<void>;
78
+ /**
79
+ * The four peer-attempt primitives, proxied. `peer_candidate` and
80
+ * `peer_handshake` take the dialog because the leaf requires it:
81
+ * a proxied request is served after crossing a channel, so the
82
+ * attempt it named may have been replaced, and it is refused
83
+ * before it can drive the replacement.
84
+ */
85
+ peer_offer(peer_hex: string): Promise<string>;
86
+ /**
87
+ * The live attempt's dialog when the pair is already direct and open on
88
+ * the leader, else `undefined`. An older leader refuses the op.
89
+ */
90
+ peer_direct_dialog(peer_hex: string): Promise<string | undefined>;
91
+ peer_accept_offer(peer_hex: string): Promise<string>;
92
+ peer_candidate(peer_hex: string, dialog_hex: string): Promise<string>;
93
+ peer_handshake(peer_hex: string, dialog_hex: string): Promise<string>;
94
+ /**
95
+ * A promise, where `LeafNode.open_stream` is synchronous: on a
96
+ * follower the stream is opened by the tab that owns the
97
+ * DataChannel.
98
+ */
99
+ open_stream(options: LeafWasmStreamOptions): Promise<LeafWasmProxyStream>;
100
+ on_event(callback: (eventJson: string) => void): void;
101
+ close(): void;
102
+ }
103
+ /**
104
+ * The generated module, with `MeshSession` refined.
105
+ *
106
+ * A bundle built before Stage 5 slice 3 does not carry it, which is
107
+ * why the base type leaves it optional and this narrows rather than
108
+ * assumes.
109
+ */
110
+ export interface LeafWasmSessionModule extends LeafWasmModule {
111
+ MeshSession: {
112
+ open(options: LeafWasmSessionOptions): Promise<LeafWasmSession>;
113
+ };
114
+ }
115
+ /**
116
+ * Narrow a loaded module to one that carries the session surface.
117
+ *
118
+ * A named refusal rather than a cast: a page bundling an older
119
+ * `net_leaf.js` would otherwise get `undefined is not a constructor`
120
+ * from inside the wrapper, which says nothing about what is actually
121
+ * wrong.
122
+ */
123
+ export declare function asSessionModule(module: LeafWasmModule): LeafWasmSessionModule;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The `wasm_bindgen` boundary for §8's session surface.
3
+ *
4
+ * `src/wasm.ts` describes the *direct* surface (`LeafNode`) and types
5
+ * `MeshSession` as `unknown` so the two cannot drift into two
6
+ * spellings of one contract. This file is the one place the session's
7
+ * shape is declared, in the snake_case `wasm-bindgen` emits from the
8
+ * Rust method names; every camelCase name a page sees is added by
9
+ * {@link MeshSession} one layer up.
10
+ */
11
+ /**
12
+ * Narrow a loaded module to one that carries the session surface.
13
+ *
14
+ * A named refusal rather than a cast: a page bundling an older
15
+ * `net_leaf.js` would otherwise get `undefined is not a constructor`
16
+ * from inside the wrapper, which says nothing about what is actually
17
+ * wrong.
18
+ */
19
+ export function asSessionModule(module) {
20
+ const candidate = module.MeshSession;
21
+ // `wasm-bindgen` emits `export class MeshSession { static open(…) }`,
22
+ // and a class is a FUNCTION, not an object — so the shape check has
23
+ // to admit both. It used to test `typeof === 'object'` only, which
24
+ // rejected every real bundle and reported the pre-slice-3 message
25
+ // for a module that carried the session all along.
26
+ const usable = candidate !== null &&
27
+ (typeof candidate === 'object' || typeof candidate === 'function') &&
28
+ 'open' in candidate &&
29
+ typeof candidate.open === 'function';
30
+ if (!usable) {
31
+ throw new Error('this net-mesh-leaf bundle has no MeshSession: leader election and the ' +
32
+ 'follower proxy need a wasm build from Stage 5 slice 3 or later');
33
+ }
34
+ // Checked above: `MeshSession.open` exists and is callable. The
35
+ // argument and return types are the wasm-bindgen contract this file
36
+ // declares, and no runtime check can establish those.
37
+ const narrowed = module;
38
+ return narrowed;
39
+ }
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Lobbies: host a game others can find, list the open ones, join by
3
+ * code or link.
4
+ *
5
+ * ```ts
6
+ * const lobby = await createLobby({ node, game: 'arena', name: 'Friday arena', capacity: 8,
7
+ * definition, initialState, actions, inputs, project });
8
+ * lobby.code; // 'K7QP2M' — say it out loud, or share lobby.link()
9
+ * const me = lobby.self; // the host's own player, same shape as a joined one
10
+ *
11
+ * const open = await listLobbies({ node, game: 'arena' });
12
+ * const world = await joinLobby({ node, definition, code: 'K7QP2M' });
13
+ * ```
14
+ *
15
+ * A thin layer over what already exists — `hostStore`, `hostPlayer`,
16
+ * `joinStore`, `announce` and `query` — and no new protocol. A lobby is
17
+ * found through capability tags, which are free-form strings the mesh
18
+ * carries in a node's signed announcement:
19
+ *
20
+ * - `net-lobby:<game>` — the listing tag; public lobbies only.
21
+ * - `net-lobby:<game>:rec:<base64url JSON>` — the record a list shows:
22
+ * code, name, players, capacity, store version and the game's own
23
+ * `info`. Public lobbies only.
24
+ * - `net-lobby:<game>:seek:<host hex>` — announced by a player joining that
25
+ * host. A browser node accepts a relayed handshake only from a peer whose
26
+ * signed announcement it holds, and the anchor delivers announcements to
27
+ * its peers — so a joiner that announces nothing cannot reach the host
28
+ * (measured: `examples/anchor-acceptance`). The tag says why this player
29
+ * announces at all.
30
+ * - `net-lobby:<game>:code:<32 hex>` — the code, hashed; how a code is
31
+ * looked up. Both kinds announce it.
32
+ *
33
+ * **What a listing is.** Anyone on the mesh can announce any tag, so a
34
+ * record is a claim. The host id beside it is not: it comes from the
35
+ * signed announcement. Records are validated and bounded before they
36
+ * are shown, and joining connects to the announcing node, whose store
37
+ * then answers for itself.
38
+ *
39
+ * **Unlisted is not secret.** An unlisted lobby publishes no record, so
40
+ * it never appears in a list — but a 6-character code can be guessed
41
+ * against its hash, and the anchor relays every announcement. Who may
42
+ * join is decided by `authorize`, as for any store.
43
+ *
44
+ * **One lobby per node.** `announce` replaces a node's tags, so the
45
+ * lobby owns its node's announcement while it is open; pass any other
46
+ * tags the node should keep announcing as `tags`.
47
+ */
48
+ import type { NodeDescriptor } from './node.js';
49
+ import { type HostedStoreHandle, type HostProjection, type HostStoreBaseOptions, type StoreTransport } from './store/host.js';
50
+ import { type JoinedStoreHandle } from './store/join.js';
51
+ import type { AccessRequest, ActionSpec, Cancel, InputSpec, StoreDefinition } from './store/types.js';
52
+ /** A node a lobby can run on: a store transport that can announce and query. */
53
+ export interface LobbyNode extends StoreTransport {
54
+ announce(capabilities: readonly string[]): Promise<void>;
55
+ query(capability: string): Promise<NodeDescriptor[]>;
56
+ }
57
+ /** Game-defined fields shown in a lobby list: small, flat, public. */
58
+ export type LobbyInfo = Readonly<Record<string, string | number | boolean>>;
59
+ /** Why a lobby call failed. */
60
+ export type LobbyErrorCode =
61
+ /** No lobby answered to that code before the deadline. */
62
+ 'not-found'
63
+ /** More than one node claims the code; joining either could be the wrong one. */
64
+ | 'ambiguous'
65
+ /** A game name, code, record or option is not usable. */
66
+ | 'invalid';
67
+ export declare class LobbyError extends Error {
68
+ readonly code: LobbyErrorCode;
69
+ constructor(code: LobbyErrorCode, message: string);
70
+ }
71
+ /** One open lobby, as a list shows it. */
72
+ export interface LobbyListing {
73
+ readonly code: string;
74
+ readonly name: string;
75
+ /** Players in it, the host included, as the host last announced. */
76
+ readonly players: number;
77
+ readonly capacity: number;
78
+ /** The store it serves, `id@version`. */
79
+ readonly store: string;
80
+ readonly info: LobbyInfo;
81
+ /** The announcing node, 16 hex — from its signed announcement, not from the record. */
82
+ readonly host: string;
83
+ }
84
+ /** Bounds on what a lobby may publish (Q4 in the lobbies plan). */
85
+ export declare const MAX_LOBBY_NAME_CHARS = 64;
86
+ export declare const MAX_LOBBY_INFO_BYTES = 256;
87
+ export declare const MAX_LOBBY_RECORD_TAG_BYTES = 512;
88
+ export declare const MAX_LOBBY_CAPACITY = 256;
89
+ /** Characters a code is made of: no 0/O, 1/I/L, so a code survives being read aloud. */
90
+ export declare const LOBBY_CODE_ALPHABET = "ABCDEFGHJKMNPQRSTUVWXYZ23456789";
91
+ export declare const LOBBY_CODE_LENGTH = 6;
92
+ /** How often a lobby re-announces itself. */
93
+ export declare const LOBBY_ANNOUNCE_MS = 2000;
94
+ /** A code as typed: case, spaces and dashes forgiven. */
95
+ export declare function normalizeLobbyCode(code: string): string;
96
+ /** What `createLobby` takes: a store's host options, minus the transport, plus the lobby. */
97
+ export type CreateLobbyOptions<S extends object, A extends ActionSpec, I extends InputSpec> = Omit<HostStoreBaseOptions<S, A, I>, 'transport' | 'authorize' | 'maxEventBytes'> & HostProjection<S> & {
98
+ readonly node: LobbyNode;
99
+ /** Your game's name on the mesh: lobbies are listed per game. */
100
+ readonly game: string;
101
+ readonly name: string;
102
+ /** Most players at once, the host included. */
103
+ readonly capacity: number;
104
+ /** `'public'` (default) is listed; `'unlisted'` is joined by code or link only. */
105
+ readonly visibility?: 'public' | 'unlisted';
106
+ /** Small public fields a list shows — map, mode. At most 256 bytes of JSON. */
107
+ readonly info?: LobbyInfo;
108
+ /** The audience the host's own player reads. Default `[]`. */
109
+ readonly audience?: readonly string[];
110
+ /**
111
+ * Your game's policy, as `hostStore`'s. Default: allow. The lobby
112
+ * applies capacity and kicks BEFORE it, so it never sees a refused
113
+ * join.
114
+ */
115
+ readonly authorize?: (request: AccessRequest<A, I>) => boolean;
116
+ readonly maxEventBytes?: number;
117
+ /** Other tags this node should keep announcing while the lobby is open. */
118
+ readonly tags?: readonly string[];
119
+ /** Re-announce interval. Default {@link LOBBY_ANNOUNCE_MS}. */
120
+ readonly announceEveryMs?: number;
121
+ };
122
+ /** An open lobby, on the node hosting it. */
123
+ export interface Lobby<S extends object, A extends ActionSpec, I extends InputSpec> {
124
+ readonly code: string;
125
+ readonly game: string;
126
+ readonly visibility: 'public' | 'unlisted';
127
+ /** The authoritative store. */
128
+ readonly host: HostedStoreHandle<S, A, I>;
129
+ /** The host's own player — the same handle shape `joinLobby` returns. */
130
+ readonly self: JoinedStoreHandle<S, A, I>;
131
+ /** Who is in: the host first, then each joined peer, 16 hex. */
132
+ players(): readonly string[];
133
+ /** Called with the new list whenever someone joins or leaves. */
134
+ subscribePlayers(listener: (players: readonly string[]) => void): Cancel;
135
+ /** A shareable link: `base` with `?lobby=<code>`. Default base: this page. */
136
+ link(base?: string): string;
137
+ /** Remove a player now and refuse them from here on. */
138
+ kick(peer: string): void;
139
+ /** Stop announcing, close the store and the host's player. */
140
+ close(): Promise<void>;
141
+ }
142
+ /**
143
+ * Open a lobby: host the store on `node`, give the host its own
144
+ * player, and announce the lobby until it closes.
145
+ */
146
+ export declare function createLobby<S extends object, A extends ActionSpec, I extends InputSpec>(options: CreateLobbyOptions<S, A, I>): Promise<Lobby<S, A, I>>;
147
+ /** The open public lobbies of `game`, as their hosts last announced them. */
148
+ export declare function listLobbies(options: {
149
+ readonly node: LobbyNode;
150
+ readonly game: string;
151
+ }): Promise<LobbyListing[]>;
152
+ /** What `joinLobby` takes. */
153
+ export interface JoinLobbyOptions<S extends object, A extends ActionSpec, I extends InputSpec> {
154
+ readonly node: LobbyNode;
155
+ readonly definition: StoreDefinition<S, A, I>;
156
+ readonly game: string;
157
+ /** A code, as typed or from a link — or a listing from `listLobbies`. */
158
+ readonly code?: string;
159
+ readonly lobby?: LobbyListing;
160
+ /** The audience to read. Default `[]`. */
161
+ readonly audience?: readonly string[];
162
+ /** The interest set to join with (see `joinStore`). Default: every entity. */
163
+ readonly interest?: readonly string[];
164
+ /** The opaque join token. Default `'player'`. */
165
+ readonly key?: string;
166
+ readonly maxEventBytes?: number;
167
+ /** How long to look for the code, and to reach the host. Default 20 s. */
168
+ readonly timeoutMs?: number;
169
+ /**
170
+ * Tags this node keeps announcing while it plays. Joining announces a
171
+ * `seek` tag so the host can discover this player, and an announcement
172
+ * replaces the node's whole tag set — so anything else the page
173
+ * announces goes here. The same option as `createLobby`'s.
174
+ */
175
+ readonly tags?: readonly string[];
176
+ }
177
+ /**
178
+ * Join a lobby by code or by listing. Resolves once the host is found
179
+ * and the join is sent; await `ready()` on the result for the world.
180
+ */
181
+ export declare function joinLobby<S extends object, A extends ActionSpec, I extends InputSpec>(options: JoinLobbyOptions<S, A, I>): Promise<JoinedStoreHandle<S, A, I>>;
182
+ /** The lobby code in a link made by `lobby.link()`, or `null`. Default: this page. */
183
+ export declare function lobbyCodeFromUrl(url?: string): string | null;