@q9labsai/chalk-client 4.1.5 → 4.1.8

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 (60) 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-DI7C5UEM.js → chunk-V6P6EHYO.js} +87 -175
  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/index.d.ts +1 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +20 -2
  23. package/dist/media/client.d.ts.map +1 -1
  24. package/dist/media/index.d.ts +2 -1
  25. package/dist/media/index.d.ts.map +1 -1
  26. package/dist/media/index.js +5 -1
  27. package/dist/media/observers.d.ts +3 -0
  28. package/dist/media/observers.d.ts.map +1 -0
  29. package/dist/media/plane.d.ts +25 -0
  30. package/dist/media/plane.d.ts.map +1 -1
  31. package/dist/media/rtk.d.ts +101 -0
  32. package/dist/media/rtk.d.ts.map +1 -0
  33. package/dist/media/target.d.ts +10 -0
  34. package/dist/media/target.d.ts.map +1 -0
  35. package/dist/public-invites/client.d.ts +4 -0
  36. package/dist/public-invites/client.d.ts.map +1 -0
  37. package/dist/public-invites/index.d.ts +5 -0
  38. package/dist/public-invites/index.d.ts.map +1 -0
  39. package/dist/public-invites/index.js +18 -0
  40. package/dist/public-invites/types.d.ts +67 -0
  41. package/dist/public-invites/types.d.ts.map +1 -0
  42. package/dist/server/{chunk-A6YMG5FM.js → chunk-C4XITY5U.js} +1 -1
  43. package/dist/server/client.d.ts.map +1 -1
  44. package/dist/server/index.cjs +72 -19
  45. package/dist/server/index.d.cts +1 -1
  46. package/dist/server/index.d.ts +1 -1
  47. package/dist/server/index.d.ts.map +1 -1
  48. package/dist/server/index.js +72 -19
  49. package/dist/server/server-only.js +1 -1
  50. package/dist/server/transport.d.cts +1 -1
  51. package/dist/server/transport.d.ts +1 -1
  52. package/dist/server/transport.d.ts.map +1 -1
  53. package/dist/server/types.d.cts +40 -0
  54. package/dist/server/types.d.ts +40 -0
  55. package/dist/server/types.d.ts.map +1 -1
  56. package/dist/whiteboard/types.d.ts +1 -1
  57. package/dist/whiteboard/types.d.ts.map +1 -1
  58. package/dist/whiteboard/v1-client.d.ts +1 -1
  59. package/dist/whiteboard/v1-client.d.ts.map +1 -1
  60. 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);
