@crowdedkingdomstudios/crowdyjs 5.1.0 → 5.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/MIGRATION.md +64 -0
  2. package/README.md +19 -0
  3. package/dist/client.d.ts +98 -5
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +74 -5
  6. package/dist/crowdy-client.d.ts +31 -0
  7. package/dist/crowdy-client.d.ts.map +1 -1
  8. package/dist/crowdy-client.js +8 -0
  9. package/dist/domains/actors.d.ts +88 -5
  10. package/dist/domains/actors.d.ts.map +1 -1
  11. package/dist/domains/actors.js +89 -6
  12. package/dist/domains/apps.d.ts +95 -41
  13. package/dist/domains/apps.d.ts.map +1 -1
  14. package/dist/domains/apps.js +80 -33
  15. package/dist/domains/auth.d.ts +139 -19
  16. package/dist/domains/auth.d.ts.map +1 -1
  17. package/dist/domains/auth.js +137 -17
  18. package/dist/domains/channels.d.ts +264 -5
  19. package/dist/domains/channels.d.ts.map +1 -1
  20. package/dist/domains/channels.js +264 -5
  21. package/dist/domains/chunks.d.ts +116 -3
  22. package/dist/domains/chunks.d.ts.map +1 -1
  23. package/dist/domains/chunks.js +116 -3
  24. package/dist/domains/gameModel.d.ts +412 -6
  25. package/dist/domains/gameModel.d.ts.map +1 -1
  26. package/dist/domains/gameModel.js +412 -6
  27. package/dist/domains/platform.d.ts +36 -20
  28. package/dist/domains/platform.d.ts.map +1 -1
  29. package/dist/domains/platform.js +29 -18
  30. package/dist/domains/serverStatus.d.ts +74 -6
  31. package/dist/domains/serverStatus.d.ts.map +1 -1
  32. package/dist/domains/serverStatus.js +74 -6
  33. package/dist/domains/state.d.ts +50 -2
  34. package/dist/domains/state.d.ts.map +1 -1
  35. package/dist/domains/state.js +50 -2
  36. package/dist/domains/teams.d.ts +265 -7
  37. package/dist/domains/teams.d.ts.map +1 -1
  38. package/dist/domains/teams.js +267 -9
  39. package/dist/domains/teleport.d.ts +30 -2
  40. package/dist/domains/teleport.d.ts.map +1 -1
  41. package/dist/domains/teleport.js +30 -2
  42. package/dist/domains/udp.d.ts +341 -5
  43. package/dist/domains/udp.d.ts.map +1 -1
  44. package/dist/domains/udp.js +341 -5
  45. package/dist/domains/users.d.ts +42 -11
  46. package/dist/domains/users.d.ts.map +1 -1
  47. package/dist/domains/users.js +41 -10
  48. package/dist/domains/voxels.d.ts +107 -2
  49. package/dist/domains/voxels.d.ts.map +1 -1
  50. package/dist/domains/voxels.js +107 -2
  51. package/dist/errors.d.ts +116 -0
  52. package/dist/errors.d.ts.map +1 -1
  53. package/dist/errors.js +100 -0
  54. package/dist/generated/graphql.d.ts +1787 -110
  55. package/dist/generated/graphql.d.ts.map +1 -1
  56. package/dist/generated/graphql.js +75 -9
  57. package/dist/index.d.ts +2 -1
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +2 -1
  60. package/dist/realtime.d.ts +226 -0
  61. package/dist/realtime.d.ts.map +1 -1
  62. package/dist/realtime.js +90 -0
  63. package/dist/session.d.ts +46 -0
  64. package/dist/session.d.ts.map +1 -1
  65. package/dist/session.js +35 -0
  66. package/dist/types.d.ts +429 -0
  67. package/dist/types.d.ts.map +1 -1
  68. package/dist/types.js +53 -0
  69. package/dist/utils.d.ts +86 -0
  70. package/dist/utils.d.ts.map +1 -1
  71. package/dist/utils.js +86 -0
  72. package/dist/world.d.ts +192 -0
  73. package/dist/world.d.ts.map +1 -1
  74. package/dist/world.js +170 -0
  75. package/package.json +1 -1
@@ -2,60 +2,214 @@ import type { SessionStore } from './session.js';
2
2
  import type { CrowdyLogger } from './logger.js';
3
3
  import { CrowdyRealtimeError } from './errors.js';
