@crowdedkingdoms/crowdyjs 18.6.0-dev.1 → 18.8.0-dev.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 (74) hide show
  1. package/MIGRATION.md +104 -0
  2. package/README.md +108 -4
  3. package/dist/binary-wire.d.ts +30 -2
  4. package/dist/binary-wire.d.ts.map +1 -1
  5. package/dist/binary-wire.js +61 -3
  6. package/dist/domains/appAccess.d.ts +32 -1
  7. package/dist/domains/appAccess.d.ts.map +1 -1
  8. package/dist/domains/appAccess.js +49 -1
  9. package/dist/domains/channels.d.ts +8 -3
  10. package/dist/domains/channels.d.ts.map +1 -1
  11. package/dist/domains/channels.js +8 -3
  12. package/dist/domains/exec.d.ts +14 -0
  13. package/dist/domains/exec.d.ts.map +1 -1
  14. package/dist/domains/exec.js +13 -1
  15. package/dist/domains/grids.d.ts +1 -0
  16. package/dist/domains/grids.d.ts.map +1 -1
  17. package/dist/domains/grids.js +1 -0
  18. package/dist/domains/portal.d.ts +18 -0
  19. package/dist/domains/portal.d.ts.map +1 -1
  20. package/dist/domains/portal.js +1 -1
  21. package/dist/domains/udp.d.ts +27 -8
  22. package/dist/domains/udp.d.ts.map +1 -1
  23. package/dist/domains/udp.js +39 -9
  24. package/dist/domains/users.d.ts +23 -2
  25. package/dist/domains/users.d.ts.map +1 -1
  26. package/dist/domains/users.js +34 -2
  27. package/dist/domains/voxels.d.ts +4 -3
  28. package/dist/domains/voxels.d.ts.map +1 -1
  29. package/dist/domains/voxels.js +4 -3
  30. package/dist/errors.d.ts +52 -0
  31. package/dist/errors.d.ts.map +1 -1
  32. package/dist/errors.js +74 -0
  33. package/dist/generated/graphql-schema.d.ts +163 -49
  34. package/dist/generated/graphql-schema.d.ts.map +1 -1
  35. package/dist/generated/graphql-schema.js +3 -1
  36. package/dist/generated/graphql.d.ts +159 -17
  37. package/dist/generated/graphql.d.ts.map +1 -1
  38. package/dist/generated/graphql.js +30 -23
  39. package/dist/grid-mods/grid-host-calls.d.ts +34 -0
  40. package/dist/grid-mods/grid-host-calls.d.ts.map +1 -1
  41. package/dist/grid-mods/grid-host-calls.js +28 -5
  42. package/dist/grid-mods/index.d.ts +1 -1
  43. package/dist/grid-mods/index.d.ts.map +1 -1
  44. package/dist/grid-mods/index.js +1 -1
  45. package/dist/grid-scope.d.ts +3 -0
  46. package/dist/grid-scope.d.ts.map +1 -1
  47. package/dist/grid-scope.js +6 -0
  48. package/dist/index.d.ts +9 -8
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +5 -4
  51. package/dist/media/voice-frames.d.ts +258 -0
  52. package/dist/media/voice-frames.d.ts.map +1 -0
  53. package/dist/media/voice-frames.js +424 -0
  54. package/dist/metrics.d.ts +1 -1
  55. package/dist/realtime.d.ts +19 -3
  56. package/dist/realtime.d.ts.map +1 -1
  57. package/dist/realtime.js +8 -0
  58. package/dist/stores/actors.d.ts +3 -1
  59. package/dist/stores/actors.d.ts.map +1 -1
  60. package/dist/stores/chunks.d.ts +65 -6
  61. package/dist/stores/chunks.d.ts.map +1 -1
  62. package/dist/stores/chunks.js +161 -19
  63. package/dist/stores/index.d.ts +2 -2
  64. package/dist/stores/index.d.ts.map +1 -1
  65. package/dist/stores/index.js +1 -1
  66. package/dist/stores/keys.d.ts +5 -0
  67. package/dist/stores/keys.d.ts.map +1 -1
  68. package/dist/stores/keys.js +7 -0
  69. package/dist/stores/session.d.ts +1 -1
  70. package/dist/stores/session.d.ts.map +1 -1
  71. package/dist/stores/session.js +2 -0
  72. package/dist/types.d.ts +49 -2
  73. package/dist/types.d.ts.map +1 -1
  74. package/package.json +1 -1
package/MIGRATION.md CHANGED
@@ -120,6 +120,110 @@ super-admin's session). There is no SDK replacement.
120
120
  `dev`'s SDL after #417 merged (`npm run schema:sync:paths -- --schema <that schema.gql>`, then
121
121
  `npm run codegen`).
122
122
 
