@crowdedkingdomstudios/crowdyjs 5.2.0 → 5.3.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 (74) hide show
  1. package/MIGRATION.md +22 -0
  2. package/dist/client.d.ts +98 -5
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +74 -5
  5. package/dist/crowdy-client.d.ts +31 -0
  6. package/dist/crowdy-client.d.ts.map +1 -1
  7. package/dist/crowdy-client.js +8 -0
  8. package/dist/domains/actors.d.ts +87 -4
  9. package/dist/domains/actors.d.ts.map +1 -1
  10. package/dist/domains/actors.js +87 -4
  11. package/dist/domains/apps.d.ts +95 -41
  12. package/dist/domains/apps.d.ts.map +1 -1
  13. package/dist/domains/apps.js +80 -33
  14. package/dist/domains/auth.d.ts +139 -19
  15. package/dist/domains/auth.d.ts.map +1 -1
  16. package/dist/domains/auth.js +137 -17
  17. package/dist/domains/channels.d.ts +264 -5
  18. package/dist/domains/channels.d.ts.map +1 -1
  19. package/dist/domains/channels.js +264 -5
  20. package/dist/domains/chunks.d.ts +116 -3
  21. package/dist/domains/chunks.d.ts.map +1 -1
  22. package/dist/domains/chunks.js +116 -3
  23. package/dist/domains/gameModel.d.ts +412 -6
  24. package/dist/domains/gameModel.d.ts.map +1 -1
  25. package/dist/domains/gameModel.js +412 -6
  26. package/dist/domains/platform.d.ts +36 -20
  27. package/dist/domains/platform.d.ts.map +1 -1
  28. package/dist/domains/platform.js +29 -18
  29. package/dist/domains/serverStatus.d.ts +74 -6
  30. package/dist/domains/serverStatus.d.ts.map +1 -1
  31. package/dist/domains/serverStatus.js +74 -6
  32. package/dist/domains/state.d.ts +50 -2
  33. package/dist/domains/state.d.ts.map +1 -1
  34. package/dist/domains/state.js +50 -2
  35. package/dist/domains/teams.d.ts +263 -5
  36. package/dist/domains/teams.d.ts.map +1 -1
  37. package/dist/domains/teams.js +263 -5
  38. package/dist/domains/teleport.d.ts +30 -2
  39. package/dist/domains/teleport.d.ts.map +1 -1
  40. package/dist/domains/teleport.js +30 -2
  41. package/dist/domains/udp.d.ts +341 -5
  42. package/dist/domains/udp.d.ts.map +1 -1
  43. package/dist/domains/udp.js +341 -5
  44. package/dist/domains/users.d.ts +42 -11
  45. package/dist/domains/users.d.ts.map +1 -1
  46. package/dist/domains/users.js +41 -10
  47. package/dist/domains/voxels.d.ts +107 -2
  48. package/dist/domains/voxels.d.ts.map +1 -1
  49. package/dist/domains/voxels.js +107 -2
  50. package/dist/errors.d.ts +116 -0
  51. package/dist/errors.d.ts.map +1 -1
  52. package/dist/errors.js +100 -0
  53. package/dist/generated/graphql.d.ts +5 -1
  54. package/dist/generated/graphql.d.ts.map +1 -1
  55. package/dist/generated/graphql.js +5 -1
  56. package/dist/index.d.ts +2 -1
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +2 -1
  59. package/dist/realtime.d.ts +226 -0
  60. package/dist/realtime.d.ts.map +1 -1
  61. package/dist/realtime.js +90 -0
  62. package/dist/session.d.ts +46 -0
  63. package/dist/session.d.ts.map +1 -1
  64. package/dist/session.js +35 -0
  65. package/dist/types.d.ts +429 -0
  66. package/dist/types.d.ts.map +1 -1
  67. package/dist/types.js +53 -0
  68. package/dist/utils.d.ts +86 -0
  69. package/dist/utils.d.ts.map +1 -1
  70. package/dist/utils.js +86 -0
  71. package/dist/world.d.ts +192 -0
  72. package/dist/world.d.ts.map +1 -1
  73. package/dist/world.js +170 -0
  74. package/package.json +1 -1
@@ -1,11 +1,37 @@
1
1
  import { ConnectUdpProxyDocument, DisconnectUdpProxyDocument, UdpProxyConnectionStatusDocument, SendActorUpdateDocument, SendVoxelUpdateDocument, SendAudioPacketDocument, SendTextPacketDocument, SendClientEventDocument, SendSingleActorMessageDocument, SendChannelMessageDocument, } from '../generated/graphql.js';
2
2
  import { SequenceAllocator } from '../utils.js';
