@q9labsai/chalk-client 5.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +39 -0
  3. package/dist/access/base64-url.d.ts +0 -1
  4. package/dist/access/grant.d.ts +0 -1
  5. package/dist/access/manager.d.ts +0 -1
  6. package/dist/chat-files/http-transport.d.ts +0 -1
  7. package/dist/chat-files/index.d.ts +0 -1
  8. package/dist/chat-files/types.d.ts +0 -1
  9. package/dist/{chunk-2HJIJVE2.js → chunk-3ZN7IXOZ.js} +341 -3
  10. package/dist/{chunk-O2NOZKJP.js → chunk-DWKTGBHR.js} +20 -8
  11. package/dist/{chunk-AVXJ4HQU.js → chunk-K7PNW3KI.js} +156 -35
  12. package/dist/{chunk-CMVO7USN.js → chunk-KHHULLBZ.js} +1 -1
  13. package/dist/{chunk-IPAN4LUZ.js → chunk-RJRSLXVT.js} +863 -409
  14. package/dist/client.d.ts +76 -1
  15. package/dist/collaboration/index.d.ts +0 -1
  16. package/dist/collaboration/types.d.ts +0 -1
  17. package/dist/collaboration/wire.d.ts +0 -1
  18. package/dist/connection/dependencies.d.ts +5 -3
  19. package/dist/connection/diagnostics.d.ts +1 -2
  20. package/dist/connection/index.d.ts +1 -1
  21. package/dist/connection/lifecycle.d.ts +2 -3
  22. package/dist/connection/media-devices.d.ts +0 -1
  23. package/dist/connection/production.d.ts +0 -1
  24. package/dist/connection/types.d.ts +2 -2
  25. package/dist/effect.d.ts +0 -1
  26. package/dist/effect.js +88 -4
  27. package/dist/foreground-deadline.d.ts +0 -1
  28. package/dist/generated/http-api.d.ts +76 -1
  29. package/dist/generated/schemas.d.ts +366 -7
  30. package/dist/generated/sync.d.ts +0 -1
  31. package/dist/generated/whiteboard-v1.d.ts +50 -7
  32. package/dist/index.d.ts +0 -1
  33. package/dist/index.js +7 -5
  34. package/dist/media/camera-uplink.d.ts +0 -1
  35. package/dist/media/capture-error.d.ts +0 -1
  36. package/dist/media/client.d.ts +3 -2
  37. package/dist/media/cloudflare-sfu.d.ts +0 -1
  38. package/dist/media/index.d.ts +0 -1
  39. package/dist/media/index.js +1 -1
  40. package/dist/media/observers.d.ts +0 -1
  41. package/dist/media/plane.d.ts +0 -1
  42. package/dist/media/remote-track-response.d.ts +0 -1
  43. package/dist/media/target.d.ts +0 -1
  44. package/dist/media/tracks.d.ts +0 -1
  45. package/dist/media/transport.d.ts +0 -1
  46. package/dist/media/types.d.ts +0 -1
  47. package/dist/public-invites/client.d.ts +0 -1
  48. package/dist/public-invites/index.d.ts +0 -1
  49. package/dist/public-invites/index.js +2 -2
  50. package/dist/public-invites/types.d.ts +0 -1
  51. package/dist/server/access-refresh.d.ts +0 -1
  52. package/dist/server/{chunk-C4XITY5U.js → chunk-XQOQTCUL.js} +22 -0
  53. package/dist/server/client.d.ts +0 -1
  54. package/dist/server/errors.d.cts +8 -1
  55. package/dist/server/errors.d.ts +8 -2
  56. package/dist/server/index.cjs +31 -7
  57. package/dist/server/index.d.cts +1 -1
  58. package/dist/server/index.d.ts +1 -2
  59. package/dist/server/index.js +10 -7
  60. package/dist/server/server-only.d.ts +0 -1
  61. package/dist/server/server-only.js +1 -1
  62. package/dist/server/transport.d.ts +0 -1
  63. package/dist/server/types.d.ts +0 -1
  64. package/dist/space-client/chat-controller-helpers.d.ts +0 -1
  65. package/dist/space-client/chat-controller.d.ts +0 -1
  66. package/dist/space-client/controller-effects.d.ts +0 -1
  67. package/dist/space-client/core.d.ts +0 -1
  68. package/dist/space-client/effect.d.ts +0 -1
  69. package/dist/space-client/errors.d.ts +4 -2
  70. package/dist/space-client/index.d.ts +1 -2
  71. package/dist/space-client/media-controller.d.ts +3 -2
  72. package/dist/space-client/media-device-selection.d.ts +0 -1
  73. package/dist/space-client/offline-actions-storage.d.ts +0 -1
  74. package/dist/space-client/offline-actions.d.ts +0 -1
  75. package/dist/space-client/participants-controller.d.ts +0 -1
  76. package/dist/space-client/promise-facade.d.ts +0 -1
  77. package/dist/space-client/reactions-controller.d.ts +0 -1
  78. package/dist/space-client/recording-controller.d.ts +0 -1
  79. package/dist/space-client/reload-rejoin.d.ts +0 -1
  80. package/dist/space-client/space-client.d.ts +0 -1
  81. package/dist/space-client/store.d.ts +0 -1
  82. package/dist/space-client/types.d.ts +7 -2
  83. package/dist/space-client/whiteboard-controller.d.ts +0 -1
  84. package/dist/sync/browser.d.ts +0 -1
  85. package/dist/sync/canonical.d.ts +0 -1
  86. package/dist/sync/dial-deadline.d.ts +3 -0
  87. package/dist/sync/errors.d.ts +0 -1
  88. package/dist/sync/index.d.ts +0 -1
  89. package/dist/sync/index.js +1 -1
  90. package/dist/sync/react-native.d.ts +0 -1
  91. package/dist/sync/types.d.ts +0 -1
  92. package/dist/sync/v1-client.d.ts +2 -2
  93. package/dist/sync/v1-codec.d.ts +0 -1
  94. package/dist/sync/v1-collaboration-state.d.ts +1 -1
  95. package/dist/sync/v1-command-scheduler.d.ts +0 -1
  96. package/dist/sync/v1-create.d.ts +0 -1
  97. package/dist/sync/v1-deferred.d.ts +0 -1
  98. package/dist/sync/v1-error.d.ts +0 -1
  99. package/dist/sync/v1-frame-signature.d.ts +0 -1
  100. package/dist/sync/v1-health.d.ts +15 -0
  101. package/dist/sync/v1-live-target-coordinator.d.ts +0 -1
  102. package/dist/sync/v1-persistence.d.ts +0 -1
  103. package/dist/sync/v1-platform-persistence.d.ts +0 -1
  104. package/dist/sync/v1-reducer.d.ts +3 -1
  105. package/dist/sync/v1-types.d.ts +10 -1
  106. package/dist/webhooks/errors.d.ts +0 -1
  107. package/dist/webhooks/generated/event-v1.d.ts +0 -1
  108. package/dist/webhooks/generated/fixtures-v1.d.ts +0 -1
  109. package/dist/webhooks/index.d.ts +0 -1
  110. package/dist/webhooks/processor.d.ts +0 -1
  111. package/dist/webhooks/server-only.d.ts +0 -1
  112. package/dist/webhooks/test.d.ts +0 -1
  113. package/dist/webhooks/types.d.ts +0 -1
  114. package/dist/webhooks/validate.d.ts +0 -1
  115. package/dist/webhooks/verify.d.ts +0 -1
  116. package/dist/whiteboard/file-download-transport.d.ts +0 -1
  117. package/dist/whiteboard/index.d.ts +5 -1
  118. package/dist/whiteboard/types.d.ts +28 -2
  119. package/dist/whiteboard/v1-client.d.ts +3 -2
  120. package/dist/whiteboard/v1-codec.d.ts +0 -1
  121. package/dist/whiteboard/v1-create.d.ts +0 -1
  122. package/dist/whiteboard/v1-multipart.d.ts +0 -1
  123. package/dist/whiteboard/v1-persistence.d.ts +0 -1
  124. package/package.json +4 -2
  125. package/dist/access/base64-url.d.ts.map +0 -1
  126. package/dist/access/grant.d.ts.map +0 -1
  127. package/dist/access/grant.test.helpers.d.ts +0 -10
  128. package/dist/access/grant.test.helpers.d.ts.map +0 -1
  129. package/dist/access/manager.d.ts.map +0 -1
  130. package/dist/chat-files/http-transport.d.ts.map +0 -1
  131. package/dist/chat-files/index.d.ts.map +0 -1
  132. package/dist/chat-files/types.d.ts.map +0 -1
  133. package/dist/client.d.ts.map +0 -1
  134. package/dist/collaboration/index.d.ts.map +0 -1
  135. package/dist/collaboration/types.d.ts.map +0 -1
  136. package/dist/collaboration/wire.d.ts.map +0 -1
  137. package/dist/connection/dependencies.d.ts.map +0 -1
  138. package/dist/connection/diagnostics.d.ts.map +0 -1
  139. package/dist/connection/index.d.ts.map +0 -1
  140. package/dist/connection/lifecycle.d.ts.map +0 -1
  141. package/dist/connection/media-devices.d.ts.map +0 -1
  142. package/dist/connection/production.d.ts.map +0 -1
  143. package/dist/connection/types.d.ts.map +0 -1
  144. package/dist/effect.d.ts.map +0 -1
  145. package/dist/foreground-deadline.d.ts.map +0 -1
  146. package/dist/generated/http-api.d.ts.map +0 -1
  147. package/dist/generated/schemas.d.ts.map +0 -1
  148. package/dist/generated/sync.d.ts.map +0 -1
  149. package/dist/generated/whiteboard-v1.d.ts.map +0 -1
  150. package/dist/index.d.ts.map +0 -1
  151. package/dist/media/camera-uplink.d.ts.map +0 -1
  152. package/dist/media/capture-error.d.ts.map +0 -1
  153. package/dist/media/client.d.ts.map +0 -1
  154. package/dist/media/cloudflare-sfu.d.ts.map +0 -1
  155. package/dist/media/index.d.ts.map +0 -1
  156. package/dist/media/observers.d.ts.map +0 -1
  157. package/dist/media/plane.d.ts.map +0 -1
  158. package/dist/media/remote-track-response.d.ts.map +0 -1
  159. package/dist/media/target.d.ts.map +0 -1
  160. package/dist/media/tracks.d.ts.map +0 -1
  161. package/dist/media/transport.d.ts.map +0 -1
  162. package/dist/media/types.d.ts.map +0 -1
  163. package/dist/public-invites/client.d.ts.map +0 -1
  164. package/dist/public-invites/index.d.ts.map +0 -1
  165. package/dist/public-invites/types.d.ts.map +0 -1
  166. package/dist/server/access-refresh.d.ts.map +0 -1
  167. package/dist/server/client.d.ts.map +0 -1
  168. package/dist/server/errors.d.ts.map +0 -1
  169. package/dist/server/index.d.ts.map +0 -1
  170. package/dist/server/server-only.d.ts.map +0 -1
  171. package/dist/server/transport.d.ts.map +0 -1
  172. package/dist/server/types.d.ts.map +0 -1
  173. package/dist/space-client/chat-controller-helpers.d.ts.map +0 -1
  174. package/dist/space-client/chat-controller.d.ts.map +0 -1
  175. package/dist/space-client/controller-effects.d.ts.map +0 -1
  176. package/dist/space-client/core.d.ts.map +0 -1
  177. package/dist/space-client/core.test.helpers.d.ts +0 -15
  178. package/dist/space-client/core.test.helpers.d.ts.map +0 -1
  179. package/dist/space-client/effect.d.ts.map +0 -1
  180. package/dist/space-client/errors.d.ts.map +0 -1
  181. package/dist/space-client/index.d.ts.map +0 -1
  182. package/dist/space-client/media-controller.d.ts.map +0 -1
  183. package/dist/space-client/media-device-selection.d.ts.map +0 -1
  184. package/dist/space-client/offline-actions-storage.d.ts.map +0 -1
  185. package/dist/space-client/offline-actions.d.ts.map +0 -1
  186. package/dist/space-client/participants-controller.d.ts.map +0 -1
  187. package/dist/space-client/promise-facade.d.ts.map +0 -1
  188. package/dist/space-client/reactions-controller.d.ts.map +0 -1
  189. package/dist/space-client/recording-controller.d.ts.map +0 -1
  190. package/dist/space-client/reload-rejoin.d.ts.map +0 -1
  191. package/dist/space-client/space-client.d.ts.map +0 -1
  192. package/dist/space-client/store.d.ts.map +0 -1
  193. package/dist/space-client/terminal.test.helpers.d.ts +0 -136
  194. package/dist/space-client/terminal.test.helpers.d.ts.map +0 -1
  195. package/dist/space-client/types.d.ts.map +0 -1
  196. package/dist/space-client/whiteboard-controller.d.ts.map +0 -1
  197. package/dist/sync/browser.d.ts.map +0 -1
  198. package/dist/sync/canonical.d.ts.map +0 -1
  199. package/dist/sync/errors.d.ts.map +0 -1
  200. package/dist/sync/index.d.ts.map +0 -1
  201. package/dist/sync/react-native.d.ts.map +0 -1
  202. package/dist/sync/types.d.ts.map +0 -1
  203. package/dist/sync/v1-client.d.ts.map +0 -1
  204. package/dist/sync/v1-codec.d.ts.map +0 -1
  205. package/dist/sync/v1-collaboration-state.d.ts.map +0 -1
  206. package/dist/sync/v1-command-scheduler.d.ts.map +0 -1
  207. package/dist/sync/v1-create.d.ts.map +0 -1
  208. package/dist/sync/v1-deferred.d.ts.map +0 -1
  209. package/dist/sync/v1-error.d.ts.map +0 -1
  210. package/dist/sync/v1-frame-signature.d.ts.map +0 -1
  211. package/dist/sync/v1-live-target-coordinator.d.ts.map +0 -1
  212. package/dist/sync/v1-persistence.d.ts.map +0 -1
  213. package/dist/sync/v1-platform-persistence.d.ts.map +0 -1
  214. package/dist/sync/v1-reducer.d.ts.map +0 -1
  215. package/dist/sync/v1-types.d.ts.map +0 -1
  216. package/dist/webhooks/errors.d.ts.map +0 -1
  217. package/dist/webhooks/generated/event-v1.d.ts.map +0 -1
  218. package/dist/webhooks/generated/fixtures-v1.d.ts.map +0 -1
  219. package/dist/webhooks/index.d.ts.map +0 -1
  220. package/dist/webhooks/processor.d.ts.map +0 -1
  221. package/dist/webhooks/server-only.d.ts.map +0 -1
  222. package/dist/webhooks/test.d.ts.map +0 -1
  223. package/dist/webhooks/types.d.ts.map +0 -1
  224. package/dist/webhooks/validate.d.ts.map +0 -1
  225. package/dist/webhooks/verify.d.ts.map +0 -1
  226. package/dist/whiteboard/file-download-transport.d.ts.map +0 -1
  227. package/dist/whiteboard/index.d.ts.map +0 -1
  228. package/dist/whiteboard/types.d.ts.map +0 -1
  229. package/dist/whiteboard/v1-client.d.ts.map +0 -1
  230. package/dist/whiteboard/v1-codec.d.ts.map +0 -1
  231. package/dist/whiteboard/v1-create.d.ts.map +0 -1
  232. package/dist/whiteboard/v1-multipart.d.ts.map +0 -1
  233. package/dist/whiteboard/v1-persistence.d.ts.map +0 -1