123
+ ## 18.8.0: builds report the SDK version they compiled against
124
+
125
+ Additive, with one new floor: **the build documents select `ExecBuild.sdkVersion`, so 18.8.0
126
+ needs ck-api v2.40.2 or later** for `exec.build`, `buildStatus`, `waitForBuild`, `modBuild`,
127
+ `modClientBuild`, `modBuildStatus` and `waitForModBuild` (an older API refuses the selection).
128
+
129
+ - **`sdkVersion`** on every build: the version of `ckx-sdk` the platform compiled the build
130
+ against (`crowdy-client-sdk` for a CLIENT half), from the toolchain of the API instance that
131
+ ran it. The platform points a crate at its own copy of the SDK whatever version the crate
132
+ names, so this, not your manifest, says what your code was checked against. `null` until the
133
+ build starts, and for builds from before the platform recorded it. The build log's first line
134
+ names the same toolchain.
135
+
136
+ `schema.gql` and the generated types carry cks-game-api `dev`'s SDL at #494.
137
+
138
+ ## 18.7.0: voice helpers, channel audio, opcode 140 on the relay, wide voxels, self-echo, pause and access refusals
139
+
140
+ Additive, with reads that change for values they used to get wrong (the wide-voxel item) and one
141
+ new floor: **the token mutations (`portal.mintAppToken`, `exchangeCode`, `refresh`) select
142
+ `runtimeGate` and the `udpNotifications` subscription selects `ChannelAudioNotification`, so
143
+ 18.7.0 needs the ck-api release after v2.39.0 that carries both** (an older one refuses the
144
+ selections). Channel audio and the voxel echo need Buddy v0.37.0.
145
+
146
+ - **Voice helpers** (`media/voice-frames.ts`, exported from the package entry): an optional
147
+ convention for what an audio payload carries. A 10-byte header, little-endian, goes in front of
148
+ each codec frame: version 1, codec (0 raw, 1 Opus 48 kHz mono, 2 G.711 µ-law 8 kHz), `u16` seq,
149
+ `u32` timestamp in codec samples (milliseconds for raw), the frame's duration in ms, and flags
150
+ (bit 0 the first packet after silence, bit 1 the last before it). `encodeVoiceHeader` /
151
+ `encodeVoicePacket` write it; `decodeVoicePacket` returns `null` for a packet shorter than
152
+ 10 bytes or of another version and never throws. `VoicePacketizer` numbers one sender's frames
153
+ across the seq and timestamp wraps and sets the flags (`packetize(frame, { last })`, `skip()`
154
+ for silence). `VoiceJitterBuffer` takes `push(key, packet, nowMs)` per sender key and returns
155
+ the due frames from `pull(key, nowMs)` / `poll(nowMs)`: in seq order, `targetDelayMs` (60) after
156
+ a talk spurt's first packet, a gap (`frame: null`) for each one that never came, late packets
157
+ dropped, at most `maxFrames` (64) a sender, starting over on a talk spurt or `resetAfterMs`
158
+ (200) of silence. A spurt that starts while the one before still has frames waiting drops
159
+ them. CrowdyCPP 0.60.0 has the same helpers and replays `test/unit/fixtures/voice-frames.json`.
160
+ No codec ships: a browser encodes Opus with WebCodecs (the module doc has the settings). A game
161
+ with a voice format of its own keeps it; positioning a voice stays the game's.
162
+ - **`genericSpatial`.** Opcode 140 (`GENERIC_SPATIAL_1`), an app-defined spatial payload such as
163
+ CrowdyCPP's `Connection::sendGenericSpatial`, reaches the new `genericSpatial` handler of
164
+ `udp.subscribe` (and `any`) as a `GenericSpatialNotification`: the spatial header fields and a
165
+ base64 `payload`. The World Stores bus has a `genericSpatial` key. **Binary relay only**
166
+ (`realtime: { binaryTransport: true }`): the GraphQL `udpNotifications` union has no member for
167
+ 140, so the proxy drops it and a client on the GraphQL transport never sees one. `UdpNotification`
168
+ gains the member, so an exhaustive `switch` over `__typename` needs the new case.
169
+ - **`ChunkStore` keeps wide voxel types and other addresses.** Positions and types are the app's
170
+ signed 16-bit values, which the platform does not check. An edit the 16×16×16 one-byte grid
171
+ cannot hold (a type outside 0-255, a position outside 0-15) — a realtime update, a hydrated
172
+ `voxelStates` entry, or a local `setVoxel` — is kept whole in the new `CachedChunk.overlay`
173
+ (`ChunkOverlayVoxel`: `x`, `y`, `z`, `voxelType`, `state`; keyed by `voxelKey(x, y, z)`), and
174
+ `voxelTypeAt` / `voxelStateAt` return it. What changes: a type outside 0-255 is no longer stored
175
+ truncated (300 used to read as 44, -1 as 255); a position outside 0-15 no longer lands on
176
+ another voxel (`(16, 0, 0)` used to overwrite `(0, 1, 0)`), and reading one returns the overlay's
177
+ or nothing; and code that walks `chunk.voxels` itself sees 0 where an in-grid voxel's type is in
178
+ the overlay. The store is a 16×16×16 helper; a game with other addressing reads the raw
179
+ `voxelUpdate` events and `chunks.get`'s `voxelStates`.
180
+ - **`createGridHostCalls({ voxelBounds })`** sets the positions and types a CLIENT half's
181
+ `voxel_set` may write, for a world that uses other signed 16-bit values. The default,
182
+ `DEFAULT_GRID_VOXEL_BOUNDS`, is today's: positions 0-15, types 0-255. Bounds that are not
183
+ integer ranges within -32768 to 32767 throw `RangeError` when the host calls are created.
184
+ - **Channel audio** (Buddy v0.37.0). `udp.sendChannelAudio({ channelId, uuid, payload,
185
+ sequenceNumber? })` sends opcode 35 (opcode 17's layout and signing) on the binary relay, else
186
+ the `sendChannelAudio` mutation; `payload` is base64, at most 1,024 bytes, and the server
187
+ refuses it with `UNAUTHORIZED` (7) without the channel's `send_voice` and the app's
188
+ `use_voice_chat`. There is no echo to the sender. Other members' frames arrive as opcode 36,
189
+ standalone or bundled, as a `ChannelAudioNotification` (`channelId`, `uuid`, `audioData`
190
+ base64, `sequenceNumber`, `epochMillis`) on the new `channelAudio` handler of `udp.subscribe`
191
+ (and `any`) and the World Stores bus key `channelAudio`, on the binary relay and on the GraphQL
192
+ transport alike (the `udpNotifications` subscription selects the union's
193
+ `ChannelAudioNotification`, so it too needs that ck-api release). `channels.create` /
194
+ `grids.createChannel` take `membersCanSpeak` (default false: the member role gets
195
+ `send_voice`); `GridScope.channels` has `sendAudio(channelId, uuid, audioBase64)`;
196
+ `serializeChannelAudio` is exported. The voice helpers are the payload: see the README.
197
+ - **UDP error 33, `APP_PAUSED`**: the replication server refuses a paused app's sends.
198
+ `UDP_ERROR_NAMES[33]` and the generated `UdpErrorCode.AppPaused` name it.
199
+ - **Voxel edits are int16 and their state at most 1,024 bytes.** `udp.sendVoxelUpdate` and
200
+ `ChunkStore.setVoxel` throw `RangeError` (nothing sent) for a position or type outside
201
+ -32768..32767 or a state over 1,024 bytes (`assertVoxelEdit`, `VOXEL_STATE_MAX_BYTES`); the
202
+ server answers those with `INVALID_REQUEST` (15). The 0-15 / 0-255 ranges in the docs were
203
+ never enforced and are gone.
204
+ - **The echo of your own voxel edit.** Buddy v0.37.0 delivers every accepted edit back to its
205
+ sender as a `VoxelUpdateNotification`. `ChunkStore` records each `setVoxel` (uuid, sequence,
206
+ voxel) for 10 s and does not apply its echo again, so a local edit fires `onChunkChanged` once;
207
+ it applies the echo only when another client's edit of the voxel arrived in between (the server
208
+ ordered yours last) and no newer local edit of it is pending. A game reading `voxelUpdate`
209
+ itself sees its own edits there now: compare the `uuid` and `sequenceNumber` with the send's.
210
+ - **Pause and access refusals.** `isAppPaused(gate)` reads `AppTokenResponse.runtimeGate` and
211
+ `GameClientBootstrap.runtimeGate` (`{ status, reason }`; anything but `ACTIVE` is paused; a
212
+ paused app still mints, so check before entering). `appPausedOf(err)` reads `APP_PAUSED`
213
+ (`reason`), `accessRefusalOf(err)` `ACCESS_REVOKED` / `ACCESS_SUSPENDED` (`suspendedUntil`) /
214
+ `ACCESS_NOT_GRANTED`, `actorExistsOf(err)` `ACTOR_EXISTS` (`ownedByCaller`), with their
215
+ `*_CODE` constants.
216
+ - **New API wraps**: `users.playerProfile(userId)` and `users.playerProfiles(userIds)` (at most
217
+ `PLAYER_PROFILES_MAX`, 100; `RangeError` above) for public `{ userId, gamertag, disambiguation }`;
218
+ `users.get` documents that another user's private fields come back null.
219
+ `appAccess.suspend(appId, userId, until, idempotencyKey?)`, `unsuspend`,
220
+ `resyncTierGridPermissions` (`manage_access_tiers`); `suspendedUntil` on every access record.
221
+ `exec.restartType(appId, nodeType)` → `{ nodeType, stopped }` (`manage_compute`);
222
+ `exec.status` adds `budgetPauseReason`, `maxInstances`, `maxReservedMb`, `instanceLimit`,
223
+ `instances`, `reservedMb`. `apps.get` / `apps.update` carry `claimOwnerKeys`;
224
+ `chunks.get` / `getByDistance` carry `voxelStatesTruncated`; `gameClientBootstrap` carries
225
+ `runtimeGate` and `wildernessWritesOpen`.
226
+
123
227
  ## 18.6.0: the input log