3
3
  /**
4
- * UDP proxy access for browser-style clients that can't open raw UDP
5
- * sockets. Exposed as `client.udp`. All send mutations go over the same
6
- * GraphQL HTTP endpoint and are forwarded to the assigned game server by
7
- * the API's UDP proxy module. Notifications come back over a single shared
8
- * WebSocket subscription managed by `SubscriptionManager`.
4
+ * UDP proxy access for browser-style clients that can't open raw UDP sockets.
5
+ * Exposed as `client.udp`; for game loops prefer the ergonomic, app-scoped
6
+ * facade `client.world(appId)`, which passes `appId` for you and tracks the
7
+ * current chunk so you don't repeat it on every send.
8
+ *
9
+ * All send mutations go over the **game-api** GraphQL HTTP endpoint and are
10
+ * forwarded to the assigned game server by the API's UDP proxy module;
11
+ * notifications come back over a single shared `graphql-transport-ws`
12
+ * WebSocket (same game-api endpoint) managed by `SubscriptionManager` — the
13
+ * first subscriber opens it and the last to unsubscribe closes it. Every call
14
+ * requires an authenticated session carrying a **bearer game token** (minted
15
+ * by the management-api, set via `client.auth.login()` or `client.setToken()`);
16
+ * a UDP proxy session is opened lazily on the first send or subscribe if you
17
+ * didn't call {@link connect} first.
18
+ *
19
+ * Each packet type comes in two shapes:
20
+ * - Plain `send*` — fire-and-forget. Resolves to a `boolean` ack that the proxy
21
+ * *accepted the datagram for sending*; it does **not** confirm the world
22
+ * applied it or that it was delivered.
23
+ * - `send*AndWait` — allocates a `sequenceNumber`, then resolves with the
24
+ * server's matching echo (a {@link SpatialNotification}) correlated by that
25
+ * sequence, or rejects on timeout. Only actor and voxel updates are echoed
26
+ * back to the sender (`ActorUpdateResponse` / `VoxelUpdateResponse`); audio,
27
+ * text, and event sends are not.
28
+ *
29
+ * Spatial caveats: a `sequenceNumber` is a uint8 (0-255) used for
30
+ * **correlation only** — not an idempotency key, and the server does not dedupe
31
+ * replays. The **first** spatial message to a brand-new chunk may be dropped
32
+ * server-side while grid permissions load, so clients should re-send (the
33
+ * two-client tests register twice for this reason). To receive any
34
+ * `...AndWait` echo or notification you must have an active {@link subscribe}.
9
35
  */