@@ -217,6 +217,47 @@ function unsubscribe(subscription) {
217
217
  }
218
218
  }
219
219
 
220
+ // src/sync/v1-health.ts
221
+ var SYNC_HEALTH_BUDGET = {
222
+ probeIntervalMs: 500,
223
+ degradedAfterMs: 750,
224
+ recoveringAfterMs: 4e3,
225
+ stablePongsForMs: 2e3
226
+ };
227
+ var V1SyncHealth = class {
228
+ health = "healthy";
229
+ #lastPongAt;
230
+ #stableSince = null;
231
+ constructor(now) {
232
+ this.#lastPongAt = now;
233
+ }
234
+ observePong(now) {
235
+ this.check(now);
236
+ const gap = now - this.#lastPongAt;
237
+ this.#lastPongAt = now;
238
+ if (this.health === "healthy") return;
239
+ if (gap > SYNC_HEALTH_BUDGET.degradedAfterMs || this.#stableSince === null) this.#stableSince = now;
240
+ if (now - this.#stableSince >= SYNC_HEALTH_BUDGET.stablePongsForMs) {
241
+ this.health = "healthy";
242
+ this.#stableSince = null;
243
+ }
244
+ }
245
+ check(now) {
246
+ const silence = now - this.#lastPongAt;
247
+ if (silence >= SYNC_HEALTH_BUDGET.degradedAfterMs) this.#stableSince = null;
248
+ if (silence >= SYNC_HEALTH_BUDGET.recoveringAfterMs) this.health = "recovering";
249
+ else if (silence >= SYNC_HEALTH_BUDGET.degradedAfterMs && this.health === "healthy") this.health = "degraded";
250
+ }
251
+ };
252
+
253
+ // src/sync/dial-deadline.ts
254
+ var SYNC_DIAL_TIMEOUT_MS = 1e4;
255
+ function scheduleSyncDialDeadline(clock, onTimeout, timeoutMs = SYNC_DIAL_TIMEOUT_MS) {
256
+ const allowance = Number.isFinite(timeoutMs) ? Math.min(6e4, Math.max(SYNC_DIAL_TIMEOUT_MS, timeoutMs)) : SYNC_DIAL_TIMEOUT_MS;
257
+ const timer = clock.setTimeout(onTimeout, allowance);
258
+ return () => clock.clearTimeout(timer);
259
+ }
260
+
220
261
  // src/generated/sync.ts
221
262
  import { Schema } from "effect";
222
263
  var SyncProtocolMetadata = {
@@ -1719,6 +1760,12 @@ var V1ReplicaError = class extends Error {
1719
1760
  this.name = "V1ReplicaError";
1720
1761
  }
1721
1762
  };
1763
+ var V1StateDigestMismatchError = class extends V1ReplicaError {
1764
+ constructor() {
1765
+ super("control state digest mismatch");
1766
+ this.name = "V1StateDigestMismatchError";
1767
+ }
1768
+ };
1722
1769
  async function restoreV1Snapshot(snapshot) {
1723
1770
  const state = snapshotToState(snapshot);
1724
1771
  assertV1ControlSemantics(state);
@@ -1832,7 +1879,7 @@ function durableProjection(state) {
1832
1879
  };
1833
1880
  }
1834
1881
  async function assertDigest(state) {
1835
- if (await computeV1StateDigest(state) !== state.stateDigest) throw new V1ReplicaError("control state digest mismatch");
1882
+ if (await computeV1StateDigest(state) !== state.stateDigest) throw new V1StateDigestMismatchError();
1836
1883
  }
1837
1884
  function reduceEvent(state, event) {
1838
1885
  switch (event.name) {
@@ -1971,9 +2018,11 @@ function sameStrings2(left, right) {
1971
2018
 
1972
2019
  // src/sync/v1-collaboration-state.ts
1973
2020
  var MAX_COLLABORATION_REQUESTS_IN_FLIGHT = 64;
2021
+ var REACTION_ACK_TIMEOUT_MS = 5e3;
1974
2022
  var V1CollaborationState = class {
1975
2023
  #options;
1976
2024
  #listeners = /* @__PURE__ */ new Set();
2025
+ #reactionTimers = /* @__PURE__ */ new Map();
1977
2026
  #reactions = /* @__PURE__ */ new Map();
1978
2027
  #chatSends = /* @__PURE__ */ new Map();
1979
2028
  #chatPages = /* @__PURE__ */ new Map();
@@ -2044,7 +2093,18 @@ var V1CollaborationState = class {
2044
2093
  this.#assertCapacity();
2045
2094
  const operationId = this.#nextRequestId();
2046
2095
  const frame = { type: "reaction_send", operation_id: operationId, reaction };
2047
- return this.#sendTrackedRequest(operationId, frame, this.#reactions);
2096
+ const pending = this.#sendTrackedRequest(operationId, frame, this.#reactions);
2097
+ this.#reactionTimers.set(
2098
+ operationId,
2099
+ this.#options.clock().setTimeout(() => {
2100
+ this.#reactionTimers.delete(operationId);
2101
+ const deferred = this.#reactions.get(operationId);
2102
+ if (!deferred) return;
2103
+ this.#reactions.delete(operationId);
2104
+ rejectV1Deferred(deferred, new V1SyncError("Reaction acknowledgement timed out", "reaction_ack_timeout"));
2105
+ }, REACTION_ACK_TIMEOUT_MS)
2106
+ );
2107
+ return pending;
2048
2108
  }
2049
2109
  sendChatMessage(input) {
2050
2110
  this.#assertReady("sendChat");
@@ -2108,6 +2168,8 @@ var V1CollaborationState = class {
2108
2168
  this.#requireNegotiated();
2109
2169
  const deferred = this.#reactions.get(frame.operation_id);
2110
2170
  if (!deferred) return;
2171
+ this.#options.clock().clearTimeout(this.#reactionTimers.get(frame.operation_id));
2172
+ this.#reactionTimers.delete(frame.operation_id);
2111
2173
  this.#reactions.delete(frame.operation_id);
2112
2174
  if (frame.outcome === "accepted") resolveV1Deferred(deferred, reactionFromFrame(frame.reaction));
2113
2175
  else rejectV1Deferred(deferred, new V1SyncError(frame.error_code, frame.error_code));
@@ -2178,6 +2240,8 @@ var V1CollaborationState = class {
2178
2240
  for (const deferred of this.#chatSends.values()) rejectV1Deferred(deferred, new V1SyncError(code, code));
2179
2241
  for (const deferred of this.#chatPages.values()) rejectV1Deferred(deferred, new V1SyncError(code, code));
2180
2242
  for (const deferred of this.#chatReads.values()) rejectV1Deferred(deferred, new V1SyncError(code, code));
2243
+ for (const timer of this.#reactionTimers.values()) this.#options.clock().clearTimeout(timer);
2244
+ this.#reactionTimers.clear();
2181
2245
  this.#reactions.clear();
2182
2246
  this.#chatSends.clear();
2183
2247
  this.#chatPages.clear();
@@ -2863,15 +2927,16 @@ var CLIENT_RESTART_CLOSE_CODE = 4e3;
2863
2927
  var DEFAULT_RECONNECT_DELAY_MS = 250;
2864
2928
  var MAX_RECONNECT_DELAY_MS = 5e3;
2865
2929
  var FAST_RECONNECT_ATTEMPTS = 8;
2866
- var NOTICE_PROBE_INTERVAL_MS = 500;
2867
- var NOTICE_SILENCE_MS = 750;
2930
+ var DIGEST_RECOVERY_COOLDOWN_MS = 6e4;
2868
2931
  var V1SyncClient = class {
2869
2932
  #options;
2870
2933
  #commandScheduler;
2871
2934
  #listeners = /* @__PURE__ */ new Set();
2935
+ #mediaPauseListeners = /* @__PURE__ */ new Set();
2872
2936
  #requestListeners = /* @__PURE__ */ new Set();
2873
2937
  #collaborationState;
2874
2938
  #controlHeads = /* @__PURE__ */ new Map();
2939
+ #snapshotRevision = null;
2875
2940
  #controlEvents = /* @__PURE__ */ new Map();
2876
2941
  #liveTargets;
2877
2942
  #requests = /* @__PURE__ */ new Map();
@@ -2885,17 +2950,20 @@ var V1SyncClient = class {
2885
2950
  #participantId = null;
2886
2951
  #participantGeneration = null;
2887
2952
  #recovery = null;
2953
+ #snapshotRecoveryRequested = false;
2954
+ #lastDigestRecoveryAt = null;
2888
2955
  #started = false;
2889
2956
  #startupGeneration = 0;
2890
2957
  #connectionGeneration = 0;
2891
2958
  #reconnectTimer;
2959
+ #cancelDial;
2960
+ #dialTimeoutCount = 0;
2892
2961
  #reconnectAttempt = 0;
2893
2962
  #heartbeatTimer;
2894
2963
  #missedHeartbeats = 0;
2895
2964
  #noticeTimer;
2896
2965
  #noticeProbeTimer;
2897
- #noticeLastInboundAt = 0;
2898
- #noticeUnresponsive = false;
2966
+ #health = new V1SyncHealth(0);
2899
2967
  #unsubscribeLifecycle;
2900
2968
  #inbound = Promise.resolve();
2901
2969
  #transportAvailable = true;
@@ -2935,6 +3003,7 @@ var V1SyncClient = class {
2935
3003
  stateChanged: () => this.#emit()
2936
3004
  });
2937
3005
  this.#collaborationState = new V1CollaborationState({
3006
+ clock: () => this.#clock(),
2938
3007
  request: options.collaboration,
2939
3008
  requestIds: options.requestIds,
2940
3009
  maxPendingRequests: options.maxPendingCollaborationRequests,
@@ -2971,6 +3040,7 @@ var V1SyncClient = class {
2971
3040
  this.#unsubscribeLifecycle?.();
2972
3041
  this.#unsubscribeLifecycle = void 0;
2973
3042
  this.#clearReconnect();
3043
+ this.#clearDial();
2974
3044
  this.#clearHeartbeat();
2975
3045
  this.#clearNoticeProbe();
2976
3046
  this.#commandScheduler.stop("client_stopped");
@@ -2989,7 +3059,7 @@ var V1SyncClient = class {
2989
3059
  const pendingCommands = this.#commandScheduler.pendingCommands;
2990
3060
  const optimisticControl = this.#control && this.#participantId ? optimisticV1Control(this.#control, this.#participantId, pendingCommands) : this.#control;
2991
3061
  return {
2992
- connection: this.#phase.phase === "live" ? { ...this.#phase, noticeUnresponsive: this.#noticeUnresponsive } : { ...this.#phase },
3062
+ connection: { ...this.#phase, ...this.#phase.phase === "live" ? { noticeUnresponsive: this.#health.health === "recovering", health: this.#health.health } : {}, ...this.#dialTimeoutCount ? { dialTimeoutCount: this.#dialTimeoutCount } : {} },
2993
3063
  participantId: this.#participantId,
2994
3064
  participantGeneration: this.#participantGeneration,
2995
3065
  control: this.#control,
@@ -3006,6 +3076,16 @@ var V1SyncClient = class {
3006
3076
  listener(this.getSnapshot());
3007
3077
  return () => this.#listeners.delete(listener);
3008
3078
  }
3079
+ onMediaPause(listener) {
3080
+ this.#mediaPauseListeners.add(listener);
3081
+ return () => this.#mediaPauseListeners.delete(listener);
3082
+ }
3083
+ #notifyMediaPause(event) {
3084
+ const source = event.name === "participant_microphone_stopped" ? "microphone" : event.name === "participant_camera_stopped" ? "camera" : event.name === "participant_screen_share_stopped" ? "screen" : null;
3085
+ if (source && "participant_id" in event.payload && event.payload.participant_id === this.#participantId) {
3086
+ for (const listener of this.#mediaPauseListeners) listener({ source, revision: event.revision });
3087
+ }
3088
+ }
3009
3089
  onDirectedRequest(listener) {
3010
3090
  this.#requestListeners.add(listener);
3011
3091
  return () => this.#requestListeners.delete(listener);
@@ -3117,13 +3197,24 @@ var V1SyncClient = class {
3117
3197
  return promise;
3118
3198
  }
3119
3199
  #connect() {
3120
- if (!this.#started || !this.#transportAvailable || this.#socket) return;
3200
+ if (!this.#started || !this.#transportAvailable || this.#socket || this.#phase.phase === "terminal") return;
3121
3201
  this.#phase = { phase: "connecting" };
3122
3202
  this.#emit();
3123
3203
  const socket = this.#options.webSocket.connect(this.#options.url);
3124
3204
  const connectionGeneration = ++this.#connectionGeneration;
3125
3205
  this.#socket = socket;
3206
+ this.#cancelDial = scheduleSyncDialDeadline(
3207
+ this.#clock(),
3208
+ () => {
3209
+ this.#cancelDial = void 0;
3210
+ this.#dialTimeoutCount += 1;
3211
+ this.#recover("WebSocket dial timeout");
3212
+ },
3213
+ this.#options.dialTimeoutMs
3214
+ );
3126
3215
  socket.onopen = () => {
3216
+ if (socket !== this.#socket) return;
3217
+ this.#clearDial();
3127
3218
  void this.#authenticate(socket);
3128
3219
  };
3129
3220
  socket.onmessage = (event) => {
@@ -3169,17 +3260,11 @@ var V1SyncClient = class {
3169
3260
  try {
3170
3261
  if (typeof data !== "string" || encoder3.encode(data).byteLength > SyncProtocolLimits.snapshotEncodedBytes) throw new V1ReplicaError("invalid inbound frame size");
3171
3262
  const frame = decodeV1ServerFrame(data);
3172
- if (this.#phase.phase === "live") {
3173
- this.#noticeLastInboundAt = this.#now();
3174
- this.#scheduleNoticeCheck();
3175
- if (this.#noticeUnresponsive) {
3176
- this.#noticeUnresponsive = false;
3177
- this.#emit();
3178
- }
3179
- }
3180
3263
  await this.#handleFrame(frame);
3181
- } catch {
3182
- this.#recover("invalid_frame");
3264
+ } catch (error) {
3265
+ if (this.#snapshotRecoveryRequested) this.#failDigestRecovery();
3266
+ else if (error instanceof V1StateDigestMismatchError) this.#recoverDigestMismatch();
3267
+ else this.#recover("invalid_frame");
3183
3268
  }
3184
3269
  }
3185
3270
  async #handleFrame(frame) {
@@ -3207,6 +3292,7 @@ var V1SyncClient = class {
3207
3292
  this.#control = await applyV1Event(this.#requireControl(), frame);
3208
3293
  this.#rememberControlHead(this.#control.revision, this.#control.stateDigest);
3209
3294
  this.#rememberControlEvent(frame);
3295
+ this.#notifyMediaPause(frame);
3210
3296
  }
3211
3297
  await this.#commandScheduler.settleTerminalLifecycle(frame, this.#participantId);
3212
3298
  await this.#commandScheduler.settleProven();
@@ -3269,10 +3355,14 @@ var V1SyncClient = class {
3269
3355
  return;
3270
3356
  case "error":
3271
3357
  throw new V1ReplicaError(frame.code);
3272
- case "pong":
3358
+ case "pong": {
3273
3359
  this.#requireLive();
3274
3360
  this.#missedHeartbeats = 0;
3361
+ const previous = this.#health.health;
3362
+ this.#health.observePong(this.#now());
3363
+ if (previous !== this.#health.health) this.#emit();
3275
3364
  return;
3365
+ }
3276
3366
  }
3277
3367
  }
3278
3368
  async #welcome(frame) {
@@ -3288,15 +3378,20 @@ var V1SyncClient = class {
3288
3378
  this.#emit();
3289
3379
  return;
3290
3380
  }
3381
+ if (this.#snapshotRecoveryRequested && frame.mode !== "snapshot") throw new V1ReplicaError("digest recovery requires a full snapshot");
3291
3382
  if (frame.mode === "snapshot") {
3292
- this.#control = await restoreV1Snapshot(frame.snapshot);
3383
+ const control = await restoreV1Snapshot(frame.snapshot);
3384
+ if (!sameHead(control, frame.head)) throw new V1ReplicaError("snapshot head mismatch");
3385
+ this.#control = control;
3386
+ this.#snapshotRevision = control.revision;
3387
+ this.#snapshotRecoveryRequested = false;
3293
3388
  this.#controlHeads.clear();
3294
3389
  this.#controlEvents.clear();
3295
3390
  this.#rememberControlHead(this.#control.revision, this.#control.stateDigest);
3391
+ await this.#commandScheduler.settleProven();
3296
3392
  }
3297
3393
  if (frame.mode === "replay" && (!this.#control || this.#control.revision >= frame.head.revision)) throw new V1ReplicaError("invalid replay welcome");
3298
3394
  if (frame.mode === "up_to_date" && !sameHead(this.#control, frame.head)) throw new V1ReplicaError("up-to-date head mismatch");
3299
- if (frame.mode === "snapshot" && !sameHead(this.#control, frame.head)) throw new V1ReplicaError("snapshot head mismatch");
3300
3395
  this.#recovery = { id: frame.recovery_id, head: frame.head, replayEvents: 0, replayBytes: 0, controlComplete: false };
3301
3396
  if (frame.mode === "snapshot") this.#ackRecovery();
3302
3397
  this.#emit();
@@ -3311,6 +3406,7 @@ var V1SyncClient = class {
3311
3406
  this.#control = await applyV1Event(this.#requireControl(), event);
3312
3407
  this.#rememberControlHead(this.#control.revision, this.#control.stateDigest);
3313
3408
  this.#rememberControlEvent(event);
3409
+ this.#notifyMediaPause(event);
3314
3410
  }
3315
3411
  this.#ackRecovery();
3316
3412
  this.#emit();
@@ -3404,7 +3500,7 @@ var V1SyncClient = class {
3404
3500
  this.#send({ type: "recovery_ack", recovery_id: recovery.id, revision: control.revision, state_digest: control.stateDigest });
3405
3501
  }
3406
3502
  #ackHeadIsProven(ack) {
3407
- return this.#controlHeads.get(ack.revision) === ack.state_digest;
3503
+ return this.#snapshotRevision !== null && ack.revision < this.#snapshotRevision || this.#controlHeads.get(ack.revision) === ack.state_digest;
3408
3504
  }
3409
3505
  #rememberControlHead(revision, digest) {
3410
3506
  this.#controlHeads.set(revision, digest);
@@ -3423,6 +3519,7 @@ var V1SyncClient = class {
3423
3519
  #disconnected(socket) {
3424
3520
  if (socket !== this.#socket) return;
3425
3521
  this.#socket = null;
3522
+ this.#clearDial();
3426
3523
  this.#recovery = null;
3427
3524
  this.#clearHeartbeat();
3428
3525
  this.#clearNoticeProbe();
@@ -3452,6 +3549,29 @@ var V1SyncClient = class {
3452
3549
  socket.close(CLIENT_RESTART_CLOSE_CODE, reason);
3453
3550
  this.#disconnected(socket);
3454
3551
  }
3552
+ #recoverDigestMismatch() {
3553
+ const now = this.#clock().now();
3554
+ if (this.#lastDigestRecoveryAt !== null && now - this.#lastDigestRecoveryAt < DIGEST_RECOVERY_COOLDOWN_MS) {
3555
+ this.#failDigestRecovery();
3556
+ return;
3557
+ }
3558
+ this.#lastDigestRecoveryAt = now;
3559
+ this.#snapshotRecoveryRequested = true;
3560
+ this.#control = null;
3561
+ this.#snapshotRevision = null;
3562
+ this.#controlHeads.clear();
3563
+ this.#controlEvents.clear();
3564
+ this.#recover("state_digest_mismatch");
3565
+ }
3566
+ #failDigestRecovery() {
3567
+ this.#phase = { phase: "terminal", terminalReason: "state_digest_recovery_failed" };
3568
+ this.#clearReconnect();
3569
+ this.#commandScheduler.stop("state_digest_recovery_failed");
3570
+ const socket = this.#socket;
3571
+ socket?.close(CLIENT_RESTART_CLOSE_CODE, "state_digest_recovery_failed");
3572
+ if (socket) this.#disconnected(socket);
3573
+ this.#emit();
3574
+ }
3455
3575
  #handleLifecycle(event) {
3456
3576
  if (event === "online" || event === "offline") this.#online = event === "online";
3457
3577
  else this.#active = event === "active";
@@ -3500,6 +3620,10 @@ var V1SyncClient = class {
3500
3620
  #now() {
3501
3621
  return this.#clock().now();
3502
3622
  }
3623
+ #clearDial() {
3624
+ this.#cancelDial?.();
3625
+ this.#cancelDial = void 0;
3626
+ }
3503
3627
  #clearReconnect() {
3504
3628
  if (this.#reconnectTimer === void 0) return;
3505
3629
  this.#clock().clearTimeout(this.#reconnectTimer);
@@ -3557,33 +3681,29 @@ var V1SyncClient = class {
3557
3681
  }
3558
3682
  #startNoticeProbe() {
3559
3683
  this.#clearNoticeProbe();
3560
- this.#noticeLastInboundAt = this.#now();
3561
- this.#scheduleNoticeCheck();
3684
+ this.#send({ type: "ping" });
3685
+ this.#health = new V1SyncHealth(this.#now());
3562
3686
  const tick = () => {
3563
3687
  this.#noticeProbeTimer = void 0;
3564
3688
  if (this.#phase.phase !== "live") return;
3689
+ const previous = this.#health.health;
3690
+ this.#health.check(this.#now());
3691
+ if (previous !== this.#health.health) this.#emit();
3565
3692
  this.#send({ type: "ping" });
3566
- this.#noticeProbeTimer = this.#clock().setTimeout(tick, NOTICE_PROBE_INTERVAL_MS);
3693
+ this.#noticeProbeTimer = this.#clock().setTimeout(tick, SYNC_HEALTH_BUDGET.probeIntervalMs);
3567
3694
  };
3568
- this.#noticeProbeTimer = this.#clock().setTimeout(tick, NOTICE_PROBE_INTERVAL_MS);
3569
- }
3570
- #scheduleNoticeCheck() {
3571
- if (this.#noticeTimer !== void 0) this.#clock().clearTimeout(this.#noticeTimer);
3572
- const remaining = Math.max(0, NOTICE_SILENCE_MS - (this.#now() - this.#noticeLastInboundAt));
3695
+ this.#noticeProbeTimer = this.#clock().setTimeout(tick, SYNC_HEALTH_BUDGET.probeIntervalMs);
3573
3696
  this.#noticeTimer = this.#clock().setTimeout(() => {
3574
3697
  this.#noticeTimer = void 0;
3575
- if (this.#phase.phase !== "live" || this.#noticeUnresponsive) return;
3576
- if (this.#now() - this.#noticeLastInboundAt < NOTICE_SILENCE_MS) return this.#scheduleNoticeCheck();
3577
- this.#noticeUnresponsive = true;
3698
+ this.#health.check(this.#now());
3578
3699
  this.#emit();
3579
- }, remaining);
3700
+ }, SYNC_HEALTH_BUDGET.degradedAfterMs);
3580
3701
  }
3581
3702
  #clearNoticeProbe() {
3582
3703
  if (this.#noticeTimer !== void 0) this.#clock().clearTimeout(this.#noticeTimer);
3583
3704
  if (this.#noticeProbeTimer !== void 0) this.#clock().clearTimeout(this.#noticeProbeTimer);
3584
3705
  this.#noticeTimer = void 0;
3585
3706
  this.#noticeProbeTimer = void 0;
3586
- this.#noticeUnresponsive = false;
3587
3707
  }
3588
3708
  #emit() {
3589
3709
  const snapshot = this.getSnapshot();
@@ -3786,6 +3906,7 @@ export {
3786
3906
  createBrowserSyncLifecycle,
3787
3907
  createReactNativeWebSocketFactory,
3788
3908
  createReactNativeSyncLifecycle,
3909
+ scheduleSyncDialDeadline,
3789
3910
  encodeV1ClientFrame,
3790
3911
  decodeV1ServerFrame,
3791
3912
  decodeV1ClientFrame,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createChalkEffectClient,
3
3
  parseAccessGrant
4
- } from "./chunk-2HJIJVE2.js";
4
+ } from "./chunk-3ZN7IXOZ.js";
5
5
 
6
6
  // src/public-invites/client.ts
7
7
  import { Effect } from "effect";