@q9labsai/chalk-client 4.1.4 → 4.1.7

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 (67) hide show
  1. package/dist/access/grant.d.ts +8 -0
  2. package/dist/access/grant.d.ts.map +1 -1
  3. package/dist/{chunk-PBCGWE2V.js → chunk-C4BIONCP.js} +442 -7
  4. package/dist/chunk-DFLTEYWX.js +144 -0
  5. package/dist/chunk-JPNE4K2L.js +8911 -0
  6. package/dist/{chunk-2DA665LP.js → chunk-MMK2K3LO.js} +181 -203
  7. package/dist/client.d.ts +951 -59
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/connection/dependencies.d.ts +4 -4
  10. package/dist/connection/dependencies.d.ts.map +1 -1
  11. package/dist/connection/lifecycle.d.ts.map +1 -1
  12. package/dist/connection/production.d.ts.map +1 -1
  13. package/dist/connection/snapshot.d.ts +2 -2
  14. package/dist/connection/snapshot.d.ts.map +1 -1
  15. package/dist/effect.js +1012 -7052
  16. package/dist/generated/http-api.d.ts +941 -62
  17. package/dist/generated/http-api.d.ts.map +1 -1
  18. package/dist/generated/schemas.d.ts +1353 -31
  19. package/dist/generated/schemas.d.ts.map +1 -1
  20. package/dist/generated/whiteboard-v1.d.ts +48 -5
  21. package/dist/generated/whiteboard-v1.d.ts.map +1 -1
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +20 -2
  25. package/dist/media/client.d.ts.map +1 -1
  26. package/dist/media/index.d.ts +2 -1
  27. package/dist/media/index.d.ts.map +1 -1
  28. package/dist/media/index.js +5 -1
  29. package/dist/media/observers.d.ts +3 -0
  30. package/dist/media/observers.d.ts.map +1 -0
  31. package/dist/media/plane.d.ts +25 -0
  32. package/dist/media/plane.d.ts.map +1 -1
  33. package/dist/media/rtk.d.ts +101 -0
  34. package/dist/media/rtk.d.ts.map +1 -0
  35. package/dist/media/target.d.ts +10 -0
  36. package/dist/media/target.d.ts.map +1 -0
  37. package/dist/public-invites/client.d.ts +4 -0
  38. package/dist/public-invites/client.d.ts.map +1 -0
  39. package/dist/public-invites/index.d.ts +5 -0
  40. package/dist/public-invites/index.d.ts.map +1 -0
  41. package/dist/public-invites/index.js +18 -0
  42. package/dist/public-invites/types.d.ts +67 -0
  43. package/dist/public-invites/types.d.ts.map +1 -0
  44. package/dist/server/{chunk-A6YMG5FM.js → chunk-C4XITY5U.js} +1 -1
  45. package/dist/server/client.d.ts.map +1 -1
  46. package/dist/server/index.cjs +72 -19
  47. package/dist/server/index.d.cts +1 -1
  48. package/dist/server/index.d.ts +1 -1
  49. package/dist/server/index.d.ts.map +1 -1
  50. package/dist/server/index.js +72 -19
  51. package/dist/server/server-only.js +1 -1
  52. package/dist/server/transport.d.cts +1 -1
  53. package/dist/server/transport.d.ts +1 -1
  54. package/dist/server/transport.d.ts.map +1 -1
  55. package/dist/server/types.d.cts +40 -0
  56. package/dist/server/types.d.ts +40 -0
  57. package/dist/server/types.d.ts.map +1 -1
  58. package/dist/space-client/controller-parity.test.helpers.d.ts.map +1 -1
  59. package/dist/space-client/types.d.ts +1 -0
  60. package/dist/space-client/types.d.ts.map +1 -1
  61. package/dist/whiteboard/types.d.ts +4 -2
  62. package/dist/whiteboard/types.d.ts.map +1 -1
  63. package/dist/whiteboard/v1-client.d.ts +1 -0
  64. package/dist/whiteboard/v1-client.d.ts.map +1 -1
  65. package/dist/whiteboard/v1-multipart.d.ts +1 -1
  66. package/dist/whiteboard/v1-multipart.d.ts.map +1 -1
  67. package/package.json +20 -2