@@ -873,6 +887,7 @@ var ChalkWhiteboardV1Client = class {
873
887
  #listeners = /* @__PURE__ */ new Set();
874
888
  #operations = /* @__PURE__ */ new Map();
875
889
  #reservedOperationIds = /* @__PURE__ */ new Set();
890
+ #pendingStoreWrites = /* @__PURE__ */ new Set();
876
891
  #operationRetryTimers = /* @__PURE__ */ new Map();
877
892
  #snapshots = /* @__PURE__ */ new Map();
878
893
  #updateAssembler = new WhiteboardV1UpdateAssembler();
@@ -894,6 +909,7 @@ var ChalkWhiteboardV1Client = class {
894
909
  #latestSnapshot = null;
895
910
  #initialSnapshot = null;
896
911
  #startPromise = null;
912
+ #stopPromise = null;
897
913
  #reconnectTimer;
898
914
  #heartbeatTimer;
899
915
  #missedHeartbeats = 0;
@@ -912,6 +928,7 @@ var ChalkWhiteboardV1Client = class {
912
928
  this.#revision = options.cursor?.revision ?? null;
913
929
  }
914
930
  startSceneSubscription() {
931
+ if (this.#stopPromise) return this.#stopPromise.then(() => this.startSceneSubscription());
915
932
  if (this.#started && this.#startPromise) return this.#startPromise;
916
933
  this.#started = true;
917
934
  this.#useLegacyHello = false;
@@ -925,6 +942,18 @@ var ChalkWhiteboardV1Client = class {
925
942
  return this.#startPromise;
926
943
  }
927
944
  stopSceneSubscription() {
945
+ if (this.#stopPromise) return this.#stopPromise;
946
+ if (this.#phase === "stopped" && !this.#started) return Promise.resolve();
947
+ this.#stopPromise = Promise.resolve().then(async () => {
948
+ while (this.#pendingStoreWrites.size > 0) {
949
+ await Promise.allSettled([...this.#pendingStoreWrites]);
950
+ }
951
+ this.#stopNow();
952
+ this.#stopPromise = null;
953
+ });
954
+ return this.#stopPromise;
955
+ }
956
+ #stopNow() {
928
957
  this.#started = false;
929
958
  this.#startupGeneration += 1;
930
959
  this.#unsubscribeLifecycle?.();
@@ -1270,11 +1299,19 @@ var ChalkWhiteboardV1Client = class {
1270
1299
  throw error(failure("submit_update", "unavailable", true, "Whiteboard operation byte capacity is full."));
1271
1300
  }
1272
1301
  this.#reservedOperationIds.add(frame.operation_id);
1302
+ let storeWrite;
1303
+ try {
1304
+ storeWrite = this.#store.put(pending);
1305
+ } catch (cause) {
1306
+ throw error(failure(operationName(frame), "unavailable", true, "Unable to persist the whiteboard operation."), cause);
1307
+ }
1308
+ this.#pendingStoreWrites.add(storeWrite);
1273
1309
  try {
1274
- await this.#store.put(pending);
1310
+ await storeWrite;
1275
1311
  } catch (cause) {
1276
1312
  throw error(failure(operationName(frame), "unavailable", true, "Unable to persist the whiteboard operation."), cause);
1277
1313
  } finally {
1314
+ this.#pendingStoreWrites.delete(storeWrite);
1278
1315
  this.#reservedOperationIds.delete(frame.operation_id);
1279
1316
  }
1280
1317
  let deferred;
@@ -1669,8 +1706,18 @@ function createChatFileTransport(apiBaseURL, input) {
1669
1706
  });
1670
1707
  }
1671
1708
  function createMediaClient(apiBaseURL, input) {
1709
+ if (input.access.media.provider === "cloudflare_rtk") {
1710
+ return createRTKConnectionMediaClient(
1711
+ new CloudflareRTKClient({
1712
+ authToken: input.access.media.clientPayload.token,
1713
+ participantId: input.access.subject.participantId,
1714
+ onError: input.onFailure,
1715
+ onScreenEnded: input.onScreenEnded
1716
+ })
1717
+ );
1718
+ }
1672
1719
  const { subject } = input.access;
1673
- return new CloudflareSFUClient({
1720
+ const client = new CloudflareSFUClient({
1674
1721
  bootstrap: input.access.media.clientPayload,
1675
1722
  participantId: subject.participantId,
1676
1723
  transport: createCloudflareSFUHTTPTransport({
@@ -1684,6 +1731,36 @@ function createMediaClient(apiBaseURL, input) {
1684
1731
  onError: input.onFailure,
1685
1732
  onScreenEnded: input.onScreenEnded
1686
1733
  });
1734
+ return createSFUConnectionMediaClient(client);
1735
+ }
1736
+ function createRTKConnectionMediaClient(client) {
1737
+ return bindConnectionMediaClient(client, (input) => {
1738
+ if (!("provider" in input) || input.provider !== "cloudflare_rtk") throw new TypeError("The RealtimeKit adapter requires a Cloudflare RealtimeKit access grant");
1739
+ return client.restart(input);
1740
+ });
1741
+ }
1742
+ function createSFUConnectionMediaClient(client) {
1743
+ return bindConnectionMediaClient(client, (input) => {
1744
+ if ("provider" in input) {
1745
+ if (input.provider !== "cloudflare_sfu") throw new TypeError("The Cloudflare SFU adapter requires a Cloudflare SFU access grant");
1746
+ return client.restart(input.clientPayload);
1747
+ }
1748
+ return client.restart(input);
1749
+ });
1750
+ }
1751
+ function bindConnectionMediaClient(client, restart) {
1752
+ return {
1753
+ setLocalPublicationTarget: client.setLocalPublicationTarget.bind(client),
1754
+ observeLocalPublications: client.observeLocalPublications.bind(client),
1755
+ observeRemotePublications: client.observeRemotePublications.bind(client),
1756
+ start: client.start.bind(client),
1757
+ stop: client.stop.bind(client),
1758
+ restart,
1759
+ prepareLocalTrack: client.prepareLocalTrack.bind(client),
1760
+ clearPreparedLocalTrack: client.clearPreparedLocalTrack.bind(client),
1761
+ getSnapshot: client.getSnapshot.bind(client),
1762
+ subscribe: client.subscribe.bind(client)
1763
+ };
1687
1764
  }
1688
1765
  function createSyncClient(syncURL, input) {
1689
1766
  return createV1SyncClient({
@@ -1725,172 +1802,6 @@ function whiteboardURL(syncURL) {
1725
1802
  // src/connection/lifecycle.ts
1726
1803
  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";
1727
1804
 
1728
- // src/access/base64-url.ts
1729
- function decodeBase64Url(value) {
1730
- const base64 = value.replaceAll("-", "+").replaceAll("_", "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
1731
- const bytes = Uint8Array.from(globalThis.atob(base64), (character) => character.charCodeAt(0));
1732
- return new TextDecoder().decode(bytes);
1733
- }
1734
-
1735
- // src/access/episode-diagnostic-credential.ts
1736
- var INTAKE_PATH = "/_internal/episode-diagnostic-events";
1737
- var RFC3339_ZONED = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
1738
- function parseEpisodeDiagnosticCredential(value) {
1739
- if (!isRecord3(value)) return null;
1740
- const token = value.token;
1741
- const expiresAt2 = value.expires_at;
1742
- const generation = value.generation;
1743
- const intakePath = value.intake_path;
1744
- if (typeof token !== "string" || !validDiagnosticCredential(token)) return null;
1745
- if (!isRFC3339Zoned(expiresAt2)) return null;
1746
- if (!Number.isSafeInteger(generation) || generation < 1) return null;
1747
- if (intakePath !== INTAKE_PATH) return null;
1748
- return Object.freeze({ token, expiresAt: expiresAt2, generation, intakePath });
1749
- }
1750
- function validEpisodeDiagnosticCredential(credential, now) {
1751
- return credential.intakePath === INTAKE_PATH && Number.isSafeInteger(credential.generation) && credential.generation > 0 && isRFC3339Zoned(credential.expiresAt) && Date.parse(credential.expiresAt) > now && validDiagnosticCredential(credential.token);
1752
- }
1753
- function validDiagnosticCredential(token) {
1754
- const segments = token.split(".");
1755
- if (segments.length !== 3) return false;
1756
- try {
1757
- const payload = JSON.parse(decodeBase64Url(segments[1] ?? ""));
1758
- return isRecord3(payload) && payload.aud === "chalk-diagnostics";
1759
- } catch {
1760
- return false;
1761
- }
1762
- }
1763
- function isRFC3339Zoned(value) {
1764
- if (typeof value !== "string") return false;
1765
- const match = RFC3339_ZONED.exec(value);
1766
- if (!match) return false;
1767
- return validTimestampFields(match) && Number.isFinite(Date.parse(value));
1768
- }
1769
- function validTimestampFields(match) {
1770
- const year = Number(match[1]);
1771
- const month = Number(match[2]);
1772
- const day = Number(match[3]);
1773
- const hour = Number(match[4]);
1774
- const minute = Number(match[5]);
1775
- const second = Number(match[6]);
1776
- const zone = match[7] ?? "";
1777
- return validCalendarDate(year, month, day) && validClockTime(hour, minute, second) && validZoneOffset(zone);
1778
- }
1779
- function inRange(value, min, max) {
1780
- return value >= min && value <= max;
1781
- }
1782
- function validCalendarDate(year, month, day) {
1783
- return inRange(month, 1, 12) && inRange(day, 1, daysInMonth(year, month));
1784
- }
1785
- function validClockTime(hour, minute, second) {
1786
- return inRange(hour, 0, 23) && inRange(minute, 0, 59) && inRange(second, 0, 59);
1787
- }
1788
- function validZoneOffset(zone) {
1789
- if (zone === "Z") return true;
1790
- return inRange(Number(zone.slice(1, 3)), 0, 23) && inRange(Number(zone.slice(4, 6)), 0, 59);
1791
- }
1792
- function daysInMonth(year, month) {
1793
- if (month === 2) return isLeapYear(year) ? 29 : 28;
1794
- return [4, 6, 9, 11].includes(month) ? 30 : 31;
1795
- }
1796
- function isLeapYear(year) {
1797
- return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
1798
- }
1799
- function isRecord3(value) {
1800
- return typeof value === "object" && value !== null && !Array.isArray(value);
1801
- }
1802
-
1803
- // src/access/grant.ts
1804
- var AccessGrantError = class extends TypeError {
1805
- code = "access.invalid";
1806
- constructor(message = "Access grant is invalid") {
1807
- super(message);
1808
- this.name = "AccessGrantError";
1809
- }
1810
- };
1811
- function parseParsedAccessGrant(value) {
1812
- if (!isRecord4(value)) throw new AccessGrantError();
1813
- const subject = parseSubject(value.subject);
1814
- const diagnostics = parseEpisodeDiagnosticCredential(value.diagnostics);
1815
- return {
1816
- subject,
1817
- sync: parseSyncAccess(value.sync),
1818
- media: parseMediaAccess(value.media),
1819
- diagnostics: diagnostics?.generation === subject.participantGeneration ? diagnostics : null
1820
- };
1821
- }
1822
- async function requireParsedAccessGrant(value) {
1823
- if (typeof Response !== "undefined" && value instanceof Response) {
1824
- if (!value.ok) throw new AccessGrantError(`Access grant request failed with HTTP ${value.status}`);
1825
- try {
1826
- return parseParsedAccessGrant(await value.json());
1827
- } catch (error2) {
1828
- if (error2 instanceof AccessGrantError) throw error2;
1829
- throw new AccessGrantError();
1830
- }
1831
- }
1832
- return parseParsedAccessGrant(value);
1833
- }
1834
- function parseSubject(value) {
1835
- if (!isRecord4(value)) throw new AccessGrantError();
1836
- return {
1837
- tenantId: requireNonEmptyString(value.tenant_id),
1838
- spaceId: requireNonEmptyString(value.space_id),
1839
- episodeId: requireNonEmptyString(value.episode_id),
1840
- participantId: requireNonEmptyString(value.participant_id),
1841
- participantGeneration: requirePositiveInteger(value.participant_generation)
1842
- };
1843
- }
1844
- function requirePositiveInteger(value) {
1845
- if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1) throw new AccessGrantError();
1846
- return value;
1847
- }
1848
- function parseSyncAccess(value) {
1849
- if (!isRecord4(value)) throw new AccessGrantError();
1850
- return {
1851
- token: requireCredential(value.token, "chalk-sync"),
1852
- expiresAt: requireDateTime(value.expires_at ?? value.expiresAt)
1853
- };
1854
- }
1855
- function parseMediaAccess(value) {
1856
- if (!isRecord4(value) || value.provider !== "cloudflare_sfu" || !isRecord4(value.client_payload ?? value.clientPayload)) throw new AccessGrantError();
1857
- const payload = value.client_payload ?? value.clientPayload;
1858
- return {
1859
- token: requireCredential(value.token, "chalk-media"),
1860
- expiresAt: requireDateTime(value.expires_at ?? value.expiresAt),
1861
- provider: value.provider,
1862
- clientPayload: {
1863
- connectionId: requireNonEmptyString(payload.connectionId),
1864
- stunServer: requireNonEmptyString(payload.stunServer)
1865
- }
1866
- };
1867
- }
1868
- function requireCredential(value, audience) {
1869
- const token = requireNonEmptyString(value);
1870
- const segments = token.split(".");
1871
- if (segments.length !== 3) throw new AccessGrantError();
1872
- let payload;
1873
- try {
1874
- payload = JSON.parse(decodeBase64Url(segments[1] ?? ""));
1875
- } catch {
1876
- throw new AccessGrantError();
1877
- }
1878
- if (!isRecord4(payload) || payload.aud !== audience) throw new AccessGrantError();
1879
- return token;
1880
- }
1881
- function requireDateTime(value) {
1882
- const dateTime = requireNonEmptyString(value);
1883
- if (!Number.isFinite(Date.parse(dateTime))) throw new AccessGrantError();
1884
- return dateTime;
1885
- }
1886
- function requireNonEmptyString(value) {
1887
- if (typeof value !== "string" || value.trim().length === 0) throw new AccessGrantError();
1888
- return value;
1889
- }
1890
- function isRecord4(value) {
1891
- return typeof value === "object" && value !== null && !Array.isArray(value);
1892
- }
1893
-
1894
1805
  // src/connection/diagnostics.ts
1895
1806
  var ConnectionDiagnostics = class {
1896
1807
  #limit;
@@ -2346,7 +2257,8 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
2346
2257
  const sync = model.sync;
2347
2258
  if (!media || !sync) return yield* Effect2.fail(lifecycleFailure("invalid_state", false, "Media recovery requires active ports"));
2348
2259
  const grant = yield* access.refresh("media_recovery", true).pipe(Effect2.mapError(accessFailure));
2349
- yield* foreign6(() => media.restart(grant.media.clientPayload));
2260
+ const restartInput = grant.media.provider === "cloudflare_sfu" ? grant.media.clientPayload : grant.media;
2261
+ yield* foreign6(() => media.restart(restartInput));
2350
2262
  model.mediaSnapshot = media.getSnapshot();
2351
2263
  yield* waitForSyncLive(sync, boundedInteger(options.recovery?.budgetMs, RECOVERY_BUDGET_MS, 1, 6e4));
2352
2264
  });
@@ -4757,7 +4669,7 @@ function stripTrailingUrlSlash(value) {
4757
4669
  return value.endsWith("/") ? value.slice(0, -1) : value;
4758
4670
  }
4759
4671
  function intakeResponse(value) {
4760
- if (!isRecord5(value) || typeof value.diagnosticReference !== "string" || !Number.isSafeInteger(value.committedCursor)) return null;
4672
+ if (!isRecord3(value) || typeof value.diagnosticReference !== "string" || !Number.isSafeInteger(value.committedCursor)) return null;
4761
4673
  if (!Array.isArray(value.accepted) || !Array.isArray(value.duplicates) || !Array.isArray(value.conflicts)) return null;
4762
4674
  return { accepted: value.accepted, duplicates: value.duplicates, conflicts: value.conflicts };
4763
4675
  }
@@ -4768,14 +4680,14 @@ function completeDelivery(delivery, expected) {
4768
4680
  function intakeIds(items, expected, cursorRequired) {
4769
4681
  const ids = [];
4770
4682
  for (const item of items) {
4771
- if (!isRecord5(item) || typeof item.eventId !== "string" || !expected.has(item.eventId)) return null;
4683
+ if (!isRecord3(item) || typeof item.eventId !== "string" || !expected.has(item.eventId)) return null;
4772
4684
  if (cursorRequired && !Number.isSafeInteger(item.cursor)) return null;
4773
4685
  if (!cursorRequired && item.code !== "fingerprint_mismatch") return null;
4774
4686
  ids.push(item.eventId);
4775
4687
  }
4776
4688
  return ids;
4777
4689
  }
4778
- function isRecord5(value) {
4690
+ function isRecord3(value) {
4779
4691
  return typeof value === "object" && value !== null && !Array.isArray(value);
4780
4692
  }
4781
4693
 
@@ -4863,7 +4775,7 @@ var makeSpaceClientCoreLayer = (options, platform = {}) => {
4863
4775
  now: baseDependencies.clock.now,
4864
4776
  setTimeout: baseDependencies.clock.setTimeout,
4865
4777
  clearTimeout: baseDependencies.clock.clearTimeout,
4866
- release: { id: "chalk-client@4.1.5" }
4778
+ release: { id: "chalk-client@4.1.8" }
4867
4779
  });
4868
4780
  const diagnosticSyncClients = /* @__PURE__ */ new Set();
4869
4781
  const dependencies = {