124
228
 
125
229
  Additive. An app with replay logging on has its client inputs recorded, and `client.inputLog`
package/README.md CHANGED
@@ -234,7 +234,7 @@ never told about, so a native refresh without it is a re-placement.
234
234
  | Sub-client | What it does |
235
235
  |---|---|
236
236
  | `client.auth` | Sign-in: `login` / `register` (email + password), magic link, social/OIDC. Passwords: `requestPasswordReset` / `resetPassword`, `changePassword`, and `setInitialPassword` for an account created by magic link or a social provider (added in 15.1.0). Log out, and linked identities (`myIdentities`, `linkIdentity`/`unlinkIdentity`). **No dev bypass** — `devLogin` was removed in 15.0.0 and `DEV_AUTH_BYPASS` is gone from every tier. |
237
- | `client.users` | `me`, `updateGamertag`, profile reads. |
237
+ | `client.users` | `me`, `updateGamertag`, profile reads; `playerProfile` / `playerProfiles` (up to 100) for other players' public `{ userId, gamertag, disambiguation }`. |
238
238
  | `client.session` | Token store, `restore()`, `getToken()`, manual `setToken()`. |
239
239
  | `client.portal` | App-scoped token minting (`mintAppToken`) and the cross-origin PKCE entry flow (`beginEntry` / `handleAuthorizeRequest` / `completeEntry` / `refresh`). |
240
240
  | `client.hosting` | Third-party hosting on Crowdy Games (17.2.0): `claim` a slug for an app, `beginPublish` / `completePublish` / `abandonPublish` a built bundle, `setEnabled`; public `game(slug)` / `listed()`. Mutations need an identity session with `manage_apps`. `@crowdedkingdoms/crowdyjs/hosting` exports `publishDirectory(client, { dir, slug })`, the Node helper that does the whole publish for a `dist/`. |
@@ -245,7 +245,7 @@ never told about, so a native refresh without it is a re-placement.
245
245
  | `client.teleport` | Teleport requests. |
246
246
  | `client.inputLog` | The input log (18.6.0): `sessions(appId)` and `messages(appId, gameTokenId)` read the client inputs recorded for an app with replay logging on (`apps.update(appId, { replayLoggingEnabled: true })`). A player reads their own; `manage_apps` reads every session. Keep paging while `hasNextPage` is true. |
247
247
  | `client.channels`, `client.teams` | Messaging channels and app-scoped player teams (membership + roles). |
248
- | `client.exec` | **ck-exec:** an app's server code as hubs and spokes. `connect(appId, { nodeType, key })` opens a player's connection to an execution host; the `ExecConnection` it returns has `call`, `callRaw`, `subscribe`, `ping`, `onReconnect` and `close`, with MessagePack payloads, and it reconnects and renews subscriptions by itself. `starters` / `build` / `deploy` build and deploy an app (`manage_compute`); `logs`, `instances`, `versions`, `activateVersion` and `setEnabled` operate it; `mod*` are players' mods on grids they own, and `modClient*`, `gridClientMods`, `consentClientMod`, `trustAuthor`, `revokeClientModConsent` and `revokeAuthorTrust` their CLIENT halves. See [ck-exec](#ck-exec). |
248
+ | `client.exec` | **ck-exec:** an app's server code as hubs and spokes. `connect(appId, { nodeType, key })` opens a player's connection to an execution host; the `ExecConnection` it returns has `call`, `callRaw`, `subscribe`, `ping`, `onReconnect` and `close`, with MessagePack payloads, and it reconnects and renews subscriptions by itself. `starters` / `build` / `deploy` build and deploy an app (`manage_compute`); `logs`, `instances`, `versions`, `activateVersion`, `setEnabled`, `restartType` and `status` operate it; `mod*` are players' mods on grids they own, and `modClient*`, `gridClientMods`, `consentClientMod`, `trustAuthor`, `revokeClientModConsent` and `revokeAuthorTrust` their CLIENT halves. See [ck-exec](#ck-exec). |
249
249
  | `client.playerWallet` | Player spend: balance, ledger, hourly charges, spend caps, card setup, auto-recharge. A ck-exec mod's compute is billed here, to its owner. |
250
250
  | `client.marketplace` | Player-authorized grid claims (`claimGridOwnership`, `claimGridChunk`, `releaseClaimedGrid`, requests and invites) and studio moderation of player code (admission queue, listing administration, claim policy). Mods publish and install through `client.exec`. |
251
251
  | `client.crowdyStudio` | Cloud project, personal-library, and common-file APIs for Crowdy Studio: target-scoped files, metadata/module names, optimistic revisions, copy-by-value imports, atomic saves. |
@@ -264,7 +264,7 @@ grouped under `client.admin` and mirrored at the top level):
264
264
  |---|---|