@@ -1,7 +1,13 @@
1
1
  import {
2
+ CloudflareRTKClient,
2
3
  CloudflareSFUClient,
3
4
  createCloudflareSFUHTTPTransport
4
- } from "./chunk-PBCGWE2V.js";
5
+ } from "./chunk-C4BIONCP.js";
6
+ import {
7
+ AccessGrantError,
8
+ requireParsedAccessGrant,
9
+ validEpisodeDiagnosticCredential
10
+ } from "./chunk-JPNE4K2L.js";
5
11
  import {
6
12
  createBrowserSyncLifecycle,
7
13
  createBrowserWebSocketFactory,
@@ -81,7 +87,15 @@ function validateRefresh(previous, next, replaceMediaConnection) {
81
87
  if (left.tenantId !== right.tenantId || left.spaceId !== right.spaceId || left.episodeId !== right.episodeId || left.participantId !== right.participantId || left.participantGeneration !== right.participantGeneration) {
82
88
  throw new TypeError("Access grant refresh changed its subject");
83
89
  }
84
- if (!replaceMediaConnection && previous.media.clientPayload.connectionId !== next.media.clientPayload.connectionId) throw new TypeError("Access grant refresh unexpectedly replaced its media connection");
90
+ if (!replaceMediaConnection && (previous.media.provider !== next.media.provider || mediaBinding(previous) !== mediaBinding(next))) throw new TypeError("Access grant refresh unexpectedly replaced its media connection");
91
+ }
92
+ function mediaBinding(access) {
93
+ switch (access.media.provider) {
94
+ case "cloudflare_sfu":
95
+ return access.media.clientPayload.connectionId;
96
+ case "cloudflare_rtk":
97
+ return access.media.clientPayload.providerSubject;
98
+ }
85
99
  }
86
100
  function expiresAt(value) {
87
101
  const timestamp = Date.parse(value);
@@ -358,6 +372,12 @@ var WhiteboardV1ProtocolMetadata = {
358
372
  route: "/v1/whiteboard",
359
373
  transport: "websocket-json-text"
360
374
  },
375
+ extensions: {
376
+ presentation: {
377
+ name: "presentation_v1",
378
+ exactFields: ["name"]
379
+ }
380
+ },
361
381
  identity: {
362
382
  credential: "participant-access-token",
363
383
  authorityKey: ["tenant_id", "space_id", "episode_id"],
@@ -372,7 +392,7 @@ var WhiteboardV1ProtocolMetadata = {
372
392
  },
373
393
  {
374
394
  id: "live",
375
- accepts: ["submit_update", "submit_update_part", "request_snapshot", "snapshot_ack", "clear", "set_draw_permission", "cursor", "ping"],
395
+ accepts: ["submit_update", "submit_update_part", "request_snapshot", "snapshot_ack", "clear", "set_draw_permission", "set_presentation", "cursor", "ping"],
376
396
  heartbeatIntervalMs: 2e4,
377
397
  missedDeadlinesBeforeClose: 2
378
398
  }
@@ -424,7 +444,8 @@ var WhiteboardV1ProtocolMetadata = {
424
444
  },
425
445
  frames: {
426
446
  hello: {
427
- exactFields: ["type", "protocol", "token", "cursor"]
447
+ exactFields: ["type", "protocol", "token", "cursor"],
448
+ extendedExactFields: ["type", "protocol", "token", "cursor", "extensions"]
428
449
  },
429
450
  submitUpdate: {
430
451
  exactFields: ["type", "operation_id", "scene_id", "sync_all", "elements"]
@@ -444,12 +465,16 @@ var WhiteboardV1ProtocolMetadata = {
444
465
  setDrawPermission: {
445
466
  exactFields: ["type", "operation_id", "participant_id", "can_draw"]
446
467
  },
468
+ setPresentation: {
469
+ exactFields: ["type", "operation_id", "presenting"]
470
+ },
447
471
  cursor: {
448
472
  clientExactFields: ["type", "x", "y"],
449
473
  serverExactFields: ["type", "participant_id", "display_name", "x", "y", "occurred_at"]
450
474
  },
451
475
  welcome: {
452
- exactFields: ["type", "protocol", "participant_id", "participant_generation", "capabilities", "participant_capabilities", "scene_id", "revision", "can_draw"]
476
+ exactFields: ["type", "protocol", "participant_id", "participant_generation", "capabilities", "participant_capabilities", "scene_id", "revision", "can_draw"],
477
+ extendedExactFields: ["type", "protocol", "participant_id", "participant_generation", "capabilities", "participant_capabilities", "scene_id", "revision", "can_draw", "presenting"]
453
478
  },
454
479
  snapshotPage: {
455
480
  exactFields: ["type", "request_id", "scene_id", "revision", "page", "page_count", "elements", "app_state"]
@@ -466,6 +491,9 @@ var WhiteboardV1ProtocolMetadata = {
466
491
  permissionUpdated: {
467
492
  exactFields: ["type", "participant_id", "can_draw"]
468
493
  },
494
+ presentationUpdated: {
495
+ exactFields: ["type", "scene_id", "revision", "presenting"]
496
+ },
469
497
  resetRequired: {
470
498
  exactFields: ["type", "scene_id", "reason"]
471
499
  },
@@ -473,9 +501,9 @@ var WhiteboardV1ProtocolMetadata = {
473
501
  exactFields: ["type", "correlation_id", "operation", "code", "recoverable", "message"]
474
502
  }
475
503
  },
476
- clientFrames: ["hello", "submit_update", "submit_update_part", "request_snapshot", "snapshot_ack", "clear", "set_draw_permission", "cursor", "ping"],
477
- serverFrames: ["welcome", "snapshot_page", "update", "update_part", "commit", "cursor", "permission_updated", "reset_required", "operation_error", "pong"],
478
- receiptOperations: ["submit_update", "clear", "set_draw_permission"],
504
+ clientFrames: ["hello", "submit_update", "submit_update_part", "request_snapshot", "snapshot_ack", "clear", "set_draw_permission", "set_presentation", "cursor", "ping"],
505
+ serverFrames: ["welcome", "snapshot_page", "update", "update_part", "commit", "cursor", "permission_updated", "presentation_updated", "reset_required", "operation_error", "pong"],
506
+ receiptOperations: ["submit_update", "clear", "set_draw_permission", "set_presentation"],
479
507
  receiptOutcomes: ["committed", "duplicate"],
480
508
  resetReasons: ["scene_changed", "cursor_expired", "gap"],
481
509
  errorCodes: ["unavailable", "permission_denied", "invalid_payload", "stale_scene", "cursor_reset_required", "rate_limited", "overloaded", "storage_unavailable"],
@@ -537,6 +565,9 @@ function isRecord2(value) {
537
565
  function hasExactKeys(value, keys) {
538
566
  return isRecord2(value) && Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
539
567
  }
568
+ function hasExactRecordKeys(value, keys) {
569
+ return Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
570
+ }
540
571
  function isBoundedIdentifier(value, minimum, maximum) {
541
572
  return typeof value === "string" && utf8Bytes(value) >= minimum && utf8Bytes(value) <= maximum;
542
573
  }
@@ -581,7 +612,7 @@ function isClientFrame(value) {
581
612
  if (!isRecord2(value)) return false;
582
613
  switch (value.type) {
583
614
  case "hello":
584
- return hasExactKeys(value, WhiteboardV1ProtocolMetadata.frames.hello.exactFields) && value.protocol === "whiteboard-v1" && typeof value.token === "string" && utf8Bytes(value.token) > 0 && utf8Bytes(value.token) <= WhiteboardV1ProtocolLimits.tokenBytes && (value.cursor === null || isCursor(value.cursor));
615
+ return (hasExactRecordKeys(value, WhiteboardV1ProtocolMetadata.frames.hello.exactFields) || hasExactRecordKeys(value, WhiteboardV1ProtocolMetadata.frames.hello.extendedExactFields) && isPresentationExtensions(value.extensions)) && value.protocol === "whiteboard-v1" && typeof value.token === "string" && utf8Bytes(value.token) > 0 && utf8Bytes(value.token) <= WhiteboardV1ProtocolLimits.tokenBytes && (value.cursor === null || isCursor(value.cursor));
585
616
  case "submit_update":
586
617
  return hasExactKeys(value, WhiteboardV1ProtocolMetadata.frames.submitUpdate.exactFields) && isBoundedIdentifier(value.operation_id, WhiteboardV1ProtocolLimits.operationIdMinBytes, WhiteboardV1ProtocolLimits.operationIdMaxBytes) && isUuid(value.scene_id) && typeof value.sync_all === "boolean" && isElementBatch(value.elements, WhiteboardV1ProtocolLimits.elementBatchMaxItems);
587
618
  case "submit_update_part":
@@ -594,6 +625,8 @@ function isClientFrame(value) {
594
625
  return hasExactKeys(value, WhiteboardV1ProtocolMetadata.frames.clear.exactFields) && isBoundedIdentifier(value.operation_id, WhiteboardV1ProtocolLimits.operationIdMinBytes, WhiteboardV1ProtocolLimits.operationIdMaxBytes) && isUuid(value.scene_id);
595
626
  case "set_draw_permission":
596
627
  return hasExactKeys(value, WhiteboardV1ProtocolMetadata.frames.setDrawPermission.exactFields) && isBoundedIdentifier(value.operation_id, WhiteboardV1ProtocolLimits.operationIdMinBytes, WhiteboardV1ProtocolLimits.operationIdMaxBytes) && isUuid(value.participant_id) && typeof value.can_draw === "boolean";
628
+ case "set_presentation":
629
+ return hasExactKeys(value, WhiteboardV1ProtocolMetadata.frames.setPresentation.exactFields) && isBoundedIdentifier(value.operation_id, WhiteboardV1ProtocolLimits.operationIdMinBytes, WhiteboardV1ProtocolLimits.operationIdMaxBytes) && typeof value.presenting === "boolean";
597
630
  case "cursor":
598
631
  return hasExactKeys(value, WhiteboardV1ProtocolMetadata.frames.cursor.clientExactFields) && isFiniteNumber(value.x) && isFiniteNumber(value.y) && encodedWhiteboardV1FrameBytes(value) <= WhiteboardV1ProtocolLimits.cursorFrameBytes;
599
632
  case "ping":
@@ -605,11 +638,18 @@ function isClientFrame(value) {
605
638
  function isAppState(value) {
606
639
  return value === null || hasExactKeys(value, WhiteboardV1ProtocolMetadata.sharedAppState.exactFields) && typeof value.view_background_color === "string" && utf8Bytes(value.view_background_color) <= 64;
607
640
  }
641
+ function isPresentationExtensions(value) {
642
+ return Array.isArray(value) && value.length === 1 && hasExactKeys(value[0], WhiteboardV1ProtocolMetadata.extensions.presentation.exactFields) && value[0].name === WhiteboardV1ProtocolMetadata.extensions.presentation.name;
643
+ }
644
+ function isWelcome(value) {
645
+ const exact = hasExactRecordKeys(value, WhiteboardV1ProtocolMetadata.frames.welcome.exactFields) || hasExactRecordKeys(value, WhiteboardV1ProtocolMetadata.frames.welcome.extendedExactFields) && typeof value.presenting === "boolean";
646
+ return exact && value.protocol === "whiteboard-v1" && isUuid(value.participant_id) && Number.isSafeInteger(value.participant_generation) && Number(value.participant_generation) > 0 && isCapabilities(value.capabilities) && isCapabilities(value.participant_capabilities) && isUuid(value.scene_id) && isUnsignedDecimal(value.revision) && typeof value.can_draw === "boolean";
647
+ }
608
648
  function isServerFrame(value) {
609
649
  if (!isRecord2(value)) return false;
610
650
  switch (value.type) {
611
651
  case "welcome":
612
- return hasExactKeys(value, WhiteboardV1ProtocolMetadata.frames.welcome.exactFields) && value.protocol === "whiteboard-v1" && isUuid(value.participant_id) && Number.isSafeInteger(value.participant_generation) && Number(value.participant_generation) > 0 && isCapabilities(value.capabilities) && isCapabilities(value.participant_capabilities) && isUuid(value.scene_id) && isUnsignedDecimal(value.revision) && typeof value.can_draw === "boolean";
652
+ return isWelcome(value);
613
653
  case "snapshot_page":
614
654
  return hasExactKeys(value, WhiteboardV1ProtocolMetadata.frames.snapshotPage.exactFields) && isBoundedIdentifier(value.request_id, WhiteboardV1ProtocolLimits.requestIdMinBytes, WhiteboardV1ProtocolLimits.requestIdMaxBytes) && isUuid(value.scene_id) && isUnsignedDecimal(value.revision) && isNonNegativeInteger(value.page) && isNonNegativeInteger(value.page_count) && value.page_count > 0 && value.page_count <= WhiteboardV1ProtocolLimits.snapshotMaxPages && value.page < value.page_count && isElementBatch(value.elements, WhiteboardV1ProtocolLimits.snapshotPageMaxItems) && isAppState(value.app_state) && encodedWhiteboardV1FrameBytes(value) <= WhiteboardV1ProtocolLimits.snapshotPageEncodedBytes;
615
655
  case "update":
@@ -622,10 +662,12 @@ function isServerFrame(value) {
622
662
  return hasExactKeys(value, WhiteboardV1ProtocolMetadata.frames.cursor.serverExactFields) && isUuid(value.participant_id) && typeof value.display_name === "string" && utf8Bytes(value.display_name) > 0 && utf8Bytes(value.display_name) <= WhiteboardV1ProtocolLimits.displayNameMaxBytes && isFiniteNumber(value.x) && isFiniteNumber(value.y) && typeof value.occurred_at === "string" && !Number.isNaN(Date.parse(value.occurred_at)) && encodedWhiteboardV1FrameBytes(value) <= WhiteboardV1ProtocolLimits.cursorFrameBytes;
623
663
  case "permission_updated":
624
664
  return hasExactKeys(value, WhiteboardV1ProtocolMetadata.frames.permissionUpdated.exactFields) && isUuid(value.participant_id) && typeof value.can_draw === "boolean";
665
+ case "presentation_updated":
666
+ return hasExactKeys(value, WhiteboardV1ProtocolMetadata.frames.presentationUpdated.exactFields) && isUuid(value.scene_id) && isUnsignedDecimal(value.revision) && typeof value.presenting === "boolean";
625
667
  case "reset_required":
626
668
  return hasExactKeys(value, WhiteboardV1ProtocolMetadata.frames.resetRequired.exactFields) && isUuid(value.scene_id) && typeof value.reason === "string" && WhiteboardV1ProtocolMetadata.resetReasons.some((reason) => reason === value.reason);
627
669
  case "operation_error":
628
- return hasExactKeys(value, WhiteboardV1ProtocolMetadata.frames.operationError.exactFields) && isBoundedIdentifier(value.correlation_id, WhiteboardV1ProtocolLimits.requestIdMinBytes, WhiteboardV1ProtocolLimits.requestIdMaxBytes) && ["submit_update", "request_snapshot", "clear", "set_draw_permission"].includes(String(value.operation)) && typeof value.code === "string" && WhiteboardV1ProtocolMetadata.errorCodes.some((code) => code === value.code) && typeof value.recoverable === "boolean" && typeof value.message === "string" && utf8Bytes(value.message) <= WhiteboardV1ProtocolLimits.errorMessageMaxBytes;
670
+ return hasExactKeys(value, WhiteboardV1ProtocolMetadata.frames.operationError.exactFields) && isBoundedIdentifier(value.correlation_id, WhiteboardV1ProtocolLimits.requestIdMinBytes, WhiteboardV1ProtocolLimits.requestIdMaxBytes) && ["submit_update", "request_snapshot", "clear", "set_draw_permission", "set_presentation"].includes(String(value.operation)) && typeof value.code === "string" && WhiteboardV1ProtocolMetadata.errorCodes.some((code) => code === value.code) && typeof value.recoverable === "boolean" && typeof value.message === "string" && utf8Bytes(value.message) <= WhiteboardV1ProtocolLimits.errorMessageMaxBytes;
629
671
  case "pong":
630
672
  return hasExactKeys(value, ["type"]);
631
673
  default:
@@ -858,6 +900,9 @@ var ChalkWhiteboardV1Client = class {
858
900
  #revision;
859
901
  #capabilities = [];
860
902
  #canDraw = false;
903
+ #presentationSupported = false;
904
+ #presenting = false;
905
+ #useLegacyHello = false;
861
906
  #summaryStatus = "unsubscribed";
862
907
  #summaryError = null;
863
908
  #latestSnapshot = null;
@@ -883,6 +928,7 @@ var ChalkWhiteboardV1Client = class {
883
928
  startSceneSubscription() {
884
929
  if (this.#started && this.#startPromise) return this.#startPromise;
885
930
  this.#started = true;
931
+ this.#useLegacyHello = false;
886
932
  this.#phase = "connecting";
887
933
  this.#publishSummary("loading", null);
888
934
  const generation = ++this.#startupGeneration;
@@ -912,6 +958,7 @@ var ChalkWhiteboardV1Client = class {
912
958
  this.#participantId = null;
913
959
  this.#capabilities = [];
914
960
  this.#canDraw = false;
961
+ this.#presenting = false;
915
962
  this.#publishSummary("unsubscribed", null);
916
963
  }
917
964
  subscribe(listener) {
@@ -968,6 +1015,21 @@ var ChalkWhiteboardV1Client = class {
968
1015
  };
969
1016
  await this.#queueOperation(frame);
970
1017
  }
1018
+ async setPresentation(presenting) {
1019
+ this.#assertLive("set_presentation");
1020
+ if (!this.#presentationSupported) {
1021
+ throw error(failure("set_presentation", "unavailable", true, "Whiteboard presentation is unavailable on this Sync server."));
1022
+ }
1023
+ if (!this.#canDraw || !this.#capabilities.includes("drawWhiteboard")) {
1024
+ throw error(failure("set_presentation", "permission_denied", false, "Whiteboard draw permission is required."));
1025
+ }
1026
+ const frame = {
1027
+ type: "set_presentation",
1028
+ operation_id: this.#nextId(),
1029
+ presenting
1030
+ };
1031
+ await this.#queueOperation(frame);
1032
+ }
971
1033
  async #restoreAndConnect(generation) {
972
1034
  try {
973
1035
  const stored = await this.#store.load();
@@ -1024,12 +1086,9 @@ var ChalkWhiteboardV1Client = class {
1024
1086
  const token = await this.#options.token();
1025
1087
  if (socket !== this.#socket) return;
1026
1088
  this.#phase = "authenticating";
1027
- this.#send({
1028
- type: "hello",
1029
- protocol: "whiteboard-v1",
1030
- token,
1031
- cursor: this.#sceneId && this.#revision ? { scene_id: this.#sceneId, revision: this.#revision } : null
1032
- });
1089
+ this.#presentationSupported = false;
1090
+ const cursor = this.#sceneId && this.#revision ? { scene_id: this.#sceneId, revision: this.#revision } : null;
1091
+ this.#send(this.#useLegacyHello ? { type: "hello", protocol: "whiteboard-v1", token, cursor } : { type: "hello", protocol: "whiteboard-v1", token, cursor, extensions: [{ name: "presentation_v1" }] });
1033
1092
  } catch {
1034
1093
  socket.close(1008, "whiteboard authentication failed");
1035
1094
  }
@@ -1080,11 +1139,22 @@ var ChalkWhiteboardV1Client = class {
1080
1139
  if (frame.participant_id === this.#participantId) this.#canDraw = frame.can_draw;
1081
1140
  this.#publishSummary("ready", null);
1082
1141
  return;
1142
+ case "presentation_updated":
1143
+ this.#requireLive();
1144
+ this.#sceneId = frame.scene_id;
1145
+ this.#revision = frame.revision;
1146
+ this.#presenting = frame.presenting;
1147
+ this.#publishSummary("ready", null);
1148
+ return;
1083
1149
  case "reset_required":
1084
1150
  this.#requireLive();
1085
1151
  this.#sceneId = frame.scene_id;
1086
1152
  this.#revision = null;
1087
1153
  this.#publishSummary("recovering", failure("request_snapshot", "cursor_reset_required", true, "Whiteboard snapshot recovery is required."));
1154
+ if (this.#presentationSupported) {
1155
+ this.#socket?.close(CLIENT_RESTART_CLOSE_CODE, "whiteboard presentation recovery required");
1156
+ return;
1157
+ }
1088
1158
  this.#emit({ type: "reset_required", sceneId: frame.scene_id, reason: frame.reason });
1089
1159
  return;
1090
1160
  case "operation_error":
@@ -1137,6 +1207,8 @@ var ChalkWhiteboardV1Client = class {
1137
1207
  this.#revision = frame.revision;
1138
1208
  this.#capabilities = [...frame.capabilities];
1139
1209
  this.#canDraw = frame.can_draw;
1210
+ this.#presentationSupported = "presenting" in frame;
1211
+ this.#presenting = "presenting" in frame ? frame.presenting : false;
1140
1212
  this.#publishSummary("loading", null);
1141
1213
  this.#missedHeartbeats = 0;
1142
1214
  for (const entry of [...this.#operations.values()].sort((left, right) => compareChalkWhiteboardV1PendingOperations(left.pending, right.pending))) {
@@ -1283,6 +1355,24 @@ var ChalkWhiteboardV1Client = class {
1283
1355
  }
1284
1356
  #disconnected(socket, closeCode) {
1285
1357
  if (socket !== this.#socket) return;
1358
+ const rejectedPresentationHello = this.#rejectedPresentationHello(closeCode);
1359
+ this.#resetDisconnectedState();
1360
+ if (!this.#started || !this.#transportAvailable) return;
1361
+ if (rejectedPresentationHello) {
1362
+ this.#useLegacyHello = true;
1363
+ this.#scheduleReconnect();
1364
+ return;
1365
+ }
1366
+ if (terminalCloseCode(closeCode)) {
1367
+ this.#failRejectedConnection(closeCode);
1368
+ return;
1369
+ }
1370
+ this.#scheduleReconnect();
1371
+ }
1372
+ #rejectedPresentationHello(closeCode) {
1373
+ return this.#phase === "authenticating" && !this.#useLegacyHello && terminalCloseCode(closeCode);
1374
+ }
1375
+ #resetDisconnectedState() {
1286
1376
  this.#socket = null;
1287
1377
  this.#clearHeartbeat();
1288
1378
  this.#clearUpdateAssemblies();
@@ -1292,15 +1382,15 @@ var ChalkWhiteboardV1Client = class {
1292
1382
  this.#canDraw = false;
1293
1383
  this.#rejectSnapshots(failure("request_snapshot", "unavailable", true, "Whiteboard connection interrupted."));
1294
1384
  this.#publishSummary("recovering", failure("start_scene_subscription", "unavailable", true, "Whiteboard connection interrupted."));
1295
- if (!this.#started || !this.#transportAvailable) return;
1296
- if (closeCode === 1008 || closeCode === 1009) {
1297
- const terminalFailure = failure("start_scene_subscription", closeCode === 1008 ? "permission_denied" : "invalid_payload", false, "Whiteboard connection was rejected.");
1298
- this.#started = false;
1299
- this.#publishSummary("failed", terminalFailure);
1300
- if (this.#initialSnapshot) rejectDeferred(this.#initialSnapshot, error(terminalFailure));
1301
- this.#rejectOperationCallers(terminalFailure);
1302
- return;
1303
- }
1385
+ }
1386
+ #failRejectedConnection(closeCode) {
1387
+ const terminalFailure = failure("start_scene_subscription", closeCode === 1008 ? "permission_denied" : "invalid_payload", false, "Whiteboard connection was rejected.");
1388
+ this.#started = false;
1389
+ this.#publishSummary("failed", terminalFailure);
1390
+ if (this.#initialSnapshot) rejectDeferred(this.#initialSnapshot, error(terminalFailure));
1391
+ this.#rejectOperationCallers(terminalFailure);
1392
+ }
1393
+ #scheduleReconnect() {
1304
1394
  this.#clearReconnect();
1305
1395
  this.#reconnectTimer = this.#clock().setTimeout(() => {
1306
1396
  this.#reconnectTimer = void 0;
@@ -1352,6 +1442,7 @@ var ChalkWhiteboardV1Client = class {
1352
1442
  capabilities: [...this.#capabilities],
1353
1443
  canDraw: this.#canDraw,
1354
1444
  canClear: this.#capabilities.includes("manageWhiteboard"),
1445
+ presenting: this.#presenting,
1355
1446
  error: this.#summaryError
1356
1447
  };
1357
1448
  try {
@@ -1473,6 +1564,9 @@ function operationName(frame) {
1473
1564
  function retryableOperationCode(code) {
1474
1565
  return code === "unavailable" || code === "overloaded" || code === "rate_limited" || code === "storage_unavailable";
1475
1566
  }
1567
+ function terminalCloseCode(closeCode) {
1568
+ return closeCode === 1008 || closeCode === 1009;
1569
+ }
1476
1570
  function mapErrorCode(code) {
1477
1571
  return code === "overloaded" || code === "rate_limited" ? "unavailable" : code;
1478
1572
  }
@@ -1589,8 +1683,18 @@ function createChatFileTransport(apiBaseURL, input) {
1589
1683
  });
1590
1684
  }
1591
1685
  function createMediaClient(apiBaseURL, input) {
1686
+ if (input.access.media.provider === "cloudflare_rtk") {
1687
+ return createRTKConnectionMediaClient(
1688
+ new CloudflareRTKClient({
1689
+ authToken: input.access.media.clientPayload.token,
1690
+ participantId: input.access.subject.participantId,
1691
+ onError: input.onFailure,
1692
+ onScreenEnded: input.onScreenEnded
1693
+ })
1694
+ );
1695
+ }
1592
1696
  const { subject } = input.access;
1593
- return new CloudflareSFUClient({
1697
+ const client = new CloudflareSFUClient({
1594
1698
  bootstrap: input.access.media.clientPayload,
1595
1699
  participantId: subject.participantId,
1596
1700
  transport: createCloudflareSFUHTTPTransport({
@@ -1604,6 +1708,36 @@ function createMediaClient(apiBaseURL, input) {
1604
1708
  onError: input.onFailure,
1605
1709
  onScreenEnded: input.onScreenEnded
1606
1710
  });
1711
+ return createSFUConnectionMediaClient(client);
1712
+ }
1713
+ function createRTKConnectionMediaClient(client) {
1714
+ return bindConnectionMediaClient(client, (input) => {
1715
+ if (!("provider" in input) || input.provider !== "cloudflare_rtk") throw new TypeError("The RealtimeKit adapter requires a Cloudflare RealtimeKit access grant");
1716
+ return client.restart(input);
1717
+ });
1718
+ }
1719
+ function createSFUConnectionMediaClient(client) {
1720
+ return bindConnectionMediaClient(client, (input) => {
1721
+ if ("provider" in input) {
1722
+ if (input.provider !== "cloudflare_sfu") throw new TypeError("The Cloudflare SFU adapter requires a Cloudflare SFU access grant");
1723
+ return client.restart(input.clientPayload);
1724
+ }
1725
+ return client.restart(input);
1726
+ });
1727
+ }
1728
+ function bindConnectionMediaClient(client, restart) {
1729
+ return {
1730
+ setLocalPublicationTarget: client.setLocalPublicationTarget.bind(client),
1731
+ observeLocalPublications: client.observeLocalPublications.bind(client),
1732
+ observeRemotePublications: client.observeRemotePublications.bind(client),
1733
+ start: client.start.bind(client),
1734
+ stop: client.stop.bind(client),
1735
+ restart,
1736
+ prepareLocalTrack: client.prepareLocalTrack.bind(client),
1737
+ clearPreparedLocalTrack: client.clearPreparedLocalTrack.bind(client),
1738
+ getSnapshot: client.getSnapshot.bind(client),
1739
+ subscribe: client.subscribe.bind(client)
1740
+ };
1607
1741
  }
1608
1742
  function createSyncClient(syncURL, input) {
1609
1743
  return createV1SyncClient({
@@ -1645,172 +1779,6 @@ function whiteboardURL(syncURL) {
1645
1779
  // src/connection/lifecycle.ts
1646
1780
  import { Clock as Clock2, Context as Context3, Data as Data2, Deferred, Duration, Effect as Effect2, Exit, Fiber, Layer as Layer3, Queue, Scope, SubscriptionRef } from "effect";
1647
1781
 
1648
- // src/access/base64-url.ts
1649
- function decodeBase64Url(value) {
1650
- const base64 = value.replaceAll("-", "+").replaceAll("_", "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
1651
- const bytes = Uint8Array.from(globalThis.atob(base64), (character) => character.charCodeAt(0));
1652
- return new TextDecoder().decode(bytes);
1653
- }
1654
-
1655
- // src/access/episode-diagnostic-credential.ts
1656
- var INTAKE_PATH = "/_internal/episode-diagnostic-events";
1657
- var RFC3339_ZONED = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
1658
- function parseEpisodeDiagnosticCredential(value) {
1659
- if (!isRecord3(value)) return null;
1660
- const token = value.token;
1661
- const expiresAt2 = value.expires_at;
1662
- const generation = value.generation;
1663
- const intakePath = value.intake_path;
1664
- if (typeof token !== "string" || !validDiagnosticCredential(token)) return null;
1665
- if (!isRFC3339Zoned(expiresAt2)) return null;
1666
- if (!Number.isSafeInteger(generation) || generation < 1) return null;
1667
- if (intakePath !== INTAKE_PATH) return null;
1668
- return Object.freeze({ token, expiresAt: expiresAt2, generation, intakePath });
1669
- }
1670
- function validEpisodeDiagnosticCredential(credential, now) {
1671
- return credential.intakePath === INTAKE_PATH && Number.isSafeInteger(credential.generation) && credential.generation > 0 && isRFC3339Zoned(credential.expiresAt) && Date.parse(credential.expiresAt) > now && validDiagnosticCredential(credential.token);
1672
- }
1673
- function validDiagnosticCredential(token) {
1674
- const segments = token.split(".");
1675
- if (segments.length !== 3) return false;
1676
- try {
1677
- const payload = JSON.parse(decodeBase64Url(segments[1] ?? ""));
1678
- return isRecord3(payload) && payload.aud === "chalk-diagnostics";
1679
- } catch {
1680
- return false;
1681
- }
1682
- }
1683
- function isRFC3339Zoned(value) {
1684
- if (typeof value !== "string") return false;
1685
- const match = RFC3339_ZONED.exec(value);
1686
- if (!match) return false;
1687
- return validTimestampFields(match) && Number.isFinite(Date.parse(value));
1688
- }
1689
- function validTimestampFields(match) {
1690
- const year = Number(match[1]);
1691
- const month = Number(match[2]);
1692
- const day = Number(match[3]);
1693
- const hour = Number(match[4]);
1694
- const minute = Number(match[5]);
1695
- const second = Number(match[6]);
1696
- const zone = match[7] ?? "";
1697
- return validCalendarDate(year, month, day) && validClockTime(hour, minute, second) && validZoneOffset(zone);
1698
- }
1699
- function inRange(value, min, max) {
1700
- return value >= min && value <= max;
1701
- }
1702
- function validCalendarDate(year, month, day) {
1703
- return inRange(month, 1, 12) && inRange(day, 1, daysInMonth(year, month));
1704
- }
1705
- function validClockTime(hour, minute, second) {
1706
- return inRange(hour, 0, 23) && inRange(minute, 0, 59) && inRange(second, 0, 59);
1707
- }
1708
- function validZoneOffset(zone) {
1709
- if (zone === "Z") return true;
1710
- return inRange(Number(zone.slice(1, 3)), 0, 23) && inRange(Number(zone.slice(4, 6)), 0, 59);
1711
- }
1712
- function daysInMonth(year, month) {
1713
- if (month === 2) return isLeapYear(year) ? 29 : 28;
1714
- return [4, 6, 9, 11].includes(month) ? 30 : 31;
1715
- }
1716
- function isLeapYear(year) {
1717
- return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
1718
- }
1719
- function isRecord3(value) {
1720
- return typeof value === "object" && value !== null && !Array.isArray(value);
1721
- }
1722
-
1723
- // src/access/grant.ts
1724
- var AccessGrantError = class extends TypeError {
1725
- code = "access.invalid";
1726
- constructor(message = "Access grant is invalid") {
1727
- super(message);
1728
- this.name = "AccessGrantError";
1729
- }
1730
- };
1731
- function parseParsedAccessGrant(value) {
1732
- if (!isRecord4(value)) throw new AccessGrantError();
1733
- const subject = parseSubject(value.subject);
1734
- const diagnostics = parseEpisodeDiagnosticCredential(value.diagnostics);
1735
- return {
1736
- subject,
1737
- sync: parseSyncAccess(value.sync),
1738
- media: parseMediaAccess(value.media),
1739
- diagnostics: diagnostics?.generation === subject.participantGeneration ? diagnostics : null
1740
- };
1741
- }
1742
- async function requireParsedAccessGrant(value) {
1743
- if (typeof Response !== "undefined" && value instanceof Response) {
1744
- if (!value.ok) throw new AccessGrantError(`Access grant request failed with HTTP ${value.status}`);
1745
- try {
1746
- return parseParsedAccessGrant(await value.json());
1747
- } catch (error2) {
1748
- if (error2 instanceof AccessGrantError) throw error2;
1749
- throw new AccessGrantError();
1750
- }
1751
- }
1752
- return parseParsedAccessGrant(value);
1753
- }
1754
- function parseSubject(value) {
1755
- if (!isRecord4(value)) throw new AccessGrantError();
1756
- return {
1757
- tenantId: requireNonEmptyString(value.tenant_id),
1758
- spaceId: requireNonEmptyString(value.space_id),
1759
- episodeId: requireNonEmptyString(value.episode_id),
1760
- participantId: requireNonEmptyString(value.participant_id),
1761
- participantGeneration: requirePositiveInteger(value.participant_generation)
1762
- };
1763
- }
1764
- function requirePositiveInteger(value) {
1765
- if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1) throw new AccessGrantError();
1766
- return value;
1767
- }
1768
- function parseSyncAccess(value) {
1769
- if (!isRecord4(value)) throw new AccessGrantError();
1770
- return {
1771
- token: requireCredential(value.token, "chalk-sync"),
1772
- expiresAt: requireDateTime(value.expires_at ?? value.expiresAt)
1773
- };
1774
- }
1775
- function parseMediaAccess(value) {
1776
- if (!isRecord4(value) || value.provider !== "cloudflare_sfu" || !isRecord4(value.client_payload ?? value.clientPayload)) throw new AccessGrantError();
1777
- const payload = value.client_payload ?? value.clientPayload;
1778
- return {
1779
- token: requireCredential(value.token, "chalk-media"),
1780
- expiresAt: requireDateTime(value.expires_at ?? value.expiresAt),
1781
- provider: value.provider,
1782
- clientPayload: {
1783
- connectionId: requireNonEmptyString(payload.connectionId),
1784
- stunServer: requireNonEmptyString(payload.stunServer)
1785
- }
1786
- };
1787
- }
1788
- function requireCredential(value, audience) {
1789
- const token = requireNonEmptyString(value);
1790
- const segments = token.split(".");
1791
- if (segments.length !== 3) throw new AccessGrantError();
1792
- let payload;
1793
- try {
1794
- payload = JSON.parse(decodeBase64Url(segments[1] ?? ""));
1795
- } catch {
1796
- throw new AccessGrantError();
1797
- }
1798
- if (!isRecord4(payload) || payload.aud !== audience) throw new AccessGrantError();
1799
- return token;
1800
- }
1801
- function requireDateTime(value) {
1802
- const dateTime = requireNonEmptyString(value);
1803
- if (!Number.isFinite(Date.parse(dateTime))) throw new AccessGrantError();
1804
- return dateTime;
1805
- }
1806
- function requireNonEmptyString(value) {
1807
- if (typeof value !== "string" || value.trim().length === 0) throw new AccessGrantError();
1808
- return value;
1809
- }
1810
- function isRecord4(value) {
1811
- return typeof value === "object" && value !== null && !Array.isArray(value);
1812
- }
1813
-
1814
1782
  // src/connection/diagnostics.ts
1815
1783
  var ConnectionDiagnostics = class {
1816
1784
  #limit;
@@ -2266,7 +2234,8 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
2266
2234
  const sync = model.sync;
2267
2235
  if (!media || !sync) return yield* Effect2.fail(lifecycleFailure("invalid_state", false, "Media recovery requires active ports"));
2268
2236
  const grant = yield* access.refresh("media_recovery", true).pipe(Effect2.mapError(accessFailure));
2269
- yield* foreign6(() => media.restart(grant.media.clientPayload));
2237
+ const restartInput = grant.media.provider === "cloudflare_sfu" ? grant.media.clientPayload : grant.media;
2238
+ yield* foreign6(() => media.restart(restartInput));
2270
2239
  model.mediaSnapshot = media.getSnapshot();
2271
2240
  yield* waitForSyncLive(sync, boundedInteger(options.recovery?.budgetMs, RECOVERY_BUDGET_MS, 1, 6e4));
2272
2241
  });
@@ -2547,7 +2516,7 @@ var EMPTY = empty({
2547
2516
  }),
2548
2517
  chat: empty({ status: "idle", messages: empty([]), pendingSends: empty([]), readReceipts: empty([]), unreadCount: 0, pagination: empty({ cursor: null, hasOlder: false, historyTruncated: false }), lastError: null }),
2549
2518
  reactions: empty({ active: empty([]) }),
2550
- whiteboard: empty({ open: false, engine: empty({ status: "unsubscribed", sceneId: null, revision: null, error: null }) })
2519
+ whiteboard: empty({ open: false, engine: empty({ status: "unsubscribed", sceneId: null, revision: null, presenting: false, error: null }) })
2551
2520
  });
2552
2521
  var SpaceStore = class _SpaceStore {
2553
2522
  #listeners = /* @__PURE__ */ new Set();
@@ -4031,7 +4000,7 @@ function sameReactions(left, right) {
4031
4000
 
4032
4001
  // src/space-client/whiteboard-controller.ts
4033
4002
  import { Context as Context9, Effect as Effect8, Layer as Layer9 } from "effect";
4034
- var EMPTY_SUMMARY = Object.freeze({ status: "unsubscribed", sceneId: null, revision: null, capabilities: Object.freeze([]), canDraw: false, canClear: false, error: null });
4003
+ var EMPTY_SUMMARY = Object.freeze({ status: "unsubscribed", sceneId: null, revision: null, capabilities: Object.freeze([]), canDraw: false, canClear: false, presenting: false, error: null });
4035
4004
  var WhiteboardControllerService = class extends Context9.Service()("@chalk/client/WhiteboardController") {
4036
4005
  };
4037
4006
  var makeWhiteboardController = (connection, store, create) => Effect8.gen(function* () {
@@ -4067,7 +4036,16 @@ var WhiteboardControllerRuntime = class {
4067
4036
  }
4068
4037
  observe(summary) {
4069
4038
  if (!this.#connected && summary.status !== "unsubscribed") return;
4070
- this.#summary = Object.freeze({ status: summary.status, sceneId: summary.sceneId, revision: summary.revision, capabilities: Object.freeze([...summary.capabilities]), canDraw: summary.canDraw, canClear: summary.canClear, error: summary.error ? Object.freeze({ ...summary.error }) : null });
4039
+ this.#summary = Object.freeze({
4040
+ status: summary.status,
4041
+ sceneId: summary.sceneId,
4042
+ revision: summary.revision,
4043
+ capabilities: Object.freeze([...summary.capabilities]),
4044
+ canDraw: summary.canDraw,
4045
+ canClear: summary.canClear,
4046
+ presenting: summary.presenting,
4047
+ error: summary.error ? Object.freeze({ ...summary.error }) : null
4048
+ });
4071
4049
  this.#publish();
4072
4050
  }
4073
4051
  #bind(ports) {
@@ -4082,14 +4060,14 @@ var WhiteboardControllerRuntime = class {
4082
4060
  this.#publish();
4083
4061
  }
4084
4062
  #publish() {
4085
- const slice = Object.freeze({ open: this.#summary.status !== "unsubscribed", engine: Object.freeze({ status: this.#summary.status, sceneId: this.#summary.sceneId, revision: this.#summary.revision, error: this.#summary.error }) });
4063
+ const slice = Object.freeze({ open: this.#summary.status !== "unsubscribed", engine: Object.freeze({ status: this.#summary.status, sceneId: this.#summary.sceneId, revision: this.#summary.revision, presenting: this.#summary.presenting, error: this.#summary.error }) });
4086
4064
  const current = this.#store.getSnapshot().whiteboard;
4087
4065
  if (sameWhiteboard(current, slice)) return;
4088
4066
  this.#store.updateWhiteboard(slice);
4089
4067
  }
4090
4068
  };
4091
4069
  function sameWhiteboard(left, right) {
4092
- return left.open === right.open && left.engine.status === right.engine.status && left.engine.sceneId === right.engine.sceneId && left.engine.revision === right.engine.revision && sameError2(left.engine.error, right.engine.error);
4070
+ return left.open === right.open && left.engine.status === right.engine.status && left.engine.sceneId === right.engine.sceneId && left.engine.revision === right.engine.revision && left.engine.presenting === right.engine.presenting && sameError2(left.engine.error, right.engine.error);
4093
4071
  }
4094
4072
  function sameError2(left, right) {
4095
4073
  if (left === right) return true;
@@ -4668,7 +4646,7 @@ function stripTrailingUrlSlash(value) {
4668
4646
  return value.endsWith("/") ? value.slice(0, -1) : value;
4669
4647
  }
4670
4648
  function intakeResponse(value) {
4671
- if (!isRecord5(value) || typeof value.diagnosticReference !== "string" || !Number.isSafeInteger(value.committedCursor)) return null;
4649
+ if (!isRecord3(value) || typeof value.diagnosticReference !== "string" || !Number.isSafeInteger(value.committedCursor)) return null;
4672
4650
  if (!Array.isArray(value.accepted) || !Array.isArray(value.duplicates) || !Array.isArray(value.conflicts)) return null;
4673
4651
  return { accepted: value.accepted, duplicates: value.duplicates, conflicts: value.conflicts };
4674
4652
  }
@@ -4679,14 +4657,14 @@ function completeDelivery(delivery, expected) {
4679
4657
  function intakeIds(items, expected, cursorRequired) {
4680
4658
  const ids = [];
4681
4659
  for (const item of items) {
4682
- if (!isRecord5(item) || typeof item.eventId !== "string" || !expected.has(item.eventId)) return null;
4660
+ if (!isRecord3(item) || typeof item.eventId !== "string" || !expected.has(item.eventId)) return null;
4683
4661
  if (cursorRequired && !Number.isSafeInteger(item.cursor)) return null;
4684
4662
  if (!cursorRequired && item.code !== "fingerprint_mismatch") return null;
4685
4663
  ids.push(item.eventId);
4686
4664
  }
4687
4665
  return ids;
4688
4666
  }
4689
- function isRecord5(value) {
4667
+ function isRecord3(value) {
4690
4668
  return typeof value === "object" && value !== null && !Array.isArray(value);
4691
4669
  }
4692
4670
 
@@ -4774,7 +4752,7 @@ var makeSpaceClientCoreLayer = (options, platform = {}) => {
4774
4752
  now: baseDependencies.clock.now,
4775
4753
  setTimeout: baseDependencies.clock.setTimeout,
4776
4754
  clearTimeout: baseDependencies.clock.clearTimeout,
4777
- release: { id: "chalk-client@4.1.4" }
4755
+ release: { id: "chalk-client@4.1.7" }
4778
4756
  });
4779
4757
  const diagnosticSyncClients = /* @__PURE__ */ new Set();
4780
4758
  const dependencies = {