@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.
- package/MIGRATION.md +64 -0
- package/README.md +19 -0
- package/dist/client.d.ts +98 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +74 -5
- package/dist/crowdy-client.d.ts +31 -0
- package/dist/crowdy-client.d.ts.map +1 -1
- package/dist/crowdy-client.js +8 -0
- package/dist/domains/actors.d.ts +88 -5
- package/dist/domains/actors.d.ts.map +1 -1
- package/dist/domains/actors.js +89 -6
- package/dist/domains/apps.d.ts +95 -41
- package/dist/domains/apps.d.ts.map +1 -1
- package/dist/domains/apps.js +80 -33
- package/dist/domains/auth.d.ts +139 -19
- package/dist/domains/auth.d.ts.map +1 -1
- package/dist/domains/auth.js +137 -17
- package/dist/domains/channels.d.ts +264 -5
- package/dist/domains/channels.d.ts.map +1 -1
- package/dist/domains/channels.js +264 -5
- package/dist/domains/chunks.d.ts +116 -3
- package/dist/domains/chunks.d.ts.map +1 -1
- package/dist/domains/chunks.js +116 -3
- package/dist/domains/gameModel.d.ts +412 -6
- package/dist/domains/gameModel.d.ts.map +1 -1
- package/dist/domains/gameModel.js +412 -6
- package/dist/domains/platform.d.ts +36 -20
- package/dist/domains/platform.d.ts.map +1 -1
- package/dist/domains/platform.js +29 -18
- package/dist/domains/serverStatus.d.ts +74 -6
- package/dist/domains/serverStatus.d.ts.map +1 -1
- package/dist/domains/serverStatus.js +74 -6
- package/dist/domains/state.d.ts +50 -2
- package/dist/domains/state.d.ts.map +1 -1
- package/dist/domains/state.js +50 -2
- package/dist/domains/teams.d.ts +265 -7
- package/dist/domains/teams.d.ts.map +1 -1
- package/dist/domains/teams.js +267 -9
- package/dist/domains/teleport.d.ts +30 -2
- package/dist/domains/teleport.d.ts.map +1 -1
- package/dist/domains/teleport.js +30 -2
- package/dist/domains/udp.d.ts +341 -5
- package/dist/domains/udp.d.ts.map +1 -1
- package/dist/domains/udp.js +341 -5
- package/dist/domains/users.d.ts +42 -11
- package/dist/domains/users.d.ts.map +1 -1
- package/dist/domains/users.js +41 -10
- package/dist/domains/voxels.d.ts +107 -2
- package/dist/domains/voxels.d.ts.map +1 -1
- package/dist/domains/voxels.js +107 -2
- package/dist/errors.d.ts +116 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +100 -0
- package/dist/generated/graphql.d.ts +1787 -110
- package/dist/generated/graphql.d.ts.map +1 -1
- package/dist/generated/graphql.js +75 -9
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/realtime.d.ts +226 -0
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +90 -0
- package/dist/session.d.ts +46 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +35 -0
- package/dist/types.d.ts +429 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +53 -0
- package/dist/utils.d.ts +86 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +86 -0
- package/dist/world.d.ts +192 -0
- package/dist/world.d.ts.map +1 -1
- package/dist/world.js +170 -0
- package/package.json +1 -1
package/dist/realtime.d.ts
CHANGED
|
@@ -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;
|
package/dist/realtime.d.ts.map
CHANGED
|
@@ -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
|
package/dist/session.d.ts.map
CHANGED
|
@@ -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;
|
|
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);
|