4
4
  import { type UdpNotificationsSubscription } from './generated/graphql.js';
5
+ /**
6
+ * Lifecycle state of the realtime WebSocket connection, as reported by
7
+ * {@link RealtimeClient.status} and {@link RealtimeClient.onStatus}.
8
+ *
9
+ * - `idle` — created but never connected; no socket open yet.
10
+ * - `connecting` — opening the socket / performing the initial handshake.
11
+ * - `connected` — the subscription is live and receiving notifications.
12
+ * - `reconnecting` — the socket dropped (or a retry is in progress) while a
13
+ * connection is still desired; backoff is running and it will resubscribe.
14
+ * - `disconnected` — intentionally closed (e.g. {@link RealtimeClient.disconnect}
15
+ * or the last subscriber unsubscribing).
16
+ * - `failed` — a fatal, non-retryable error (e.g. not authenticated, or a
17
+ * non-retryable `RealtimeConnectionEvent` such as `APP_ID_REQUIRED`); it will
18
+ * not reconnect on its own.
19
+ */
5
20
  export type RealtimeStatus = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'disconnected' | 'failed';
21
+ /**
22
+ * Any single message delivered on the `udpNotifications` subscription — the
23
+ * union of every spatial echo/fan-out notification plus `GenericErrorResponse`
24
+ * and `RealtimeConnectionEvent`. This is the codegen-derived (canonical)
25
+ * shape, narrowed to the non-null payload; discriminate the members by their
26
+ * `__typename`.
27
+ */
6
28
  export type UdpNotification = NonNullable<UdpNotificationsSubscription['udpNotifications']>;
29
+ /**
30
+ * The members of {@link UdpNotification} that carry a `sequenceNumber` and can
31
+ * therefore be correlated back to the send that produced them — the spatial
32
+ * echoes/fan-out (actor/voxel/audio/text/event notifications and responses,
33
+ * single-actor and channel messages) plus `GenericErrorResponse`. Excludes
34
+ * `RealtimeConnectionEvent`, which has no sequence number.
35
+ *
36
+ * {@link RealtimeClient.waitForSequence} resolves with one of these when a
37
+ * matching success arrives (it rejects instead when the match is a
38
+ * `GenericErrorResponse`), which is what powers the `...AndWait` spatial sends.
39
+ */
7
40
  export type SpatialNotification = Extract<UdpNotification, {
8
41
  sequenceNumber: number;
9
42
  }>;