265
265
  | `client.organizations` | Orgs, members, RBAC roles, org API tokens. |
266
266
  | `client.apps` | App registry, discovery + routing (`create`, `routeFor`, `marketplace`), visibility, and player-code admission mode / allow-list administration. |
267
- | `client.appAccess` | Access tiers + per-user grants, and the feature keys a tier grants (`defineFeature`, `grantTierFeature`, `tierFeatures`, …), which a hub checks with the node API's `players.features`. |
267
+ | `client.appAccess` | Access tiers + per-user grants, and the feature keys a tier grants (`defineFeature`, `grantTierFeature`, `tierFeatures`, …), which a hub checks with the node API's `players.features`; timed bans (`suspend`, `unsuspend`) and `resyncTierGridPermissions`. |
268
268
  | `client.billing` | Org wallet + per-app spend budgets. |
269
269
  | `client.payments` | Payment checkouts (wallet top-ups, plan purchases). |
270
270
  | `client.quotas` | Usage quotas at the org/app scope. |
@@ -346,6 +346,10 @@ const unsubscribe = client.udp.subscribe(
346
346
  },
347
347
  clientEvent: (event) => { /* ... */ },
348
348
  serverEvent: (event) => { /* ... */ },
349
+ genericSpatial: (event) => {
350
+ // An app-defined spatial payload (opcode 140): binary relay only, see below.
351
+ console.log(event.uuid, event.payload); // payload is base64
352
+ },
349
353
  singleActorMessage: (event) => {
350
354
  // A direct actor-to-actor message addressed to you.
351
355
  console.log(event.uuid, event.payload); // payload is base64
@@ -386,6 +390,13 @@ while `AUTH_REQUIRED` / `APP_ID_REQUIRED` must be fixed by the caller first.
386
390
  Unsubscribing stops delivery only; call `client.udp.disconnect()` to close the
387
391
  UDP proxy session.
388
392
 
393
+ `genericSpatial` receives opcode 140 (`GENERIC_SPATIAL_1`), an app-defined spatial payload the
394
+ server fans out without reading — what CrowdyCPP's `Connection::sendGenericSpatial` sends. It
395
+ arrives only on the binary relay (`realtime: { binaryTransport: true }`): the GraphQL
396
+ `udpNotifications` union has no member for it, so the GraphQL proxy drops it and a client on that
397
+ transport never sees one. A World Stores session carries it on its bus too:
398
+ `session.context.on('genericSpatial', …)`.
399
+
389
400
  ### Surviving the loss of an instance (direct connect)
390
401
 
391
402
  **If your environment uses direct connect, set `realtime.discoveryUrl`.** Under
@@ -450,6 +461,12 @@ with a live actor within `maxDistance` chunks (straight-line distance between
450
461
  chunk coordinates) of `chunk` receive it; they get the same `channelMessage`
451
462
  notification.
452
463
 
464
+ `sendVoxelUpdate` takes the app's signed 16-bit positions and type and a state of
465
+ at most 1,024 bytes (`RangeError` otherwise, nothing sent). Buddy v0.37.0 echoes
466
+ every accepted voxel edit back to its sender as a `voxelUpdate`, so a game reading
467
+ those sees its own edits; World Stores' `ChunkStore` applies that echo once. A
468
+ paused app's sends are refused with UDP error 33, `APP_PAUSED`.
469
+
453
470
  ### Bundled sends on the binary relay
454
471
 
455
472
  With `realtime: { binaryTransport: true }` the SDK signs each message itself
@@ -477,6 +494,76 @@ await client.udp.sendSingleActorMessage({
477
494
  });
478
495
  ```
479
496
 
497
+ ### Voice payloads
498
+
499
+ `sendAudioPacket` carries whatever bytes it is given, and a game with a voice format of its own
500
+ keeps it. The voice helpers are an optional one that both SDKs share (CrowdyCPP's
501
+ `crowdy/media/voice_frames.hpp`, held to the same cases in `test/unit/fixtures/voice-frames.json`):
502
+ a 10-byte header in front of each codec frame, little-endian — the version (1), the codec (0 raw,
503
+ 1 Opus 48 kHz mono, 2 G.711 µ-law 8 kHz), a `u16` seq, a `u32` timestamp in codec samples, the
504
+ frame's duration in milliseconds and two talk-spurt flags (bit 0 the first packet after silence,
505
+ bit 1 the last before it). A reader refuses a packet shorter than the header or of another
506
+ version. `VoicePacketizer` numbers one sender's frames; `VoiceJitterBuffer` puts each sender's
507
+ packets back in order, plays them out 60 ms (`targetDelayMs`) after the first packet of a talk
508
+ spurt arrived, reports a frame that never came as a gap, drops one that arrives after its
509
+ playout time, and holds at most 64 frames a sender.
510
+
511
+ ```ts
512
+ import {
513
+ VoiceCodec, VoiceJitterBuffer, VoicePacketizer, decodeBase64, encodeBase64,
514
+ } from '@crowdedkingdoms/crowdyjs';
515
+
516
+ const packetizer = new VoicePacketizer({ codec: VoiceCodec.OPUS, frameMs: 20 });
517
+ // Every 20 ms while the player talks (`opusFrame` from a WebCodecs AudioEncoder):
518
+ await client.udp.sendAudioPacket({
519
+ appId, chunk, uuid: me.uuid,
520
+ audioData: encodeBase64(packetizer.packetize(opusFrame, { last: talkKeyReleased })),
521
+ });
522
+ // ...and packetizer.skip() for every 20 ms of silence that is not sent.
523
+
524
+ const voices = new VoiceJitterBuffer();
525
+ client.udp.subscribe({
526
+ audio: (n) => voices.push(n.uuid, decodeBase64(n.audioData), performance.now()),
527
+ actorLeft: (n) => voices.forget(n.uuid),
528
+ }, appId);
529
+ // From the audio clock, at least once a frame:
530
+ for (const slot of voices.poll(performance.now())) {
531
+ if (slot.gap) conceal(slot.key, slot.frameMs); // the frame never came
532
+ else play(slot.key, slot.codec, slot.frame!);
533
+ }
534
+ ```
535
+
536
+ The SDK ships no codec. A browser encodes and decodes Opus with WebCodecs (`AudioEncoder` /
537
+ `AudioDecoder`, `codec: 'opus'`, 48 kHz, one channel; check `AudioEncoder.isConfigSupported`
538
+ first) and falls back to G.711 µ-law where it has none; `media/voice-frames.ts` has the settings.
539
+ Where a voice sits in the world (panning, distance attenuation) stays the game's.
540
+
541
+ #### Channel audio (party and guild voice)
542
+
543
+ `sendAudioPacket` reaches players near a chunk. `sendChannelAudio` (Buddy v0.37.0) reaches every
544
+ active member of a channel wherever they are: opcode 35 on the binary relay, the
545
+ `sendChannelAudio` mutation otherwise, at most 1,024 payload bytes. The sender needs the channel's
546
+ `send_voice` (`channels.create({ ..., membersCanSpeak: true })` gives it to the member role) and the
547
+ app's `use_voice_chat`; without them the server answers `UNAUTHORIZED` on the subscription. There is
548
+ no echo. Members get a `channelAudio` notification; key the jitter buffer by channel and sender so
549
+ one player in two channels is two streams. It arrives on the binary relay and on the GraphQL
550
+ transport alike.
551
+
552
+ ```ts
553
+ const party = new VoicePacketizer({ codec: VoiceCodec.OPUS, frameMs: 20 });
554
+ await client.udp.sendChannelAudio({
555
+ channelId, uuid: me.uuid,
556
+ payload: encodeBase64(party.packetize(opusFrame, { last: talkKeyReleased })),
557
+ });
558
+
559
+ const partyVoices = new VoiceJitterBuffer();
560
+ client.udp.subscribe({
561
+ channelAudio: (n) =>
562
+ partyVoices.push(`${n.channelId}:${n.uuid}`, decodeBase64(n.audioData), performance.now()),
563
+ }, appId);
564
+ // ...poll(performance.now()) from the audio clock as above.
565
+ ```
566
+
480
567
  ## World helpers
481
568
 
482
569
  ```ts
@@ -545,7 +632,9 @@ holds it to rules of its own (18.0.2): `grid_permission_check` answers only for
545
632
  the player's four code-permission keys (`GRID_PERMISSION_CHECK_KEYS`) and
546
633
  refuses any other key instead of answering false, because a game knows no other
547
634
  key for a grid; `voxel_set` takes a voxel inside its chunk (0-15) and a type
548
- 0-255; and a half's spatial and channel sends go out as an actor uuid the page
635
+ 0-255, or the ranges the game sets with `createGridHostCalls({ voxelBounds })` for a world
636
+ whose positions and types are other signed 16-bit values (18.7.0); and a half's spatial and
637
+ channel sends go out as an actor uuid the page
549
638
  derives for the grid from the one the half names (`clientHalfActorUuid`), so a
550
639
  half can never move the player's avatar or speak as another player. The broker
551
640
  refuses a call that names its chunk a second way (`chunk`, `chunk_x`, …): route
@@ -614,6 +703,14 @@ another player's realtime voxel update) reaches a reload only through hydration:
614
703
  returns it in `voxelStates`, and the bulk load's `voxels` never hold it. The store hydrates when
615
704
  it has a `voxelStateCodec`; without one, set `chunks: { hydrateVoxelStates: true }`.
616
705
 
706
+ `chunks` is a helper for 16×16×16 chunks with one byte per voxel. Voxel positions and types are
707
+ the app's signed 16-bit values, which the platform does not check: an edit the dense grid cannot
708
+ hold (a type outside 0-255, a position outside 0-15), whether a realtime update, a hydrated
709
+ `voxelStates` entry or a local `setVoxel`, is kept whole in the chunk's `overlay` (keyed by
710
+ `voxelKey(x, y, z)`) instead of being truncated into `voxels`, and `voxelTypeAt` / `voxelStateAt`
711
+ return it (18.7.0). A game with other addressing reads the raw `voxelUpdate` events and
712
+ `chunks.get`'s `voxelStates`.
713
+
617
714
  A chunk write-back the server refuses (a visitor on someone else's plot, a safe zone, a closed
618
715
  wilderness) is dropped, not retried; one that fails for a reason that can clear is tried five
