@q9labsai/chalk-client 4.1.16 → 5.0.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 (195) hide show
  1. package/README.md +99 -40
  2. package/dist/access/grant.d.ts +0 -10
  3. package/dist/access/grant.d.ts.map +1 -1
  4. package/dist/access/manager.d.ts.map +1 -1
  5. package/dist/{chunk-M3S7HNQF.js → chunk-2HJIJVE2.js} +601 -3628
  6. package/dist/{chunk-OKIL2ELF.js → chunk-AVXJ4HQU.js} +154 -50
  7. package/dist/{chunk-E4OK5K6N.js → chunk-CMVO7USN.js} +2 -8
  8. package/dist/{chunk-BD7FRQ67.js → chunk-IPAN4LUZ.js} +1089 -2457
  9. package/dist/{chunk-O7WTAXAY.js → chunk-O2NOZKJP.js} +170 -639
  10. package/dist/client.d.ts +2500 -5206
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/collaboration/types.d.ts +21 -1
  13. package/dist/collaboration/types.d.ts.map +1 -1
  14. package/dist/collaboration/wire.d.ts +8 -1
  15. package/dist/collaboration/wire.d.ts.map +1 -1
  16. package/dist/connection/dependencies.d.ts +8 -13
  17. package/dist/connection/dependencies.d.ts.map +1 -1
  18. package/dist/connection/diagnostics.d.ts +1 -1
  19. package/dist/connection/diagnostics.d.ts.map +1 -1
  20. package/dist/connection/index.d.ts +2 -6
  21. package/dist/connection/index.d.ts.map +1 -1
  22. package/dist/connection/lifecycle.d.ts +4 -1
  23. package/dist/connection/lifecycle.d.ts.map +1 -1
  24. package/dist/connection/media-devices.d.ts +11 -0
  25. package/dist/connection/media-devices.d.ts.map +1 -1
  26. package/dist/connection/production.d.ts +1 -1
  27. package/dist/connection/production.d.ts.map +1 -1
  28. package/dist/connection/types.d.ts +4 -4
  29. package/dist/connection/types.d.ts.map +1 -1
  30. package/dist/effect.d.ts +0 -4
  31. package/dist/effect.d.ts.map +1 -1
  32. package/dist/effect.js +65 -776
  33. package/dist/foreground-deadline.d.ts +9 -0
  34. package/dist/foreground-deadline.d.ts.map +1 -0
  35. package/dist/generated/http-api.d.ts +2561 -5213
  36. package/dist/generated/http-api.d.ts.map +1 -1
  37. package/dist/generated/schemas.d.ts +1919 -6814
  38. package/dist/generated/schemas.d.ts.map +1 -1
  39. package/dist/generated/sync.d.ts +191 -28
  40. package/dist/generated/sync.d.ts.map +1 -1
  41. package/dist/generated/whiteboard-v1.d.ts +0 -7
  42. package/dist/generated/whiteboard-v1.d.ts.map +1 -1
  43. package/dist/index.d.ts +0 -4
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +19 -128
  46. package/dist/media/client.d.ts +6 -5
  47. package/dist/media/client.d.ts.map +1 -1
  48. package/dist/media/index.d.ts +1 -2
  49. package/dist/media/index.d.ts.map +1 -1
  50. package/dist/media/index.js +1 -7
  51. package/dist/media/plane.d.ts +12 -1
  52. package/dist/media/plane.d.ts.map +1 -1
  53. package/dist/media/target.d.ts +4 -2
  54. package/dist/media/target.d.ts.map +1 -1
  55. package/dist/media/tracks.d.ts +2 -2
  56. package/dist/media/tracks.d.ts.map +1 -1
  57. package/dist/media/transport.d.ts.map +1 -1
  58. package/dist/media/types.d.ts +4 -11
  59. package/dist/media/types.d.ts.map +1 -1
  60. package/dist/public-invites/client.d.ts.map +1 -1
  61. package/dist/public-invites/index.d.ts +2 -2
  62. package/dist/public-invites/index.d.ts.map +1 -1
  63. package/dist/public-invites/index.js +2 -5
  64. package/dist/public-invites/types.d.ts +2 -6
  65. package/dist/public-invites/types.d.ts.map +1 -1
  66. package/dist/server/access-refresh.d.cts +5 -0
  67. package/dist/server/access-refresh.d.ts +5 -0
  68. package/dist/server/access-refresh.d.ts.map +1 -1
  69. package/dist/server/client.d.ts.map +1 -1
  70. package/dist/server/index.cjs +22 -117
  71. package/dist/server/index.d.cts +2 -2
  72. package/dist/server/index.d.ts +2 -2
  73. package/dist/server/index.d.ts.map +1 -1
  74. package/dist/server/index.js +19 -114
  75. package/dist/server/types.d.cts +2 -8
  76. package/dist/server/types.d.ts +2 -8
  77. package/dist/server/types.d.ts.map +1 -1
  78. package/dist/space-client/chat-controller-helpers.d.ts.map +1 -1
  79. package/dist/space-client/chat-controller.d.ts +2 -3
  80. package/dist/space-client/chat-controller.d.ts.map +1 -1
  81. package/dist/space-client/controller-effects.d.ts +0 -3
  82. package/dist/space-client/controller-effects.d.ts.map +1 -1
  83. package/dist/space-client/core.d.ts +3 -18
  84. package/dist/space-client/core.d.ts.map +1 -1
  85. package/dist/space-client/core.test.helpers.d.ts +1 -1
  86. package/dist/space-client/core.test.helpers.d.ts.map +1 -1
  87. package/dist/space-client/effect.d.ts +0 -2
  88. package/dist/space-client/effect.d.ts.map +1 -1
  89. package/dist/space-client/index.d.ts +1 -2
  90. package/dist/space-client/index.d.ts.map +1 -1
  91. package/dist/space-client/media-controller.d.ts +4 -4
  92. package/dist/space-client/media-controller.d.ts.map +1 -1
  93. package/dist/space-client/offline-actions-storage.d.ts +18 -3
  94. package/dist/space-client/offline-actions-storage.d.ts.map +1 -1
  95. package/dist/space-client/offline-actions.d.ts +1 -0
  96. package/dist/space-client/offline-actions.d.ts.map +1 -1
  97. package/dist/space-client/participants-controller.d.ts +2 -3
  98. package/dist/space-client/participants-controller.d.ts.map +1 -1
  99. package/dist/space-client/reactions-controller.d.ts +2 -3
  100. package/dist/space-client/reactions-controller.d.ts.map +1 -1
  101. package/dist/space-client/recording-controller.d.ts +2 -3
  102. package/dist/space-client/recording-controller.d.ts.map +1 -1
  103. package/dist/space-client/space-client.d.ts.map +1 -1
  104. package/dist/space-client/store.d.ts.map +1 -1
  105. package/dist/space-client/terminal.test.helpers.d.ts +136 -0
  106. package/dist/space-client/terminal.test.helpers.d.ts.map +1 -0
  107. package/dist/space-client/types.d.ts +17 -8
  108. package/dist/space-client/types.d.ts.map +1 -1
  109. package/dist/sync/index.d.ts +1 -1
  110. package/dist/sync/index.d.ts.map +1 -1
  111. package/dist/sync/index.js +1 -3
  112. package/dist/sync/v1-client.d.ts +5 -3
  113. package/dist/sync/v1-client.d.ts.map +1 -1
  114. package/dist/sync/v1-collaboration-state.d.ts +10 -3
  115. package/dist/sync/v1-collaboration-state.d.ts.map +1 -1
  116. package/dist/sync/v1-types.d.ts +10 -9
  117. package/dist/sync/v1-types.d.ts.map +1 -1
  118. package/dist/webhooks/generated/event-v1.d.cts +10 -2
  119. package/dist/webhooks/generated/event-v1.d.ts +10 -2
  120. package/dist/webhooks/generated/event-v1.d.ts.map +1 -1
  121. package/dist/webhooks/generated/fixtures-v1.d.cts +2 -2
  122. package/dist/webhooks/generated/fixtures-v1.d.ts +2 -2
  123. package/dist/webhooks/index.cjs +10 -2
  124. package/dist/webhooks/index.js +10 -2
  125. package/dist/webhooks/test.cjs +2 -2
  126. package/dist/webhooks/test.js +2 -2
  127. package/dist/whiteboard/file-download-transport.d.ts +8 -0
  128. package/dist/whiteboard/file-download-transport.d.ts.map +1 -0
  129. package/dist/whiteboard/index.d.ts +1 -1
  130. package/dist/whiteboard/index.d.ts.map +1 -1
  131. package/dist/whiteboard/types.d.ts +12 -15
  132. package/dist/whiteboard/types.d.ts.map +1 -1
  133. package/dist/whiteboard/v1-client.d.ts.map +1 -1
  134. package/package.json +4 -16
  135. package/dist/access/episode-diagnostic-credential.d.ts +0 -11
  136. package/dist/access/episode-diagnostic-credential.d.ts.map +0 -1
  137. package/dist/chunk-EWTITNJ3.js +0 -1163
  138. package/dist/chunk-HW5TTZL2.js +0 -99
  139. package/dist/chunk-PBSYXPNJ.js +0 -23
  140. package/dist/chunk-RCDXIDCN.js +0 -33
  141. package/dist/connection/snapshot.d.ts +0 -23
  142. package/dist/connection/snapshot.d.ts.map +0 -1
  143. package/dist/feedback/collector.d.ts +0 -14
  144. package/dist/feedback/collector.d.ts.map +0 -1
  145. package/dist/feedback/context.d.ts +0 -38
  146. package/dist/feedback/context.d.ts.map +0 -1
  147. package/dist/feedback/controller.d.ts +0 -4
  148. package/dist/feedback/controller.d.ts.map +0 -1
  149. package/dist/feedback/index.d.ts +0 -7
  150. package/dist/feedback/index.d.ts.map +0 -1
  151. package/dist/feedback/transport.d.ts +0 -29
  152. package/dist/feedback/transport.d.ts.map +0 -1
  153. package/dist/feedback/types.d.ts +0 -202
  154. package/dist/feedback/types.d.ts.map +0 -1
  155. package/dist/feedback/validation.d.ts +0 -30
  156. package/dist/feedback/validation.d.ts.map +0 -1
  157. package/dist/media/rtk.d.ts +0 -101
  158. package/dist/media/rtk.d.ts.map +0 -1
  159. package/dist/space-client/episode-diagnostic-registry.d.ts +0 -13
  160. package/dist/space-client/episode-diagnostic-registry.d.ts.map +0 -1
  161. package/dist/space-client/episode-diagnostic-render-registry.d.ts +0 -4
  162. package/dist/space-client/episode-diagnostic-render-registry.d.ts.map +0 -1
  163. package/dist/space-client/episode-diagnostic-runtime.d.ts +0 -105
  164. package/dist/space-client/episode-diagnostic-runtime.d.ts.map +0 -1
  165. package/dist/telemetry/attributes.d.ts +0 -7
  166. package/dist/telemetry/attributes.d.ts.map +0 -1
  167. package/dist/telemetry/client.d.ts +0 -44
  168. package/dist/telemetry/client.d.ts.map +0 -1
  169. package/dist/telemetry/delivery.d.ts +0 -107
  170. package/dist/telemetry/delivery.d.ts.map +0 -1
  171. package/dist/telemetry/exporter.d.ts +0 -38
  172. package/dist/telemetry/exporter.d.ts.map +0 -1
  173. package/dist/telemetry/index.d.ts +0 -7
  174. package/dist/telemetry/index.d.ts.map +0 -1
  175. package/dist/telemetry/index.js +0 -50
  176. package/dist/telemetry/journey.d.ts +0 -52
  177. package/dist/telemetry/journey.d.ts.map +0 -1
  178. package/dist/telemetry/random.d.ts +0 -18
  179. package/dist/telemetry/random.d.ts.map +0 -1
  180. package/dist/telemetry/reconnect.d.ts +0 -7
  181. package/dist/telemetry/reconnect.d.ts.map +0 -1
  182. package/dist/telemetry/rtc.d.ts +0 -41
  183. package/dist/telemetry/rtc.d.ts.map +0 -1
  184. package/dist/telemetry/storage.d.ts +0 -22
  185. package/dist/telemetry/storage.d.ts.map +0 -1
  186. package/dist/telemetry/sync.d.ts +0 -10
  187. package/dist/telemetry/sync.d.ts.map +0 -1
  188. package/dist/telemetry/test-support.d.ts +0 -51
  189. package/dist/telemetry/test-support.d.ts.map +0 -1
  190. package/dist/telemetry/trace.d.ts +0 -18
  191. package/dist/telemetry/trace.d.ts.map +0 -1
  192. package/dist/telemetry/types.d.ts +0 -109
  193. package/dist/telemetry/types.d.ts.map +0 -1
  194. package/dist/whiteboard/file-http-transport.d.ts +0 -9
  195. package/dist/whiteboard/file-http-transport.d.ts.map +0 -1