43
+ /**
44
+ * Per-notification callbacks passed to `client.udp.subscribe(handlers, appId)`
45
+ * (or `client.world(appId).subscribe`). Every handler is optional — supply
46
+ * only the ones you care about. Each key maps a notification's GraphQL
47
+ * `__typename` to its callback, except {@link any} and {@link error}, which are
48
+ * special (see below).
49
+ *
50
+ * Handlers are dispatched synchronously as messages arrive, and exceptions
51
+ * thrown inside one are caught and logged so a single bad handler can't tear
52
+ * down the stream. For each notification {@link any} runs first, then the
53
+ * matching typed handler.
54
+ */
10
55
  export interface UdpNotificationHandlers {
56
+ /**
57
+ * Another actor's position/state changed within your area of interest —
58
+ * the spatial fan-out of someone else's `sendActorUpdate`. `state` is
59
+ * base64-encoded actor state.
60
+ */
11
61
  actorUpdate?: (notification: Extract<UdpNotification, {
12
62
  __typename?: 'ActorUpdateNotification';
13
63
  }>) => void;
64
+ /**
65
+ * Server acknowledgement echoing one of **your own** actor updates. This is
66
+ * the notification a `sendActorUpdateAndWait` correlates to via
67
+ * `sequenceNumber`.
68
+ */
14
69
  actorUpdateResponse?: (notification: Extract<UdpNotification, {
15
70
  __typename?: 'ActorUpdateResponse';
16
71
  }>) => void;
72
+ /**
73
+ * A voxel changed within range — the fan-out of another client's voxel edit.
74
+ * `voxelState` is base64-encoded.
75
+ */
17
76
  voxelUpdate?: (notification: Extract<UdpNotification, {
18
77
  __typename?: 'VoxelUpdateNotification';
19
78
  }>) => void;
79
+ /**
80
+ * Server acknowledgement echoing one of **your own** voxel updates (the
81
+ * `sendVoxelUpdateAndWait` correlation target).
82
+ */
20
83
  voxelUpdateResponse?: (notification: Extract<UdpNotification, {
21
84
  __typename?: 'VoxelUpdateResponse';
22
85
  }>) => void;
86
+ /**
87
+ * A nearby client sent a voice/audio packet; `audioData` is base64-encoded
88
+ * compressed audio (decode with {@link decodeBase64}).
89
+ */
23
90
  audio?: (notification: Extract<UdpNotification, {
24
91
  __typename?: 'ClientAudioNotification';
25
92
  }>) => void;
93
+ /** A nearby client sent a text/chat message (`text` is UTF-8). */
26
94
  text?: (notification: Extract<UdpNotification, {
27
95
  __typename?: 'ClientTextNotification';
28
96
  }>) => void;
97
+ /**
98
+ * A nearby client emitted a custom client event (a client-defined
99
+ * `eventType` with a base64 `state` payload).
100
+ */
29
101
  clientEvent?: (notification: Extract<UdpNotification, {
30
102
  __typename?: 'ClientEventNotification';
31
103
  }>) => void;
104
+ /**
105
+ * A server-originated spatial event broadcast to a region (e.g. world or NPC
106
+ * events), shaped like a client event (`eventType` + base64 `state`).
107
+ */
32
108
  serverEvent?: (notification: Extract<UdpNotification, {
33
109
  __typename?: 'ServerEventNotification';
34
110
  }>) => void;
111
+ /**
112
+ * A direct actor-to-actor message addressed specifically to you; `payload`
113
+ * is base64. There is no sender echo, so this only ever arrives on the
114
+ * recipient's subscription.
115
+ */
35
116
  singleActorMessage?: (notification: Extract<UdpNotification, {
36
117
  __typename?: 'SingleActorMessageNotification';
37
118
  }>) => void;
119
+ /**
120
+ * A message broadcast on a channel (group) you're subscribed to; `payload`
121
+ * is base64 and opaque to the server.
122
+ */
38
123
  channelMessage?: (notification: Extract<UdpNotification, {
39
124
  __typename?: 'ChannelMessageNotification';
40
125
  }>) => void;
126
+ /**
127
+ * An asynchronous error for a previously sent datagram. Correlate it to the
128
+ * originating send via `sequenceNumber` and read `errorCode`
129
+ * ({@link UdpErrorCode}) for the reason. The matching `...AndWait` promise
130
+ * rejects on this; the handler still fires for observability.
131
+ */
41
132
  genericError?: (notification: Extract<UdpNotification, {
42
133
  __typename?: 'GenericErrorResponse';
43
134
  }>) => void;
135
+ /**
136
+ * A connection-lifecycle event from the game-api (handshake / auth /
137
+ * routing), carrying `status`, `code`, `message`, and `retryable`. A
138
+ * non-retryable event such as `code: 'APP_ID_REQUIRED'` means the
139
+ * subscription was rejected and will not be retried automatically.
140
+ */
44
141
  connectionEvent?: (notification: Extract<UdpNotification, {
45
142
  __typename?: 'RealtimeConnectionEvent';
46
143
  }>) => void;
144
+ /**
145
+ * SDK-level realtime failures surfaced as a {@link CrowdyRealtimeError}
146
+ * (socket error, auth token cleared, subscription failed, wait timeout).
147
+ * This is a **client-side** signal, not a server notification.
148
+ */
47
149
  error?: (error: CrowdyRealtimeError) => void;
150
+ /**
151
+ * Catch-all invoked for **every** notification, before the specific typed
152
+ * handler above. Handy for logging, metrics, or custom dispatch.
153
+ */
48
154
  any?: (notification: UdpNotification) => void;
49
155
  }