10
36
  export class UdpAPI {
11
37
  constructor(gql, subs) {
@@ -13,62 +39,317 @@ export class UdpAPI {
13
39
  this.subs = subs;
14
40
  this.sequences = new SequenceAllocator();
15
41
  }
42
+ /**
43
+ * Open (or re-fetch) the UDP proxy session for this game token. Idempotent:
44
+ * if a session is already open it returns the existing status; on first open
45
+ * it binds a socket and selects the game server with the fewest clients.
46
+ * Calling this is optional — any `send*` mutation or {@link subscribe} opens a
47
+ * session lazily — but use it to pre-warm the socket or surface auth/
48
+ * connectivity problems eagerly. To force a fresh socket, call
49
+ * {@link disconnect} first.
50
+ *
51
+ * @returns The {@link UdpProxyConnectionStatus}: `connected`, plus (when
52
+ * connected) `serverIp6`, `serverClientPort`, and `lastMessageTime`.
53
+ * @throws {CrowdyGraphQLError} `UNAUTHENTICATED` if the request carries no
54
+ * valid bearer game token.
55
+ */
16
56
  async connect() {
17
57
  const data = await this.gql.request(ConnectUdpProxyDocument, undefined);
18
58
  return data.connectUdpProxy;
19
59
  }
60
+ /**
61
+ * Close the UDP proxy session and socket for this game token. Note that
62
+ * unsubscribing from notifications does **not** disconnect — call this (or
63
+ * rely on the server's inactivity timeout) to release the session, e.g. to
64
+ * force a fresh socket on the next {@link connect} or send.
65
+ *
66
+ * @returns `true` once the session has been closed.
67
+ * @throws {CrowdyGraphQLError} on auth failures.
68
+ */
20
69
  async disconnect() {
21
70
  const data = await this.gql.request(DisconnectUdpProxyDocument, undefined);
22
71
  return data.disconnectUdpProxy;
23
72
  }
73
+ /**
74
+ * Read the current UDP proxy session status **without** opening one. With no
75
+ * game token it simply reports `connected: false` rather than throwing.
76
+ * Inspect `lastMessageTime` to gauge connection health.
77
+ *
78
+ * @returns The {@link UdpProxyConnectionStatus} (`connected`, plus when
79
+ * connected `serverIp6`, `serverClientPort`, and `lastMessageTime`).
80
+ */
24
81
  async connectionStatus() {
25
82
  const data = await this.gql.request(UdpProxyConnectionStatusDocument, undefined);
26
83
  return data.udpProxyConnectionStatus;
27
84
  }
85
+ /**
86
+ * Send an actor (player/NPC) state update for spatial replication to nearby
87
+ * chunks. Fire-and-forget; opens a UDP proxy session automatically if none
88
+ * exists. The applied echo (`ActorUpdateResponse`) and any failure
89
+ * (`GenericErrorResponse`) arrive asynchronously on {@link subscribe},
90
+ * correlated by `sequenceNumber`; use {@link sendActorUpdateAndWait} to await
91
+ * that echo inline.
92
+ *
93
+ * @param input - {@link ActorUpdateRequestInput}:
94
+ * - `appId` — owning app id (`BigInt` as a decimal string).
95
+ * - `chunk` — `{ x, y, z }` chunk address; each axis is a signed int64
96
+ * decimal string. A chunk is a 16x16x16 voxel cube.
97
+ * - `uuid` — the actor id: exactly 32 ASCII characters (the UDP-wire id),
98
+ * **not** an RFC-4122 UUID.
99
+ * - `state` — actor state blob, base64-encoded; may be `''` for a
100
+ * registration-only update.
101
+ * - `distance` — replication radius in chunk units, 0-8 (clamped); defaults
102
+ * to 8 for actor updates.
103
+ * - `decayRate` — replication decay algorithm: 0 none, 1 exponential,
104
+ * 2 linear 50%, 3 linear 25%, 4 linear 10%, 5 linear 5%; defaults to 1
105
+ * (exponential) for actor updates.
106
+ * - `sequenceNumber` — optional uint8 (0-255) correlation id; not an
107
+ * idempotency key.
108
+ * @returns `true` when the datagram was accepted for sending to the game
109
+ * server — **not** confirmation that the world applied it.
110
+ * @throws {CrowdyGraphQLError} e.g. `UNAUTHENTICATED` without a valid game
111
+ * token, or `BAD_USER_INPUT` for a malformed packet.
112
+ */
28
113
  async sendActorUpdate(input) {
29
114
  const data = await this.gql.request(SendActorUpdateDocument, { input });
30
115
  return data.sendActorUpdate;
31
116
  }
117
+ /**
118
+ * Send an actor update and wait for the server's applied echo. Allocates a
119
+ * `sequenceNumber` when `input.sequenceNumber` is omitted, registers the wait
120
+ * *before* sending, then resolves with the matching `ActorUpdateResponse`.
121
+ * Requires an active {@link subscribe} so the echo can be delivered over the
122
+ * shared WebSocket.
123
+ *
124
+ * @param input - {@link ActorUpdateRequestInput} (see {@link sendActorUpdate}
125
+ * for field units/encoding). Omit `sequenceNumber` to let the SDK allocate
126
+ * one.
127
+ * @param options - `timeoutMs`: how long to wait for the echo, in
128
+ * milliseconds; defaults to the client's realtime `waitTimeoutMs` (5000).
129
+ * @returns The correlated {@link SpatialNotification} (the `ActorUpdateResponse`
130
+ * echo).
131
+ * @throws {CrowdyGraphQLError} if the underlying send is rejected.
132
+ * @throws {CrowdyTimeoutError} if the HTTP send leg exceeds the client's
133
+ * request timeout.
134
+ * @throws {CrowdyRealtimeError} if no echo arrives within `timeoutMs`
135
+ * (`code === 'UDP_SEQUENCE_TIMEOUT'`, retryable), or the server returns a
136
+ * matching `GenericErrorResponse` (its `code` is the server's `errorCode`).
137
+ * @example
138
+ * ```ts
139
+ * const unsubscribe = client.udp.subscribe({}, appId); // echoes need an open WS
140
+ * const echo = await client.udp.sendActorUpdateAndWait({
141
+ * appId,
142
+ * chunk: { x: '0', y: '0', z: '0' },
143
+ * uuid,
144
+ * state: 'AA==',
145
+ * distance: 8,
146
+ * });
147
+ * console.log(echo.__typename, echo.sequenceNumber);
148
+ * ```
149
+ */
32
150
  async sendActorUpdateAndWait(input, options = {}) {
33
151
  const request = this.withSequence(input);
34
152
  const wait = this.subs.waitForSequence(request.sequenceNumber, options.timeoutMs);
35
153
  await this.sendActorUpdate(request);
36
154
  return wait;
37
155
  }
156
+ /**
157
+ * Send a single voxel (block) update for spatial replication to nearby
158
+ * chunks. Fire-and-forget; opens a UDP proxy session automatically. The
159
+ * applied echo (`VoxelUpdateResponse`) and failures (`GenericErrorResponse`)
160
+ * arrive asynchronously on {@link subscribe}; use
161
+ * {@link sendVoxelUpdateAndWait} to await the echo inline.
162
+ *
163
+ * @param input - {@link VoxelUpdateRequestInput}:
164
+ * - `appId` — owning app id (`BigInt` as a decimal string).
165
+ * - `chunk` — `{ x, y, z }` chunk address (signed int64 decimal strings); a
166
+ * chunk is a 16x16x16 voxel cube.
167
+ * - `uuid` — 32-ASCII-character actor/source id (not RFC-4122).
168
+ * - `voxel` — `{ x, y, z }` voxel coordinates within the chunk; each is an
169
+ * int16 (-32768 to 32767).
170
+ * - `voxelType` — the new voxel type id.
171
+ * - `voxelState` — voxel state blob, base64-encoded.
172
+ * - `distance` — replication radius in chunk units, 0-8 (clamped); defaults
173
+ * to 8 for voxel updates.
174
+ * - `decayRate` — decay algorithm 0-5 (see {@link sendActorUpdate});
175
+ * defaults to 0 (none) for voxel updates.
176
+ * - `sequenceNumber` — optional uint8 (0-255) correlation id.
177
+ * @returns `true` when accepted for sending — **not** confirmation the world
178
+ * applied the change.
179
+ * @throws {CrowdyGraphQLError} on auth/validation failures.
180
+ */
38
181
  async sendVoxelUpdate(input) {
39
182
  const data = await this.gql.request(SendVoxelUpdateDocument, { input });
40
183
  return data.sendVoxelUpdate;
41
184
  }
185
+ /**
186
+ * Send a voxel update and wait for the server's applied `VoxelUpdateResponse`
187
+ * echo. Allocates a `sequenceNumber` when omitted and requires an active
188
+ * {@link subscribe}.
189
+ *
190
+ * @param input - {@link VoxelUpdateRequestInput} (see {@link sendVoxelUpdate}
191
+ * for field units/encoding).
192
+ * @param options - `timeoutMs`: echo wait in milliseconds; defaults to the
193
+ * client's realtime `waitTimeoutMs` (5000).
194
+ * @returns The correlated {@link SpatialNotification} (`VoxelUpdateResponse`).
195
+ * @throws {CrowdyGraphQLError} if the underlying send is rejected.
196
+ * @throws {CrowdyTimeoutError} if the HTTP send leg times out.
197
+ * @throws {CrowdyRealtimeError} on echo timeout
198
+ * (`code === 'UDP_SEQUENCE_TIMEOUT'`) or a matching `GenericErrorResponse`
199
+ * (its `code` is the server's `errorCode`).
200
+ */
42
201
  async sendVoxelUpdateAndWait(input, options = {}) {
43
202
  const request = this.withSequence(input);
44
203
  const wait = this.subs.waitForSequence(request.sequenceNumber, options.timeoutMs);
45
204
  await this.sendVoxelUpdate(request);
46
205
  return wait;
47
206
  }
207
+ /**
208
+ * Send a spatial voice/audio packet, fanned out to nearby actors as a
209
+ * `ClientAudioNotification`. Fire-and-forget; opens a UDP proxy session
210
+ * automatically. Voice may be gated by a runtime grid permission
211
+ * (`use_voice_chat`) for the region — if the caller lacks it the game server
212
+ * replies asynchronously with a `GenericErrorResponse` (`errorCode`
213
+ * `UNAUTHORIZED`) on {@link subscribe}. The sender receives no success echo.
214
+ *
215
+ * @param input - {@link ClientAudioPacketInput}:
216
+ * - `appId` — owning app id (`BigInt` as a decimal string).
217
+ * - `chunk` — `{ x, y, z }` chunk address (signed int64 decimal strings).
218
+ * - `uuid` — 32-ASCII-character source id (typically the player; not
219
+ * RFC-4122).
220
+ * - `audioData` — compressed audio, base64-encoded.
221
+ * - `distance` — replication radius in chunk units, 0-8 (clamped); defaults
222
+ * to 1 for audio packets.
223
+ * - `decayRate` — decay algorithm 0-5 (see {@link sendActorUpdate});
224
+ * defaults to 0 (none) for audio packets.
225
+ * - `sequenceNumber` — optional uint8 (0-255) correlation id.
226
+ * @returns `true` when accepted for sending — **not** confirmation of
227
+ * delivery.
228
+ * @throws {CrowdyGraphQLError} on auth/validation failures.
229
+ */
48
230
  async sendAudioPacket(input) {
49
231
  const data = await this.gql.request(SendAudioPacketDocument, { input });
50
232
  return data.sendAudioPacket;
51
233
  }
234
+ /**
235
+ * Send a voice/audio packet and wait for a response correlated by
236
+ * `sequenceNumber`. **Note:** the server does not echo audio packets back to
237
+ * the sender (only `GenericErrorResponse` failures are correlated), so on
238
+ * success there is usually nothing to resolve and this rejects on timeout —
239
+ * prefer the fire-and-forget {@link sendAudioPacket} unless you specifically
240
+ * want to surface a send error inline. Requires an active {@link subscribe}.
241
+ *
242
+ * @param input - {@link ClientAudioPacketInput} (see {@link sendAudioPacket}).
243
+ * @param options - `timeoutMs`: wait in milliseconds; defaults to the client's
244
+ * realtime `waitTimeoutMs` (5000).
245
+ * @returns The correlated {@link SpatialNotification}, if one is delivered.
246
+ * @throws {CrowdyGraphQLError} if the underlying send is rejected.
247
+ * @throws {CrowdyTimeoutError} if the HTTP send leg times out.
248
+ * @throws {CrowdyRealtimeError} on timeout (`code === 'UDP_SEQUENCE_TIMEOUT'`)
249
+ * or a matching `GenericErrorResponse` (its `code` is the server's
250
+ * `errorCode`, e.g. `UNAUTHORIZED` for a missing voice permission).
251
+ */
52
252
  async sendAudioPacketAndWait(input, options = {}) {
53
253
  const request = this.withSequence(input);
54
254
  const wait = this.subs.waitForSequence(request.sequenceNumber, options.timeoutMs);
55
255
  await this.sendAudioPacket(request);
56
256
  return wait;
57
257
  }
258
+ /**
259
+ * Send a spatial text/chat packet, fanned out to nearby actors as a
260
+ * `ClientTextNotification`. Fire-and-forget; opens a UDP proxy session
261
+ * automatically. The sender receives no success echo; failures arrive
262
+ * asynchronously as `GenericErrorResponse` on {@link subscribe}.
263
+ *
264
+ * @param input - {@link ClientTextPacketInput}:
265
+ * - `appId` — owning app id (`BigInt` as a decimal string).
266
+ * - `chunk` — `{ x, y, z }` chunk address (signed int64 decimal strings).
267
+ * - `uuid` — 32-ASCII-character source id (not RFC-4122).
268
+ * - `text` — message content, UTF-8 (sent as plain text, not base64).
269
+ * - `distance` — replication radius in chunk units, 0-8 (clamped); defaults
270
+ * to 8 for text packets.
271
+ * - `decayRate` — decay algorithm 0-5 (see {@link sendActorUpdate});
272
+ * defaults to 0 (none) for text packets.
273
+ * - `sequenceNumber` — optional uint8 (0-255) correlation id.
274
+ * @returns `true` when accepted for sending — **not** confirmation of
275
+ * delivery.
276
+ * @throws {CrowdyGraphQLError} on auth/validation failures.
277
+ */
58
278
  async sendTextPacket(input) {
59
279
  const data = await this.gql.request(SendTextPacketDocument, { input });
60
280
  return data.sendTextPacket;
61
281
  }
282
+ /**
283
+ * Send a text/chat packet and wait for a response correlated by
284
+ * `sequenceNumber`. **Note:** the server does not echo text packets back to
285
+ * the sender (only `GenericErrorResponse` failures are correlated), so on
286
+ * success there is usually nothing to resolve and this rejects on timeout —
287
+ * prefer the fire-and-forget {@link sendTextPacket} unless you specifically
288
+ * want to surface a send error inline. Requires an active {@link subscribe}.
289
+ *
290
+ * @param input - {@link ClientTextPacketInput} (see {@link sendTextPacket}).
291
+ * @param options - `timeoutMs`: wait in milliseconds; defaults to the client's
292
+ * realtime `waitTimeoutMs` (5000).
293
+ * @returns The correlated {@link SpatialNotification}, if one is delivered.
294
+ * @throws {CrowdyGraphQLError} if the underlying send is rejected.
295
+ * @throws {CrowdyTimeoutError} if the HTTP send leg times out.
296
+ * @throws {CrowdyRealtimeError} on timeout (`code === 'UDP_SEQUENCE_TIMEOUT'`)
297
+ * or a matching `GenericErrorResponse` (its `code` is the server's
298
+ * `errorCode`).
299
+ */
62
300
  async sendTextPacketAndWait(input, options = {}) {
63
301
  const request = this.withSequence(input);
64
302
  const wait = this.subs.waitForSequence(request.sequenceNumber, options.timeoutMs);
65
303
  await this.sendTextPacket(request);
66
304
  return wait;
67
305
  }
306
+ /**
307
+ * Send a custom, app-defined client event for spatial replication to nearby
308
+ * chunks; nearby actors receive it as a `ClientEventNotification`.
309
+ * Fire-and-forget; opens a UDP proxy session automatically. The sender
310
+ * receives no success echo; failures arrive asynchronously as
311
+ * `GenericErrorResponse` on {@link subscribe}.
312
+ *
313
+ * @param input - {@link ClientEventNotificationInput}:
314
+ * - `appId` — owning app id (`BigInt` as a decimal string).
315
+ * - `chunk` — `{ x, y, z }` chunk address (signed int64 decimal strings).
316
+ * - `uuid` — 32-ASCII-character id of the object controlling the event
317
+ * (not RFC-4122).
318
+ * - `eventType` — app-defined event id, a uint16 (0-65535).
319
+ * - `state` — event state blob, base64-encoded; format defined by the event
320
+ * type.
321
+ * - `distance` — replication radius in chunk units, 0-8 (clamped); defaults
322
+ * to 8 for events.
323
+ * - `decayRate` — decay algorithm 0-5 (see {@link sendActorUpdate});
324
+ * defaults to 0 (none) for events.
325
+ * - `sequenceNumber` — optional uint8 (0-255) correlation id.
326
+ * @returns `true` when accepted for sending — **not** confirmation the world
327
+ * processed it.
328
+ * @throws {CrowdyGraphQLError} on auth/validation failures.
329
+ */
68
330
  async sendClientEvent(input) {
69
331
  const data = await this.gql.request(SendClientEventDocument, { input });
70
332
  return data.sendClientEvent;
71
333
  }
334
+ /**
335
+ * Send a client event and wait for a response correlated by `sequenceNumber`.
336
+ * **Note:** the server does not echo events back to the sender (only
337
+ * `GenericErrorResponse` failures are correlated), so on success there is
338
+ * usually nothing to resolve and this rejects on timeout — prefer the
339
+ * fire-and-forget {@link sendClientEvent} unless you specifically want to
340
+ * surface a send error inline. Requires an active {@link subscribe}.
341
+ *
342
+ * @param input - {@link ClientEventNotificationInput} (see
343
+ * {@link sendClientEvent}).
344
+ * @param options - `timeoutMs`: wait in milliseconds; defaults to the client's
345
+ * realtime `waitTimeoutMs` (5000).
346
+ * @returns The correlated {@link SpatialNotification}, if one is delivered.
347
+ * @throws {CrowdyGraphQLError} if the underlying send is rejected.
348
+ * @throws {CrowdyTimeoutError} if the HTTP send leg times out.
349
+ * @throws {CrowdyRealtimeError} on timeout (`code === 'UDP_SEQUENCE_TIMEOUT'`)
350
+ * or a matching `GenericErrorResponse` (its `code` is the server's
351
+ * `errorCode`).
352
+ */
72
353
  async sendClientEventAndWait(input, options = {}) {
73
354
  const request = this.withSequence(input);
74
355
  const wait = this.subs.waitForSequence(request.sequenceNumber, options.timeoutMs);
@@ -81,6 +362,20 @@ export class UdpAPI {
81
362
  * Fire-and-forget: the sender receives no echo, so there is no
82
363
  * `sendSingleActorMessageAndWait` variant. The target receives a
83
364
  * `SingleActorMessageNotification` on its `udpNotifications` subscription.
365
+ *
366
+ * @param input - {@link SingleActorMessageInput}:
367
+ * - `appId` — the destination actor's app id (`BigInt` as a decimal string).
368
+ * - `chunk` — the **destination** actor's current `{ x, y, z }` chunk
369
+ * (signed int64 decimal strings); the sender must know it.
370
+ * - `targetUuid` — the destination actor's id: exactly 32 ASCII characters
371
+ * (not RFC-4122).
372
+ * - `payload` — message body, base64-encoded; opaque to the server (embed
373
+ * the sender identity yourself if needed).
374
+ * - `sequenceNumber` — optional uint8 (0-255) correlation id (used only to
375
+ * correlate a `GenericErrorResponse`).
376
+ * @returns `true` when accepted for sending — **not** confirmation of
377
+ * delivery.
378
+ * @throws {CrowdyGraphQLError} on auth/validation failures.
84
379
  */
85
380
  async sendSingleActorMessage(input) {
86
381
  const data = await this.gql.request(SendSingleActorMessageDocument, {
@@ -94,6 +389,19 @@ export class UdpAPI {
94
389
  * `udpNotifications` subscription. Requires the channel `send_messages`
95
390
  * permission; the server drops the message otherwise. Fire-and-forget: the
96
391
  * sender receives no echo.
392
+ *
393
+ * @param input - {@link ChannelMessageInput}:
394
+ * - `channelId` — the channel id (`groups.group_id`) as a `BigInt` decimal
395
+ * string.
396
+ * - `uuid` — the sender's own actor id: exactly 32 ASCII characters (not
397
+ * RFC-4122).
398
+ * - `payload` — message body, base64-encoded; opaque to the server, max
399
+ * 1024 bytes.
400
+ * - `sequenceNumber` — optional uint8 (0-255) correlation id.
401
+ * @returns `true` when accepted for sending — **not** confirmation of
402
+ * delivery; note that a message dropped for a missing `send_messages`
403
+ * permission still returns `true`.
404
+ * @throws {CrowdyGraphQLError} on auth/validation failures.
97
405
  */
98
406
  async sendChannelMessage(input) {
99
407
  const data = await this.gql.request(SendChannelMessageDocument, { input });
@@ -108,6 +416,34 @@ export class UdpAPI {
108
416
  * only delivers that app's spatial notifications and rejects app-agnostic
109
417
  * subscriptions (a single game token reused across apps would otherwise
110
418
  * cross-deliver). Use a separate client per app (sharing the token store).
419
+ *
420
+ * A missing `appId` is rejected by the game-api with a `RealtimeConnectionEvent`
421
+ * (`code === 'APP_ID_REQUIRED'`) delivered to your `connectionEvent` handler,
422
+ * after which the stream ends. Subscribing without a session token throws
423
+ * synchronously instead.
424
+ *
425
+ * @param handlers - {@link UdpNotificationHandlers}: optional per-typename
426
+ * callbacks (`actorUpdate`, `voxelUpdate`, `audio`, `text`, `clientEvent`,
427
+ * `singleActorMessage`, `channelMessage`, `genericError`, `connectionEvent`,
428
+ * etc.) plus `any` (every notification) and `error` (a
429
+ * {@link CrowdyRealtimeError}).
430
+ * @param appId - The app to scope delivery to (`BigInt` as a decimal string).
431
+ * Required.
432
+ * @returns An unsubscribe function that detaches these handlers (and closes
433
+ * the shared WebSocket if it was the last subscription).
434
+ * @throws {CrowdyRealtimeError} `code === 'AUTH_REQUIRED'` if called with no
435
+ * session token. Server-side connection problems (e.g. `APP_ID_REQUIRED`,
436
+ * `UDP_PROXY_CONNECTION_FAILED`) are delivered to the `connectionEvent` /
437
+ * `error` handlers rather than thrown.
438
+ * @example
439
+ * ```ts
440
+ * const unsubscribe = client.udp.subscribe({
441
+ * actorUpdate: (n) => console.log('actor', n.uuid, n.sequenceNumber),
442
+ * genericError: (e) => console.warn('udp error', e.errorCode),
443
+ * }, appId);
444
+ * // later…
445
+ * unsubscribe();
446
+ * ```
111
447
  */
112
448
  subscribe(handlers, appId) {
113
449
  return this.subs.subscribe(handlers, appId);
@@ -1,26 +1,57 @@
1
+ import type { GraphQLClient } from '../client.js';
2
+ import { type MeQuery, type UpdateGamertagInput, type UpdateGamertagMutation } from '../generated/graphql.js';
1
3
  /**
2
- * Users sub-client. Targets `cks-management-api`game-api never exposes
3
- * identity mutations after the split.
4
+ * User identity & account management — exposed as `client.users`.
4
5
  *
5
- * Only the read/identity surface that game clients realistically need is
6
- * here. Super-admin / operator screens use cks-management-ui (Apollo)
6
+ * Targets the **management-api** (every call routes to `managementUrl`). After
7
+ * the database split the users table is management-owned, so game-api no longer
8
+ * exposes these identity mutations — calling them against a game-api endpoint
9
+ * throws {@link CrowdyGraphQLError} with `FORBIDDEN`, directing you to the
10
+ * management API. Only the read/identity surface a game client realistically
11
+ * needs lives here; super-admin / operator screens use the management UI
7
12
  * directly rather than the SDK.
13
+ *
14
+ * Every method needs a valid session (a bearer token set by
15
+ * `client.auth.login()` / `register()` or `client.setToken()`); without one the
16
+ * server returns `UNAUTHENTICATED` — except {@link me}, which resolves to
17
+ * `null`. `BigInt` ids such as `userId` and `orgId` are decimal strings.
8
18
  */
9
- import type { GraphQLClient } from '../client.js';
10
- import { type MeQuery, type UpdateGamertagInput, type UpdateGamertagMutation } from '../generated/graphql.js';
11
19
  export declare class UsersAPI {
12
20
  private readonly graphql;
13
21
  constructor(graphql: GraphQLClient);
14
22
  /**
15
- * Validate the current Bearer token and return the user record. Returns
16
- * null if the token is expired/revoked. Use this for session restore on
17
- * SDK init.
23
+ * Validate the current bearer token and return the authenticated user record.
24
+ * Handy for restoring a session on SDK init.
25
+ *
26
+ * @returns The {@link User}, or `null` if the token is missing, expired, or
27
+ * revoked (an invalid token resolves to `null` rather than throwing).
28
+ * @throws {CrowdyGraphQLError} on transport/validation failures.
18
29
  */
19
30
  me(): Promise<MeQuery['me']>;
31
+ /**
32
+ * Set the authenticated user's gamertag and disambiguation (and append a
33
+ * gamertag-history row). Only ever updates the caller. Requires a valid
34
+ * session.
35
+ *
36
+ * @param input - {@link UpdateGamertagInput}: the new `gamertag` (max 64
37
+ * characters) and `disambiguation` (max 128 characters); the pair must be
38
+ * unique across the platform.
39
+ * @returns The updated {@link User} (its `userId`, `gamertag`,
40
+ * `disambiguation`, and `userType`).
41
+ * @throws {CrowdyGraphQLError} `BAD_USER_INPUT` if the gamertag +
42
+ * disambiguation pair is already taken, `UNAUTHENTICATED` without a session,
43
+ * or `FORBIDDEN` when called against game-api (call the management API).
44
+ */
20
45
  updateGamertag(input: UpdateGamertagInput): Promise<UpdateGamertagMutation['updateGamertag']>;
21
46
  /**
22
- * Soft-deletes the current user's account: anonymizes PII and revokes
23
- * sessions. Wallet / donation history stays intact via FK.
47
+ * **Destructive, self-service** soft-delete of the caller's **own** account:
48
+ * anonymizes PII and revokes all sessions. Wallet, voxel, and donation history
49
+ * stay intact via foreign keys. Acts only on the caller (no target argument).
50
+ * Requires a valid session.
51
+ *
52
+ * @returns `true` on success.
53
+ * @throws {CrowdyGraphQLError} `UNAUTHENTICATED` without a session, or
54
+ * `FORBIDDEN` when called against game-api (call the management API).
24
55
  */
25
56
  deleteMyAccount(): Promise<boolean>;
26
57
  }
@@ -1 +1 @@
1
- {"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/domains/users.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAIL,KAAK,OAAO,EACZ,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC5B,MAAM,yBAAyB,CAAC;AAEjC,qBAAa,QAAQ;IACP,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,aAAa;IAEnD;;;;OAIG;IACG,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAK5B,cAAc,CAClB,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,sBAAsB,CAAC,gBAAgB,CAAC,CAAC;IAKpD;;;OAGG;IACG,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;CAI1C"}
1
+ {"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/domains/users.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAIL,KAAK,OAAO,EACZ,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC5B,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,QAAQ;IACP,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,aAAa;IAEnD;;;;;;;OAOG;IACG,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAKlC;;;;;;;;;;;;;OAaG;IACG,cAAc,CAClB,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,sBAAsB,CAAC,gBAAgB,CAAC,CAAC;IAKpD;;;;;;;;;OASG;IACG,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;CAI1C"}
@@ -1,32 +1,63 @@
1
+ import { MeDocument, UpdateGamertagDocument, DeleteMyAccountDocument, } from '../generated/graphql.js';
1
2
  /**
2
- * Users sub-client. Targets `cks-management-api`game-api never exposes
3
- * identity mutations after the split.
3
+ * User identity & account management — exposed as `client.users`.
4
4
  *
5
- * Only the read/identity surface that game clients realistically need is
6
- * here. Super-admin / operator screens use cks-management-ui (Apollo)
5
+ * Targets the **management-api** (every call routes to `managementUrl`). After
6
+ * the database split the users table is management-owned, so game-api no longer
7
+ * exposes these identity mutations — calling them against a game-api endpoint
8
+ * throws {@link CrowdyGraphQLError} with `FORBIDDEN`, directing you to the
9
+ * management API. Only the read/identity surface a game client realistically
10
+ * needs lives here; super-admin / operator screens use the management UI
7
11
  * directly rather than the SDK.
12
+ *
13
+ * Every method needs a valid session (a bearer token set by
14
+ * `client.auth.login()` / `register()` or `client.setToken()`); without one the
15
+ * server returns `UNAUTHENTICATED` — except {@link me}, which resolves to
16
+ * `null`. `BigInt` ids such as `userId` and `orgId` are decimal strings.
8
17
  */
9
- import { MeDocument, UpdateGamertagDocument, DeleteMyAccountDocument, } from '../generated/graphql.js';
10
18
  export class UsersAPI {
11
19
  constructor(graphql) {
12
20
  this.graphql = graphql;
13
21
  }
14
22
  /**
15
- * Validate the current Bearer token and return the user record. Returns
16
- * null if the token is expired/revoked. Use this for session restore on
17
- * SDK init.
23
+ * Validate the current bearer token and return the authenticated user record.
24
+ * Handy for restoring a session on SDK init.
25
+ *
26
+ * @returns The {@link User}, or `null` if the token is missing, expired, or
27
+ * revoked (an invalid token resolves to `null` rather than throwing).
28
+ * @throws {CrowdyGraphQLError} on transport/validation failures.
18
29
  */
19
30
  async me() {
20
31
  const data = await this.graphql.request(MeDocument);
21
32
  return data.me;
22
33
  }
34
+ /**
35
+ * Set the authenticated user's gamertag and disambiguation (and append a
36
+ * gamertag-history row). Only ever updates the caller. Requires a valid
37
+ * session.
38
+ *
39
+ * @param input - {@link UpdateGamertagInput}: the new `gamertag` (max 64
40
+ * characters) and `disambiguation` (max 128 characters); the pair must be
41
+ * unique across the platform.
42
+ * @returns The updated {@link User} (its `userId`, `gamertag`,
43
+ * `disambiguation`, and `userType`).
44
+ * @throws {CrowdyGraphQLError} `BAD_USER_INPUT` if the gamertag +
45
+ * disambiguation pair is already taken, `UNAUTHENTICATED` without a session,
46
+ * or `FORBIDDEN` when called against game-api (call the management API).
47
+ */
23
48
  async updateGamertag(input) {
24
49
  const data = await this.graphql.request(UpdateGamertagDocument, { input });
25
50
  return data.updateGamertag;
26
51
  }
27
52
  /**
28
- * Soft-deletes the current user's account: anonymizes PII and revokes
29
- * sessions. Wallet / donation history stays intact via FK.
53
+ * **Destructive, self-service** soft-delete of the caller's **own** account:
54
+ * anonymizes PII and revokes all sessions. Wallet, voxel, and donation history
55
+ * stay intact via foreign keys. Acts only on the caller (no target argument).
56
+ * Requires a valid session.
57
+ *
58
+ * @returns `true` on success.
59
+ * @throws {CrowdyGraphQLError} `UNAUTHENTICATED` without a session, or
60
+ * `FORBIDDEN` when called against game-api (call the management API).
30
61
  */
31
62
  async deleteMyAccount() {
32
63
  const data = await this.graphql.request(DeleteMyAccountDocument);