@@ -1,11 +1,3 @@
1
- import {
2
- journeyHeaders
3
- } from "./chunk-HW5TTZL2.js";
4
- import {
5
- recordReconnect,
6
- traceReconnect
7
- } from "./chunk-RCDXIDCN.js";
8
-
9
1
  // src/media/camera-uplink.ts
10
2
  var CameraUplinkPolicy = class {
11
3
  #healthySamples = 0;
@@ -46,14 +38,6 @@ function subscribeSnapshot(listeners, listener) {
46
38
  listeners.delete(listener);
47
39
  };
48
40
  }
49
- function observePublications(listeners, listener, project) {
50
- const publication = () => listener(project());
51
- listeners.add(publication);
52
- listener(project());
53
- return () => {
54
- listeners.delete(publication);
55
- };
56
- }
57
41
 
58
42
  // src/media/target.ts
59
43
  function resolveMediaTarget(participantId, stopped, tracks, target) {
@@ -203,8 +187,6 @@ var CloudflareSFUClient = class {
203
187
  #localListeners = /* @__PURE__ */ new Set();
204
188
  #onError;
205
189
  #onRemoteTrack;
206
- #recordReconnect;
207
- #onRtcSummary;
208
190
  #onScreenEnded;
209
191
  #participantId;
210
192
  #peerConnectionFactory;
@@ -225,11 +207,17 @@ var CloudflareSFUClient = class {
225
207
  #cursor = null;
226
208
  #remotePullIncomplete = false;
227
209
  #remotePullRetryAfter = /* @__PURE__ */ new Map();
210
+ #remoteParticipantIds = null;
211
+ #remoteParticipantRevision = 0;
212
+ #subscribedPublicationIds = /* @__PURE__ */ new Set();
228
213
  #generation = 0;
229
214
  #negotiatedGeneration = null;
230
215
  #replacementAttemptedGeneration = null;
216
+ #remoteRecoveryGeneration = null;
231
217
  #polling = false;
232
218
  #pollAfterCurrent = false;
219
+ #pollFailures = 0;
220
+ #pollFailedPull = false;
233
221
  #pollTimer;
234
222
  #sdpTail = Promise.resolve();
235
223
  #snapshot;
@@ -245,8 +233,6 @@ var CloudflareSFUClient = class {
245
233
  this.#replaceMediaConnection = options.replaceMediaConnection;
246
234
  this.#onError = options.onError;
247
235
  this.#onRemoteTrack = options.onRemoteTrack;
248
- this.#onRtcSummary = options.onRtcSummary;
249
- this.#recordReconnect = options.recordReconnect;
250
236
  this.#onScreenEnded = options.onScreenEnded;
251
237
  this.#peerConnectionFactory = options.peerConnectionFactory;
252
238
  this.#connection = this.#createPeerConnection(options.bootstrap);
@@ -269,8 +255,13 @@ var CloudflareSFUClient = class {
269
255
  this.#remotePullRetryAfter.delete(publicationId);
270
256
  this.remotePublicationsChanged();
271
257
  }
258
+ remoteParticipantsChanged(participantIds) {
259
+ this.#remoteParticipantIds = new Set(participantIds);
260
+ this.#remoteParticipantRevision++;
261
+ this.#remotePullIncomplete = true;
262
+ this.remotePublicationsChanged();
263
+ }
272
264
  remotePublicationsChanged() {
273
- recordReconnect(this.#recordReconnect, "remote_publications_changed");
274
265
  if (this.#polling) {
275
266
  this.#pollAfterCurrent = true;
276
267
  return;
@@ -308,6 +299,17 @@ var CloudflareSFUClient = class {
308
299
  async clearPreparedLocalTrack(source) {
309
300
  const state = this.#localTracks.get(source);
310
301
  if (!state) return;
302
+ if (source === "screen_audio") {
303
+ try {
304
+ await this.#retireLocalPublication(source);
305
+ } finally {
306
+ this.#removeOwnedLocalTrack(state);
307
+ this.#localTracks.delete(source);
308
+ this.#publishSnapshot();
309
+ this.#emitLocal();
310
+ }
311
+ return;
312
+ }
311
313
  if (state.enabled) await this.#setPreparedTrackEnabled(state, false);
312
314
  if (state.transceiver) this.#reusableLocalTransceivers.set(source, state.transceiver);
313
315
  if (state.providerPublicationId) this.#reusableLocalPublicationIds.set(source, state.providerPublicationId);
@@ -324,7 +326,7 @@ var CloudflareSFUClient = class {
324
326
  const generation = this.#generation;
325
327
  this.#setPhase("connecting", null);
326
328
  await this.#activatePreparedTracks(
327
- [...this.#localTracks.values()].filter((state) => state.source !== "screen"),
329
+ [...this.#localTracks.values()].filter((state) => state.source === "microphone" || state.source === "camera"),
328
330
  generation
329
331
  );
330
332
  }
@@ -332,26 +334,35 @@ var CloudflareSFUClient = class {
332
334
  if (!this.#started || this.#stopped || this.#polling) return;
333
335
  this.#polling = true;
334
336
  const generation = this.#generation;
337
+ this.#pollFailedPull = false;
335
338
  try {
336
339
  const transport = this.#requireTransport();
337
- const authoritative = await traceReconnect(this.#recordReconnect, "list_publications", () => transport.listPublications());
340
+ const authoritative = await (() => transport.listPublications())();
338
341
  this.#requireGeneration(generation);
339
342
  await this.#reconcileRemotePublications(authoritative, generation);
343
+ this.#requireGeneration(generation);
344
+ if (!this.#remotePullIncomplete) this.#pollFailures = 0;
340
345
  } catch (error) {
341
346
  if (generation === this.#generation && !this.#stopped) this.#reportError(error);
342
347
  throw error;
343
348
  } finally {
344
- if (generation === this.#generation) {
345
- this.#polling = false;
346
- if (this.#pollAfterCurrent) {
347
- this.#pollAfterCurrent = false;
348
- this.#clearPoll();
349
- this.#schedulePoll(0);
350
- }
351
- }
349
+ this.#finishPublicationPoll(generation);
352
350
  }
353
351
  }
352
+ #finishPublicationPoll(generation) {
353
+ if (generation !== this.#generation) return;
354
+ this.#polling = false;
355
+ if (!this.#pollAfterCurrent) return;
356
+ this.#pollAfterCurrent = false;
357
+ this.#clearPoll();
358
+ this.#schedulePoll(0);
359
+ }
354
360
  async setLocalPublicationTarget(target) {
361
+ const result = await this.#setSinglePublicationTarget(target);
362
+ if (target.source !== "screen" || !this.#localTracks.has("screen_audio") || result.outcome !== "confirmed" && result.outcome !== "satisfied") return result;
363
+ return this.#setSinglePublicationTarget({ ...target, source: "screen_audio" });
364
+ }
365
+ async #setSinglePublicationTarget(target) {
355
366
  const resolved = resolveMediaTarget(this.#participantId, this.#stopped, this.#localTracks, target);
356
367
  if (resolved.kind === "result") return resolved.result;
357
368
  const state = resolved.value;
@@ -371,6 +382,7 @@ var CloudflareSFUClient = class {
371
382
  return target.enabled && this.#locallyPausedSources.has(target.source);
372
383
  }
373
384
  setLocalSourceIntent(source, enabled) {
385
+ if (source === "screen") this.setLocalSourceIntent("screen_audio", enabled);
374
386
  if (enabled) {
375
387
  this.#locallyPausedSources.delete(source);
376
388
  return;
@@ -384,6 +396,7 @@ var CloudflareSFUClient = class {
384
396
  async closeForcedLocalPublication(source) {
385
397
  this.setLocalSourceIntent(source, false);
386
398
  await this.#retireLocalPublication(source);
399
+ if (source === "screen") await this.#retireLocalPublication("screen_audio");
387
400
  if (!this.#replaceMediaConnection) return;
388
401
  const generation = this.#generation;
389
402
  await this.#serializeSDP(async () => {
@@ -402,7 +415,7 @@ var CloudflareSFUClient = class {
402
415
  async #retireUnavailableLocalPublication(state) {
403
416
  const publicationId = state.providerPublicationId;
404
417
  if (!publicationId) return;
405
- const authoritative = await traceReconnect(this.#recordReconnect, "list_publications", () => this.#requireTransport().listPublications());
418
+ const authoritative = await (() => this.#requireTransport().listPublications())();
406
419
  if (authoritative.publications.some((publication) => publication.publicationId === publicationId)) return;
407
420
  if (state.providerPublicationId === publicationId) await this.#retireLocalPublication(state.source);
408
421
  }
@@ -430,8 +443,8 @@ var CloudflareSFUClient = class {
430
443
  const mid = requireTransceiverMid(transceiver);
431
444
  await this.#boundPeerOperation(transceiver.sender.replaceTrack(null));
432
445
  transceiver.stop();
433
- const offer = await traceReconnect(this.#recordReconnect, "create_offer", () => connection.createOffer());
434
- await traceReconnect(this.#recordReconnect, "set_local_description", () => connection.setLocalDescription(offer));
446
+ const offer = await (() => connection.createOffer())();
447
+ await (() => connection.setLocalDescription(offer))();
435
448
  try {
436
449
  const response = await this.#requireTransport().closeTracks({
437
450
  connectionId,
@@ -461,7 +474,6 @@ var CloudflareSFUClient = class {
461
474
  this.#requireActive();
462
475
  const options = "connectionId" in input ? { bootstrap: input } : input;
463
476
  validateBootstrap(options.bootstrap);
464
- recordReconnect(this.#recordReconnect, "media_rebuild_start");
465
477
  const generation = ++this.#generation;
466
478
  const connectionEpoch = ++this.#connectionEpoch;
467
479
  this.#polling = false;
@@ -478,20 +490,19 @@ var CloudflareSFUClient = class {
478
490
  if (options.bootstrap.connectionId !== this.#bootstrap.connectionId) this.#started = false;
479
491
  this.#bootstrap = options.bootstrap;
480
492
  if (options.transport) this.#transport = options.transport;
481
- this.#connection = this.#createPeerConnection(options.bootstrap);
482
- this.#observeConnection(this.#connection, generation, connectionEpoch);
493
+ this.#rebuildPeerConnection(options.bootstrap, generation, connectionEpoch);
483
494
  for (const state of this.#localTracks.values()) {
484
495
  state.transceiver = null;
485
496
  state.enabled = false;
486
497
  }
487
498
  const enabled = [...this.#localTracks.values()].filter((state) => state.desiredEnabled && state.track.readyState !== "ended");
488
- this.#setPhase("recovering", null);
489
499
  await this.#activatePreparedTracks(enabled, generation);
490
500
  }
491
501
  async #activatePreparedTracks(states, generation) {
492
502
  try {
493
503
  await this.#publishPreparedTracks(states, generation);
494
504
  this.#requireGeneration(generation);
505
+ if (this.#remoteRecoveryGeneration !== generation) this.#replacementAttemptedGeneration = null;
495
506
  this.#started = true;
496
507
  this.#setPhase("live", null);
497
508
  this.#schedulePoll(0);
@@ -503,6 +514,7 @@ var CloudflareSFUClient = class {
503
514
  stop() {
504
515
  if (this.#stopped) return;
505
516
  this.#stopped = true;
517
+ this.#subscribedPublicationIds.clear();
506
518
  this.#generation++;
507
519
  this.#connectionEpoch++;
508
520
  this.#clearPoll();
@@ -557,6 +569,7 @@ var CloudflareSFUClient = class {
557
569
  const generation = this.#generation;
558
570
  try {
559
571
  await this.#publishPreparedTracks([state], generation);
572
+ if (this.#remoteRecoveryGeneration !== generation) this.#replacementAttemptedGeneration = null;
560
573
  } catch (error) {
561
574
  state.desiredEnabled = false;
562
575
  state.enabled = false;
@@ -580,7 +593,6 @@ var CloudflareSFUClient = class {
580
593
  if (states.length === 0) return;
581
594
  await this.#serializeSDP(async () => {
582
595
  const pending = await this.#prepareConnectionForPublication(states, generation);
583
- this.#replacementAttemptedGeneration = null;
584
596
  try {
585
597
  await this.#publishPreparedTracksSerialized(pending, generation, this.#connection, this.#bootstrap.connectionId);
586
598
  } catch (error) {
@@ -600,7 +612,7 @@ var CloudflareSFUClient = class {
600
612
  await this.#replaceDormantConnectionBeforeNegotiation(generation);
601
613
  return states;
602
614
  }
603
- const authoritative = await traceReconnect(this.#recordReconnect, "list_publications", () => this.#requireTransport().listPublications());
615
+ const authoritative = await (() => this.#requireTransport().listPublications())();
604
616
  this.#pauseUnavailableOtherSources(authoritative.publications, states);
605
617
  this.#replacementAttemptedGeneration = null;
606
618
  await this.#replaceMediaConnectionForRecovery(generation);
@@ -664,8 +676,8 @@ var CloudflareSFUClient = class {
664
676
  async #negotiateLocalPublications(connection, connectionId, publications, generation) {
665
677
  let offer;
666
678
  try {
667
- offer = await traceReconnect(this.#recordReconnect, "create_offer", () => connection.createOffer());
668
- await traceReconnect(this.#recordReconnect, "set_local_description", () => connection.setLocalDescription(offer));
679
+ offer = await (() => connection.createOffer())();
680
+ await (() => connection.setLocalDescription(offer))();
669
681
  } catch (error) {
670
682
  const cameras = publications.filter(({ state, transceiver }) => state.source === "camera" && transceiver.sender.getParameters().encodings.length > 1);
671
683
  if (!simulcastUnsupported(error) || cameras.length === 0) throw error;
@@ -677,8 +689,8 @@ var CloudflareSFUClient = class {
677
689
  publication.transceiver = connection.addTransceiver(publication.state.track, { direction: "sendonly" });
678
690
  publication.state.transceiver = publication.transceiver;
679
691
  }
680
- offer = await traceReconnect(this.#recordReconnect, "create_offer", () => connection.createOffer());
681
- await traceReconnect(this.#recordReconnect, "set_local_description", () => connection.setLocalDescription(offer));
692
+ offer = await (() => connection.createOffer())();
693
+ await (() => connection.setLocalDescription(offer))();
682
694
  }
683
695
  const tracks = publications.map(
684
696
  ({ state, transceiver, trackName }) => ({
@@ -688,7 +700,7 @@ var CloudflareSFUClient = class {
688
700
  source: state.source
689
701
  })
690
702
  );
691
- const response = await traceReconnect(this.#recordReconnect, "publish_tracks", () => this.#requireTransport().addTracks({ connectionId, ...providerDescription(requireDescription(offer)), tracks }));
703
+ const response = await (() => this.#requireTransport().addTracks({ connectionId, ...providerDescription(requireDescription(offer)), tracks }))();
692
704
  this.#requireGeneration(generation);
693
705
  await this.#applyProviderDescription(response, connection);
694
706
  return response;
@@ -734,12 +746,12 @@ var CloudflareSFUClient = class {
734
746
  }
735
747
  }
736
748
  async #reconcileRemotePublications(authoritative, generation) {
737
- const cursor = this.#validatedRemotePublicationCursor(authoritative);
749
+ const cursor = this.#nextRemotePublicationCursor(authoritative);
738
750
  if (cursor === null) return;
739
- const ordering = comparePublicationCursor(this.#cursor, cursor);
740
- if (ordering === "stale" || ordering === "same" && !this.#remotePullIncomplete) return;
751
+ const participantRevision = this.#remoteParticipantRevision;
741
752
  const desired = this.#desiredRemotePublications(authoritative, cursor);
742
753
  const toPull = this.#pendingRemotePulls(desired);
754
+ this.#pollFailedPull = true;
743
755
  const pulled = await this.#pullWithRecovery(toPull, cursor, generation);
744
756
  if (pulled === null) return;
745
757
  this.#requireGeneration(generation);
@@ -748,8 +760,13 @@ var CloudflareSFUClient = class {
748
760
  this.#remoteTracks.clear();
749
761
  for (const [key, publication] of next) this.#remoteTracks.set(key, publication);
750
762
  this.#cursor = cursor;
751
- this.#remotePullIncomplete = [...desired].some(([key, publication]) => next.get(key)?.publicationId !== publication.publicationId);
752
- for (const publication of pulled) this.#invokeListener(() => this.#onRemoteTrack?.(publication));
763
+ this.#remotePullIncomplete = participantRevision !== this.#remoteParticipantRevision || next.size !== desired.size;
764
+ if (!this.#remotePullIncomplete) this.#remoteRecoveryGeneration = null;
765
+ if (!this.#remotePullIncomplete && this.#hasLiveRemoteTracks()) this.#replacementAttemptedGeneration = null;
766
+ for (const publication of pulled) {
767
+ this.#subscribedPublicationIds.add(publication.publicationId);
768
+ this.#invokeListener(() => this.#onRemoteTrack?.(publication));
769
+ }
753
770
  this.#publishSnapshot();
754
771
  this.#emitRemote();
755
772
  }
@@ -758,31 +775,40 @@ var CloudflareSFUClient = class {
758
775
  for (const publicationId of this.#remotePullRetryAfter.keys()) {
759
776
  if (!desiredIds.has(publicationId)) this.#remotePullRetryAfter.delete(publicationId);
760
777
  }
778
+ for (const publicationId of this.#subscribedPublicationIds) {
779
+ if (!desiredIds.has(publicationId)) this.#subscribedPublicationIds.delete(publicationId);
780
+ }
761
781
  const now = Date.now();
762
782
  return [...desired].filter(([key, publication]) => {
763
783
  if (this.#remoteTracks.get(key)?.publicationId === publication.publicationId) return false;
764
784
  const remaining = (this.#remotePullRetryAfter.get(publication.publicationId) ?? 0) - now;
765
- if (remaining > 0) recordReconnect(this.#recordReconnect, "remote_pull_deferred", { remaining_ms: remaining, source: publication.source });
766
785
  return remaining <= 0;
767
786
  }).map(([, publication]) => publication);
768
787
  }
769
- #validatedRemotePublicationCursor(authoritative) {
788
+ #nextRemotePublicationCursor(authoritative) {
770
789
  if (this.#cursor?.signature === INVALID_PUBLICATION_SIGNATURE && this.#cursor.incarnation === authoritative.incarnation && this.#cursor.sequence === authoritative.sequence) return null;
790
+ let cursor;
771
791
  try {
772
- return validatePublicationSnapshot(authoritative);
792
+ cursor = validatePublicationSnapshot(authoritative);
773
793
  } catch (error) {
774
794
  this.#quarantineInvalidRemotePublicationCursor(authoritative);
775
795
  throw error;
776
796
  }
797
+ const ordering = comparePublicationCursor(this.#cursor, cursor);
798
+ return ordering === "stale" || ordering === "same" && !this.#remotePullIncomplete ? null : cursor;
777
799
  }
778
800
  #desiredRemotePublications(authoritative, cursor) {
779
801
  try {
780
- return desiredRemotePublications(authoritative.publications, this.#participantId);
802
+ const publications = authoritative.publications.filter((publication) => this.#canSubscribeToRemotePublication(publication));
803
+ return desiredRemotePublications(publications, this.#participantId);
781
804
  } catch (error) {
782
805
  this.#observeRemotePublicationCursor(cursor);
783
806
  throw error;
784
807
  }
785
808
  }
809
+ #canSubscribeToRemotePublication(publication) {
810
+ return this.#remoteParticipantIds === null || this.#remoteParticipantIds.has(publication.participantId) || this.#subscribedPublicationIds.has(publication.publicationId);
811
+ }
786
812
  async #pullWithRecovery(publications, cursor, generation) {
787
813
  try {
788
814
  return await this.#pull(publications, generation);
@@ -796,12 +822,13 @@ var CloudflareSFUClient = class {
796
822
  }
797
823
  }
798
824
  #canReplaceConnectionAfterRemotePull(error, generation) {
799
- return error instanceof CloudflareSFUError && error.code === "signaling_failed" && !this.#hasLiveRemoteTracks() && this.#replaceMediaConnection !== void 0 && this.#replacementAttemptedGeneration !== generation;
825
+ return this.#isRetryableConnectionFailure(error) && !this.#hasLiveRemoteTracks() && this.#replaceMediaConnection !== void 0 && this.#replacementAttemptedGeneration !== generation;
800
826
  }
801
827
  #hasLiveRemoteTracks() {
802
828
  return [...this.#remoteTracks.values()].some((publication) => publication.track.readyState === "live");
803
829
  }
804
830
  async #retryRemotePullOnReplacement(publications, cursor, generation) {
831
+ this.#remoteRecoveryGeneration = generation;
805
832
  try {
806
833
  await this.#replaceMediaConnectionForRecovery(generation);
807
834
  await this.#republishPreparedTracksAfterRecovery(generation);
@@ -813,6 +840,44 @@ var CloudflareSFUClient = class {
813
840
  }
814
841
  }
815
842
  async #pull(publications, generation) {
843
+ const eligible = publications.filter((publication) => this.#canSubscribeToRemotePublication(publication));
844
+ try {
845
+ return await this.#pullBatch(eligible, generation);
846
+ } catch (error) {
847
+ this.#requireGeneration(generation);
848
+ if (eligible.length <= 1 || !retryableRemotePull(error) || isRateLimited(error) || this.#canReplaceConnectionAfterRemotePull(error, generation) || error instanceof CloudflareSFUError && (error.options.status === 404 || error.options.status === 410)) {
849
+ this.#deferRemotePulls(eligible, error);
850
+ throw error;
851
+ }
852
+ }
853
+ return this.#pullIndividually(eligible, generation);
854
+ }
855
+ async #pullIndividually(eligible, generation) {
856
+ const pulled = [];
857
+ let failure;
858
+ for (const [index, publication] of eligible.entries()) {
859
+ if (!this.#canSubscribeToRemotePublication(publication)) continue;
860
+ try {
861
+ pulled.push(...await this.#pullBatch([publication], generation));
862
+ } catch (error) {
863
+ this.#requireGeneration(generation);
864
+ this.#deferRemotePulls([publication], error);
865
+ this.#reportError(error);
866
+ if (failure === void 0 || retryableRemotePull(error)) failure = error;
867
+ if (isRateLimited(error)) {
868
+ this.#deferRemotePulls(eligible.slice(index + 1), error);
869
+ break;
870
+ }
871
+ }
872
+ }
873
+ if (failure && pulled.length === 0) throw failure;
874
+ return pulled;
875
+ }
876
+ #deferRemotePulls(publications, error) {
877
+ const retryAfter = retryableRemotePull(error) ? Date.now() + 15e3 : Number.POSITIVE_INFINITY;
878
+ for (const publication of publications) this.#remotePullRetryAfter.set(publication.publicationId, retryAfter);
879
+ }
880
+ async #pullBatch(publications, generation) {
816
881
  if (publications.length === 0) return [];
817
882
  return this.#serializeSDP(async () => {
818
883
  const connection = this.#connection;
@@ -836,7 +901,7 @@ var CloudflareSFUClient = class {
836
901
  connection.addEventListener("track", onTrack);
837
902
  try {
838
903
  this.#requireGeneration(generation);
839
- const response = await traceReconnect(this.#recordReconnect, "pull_tracks", () => this.#requireTransport().addTracks({ connectionId, tracks: requested, allowPartialRemoteTracks: true }));
904
+ const response = await (() => this.#requireTransport().addTracks({ connectionId, tracks: requested, allowPartialRemoteTracks: true }))();
840
905
  this.#requireGeneration(generation);
841
906
  this.#recordRemotePullErrors(publications, response.trackErrors ?? []);
842
907
  const responseTracks = response.tracks ?? [];
@@ -846,7 +911,7 @@ var CloudflareSFUClient = class {
846
911
  if (matched.length === 0) return [];
847
912
  await this.#waitForConnection(connection, generation);
848
913
  this.#negotiatedGeneration = generation;
849
- await traceReconnect(this.#recordReconnect, "remote_track_arrival", () => waitFor(() => responseTracks.every((track) => track.mid !== void 0 && received.has(track.mid)), 5e3));
914
+ await (() => waitFor(() => responseTracks.every((track) => track.mid !== void 0 && received.has(track.mid)), 5e3))();
850
915
  this.#requireGeneration(generation);
851
916
  return matched.map(({ publication, mid }) => {
852
917
  const track = received.get(mid);
@@ -871,26 +936,24 @@ var CloudflareSFUClient = class {
871
936
  for (const failure of failures) {
872
937
  const publication = requested.get(`${failure.connectionId}\0${failure.trackName}`);
873
938
  if (!publication) throw new CloudflareSFUError("Cloudflare SFU returned an unrequested remote track error", "invalid_publication");
874
- const delay = 750;
939
+ const delay = 15e3;
875
940
  this.#remotePullRetryAfter.set(publication.publicationId, Date.now() + delay);
876
- recordReconnect(this.#recordReconnect, "remote_pull_backoff", { delay_ms: delay, retry_kind: failure.code === "empty_track_error" ? "empty_track" : "provider_error", source: publication.source });
877
941
  this.#reportError(new CloudflareSFUError(`Cloudflare SFU remote track failed: ${failure.code}`, "media_failed", { providerCode: failure.code }));
878
942
  }
879
943
  }
880
944
  async #completeRenegotiation(response, connection, connectionId, generation) {
881
945
  if (!response.requiresImmediateRenegotiation) return;
882
946
  await this.#applyProviderDescription(response, connection);
883
- const answer = await traceReconnect(this.#recordReconnect, "create_answer", () => connection.createAnswer());
884
- await traceReconnect(this.#recordReconnect, "set_local_answer", () => connection.setLocalDescription(answer));
885
- await traceReconnect(this.#recordReconnect, "renegotiate", () => this.#requireTransport().renegotiate({ connectionId, ...providerDescription(requireDescription(answer)) }));
947
+ const answer = await (() => connection.createAnswer())();
948
+ await (() => connection.setLocalDescription(answer))();
949
+ await (() => this.#requireTransport().renegotiate({ connectionId, ...providerDescription(requireDescription(answer)) }))();
886
950
  this.#requireGeneration(generation);
887
951
  }
888
952
  async #applyProviderDescription(response, connection = this.#connection) {
889
- await traceReconnect(this.#recordReconnect, "set_remote_description", () => connection.setRemoteDescription(requireSFUDescription(response.sessionDescription)));
953
+ await (() => connection.setRemoteDescription(requireSFUDescription(response.sessionDescription)))();
890
954
  }
891
955
  #serializeSDP(operation) {
892
956
  const generation = this.#generation;
893
- const queuedAt = performance.now();
894
957
  let expired = false;
895
958
  let timer;
896
959
  const timeout = new CloudflareSFUError("Cloudflare SFU negotiation timed out. Try again.", "negotiation_timeout");
@@ -903,7 +966,6 @@ var CloudflareSFUClient = class {
903
966
  });
904
967
  const run = () => {
905
968
  if (expired || generation !== this.#generation) throw timeout;
906
- recordReconnect(this.#recordReconnect, "sdp_queue", { duration_ms: performance.now() - queuedAt });
907
969
  return operation();
908
970
  };
909
971
  const pending = this.#sdpTail.then(run, run);
@@ -944,18 +1006,14 @@ var CloudflareSFUClient = class {
944
1006
  this.#setFailure(error, "media_failed");
945
1007
  }
946
1008
  #waitForConnection(connection, generation) {
947
- return traceReconnect(
948
- this.#recordReconnect,
949
- "connection_ready",
950
- () => waitFor(
951
- () => {
952
- this.#requireGeneration(generation);
953
- return connectionIsLive(connection);
954
- },
955
- CONNECTION_TIMEOUT_MS,
956
- "Timed out waiting for the Cloudflare SFU peer connection"
957
- )
958
- );
1009
+ return (() => waitFor(
1010
+ () => {
1011
+ this.#requireGeneration(generation);
1012
+ return connectionIsLive(connection);
1013
+ },
1014
+ CONNECTION_TIMEOUT_MS,
1015
+ "Timed out waiting for the Cloudflare SFU peer connection"
1016
+ ))();
959
1017
  }
960
1018
  #cameraSimulcastSender(state) {
961
1019
  if (!state?.enabled) return void 0;
@@ -1018,84 +1076,16 @@ var CloudflareSFUClient = class {
1018
1076
  };
1019
1077
  const capture = () => {
1020
1078
  if (disposed || generation !== this.#generation || connectionEpoch !== this.#connectionEpoch || this.#stopped) return;
1021
- recordReconnect(this.#recordReconnect, "rtc_state", { connection_state: connection.connectionState, ice_connection_state: connection.iceConnectionState, signaling_state: connection.signalingState });
1022
1079
  observe();
1023
- const recorder = this.#onRtcSummary;
1024
- if (recorder) {
1025
- let stats;
1026
- try {
1027
- stats = Promise.resolve(connection.getStats());
1028
- } catch {
1029
- stats = Promise.reject(new Error("RTC stats are unavailable"));
1030
- }
1031
- void stats.then((report) => {
1032
- if (disposed || generation !== this.#generation || connectionEpoch !== this.#connectionEpoch || this.#stopped) return;
1033
- recorder(rtcConnectionState(connection), rtcStats(report));
1034
- }).catch(() => void 0);
1035
- }
1036
1080
  if (connection.connectionState === "closed") dispose();
1037
1081
  };
1038
1082
  this.#disposeConnectionObservation = dispose;
1039
1083
  cameraTimer = globalThis.setTimeout(adaptCamera, 1e3);
1040
- if (this.#recordReconnect) this.#sampleRecoveryTraffic(connection, generation, connectionEpoch);
1041
1084
  connection.addEventListener("connectionstatechange", capture);
1042
1085
  connection.addEventListener("iceconnectionstatechange", capture);
1043
1086
  connection.addEventListener("signalingstatechange", capture);
1044
1087
  capture();
1045
1088
  }
1046
- #sampleRecoveryTraffic(connection, generation, connectionEpoch) {
1047
- const traffic = /* @__PURE__ */ new Map();
1048
- let dtlsState;
1049
- const current = () => generation === this.#generation && connectionEpoch === this.#connectionEpoch && !this.#stopped && connection.connectionState !== "closed" && connection.connectionState !== "failed";
1050
- const sample = async () => {
1051
- if (!current()) return;
1052
- try {
1053
- const report = await connection.getStats();
1054
- if (!current()) return;
1055
- const now = performance.now();
1056
- report.forEach((entry) => {
1057
- if (entry.type === "transport") {
1058
- const nextState = rtcString(entry, "dtlsState");
1059
- if (nextState !== dtlsState) recordReconnect(this.#recordReconnect, "dtls_state", { dtls_state: nextState ?? "unknown" });
1060
- dtlsState = nextState;
1061
- }
1062
- this.#observeRecoveryTraffic(entry, traffic, now);
1063
- });
1064
- } catch {
1065
- if (current()) recordReconnect(this.#recordReconnect, "rtc_stats_unavailable");
1066
- }
1067
- if (current()) globalThis.setTimeout(() => void sample(), 250);
1068
- };
1069
- void sample();
1070
- }
1071
- #observeRecoveryTraffic(entry, traffic, now) {
1072
- if (entry.type !== "inbound-rtp" && entry.type !== "outbound-rtp") return;
1073
- const direction = entry.type === "inbound-rtp" ? "inbound" : "outbound";
1074
- const kind = rtcString(entry, "kind") ?? "unknown";
1075
- const previous = traffic.get(entry.id);
1076
- const next = previous ?? {
1077
- packets: { count: 0, lastProgress: now, stalled: false },
1078
- frames: { count: 0, lastProgress: now, stalled: false }
1079
- };
1080
- const packets = recoveryTrafficCount(entry, direction, "packet");
1081
- const frames = recoveryTrafficCount(entry, direction, "frame");
1082
- this.#observeTrafficCounter(next.packets, { count: packets, now, direction, kind, unit: "packet", existed: previous !== void 0 });
1083
- this.#observeTrafficCounter(next.frames, { count: frames, now, direction, kind, unit: "frame", existed: previous !== void 0 });
1084
- traffic.set(entry.id, next);
1085
- }
1086
- #observeTrafficCounter(progress, sample) {
1087
- const attributes = { direction: sample.direction, kind: sample.kind };
1088
- if (sample.count > progress.count) {
1089
- if (progress.count === 0) recordReconnect(this.#recordReconnect, `first_${sample.unit}`, attributes);
1090
- else if (progress.stalled) recordReconnect(this.#recordReconnect, sample.unit === "packet" ? "rtp_resumed" : "frame_resumed", attributes);
1091
- progress.lastProgress = sample.now;
1092
- progress.stalled = false;
1093
- } else if (trafficStalled(progress, sample)) {
1094
- progress.stalled = true;
1095
- recordReconnect(this.#recordReconnect, sample.unit === "packet" ? "rtp_stall" : "frame_stall", attributes);
1096
- }
1097
- progress.count = sample.count;
1098
- }
1099
1089
  async #replaceDormantConnectionBeforeNegotiation(generation) {
1100
1090
  if (!this.#started || this.#negotiatedGeneration === generation || this.#replacementAttemptedGeneration === generation || !this.#replaceMediaConnection) return;
1101
1091
  await this.#replaceMediaConnectionForRecovery(generation);
@@ -1107,7 +1097,7 @@ var CloudflareSFUClient = class {
1107
1097
  }
1108
1098
  this.#replacementAttemptedGeneration = generation;
1109
1099
  const replaceMediaConnection = this.#replaceMediaConnection;
1110
- const bootstrap = await traceReconnect(this.#recordReconnect, "replace_access", replaceMediaConnection);
1100
+ const bootstrap = await replaceMediaConnection();
1111
1101
  this.#requireGeneration(generation);
1112
1102
  validateBootstrap(bootstrap);
1113
1103
  if (bootstrap.connectionId === this.#bootstrap.connectionId) {
@@ -1120,16 +1110,14 @@ var CloudflareSFUClient = class {
1120
1110
  this.#clearRemoteTracks();
1121
1111
  this.#cursor = null;
1122
1112
  this.#bootstrap = bootstrap;
1123
- this.#connection = this.#createPeerConnection(bootstrap);
1113
+ this.#rebuildPeerConnection(bootstrap, generation, connectionEpoch);
1124
1114
  this.#retiredLocalConnection = null;
1125
1115
  this.#negotiatedGeneration = null;
1126
- this.#observeConnection(this.#connection, generation, connectionEpoch);
1127
1116
  for (const state of this.#localTracks.values()) {
1128
1117
  state.transceiver = null;
1129
1118
  state.enabled = false;
1130
1119
  state.providerPublicationId = null;
1131
1120
  }
1132
- this.#setPhase("recovering", null);
1133
1121
  this.remotePublicationsChanged();
1134
1122
  }
1135
1123
  async #republishPreparedTracksAfterRecovery(generation) {
@@ -1152,7 +1140,17 @@ var CloudflareSFUClient = class {
1152
1140
  const create = this.#peerConnectionFactory ?? ((configuration) => new RTCPeerConnection(configuration));
1153
1141
  return create({ iceServers: [{ urls: bootstrap.stunServer }], bundlePolicy: "max-bundle" });
1154
1142
  }
1143
+ #rebuildPeerConnection(bootstrap, generation, connectionEpoch) {
1144
+ try {
1145
+ this.#connection = this.#createPeerConnection(bootstrap);
1146
+ this.#observeConnection(this.#connection, generation, connectionEpoch);
1147
+ } catch (error) {
1148
+ this.#setFailure(error, "media_failed");
1149
+ throw error;
1150
+ }
1151
+ }
1155
1152
  #disposeConnection(stopSenders) {
1153
+ if (!stopSenders) this.#setPhase("recovering", null);
1156
1154
  this.#disposeConnectionObservation?.();
1157
1155
  this.#disposeConnectionObservation = void 0;
1158
1156
  if (stopSenders) {
@@ -1198,15 +1196,27 @@ var CloudflareSFUClient = class {
1198
1196
  this.#schedulePoll();
1199
1197
  return;
1200
1198
  }
1199
+ let nextDelay = this.#pollIntervalMs;
1201
1200
  try {
1202
1201
  await this.refreshRemotePublications();
1202
+ if (this.#remotePullIncomplete) nextDelay = this.#incompletePullDelay();
1203
1203
  } catch {
1204
+ if (this.#pollFailedPull && this.#remotePullIncomplete) nextDelay = this.#incompletePullDelay();
1205
+ else {
1206
+ this.#pollFailures = Math.min(this.#pollFailures + 1, 6);
1207
+ nextDelay = Math.min(this.#pollIntervalMs, (500 + Math.random() * 500) * 2 ** (this.#pollFailures - 1));
1208
+ }
1204
1209
  } finally {
1205
- this.#schedulePoll(this.#remotePullIncomplete ? 750 : this.#pollIntervalMs);
1210
+ this.#schedulePoll(nextDelay);
1206
1211
  }
1207
1212
  }, delayMs);
1208
1213
  }
1214
+ #incompletePullDelay() {
1215
+ this.#pollFailures = Math.min(this.#pollFailures + 1, 6);
1216
+ return Math.min(this.#pollIntervalMs, 750 * 2 ** (this.#pollFailures - 1));
1217
+ }
1209
1218
  #clearPoll() {
1219
+ this.#pollFailures = 0;
1210
1220
  if (this.#pollTimer !== void 0) globalThis.clearTimeout(this.#pollTimer);
1211
1221
  this.#pollTimer = void 0;
1212
1222
  }
@@ -1291,76 +1301,11 @@ function validateClientOptions(options) {
1291
1301
  throw new CloudflareSFUError("Cloudflare SFU polling interval is invalid", "invalid_bootstrap");
1292
1302
  }
1293
1303
  }
1294
- function rtcConnectionState(connection) {
1295
- return {
1296
- connectionState: connection.connectionState,
1297
- iceConnectionState: connection.iceConnectionState,
1298
- signalingState: connection.signalingState
1299
- };
1300
- }
1301
- function rtcStats(report) {
1302
- const entries = [];
1303
- report.forEach((entry) => {
1304
- if (typeof entry.type !== "string") return;
1305
- entries.push({ type: entry.type, ...rtcTrafficStats(entry), ...rtcQualityStats(entry), ...rtcTransportStats(entry) });
1306
- });
1307
- return entries;
1308
- }
1309
- function rtcTrafficStats(entry) {
1310
- const bytesReceived = rtcNumber(entry, "bytesReceived");
1311
- const bytesSent = rtcNumber(entry, "bytesSent");
1312
- const packetsReceived = rtcNumber(entry, "packetsReceived");
1313
- const packetsSent = rtcNumber(entry, "packetsSent");
1314
- return {
1315
- ...bytesReceived !== void 0 ? { bytesReceived } : {},
1316
- ...bytesSent !== void 0 ? { bytesSent } : {},
1317
- ...packetsReceived !== void 0 ? { packetsReceived } : {},
1318
- ...packetsSent !== void 0 ? { packetsSent } : {}
1319
- };
1320
- }
1321
- function rtcQualityStats(entry) {
1322
- const kind = rtcString(entry, "kind");
1323
- const framesDropped = rtcNumber(entry, "framesDropped");
1324
- const jitter = rtcNumber(entry, "jitter");
1325
- const packetsLost = rtcNumber(entry, "packetsLost");
1326
- const roundTripTime = rtcNumber(entry, "roundTripTime");
1327
- return {
1328
- ...kind !== void 0 ? { kind } : {},
1329
- ...framesDropped !== void 0 ? { framesDropped } : {},
1330
- ...jitter !== void 0 ? { jitter } : {},
1331
- ...packetsLost !== void 0 ? { packetsLost } : {},
1332
- ...roundTripTime !== void 0 ? { roundTripTime } : {}
1333
- };
1334
- }
1335
- function rtcTransportStats(entry) {
1336
- const state = rtcString(entry, "state");
1337
- const selected = rtcBoolean(entry, "selected");
1338
- const nominated = rtcBoolean(entry, "nominated");
1339
- const dtlsState = rtcString(entry, "dtlsState");
1340
- return {
1341
- ...state !== void 0 ? { state } : {},
1342
- ...selected !== void 0 ? { selected } : {},
1343
- ...nominated !== void 0 ? { nominated } : {},
1344
- ...dtlsState !== void 0 ? { dtlsState } : {}
1345
- };
1346
- }
1347
- function rtcString(value, property) {
1348
- const candidate = Reflect.get(value, property);
1349
- return typeof candidate === "string" ? candidate : void 0;
1350
- }
1351
- function rtcNumber(value, property) {
1352
- const candidate = Reflect.get(value, property);
1353
- return typeof candidate === "number" ? candidate : void 0;
1354
- }
1355
- function rtcBoolean(value, property) {
1356
- const candidate = Reflect.get(value, property);
1357
- return typeof candidate === "boolean" ? candidate : void 0;
1358
- }
1359
1304
  function validateBootstrap(bootstrap) {
1360
1305
  if (!bootstrap.connectionId.trim() || !bootstrap.stunServer.trim()) throw new CloudflareSFUError("Cloudflare SFU bootstrap is incomplete", "invalid_bootstrap");
1361
1306
  }
1362
1307
  function validateTrackSource(source, track) {
1363
- const valid = source === "microphone" ? track.kind === "audio" : track.kind === "video";
1308
+ const valid = source === "microphone" || source === "screen_audio" ? track.kind === "audio" : track.kind === "video";
1364
1309
  if (!valid) throw new CloudflareSFUError(`The prepared ${source} track has an incompatible kind`, "media_failed");
1365
1310
  }
1366
1311
  function desiredRemotePublications(publications, participantId) {
@@ -1437,8 +1382,13 @@ function remotePublicationEqual(left, right) {
1437
1382
  function providerDescription(description) {
1438
1383
  return { sessionDescription: description };
1439
1384
  }
1385
+ function isRateLimited(error) {
1386
+ return error instanceof CloudflareSFUError && error.options.status === 429;
1387
+ }
1440
1388
  function retryableRemotePull(error) {
1441
- return error instanceof CloudflareSFUError && ["signaling_failed", "signaling_timeout", "media_failed", "negotiation_timeout"].includes(error.code);
1389
+ if (!(error instanceof CloudflareSFUError)) return false;
1390
+ if (error.code === "signaling_failed") return error.options.retryableConnection === true || error.options.status === 429;
1391
+ return ["signaling_timeout", "media_failed", "negotiation_timeout"].includes(error.code);
1442
1392
  }
1443
1393
  function firefoxCameraSimulcastUnsupported() {
1444
1394
  return typeof navigator !== "undefined" && /\bFirefox\/\d+/i.test(navigator.userAgent);
@@ -1451,13 +1401,6 @@ function mediaTargetFailure(error) {
1451
1401
  const ambiguous = code === "signaling_timeout" || code === "negotiation_timeout";
1452
1402
  return { outcome: ambiguous ? "ambiguous" : "retryable_failure", errorCode: code };
1453
1403
  }
1454
- function recoveryTrafficCount(entry, direction, unit) {
1455
- const property = unit === "packet" ? direction === "inbound" ? "packetsReceived" : "packetsSent" : direction === "inbound" ? "framesDecoded" : "framesEncoded";
1456
- return rtcNumber(entry, property) ?? 0;
1457
- }
1458
- function trafficStalled(progress, sample) {
1459
- return sample.existed && !progress.stalled && sample.now - progress.lastProgress >= 1e3 && (sample.unit === "packet" || progress.count > 0);
1460
- }
1461
1404
 
1462
1405
  // src/media/transport.ts
1463
1406
  var SIGNALING_TIMEOUT_MS = 8e3;
@@ -1476,7 +1419,7 @@ function createCloudflareSFUHTTPTransport(options) {
1476
1419
  const response = await fetch(`${mediaPath}/${path}`, {
1477
1420
  ...init,
1478
1421
  signal,
1479
- headers: { ...options.telemetry ? journeyHeaders(options.telemetry) : {}, Authorization: `Bearer ${token}`, "Content-Type": "application/json", ...init?.headers }
1422
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", ...init?.headers }
1480
1423
  });
1481
1424
  if (!response.ok) {
1482
1425
  throw new CloudflareSFUError(`Chalk SFU signaling failed with HTTP ${response.status}`, "signaling_failed", {
@@ -1553,416 +1496,6 @@ function requireCredential(options) {
1553
1496
  throw new CloudflareSFUError("A media credential provider is required", "signaling_failed");
1554
1497
  }
1555
1498
 
1556
- // src/media/rtk.ts
1557
- var CloudflareRTKError = class extends Error {
1558
- code;
1559
- constructor(message, code) {
1560
- super(message);
1561
- this.name = "CloudflareRTKError";
1562
- this.code = code;
1563
- }
1564
- };
1565
- var EMPTY_LOCAL2 = Object.freeze([]);
1566
- var EMPTY_REMOTE2 = Object.freeze([]);
1567
- var CloudflareRTKClient = class {
1568
- #participantId;
1569
- #onError;
1570
- #onScreenEnded;
1571
- #clientFactory;
1572
- #snapshotListeners = /* @__PURE__ */ new Set();
1573
- #sourceOperations = /* @__PURE__ */ new Map();
1574
- #connection = null;
1575
- #localTracks = /* @__PURE__ */ new Map();
1576
- #connectionUnsubscribers = [];
1577
- #participantUnsubscribers = /* @__PURE__ */ new Map();
1578
- #snapshot;
1579
- #stopped = false;
1580
- #screenDisableRequested = false;
1581
- #initialAuthToken;
1582
- constructor(options) {
1583
- if (!options.authToken.trim()) throw new CloudflareRTKError("A RealtimeKit auth token is required", "invalid_client");
1584
- if (!options.participantId.trim()) throw new CloudflareRTKError("A participant ID is required", "invalid_participant");
1585
- this.#participantId = options.participantId;
1586
- this.#onError = options.onError;
1587
- this.#onScreenEnded = options.onScreenEnded;
1588
- this.#clientFactory = options.clientFactory ?? defaultClientFactory;
1589
- this.#snapshot = freezeSnapshot2({
1590
- connection: { phase: "idle", peerConnectionState: null, iceConnectionState: null },
1591
- cursor: null,
1592
- localTracks: EMPTY_LOCAL2,
1593
- remoteTracks: EMPTY_REMOTE2,
1594
- failure: null
1595
- });
1596
- this.#initialAuthToken = options.authToken;
1597
- }
1598
- getSnapshot() {
1599
- return this.#snapshot;
1600
- }
1601
- subscribe(listener) {
1602
- return subscribeSnapshot(this.#snapshotListeners, listener);
1603
- }
1604
- prepareLocalTrack(source, track) {
1605
- this.#requireStartable();
1606
- validateTrack(source, track);
1607
- if (this.#localTracks.has(source)) throw new CloudflareRTKError(`A ${source} track is already prepared`, "media_failed");
1608
- const endedListener = source === "screen" ? () => this.#onScreenEnded?.() : null;
1609
- if (endedListener) track.addEventListener("ended", endedListener);
1610
- this.#localTracks.set(source, { source, track, enabled: false, endedListener });
1611
- this.#publishSnapshot();
1612
- }
1613
- async clearPreparedLocalTrack(source) {
1614
- const state = this.#localTracks.get(source);
1615
- if (!state) return;
1616
- if (state.enabled) {
1617
- await this.#setSourceEnabled(state, false);
1618
- }
1619
- if (state.endedListener) state.track.removeEventListener("ended", state.endedListener);
1620
- this.#localTracks.delete(source);
1621
- this.#publishSnapshot();
1622
- }
1623
- async start(localMedia) {
1624
- if (this.#snapshot.connection.phase === "live") return;
1625
- this.#requireStartable();
1626
- this.#setPhase("connecting", null);
1627
- try {
1628
- const tracks = localMedia.getTracks().filter((track) => track.kind === "audio" || track.kind === "video");
1629
- for (const track of tracks) {
1630
- const source = track.kind === "audio" ? "microphone" : "camera";
1631
- if (!this.#localTracks.has(source)) this.#localTracks.set(source, { source, track, enabled: false, endedListener: null });
1632
- }
1633
- await this.#openConnection(this.#initialAuthToken);
1634
- for (const state of this.#localTracks.values()) {
1635
- if (state.source === "microphone" || state.source === "camera") await this.#setSourceEnabled(state, true);
1636
- }
1637
- this.#setPhase("live", null);
1638
- } catch (error) {
1639
- this.#setFailure(error);
1640
- this.#reportError(error);
1641
- throw error;
1642
- }
1643
- }
1644
- async setLocalPublicationTarget(target) {
1645
- const resolved = resolveMediaTarget(this.#participantId, this.#stopped, this.#localTracks, target);
1646
- if (resolved.kind === "result") return resolved.result;
1647
- const state = resolved.value;
1648
- const previous = this.#sourceOperations.get(target.source) ?? Promise.resolve();
1649
- const operation = previous.catch(() => void 0).then(async () => {
1650
- if (state.enabled === target.enabled) return "satisfied";
1651
- try {
1652
- await this.#setSourceEnabled(state, target.enabled);
1653
- return "confirmed";
1654
- } catch (error) {
1655
- this.#reportError(error);
1656
- return "retryable_failure";
1657
- }
1658
- });
1659
- this.#sourceOperations.set(target.source, operation);
1660
- try {
1661
- const outcome = await operation;
1662
- return { outcome, errorCode: outcome === "retryable_failure" ? "media_failed" : null };
1663
- } finally {
1664
- if (this.#sourceOperations.get(target.source) === operation) this.#sourceOperations.delete(target.source);
1665
- }
1666
- }
1667
- observeLocalPublications(listener) {
1668
- return observePublications(this.#snapshotListeners, listener, () => this.#projectLocalPublications());
1669
- }
1670
- observeRemotePublications(listener) {
1671
- return observePublications(this.#snapshotListeners, listener, () => this.#projectRemotePublications());
1672
- }
1673
- async restart(input) {
1674
- if (!isRTKAccess(input)) throw new CloudflareRTKError("The RealtimeKit adapter requires a Cloudflare RealtimeKit access grant", "invalid_client");
1675
- if (this.#stopped) throw new CloudflareRTKError("The RealtimeKit media client has stopped", "media_stopped");
1676
- this.#setPhase("recovering", null);
1677
- try {
1678
- await this.#closeConnection();
1679
- await this.#openConnection(input.clientPayload.token);
1680
- for (const state of this.#localTracks.values()) if (state.enabled) await this.#setSourceEnabled(state, true);
1681
- this.#setPhase("live", null);
1682
- } catch (error) {
1683
- this.#setFailure(error);
1684
- this.#reportError(error);
1685
- throw error;
1686
- }
1687
- }
1688
- stop() {
1689
- if (this.#stopped) return;
1690
- this.#stopped = true;
1691
- const leave = this.#closeConnection();
1692
- void leave.catch((error) => this.#reportError(error));
1693
- for (const state of this.#localTracks.values()) if (state.endedListener) state.track.removeEventListener("ended", state.endedListener);
1694
- this.#localTracks.clear();
1695
- this.#setPhase("stopped", null);
1696
- }
1697
- async #openConnection(authToken) {
1698
- this.#connection = await this.#clientFactory({ authToken, onError: (error) => this.#reportError(error) });
1699
- this.#bindConnection(this.#connection);
1700
- await this.#connection.join();
1701
- }
1702
- async #closeConnection() {
1703
- const connection = this.#connection;
1704
- this.#connection = null;
1705
- this.#unbindConnection();
1706
- if (connection) await connection.leave();
1707
- }
1708
- #bindConnection(connection) {
1709
- const selfAudio = connection.self.onAudioUpdate(() => this.#syncLocalState("microphone"));
1710
- const selfVideo = connection.self.onVideoUpdate(() => this.#syncLocalState("camera"));
1711
- const selfScreen = connection.self.onScreenShareUpdate((payload) => {
1712
- this.#syncLocalState("screen");
1713
- if (!payload.screenShareEnabled && !this.#screenDisableRequested) this.#onScreenEnded?.();
1714
- this.#screenDisableRequested = false;
1715
- });
1716
- const connectionLeft = connection.self.onLeft(() => {
1717
- if (this.#stopped) return;
1718
- const error = new CloudflareRTKError("The RealtimeKit connection ended unexpectedly", "media_failed");
1719
- this.#setFailure(error);
1720
- this.#reportError(error);
1721
- });
1722
- for (const participant of connection.participants.joined.list()) this.#bindParticipant(participant);
1723
- const participantJoined = connection.participants.joined.onJoined((participant) => {
1724
- this.#bindParticipant(participant);
1725
- this.#publishSnapshot();
1726
- });
1727
- const participantLeft = connection.participants.joined.onLeft((participant) => {
1728
- this.#unbindParticipant(participant.id);
1729
- this.#publishSnapshot();
1730
- });
1731
- this.#connectionUnsubscribers = [selfAudio, selfVideo, selfScreen, connectionLeft, participantJoined, participantLeft];
1732
- }
1733
- #unbindConnection() {
1734
- for (const unsubscribe of this.#connectionUnsubscribers) unsubscribe();
1735
- this.#connectionUnsubscribers = [];
1736
- for (const unsubscribers of this.#participantUnsubscribers.values()) for (const unsubscribe of unsubscribers) unsubscribe();
1737
- this.#participantUnsubscribers.clear();
1738
- }
1739
- #bindParticipant(participant) {
1740
- if (this.#participantUnsubscribers.has(participant.id)) return;
1741
- const unsubscribers = [participant.onAudioUpdate?.(() => this.#publishSnapshot()), participant.onVideoUpdate?.(() => this.#publishSnapshot()), participant.onScreenShareUpdate?.(() => this.#publishSnapshot())].filter((unsubscribe) => unsubscribe !== void 0);
1742
- this.#participantUnsubscribers.set(participant.id, unsubscribers);
1743
- }
1744
- #unbindParticipant(participantId) {
1745
- const unsubscribers = this.#participantUnsubscribers.get(participantId);
1746
- if (!unsubscribers) return;
1747
- for (const unsubscribe of unsubscribers) unsubscribe();
1748
- this.#participantUnsubscribers.delete(participantId);
1749
- }
1750
- async #setSourceEnabled(state, enabled) {
1751
- const connection = this.#connection;
1752
- if (!connection) throw new CloudflareRTKError("The RealtimeKit connection is not active", "media_stopped");
1753
- if (state.source === "microphone") {
1754
- if (enabled) await connection.self.enableAudio(state.track);
1755
- else await connection.self.disableAudio();
1756
- } else if (state.source === "camera") {
1757
- if (enabled) await connection.self.enableVideo(state.track);
1758
- else await connection.self.disableVideo();
1759
- } else {
1760
- this.#screenDisableRequested = !enabled;
1761
- if (enabled) await connection.self.enableScreenShare();
1762
- else await connection.self.disableScreenShare();
1763
- }
1764
- state.enabled = enabled;
1765
- state.track.enabled = enabled;
1766
- this.#publishSnapshot();
1767
- }
1768
- #syncLocalState(source) {
1769
- const state = this.#localTracks.get(source);
1770
- const connection = this.#connection;
1771
- if (!state || !connection) return;
1772
- state.enabled = source === "microphone" ? connection.self.audioEnabled : source === "camera" ? connection.self.videoEnabled : connection.self.screenShareEnabled;
1773
- state.track.enabled = state.enabled;
1774
- this.#publishSnapshot();
1775
- }
1776
- #publishSnapshot() {
1777
- this.#snapshot = freezeSnapshot2({ ...this.#snapshot, localTracks: this.#projectLocalTracks(), remoteTracks: this.#projectRemoteTracks() });
1778
- this.#notifyListeners();
1779
- }
1780
- #projectLocalTracks() {
1781
- return Object.freeze([...this.#localTracks.values()].map((state) => ({ source: state.source, enabled: state.enabled, publicationId: `${this.#participantId}:${state.source}`, track: state.track })));
1782
- }
1783
- #projectLocalPublications() {
1784
- return Object.freeze(this.#projectLocalTracks().map((track) => ({ participantId: this.#participantId, source: track.source, enabled: track.enabled, publicationId: track.publicationId })));
1785
- }
1786
- #projectRemotePublications() {
1787
- return Object.freeze(this.#projectRemoteTracks().map((track) => ({ participantId: track.participantId, source: track.source, enabled: true, publicationId: track.publicationId })));
1788
- }
1789
- #projectRemoteTracks() {
1790
- return projectRemoteTracks(this.#connection?.participants.joined.list() ?? []);
1791
- }
1792
- #setPhase(phase, failure) {
1793
- this.#snapshot = freezeSnapshot2({ ...this.#snapshot, connection: { phase, peerConnectionState: null, iceConnectionState: null }, failure });
1794
- this.#notifyListeners();
1795
- }
1796
- #setFailure(error) {
1797
- this.#setPhase("failed", { code: "media_failed", recoverable: true });
1798
- if (!(error instanceof CloudflareRTKError)) this.#reportError(error);
1799
- }
1800
- #requireStartable() {
1801
- if (this.#stopped) throw new CloudflareRTKError("The RealtimeKit media client has stopped", "media_stopped");
1802
- }
1803
- #reportError(error) {
1804
- try {
1805
- this.#onError?.(error);
1806
- } catch {
1807
- }
1808
- }
1809
- #notifyListeners() {
1810
- for (const listener of this.#snapshotListeners) {
1811
- try {
1812
- listener();
1813
- } catch (error) {
1814
- this.#reportError(error);
1815
- }
1816
- }
1817
- }
1818
- };
1819
- function projectRemoteTracks(participants) {
1820
- return Object.freeze(participants.flatMap(remoteTracksForParticipant));
1821
- }
1822
- function remoteTracksForParticipant(participant) {
1823
- const participantId = participantIdentifier(participant);
1824
- if (!participantId) return [];
1825
- const publications = [];
1826
- addRemotePublication(publications, participant, participantId, "microphone", participant.audioEnabled ? participant.audioTrack : null);
1827
- addRemotePublication(publications, participant, participantId, "camera", participant.videoEnabled ? participant.videoTrack : null);
1828
- addRemotePublication(publications, participant, participantId, "screen", participant.screenShareEnabled ? participant.screenShareTracks.video ?? null : null);
1829
- return publications;
1830
- }
1831
- function participantIdentifier(participant) {
1832
- return participant.customParticipantId?.trim() || participant.userId.trim() || participant.id.trim();
1833
- }
1834
- function addRemotePublication(publications, participant, participantId, source, track) {
1835
- if (!track) return;
1836
- publications.push({ participantId, source, publicationId: `${participant.id}:${source}`, track });
1837
- }
1838
- function validateTrack(source, track) {
1839
- const expectedKind = source === "microphone" ? "audio" : "video";
1840
- if (track.kind !== expectedKind) throw new CloudflareRTKError(`A ${source} track must be ${expectedKind}`, "invalid_target");
1841
- }
1842
- function isRTKAccess(value) {
1843
- return value.provider === "cloudflare_rtk";
1844
- }
1845
- function freezeSnapshot2(snapshot) {
1846
- const failure = snapshot.failure ? Object.freeze(snapshot.failure) : null;
1847
- return Object.freeze({
1848
- ...snapshot,
1849
- connection: Object.freeze(snapshot.connection),
1850
- localTracks: Object.freeze(snapshot.localTracks.map((track) => Object.freeze(track))),
1851
- remoteTracks: Object.freeze(snapshot.remoteTracks.map((track) => Object.freeze(track))),
1852
- failure
1853
- });
1854
- }
1855
- async function defaultClientFactory(input) {
1856
- const { default: RealtimeKitClient } = await import("@cloudflare/realtimekit");
1857
- const client = await RealtimeKitClient.init({
1858
- authToken: input.authToken,
1859
- defaults: { audio: false, video: false },
1860
- onError: input.onError
1861
- });
1862
- return adaptRealtimeKitClient(client);
1863
- }
1864
- function adaptRealtimeKitClient(client) {
1865
- const self = client.self;
1866
- return {
1867
- join: () => client.join(),
1868
- leave: () => client.leave(),
1869
- self: {
1870
- get peerId() {
1871
- return self.peerId;
1872
- },
1873
- get audioEnabled() {
1874
- return self.audioEnabled;
1875
- },
1876
- get videoEnabled() {
1877
- return self.videoEnabled;
1878
- },
1879
- get screenShareEnabled() {
1880
- return self.screenShareEnabled;
1881
- },
1882
- get audioTrack() {
1883
- return self.audioTrack ?? null;
1884
- },
1885
- get videoTrack() {
1886
- return self.videoTrack ?? null;
1887
- },
1888
- get screenShareTracks() {
1889
- return {
1890
- ...self.screenShareTracks.audio ? { audio: self.screenShareTracks.audio } : {},
1891
- ...self.screenShareTracks.video ? { video: self.screenShareTracks.video } : {}
1892
- };
1893
- },
1894
- enableAudio: (customTrack) => self.enableAudio(customTrack),
1895
- enableVideo: (customTrack) => self.enableVideo(customTrack),
1896
- enableScreenShare: () => self.enableScreenShare(),
1897
- disableAudio: () => self.disableAudio(),
1898
- disableVideo: () => self.disableVideo(),
1899
- disableScreenShare: () => self.disableScreenShare(),
1900
- onAudioUpdate: (listener) => {
1901
- self.on("audioUpdate", listener);
1902
- return () => self.off("audioUpdate", listener);
1903
- },
1904
- onVideoUpdate: (listener) => {
1905
- self.on("videoUpdate", listener);
1906
- return () => self.off("videoUpdate", listener);
1907
- },
1908
- onScreenShareUpdate: (listener) => {
1909
- self.on("screenShareUpdate", listener);
1910
- return () => self.off("screenShareUpdate", listener);
1911
- },
1912
- onLeft: (listener) => {
1913
- self.on("roomLeft", listener);
1914
- return () => self.off("roomLeft", listener);
1915
- }
1916
- },
1917
- participants: {
1918
- joined: {
1919
- list: () => client.participants.joined.toArray().map(toParticipant),
1920
- onJoined: (listener) => {
1921
- const callback = (participant) => listener(toParticipant(participant));
1922
- client.participants.joined.on("participantJoined", callback);
1923
- return () => client.participants.joined.off("participantJoined", callback);
1924
- },
1925
- onLeft: (listener) => {
1926
- const callback = (participant) => listener(toParticipant(participant));
1927
- client.participants.joined.on("participantLeft", callback);
1928
- return () => client.participants.joined.off("participantLeft", callback);
1929
- }
1930
- }
1931
- }
1932
- };
1933
- }
1934
- function toParticipant(participant) {
1935
- return {
1936
- id: participant.id,
1937
- userId: participant.userId,
1938
- ...participant.customParticipantId === void 0 ? {} : { customParticipantId: participant.customParticipantId },
1939
- audioEnabled: participant.audioEnabled,
1940
- videoEnabled: participant.videoEnabled,
1941
- screenShareEnabled: participant.screenShareEnabled,
1942
- audioTrack: participant.audioTrack ?? null,
1943
- videoTrack: participant.videoTrack ?? null,
1944
- screenShareTracks: {
1945
- ...participant.screenShareTracks.audio ? { audio: participant.screenShareTracks.audio } : {},
1946
- ...participant.screenShareTracks.video ? { video: participant.screenShareTracks.video } : {}
1947
- },
1948
- onAudioUpdate: (listener) => {
1949
- const callback = () => listener();
1950
- participant.on("audioUpdate", callback);
1951
- return () => participant.off("audioUpdate", callback);
1952
- },
1953
- onVideoUpdate: (listener) => {
1954
- const callback = () => listener();
1955
- participant.on("videoUpdate", callback);
1956
- return () => participant.off("videoUpdate", callback);
1957
- },
1958
- onScreenShareUpdate: (listener) => {
1959
- const callback = () => listener();
1960
- participant.on("screenShareUpdate", callback);
1961
- return () => participant.off("screenShareUpdate", callback);
1962
- }
1963
- };
1964
- }
1965
-
1966
1499
  // src/media/capture-error.ts
1967
1500
  function describeMediaCaptureError(cause) {
1968
1501
  const name = typeof cause === "object" && cause !== null && "name" in cause ? cause.name : void 0;
@@ -1988,6 +1521,7 @@ function describeMediaCaptureError(cause) {
1988
1521
  }
1989
1522
 
1990
1523
  export {
1524
+ describeMediaCaptureError,
1991
1525
  CloudflareSFUError,
1992
1526
  parseCloudflareSFUPublicationID,
1993
1527
  requireDescription,
@@ -1997,8 +1531,5 @@ export {
1997
1531
  publicationKey,
1998
1532
  waitFor,
1999
1533
  CloudflareSFUClient,
2000
- createCloudflareSFUHTTPTransport,
2001
- CloudflareRTKError,
2002
- CloudflareRTKClient,
2003
- describeMediaCaptureError
1534
+ createCloudflareSFUHTTPTransport
2004
1535
  };