156
+ /**
157
+ * Tuning options for {@link RealtimeClient} (the WebSocket subscription layer),
158
+ * passed through from `CrowdyClient`'s `realtime` config. Every field is
159
+ * optional and has a default.
160
+ */
50
161
  export interface RealtimeConfig {
162
+ /**
163
+ * WebSocket URL of the game-api GraphQL endpoint (e.g.
164
+ * `wss://game.example.com/graphql`). Used when {@link wsEndpoint} is not set;
165
+ * falls back to `ws://localhost:3000/graphql` when both are omitted.
166
+ */
51
167
  wsUrl?: string;
168
+ /** Alias for {@link wsUrl}; used only when {@link wsUrl} is not provided. */
52
169
  wsEndpoint?: string;
170
+ /**
171
+ * Maximum number of automatic reconnect attempts after the socket drops
172
+ * before giving up. Defaults to `8`.
173
+ */
53
174
  retryAttempts?: number;
175
+ /**
176
+ * Base delay in **milliseconds** for the exponential reconnect backoff (also
177
+ * the upper bound of the random jitter added to each wait). Defaults to
178
+ * `250`.
179
+ */
54
180
  retryInitialDelayMs?: number;
181
+ /**
182
+ * Ceiling in **milliseconds** for the reconnect backoff, so the delay never
183
+ * grows past this between attempts. Defaults to `5000`.
184
+ */
55
185
  retryMaxDelayMs?: number;
186
+ /**
187
+ * Default time in **milliseconds** a `...AndWait` send waits for its matching
188
+ * echo before timing out (overridable per call via
189
+ * {@link RealtimeClient.waitForSequence}). Defaults to `5000`.
190
+ */
56
191
  waitTimeoutMs?: number;
192
+ /** Optional logger for realtime diagnostics. Defaults to a silent logger. */
57
193
  logger?: CrowdyLogger;
58
194
  }