619
716
  times. Listen with `session.chunks.onWriteBackFailed(...)` to undo or flag the edit (18.0.4).
@@ -986,6 +1083,13 @@ GraphQL errors carry a stable `extensions.code` (e.g. `UNAUTHENTICATED`,
986
1083
  where applicable, `extensions.remediation` and `extensions.requiredPermission`.
987
1084
  Branch on `error.extensions?.code` rather than parsing messages.
988
1085
 
1086
+ Readers for the refusals a player should be told about, each `null` for any other error:
1087
+ `accessRefusalOf(err)` (`ACCESS_REVOKED`, `ACCESS_SUSPENDED` with `suspendedUntil`,
1088
+ `ACCESS_NOT_GRANTED`), `appPausedOf(err)` (`APP_PAUSED`, with `reason`) and `actorExistsOf(err)`
1089
+ (`ACTOR_EXISTS`; `ownedByCaller: true` means a retried create of your own actor). A paused app
1090
+ still mints tokens: check `isAppPaused(token.runtimeGate)` (or the bootstrap's `runtimeGate`)
1091
+ before entering the world.
1092
+
989
1093
  ## Idempotent retries
990
1094
 
991
1095
  Destructive game-client mutations accept an optional **idempotency key**. Pass
@@ -52,6 +52,8 @@ export declare const WireMessageType: {
52
52
  readonly CHANNEL_MESSAGE_REQUEST: 17;
53
53
  readonly CHANNEL_MESSAGE_NOTIFICATION: 18;
54
54
  readonly CHANNEL_MESSAGE_RANGED_REQUEST: 32;
55
+ readonly CHANNEL_AUDIO_REQUEST: 35;
56
+ readonly CHANNEL_AUDIO_NOTIFICATION: 36;
55
57
  readonly ACTOR_UPDATE_REQUEST_2: 128;
56
58
  readonly ACTOR_UPDATE_RESPONSE_2: 129;
57
59
  readonly ACTOR_UPDATE_NOTIFICATION_2: 130;
@@ -64,6 +66,7 @@ export declare const WireMessageType: {
64
66
  readonly CLIENT_TEXT_NOTIFICATION_2: 137;
65
67
  readonly CLIENT_EVENT_NOTIFICATION_2: 138;
66
68
  readonly SERVER_EVENT_NOTIFICATION_2: 139;
69
+ readonly GENERIC_SPATIAL_1: 140;
67
70
  readonly SINGLE_ACTOR_MESSAGE: 142;
68
71
  readonly CLIENT_VIDEO_PACKET_2: 143;
69
72
  readonly CLIENT_VIDEO_NOTIFICATION_2: 144;
@@ -111,6 +114,23 @@ export interface SpatialSendBase {
111
114
  export declare function serializeActorUpdate(ctx: RelaySignContext, input: SpatialSendBase & {
112
115
  state: string;
113
116
  }): Promise<Uint8Array>;
117
+ /** Most bytes a voxel edit's state may carry; the server refuses longer with INVALID_REQUEST. */
118
+ export declare const VOXEL_STATE_MAX_BYTES = 1024;
119
+ /**
120
+ * Check a voxel edit before it is sent: positions and type are app-defined
121
+ * signed 16-bit integers (no narrower range), and the state is at most
122
+ * {@link VOXEL_STATE_MAX_BYTES} decoded.
123
+ * @throws {RangeError} naming the field that does not fit.
124
+ */
125
+ export declare function assertVoxelEdit(input: {
126
+ voxel: {
127
+ x: number;
128
+ y: number;
129
+ z: number;
130
+ };
131
+ voxelType: number;
132
+ voxelState?: string | null;
133
+ }): void;
114
134
  export declare function serializeVoxelUpdate(ctx: RelaySignContext, input: SpatialSendBase & {
115
135
  voxel: {
116
136
  x: number;
@@ -153,12 +173,20 @@ export declare function serializeSingleActorMessage(ctx: RelaySignContext, input
153
173
  * `[1B type=17][8B channelId][32B uuid][2B payloadLen][payload][1B containsAuth]
154
174
  * [32B HMAC][8B gameTokenId][1B seq]` — HMAC over everything before it.
155
175
  */
156
- export declare function serializeChannelMessage(ctx: RelaySignContext, input: {
176
+ export declare function serializeChannelMessage(ctx: RelaySignContext, input: ChannelSendInput): Promise<Uint8Array>;
177
+ /**
178
+ * CHANNEL_AUDIO_REQUEST (Buddy v0.37.0): opcode 17's layout and signing with
179
+ * type byte 35. The payload is opaque (at most 1,024 bytes), typically one
180
+ * {@link VoicePacketizer} packet.
181
+ */
182
+ export declare function serializeChannelAudio(ctx: RelaySignContext, input: ChannelSendInput): Promise<Uint8Array>;
183
+ /** Input shared by the channel message and channel audio uplinks (`ChannelMessageInput`). */
184
+ export interface ChannelSendInput {
157
185
  channelId: string;
158
186
  uuid: string;
159
187
  payload?: string | null;
160
188
  sequenceNumber?: number | null;
161
- }): Promise<Uint8Array>;
189
+ }
162
190
  /** Largest `maxDistance` a ranged channel message may carry (the GraphQL `Int` ceiling). */
163
191
  export declare const CHANNEL_RANGED_MAX_DISTANCE = 2147483647;
164
192
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"binary-wire.d.ts","sourceRoot":"","sources":["../src/binary-wire.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"binary-wire.d.ts","sourceRoot":"","sources":["../src/binary-wire.ts"],"names":[],"mappings":"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"}
@@ -89,6 +89,9 @@ export const WireMessageType = {
89
89
  // Buddy v0.35.0: a channel publish only members near an origin chunk receive
90
90
  // (they get an ordinary CHANNEL_MESSAGE_NOTIFICATION).
91
91
  CHANNEL_MESSAGE_RANGED_REQUEST: 32,
92
+ // Buddy v0.37.0: channel audio, laid out exactly as 17 / 18 with their own type byte.
93
+ CHANNEL_AUDIO_REQUEST: 35,
94
+ CHANNEL_AUDIO_NOTIFICATION: 36,
92
95
  ACTOR_UPDATE_REQUEST_2: 128,
93
96
  ACTOR_UPDATE_RESPONSE_2: 129,
94
97
  ACTOR_UPDATE_NOTIFICATION_2: 130,
@@ -101,6 +104,9 @@ export const WireMessageType = {
101
104
  CLIENT_TEXT_NOTIFICATION_2: 137,
102
105
  CLIENT_EVENT_NOTIFICATION_2: 138,
103
106
  SERVER_EVENT_NOTIFICATION_2: 139,
107
+ // An app-defined spatial payload (CrowdyCPP `Connection::sendGenericSpatial`, a hub's
108
+ // emit). Parsed on the binary relay only: the GraphQL union has no member for it.
109
+ GENERIC_SPATIAL_1: 140,
104
110
  SINGLE_ACTOR_MESSAGE: 142,
105
111
  // Buddy v0.25.0: webcam video pair (audio's shape, payload = one SDK
106
112
  // fragment, see media/video-frames.ts) and the server-only actor-left downlink.
@@ -153,6 +159,7 @@ const UDP_ERROR_NAMES = {
153
159
  30: 'CANNOT_DELETE_DEFAULT_WORLD_GRID',
154
160
  31: 'GRID_HAS_NESTED_CHILDREN',
155
161
  32: 'TOKEN_EXPIRED',
162
+ 33: 'APP_PAUSED',
156
163
  };
157
164
  const textEncoder = new TextEncoder();
158
165
  const textDecoder = new TextDecoder();
@@ -252,6 +259,29 @@ export function serializeClientCapabilities(ctx, appId, flags = SDK_CAPABILITIES
252
259
  export function serializeActorUpdate(ctx, input) {
253
260
  return serializeSpatial(ctx, WireMessageType.ACTOR_UPDATE_REQUEST_2, input.appId, input.chunk, input.distance ?? 8, input.decayRate ?? 1, input.uuid, decodeBase64(input.state ?? ''), input.sequenceNumber ?? 0);
254
261
  }
262
+ /** Most bytes a voxel edit's state may carry; the server refuses longer with INVALID_REQUEST. */
263
+ export const VOXEL_STATE_MAX_BYTES = 1024;
264
+ /**
265
+ * Check a voxel edit before it is sent: positions and type are app-defined
266
+ * signed 16-bit integers (no narrower range), and the state is at most
267
+ * {@link VOXEL_STATE_MAX_BYTES} decoded.
268
+ * @throws {RangeError} naming the field that does not fit.
269
+ */
270
+ export function assertVoxelEdit(input) {
271
+ const int16 = (name, v) => {
272
+ if (!Number.isInteger(v) || v < -32768 || v > 32767) {
273
+ throw new RangeError(`${name} must be a signed 16-bit integer: ${v}`);
274
+ }
275
+ };
276
+ int16('voxel.x', input.voxel.x);
277
+ int16('voxel.y', input.voxel.y);
278
+ int16('voxel.z', input.voxel.z);
279
+ int16('voxelType', input.voxelType);
280
+ const stateBytes = input.voxelState ? decodeBase64(input.voxelState).length : 0;
281
+ if (stateBytes > VOXEL_STATE_MAX_BYTES) {
282
+ throw new RangeError(`voxelState is ${stateBytes} bytes; at most ${VOXEL_STATE_MAX_BYTES}`);
283
+ }
284
+ }
255
285
  export function serializeVoxelUpdate(ctx, input) {
256
286
  const voxelState = decodeBase64(input.voxelState ?? '');
257
287
  const payload = new Uint8Array(10 + voxelState.length);
@@ -292,7 +322,18 @@ export function serializeSingleActorMessage(ctx, input) {
292
322
  * `[1B type=17][8B channelId][32B uuid][2B payloadLen][payload][1B containsAuth]
293
323
  * [32B HMAC][8B gameTokenId][1B seq]` — HMAC over everything before it.
294
324
  */
295
- export async function serializeChannelMessage(ctx, input) {
325
+ export function serializeChannelMessage(ctx, input) {
326
+ return serializeChannelFrame(ctx, WireMessageType.CHANNEL_MESSAGE_REQUEST, input);
327
+ }
328
+ /**
329
+ * CHANNEL_AUDIO_REQUEST (Buddy v0.37.0): opcode 17's layout and signing with
330
+ * type byte 35. The payload is opaque (at most 1,024 bytes), typically one
331
+ * {@link VoicePacketizer} packet.
332
+ */
333
+ export function serializeChannelAudio(ctx, input) {
334
+ return serializeChannelFrame(ctx, WireMessageType.CHANNEL_AUDIO_REQUEST, input);
335
+ }
336
+ async function serializeChannelFrame(ctx, messageType, input) {
296
337
  const payload = input.payload ? decodeBase64(input.payload) : new Uint8Array(0);
297
338
  if (payload.length > 1024) {
298
339
  throw new Error(`Channel payload exceeds 1024 bytes: ${payload.length}`);
@@ -305,7 +346,7 @@ export async function serializeChannelMessage(ctx, input) {
305
346
  const prefix = new Uint8Array(prefixLen);
306
347
  const view = new DataView(prefix.buffer);
307
348
  let off = 0;
308
- view.setUint8(off, WireMessageType.CHANNEL_MESSAGE_REQUEST);
349
+ view.setUint8(off, messageType);
309
350
  off += 1;
310
351
  view.setBigUint64(off, BigInt(input.channelId), true);
311
352
  off += 8;
@@ -462,7 +503,8 @@ function parseOne(bytes) {
462
503
  'UNKNOWN_ERROR'),
463
504
  };
464
505
  }
465
- if (messageType === WireMessageType.CHANNEL_MESSAGE_NOTIFICATION) {
506
+ if (messageType === WireMessageType.CHANNEL_MESSAGE_NOTIFICATION ||
507
+ messageType === WireMessageType.CHANNEL_AUDIO_NOTIFICATION) {
466
508
  // [1B type][8B channelId][32B senderUuid][2B payloadLen][payload][8B epochMillis][1B seq]
467
509
  if (bytes.length < 1 + 8 + UUID_SIZE + 2)
468
510
  return null;
@@ -480,6 +522,16 @@ function parseOne(bytes) {
480
522
  const epochMillis = view.getBigUint64(off, true);
481
523
  off += 8;
482
524
  const sequenceNumber = view.getUint8(off);
525
+ if (messageType === WireMessageType.CHANNEL_AUDIO_NOTIFICATION) {
526
+ return {
527
+ __typename: 'ChannelAudioNotification',
528
+ channelId: channelId.toString(),
529
+ uuid,
530
+ audioData: encodeBase64(payload),
531
+ sequenceNumber,
532
+ epochMillis: epochMillis.toString(),
533
+ };
534
+ }
483
535
  return {
484
536
  __typename: 'ChannelMessageNotification',
485
537
  channelId: channelId.toString(),
@@ -574,6 +626,12 @@ function parseOne(bytes) {
574
626
  payload: encodeBase64(p.payload),
575
627
  };
576
628
  }
629
+ case WireMessageType.GENERIC_SPATIAL_1:
630
+ return {
631
+ __typename: 'GenericSpatialNotification',
632
+ ...spatialCommon(p),
633
+ payload: encodeBase64(p.payload),
634
+ };
577
635
  default:
578
636
  return null;
579
637
  }
@@ -1,5 +1,5 @@
1
1
  import type { GraphQLClient } from '../client.js';
2
- import { type DefineAppFeatureInput, type DefineAppFeatureMutation, type AppFeaturesQuery, type GrantTierFeatureInput, type GrantTierFeatureMutation, type TierFeaturesQuery, type AppAccessTiersQuery, type MyAppAccessQuery, type AppUserAccessByAppQuery, type AppUserAccessConnectionQuery, type AppUserAccessConnectionQueryVariables, type RuntimePermissionsQuery, type AppGrantMemberCandidatesQuery, type ClaimFreeAppAccessMutation, type GrantMyAppAccessMutation, type CreateAccessTierMutation, type UpdateAccessTierMutation, type ArchiveAccessTierMutation, type GrantAppAccessMutation, type RevokeAppAccessMutation, type CreateAccessTierInput, type UpdateAccessTierInput, type GrantAppAccessInput } from '../generated/graphql.js';
2
+ import { type SuspendAppAccessMutation, type UnsuspendAppAccessMutation, type DefineAppFeatureInput, type DefineAppFeatureMutation, type AppFeaturesQuery, type GrantTierFeatureInput, type GrantTierFeatureMutation, type TierFeaturesQuery, type AppAccessTiersQuery, type MyAppAccessQuery, type AppUserAccessByAppQuery, type AppUserAccessConnectionQuery, type AppUserAccessConnectionQueryVariables, type RuntimePermissionsQuery, type AppGrantMemberCandidatesQuery, type ClaimFreeAppAccessMutation, type GrantMyAppAccessMutation, type CreateAccessTierMutation, type UpdateAccessTierMutation, type ArchiveAccessTierMutation, type GrantAppAccessMutation, type RevokeAppAccessMutation, type CreateAccessTierInput, type UpdateAccessTierInput, type GrantAppAccessInput } from '../generated/graphql.js';
3
3
  /**
4
4
  * App access tiers + per-user access grants — exposed as `client.appAccess`
5
5
  * (and grouped under `client.admin`).
@@ -137,6 +137,37 @@ export declare class AppAccessAPI {
137
137
  * @returns `true` on success.
138
138
  */
139
139
  revoke(appId: string, userId: string): Promise<RevokeAppAccessMutation['revokeAppAccess']>;
140
+ /**
141
+ * Suspend a player's access to an app until `until`: a timed ban that lifts by
142
+ * itself. Until then their gameplay tokens are refused with `ACCESS_SUSPENDED`
143
+ * (carrying `suspendedUntil`, see `accessRefusalOf`), and the tokens they hold
144
+ * for the app are deleted and their realtime sessions ended at once. `until`
145
+ * must be in the future and at most 365 days away; the player must have an
146
+ * access record. Requires the `manage_access_tiers` app permission.
147
+ *
148
+ * @param appId - Numeric app id.
149
+ * @param userId - Numeric id of the player to suspend.
150
+ * @param until - When the suspension lapses (a `Date` or ISO-8601 string).
151
+ * @param idempotencyKey - Optional; a replay with the same key and arguments returns the first result.
152
+ * @returns The access record, with `suspendedUntil` set.
153
+ */
154
+ suspend(appId: string, userId: string, until: Date | string, idempotencyKey?: string): Promise<SuspendAppAccessMutation['suspendAppAccess']>;
155
+ /**
156
+ * Lift a player's suspension before it lapses (a no-op for a player who is
157
+ * not suspended). Requires the `manage_access_tiers` app permission.
158
+ *
159
+ * @param appId - Numeric app id.
160
+ * @param userId - Numeric id of the player whose suspension to lift.
161
+ * @returns The access record, with `suspendedUntil` cleared.
162
+ */
163
+ unsuspend(appId: string, userId: string): Promise<UnsuspendAppAccessMutation['unsuspendAppAccess']>;
164
+ /**
165
+ * Re-apply every player's tier keys that follow onto the app's world grid and
166
+ * rebuild the world grid's permissions. Tier changes made through this API
167
+ * already do it; call it after a change made another way. Requires the
168
+ * `manage_access_tiers` app permission.
169
+ */
170
+ resyncTierGridPermissions(appId: string): Promise<boolean>;
140
171
  /**
141
172
  * Define (or re-describe) a feature key for an app. Idempotent on `(appId, featureKey)`.
142
173
  * Requires the app-admin `manage_apps` permission.
@@ -1 +1 @@
1
- {"version":3,"file":"appAccess.d.ts","sourceRoot":"","sources":["../../src/domains/appAccess.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"appAccess.d.ts","sourceRoot":"","sources":["../../src/domains/appAccess.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { AppAccessTiersDocument, MyAppAccessDocument, AppUserAccessByAppDocument, AppUserAccessConnectionDocument, RuntimePermissionsDocument, AppGrantMemberCandidatesDocument, ClaimFreeAppAccessDocument, GrantMyAppAccessDocument, CreateAccessTierDocument, UpdateAccessTierDocument, ArchiveAccessTierDocument, GrantAppAccessDocument, RevokeAppAccessDocument, DefineAppFeatureDocument, AppFeaturesDocument, GrantTierFeatureDocument, RevokeTierFeatureDocument, TierFeaturesDocument, } from '../generated/graphql.js';
1
+ import { AppAccessTiersDocument, MyAppAccessDocument, AppUserAccessByAppDocument, AppUserAccessConnectionDocument, RuntimePermissionsDocument, AppGrantMemberCandidatesDocument, ClaimFreeAppAccessDocument, GrantMyAppAccessDocument, CreateAccessTierDocument, UpdateAccessTierDocument, ArchiveAccessTierDocument, GrantAppAccessDocument, RevokeAppAccessDocument, SuspendAppAccessDocument, UnsuspendAppAccessDocument, ResyncTierGridPermissionsDocument, DefineAppFeatureDocument, AppFeaturesDocument, GrantTierFeatureDocument, RevokeTierFeatureDocument, TierFeaturesDocument, } from '../generated/graphql.js';
2
2
  /**
3
3
  * App access tiers + per-user access grants — exposed as `client.appAccess`
4
4
  * (and grouped under `client.admin`).
@@ -195,6 +195,54 @@ export class AppAccessAPI {
195
195
  });
196
196
  return data.revokeAppAccess;
197
197
  }
198
+ /**
199
+ * Suspend a player's access to an app until `until`: a timed ban that lifts by
200
+ * itself. Until then their gameplay tokens are refused with `ACCESS_SUSPENDED`
201
+ * (carrying `suspendedUntil`, see `accessRefusalOf`), and the tokens they hold
202
+ * for the app are deleted and their realtime sessions ended at once. `until`
203
+ * must be in the future and at most 365 days away; the player must have an
204
+ * access record. Requires the `manage_access_tiers` app permission.
205
+ *
206
+ * @param appId - Numeric app id.
207
+ * @param userId - Numeric id of the player to suspend.
208
+ * @param until - When the suspension lapses (a `Date` or ISO-8601 string).
209
+ * @param idempotencyKey - Optional; a replay with the same key and arguments returns the first result.
210
+ * @returns The access record, with `suspendedUntil` set.
211
+ */
212
+ async suspend(appId, userId, until, idempotencyKey) {
213
+ const data = await this.api.request(SuspendAppAccessDocument, {
214
+ appId,
215
+ userId,
216
+ until: until instanceof Date ? until.toISOString() : until,
217
+ idempotencyKey,
218
+ });
219
+ return data.suspendAppAccess;
220
+ }
221
+ /**
222
+ * Lift a player's suspension before it lapses (a no-op for a player who is
223
+ * not suspended). Requires the `manage_access_tiers` app permission.
224
+ *
225
+ * @param appId - Numeric app id.
226
+ * @param userId - Numeric id of the player whose suspension to lift.
227
+ * @returns The access record, with `suspendedUntil` cleared.
228
+ */
229
+ async unsuspend(appId, userId) {
230
+ const data = await this.api.request(UnsuspendAppAccessDocument, {
231
+ appId,
232
+ userId,
233
+ });
234
+ return data.unsuspendAppAccess;
235
+ }
236
+ /**
237
+ * Re-apply every player's tier keys that follow onto the app's world grid and
238
+ * rebuild the world grid's permissions. Tier changes made through this API
239
+ * already do it; call it after a change made another way. Requires the
240
+ * `manage_access_tiers` app permission.
241
+ */
242
+ async resyncTierGridPermissions(appId) {
243
+ const data = await this.api.request(ResyncTierGridPermissionsDocument, { appId });
244
+ return data.resyncTierGridPermissions;
245
+ }
198
246
  // -- Tier features -----------------------------------------------------------
199
247
  //
200
248
  // Feature keys an access tier grants. A ck-exec hub checks a player's with the node API's