195
+ /**
196
+ * Manages the single WebSocket subscription to the game-api's
197
+ * `udpNotifications` stream — the realtime layer behind `client.udp` and
198
+ * `client.realtime`. It opens the socket lazily on the first {@link subscribe},
199
+ * authenticates with the shared session token, scopes the session to one
200
+ * `appId`, reconnects with jittered exponential backoff, re-reads the token and
201
+ * resubscribes on reconnect, fans each notification out to the registered
202
+ * {@link UdpNotificationHandlers}, and resolves `...AndWait` sends via
203
+ * {@link waitForSequence}.
204
+ *
205
+ * The connection lifecycle is observable through {@link status} /
206
+ * {@link onStatus} ({@link RealtimeStatus}). A realtime session is scoped to a
207
+ * single app, so run one client per app (sharing the same token store) for a
208
+ * player who is in multiple apps at once.
209
+ *
210
+ * You normally interact with this through `client.udp` / `client.realtime`
211
+ * rather than constructing it directly.
212
+ */
59
213
  export declare class RealtimeClient {
60
214
  private readonly session;
61
215
  private readonly wsUrl;
@@ -73,13 +227,85 @@ export declare class RealtimeClient {
73
227
  private readonly pending;
74
228
  private nextSubscriberId;
75
229
  private subscribedAppId;
230
+ /**
231
+ * @param config - Reconnect/timeout/endpoint tuning; see
232
+ * {@link RealtimeConfig}.
233
+ * @param session - Shared session store. The client reads the Bearer token
234
+ * from it for the connection handshake and watches it for changes: clearing
235
+ * the token tears the connection down (emitting an `AUTH_CLEARED`
236
+ * {@link CrowdyRealtimeError}), while a token change made while connected
237
+ * forces a reconnect using the new token.
238
+ */
76
239
  constructor(config: RealtimeConfig | undefined, session: SessionStore);
240
+ /**
241
+ * The current connection state.
242
+ *
243
+ * @returns The latest {@link RealtimeStatus}.
244
+ */
77
245
  status(): RealtimeStatus;
246
+ /**
247
+ * Subscribe to connection-state changes. The listener is invoked
248
+ * **immediately** with the current status, then again on every transition.
249
+ *
250
+ * @param listener - Called with each new {@link RealtimeStatus}.
251
+ * @returns An unsubscribe function that removes the listener.
252
+ */
78
253
  onStatus(listener: (status: RealtimeStatus) => void): () => void;
254
+ /**
255
+ * Mark the connection as desired and open the subscription if it isn't
256
+ * already open. You usually don't call this directly — {@link subscribe}
257
+ * calls it for you; use it (or `client.realtime.connect()`) only to pre-warm
258
+ * the socket.
259
+ *
260
+ * @throws {CrowdyRealtimeError} `AUTH_REQUIRED` if there is no session token.
261
+ */
79
262
  connect(): void;
263
+ /**
264
+ * Close the socket and stop wanting a connection. Outstanding
265
+ * {@link waitForSequence} promises are left intact (they will time out on
266
+ * their own); use {@link close} to also reject those and drop all
267
+ * subscribers. Safe to call when already disconnected.
268
+ */
80
269
  disconnect(): void;
270
+ /**
271
+ * Fully tear down the client: {@link disconnect}, drop all notification
272
+ * subscribers, and reject every outstanding {@link waitForSequence} promise
273
+ * with a non-retryable {@link CrowdyRealtimeError}. Call this when disposing
274
+ * the SDK instance.
275
+ */
81
276
  close(): void;
277
+ /**
278
+ * Register a set of {@link UdpNotificationHandlers} and ensure the realtime
279
+ * connection is open, scoping the session to `appId`. The game-api requires
280
+ * an app id and rejects an app-agnostic subscription with a
281
+ * `RealtimeConnectionEvent` (`code: 'APP_ID_REQUIRED'`).
282
+ *
283
+ * Multiple handler sets can be registered at once; the returned function
284
+ * unregisters this one, and the socket closes automatically once the last
285
+ * subscriber unsubscribes.
286
+ *
287
+ * @param handlers - Callbacks for the notification types you care about.
288
+ * @param appId - The app to scope this realtime session to (decimal id;
289
+ * coerced to a string). Required.
290
+ * @returns An unsubscribe function that removes these handlers (and
291
+ * disconnects when none remain).
292
+ */
82
293
  subscribe(handlers: UdpNotificationHandlers, appId: string): () => void;
294
+ /**
295
+ * Return a promise that resolves when a notification carrying the given
296
+ * `sequenceNumber` arrives — the mechanism behind the `...AndWait` spatial
297
+ * sends. Resolves with the matching {@link SpatialNotification}, or rejects
298
+ * if that match is a `GenericErrorResponse` or the wait times out.
299
+ *
300
+ * @param sequenceNumber - The sequence number to wait for (as allocated by
301
+ * {@link SequenceAllocator} and stamped on the send).
302
+ * @param timeoutMs - How long to wait before rejecting, in milliseconds.
303
+ * Defaults to the configured {@link RealtimeConfig.waitTimeoutMs}.
304
+ * @returns The matching spatial notification.
305
+ * @throws {CrowdyRealtimeError} `UDP_SEQUENCE_TIMEOUT` (retryable) on timeout,
306
+ * or carrying the server `errorCode` when the match is a
307
+ * `GenericErrorResponse`.
308
+ */
83
309
  waitForSequence(sequenceNumber: number, timeoutMs?: number): Promise<SpatialNotification>;
84
310
  private ensureSubscription;
85
311
  private restart;
@@ -1 +1 @@
1
- {"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"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"}
package/dist/realtime.js CHANGED
@@ -3,7 +3,34 @@ import { createClient } from 'graphql-ws';
3
3
  import { silentLogger } from './logger.js';
4
4
  import { CrowdyRealtimeError } from './errors.js';
5
5
  import { UdpNotificationsDocument, } from './generated/graphql.js';
6
+ /**
7
+ * Manages the single WebSocket subscription to the game-api's
8
+ * `udpNotifications` stream — the realtime layer behind `client.udp` and
9
+ * `client.realtime`. It opens the socket lazily on the first {@link subscribe},
10
+ * authenticates with the shared session token, scopes the session to one
11
+ * `appId`, reconnects with jittered exponential backoff, re-reads the token and
12
+ * resubscribes on reconnect, fans each notification out to the registered
13
+ * {@link UdpNotificationHandlers}, and resolves `...AndWait` sends via
14
+ * {@link waitForSequence}.
15
+ *
16
+ * The connection lifecycle is observable through {@link status} /
17
+ * {@link onStatus} ({@link RealtimeStatus}). A realtime session is scoped to a
18
+ * single app, so run one client per app (sharing the same token store) for a
19
+ * player who is in multiple apps at once.
20
+ *
21
+ * You normally interact with this through `client.udp` / `client.realtime`
22
+ * rather than constructing it directly.
23
+ */
6
24
  export class RealtimeClient {
25
+ /**
26
+ * @param config - Reconnect/timeout/endpoint tuning; see
27
+ * {@link RealtimeConfig}.
28
+ * @param session - Shared session store. The client reads the Bearer token
29
+ * from it for the connection handshake and watches it for changes: clearing
30
+ * the token tears the connection down (emitting an `AUTH_CLEARED`
31
+ * {@link CrowdyRealtimeError}), while a token change made while connected
32
+ * forces a reconnect using the new token.
33
+ */
7
34
  constructor(config = {}, session) {
8
35
  this.session = session;
9
36
  this.client = null;
@@ -38,9 +65,21 @@ export class RealtimeClient {
38
65
  this.restart();
39
66
  });
40
67
  }
68
+ /**
69
+ * The current connection state.
70
+ *
71
+ * @returns The latest {@link RealtimeStatus}.
72
+ */
41
73
  status() {
42
74
  return this.statusValue;
43
75
  }
76
+ /**
77
+ * Subscribe to connection-state changes. The listener is invoked
78
+ * **immediately** with the current status, then again on every transition.
79
+ *
80
+ * @param listener - Called with each new {@link RealtimeStatus}.
81
+ * @returns An unsubscribe function that removes the listener.
82
+ */
44
83
  onStatus(listener) {
45
84
  this.statusListeners.add(listener);
46
85
  listener(this.statusValue);
@@ -48,10 +87,24 @@ export class RealtimeClient {
48
87
  this.statusListeners.delete(listener);
49
88
  };
50
89
  }
90
+ /**
91
+ * Mark the connection as desired and open the subscription if it isn't
92
+ * already open. You usually don't call this directly — {@link subscribe}
93
+ * calls it for you; use it (or `client.realtime.connect()`) only to pre-warm
94
+ * the socket.
95
+ *
96
+ * @throws {CrowdyRealtimeError} `AUTH_REQUIRED` if there is no session token.
97
+ */
51
98
  connect() {
52
99
  this.desired = true;
53
100
  this.ensureSubscription();
54
101
  }
102
+ /**
103
+ * Close the socket and stop wanting a connection. Outstanding
104
+ * {@link waitForSequence} promises are left intact (they will time out on
105
+ * their own); use {@link close} to also reject those and drop all
106
+ * subscribers. Safe to call when already disconnected.
107
+ */
55
108
  disconnect() {
56
109
  this.desired = false;
57
110
  this.release?.();
@@ -60,11 +113,33 @@ export class RealtimeClient {
60
113
  this.client = null;
61
114
  this.setStatus('disconnected');
62
115
  }
116
+ /**
117
+ * Fully tear down the client: {@link disconnect}, drop all notification
118
+ * subscribers, and reject every outstanding {@link waitForSequence} promise
119
+ * with a non-retryable {@link CrowdyRealtimeError}. Call this when disposing
120
+ * the SDK instance.
121
+ */
63
122
  close() {
64
123
  this.disconnect();
65
124
  this.subscribers.clear();
66
125
  this.rejectAllPending(new CrowdyRealtimeError('Realtime client closed', { retryable: false }));
67
126
  }
127
+ /**
128
+ * Register a set of {@link UdpNotificationHandlers} and ensure the realtime
129
+ * connection is open, scoping the session to `appId`. The game-api requires
130
+ * an app id and rejects an app-agnostic subscription with a
131
+ * `RealtimeConnectionEvent` (`code: 'APP_ID_REQUIRED'`).
132
+ *
133
+ * Multiple handler sets can be registered at once; the returned function
134
+ * unregisters this one, and the socket closes automatically once the last
135
+ * subscriber unsubscribes.
136
+ *
137
+ * @param handlers - Callbacks for the notification types you care about.
138
+ * @param appId - The app to scope this realtime session to (decimal id;
139
+ * coerced to a string). Required.
140
+ * @returns An unsubscribe function that removes these handlers (and
141
+ * disconnects when none remain).
142
+ */
68
143
  subscribe(handlers, appId) {
69
144
  // appId is required by the type; guard for JS callers so a missing value
70
145
  // is sent as "no app" (cleanly rejected by the game-api) rather than the
@@ -80,6 +155,21 @@ export class RealtimeClient {
80
155
  }
81
156
  };
82
157
  }
158
+ /**
159
+ * Return a promise that resolves when a notification carrying the given
160
+ * `sequenceNumber` arrives — the mechanism behind the `...AndWait` spatial
161
+ * sends. Resolves with the matching {@link SpatialNotification}, or rejects
162
+ * if that match is a `GenericErrorResponse` or the wait times out.
163
+ *
164
+ * @param sequenceNumber - The sequence number to wait for (as allocated by
165
+ * {@link SequenceAllocator} and stamped on the send).
166
+ * @param timeoutMs - How long to wait before rejecting, in milliseconds.
167
+ * Defaults to the configured {@link RealtimeConfig.waitTimeoutMs}.
168
+ * @returns The matching spatial notification.
169
+ * @throws {CrowdyRealtimeError} `UDP_SEQUENCE_TIMEOUT` (retryable) on timeout,
170
+ * or carrying the server `errorCode` when the match is a
171
+ * `GenericErrorResponse`.
172
+ */
83
173
  waitForSequence(sequenceNumber, timeoutMs = this.waitTimeoutMs) {
84
174
  return new Promise((resolve, reject) => {
85
175
  const timer = setTimeout(() => {
package/dist/session.d.ts CHANGED
@@ -1,27 +1,73 @@
1
+ /** Callback notified whenever the active token changes (`null` on sign-out). */
1
2
  export type SessionListener = (token: string | null) => void;
3
+ /**
4
+ * Pluggable persistence for the Bearer token. Implement this to back the
5
+ * session with whatever storage your runtime offers (cookies, secure storage,
6
+ * a database for SSR, etc.). All three methods may be sync or async.
7
+ *
8
+ * `BrowserLocalStorageTokenStore` is provided for browser apps.
9
+ */
2
10
  export interface TokenStore {
11
+ /** Return the persisted token, or `null`/`undefined` if none. */
3
12
  get(): string | null | Promise<string | null>;
13
+ /** Persist a token (called on login and token refresh). */
4
14
  set(token: string): void | Promise<void>;
15
+ /** Remove the persisted token (called on logout). */
5
16
  clear(): void | Promise<void>;
6
17
  }
18
+ /**
19
+ * {@link TokenStore} backed by the browser `localStorage`. No-ops gracefully
20
+ * when `localStorage` is unavailable (e.g. SSR), so it's safe to construct
21
+ * unconditionally.
22
+ */
7
23
  export declare class BrowserLocalStorageTokenStore implements TokenStore {
8
24
  private readonly key;
25
+ /** @param key - localStorage key under which the token is stored. */
9
26
  constructor(key?: string);
10
27
  get(): string | null;
11
28
  set(token: string): void;
12
29
  clear(): void;
13
30
  }
31
+ /**
32
+ * In-memory token holder with change notifications and optional persistence via
33
+ * a {@link TokenStore}. Setting the token fans out to every {@link onChange}
34
+ * listener, which is how the HTTP client and the WebSocket stay in lock-step
35
+ * (their auth can never drift).
36
+ */
14
37
  export declare class SessionStore {
15
38
  private readonly tokenStore?;
16
39
  private token;
17
40
  private readonly listeners;
41
+ /** @param tokenStore - Optional persistence; when omitted the token is memory-only. */
18
42
  constructor(tokenStore?: TokenStore | undefined);
43
+ /**
44
+ * Load the token from the {@link TokenStore} into memory (without re-persisting)
45
+ * and notify listeners. Call once on startup to resume a saved session.
46
+ *
47
+ * @returns The restored token, or `null` if none was stored.
48
+ */
19
49
  restore(): Promise<string | null>;
50
+ /** The current in-memory token, or `null` if there's no active session. */
20
51
  getToken(): string | null;
52
+ /**
53
+ * Set (or clear, with `null`) the active token. Persists to the
54
+ * {@link TokenStore} unless `options.persist` is `false`, then notifies all
55
+ * listeners. A no-op if the token is unchanged.
56
+ *
57
+ * @param token - The new Bearer token, or `null` to sign out.
58
+ * @param options - `persist: false` updates memory + listeners only.
59
+ */
21
60
  setToken(token: string | null, options?: {
22
61
  persist?: boolean;
23
62
  }): void;
63
+ /** Clear the active token (equivalent to `setToken(null)`). */
24
64
  clear(): void;
65
+ /**
66
+ * Subscribe to token changes. The listener fires immediately with the current
67
+ * token, then on every change.
68
+ *
69
+ * @returns An unsubscribe function.
70
+ */
25
71
  onChange(listener: SessionListener): () => void;
26
72
  }
27
73
  //# sourceMappingURL=session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;AAE7D,MAAM,WAAW,UAAU;IACzB,GAAG,IAAI,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,KAAK,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,qBAAa,6BAA8B,YAAW,UAAU;IAClD,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,SAAmB;IAEnD,GAAG,IAAI,MAAM,GAAG,IAAI;IAKpB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKxB,KAAK,IAAI,IAAI;CAId;AAED,qBAAa,YAAY;IAIX,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAHxC,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8B;gBAE3B,UAAU,CAAC,EAAE,UAAU,YAAA;IAE9C,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAMvC,QAAQ,IAAI,MAAM,GAAG,IAAI;IAIzB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,GAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,IAAI;IAiBzE,KAAK,IAAI,IAAI;IAIb,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,IAAI;CAOhD"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;AAE7D;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,iEAAiE;IACjE,GAAG,IAAI,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,2DAA2D;IAC3D,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,qDAAqD;IACrD,KAAK,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED;;;;GAIG;AACH,qBAAa,6BAA8B,YAAW,UAAU;IAElD,OAAO,CAAC,QAAQ,CAAC,GAAG;IADhC,qEAAqE;gBACxC,GAAG,SAAmB;IAEnD,GAAG,IAAI,MAAM,GAAG,IAAI;IAKpB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKxB,KAAK,IAAI,IAAI;CAId;AAED;;;;;GAKG;AACH,qBAAa,YAAY;IAKX,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAJxC,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8B;IAExD,uFAAuF;gBAC1D,UAAU,CAAC,EAAE,UAAU,YAAA;IAEpD;;;;;OAKG;IACG,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAMvC,2EAA2E;IAC3E,QAAQ,IAAI,MAAM,GAAG,IAAI;IAIzB;;;;;;;OAOG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,GAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,IAAI;IAiBzE,+DAA+D;IAC/D,KAAK,IAAI,IAAI;IAIb;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,IAAI;CAOhD"}
package/dist/session.js CHANGED
@@ -1,4 +1,10 @@
1
+ /**
2
+ * {@link TokenStore} backed by the browser `localStorage`. No-ops gracefully
3
+ * when `localStorage` is unavailable (e.g. SSR), so it's safe to construct
4
+ * unconditionally.
5
+ */
1
6
  export class BrowserLocalStorageTokenStore {
7
+ /** @param key - localStorage key under which the token is stored. */
2
8
  constructor(key = 'crowdyjs:token') {
3
9
  this.key = key;
4
10
  }
@@ -18,20 +24,42 @@ export class BrowserLocalStorageTokenStore {
18
24
  localStorage.removeItem(this.key);
19
25
  }
20
26
  }
27
+ /**
28
+ * In-memory token holder with change notifications and optional persistence via
29
+ * a {@link TokenStore}. Setting the token fans out to every {@link onChange}
30
+ * listener, which is how the HTTP client and the WebSocket stay in lock-step
31
+ * (their auth can never drift).
32
+ */
21
33
  export class SessionStore {
34
+ /** @param tokenStore - Optional persistence; when omitted the token is memory-only. */
22
35
  constructor(tokenStore) {
23
36
  this.tokenStore = tokenStore;
24
37
  this.token = null;
25
38
  this.listeners = new Set();
26
39
  }
40
+ /**
41
+ * Load the token from the {@link TokenStore} into memory (without re-persisting)
42
+ * and notify listeners. Call once on startup to resume a saved session.
43
+ *
44
+ * @returns The restored token, or `null` if none was stored.
45
+ */
27
46
  async restore() {
28
47
  const token = (await this.tokenStore?.get()) ?? null;
29
48
  this.setToken(token, { persist: false });
30
49
  return token;
31
50
  }
51
+ /** The current in-memory token, or `null` if there's no active session. */
32
52
  getToken() {
33
53
  return this.token;
34
54
  }
55
+ /**
56
+ * Set (or clear, with `null`) the active token. Persists to the
57
+ * {@link TokenStore} unless `options.persist` is `false`, then notifies all
58
+ * listeners. A no-op if the token is unchanged.
59
+ *
60
+ * @param token - The new Bearer token, or `null` to sign out.
61
+ * @param options - `persist: false` updates memory + listeners only.
62
+ */
35
63
  setToken(token, options = {}) {
36
64
  if (token === this.token)
37
65
  return;
@@ -48,9 +76,16 @@ export class SessionStore {
48
76
  listener(token);
49
77
  }
50
78
  }
79
+ /** Clear the active token (equivalent to `setToken(null)`). */
51
80
  clear() {
52
81
  this.setToken(null);
53
82
  }
83
+ /**
84
+ * Subscribe to token changes. The listener fires immediately with the current
85
+ * token, then on every change.
86
+ *
87
+ * @returns An unsubscribe function.
88
+ */
54
89
  onChange(listener) {
55
90
  this.listeners.add(listener);
56
91
  listener(this.token);