livekit-client 2.17.1 → 2.17.3

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 (145) hide show
  1. package/README.md +7 -5
  2. package/dist/livekit-client.e2ee.worker.js +1 -1
  3. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  4. package/dist/livekit-client.e2ee.worker.mjs +21 -14
  5. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  6. package/dist/livekit-client.esm.mjs +2087 -1920
  7. package/dist/livekit-client.esm.mjs.map +1 -1
  8. package/dist/livekit-client.umd.js +1 -1
  9. package/dist/livekit-client.umd.js.map +1 -1
  10. package/dist/src/e2ee/E2eeManager.d.ts +2 -0
  11. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  12. package/dist/src/e2ee/KeyProvider.d.ts +2 -0
  13. package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
  14. package/dist/src/e2ee/events.d.ts +1 -1
  15. package/dist/src/e2ee/events.d.ts.map +1 -1
  16. package/dist/src/e2ee/types.d.ts +1 -0
  17. package/dist/src/e2ee/types.d.ts.map +1 -1
  18. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +2 -2
  19. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
  20. package/dist/src/index.d.ts +7 -6
  21. package/dist/src/index.d.ts.map +1 -1
  22. package/dist/src/logger.d.ts +2 -1
  23. package/dist/src/logger.d.ts.map +1 -1
  24. package/dist/src/room/PCTransport.d.ts +1 -4
  25. package/dist/src/room/PCTransport.d.ts.map +1 -1
  26. package/dist/src/room/PCTransportManager.d.ts.map +1 -1
  27. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  28. package/dist/src/room/Room.d.ts.map +1 -1
  29. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
  30. package/dist/src/room/data-stream/incoming/StreamReader.d.ts +2 -4
  31. package/dist/src/room/data-stream/incoming/StreamReader.d.ts.map +1 -1
  32. package/dist/src/room/data-track/depacketizer.d.ts +51 -0
  33. package/dist/src/room/data-track/depacketizer.d.ts.map +1 -0
  34. package/dist/src/room/data-track/e2ee.d.ts +12 -0
  35. package/dist/src/room/data-track/e2ee.d.ts.map +1 -0
  36. package/dist/src/room/data-track/frame.d.ts +7 -0
  37. package/dist/src/room/data-track/frame.d.ts.map +1 -0
  38. package/dist/src/room/data-track/handle.d.ts +6 -7
  39. package/dist/src/room/data-track/handle.d.ts.map +1 -1
  40. package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +76 -0
  41. package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts.map +1 -0
  42. package/dist/src/room/data-track/outgoing/errors.d.ts +64 -0
  43. package/dist/src/room/data-track/outgoing/errors.d.ts.map +1 -0
  44. package/dist/src/room/data-track/outgoing/pipeline.d.ts +22 -0
  45. package/dist/src/room/data-track/outgoing/pipeline.d.ts.map +1 -0
  46. package/dist/src/room/data-track/outgoing/types.d.ts +31 -0
  47. package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -0
  48. package/dist/src/room/data-track/packet/index.d.ts +3 -3
  49. package/dist/src/room/data-track/packet/index.d.ts.map +1 -1
  50. package/dist/src/room/data-track/packetizer.d.ts +43 -0
  51. package/dist/src/room/data-track/packetizer.d.ts.map +1 -0
  52. package/dist/src/room/data-track/track.d.ts +30 -0
  53. package/dist/src/room/data-track/track.d.ts.map +1 -0
  54. package/dist/src/room/data-track/utils.d.ts +34 -2
  55. package/dist/src/room/data-track/utils.d.ts.map +1 -1
  56. package/dist/src/room/debounce.d.ts +11 -0
  57. package/dist/src/room/debounce.d.ts.map +1 -0
  58. package/dist/src/room/events.d.ts +1 -1
  59. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  60. package/dist/src/room/track/LocalAudioTrack.d.ts +1 -1
  61. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  62. package/dist/src/room/track/LocalTrack.d.ts +2 -1
  63. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  64. package/dist/src/room/types.d.ts +0 -2
  65. package/dist/src/room/types.d.ts.map +1 -1
  66. package/dist/src/room/utils.d.ts +6 -1
  67. package/dist/src/room/utils.d.ts.map +1 -1
  68. package/dist/src/utils/subscribeToEvents.d.ts +12 -0
  69. package/dist/src/utils/subscribeToEvents.d.ts.map +1 -0
  70. package/dist/src/utils/throws.d.ts +4 -2
  71. package/dist/src/utils/throws.d.ts.map +1 -1
  72. package/dist/ts4.2/e2ee/E2eeManager.d.ts +2 -0
  73. package/dist/ts4.2/e2ee/KeyProvider.d.ts +2 -0
  74. package/dist/ts4.2/e2ee/events.d.ts +1 -1
  75. package/dist/ts4.2/e2ee/types.d.ts +1 -0
  76. package/dist/ts4.2/e2ee/worker/ParticipantKeyHandler.d.ts +2 -2
  77. package/dist/ts4.2/index.d.ts +7 -3
  78. package/dist/ts4.2/logger.d.ts +2 -1
  79. package/dist/ts4.2/room/PCTransport.d.ts +1 -6
  80. package/dist/ts4.2/room/data-stream/incoming/StreamReader.d.ts +2 -4
  81. package/dist/ts4.2/room/data-track/depacketizer.d.ts +51 -0
  82. package/dist/ts4.2/room/data-track/e2ee.d.ts +12 -0
  83. package/dist/ts4.2/room/data-track/frame.d.ts +7 -0
  84. package/dist/ts4.2/room/data-track/handle.d.ts +6 -7
  85. package/dist/ts4.2/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +77 -0
  86. package/dist/ts4.2/room/data-track/outgoing/errors.d.ts +64 -0
  87. package/dist/ts4.2/room/data-track/outgoing/pipeline.d.ts +22 -0
  88. package/dist/ts4.2/room/data-track/outgoing/types.d.ts +31 -0
  89. package/dist/ts4.2/room/data-track/packet/index.d.ts +3 -3
  90. package/dist/ts4.2/room/data-track/packetizer.d.ts +43 -0
  91. package/dist/ts4.2/room/data-track/track.d.ts +30 -0
  92. package/dist/ts4.2/room/data-track/utils.d.ts +34 -2
  93. package/dist/ts4.2/room/debounce.d.ts +11 -0
  94. package/dist/ts4.2/room/events.d.ts +1 -1
  95. package/dist/ts4.2/room/track/LocalAudioTrack.d.ts +1 -1
  96. package/dist/ts4.2/room/track/LocalTrack.d.ts +2 -1
  97. package/dist/ts4.2/room/types.d.ts +0 -2
  98. package/dist/ts4.2/room/utils.d.ts +6 -1
  99. package/dist/ts4.2/utils/subscribeToEvents.d.ts +12 -0
  100. package/dist/ts4.2/utils/throws.d.ts +4 -2
  101. package/package.json +4 -5
  102. package/src/e2ee/E2eeManager.ts +9 -5
  103. package/src/e2ee/KeyProvider.ts +10 -1
  104. package/src/e2ee/events.ts +1 -1
  105. package/src/e2ee/types.ts +1 -0
  106. package/src/e2ee/worker/ParticipantKeyHandler.ts +7 -4
  107. package/src/e2ee/worker/e2ee.worker.ts +20 -10
  108. package/src/index.ts +15 -5
  109. package/src/logger.ts +1 -0
  110. package/src/room/PCTransport.ts +2 -1
  111. package/src/room/PCTransportManager.ts +27 -9
  112. package/src/room/RTCEngine.ts +13 -2
  113. package/src/room/Room.ts +11 -5
  114. package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +5 -25
  115. package/src/room/data-stream/incoming/StreamReader.ts +56 -73
  116. package/src/room/data-track/depacketizer.test.ts +442 -0
  117. package/src/room/data-track/depacketizer.ts +298 -0
  118. package/src/room/data-track/e2ee.ts +14 -0
  119. package/src/room/data-track/frame.ts +8 -0
  120. package/src/room/data-track/handle.test.ts +1 -1
  121. package/src/room/data-track/handle.ts +9 -14
  122. package/src/room/data-track/outgoing/OutgoingDataTrackManager.test.ts +392 -0
  123. package/src/room/data-track/outgoing/OutgoingDataTrackManager.ts +302 -0
  124. package/src/room/data-track/outgoing/errors.ts +157 -0
  125. package/src/room/data-track/outgoing/pipeline.ts +76 -0
  126. package/src/room/data-track/outgoing/types.ts +37 -0
  127. package/src/room/data-track/packet/index.test.ts +9 -9
  128. package/src/room/data-track/packet/index.ts +11 -9
  129. package/src/room/data-track/packet/serializable.ts +1 -1
  130. package/src/room/data-track/packetizer.test.ts +131 -0
  131. package/src/room/data-track/packetizer.ts +132 -0
  132. package/src/room/data-track/track.ts +50 -0
  133. package/src/room/data-track/utils.test.ts +27 -1
  134. package/src/room/data-track/utils.ts +125 -5
  135. package/src/room/debounce.ts +115 -0
  136. package/src/room/events.ts +1 -1
  137. package/src/room/participant/LocalParticipant.ts +2 -0
  138. package/src/room/track/LocalAudioTrack.ts +10 -10
  139. package/src/room/track/LocalTrack.ts +14 -5
  140. package/src/room/track/LocalVideoTrack.ts +1 -1
  141. package/src/room/track/RemoteVideoTrack.ts +1 -1
  142. package/src/room/types.ts +0 -2
  143. package/src/room/utils.ts +7 -2
  144. package/src/utils/subscribeToEvents.ts +63 -0
  145. package/src/utils/throws.ts +3 -1
@@ -8073,6 +8073,7 @@ var LoggerNames;
8073
8073
  LoggerNames["PCManager"] = "livekit-pc-manager";
8074
8074
  LoggerNames["PCTransport"] = "livekit-pc-transport";
8075
8075
  LoggerNames["E2EE"] = "lk-e2ee";
8076
+ LoggerNames["DataTracks"] = "livekit-data-tracks";
8076
8077
  })(LoggerNames || (LoggerNames = {}));
8077
8078
  let livekitLogger = loglevelExports.getLogger('livekit');
8078
8079
  const livekitLoggers = Object.values(LoggerNames).map(name => loglevelExports.getLogger(name));
@@ -11605,7 +11606,7 @@ function getMatch(exp, ua) {
11605
11606
  }
11606
11607
  function getOSVersion(ua) {
11607
11608
  return ua.includes('mac os') ? getMatch(/\(.+?(\d+_\d+(:?_\d+)?)/, ua, 1).replace(/_/g, '.') : undefined;
11608
- }var version$1 = "2.17.1";const version = version$1;
11609
+ }var version$1 = "2.17.3";const version = version$1;
11609
11610
  const protocolVersion = 16;/** Base error that all LiveKit specific custom errors inherit from. */
11610
11611
  class LivekitError extends Error {
11611
11612
  constructor(code, message, options) {
@@ -11834,7 +11835,7 @@ var RoomEvent;
11834
11835
  RoomEvent["Reconnected"] = "reconnected";
11835
11836
  /**
11836
11837
  * When disconnected from room. This fires when room.disconnect() is called or
11837
- * when an unrecoverable connection issue had occured.
11838
+ * when an unrecoverable connection issue had occurred.
11838
11839
  *
11839
11840
  * DisconnectReason can be used to determine why the participant was disconnected. Notable reasons are
11840
11841
  * - DUPLICATE_IDENTITY: another client with the same identity has joined the room
@@ -13567,6 +13568,10 @@ function getEmptyAudioStreamTrack() {
13567
13568
  }
13568
13569
  return emptyAudioStreamTrack.clone();
13569
13570
  }
13571
+ /** An object that represents a serialized version of a `new Promise((resolve, reject) => {})`
13572
+ * constructor. Wait for a promise resolution with `await future.promise` and explicitly resolve or
13573
+ * reject the inner promise with `future.resolve(...)` or `future.reject(...)`.
13574
+ */
13570
13575
  class Future {
13571
13576
  get isResolved() {
13572
13577
  return this._isResolved;
@@ -14051,6 +14056,7 @@ class BaseKeyProvider extends eventsExports.EventEmitter {
14051
14056
  constructor() {
14052
14057
  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
14053
14058
  super();
14059
+ this.latestManuallySetKeyIndex = 0;
14054
14060
  /**
14055
14061
  * Callback being invoked after a key has been ratcheted.
14056
14062
  * Can happen when:
@@ -14087,11 +14093,17 @@ class BaseKeyProvider extends eventsExports.EventEmitter {
14087
14093
  throw new Error('participant identity needs to be passed for encryption key if sharedKey option is false');
14088
14094
  }
14089
14095
  this.keyInfoMap.set("".concat(participantIdentity !== null && participantIdentity !== void 0 ? participantIdentity : 'shared', "-").concat(keyIndex !== null && keyIndex !== void 0 ? keyIndex : 0), keyInfo);
14090
- this.emit(KeyProviderEvent.SetKey, keyInfo);
14096
+ if (keyIndex !== undefined) {
14097
+ this.latestManuallySetKeyIndex = keyIndex;
14098
+ }
14099
+ this.emit(KeyProviderEvent.SetKey, keyInfo, keyIndex !== undefined);
14091
14100
  }
14092
14101
  getKeys() {
14093
14102
  return Array.from(this.keyInfoMap.values());
14094
14103
  }
14104
+ getLatestManuallySetKeyIndex() {
14105
+ return this.latestManuallySetKeyIndex;
14106
+ }
14095
14107
  getOptions() {
14096
14108
  return this.options;
14097
14109
  }
@@ -14182,14 +14194,14 @@ class E2EEManager extends eventsExports.EventEmitter {
14182
14194
  case 'initAck':
14183
14195
  if (data.enabled) {
14184
14196
  this.keyProvider.getKeys().forEach(keyInfo => {
14185
- this.postKey(keyInfo);
14197
+ this.postKey(keyInfo, false);
14186
14198
  });
14187
14199
  }
14188
14200
  break;
14189
14201
  case 'enable':
14190
14202
  if (data.enabled) {
14191
14203
  this.keyProvider.getKeys().forEach(keyInfo => {
14192
- this.postKey(keyInfo);
14204
+ this.postKey(keyInfo, false);
14193
14205
  });
14194
14206
  }
14195
14207
  if (this.encryptionEnabled !== data.enabled && data.participantIdentity === ((_a = this.room) === null || _a === void 0 ? void 0 : _a.localParticipant.identity)) {
@@ -14314,8 +14326,10 @@ class E2EEManager extends eventsExports.EventEmitter {
14314
14326
  if (!this.room) {
14315
14327
  throw new TypeError("expected room to be present on signal connect");
14316
14328
  }
14329
+ const latestKeyIndex = keyProvider.getLatestManuallySetKeyIndex();
14317
14330
  keyProvider.getKeys().forEach(keyInfo => {
14318
- this.postKey(keyInfo);
14331
+ var _a;
14332
+ this.postKey(keyInfo, latestKeyIndex === ((_a = keyInfo.keyIndex) !== null && _a !== void 0 ? _a : 0));
14319
14333
  });
14320
14334
  this.setParticipantCryptorEnabled(this.room.localParticipant.isE2EEEnabled, this.room.localParticipant.identity);
14321
14335
  });
@@ -14337,7 +14351,7 @@ class E2EEManager extends eventsExports.EventEmitter {
14337
14351
  };
14338
14352
  this.worker.postMessage(msg);
14339
14353
  });
14340
- keyProvider.on(KeyProviderEvent.SetKey, keyInfo => this.postKey(keyInfo)).on(KeyProviderEvent.RatchetRequest, (participantId, keyIndex) => this.postRatchetRequest(participantId, keyIndex));
14354
+ keyProvider.on(KeyProviderEvent.SetKey, (keyInfo, updateCurrentKeyIndex) => this.postKey(keyInfo, updateCurrentKeyIndex !== null && updateCurrentKeyIndex !== void 0 ? updateCurrentKeyIndex : true)).on(KeyProviderEvent.RatchetRequest, (participantId, keyIndex) => this.postRatchetRequest(participantId, keyIndex));
14341
14355
  }
14342
14356
  encryptData(data) {
14343
14357
  return __awaiter(this, void 0, void 0, function* () {
@@ -14398,7 +14412,7 @@ class E2EEManager extends eventsExports.EventEmitter {
14398
14412
  };
14399
14413
  this.worker.postMessage(msg);
14400
14414
  }
14401
- postKey(_ref) {
14415
+ postKey(_ref, updateCurrentKeyIndex) {
14402
14416
  let {
14403
14417
  key,
14404
14418
  participantIdentity,
@@ -14414,7 +14428,8 @@ class E2EEManager extends eventsExports.EventEmitter {
14414
14428
  participantIdentity: participantIdentity,
14415
14429
  isPublisher: participantIdentity === ((_a = this.room) === null || _a === void 0 ? void 0 : _a.localParticipant.identity),
14416
14430
  key,
14417
- keyIndex
14431
+ keyIndex,
14432
+ updateCurrentKeyIndex
14418
14433
  }
14419
14434
  };
14420
14435
  this.worker.postMessage(msg);
@@ -16530,48 +16545,106 @@ function requireLib() {
16530
16545
  lib.parseImageAttributes = parser.parseImageAttributes;
16531
16546
  lib.parseSimulcastStreamList = parser.parseSimulcastStreamList;
16532
16547
  return lib;
16533
- }var libExports = requireLib();function r(r, e, n) {
16534
- var i, t, o;
16535
- void 0 === e && (e = 50), void 0 === n && (n = {});
16536
- var a = null != (i = n.isImmediate) && i,
16537
- u = null != (t = n.callback) && t,
16538
- c = n.maxWait,
16539
- v = Date.now(),
16540
- l = [];
16541
- function f() {
16542
- if (void 0 !== c) {
16543
- var r = Date.now() - v;
16544
- if (r + e >= c) return c - r;
16545
- }
16546
- return e;
16547
- }
16548
- var d = function () {
16549
- var e = [].slice.call(arguments),
16550
- n = this;
16551
- return new Promise(function (i, t) {
16552
- var c = a && void 0 === o;
16553
- if (void 0 !== o && clearTimeout(o), o = setTimeout(function () {
16554
- if (o = void 0, v = Date.now(), !a) {
16555
- var i = r.apply(n, e);
16556
- u && u(i), l.forEach(function (r) {
16557
- return (0, r.resolve)(i);
16558
- }), l = [];
16559
- }
16560
- }, f()), c) {
16561
- var d = r.apply(n, e);
16562
- return u && u(d), i(d);
16563
- }
16564
- l.push({
16565
- resolve: i,
16566
- reject: t
16548
+ }var libExports = requireLib();/**
16549
+ * Originally from ts-debounce (https://github.com/chodorowicz/ts-debounce)
16550
+ * with the following license:
16551
+ *
16552
+ * MIT License
16553
+ *
16554
+ * Copyright (c) 2017 Jakub Chodorowicz
16555
+ *
16556
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
16557
+ * of this software and associated documentation files (the "Software"), to deal
16558
+ * in the Software without restriction, including without limitation the rights
16559
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16560
+ * copies of the Software, and to permit persons to whom the Software is
16561
+ * furnished to do so, subject to the following conditions:
16562
+ *
16563
+ * The above copyright notice and this permission notice shall be included in all
16564
+ * copies or substantial portions of the Software.
16565
+ *
16566
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16567
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16568
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16569
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16570
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16571
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
16572
+ * SOFTWARE.
16573
+ *
16574
+ * Modified to use CriticalTimers for reliable timer execution.
16575
+ */
16576
+ /* eslint-disable @typescript-eslint/no-this-alias, @typescript-eslint/no-unused-expressions, @typescript-eslint/no-shadow */
16577
+ function debounce(func) {
16578
+ let waitMilliseconds = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 50;
16579
+ let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
16580
+ var _a, _b;
16581
+ let timeoutId;
16582
+ const isImmediate = (_a = options.isImmediate) !== null && _a !== void 0 ? _a : false;
16583
+ const callback = (_b = options.callback) !== null && _b !== void 0 ? _b : false;
16584
+ const maxWait = options.maxWait;
16585
+ let lastInvokeTime = Date.now();
16586
+ let promises = [];
16587
+ function nextInvokeTimeout() {
16588
+ if (maxWait !== undefined) {
16589
+ const timeSinceLastInvocation = Date.now() - lastInvokeTime;
16590
+ if (timeSinceLastInvocation + waitMilliseconds >= maxWait) {
16591
+ return maxWait - timeSinceLastInvocation;
16592
+ }
16593
+ }
16594
+ return waitMilliseconds;
16595
+ }
16596
+ const debouncedFunction = function () {
16597
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
16598
+ args[_key] = arguments[_key];
16599
+ }
16600
+ const context = this;
16601
+ return new Promise((resolve, reject) => {
16602
+ const invokeFunction = function () {
16603
+ timeoutId = undefined;
16604
+ lastInvokeTime = Date.now();
16605
+ if (!isImmediate) {
16606
+ const result = func.apply(context, args);
16607
+ callback && callback(result);
16608
+ // biome-ignore lint/suspicious/useIterableCallbackReturn: vendored code
16609
+ promises.forEach(_ref => {
16610
+ let {
16611
+ resolve
16612
+ } = _ref;
16613
+ return resolve(result);
16614
+ });
16615
+ promises = [];
16616
+ }
16617
+ };
16618
+ const shouldCallNow = isImmediate && timeoutId === undefined;
16619
+ if (timeoutId !== undefined) {
16620
+ CriticalTimers.clearTimeout(timeoutId);
16621
+ }
16622
+ timeoutId = CriticalTimers.setTimeout(invokeFunction, nextInvokeTimeout());
16623
+ if (shouldCallNow) {
16624
+ const result = func.apply(context, args);
16625
+ callback && callback(result);
16626
+ return resolve(result);
16627
+ }
16628
+ promises.push({
16629
+ resolve,
16630
+ reject
16567
16631
  });
16568
16632
  });
16569
16633
  };
16570
- return d.cancel = function (r) {
16571
- void 0 !== o && clearTimeout(o), l.forEach(function (e) {
16572
- return (0, e.reject)(r);
16573
- }), l = [];
16574
- }, d;
16634
+ debouncedFunction.cancel = function (reason) {
16635
+ if (timeoutId !== undefined) {
16636
+ CriticalTimers.clearTimeout(timeoutId);
16637
+ }
16638
+ // biome-ignore lint/suspicious/useIterableCallbackReturn: vendored code
16639
+ promises.forEach(_ref2 => {
16640
+ let {
16641
+ reject
16642
+ } = _ref2;
16643
+ return reject(reason);
16644
+ });
16645
+ promises = [];
16646
+ };
16647
+ return debouncedFunction;
16575
16648
  }/* The svc codec (av1/vp9) would use a very low bitrate at the begining and
16576
16649
  increase slowly by the bandwidth estimator until it reach the target bitrate. The
16577
16650
  process commonly cost more than 10 seconds cause subscriber will get blur video at
@@ -16607,7 +16680,7 @@ class PCTransport extends eventsExports.EventEmitter {
16607
16680
  this.remoteStereoMids = [];
16608
16681
  this.remoteNackMids = [];
16609
16682
  // debounced negotiate interface
16610
- this.negotiate = r(onError => __awaiter(this, void 0, void 0, function* () {
16683
+ this.negotiate = debounce(onError => __awaiter(this, void 0, void 0, function* () {
16611
16684
  this.emit(PCEvents.NegotiationStarted);
16612
16685
  try {
16613
16686
  yield this.createAndSendOffer();
@@ -16800,6 +16873,7 @@ class PCTransport extends eventsExports.EventEmitter {
16800
16873
  yield this._pc.setRemoteDescription(currentSD);
16801
16874
  } else {
16802
16875
  this.renegotiate = true;
16876
+ this.log.debug('requesting renegotiation', Object.assign({}, this.logContext));
16803
16877
  return;
16804
16878
  }
16805
16879
  } else if (!this._pc || this._pc.signalingState === 'closed') {
@@ -17400,25 +17474,41 @@ class PCTransportManager {
17400
17474
  negotiate(abortController) {
17401
17475
  return __awaiter(this, void 0, void 0, function* () {
17402
17476
  return new TypedPromise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
17403
- const negotiationTimeout = setTimeout(() => {
17477
+ let negotiationTimeout = setTimeout(() => {
17404
17478
  reject(new NegotiationError('negotiation timed out'));
17405
17479
  }, this.peerConnectionTimeout);
17406
- const abortHandler = () => {
17480
+ const cleanup = () => {
17407
17481
  clearTimeout(negotiationTimeout);
17482
+ this.publisher.off(PCEvents.NegotiationStarted, onNegotiationStarted);
17483
+ abortController.signal.removeEventListener('abort', abortHandler);
17484
+ };
17485
+ const abortHandler = () => {
17486
+ cleanup();
17408
17487
  reject(new NegotiationError('negotiation aborted'));
17409
17488
  };
17410
- abortController.signal.addEventListener('abort', abortHandler);
17411
- this.publisher.once(PCEvents.NegotiationStarted, () => {
17489
+ // Reset the timeout each time a renegotiation cycle starts. This
17490
+ // prevents premature timeouts when the negotiation machinery is
17491
+ // actively renegotiating (offers going out, answers coming back) but
17492
+ // NegotiationComplete hasn't fired yet because new requirements keep
17493
+ // arriving between offer/answer round-trips.
17494
+ const onNegotiationStarted = () => {
17412
17495
  if (abortController.signal.aborted) {
17413
17496
  return;
17414
17497
  }
17415
- this.publisher.once(PCEvents.NegotiationComplete, () => {
17416
- clearTimeout(negotiationTimeout);
17417
- resolve();
17418
- });
17498
+ clearTimeout(negotiationTimeout);
17499
+ negotiationTimeout = setTimeout(() => {
17500
+ cleanup();
17501
+ reject(new NegotiationError('negotiation timed out'));
17502
+ }, this.peerConnectionTimeout);
17503
+ };
17504
+ abortController.signal.addEventListener('abort', abortHandler);
17505
+ this.publisher.on(PCEvents.NegotiationStarted, onNegotiationStarted);
17506
+ this.publisher.once(PCEvents.NegotiationComplete, () => {
17507
+ cleanup();
17508
+ resolve();
17419
17509
  });
17420
17510
  yield this.publisher.negotiate(e => {
17421
- clearTimeout(negotiationTimeout);
17511
+ cleanup();
17422
17512
  if (e instanceof Error) {
17423
17513
  reject(e);
17424
17514
  } else {
@@ -17914,9 +18004,10 @@ class LocalTrack extends Track {
17914
18004
  let loggerOptions = arguments.length > 4 ? arguments[4] : undefined;
17915
18005
  super(mediaTrack, kind, loggerOptions);
17916
18006
  this.manuallyStopped = false;
18007
+ this.pendingDeviceChange = false;
17917
18008
  this._isUpstreamPaused = false;
17918
18009
  this.handleTrackMuteEvent = () => this.debouncedTrackMuteHandler().catch(() => this.log.debug('track mute bounce got cancelled by an unmute event', this.logContext));
17919
- this.debouncedTrackMuteHandler = r(() => __awaiter(this, void 0, void 0, function* () {
18010
+ this.debouncedTrackMuteHandler = debounce(() => __awaiter(this, void 0, void 0, function* () {
17920
18011
  yield this.pauseUpstream();
17921
18012
  }), 5000);
17922
18013
  this.handleTrackUnmuteEvent = () => __awaiter(this, void 0, void 0, function* () {
@@ -17988,7 +18079,7 @@ class LocalTrack extends Track {
17988
18079
  getSourceTrackSettings() {
17989
18080
  return this._mediaStreamTrack.getSettings();
17990
18081
  }
17991
- setMediaStreamTrack(newTrack, force) {
18082
+ setMediaStreamTrack(newTrack, force, isUnmuting) {
17992
18083
  return __awaiter(this, void 0, void 0, function* () {
17993
18084
  var _a;
17994
18085
  if (newTrack === this._mediaStreamTrack && !force) {
@@ -18045,7 +18136,8 @@ class LocalTrack extends Track {
18045
18136
  this._mediaStreamTrack = newTrack;
18046
18137
  if (newTrack) {
18047
18138
  // sync muted state with the enabled state of the newly provided track
18048
- this._mediaStreamTrack.enabled = !this.isMuted;
18139
+ // if restarting as part of an unmute, set enabled to true directly to avoid mute cycling
18140
+ this._mediaStreamTrack.enabled = isUnmuting ? true : !this.isMuted;
18049
18141
  // when a valid track is replace, we'd want to start producing
18050
18142
  yield this.resumeUpstream();
18051
18143
  this.attachedElements.forEach(el => {
@@ -18089,6 +18181,7 @@ class LocalTrack extends Track {
18089
18181
  // when track is muted, underlying media stream track is stopped and
18090
18182
  // will be restarted later
18091
18183
  if (this.isMuted) {
18184
+ this.pendingDeviceChange = true;
18092
18185
  return true;
18093
18186
  }
18094
18187
  yield this.restartTrack();
@@ -18157,7 +18250,7 @@ class LocalTrack extends Track {
18157
18250
  }
18158
18251
  });
18159
18252
  }
18160
- restart(constraints) {
18253
+ restart(constraints, isUnmuting) {
18161
18254
  return __awaiter(this, void 0, void 0, function* () {
18162
18255
  this.manuallyStopped = false;
18163
18256
  const unlock = yield this.trackChangeLock.lock();
@@ -18206,8 +18299,9 @@ class LocalTrack extends Track {
18206
18299
  }
18207
18300
  newTrack.addEventListener('ended', this.handleEnded);
18208
18301
  this.log.debug('re-acquired MediaStreamTrack', this.logContext);
18209
- yield this.setMediaStreamTrack(newTrack);
18302
+ yield this.setMediaStreamTrack(newTrack, false, isUnmuting);
18210
18303
  this._constraints = constraints;
18304
+ this.pendingDeviceChange = false;
18211
18305
  this.emit(TrackEvent.Restarted, this);
18212
18306
  if (this.manuallyStopped) {
18213
18307
  this.log.warn('track was stopped during a restart, stopping restarted track', this.logContext);
@@ -18586,10 +18680,9 @@ class LocalTrack extends Track {
18586
18680
  this.log.debug('Track already unmuted', this.logContext);
18587
18681
  return this;
18588
18682
  }
18589
- const deviceHasChanged = this._constraints.deviceId && this._mediaStreamTrack.getSettings().deviceId !== unwrapConstraint(this._constraints.deviceId);
18590
- if (this.source === Track.Source.Microphone && (this.stopOnMute || this._mediaStreamTrack.readyState === 'ended' || deviceHasChanged) && !this.isUserProvided) {
18683
+ if (this.source === Track.Source.Microphone && (this.stopOnMute || this._mediaStreamTrack.readyState === 'ended' || this.pendingDeviceChange) && !this.isUserProvided) {
18591
18684
  this.log.debug('reacquiring mic track', this.logContext);
18592
- yield this.restartTrack();
18685
+ yield this.restart(undefined, true);
18593
18686
  }
18594
18687
  yield _super.unmute.call(this);
18595
18688
  return this;
@@ -18612,14 +18705,14 @@ class LocalTrack extends Track {
18612
18705
  yield this.restart(constraints);
18613
18706
  });
18614
18707
  }
18615
- restart(constraints) {
18708
+ restart(constraints, isUnmuting) {
18616
18709
  const _super = Object.create(null, {
18617
18710
  restart: {
18618
18711
  get: () => super.restart
18619
18712
  }
18620
18713
  });
18621
18714
  return __awaiter(this, void 0, void 0, function* () {
18622
- const track = yield _super.restart.call(this, constraints);
18715
+ const track = yield _super.restart.call(this, constraints, isUnmuting);
18623
18716
  this.checkForSilence();
18624
18717
  return track;
18625
18718
  });
@@ -19252,7 +19345,7 @@ class LocalVideoTrack extends LocalTrack {
19252
19345
  }
19253
19346
  if (this.source === Track.Source.Camera && !this.isUserProvided) {
19254
19347
  this.log.debug('reacquiring camera track', this.logContext);
19255
- yield this.restartTrack();
19348
+ yield this.restart(undefined, true);
19256
19349
  }
19257
19350
  yield _super.unmute.call(this);
19258
19351
  return this;
@@ -20914,8 +21007,8 @@ class RTCEngine extends eventsExports.EventEmitter {
20914
21007
  if (!this.pcManager) {
20915
21008
  return false;
20916
21009
  }
20917
- // primary connection
20918
- if (this.pcManager.currentState !== PCTransportState.CONNECTED) {
21010
+ const allowedConnectionStates = [PCTransportState.CONNECTING, PCTransportState.CONNECTED];
21011
+ if (!allowedConnectionStates.includes(this.pcManager.currentState)) {
20919
21012
  return false;
20920
21013
  }
20921
21014
  // ensure signal is connected
@@ -20949,6 +21042,7 @@ class RTCEngine extends eventsExports.EventEmitter {
20949
21042
  reject(new NegotiationError('cannot negotiate on closed engine'));
20950
21043
  }
20951
21044
  this.on(EngineEvent.Closing, handleClosed);
21045
+ this.on(EngineEvent.Restarting, handleClosed);
20952
21046
  this.pcManager.publisher.once(PCEvents.RTPVideoPayloadTypes, rtpTypes => {
20953
21047
  const rtpMap = new Map();
20954
21048
  rtpTypes.forEach(rtp => {
@@ -20963,6 +21057,12 @@ class RTCEngine extends eventsExports.EventEmitter {
20963
21057
  yield this.pcManager.negotiate(abortController);
20964
21058
  resolve();
20965
21059
  } catch (e) {
21060
+ if (abortController.signal.aborted) {
21061
+ // negotiation was aborted due to engine close or restart, resolve
21062
+ // cleanly to avoid triggering a cascading reconnect loop
21063
+ resolve();
21064
+ return;
21065
+ }
20966
21066
  if (e instanceof NegotiationError) {
20967
21067
  this.fullReconnectOnNext = true;
20968
21068
  }
@@ -20974,6 +21074,7 @@ class RTCEngine extends eventsExports.EventEmitter {
20974
21074
  }
20975
21075
  } finally {
20976
21076
  this.off(EngineEvent.Closing, handleClosed);
21077
+ this.off(EngineEvent.Restarting, handleClosed);
20977
21078
  }
20978
21079
  }));
20979
21080
  });
@@ -21136,12 +21237,11 @@ function applyUserDataCompat(newObj, oldObj) {
21136
21237
  throw new DataStreamError("Extra chunk(s) received - expected ".concat(this.totalByteSize, " bytes of data total, received ").concat(this.bytesReceived, " bytes"), DataStreamErrorReason.LengthExceeded);
21137
21238
  }
21138
21239
  }
21139
- constructor(info, stream, totalByteSize, outOfBandFailureRejectingFuture) {
21240
+ constructor(info, stream, totalByteSize) {
21140
21241
  this.reader = stream;
21141
21242
  this.totalByteSize = totalByteSize;
21142
21243
  this._info = info;
21143
21244
  this.bytesReceived = 0;
21144
- this.outOfBandFailureRejectingFuture = outOfBandFailureRejectingFuture;
21145
21245
  }
21146
21246
  }
21147
21247
  class ByteStreamReader extends BaseStreamReader {
@@ -21154,50 +21254,44 @@ class ByteStreamReader extends BaseStreamReader {
21154
21254
  }
21155
21255
  [Symbol.asyncIterator]() {
21156
21256
  const reader = this.reader.getReader();
21157
- let rejectingSignalFuture = new Future();
21158
- let activeSignal = null;
21159
- let onAbort = null;
21160
- if (this.signal) {
21161
- const signal = this.signal;
21162
- onAbort = () => {
21163
- var _a;
21164
- (_a = rejectingSignalFuture.reject) === null || _a === void 0 ? void 0 : _a.call(rejectingSignalFuture, signal.reason);
21165
- };
21166
- signal.addEventListener('abort', onAbort);
21167
- activeSignal = signal;
21168
- }
21257
+ // Suppress unhandled rejection on reader.closed — errors are
21258
+ // already propagated through reader.read() to the consumer.
21259
+ reader.closed.catch(() => {});
21169
21260
  const cleanup = () => {
21170
21261
  reader.releaseLock();
21171
- if (activeSignal && onAbort) {
21172
- activeSignal.removeEventListener('abort', onAbort);
21173
- }
21174
21262
  this.signal = undefined;
21175
21263
  };
21176
21264
  return {
21177
21265
  next: () => __awaiter(this, void 0, void 0, function* () {
21178
- var _a, _b;
21179
21266
  try {
21180
- const {
21181
- done,
21182
- value
21183
- } = yield Promise.race([reader.read(),
21184
- // Rejects if this.signal is aborted
21185
- rejectingSignalFuture.promise,
21186
- // Rejects if something external says it should, like a participant disconnecting, etc
21187
- (_b = (_a = this.outOfBandFailureRejectingFuture) === null || _a === void 0 ? void 0 : _a.promise) !== null && _b !== void 0 ? _b : new Promise(() => {
21188
- /* never resolves */
21189
- })]);
21190
- if (done) {
21267
+ const signal = this.signal;
21268
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
21269
+ throw signal.reason;
21270
+ }
21271
+ const result = yield new Promise((resolve, reject) => {
21272
+ if (signal) {
21273
+ const onAbort = () => reject(signal.reason);
21274
+ signal.addEventListener('abort', onAbort, {
21275
+ once: true
21276
+ });
21277
+ reader.read().then(resolve, reject).finally(() => {
21278
+ signal.removeEventListener('abort', onAbort);
21279
+ });
21280
+ } else {
21281
+ reader.read().then(resolve, reject);
21282
+ }
21283
+ });
21284
+ if (result.done) {
21191
21285
  this.validateBytesReceived(true);
21192
21286
  return {
21193
21287
  done: true,
21194
21288
  value: undefined
21195
21289
  };
21196
21290
  } else {
21197
- this.handleChunkReceived(value);
21291
+ this.handleChunkReceived(result.value);
21198
21292
  return {
21199
21293
  done: false,
21200
- value: value.content
21294
+ value: result.value.content
21201
21295
  };
21202
21296
  }
21203
21297
  } catch (err) {
@@ -21268,8 +21362,8 @@ class TextStreamReader extends BaseStreamReader {
21268
21362
  * A TextStreamReader instance can be used as an AsyncIterator that returns the entire string
21269
21363
  * that has been received up to the current point in time.
21270
21364
  */
21271
- constructor(info, stream, totalChunkCount, outOfBandFailureRejectingFuture) {
21272
- super(info, stream, totalChunkCount, outOfBandFailureRejectingFuture);
21365
+ constructor(info, stream, totalChunkCount) {
21366
+ super(info, stream, totalChunkCount);
21273
21367
  this.receivedChunks = new Map();
21274
21368
  }
21275
21369
  handleChunkReceived(chunk) {
@@ -21293,55 +21387,49 @@ class TextStreamReader extends BaseStreamReader {
21293
21387
  */
21294
21388
  [Symbol.asyncIterator]() {
21295
21389
  const reader = this.reader.getReader();
21390
+ // Suppress unhandled rejection on reader.closed — errors are
21391
+ // already propagated through reader.read() to the consumer.
21392
+ reader.closed.catch(() => {});
21296
21393
  const decoder = new TextDecoder('utf-8', {
21297
21394
  fatal: true
21298
21395
  });
21299
- let rejectingSignalFuture = new Future();
21300
- let activeSignal = null;
21301
- let onAbort = null;
21302
- if (this.signal) {
21303
- const signal = this.signal;
21304
- onAbort = () => {
21305
- var _a;
21306
- (_a = rejectingSignalFuture.reject) === null || _a === void 0 ? void 0 : _a.call(rejectingSignalFuture, signal.reason);
21307
- };
21308
- signal.addEventListener('abort', onAbort);
21309
- activeSignal = signal;
21310
- }
21396
+ const signal = this.signal;
21311
21397
  const cleanup = () => {
21312
21398
  reader.releaseLock();
21313
- if (activeSignal && onAbort) {
21314
- activeSignal.removeEventListener('abort', onAbort);
21315
- }
21316
21399
  this.signal = undefined;
21317
21400
  };
21318
21401
  return {
21319
21402
  next: () => __awaiter(this, void 0, void 0, function* () {
21320
- var _a, _b;
21321
21403
  try {
21322
- const {
21323
- done,
21324
- value
21325
- } = yield Promise.race([reader.read(),
21326
- // Rejects if this.signal is aborted
21327
- rejectingSignalFuture.promise,
21328
- // Rejects if something external says it should, like a participant disconnecting, etc
21329
- (_b = (_a = this.outOfBandFailureRejectingFuture) === null || _a === void 0 ? void 0 : _a.promise) !== null && _b !== void 0 ? _b : new Promise(() => {
21330
- /* never resolves */
21331
- })]);
21332
- if (done) {
21404
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
21405
+ throw signal.reason;
21406
+ }
21407
+ const result = yield new Promise((resolve, reject) => {
21408
+ if (signal) {
21409
+ const onAbort = () => reject(signal.reason);
21410
+ signal.addEventListener('abort', onAbort, {
21411
+ once: true
21412
+ });
21413
+ reader.read().then(resolve, reject).finally(() => {
21414
+ signal.removeEventListener('abort', onAbort);
21415
+ });
21416
+ } else {
21417
+ reader.read().then(resolve, reject);
21418
+ }
21419
+ });
21420
+ if (result.done) {
21333
21421
  this.validateBytesReceived(true);
21334
21422
  return {
21335
21423
  done: true,
21336
21424
  value: undefined
21337
21425
  };
21338
21426
  } else {
21339
- this.handleChunkReceived(value);
21427
+ this.handleChunkReceived(result.value);
21340
21428
  let decodedResult;
21341
21429
  try {
21342
- decodedResult = decoder.decode(value.content);
21430
+ decodedResult = decoder.decode(result.value.content);
21343
21431
  } catch (err) {
21344
- throw new DataStreamError("Cannot decode datastream chunk ".concat(value.chunkIndex, " as text: ").concat(err), DataStreamErrorReason.DecodeFailed);
21432
+ throw new DataStreamError("Cannot decode datastream chunk ".concat(result.value.chunkIndex, " as text: ").concat(err), DataStreamErrorReason.DecodeFailed);
21345
21433
  }
21346
21434
  return {
21347
21435
  done: false,
@@ -21438,18 +21526,17 @@ class TextStreamReader extends BaseStreamReader {
21438
21526
  this.textStreamControllers.clear();
21439
21527
  }
21440
21528
  validateParticipantHasNoActiveDataStreams(participantIdentity) {
21441
- var _a, _b, _c, _d;
21442
21529
  // Terminate any in flight data stream receives from the given participant
21443
21530
  const textStreamsBeingSentByDisconnectingParticipant = Array.from(this.textStreamControllers.entries()).filter(entry => entry[1].sendingParticipantIdentity === participantIdentity);
21444
21531
  const byteStreamsBeingSentByDisconnectingParticipant = Array.from(this.byteStreamControllers.entries()).filter(entry => entry[1].sendingParticipantIdentity === participantIdentity);
21445
21532
  if (textStreamsBeingSentByDisconnectingParticipant.length > 0 || byteStreamsBeingSentByDisconnectingParticipant.length > 0) {
21446
21533
  const abnormalEndError = new DataStreamError("Participant ".concat(participantIdentity, " unexpectedly disconnected in the middle of sending data"), DataStreamErrorReason.AbnormalEnd);
21447
21534
  for (const [id, controller] of byteStreamsBeingSentByDisconnectingParticipant) {
21448
- (_b = (_a = controller.outOfBandFailureRejectingFuture).reject) === null || _b === void 0 ? void 0 : _b.call(_a, abnormalEndError);
21535
+ controller.controller.error(abnormalEndError);
21449
21536
  this.byteStreamControllers.delete(id);
21450
21537
  }
21451
21538
  for (const [id, controller] of textStreamsBeingSentByDisconnectingParticipant) {
21452
- (_d = (_c = controller.outOfBandFailureRejectingFuture).reject) === null || _d === void 0 ? void 0 : _d.call(_c, abnormalEndError);
21539
+ controller.controller.error(abnormalEndError);
21453
21540
  this.textStreamControllers.delete(id);
21454
21541
  }
21455
21542
  }
@@ -21478,10 +21565,6 @@ class TextStreamReader extends BaseStreamReader {
21478
21565
  return;
21479
21566
  }
21480
21567
  let streamController;
21481
- const outOfBandFailureRejectingFuture = new Future();
21482
- outOfBandFailureRejectingFuture.promise.catch(err => {
21483
- this.log.error(err);
21484
- });
21485
21568
  const info = {
21486
21569
  id: streamHeader.streamId,
21487
21570
  name: (_a = streamHeader.contentHeader.value.name) !== null && _a !== void 0 ? _a : 'unknown',
@@ -21502,12 +21585,11 @@ class TextStreamReader extends BaseStreamReader {
21502
21585
  info,
21503
21586
  controller: streamController,
21504
21587
  startTime: Date.now(),
21505
- sendingParticipantIdentity: participantIdentity,
21506
- outOfBandFailureRejectingFuture
21588
+ sendingParticipantIdentity: participantIdentity
21507
21589
  });
21508
21590
  }
21509
21591
  });
21510
- streamHandlerCallback(new ByteStreamReader(info, stream, bigIntToNumber(streamHeader.totalLength), outOfBandFailureRejectingFuture), {
21592
+ streamHandlerCallback(new ByteStreamReader(info, stream, bigIntToNumber(streamHeader.totalLength)), {
21511
21593
  identity: participantIdentity
21512
21594
  });
21513
21595
  } else if (streamHeader.contentHeader.case === 'textHeader') {
@@ -21517,10 +21599,6 @@ class TextStreamReader extends BaseStreamReader {
21517
21599
  return;
21518
21600
  }
21519
21601
  let streamController;
21520
- const outOfBandFailureRejectingFuture = new Future();
21521
- outOfBandFailureRejectingFuture.promise.catch(err => {
21522
- this.log.error(err);
21523
- });
21524
21602
  const info = {
21525
21603
  id: streamHeader.streamId,
21526
21604
  mimeType: streamHeader.mimeType,
@@ -21541,12 +21619,11 @@ class TextStreamReader extends BaseStreamReader {
21541
21619
  info,
21542
21620
  controller: streamController,
21543
21621
  startTime: Date.now(),
21544
- sendingParticipantIdentity: participantIdentity,
21545
- outOfBandFailureRejectingFuture
21622
+ sendingParticipantIdentity: participantIdentity
21546
21623
  });
21547
21624
  }
21548
21625
  });
21549
- streamHandlerCallback(new TextStreamReader(info, stream, bigIntToNumber(streamHeader.totalLength), outOfBandFailureRejectingFuture), {
21626
+ streamHandlerCallback(new TextStreamReader(info, stream, bigIntToNumber(streamHeader.totalLength)), {
21550
21627
  identity: participantIdentity
21551
21628
  });
21552
21629
  }
@@ -22226,7 +22303,7 @@ class RemoteVideoTrack extends RemoteTrack {
22226
22303
  }
22227
22304
  this.prevStats = stats;
22228
22305
  });
22229
- this.debouncedHandleResize = r(() => {
22306
+ this.debouncedHandleResize = debounce(() => {
22230
22307
  this.updateDimensions();
22231
22308
  }, REACTION_DELAY);
22232
22309
  this.adaptiveStreamSettings = adaptiveStreamSettings;
@@ -23245,6 +23322,8 @@ class Participant extends eventsExports.EventEmitter {
23245
23322
  this.handleClosing = () => {
23246
23323
  var _a, _b, _c, _d, _e, _f;
23247
23324
  if (this.reconnectFuture) {
23325
+ // @throws-transformer ignore - introduced due to adding Throws into Future, investigate this
23326
+ // further
23248
23327
  this.reconnectFuture.promise.catch(e => this.log.warn(e.message, this.logContext));
23249
23328
  (_b = (_a = this.reconnectFuture) === null || _a === void 0 ? void 0 : _a.reject) === null || _b === void 0 ? void 0 : _b.call(_a, new Error('Got disconnected during reconnection attempt'));
23250
23329
  this.reconnectFuture = undefined;
@@ -25602,7 +25681,7 @@ class Room extends eventsExports.EventEmitter {
25602
25681
  }
25603
25682
  if (nextUrl && !((_b = this.abortController) === null || _b === void 0 ? void 0 : _b.signal.aborted)) {
25604
25683
  this.log.info("Initial connection failed with ConnectionError: ".concat(error.message, ". Retrying with another region: ").concat(nextUrl), this.logContext);
25605
- this.recreateEngine();
25684
+ this.recreateEngine(true);
25606
25685
  yield connectFn(resolve, reject, nextUrl);
25607
25686
  } else {
25608
25687
  this.handleDisconnect(this.options.stopLocalTrackOnUnpublish, getDisconnectReasonFromConnectionError(error));
@@ -25687,7 +25766,7 @@ class Room extends eventsExports.EventEmitter {
25687
25766
  if (this.state === ConnectionState.Reconnecting || this.isResuming || ((_a = this.engine) === null || _a === void 0 ? void 0 : _a.pendingReconnect)) {
25688
25767
  this.log.info('Reconnection attempt replaced by new connection attempt', this.logContext);
25689
25768
  // make sure we close and recreate the existing engine in order to get rid of any potentially ongoing reconnection attempts
25690
- this.recreateEngine();
25769
+ this.recreateEngine(true);
25691
25770
  } else {
25692
25771
  // create engine if previously disconnected
25693
25772
  this.maybeCreateEngine();
@@ -26800,9 +26879,13 @@ class Room extends eventsExports.EventEmitter {
26800
26879
  setupLocalParticipantEvents() {
26801
26880
  this.localParticipant.on(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged).on(ParticipantEvent.ParticipantNameChanged, this.onLocalParticipantNameChanged).on(ParticipantEvent.AttributesChanged, this.onLocalAttributesChanged).on(ParticipantEvent.TrackMuted, this.onLocalTrackMuted).on(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted).on(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished).on(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished).on(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged).on(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError).on(ParticipantEvent.AudioStreamAcquired, this.startAudio).on(ParticipantEvent.ChatMessage, this.onLocalChatMessageSent).on(ParticipantEvent.ParticipantPermissionsChanged, this.onLocalParticipantPermissionsChanged);
26802
26881
  }
26803
- recreateEngine() {
26804
- var _a;
26805
- (_a = this.engine) === null || _a === void 0 ? void 0 : _a.close();
26882
+ recreateEngine(sendLeave) {
26883
+ const oldEngine = this.engine;
26884
+ if (sendLeave && oldEngine && !oldEngine.client.isDisconnected) {
26885
+ oldEngine.client.sendLeave().finally(() => oldEngine.close());
26886
+ } else {
26887
+ oldEngine === null || oldEngine === void 0 ? void 0 : oldEngine.close();
26888
+ }
26806
26889
  /* @ts-ignore */
26807
26890
  this.engine = undefined;
26808
26891
  this.isResuming = false;
@@ -27223,7 +27306,7 @@ class Room extends eventsExports.EventEmitter {
27223
27306
  numFailures: consecutiveFailures,
27224
27307
  engine: this.engine ? {
27225
27308
  closed: this.engine.isClosed,
27226
- transportsConnected: this.engine.verifyTransport()
27309
+ transportsConnectedOrConnecting: this.engine.verifyTransport()
27227
27310
  } : undefined
27228
27311
  }));
27229
27312
  if (consecutiveFailures >= 3) {
@@ -27441,1946 +27524,2030 @@ class Convert {
27441
27524
  static transcriptionAttributesToJson(value) {
27442
27525
  return JSON.stringify(value);
27443
27526
  }
27444
- }var attributeTypings=/*#__PURE__*/Object.freeze({__proto__:null,Convert:Convert});var CheckStatus;
27445
- (function (CheckStatus) {
27446
- CheckStatus[CheckStatus["IDLE"] = 0] = "IDLE";
27447
- CheckStatus[CheckStatus["RUNNING"] = 1] = "RUNNING";
27448
- CheckStatus[CheckStatus["SKIPPED"] = 2] = "SKIPPED";
27449
- CheckStatus[CheckStatus["SUCCESS"] = 3] = "SUCCESS";
27450
- CheckStatus[CheckStatus["FAILED"] = 4] = "FAILED";
27451
- })(CheckStatus || (CheckStatus = {}));
27452
- class Checker extends eventsExports.EventEmitter {
27453
- constructor(url, token) {
27454
- let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
27455
- super();
27456
- this.status = CheckStatus.IDLE;
27457
- this.logs = [];
27458
- this.options = {};
27459
- this.url = url;
27460
- this.token = token;
27461
- this.name = this.constructor.name;
27462
- this.room = new Room(options.roomOptions);
27463
- this.connectOptions = options.connectOptions;
27464
- this.options = options;
27527
+ }var attributeTypings=/*#__PURE__*/Object.freeze({__proto__:null,Convert:Convert});const U16_MAX_SIZE = 0xffff;
27528
+ const U32_MAX_SIZE = 0xffffffff;
27529
+ /**
27530
+ * A number of fields withing the data tracks packet specification assume wrap around behavior when
27531
+ * an unsigned type is incremented beyond its max size (ie, the packet `sequence` field). This
27532
+ * wrapper type manually reimplements this wrap around behavior given javascript's lack of fixed
27533
+ * size integer types.
27534
+ */
27535
+ class WrapAroundUnsignedInt {
27536
+ static u16(raw) {
27537
+ return new WrapAroundUnsignedInt(raw, U16_MAX_SIZE);
27465
27538
  }
27466
- run(onComplete) {
27467
- return __awaiter(this, void 0, void 0, function* () {
27468
- if (this.status !== CheckStatus.IDLE) {
27469
- throw Error('check is running already');
27470
- }
27471
- this.setStatus(CheckStatus.RUNNING);
27472
- try {
27473
- yield this.perform();
27474
- } catch (err) {
27475
- if (err instanceof Error) {
27476
- if (this.options.errorsAsWarnings) {
27477
- this.appendWarning(err.message);
27478
- } else {
27479
- this.appendError(err.message);
27480
- }
27481
- }
27482
- }
27483
- yield this.disconnect();
27484
- // sleep for a bit to ensure disconnect
27485
- yield new Promise(resolve => setTimeout(resolve, 500));
27486
- // @ts-ignore
27487
- if (this.status !== CheckStatus.SKIPPED) {
27488
- this.setStatus(this.isSuccess() ? CheckStatus.SUCCESS : CheckStatus.FAILED);
27489
- }
27490
- if (onComplete) {
27491
- onComplete();
27492
- }
27493
- return this.getInfo();
27494
- });
27539
+ static u32(raw) {
27540
+ return new WrapAroundUnsignedInt(raw, U32_MAX_SIZE);
27495
27541
  }
27496
- isSuccess() {
27497
- return !this.logs.some(l => l.level === 'error');
27542
+ constructor(raw, maxSize) {
27543
+ this.value = raw;
27544
+ if (raw < 0) {
27545
+ throw new Error('WrapAroundUnsignedInt: cannot faithfully represent an integer smaller than 0');
27546
+ }
27547
+ if (maxSize > Number.MAX_SAFE_INTEGER) {
27548
+ throw new Error('WrapAroundUnsignedInt: cannot faithfully represent an integer bigger than MAX_SAFE_INTEGER.');
27549
+ }
27550
+ this.maxSize = maxSize;
27551
+ this.clamp();
27498
27552
  }
27499
- connect(url) {
27500
- return __awaiter(this, void 0, void 0, function* () {
27501
- if (this.room.state === ConnectionState.Connected) {
27502
- return this.room;
27503
- }
27504
- if (!url) {
27505
- url = this.url;
27506
- }
27507
- yield this.room.connect(url, this.token, this.connectOptions);
27508
- return this.room;
27509
- });
27553
+ /** Manually clamp the given containing value according to the wrap around max size bounds. Use
27554
+ * this after out of bounds modification to the contained value by external code. */
27555
+ clamp() {
27556
+ while (this.value > this.maxSize) {
27557
+ this.value -= this.maxSize + 1;
27558
+ }
27559
+ while (this.value < 0) {
27560
+ this.value += this.maxSize + 1;
27561
+ }
27510
27562
  }
27511
- disconnect() {
27512
- return __awaiter(this, void 0, void 0, function* () {
27513
- if (this.room && this.room.state !== ConnectionState.Disconnected) {
27514
- yield this.room.disconnect();
27515
- // wait for it to go through
27516
- yield new Promise(resolve => setTimeout(resolve, 500));
27517
- }
27518
- });
27563
+ clone() {
27564
+ return new WrapAroundUnsignedInt(this.value, this.maxSize);
27519
27565
  }
27520
- skip() {
27521
- this.setStatus(CheckStatus.SKIPPED);
27566
+ /** When called, maps the containing value to a new containing value. After mapping, the wrap
27567
+ * around external max size bounds are applied. Note that this is a mutative operation. */
27568
+ update(updateFn) {
27569
+ this.value = updateFn(this.value);
27570
+ this.clamp();
27522
27571
  }
27523
- switchProtocol(protocol) {
27524
- return __awaiter(this, void 0, void 0, function* () {
27525
- let hasReconnecting = false;
27526
- let hasReconnected = false;
27527
- this.room.on(RoomEvent.Reconnecting, () => {
27528
- hasReconnecting = true;
27529
- });
27530
- this.room.once(RoomEvent.Reconnected, () => {
27531
- hasReconnected = true;
27532
- });
27533
- this.room.simulateScenario("force-".concat(protocol));
27534
- yield new Promise(resolve => setTimeout(resolve, 1000));
27535
- if (!hasReconnecting) {
27536
- // no need to wait for reconnection
27537
- return;
27538
- }
27539
- // wait for 10 seconds for reconnection
27540
- const timeout = Date.now() + 10000;
27541
- while (Date.now() < timeout) {
27542
- if (hasReconnected) {
27543
- return;
27544
- }
27545
- yield sleep(100);
27546
- }
27547
- throw new Error("Could not reconnect using ".concat(protocol, " protocol after 10 seconds"));
27548
- });
27572
+ /** Increments the given `n` to the inner value. Note that this is a mutative operation. */
27573
+ increment() {
27574
+ let n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
27575
+ this.update(value => value + n);
27549
27576
  }
27550
- appendMessage(message) {
27551
- this.logs.push({
27552
- level: 'info',
27553
- message
27554
- });
27555
- this.emit('update', this.getInfo());
27577
+ /** Decrements the given `n` from the inner value. Note that this is a mutative operation. */
27578
+ decrement() {
27579
+ let n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
27580
+ this.update(value => value - n);
27556
27581
  }
27557
- appendWarning(message) {
27558
- this.logs.push({
27559
- level: 'warning',
27560
- message
27561
- });
27562
- this.emit('update', this.getInfo());
27582
+ getThenIncrement() {
27583
+ const previousValue = this.value;
27584
+ this.increment();
27585
+ return new WrapAroundUnsignedInt(previousValue, this.maxSize);
27563
27586
  }
27564
- appendError(message) {
27565
- this.logs.push({
27566
- level: 'error',
27567
- message
27568
- });
27569
- this.emit('update', this.getInfo());
27587
+ /** Returns true if {@link this} is before the passed other {@link WrapAroundUnsignedInt}. */
27588
+ isBefore(other) {
27589
+ const a = this.value >>> 0;
27590
+ const b = other.value >>> 0;
27591
+ const diff = b - a >>> 0;
27592
+ return diff !== 0 && diff < this.maxSize + 1;
27570
27593
  }
27571
- setStatus(status) {
27572
- this.status = status;
27573
- this.emit('update', this.getInfo());
27594
+ }
27595
+ class DataTrackTimestamp {
27596
+ static fromRtpTicks(rtpTicks) {
27597
+ return new DataTrackTimestamp(rtpTicks, 90000);
27574
27598
  }
27575
- get engine() {
27576
- var _a;
27577
- return (_a = this.room) === null || _a === void 0 ? void 0 : _a.engine;
27599
+ /** Generates a timestamp initialized to a non cryptographically secure random value, so that
27600
+ * different streams are more difficult to correlate in packet capture. */
27601
+ static rtpRandom() {
27602
+ const randomValue = Math.round(Math.random() * U32_MAX_SIZE);
27603
+ return DataTrackTimestamp.fromRtpTicks(randomValue);
27578
27604
  }
27579
- getInfo() {
27580
- return {
27581
- logs: this.logs,
27582
- name: this.name,
27583
- status: this.status,
27584
- description: this.description
27585
- };
27605
+ constructor(raw, rateInHz) {
27606
+ this.timestamp = WrapAroundUnsignedInt.u32(raw);
27607
+ this.rateInHz = rateInHz;
27586
27608
  }
27587
- }/**
27588
- * Checks for connections quality to closests Cloud regions and determining the best quality
27589
- */
27590
- class CloudRegionCheck extends Checker {
27591
- get description() {
27592
- return 'Cloud regions';
27609
+ asTicks() {
27610
+ return this.timestamp.value;
27593
27611
  }
27594
- perform() {
27595
- return __awaiter(this, void 0, void 0, function* () {
27596
- const regionProvider = new RegionUrlProvider(this.url, this.token);
27597
- if (!regionProvider.isCloud()) {
27598
- this.skip();
27599
- return;
27600
- }
27601
- const regionStats = [];
27602
- const seenUrls = new Set();
27603
- for (let i = 0; i < 3; i++) {
27604
- const regionUrl = yield regionProvider.getNextBestRegionUrl();
27605
- if (!regionUrl) {
27606
- break;
27607
- }
27608
- if (seenUrls.has(regionUrl)) {
27609
- continue;
27610
- }
27611
- seenUrls.add(regionUrl);
27612
- const stats = yield this.checkCloudRegion(regionUrl);
27613
- this.appendMessage("".concat(stats.region, " RTT: ").concat(stats.rtt, "ms, duration: ").concat(stats.duration, "ms"));
27614
- regionStats.push(stats);
27615
- }
27616
- regionStats.sort((a, b) => {
27617
- return (a.duration - b.duration) * 0.5 + (a.rtt - b.rtt) * 0.5;
27618
- });
27619
- const bestRegion = regionStats[0];
27620
- this.bestStats = bestRegion;
27621
- this.appendMessage("best Cloud region: ".concat(bestRegion.region));
27622
- });
27612
+ clone() {
27613
+ return new DataTrackTimestamp(this.timestamp.value, this.rateInHz);
27623
27614
  }
27624
- getInfo() {
27625
- const info = super.getInfo();
27626
- info.data = this.bestStats;
27627
- return info;
27615
+ wrappingAdd(n) {
27616
+ this.timestamp.increment(n);
27628
27617
  }
27629
- checkCloudRegion(url) {
27630
- return __awaiter(this, void 0, void 0, function* () {
27631
- var _a, _b;
27632
- yield this.connect(url);
27633
- if (this.options.protocol === 'tcp') {
27634
- yield this.switchProtocol('tcp');
27635
- }
27636
- const region = (_a = this.room.serverInfo) === null || _a === void 0 ? void 0 : _a.region;
27637
- if (!region) {
27638
- throw new Error('Region not found');
27639
- }
27640
- const writer = yield this.room.localParticipant.streamText({
27641
- topic: 'test'
27642
- });
27643
- const chunkSize = 1000; // each chunk is about 1000 bytes
27644
- const totalSize = 1000000; // approximately 1MB of data
27645
- const numChunks = totalSize / chunkSize; // will yield 1000 chunks
27646
- const chunkData = 'A'.repeat(chunkSize); // create a string of 1000 'A' characters
27647
- const startTime = Date.now();
27648
- for (let i = 0; i < numChunks; i++) {
27649
- yield writer.write(chunkData);
27650
- }
27651
- yield writer.close();
27652
- const endTime = Date.now();
27653
- const stats = yield (_b = this.room.engine.pcManager) === null || _b === void 0 ? void 0 : _b.publisher.getStats();
27654
- const regionStats = {
27655
- region: region,
27656
- rtt: 10000,
27657
- duration: endTime - startTime
27658
- };
27659
- stats === null || stats === void 0 ? void 0 : stats.forEach(stat => {
27660
- if (stat.type === 'candidate-pair' && stat.nominated) {
27661
- regionStats.rtt = stat.currentRoundTripTime * 1000;
27662
- }
27663
- });
27664
- yield this.disconnect();
27665
- return regionStats;
27666
- });
27618
+ /** Returns true if {@link this} is before the passed other {@link DataTrackTimestamp}. */
27619
+ isBefore(other) {
27620
+ return this.timestamp.isBefore(other.timestamp);
27667
27621
  }
27668
- }const TEST_DURATION = 10000;
27669
- class ConnectionProtocolCheck extends Checker {
27670
- get description() {
27671
- return 'Connection via UDP vs TCP';
27622
+ }
27623
+ function coerceToDataView(input) {
27624
+ if (input instanceof DataView) {
27625
+ return input;
27626
+ } else if (input instanceof ArrayBuffer) {
27627
+ return new DataView(input);
27628
+ } else if (input instanceof Uint8Array) {
27629
+ return new DataView(input.buffer, input.byteOffset, input.byteLength);
27630
+ } else {
27631
+ throw new Error("Error coercing ".concat(input, " to DataView - input was not DataView, ArrayBuffer, or Uint8Array."));
27672
27632
  }
27673
- perform() {
27674
- return __awaiter(this, void 0, void 0, function* () {
27675
- const udpStats = yield this.checkConnectionProtocol('udp');
27676
- const tcpStats = yield this.checkConnectionProtocol('tcp');
27677
- this.bestStats = udpStats;
27678
- // udp should is the better protocol typically. however, we'd prefer TCP when either of these conditions are true:
27679
- // 1. the bandwidth limitation is worse on UDP by 500ms
27680
- // 2. the packet loss is higher on UDP by 1%
27681
- if (udpStats.qualityLimitationDurations.bandwidth - tcpStats.qualityLimitationDurations.bandwidth > 0.5 || (udpStats.packetsLost - tcpStats.packetsLost) / udpStats.packetsSent > 0.01) {
27682
- this.appendMessage('best connection quality via tcp');
27683
- this.bestStats = tcpStats;
27684
- } else {
27685
- this.appendMessage('best connection quality via udp');
27686
- }
27687
- const stats = this.bestStats;
27688
- this.appendMessage("upstream bitrate: ".concat((stats.bitrateTotal / stats.count / 1000 / 1000).toFixed(2), " mbps"));
27689
- this.appendMessage("RTT: ".concat((stats.rttTotal / stats.count * 1000).toFixed(2), " ms"));
27690
- this.appendMessage("jitter: ".concat((stats.jitterTotal / stats.count * 1000).toFixed(2), " ms"));
27691
- if (stats.packetsLost > 0) {
27692
- this.appendWarning("packets lost: ".concat((stats.packetsLost / stats.packetsSent * 100).toFixed(2), "%"));
27693
- }
27694
- if (stats.qualityLimitationDurations.bandwidth > 1) {
27695
- this.appendWarning("bandwidth limited ".concat((stats.qualityLimitationDurations.bandwidth / (TEST_DURATION / 1000) * 100).toFixed(2), "%"));
27696
- }
27697
- if (stats.qualityLimitationDurations.cpu > 0) {
27698
- this.appendWarning("cpu limited ".concat((stats.qualityLimitationDurations.cpu / (TEST_DURATION / 1000) * 100).toFixed(2), "%"));
27699
- }
27700
- });
27633
+ }// Number type sizes
27634
+ const U8_LENGTH_BYTES = 1;
27635
+ const U16_LENGTH_BYTES = 2;
27636
+ const U32_LENGTH_BYTES = 4;
27637
+ const U64_LENGTH_BYTES = 8;
27638
+ /// Constants used for serialization and deserialization.
27639
+ const SUPPORTED_VERSION = 0;
27640
+ const BASE_HEADER_LEN = 12;
27641
+ // Bitfield shifts and masks for header flags
27642
+ const VERSION_SHIFT = 5;
27643
+ const VERSION_MASK = 0x07;
27644
+ const FRAME_MARKER_SHIFT = 3;
27645
+ const FRAME_MARKER_MASK = 0x3;
27646
+ const FRAME_MARKER_START = 0x2;
27647
+ const FRAME_MARKER_FINAL = 0x1;
27648
+ const FRAME_MARKER_INTER = 0x0;
27649
+ const FRAME_MARKER_SINGLE = 0x3;
27650
+ const EXT_WORDS_INDICATOR_SIZE = 2;
27651
+ const EXT_FLAG_SHIFT = 0x2;
27652
+ const EXT_FLAG_MASK = 0x1;
27653
+ const EXT_TAG_PADDING = 0;var DataTrackDeserializeErrorReason;
27654
+ (function (DataTrackDeserializeErrorReason) {
27655
+ DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["TooShort"] = 0] = "TooShort";
27656
+ DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["HeaderOverrun"] = 1] = "HeaderOverrun";
27657
+ DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["MissingExtWords"] = 2] = "MissingExtWords";
27658
+ DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["UnsupportedVersion"] = 3] = "UnsupportedVersion";
27659
+ DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["InvalidHandle"] = 4] = "InvalidHandle";
27660
+ DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["MalformedExt"] = 5] = "MalformedExt";
27661
+ })(DataTrackDeserializeErrorReason || (DataTrackDeserializeErrorReason = {}));
27662
+ class DataTrackDeserializeError extends LivekitReasonedError {
27663
+ constructor(message, reason, options) {
27664
+ super(19, message, options);
27665
+ this.name = 'DataTrackDeserializeError';
27666
+ this.reason = reason;
27667
+ this.reasonName = DataTrackDeserializeErrorReason[reason];
27701
27668
  }
27702
- getInfo() {
27703
- const info = super.getInfo();
27704
- info.data = this.bestStats;
27705
- return info;
27669
+ static tooShort() {
27670
+ return new DataTrackDeserializeError('Too short to contain a valid header', DataTrackDeserializeErrorReason.TooShort);
27706
27671
  }
27707
- checkConnectionProtocol(protocol) {
27708
- return __awaiter(this, void 0, void 0, function* () {
27709
- yield this.connect();
27710
- if (protocol === 'tcp') {
27711
- yield this.switchProtocol('tcp');
27712
- } else {
27713
- yield this.switchProtocol('udp');
27714
- }
27715
- // create a canvas with animated content
27716
- const canvas = document.createElement('canvas');
27717
- canvas.width = 1280;
27718
- canvas.height = 720;
27719
- const ctx = canvas.getContext('2d');
27720
- if (!ctx) {
27721
- throw new Error('Could not get canvas context');
27722
- }
27723
- let hue = 0;
27724
- const animate = () => {
27725
- hue = (hue + 1) % 360;
27726
- ctx.fillStyle = "hsl(".concat(hue, ", 100%, 50%)");
27727
- ctx.fillRect(0, 0, canvas.width, canvas.height);
27728
- requestAnimationFrame(animate);
27729
- };
27730
- animate();
27731
- // create video track from canvas
27732
- const stream = canvas.captureStream(30); // 30fps
27733
- const videoTrack = stream.getVideoTracks()[0];
27734
- // publish to room
27735
- const pub = yield this.room.localParticipant.publishTrack(videoTrack, {
27736
- simulcast: false,
27737
- degradationPreference: 'maintain-resolution',
27738
- videoEncoding: {
27739
- maxBitrate: 2000000
27740
- }
27741
- });
27742
- const track = pub.track;
27743
- const protocolStats = {
27744
- protocol,
27745
- packetsLost: 0,
27746
- packetsSent: 0,
27747
- qualityLimitationDurations: {},
27748
- rttTotal: 0,
27749
- jitterTotal: 0,
27750
- bitrateTotal: 0,
27751
- count: 0
27752
- };
27753
- // gather stats once a second
27754
- const interval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
27755
- const stats = yield track.getRTCStatsReport();
27756
- stats === null || stats === void 0 ? void 0 : stats.forEach(stat => {
27757
- if (stat.type === 'outbound-rtp') {
27758
- protocolStats.packetsSent = stat.packetsSent;
27759
- protocolStats.qualityLimitationDurations = stat.qualityLimitationDurations;
27760
- protocolStats.bitrateTotal += stat.targetBitrate;
27761
- protocolStats.count++;
27762
- } else if (stat.type === 'remote-inbound-rtp') {
27763
- protocolStats.packetsLost = stat.packetsLost;
27764
- protocolStats.rttTotal += stat.roundTripTime;
27765
- protocolStats.jitterTotal += stat.jitter;
27766
- }
27767
- });
27768
- }), 1000);
27769
- // wait a bit to gather stats
27770
- yield new Promise(resolve => setTimeout(resolve, TEST_DURATION));
27771
- clearInterval(interval);
27772
- videoTrack.stop();
27773
- canvas.remove();
27774
- yield this.disconnect();
27775
- return protocolStats;
27776
- });
27672
+ static headerOverrun() {
27673
+ return new DataTrackDeserializeError('Header exceeds total packet length', DataTrackDeserializeErrorReason.HeaderOverrun);
27777
27674
  }
27778
- }class PublishAudioCheck extends Checker {
27779
- get description() {
27780
- return 'Can publish audio';
27675
+ static missingExtWords() {
27676
+ return new DataTrackDeserializeError('Extension word indicator is missing', DataTrackDeserializeErrorReason.MissingExtWords);
27781
27677
  }
27782
- perform() {
27783
- return __awaiter(this, void 0, void 0, function* () {
27784
- var _a;
27785
- const room = yield this.connect();
27786
- const track = yield createLocalAudioTrack();
27787
- const trackIsSilent = yield detectSilence(track, 1000);
27788
- if (trackIsSilent) {
27789
- throw new Error('unable to detect audio from microphone');
27790
- }
27791
- this.appendMessage('detected audio from microphone');
27792
- room.localParticipant.publishTrack(track);
27793
- // wait for a few seconds to publish
27794
- yield new Promise(resolve => setTimeout(resolve, 3000));
27795
- // verify RTC stats that it's publishing
27796
- const stats = yield (_a = track.sender) === null || _a === void 0 ? void 0 : _a.getStats();
27797
- if (!stats) {
27798
- throw new Error('Could not get RTCStats');
27799
- }
27800
- let numPackets = 0;
27801
- stats.forEach(stat => {
27802
- if (stat.type === 'outbound-rtp' && (stat.kind === 'audio' || !stat.kind && stat.mediaType === 'audio')) {
27803
- numPackets = stat.packetsSent;
27804
- }
27805
- });
27806
- if (numPackets === 0) {
27807
- throw new Error('Could not determine packets are sent');
27808
- }
27809
- this.appendMessage("published ".concat(numPackets, " audio packets"));
27678
+ static unsupportedVersion(version) {
27679
+ return new DataTrackDeserializeError("Unsupported version ".concat(version), DataTrackDeserializeErrorReason.UnsupportedVersion);
27680
+ }
27681
+ static invalidHandle(cause) {
27682
+ return new DataTrackDeserializeError("invalid track handle: ".concat(cause.message), DataTrackDeserializeErrorReason.InvalidHandle, {
27683
+ cause
27810
27684
  });
27811
27685
  }
27812
- }class PublishVideoCheck extends Checker {
27813
- get description() {
27814
- return 'Can publish video';
27686
+ static malformedExt(tag) {
27687
+ return new DataTrackDeserializeError("Extension with tag ".concat(tag, " is malformed"), DataTrackDeserializeErrorReason.MalformedExt);
27815
27688
  }
27816
- perform() {
27817
- return __awaiter(this, void 0, void 0, function* () {
27818
- var _a;
27819
- const room = yield this.connect();
27820
- const track = yield createLocalVideoTrack();
27821
- // check if we have video from camera
27822
- yield this.checkForVideo(track.mediaStreamTrack);
27823
- room.localParticipant.publishTrack(track);
27824
- // wait for a few seconds to publish
27825
- yield new Promise(resolve => setTimeout(resolve, 5000));
27826
- // verify RTC stats that it's publishing
27827
- const stats = yield (_a = track.sender) === null || _a === void 0 ? void 0 : _a.getStats();
27828
- if (!stats) {
27829
- throw new Error('Could not get RTCStats');
27830
- }
27831
- let numPackets = 0;
27832
- stats.forEach(stat => {
27833
- if (stat.type === 'outbound-rtp' && (stat.kind === 'video' || !stat.kind && stat.mediaType === 'video')) {
27834
- numPackets += stat.packetsSent;
27835
- }
27836
- });
27837
- if (numPackets === 0) {
27838
- throw new Error('Could not determine packets are sent');
27839
- }
27840
- this.appendMessage("published ".concat(numPackets, " video packets"));
27841
- });
27689
+ }
27690
+ var DataTrackSerializeErrorReason;
27691
+ (function (DataTrackSerializeErrorReason) {
27692
+ DataTrackSerializeErrorReason[DataTrackSerializeErrorReason["TooSmallForHeader"] = 0] = "TooSmallForHeader";
27693
+ DataTrackSerializeErrorReason[DataTrackSerializeErrorReason["TooSmallForPayload"] = 1] = "TooSmallForPayload";
27694
+ })(DataTrackSerializeErrorReason || (DataTrackSerializeErrorReason = {}));
27695
+ class DataTrackSerializeError extends LivekitReasonedError {
27696
+ constructor(message, reason, options) {
27697
+ super(19, message, options);
27698
+ this.name = 'DataTrackSerializeError';
27699
+ this.reason = reason;
27700
+ this.reasonName = DataTrackSerializeErrorReason[reason];
27842
27701
  }
27843
- checkForVideo(track) {
27844
- return __awaiter(this, void 0, void 0, function* () {
27845
- const stream = new MediaStream();
27846
- stream.addTrack(track.clone());
27847
- // Create video element to check frames
27848
- const video = document.createElement('video');
27849
- video.srcObject = stream;
27850
- video.muted = true;
27851
- video.autoplay = true;
27852
- video.playsInline = true;
27853
- // For iOS Safari
27854
- video.setAttribute('playsinline', 'true');
27855
- document.body.appendChild(video);
27856
- yield new Promise(resolve => {
27857
- video.onplay = () => {
27858
- setTimeout(() => {
27859
- var _a, _b, _c, _d;
27860
- const canvas = document.createElement('canvas');
27861
- const settings = track.getSettings();
27862
- const width = (_b = (_a = settings.width) !== null && _a !== void 0 ? _a : video.videoWidth) !== null && _b !== void 0 ? _b : 1280;
27863
- const height = (_d = (_c = settings.height) !== null && _c !== void 0 ? _c : video.videoHeight) !== null && _d !== void 0 ? _d : 720;
27864
- canvas.width = width;
27865
- canvas.height = height;
27866
- const ctx = canvas.getContext('2d');
27867
- // Draw video frame to canvas
27868
- ctx.drawImage(video, 0, 0);
27869
- // Get image data and check if all pixels are black
27870
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
27871
- const data = imageData.data;
27872
- let isAllBlack = true;
27873
- for (let i = 0; i < data.length; i += 4) {
27874
- if (data[i] !== 0 || data[i + 1] !== 0 || data[i + 2] !== 0) {
27875
- isAllBlack = false;
27876
- break;
27877
- }
27878
- }
27879
- if (isAllBlack) {
27880
- this.appendError('camera appears to be producing only black frames');
27881
- } else {
27882
- this.appendMessage('received video frames');
27883
- }
27884
- resolve();
27885
- }, 1000);
27886
- };
27887
- video.play();
27888
- });
27889
- stream.getTracks().forEach(t => t.stop());
27890
- video.remove();
27891
- });
27702
+ static tooSmallForHeader() {
27703
+ return new DataTrackSerializeError('Buffer cannot fit header', DataTrackSerializeErrorReason.TooSmallForHeader);
27892
27704
  }
27893
- }class ReconnectCheck extends Checker {
27894
- get description() {
27895
- return 'Resuming connection after interruption';
27705
+ static tooSmallForPayload() {
27706
+ return new DataTrackSerializeError('Buffer cannot fit payload', DataTrackSerializeErrorReason.TooSmallForPayload);
27896
27707
  }
27897
- perform() {
27898
- return __awaiter(this, void 0, void 0, function* () {
27899
- var _a;
27900
- const room = yield this.connect();
27901
- let reconnectingTriggered = false;
27902
- let reconnected = false;
27903
- let reconnectResolver;
27904
- const reconnectTimeout = new Promise(resolve => {
27905
- setTimeout(resolve, 5000);
27906
- reconnectResolver = resolve;
27907
- });
27908
- const handleReconnecting = () => {
27909
- reconnectingTriggered = true;
27910
- };
27911
- room.on(RoomEvent.SignalReconnecting, handleReconnecting).on(RoomEvent.Reconnecting, handleReconnecting).on(RoomEvent.Reconnected, () => {
27912
- reconnected = true;
27913
- reconnectResolver(true);
27914
- });
27915
- (_a = room.engine.client.ws) === null || _a === void 0 ? void 0 : _a.close();
27916
- const onClose = room.engine.client.onClose;
27917
- if (onClose) {
27918
- onClose('');
27919
- }
27920
- yield reconnectTimeout;
27921
- if (!reconnectingTriggered) {
27922
- throw new Error('Did not attempt to reconnect');
27923
- } else if (!reconnected || room.state !== ConnectionState.Connected) {
27924
- this.appendWarning('reconnection is only possible in Redis-based configurations');
27925
- throw new Error('Not able to reconnect');
27926
- }
27927
- });
27708
+ }/** An abstract class implementing common behavior related to data track binary serialization. */
27709
+ class Serializable {
27710
+ /** Encodes the instance as binary and returns the data as a Uint8Array. */
27711
+ toBinary() {
27712
+ const lengthBytes = this.toBinaryLengthBytes();
27713
+ const output = new ArrayBuffer(lengthBytes);
27714
+ const view = new DataView(output);
27715
+ const writtenBytes = this.toBinaryInto(view);
27716
+ if (lengthBytes !== writtenBytes) {
27717
+ // @throws-transformer ignore - this should be treated as a "panic" and not be caught
27718
+ throw new Error("".concat(this.constructor.name, ".toBinary: written bytes (").concat(writtenBytes, " bytes) not equal to allocated array buffer length (").concat(lengthBytes, " bytes)."));
27719
+ }
27720
+ return new Uint8Array(output);
27928
27721
  }
27929
- }class TURNCheck extends Checker {
27930
- get description() {
27931
- return 'Can connect via TURN';
27722
+ }var DataTrackExtensionTag;
27723
+ (function (DataTrackExtensionTag) {
27724
+ DataTrackExtensionTag[DataTrackExtensionTag["UserTimestamp"] = 2] = "UserTimestamp";
27725
+ DataTrackExtensionTag[DataTrackExtensionTag["E2ee"] = 1] = "E2ee";
27726
+ })(DataTrackExtensionTag || (DataTrackExtensionTag = {}));
27727
+ class DataTrackExtension extends Serializable {}
27728
+ class DataTrackUserTimestampExtension extends DataTrackExtension {
27729
+ constructor(timestamp) {
27730
+ super();
27731
+ this.timestamp = timestamp;
27932
27732
  }
27933
- perform() {
27934
- return __awaiter(this, void 0, void 0, function* () {
27935
- var _a, _b, _c;
27936
- if (isCloud(new URL(this.url))) {
27937
- this.appendMessage('Using region specific url');
27938
- this.url = (_a = yield new RegionUrlProvider(this.url, this.token).getNextBestRegionUrl()) !== null && _a !== void 0 ? _a : this.url;
27939
- }
27940
- const signalClient = new SignalClient();
27941
- const joinRes = yield signalClient.join(this.url, this.token, {
27942
- autoSubscribe: true,
27943
- maxRetries: 0,
27944
- e2eeEnabled: false,
27945
- websocketTimeout: 15000
27946
- }, undefined, true);
27947
- let hasTLS = false;
27948
- let hasTURN = false;
27949
- let hasSTUN = false;
27950
- for (let iceServer of joinRes.iceServers) {
27951
- for (let url of iceServer.urls) {
27952
- if (url.startsWith('turn:')) {
27953
- hasTURN = true;
27954
- hasSTUN = true;
27955
- } else if (url.startsWith('turns:')) {
27956
- hasTURN = true;
27957
- hasSTUN = true;
27958
- hasTLS = true;
27959
- }
27960
- if (url.startsWith('stun:')) {
27961
- hasSTUN = true;
27962
- }
27963
- }
27964
- }
27965
- if (!hasSTUN) {
27966
- this.appendWarning('No STUN servers configured on server side.');
27967
- } else if (hasTURN && !hasTLS) {
27968
- this.appendWarning('TURN is configured server side, but TURN/TLS is unavailable.');
27969
- }
27970
- yield signalClient.close();
27971
- if (((_c = (_b = this.connectOptions) === null || _b === void 0 ? void 0 : _b.rtcConfig) === null || _c === void 0 ? void 0 : _c.iceServers) || hasTURN) {
27972
- yield this.room.connect(this.url, this.token, {
27973
- rtcConfig: {
27974
- iceTransportPolicy: 'relay'
27975
- }
27976
- });
27977
- } else {
27978
- this.appendWarning('No TURN servers configured.');
27979
- this.skip();
27980
- yield new Promise(resolve => setTimeout(resolve, 0));
27981
- }
27982
- });
27733
+ toBinaryLengthBytes() {
27734
+ return U16_LENGTH_BYTES /* tag */ + U16_LENGTH_BYTES /* length */ + DataTrackUserTimestampExtension.lengthBytes;
27983
27735
  }
27984
- }class WebRTCCheck extends Checker {
27985
- get description() {
27986
- return 'Establishing WebRTC connection';
27736
+ toBinaryInto(dataView) {
27737
+ let byteIndex = 0;
27738
+ dataView.setUint16(byteIndex, DataTrackUserTimestampExtension.tag);
27739
+ byteIndex += U16_LENGTH_BYTES;
27740
+ const rtpOrientedLength = DataTrackUserTimestampExtension.lengthBytes - 1;
27741
+ dataView.setUint16(byteIndex, rtpOrientedLength);
27742
+ byteIndex += U16_LENGTH_BYTES;
27743
+ dataView.setBigUint64(byteIndex, this.timestamp);
27744
+ byteIndex += U64_LENGTH_BYTES;
27745
+ const totalLengthBytes = this.toBinaryLengthBytes();
27746
+ if (byteIndex !== totalLengthBytes) {
27747
+ // @throws-transformer ignore - this should be treated as a "panic" and not be caught
27748
+ throw new Error("DataTrackUserTimestampExtension.toBinaryInto: Wrote ".concat(byteIndex, " bytes but expected length was ").concat(totalLengthBytes, " bytes"));
27749
+ }
27750
+ return byteIndex;
27751
+ }
27752
+ toJSON() {
27753
+ return {
27754
+ tag: DataTrackUserTimestampExtension.tag,
27755
+ lengthBytes: DataTrackUserTimestampExtension.lengthBytes,
27756
+ timestamp: this.timestamp
27757
+ };
27758
+ }
27759
+ }
27760
+ DataTrackUserTimestampExtension.tag = DataTrackExtensionTag.UserTimestamp;
27761
+ DataTrackUserTimestampExtension.lengthBytes = 8;
27762
+ class DataTrackE2eeExtension extends DataTrackExtension {
27763
+ constructor(keyIndex, iv) {
27764
+ super();
27765
+ this.keyIndex = keyIndex;
27766
+ this.iv = iv;
27767
+ }
27768
+ toBinaryLengthBytes() {
27769
+ return U16_LENGTH_BYTES /* tag */ + U16_LENGTH_BYTES /* length */ + DataTrackE2eeExtension.lengthBytes;
27987
27770
  }
27988
- perform() {
27989
- return __awaiter(this, void 0, void 0, function* () {
27990
- let hasTcp = false;
27991
- let hasIpv4Udp = false;
27992
- this.room.on(RoomEvent.SignalConnected, () => {
27993
- var _a;
27994
- const prevTrickle = this.room.engine.client.onTrickle;
27995
- this.room.engine.client.onTrickle = (sd, target) => {
27996
- if (sd.candidate) {
27997
- const candidate = new RTCIceCandidate(sd);
27998
- let str = "".concat(candidate.protocol, " ").concat(candidate.address, ":").concat(candidate.port, " ").concat(candidate.type);
27999
- if (candidate.address) {
28000
- if (isIPPrivate(candidate.address)) {
28001
- str += ' (private)';
28002
- } else {
28003
- if (candidate.protocol === 'tcp' && candidate.tcpType === 'passive') {
28004
- hasTcp = true;
28005
- str += ' (passive)';
28006
- } else if (candidate.protocol === 'udp') {
28007
- hasIpv4Udp = true;
28008
- }
28009
- }
28010
- }
28011
- this.appendMessage(str);
28012
- }
28013
- if (prevTrickle) {
28014
- prevTrickle(sd, target);
28015
- }
28016
- };
28017
- if ((_a = this.room.engine.pcManager) === null || _a === void 0 ? void 0 : _a.subscriber) {
28018
- this.room.engine.pcManager.subscriber.onIceCandidateError = ev => {
28019
- if (ev instanceof RTCPeerConnectionIceErrorEvent) {
28020
- this.appendWarning("error with ICE candidate: ".concat(ev.errorCode, " ").concat(ev.errorText, " ").concat(ev.url));
28021
- }
28022
- };
28023
- }
28024
- });
28025
- try {
28026
- yield this.connect();
28027
- livekitLogger.info('now the room is connected');
28028
- } catch (err) {
28029
- this.appendWarning('ports need to be open on firewall in order to connect.');
28030
- throw err;
28031
- }
28032
- if (!hasTcp) {
28033
- this.appendWarning('Server is not configured for ICE/TCP');
28034
- }
28035
- if (!hasIpv4Udp) {
28036
- this.appendWarning('No public IPv4 UDP candidates were found. Your server is likely not configured correctly');
28037
- }
28038
- });
27771
+ toBinaryInto(dataView) {
27772
+ let byteIndex = 0;
27773
+ dataView.setUint16(byteIndex, DataTrackE2eeExtension.tag);
27774
+ byteIndex += U16_LENGTH_BYTES;
27775
+ const rtpOrientedLength = DataTrackE2eeExtension.lengthBytes - 1;
27776
+ dataView.setUint16(byteIndex, rtpOrientedLength);
27777
+ byteIndex += U16_LENGTH_BYTES;
27778
+ dataView.setUint8(byteIndex, this.keyIndex);
27779
+ byteIndex += U8_LENGTH_BYTES;
27780
+ for (let i = 0; i < this.iv.length; i += 1) {
27781
+ dataView.setUint8(byteIndex, this.iv[i]);
27782
+ byteIndex += U8_LENGTH_BYTES;
27783
+ }
27784
+ const totalLengthBytes = this.toBinaryLengthBytes();
27785
+ if (byteIndex !== totalLengthBytes) {
27786
+ // @throws-transformer ignore - this should be treated as a "panic" and not be caught
27787
+ throw new Error("DataTrackE2eeExtension.toBinaryInto: Wrote ".concat(byteIndex, " bytes but expected length was ").concat(totalLengthBytes, " bytes"));
27788
+ }
27789
+ return byteIndex;
27790
+ }
27791
+ toJSON() {
27792
+ return {
27793
+ tag: DataTrackE2eeExtension.tag,
27794
+ lengthBytes: DataTrackE2eeExtension.lengthBytes,
27795
+ keyIndex: this.keyIndex,
27796
+ iv: this.iv
27797
+ };
28039
27798
  }
28040
27799
  }
28041
- function isIPPrivate(address) {
28042
- const parts = address.split('.');
28043
- if (parts.length === 4) {
28044
- if (parts[0] === '10') {
28045
- return true;
28046
- } else if (parts[0] === '192' && parts[1] === '168') {
28047
- return true;
28048
- } else if (parts[0] === '172') {
28049
- const second = parseInt(parts[1], 10);
28050
- if (second >= 16 && second <= 31) {
28051
- return true;
28052
- }
27800
+ DataTrackE2eeExtension.tag = DataTrackExtensionTag.E2ee;
27801
+ DataTrackE2eeExtension.lengthBytes = 13;
27802
+ class DataTrackExtensions extends Serializable {
27803
+ constructor() {
27804
+ let opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
27805
+ super();
27806
+ this.userTimestamp = opts.userTimestamp;
27807
+ this.e2ee = opts.e2ee;
27808
+ }
27809
+ toBinaryLengthBytes() {
27810
+ let lengthBytes = 0;
27811
+ if (this.userTimestamp) {
27812
+ lengthBytes += this.userTimestamp.toBinaryLengthBytes();
27813
+ }
27814
+ if (this.e2ee) {
27815
+ lengthBytes += this.e2ee.toBinaryLengthBytes();
28053
27816
  }
27817
+ return lengthBytes;
28054
27818
  }
28055
- return false;
28056
- }class WebSocketCheck extends Checker {
28057
- get description() {
28058
- return 'Connecting to signal connection via WebSocket';
27819
+ toBinaryInto(dataView) {
27820
+ let byteIndex = 0;
27821
+ if (this.e2ee) {
27822
+ const e2eeBytes = this.e2ee.toBinaryInto(dataView);
27823
+ byteIndex += e2eeBytes;
27824
+ }
27825
+ if (this.userTimestamp) {
27826
+ const userTimestampBytes = this.userTimestamp.toBinaryInto(new DataView(dataView.buffer, dataView.byteOffset + byteIndex));
27827
+ byteIndex += userTimestampBytes;
27828
+ }
27829
+ const totalLengthBytes = this.toBinaryLengthBytes();
27830
+ if (byteIndex !== totalLengthBytes) {
27831
+ // @throws-transformer ignore - this should be treated as a "panic" and not be caught
27832
+ throw new Error("DataTrackExtensions.toBinaryInto: Wrote ".concat(byteIndex, " bytes but expected length was ").concat(totalLengthBytes, " bytes"));
27833
+ }
27834
+ return byteIndex;
28059
27835
  }
28060
- perform() {
28061
- return __awaiter(this, void 0, void 0, function* () {
28062
- var _a, _b, _c;
28063
- if (this.url.startsWith('ws:') || this.url.startsWith('http:')) {
28064
- this.appendWarning('Server is insecure, clients may block connections to it');
27836
+ static fromBinary(input) {
27837
+ const dataView = coerceToDataView(input);
27838
+ let userTimestamp;
27839
+ let e2ee;
27840
+ let byteIndex = 0;
27841
+ while (dataView.byteLength - byteIndex >= U16_LENGTH_BYTES + U16_LENGTH_BYTES) {
27842
+ const tag = dataView.getUint16(byteIndex);
27843
+ byteIndex += U16_LENGTH_BYTES;
27844
+ const rtpOrientedLength = dataView.getUint16(byteIndex);
27845
+ const lengthBytes = rtpOrientedLength + 1;
27846
+ byteIndex += U16_LENGTH_BYTES;
27847
+ if (tag === EXT_TAG_PADDING) {
27848
+ // Skip padding
27849
+ continue;
28065
27850
  }
28066
- let signalClient = new SignalClient();
28067
- let joinRes;
28068
- try {
28069
- joinRes = yield signalClient.join(this.url, this.token, {
28070
- autoSubscribe: true,
28071
- maxRetries: 0,
28072
- e2eeEnabled: false,
28073
- websocketTimeout: 15000
28074
- }, undefined, true);
28075
- } catch (e) {
28076
- if (isCloud(new URL(this.url))) {
28077
- this.appendMessage("Initial connection failed with error ".concat(e.message, ". Retrying with region fallback"));
28078
- const regionProvider = new RegionUrlProvider(this.url, this.token);
28079
- const regionUrl = yield regionProvider.getNextBestRegionUrl();
28080
- if (regionUrl) {
28081
- joinRes = yield signalClient.join(regionUrl, this.token, {
28082
- autoSubscribe: true,
28083
- maxRetries: 0,
28084
- e2eeEnabled: false,
28085
- websocketTimeout: 15000
28086
- }, undefined, true);
28087
- this.appendMessage("Fallback to region worked. To avoid initial connections failing, ensure you're calling room.prepareConnection() ahead of time");
27851
+ switch (tag) {
27852
+ case DataTrackExtensionTag.UserTimestamp:
27853
+ if (dataView.byteLength - byteIndex < DataTrackUserTimestampExtension.lengthBytes) {
27854
+ throw DataTrackDeserializeError.malformedExt(tag);
28088
27855
  }
28089
- }
28090
- }
28091
- if (joinRes) {
28092
- this.appendMessage("Connected to server, version ".concat(joinRes.serverVersion, "."));
28093
- if (((_a = joinRes.serverInfo) === null || _a === void 0 ? void 0 : _a.edition) === ServerInfo_Edition.Cloud && ((_b = joinRes.serverInfo) === null || _b === void 0 ? void 0 : _b.region)) {
28094
- this.appendMessage("LiveKit Cloud: ".concat((_c = joinRes.serverInfo) === null || _c === void 0 ? void 0 : _c.region));
28095
- }
28096
- } else {
28097
- this.appendError("Websocket connection could not be established");
27856
+ userTimestamp = new DataTrackUserTimestampExtension(dataView.getBigUint64(byteIndex));
27857
+ byteIndex += lengthBytes;
27858
+ break;
27859
+ case DataTrackExtensionTag.E2ee:
27860
+ if (dataView.byteLength - byteIndex < DataTrackE2eeExtension.lengthBytes) {
27861
+ throw DataTrackDeserializeError.malformedExt(tag);
27862
+ }
27863
+ const keyIndex = dataView.getUint8(byteIndex);
27864
+ const iv = new Uint8Array(12);
27865
+ for (let i = 0; i < iv.length; i += 1) {
27866
+ let byteIndexForIv = byteIndex;
27867
+ byteIndexForIv += U8_LENGTH_BYTES; // key index
27868
+ byteIndexForIv += i * U8_LENGTH_BYTES; // Index into iv array
27869
+ iv[i] = dataView.getUint8(byteIndexForIv);
27870
+ }
27871
+ e2ee = new DataTrackE2eeExtension(keyIndex, iv);
27872
+ byteIndex += lengthBytes;
27873
+ break;
27874
+ default:
27875
+ // Skip over unknown extensions (forward compatible).
27876
+ if (dataView.byteLength - byteIndex < lengthBytes) {
27877
+ throw DataTrackDeserializeError.malformedExt(tag);
27878
+ }
27879
+ byteIndex += lengthBytes;
27880
+ break;
28098
27881
  }
28099
- yield signalClient.close();
28100
- });
28101
- }
28102
- }class ConnectionCheck extends eventsExports.EventEmitter {
28103
- constructor(url, token) {
28104
- let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
28105
- super();
28106
- this.options = {};
28107
- this.checkResults = new Map();
28108
- this.url = url;
28109
- this.token = token;
28110
- this.options = options;
28111
- }
28112
- getNextCheckId() {
28113
- const nextId = this.checkResults.size;
28114
- this.checkResults.set(nextId, {
28115
- logs: [],
28116
- status: CheckStatus.IDLE,
28117
- name: '',
28118
- description: ''
28119
- });
28120
- return nextId;
28121
- }
28122
- updateCheck(checkId, info) {
28123
- this.checkResults.set(checkId, info);
28124
- this.emit('checkUpdate', checkId, info);
27882
+ }
27883
+ // NOTE: padding bytes after extension data is intentionally not being processed
27884
+ return [new DataTrackExtensions({
27885
+ userTimestamp,
27886
+ e2ee
27887
+ }), dataView.byteLength];
28125
27888
  }
28126
- isSuccess() {
28127
- return Array.from(this.checkResults.values()).every(r => r.status !== CheckStatus.FAILED);
27889
+ toJSON() {
27890
+ var _a, _b, _c, _d;
27891
+ return {
27892
+ userTimestamp: (_b = (_a = this.userTimestamp) === null || _a === void 0 ? void 0 : _a.toJSON()) !== null && _b !== void 0 ? _b : null,
27893
+ e2ee: (_d = (_c = this.e2ee) === null || _c === void 0 ? void 0 : _c.toJSON()) !== null && _d !== void 0 ? _d : null
27894
+ };
28128
27895
  }
28129
- getResults() {
28130
- return Array.from(this.checkResults.values());
27896
+ }var DataTrackHandleErrorReason;
27897
+ (function (DataTrackHandleErrorReason) {
27898
+ DataTrackHandleErrorReason[DataTrackHandleErrorReason["Reserved"] = 0] = "Reserved";
27899
+ DataTrackHandleErrorReason[DataTrackHandleErrorReason["TooLarge"] = 1] = "TooLarge";
27900
+ })(DataTrackHandleErrorReason || (DataTrackHandleErrorReason = {}));
27901
+ class DataTrackHandleError extends LivekitReasonedError {
27902
+ constructor(message, reason) {
27903
+ super(19, message);
27904
+ this.name = 'DataTrackHandleError';
27905
+ this.reason = reason;
27906
+ this.reasonName = DataTrackHandleErrorReason[reason];
28131
27907
  }
28132
- createAndRunCheck(check) {
28133
- return __awaiter(this, void 0, void 0, function* () {
28134
- const checkId = this.getNextCheckId();
28135
- const test = new check(this.url, this.token, this.options);
28136
- const handleUpdate = info => {
28137
- this.updateCheck(checkId, info);
28138
- };
28139
- test.on('update', handleUpdate);
28140
- const result = yield test.run();
28141
- test.off('update', handleUpdate);
28142
- return result;
28143
- });
27908
+ isReason(reason) {
27909
+ return this.reason === reason;
28144
27910
  }
28145
- checkWebsocket() {
28146
- return __awaiter(this, void 0, void 0, function* () {
28147
- return this.createAndRunCheck(WebSocketCheck);
28148
- });
27911
+ static tooLarge() {
27912
+ return new DataTrackHandleError('Value too large to be a valid track handle', DataTrackHandleErrorReason.TooLarge);
28149
27913
  }
28150
- checkWebRTC() {
28151
- return __awaiter(this, void 0, void 0, function* () {
28152
- return this.createAndRunCheck(WebRTCCheck);
28153
- });
27914
+ static reserved(value) {
27915
+ return new DataTrackHandleError("0x".concat(value.toString(16), " is a reserved value."), DataTrackHandleErrorReason.Reserved);
28154
27916
  }
28155
- checkTURN() {
28156
- return __awaiter(this, void 0, void 0, function* () {
28157
- return this.createAndRunCheck(TURNCheck);
28158
- });
27917
+ }
27918
+ const DataTrackHandle = {
27919
+ fromNumber(raw) {
27920
+ if (raw === 0) {
27921
+ throw DataTrackHandleError.reserved(raw);
27922
+ }
27923
+ if (raw > U16_MAX_SIZE) {
27924
+ throw DataTrackHandleError.tooLarge();
27925
+ }
27926
+ return raw;
28159
27927
  }
28160
- checkReconnect() {
28161
- return __awaiter(this, void 0, void 0, function* () {
28162
- return this.createAndRunCheck(ReconnectCheck);
28163
- });
27928
+ };/** A class for serializing / deserializing data track packet header sections. */
27929
+ class DataTrackPacketHeader extends Serializable {
27930
+ constructor(opts) {
27931
+ var _a;
27932
+ super();
27933
+ this.marker = opts.marker;
27934
+ this.trackHandle = opts.trackHandle;
27935
+ this.sequence = opts.sequence;
27936
+ this.frameNumber = opts.frameNumber;
27937
+ this.timestamp = opts.timestamp;
27938
+ this.extensions = (_a = opts.extensions) !== null && _a !== void 0 ? _a : new DataTrackExtensions();
28164
27939
  }
28165
- checkPublishAudio() {
28166
- return __awaiter(this, void 0, void 0, function* () {
28167
- return this.createAndRunCheck(PublishAudioCheck);
28168
- });
27940
+ extensionsMetrics() {
27941
+ const lengthBytes = this.extensions.toBinaryLengthBytes();
27942
+ const lengthWords = Math.ceil(lengthBytes / 4);
27943
+ const paddingLengthBytes = lengthWords * 4 - lengthBytes;
27944
+ return {
27945
+ lengthBytes,
27946
+ lengthWords,
27947
+ paddingLengthBytes
27948
+ };
28169
27949
  }
28170
- checkPublishVideo() {
28171
- return __awaiter(this, void 0, void 0, function* () {
28172
- return this.createAndRunCheck(PublishVideoCheck);
28173
- });
27950
+ toBinaryLengthBytes() {
27951
+ const {
27952
+ lengthBytes: extLengthBytes,
27953
+ paddingLengthBytes: extPaddingLengthBytes
27954
+ } = this.extensionsMetrics();
27955
+ let totalLengthBytes = BASE_HEADER_LEN;
27956
+ if (extLengthBytes > 0) {
27957
+ totalLengthBytes += EXT_WORDS_INDICATOR_SIZE + extLengthBytes + extPaddingLengthBytes;
27958
+ }
27959
+ return totalLengthBytes;
28174
27960
  }
28175
- checkConnectionProtocol() {
28176
- return __awaiter(this, void 0, void 0, function* () {
28177
- const info = yield this.createAndRunCheck(ConnectionProtocolCheck);
28178
- if (info.data && 'protocol' in info.data) {
28179
- const stats = info.data;
28180
- this.options.protocol = stats.protocol;
27961
+ toBinaryInto(dataView) {
27962
+ if (dataView.byteLength < this.toBinaryLengthBytes()) {
27963
+ throw DataTrackSerializeError.tooSmallForHeader();
27964
+ }
27965
+ let initial = SUPPORTED_VERSION << VERSION_SHIFT;
27966
+ let marker;
27967
+ switch (this.marker) {
27968
+ case FrameMarker.Inter:
27969
+ marker = FRAME_MARKER_INTER;
27970
+ break;
27971
+ case FrameMarker.Final:
27972
+ marker = FRAME_MARKER_FINAL;
27973
+ break;
27974
+ case FrameMarker.Start:
27975
+ marker = FRAME_MARKER_START;
27976
+ break;
27977
+ case FrameMarker.Single:
27978
+ marker = FRAME_MARKER_SINGLE;
27979
+ break;
27980
+ }
27981
+ initial |= marker << FRAME_MARKER_SHIFT;
27982
+ const {
27983
+ lengthBytes: extensionsLengthBytes,
27984
+ lengthWords: extensionsLengthWords,
27985
+ paddingLengthBytes: extensionsPaddingLengthBytes
27986
+ } = this.extensionsMetrics();
27987
+ if (extensionsLengthBytes > 0) {
27988
+ initial |= 1 << EXT_FLAG_SHIFT;
27989
+ }
27990
+ let byteIndex = 0;
27991
+ dataView.setUint8(byteIndex, initial);
27992
+ byteIndex += U8_LENGTH_BYTES;
27993
+ dataView.setUint8(byteIndex, 0); // Reserved
27994
+ byteIndex += U8_LENGTH_BYTES;
27995
+ dataView.setUint16(byteIndex, this.trackHandle);
27996
+ byteIndex += U16_LENGTH_BYTES;
27997
+ dataView.setUint16(byteIndex, this.sequence.value);
27998
+ byteIndex += U16_LENGTH_BYTES;
27999
+ dataView.setUint16(byteIndex, this.frameNumber.value);
28000
+ byteIndex += U16_LENGTH_BYTES;
28001
+ dataView.setUint32(byteIndex, this.timestamp.asTicks());
28002
+ byteIndex += U32_LENGTH_BYTES;
28003
+ if (extensionsLengthBytes > 0) {
28004
+ // NOTE: The protocol is implemented in a way where if the extension bit is set, any
28005
+ // deserializer assumes the extensions section is at least one byte long, and the "length"
28006
+ // field represents the "number of additional bytes" long the extensions section is. This is
28007
+ // potentially unintuitive so I wanted to call it out.
28008
+ const rtpOrientedExtensionLengthWords = extensionsLengthWords - 1;
28009
+ dataView.setUint16(byteIndex, rtpOrientedExtensionLengthWords);
28010
+ byteIndex += U16_LENGTH_BYTES;
28011
+ const extensionBytes = this.extensions.toBinaryInto(new DataView(dataView.buffer, dataView.byteOffset + byteIndex));
28012
+ byteIndex += extensionBytes;
28013
+ for (let i = 0; i < extensionsPaddingLengthBytes; i += 1) {
28014
+ dataView.setUint8(byteIndex, 0);
28015
+ byteIndex += U8_LENGTH_BYTES;
28181
28016
  }
28182
- return info;
28183
- });
28184
- }
28185
- checkCloudRegion() {
28186
- return __awaiter(this, void 0, void 0, function* () {
28187
- return this.createAndRunCheck(CloudRegionCheck);
28188
- });
28017
+ }
28018
+ const totalLengthBytes = this.toBinaryLengthBytes();
28019
+ if (byteIndex !== totalLengthBytes) {
28020
+ // @throws-transformer ignore - this should be treated as a "panic" and not be caught
28021
+ throw new Error("DataTrackPacketHeader.toBinaryInto: Wrote ".concat(byteIndex, " bytes but expected length was ").concat(totalLengthBytes, " bytes"));
28022
+ }
28023
+ return totalLengthBytes;
28189
28024
  }
28190
- }/** A Fixed TokenSource is a token source that takes no parameters and returns a completely
28191
- * independently derived value on each fetch() call.
28192
- *
28193
- * The most common downstream implementer is {@link TokenSourceLiteral}.
28194
- */
28195
- class TokenSourceFixed {}
28196
- /** A Configurable TokenSource is a token source that takes a
28197
- * {@link TokenSourceFetchOptions} object as input and returns a deterministic
28198
- * {@link TokenSourceResponseObject} output based on the options specified.
28199
- *
28200
- * For example, if options.participantName is set, it should be expected that
28201
- * all tokens that are generated will have participant name field set to the
28202
- * provided value.
28203
- *
28204
- * A few common downstream implementers are {@link TokenSourceEndpoint}
28205
- * and {@link TokenSourceCustom}.
28206
- */
28207
- class TokenSourceConfigurable {}function _defineProperty(e, r, t) {
28208
- return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
28209
- value: t,
28210
- enumerable: true,
28211
- configurable: true,
28212
- writable: true
28213
- }) : e[r] = t, e;
28214
- }
28215
- function _toPrimitive(t, r) {
28216
- if ("object" != typeof t || !t) return t;
28217
- var e = t[Symbol.toPrimitive];
28218
- if (void 0 !== e) {
28219
- var i = e.call(t, r);
28220
- if ("object" != typeof i) return i;
28221
- throw new TypeError("@@toPrimitive must return a primitive value.");
28025
+ static fromBinary(input) {
28026
+ const dataView = coerceToDataView(input);
28027
+ if (dataView.byteLength < BASE_HEADER_LEN) {
28028
+ throw DataTrackDeserializeError.tooShort();
28029
+ }
28030
+ let byteIndex = 0;
28031
+ const initial = dataView.getUint8(byteIndex);
28032
+ byteIndex += U8_LENGTH_BYTES;
28033
+ const version = initial >> VERSION_SHIFT & VERSION_MASK;
28034
+ if (version > SUPPORTED_VERSION) {
28035
+ throw DataTrackDeserializeError.unsupportedVersion(version);
28036
+ }
28037
+ let marker;
28038
+ switch (initial >> FRAME_MARKER_SHIFT & FRAME_MARKER_MASK) {
28039
+ case FRAME_MARKER_START:
28040
+ marker = FrameMarker.Start;
28041
+ break;
28042
+ case FRAME_MARKER_FINAL:
28043
+ marker = FrameMarker.Final;
28044
+ break;
28045
+ case FRAME_MARKER_SINGLE:
28046
+ marker = FrameMarker.Single;
28047
+ break;
28048
+ case FRAME_MARKER_INTER:
28049
+ default:
28050
+ marker = FrameMarker.Inter;
28051
+ break;
28052
+ }
28053
+ const extensionsFlag = (initial >> EXT_FLAG_SHIFT & EXT_FLAG_MASK) > 0;
28054
+ byteIndex += U8_LENGTH_BYTES; // Reserved
28055
+ let trackHandle;
28056
+ try {
28057
+ trackHandle = DataTrackHandle.fromNumber(dataView.getUint16(byteIndex));
28058
+ } catch (e) {
28059
+ if (e instanceof DataTrackHandleError && (e.isReason(DataTrackHandleErrorReason.Reserved) || e.isReason(DataTrackHandleErrorReason.TooLarge))) {
28060
+ throw DataTrackDeserializeError.invalidHandle(e);
28061
+ } else {
28062
+ throw e;
28063
+ }
28064
+ }
28065
+ byteIndex += U16_LENGTH_BYTES;
28066
+ const sequence = WrapAroundUnsignedInt.u16(dataView.getUint16(byteIndex));
28067
+ byteIndex += U16_LENGTH_BYTES;
28068
+ const frameNumber = WrapAroundUnsignedInt.u16(dataView.getUint16(byteIndex));
28069
+ byteIndex += U16_LENGTH_BYTES;
28070
+ const timestamp = DataTrackTimestamp.fromRtpTicks(dataView.getUint32(byteIndex));
28071
+ byteIndex += U32_LENGTH_BYTES;
28072
+ let extensions = new DataTrackExtensions();
28073
+ if (extensionsFlag) {
28074
+ if (dataView.byteLength - byteIndex < U16_LENGTH_BYTES) {
28075
+ throw DataTrackDeserializeError.missingExtWords();
28076
+ }
28077
+ let rtpOrientedExtensionWords = dataView.getUint16(byteIndex);
28078
+ byteIndex += U16_LENGTH_BYTES;
28079
+ // NOTE: The protocol is implemented in a way where if the extension bit is set, any
28080
+ // deserializer assumes the extensions section is at least one byte long, and the "length"
28081
+ // field represents the "number of additional bytes" long the extensions section is. This is
28082
+ // potentially unintuitive so I wanted to call it out.
28083
+ const extensionWords = rtpOrientedExtensionWords + 1;
28084
+ let extensionLengthBytes = 4 * extensionWords;
28085
+ if (byteIndex + extensionLengthBytes > dataView.byteLength) {
28086
+ throw DataTrackDeserializeError.headerOverrun();
28087
+ }
28088
+ let extensionDataView = new DataView(dataView.buffer, dataView.byteOffset + byteIndex, extensionLengthBytes);
28089
+ const [result, readBytes] = DataTrackExtensions.fromBinary(extensionDataView);
28090
+ extensions = result;
28091
+ byteIndex += readBytes;
28092
+ }
28093
+ return [new DataTrackPacketHeader({
28094
+ marker,
28095
+ trackHandle: trackHandle,
28096
+ sequence,
28097
+ frameNumber,
28098
+ timestamp,
28099
+ extensions
28100
+ }), byteIndex];
28101
+ }
28102
+ toJSON() {
28103
+ return {
28104
+ marker: this.marker,
28105
+ trackHandle: this.trackHandle,
28106
+ sequence: this.sequence.value,
28107
+ frameNumber: this.frameNumber.value,
28108
+ timestamp: this.timestamp.asTicks(),
28109
+ extensions: this.extensions.toJSON()
28110
+ };
28222
28111
  }
28223
- return ("string" === r ? String : Number)(t);
28224
28112
  }
28225
- function _toPropertyKey(t) {
28226
- var i = _toPrimitive(t, "string");
28227
- return "symbol" == typeof i ? i : i + "";
28228
- }new TextEncoder();
28229
- const decoder = new TextDecoder();function decodeBase64(encoded) {
28230
- if (Uint8Array.fromBase64) {
28231
- return Uint8Array.fromBase64(encoded);
28113
+ /** Marker indicating a packet's position in relation to a frame. */
28114
+ var FrameMarker;
28115
+ (function (FrameMarker) {
28116
+ /** Packet is the first in a frame. */
28117
+ FrameMarker[FrameMarker["Start"] = 0] = "Start";
28118
+ /** Packet is within a frame. */
28119
+ FrameMarker[FrameMarker["Inter"] = 1] = "Inter";
28120
+ /** Packet is the last in a frame. */
28121
+ FrameMarker[FrameMarker["Final"] = 2] = "Final";
28122
+ /** Packet is the only one in a frame. */
28123
+ FrameMarker[FrameMarker["Single"] = 3] = "Single";
28124
+ })(FrameMarker || (FrameMarker = {}));
28125
+ /** A class for serializing / deserializing data track packets. */
28126
+ class DataTrackPacket extends Serializable {
28127
+ constructor(header, payload) {
28128
+ super();
28129
+ this.header = header;
28130
+ this.payload = payload;
28232
28131
  }
28233
- const binary = atob(encoded);
28234
- const bytes = new Uint8Array(binary.length);
28235
- for (let i = 0; i < binary.length; i++) {
28236
- bytes[i] = binary.charCodeAt(i);
28132
+ toBinaryLengthBytes() {
28133
+ return this.header.toBinaryLengthBytes() + this.payload.byteLength;
28237
28134
  }
28238
- return bytes;
28239
- }function decode(input) {
28240
- if (Uint8Array.fromBase64) {
28241
- return Uint8Array.fromBase64(typeof input === 'string' ? input : decoder.decode(input), {
28242
- alphabet: 'base64url'
28243
- });
28135
+ toBinaryInto(dataView) {
28136
+ let byteIndex = 0;
28137
+ const headerLengthBytes = this.header.toBinaryInto(dataView);
28138
+ byteIndex += headerLengthBytes;
28139
+ if (dataView.byteLength - byteIndex < this.payload.byteLength) {
28140
+ throw DataTrackSerializeError.tooSmallForPayload();
28141
+ }
28142
+ for (let index = 0; index < this.payload.length; index += 1) {
28143
+ dataView.setUint8(byteIndex, this.payload[index]);
28144
+ byteIndex += U8_LENGTH_BYTES;
28145
+ }
28146
+ const totalLengthBytes = this.toBinaryLengthBytes();
28147
+ if (byteIndex !== totalLengthBytes) {
28148
+ // @throws-transformer ignore - this should be treated as a "panic" and not be caught
28149
+ throw new Error("DataTrackPacket.toBinaryInto: Wrote ".concat(byteIndex, " bytes but expected length was ").concat(totalLengthBytes, " bytes"));
28150
+ }
28151
+ return totalLengthBytes;
28244
28152
  }
28245
- let encoded = input;
28246
- if (encoded instanceof Uint8Array) {
28247
- encoded = decoder.decode(encoded);
28153
+ static fromBinary(input) {
28154
+ const dataView = coerceToDataView(input);
28155
+ const [header, headerByteLength] = DataTrackPacketHeader.fromBinary(dataView);
28156
+ const payload = dataView.buffer.slice(dataView.byteOffset + headerByteLength, dataView.byteOffset + dataView.byteLength);
28157
+ return [new DataTrackPacket(header, new Uint8Array(payload)), dataView.byteLength];
28248
28158
  }
28249
- encoded = encoded.replace(/-/g, '+').replace(/_/g, '/');
28250
- try {
28251
- return decodeBase64(encoded);
28252
- } catch (_unused) {
28253
- throw new TypeError('The input to be decoded is not correctly encoded.');
28159
+ toJSON() {
28160
+ return {
28161
+ header: this.header.toJSON(),
28162
+ payload: this.payload
28163
+ };
28254
28164
  }
28255
- }class JOSEError extends Error {
28256
- constructor(message, options) {
28257
- var _Error$captureStackTr;
28258
- super(message, options);
28259
- _defineProperty(this, "code", 'ERR_JOSE_GENERIC');
28165
+ }var DataTrackPacketizerReason;
28166
+ (function (DataTrackPacketizerReason) {
28167
+ DataTrackPacketizerReason[DataTrackPacketizerReason["MtuTooShort"] = 0] = "MtuTooShort";
28168
+ })(DataTrackPacketizerReason || (DataTrackPacketizerReason = {}));getLogger(LoggerNames.DataTracks);
28169
+ /** Reason why a frame was dropped. */
28170
+ var DataTrackDepacketizerDropReason;
28171
+ (function (DataTrackDepacketizerDropReason) {
28172
+ DataTrackDepacketizerDropReason[DataTrackDepacketizerDropReason["Interrupted"] = 0] = "Interrupted";
28173
+ DataTrackDepacketizerDropReason[DataTrackDepacketizerDropReason["UnknownFrame"] = 1] = "UnknownFrame";
28174
+ DataTrackDepacketizerDropReason[DataTrackDepacketizerDropReason["BufferFull"] = 2] = "BufferFull";
28175
+ DataTrackDepacketizerDropReason[DataTrackDepacketizerDropReason["Incomplete"] = 3] = "Incomplete";
28176
+ })(DataTrackDepacketizerDropReason || (DataTrackDepacketizerDropReason = {}));var DataTrackPublishErrorReason;
28177
+ (function (DataTrackPublishErrorReason) {
28178
+ /**
28179
+ * Local participant does not have permission to publish data tracks.
28180
+ *
28181
+ * Ensure the participant's token contains the `canPublishData` grant.
28182
+ */
28183
+ DataTrackPublishErrorReason[DataTrackPublishErrorReason["NotAllowed"] = 0] = "NotAllowed";
28184
+ /** A track with the same name is already published by the local participant. */
28185
+ DataTrackPublishErrorReason[DataTrackPublishErrorReason["DuplicateName"] = 1] = "DuplicateName";
28186
+ /** Request to publish the track took long to complete. */
28187
+ DataTrackPublishErrorReason[DataTrackPublishErrorReason["Timeout"] = 2] = "Timeout";
28188
+ /** No additional data tracks can be published by the local participant. */
28189
+ DataTrackPublishErrorReason[DataTrackPublishErrorReason["LimitReached"] = 3] = "LimitReached";
28190
+ /** Cannot publish data track when the room is disconnected. */
28191
+ DataTrackPublishErrorReason[DataTrackPublishErrorReason["Disconnected"] = 4] = "Disconnected";
28192
+ // NOTE: this was introduced by web / there isn't a corresponding case in the rust version.
28193
+ DataTrackPublishErrorReason[DataTrackPublishErrorReason["Cancelled"] = 5] = "Cancelled";
28194
+ })(DataTrackPublishErrorReason || (DataTrackPublishErrorReason = {}));
28195
+ var DataTrackPushFrameErrorReason;
28196
+ (function (DataTrackPushFrameErrorReason) {
28197
+ /** Track is no longer published. */
28198
+ DataTrackPushFrameErrorReason[DataTrackPushFrameErrorReason["TrackUnpublished"] = 0] = "TrackUnpublished";
28199
+ /** Frame was dropped. */
28200
+ // NOTE: this should become a web specific error, the rust version of this "dropped" error means
28201
+ // something different and will be renamed to "QueueFull".
28202
+ DataTrackPushFrameErrorReason[DataTrackPushFrameErrorReason["Dropped"] = 1] = "Dropped";
28203
+ })(DataTrackPushFrameErrorReason || (DataTrackPushFrameErrorReason = {}));
28204
+ var DataTrackOutgoingPipelineErrorReason;
28205
+ (function (DataTrackOutgoingPipelineErrorReason) {
28206
+ DataTrackOutgoingPipelineErrorReason[DataTrackOutgoingPipelineErrorReason["Packetizer"] = 0] = "Packetizer";
28207
+ DataTrackOutgoingPipelineErrorReason[DataTrackOutgoingPipelineErrorReason["Encryption"] = 1] = "Encryption";
28208
+ })(DataTrackOutgoingPipelineErrorReason || (DataTrackOutgoingPipelineErrorReason = {}));getLogger(LoggerNames.DataTracks);var CheckStatus;
28209
+ (function (CheckStatus) {
28210
+ CheckStatus[CheckStatus["IDLE"] = 0] = "IDLE";
28211
+ CheckStatus[CheckStatus["RUNNING"] = 1] = "RUNNING";
28212
+ CheckStatus[CheckStatus["SKIPPED"] = 2] = "SKIPPED";
28213
+ CheckStatus[CheckStatus["SUCCESS"] = 3] = "SUCCESS";
28214
+ CheckStatus[CheckStatus["FAILED"] = 4] = "FAILED";
28215
+ })(CheckStatus || (CheckStatus = {}));
28216
+ class Checker extends eventsExports.EventEmitter {
28217
+ constructor(url, token) {
28218
+ let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
28219
+ super();
28220
+ this.status = CheckStatus.IDLE;
28221
+ this.logs = [];
28222
+ this.options = {};
28223
+ this.url = url;
28224
+ this.token = token;
28260
28225
  this.name = this.constructor.name;
28261
- (_Error$captureStackTr = Error.captureStackTrace) === null || _Error$captureStackTr === void 0 || _Error$captureStackTr.call(Error, this, this.constructor);
28226
+ this.room = new Room(options.roomOptions);
28227
+ this.connectOptions = options.connectOptions;
28228
+ this.options = options;
28262
28229
  }
28263
- }
28264
- _defineProperty(JOSEError, "code", 'ERR_JOSE_GENERIC');
28265
- class JWTClaimValidationFailed extends JOSEError {
28266
- constructor(message, payload) {
28267
- let claim = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'unspecified';
28268
- let reason = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'unspecified';
28269
- super(message, {
28270
- cause: {
28271
- claim,
28272
- reason,
28273
- payload
28230
+ run(onComplete) {
28231
+ return __awaiter(this, void 0, void 0, function* () {
28232
+ if (this.status !== CheckStatus.IDLE) {
28233
+ throw Error('check is running already');
28274
28234
  }
28275
- });
28276
- _defineProperty(this, "code", 'ERR_JWT_CLAIM_VALIDATION_FAILED');
28277
- _defineProperty(this, "claim", void 0);
28278
- _defineProperty(this, "reason", void 0);
28279
- _defineProperty(this, "payload", void 0);
28280
- this.claim = claim;
28281
- this.reason = reason;
28282
- this.payload = payload;
28283
- }
28284
- }
28285
- _defineProperty(JWTClaimValidationFailed, "code", 'ERR_JWT_CLAIM_VALIDATION_FAILED');
28286
- class JWTExpired extends JOSEError {
28287
- constructor(message, payload) {
28288
- let claim = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'unspecified';
28289
- let reason = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'unspecified';
28290
- super(message, {
28291
- cause: {
28292
- claim,
28293
- reason,
28294
- payload
28235
+ this.setStatus(CheckStatus.RUNNING);
28236
+ try {
28237
+ yield this.perform();
28238
+ } catch (err) {
28239
+ if (err instanceof Error) {
28240
+ if (this.options.errorsAsWarnings) {
28241
+ this.appendWarning(err.message);
28242
+ } else {
28243
+ this.appendError(err.message);
28244
+ }
28245
+ }
28246
+ }
28247
+ yield this.disconnect();
28248
+ // sleep for a bit to ensure disconnect
28249
+ yield new Promise(resolve => setTimeout(resolve, 500));
28250
+ // @ts-ignore
28251
+ if (this.status !== CheckStatus.SKIPPED) {
28252
+ this.setStatus(this.isSuccess() ? CheckStatus.SUCCESS : CheckStatus.FAILED);
28253
+ }
28254
+ if (onComplete) {
28255
+ onComplete();
28295
28256
  }
28257
+ return this.getInfo();
28296
28258
  });
28297
- _defineProperty(this, "code", 'ERR_JWT_EXPIRED');
28298
- _defineProperty(this, "claim", void 0);
28299
- _defineProperty(this, "reason", void 0);
28300
- _defineProperty(this, "payload", void 0);
28301
- this.claim = claim;
28302
- this.reason = reason;
28303
- this.payload = payload;
28304
- }
28305
- }
28306
- _defineProperty(JWTExpired, "code", 'ERR_JWT_EXPIRED');
28307
- class JOSEAlgNotAllowed extends JOSEError {
28308
- constructor() {
28309
- super(...arguments);
28310
- _defineProperty(this, "code", 'ERR_JOSE_ALG_NOT_ALLOWED');
28311
28259
  }
28312
- }
28313
- _defineProperty(JOSEAlgNotAllowed, "code", 'ERR_JOSE_ALG_NOT_ALLOWED');
28314
- class JOSENotSupported extends JOSEError {
28315
- constructor() {
28316
- super(...arguments);
28317
- _defineProperty(this, "code", 'ERR_JOSE_NOT_SUPPORTED');
28318
- }
28319
- }
28320
- _defineProperty(JOSENotSupported, "code", 'ERR_JOSE_NOT_SUPPORTED');
28321
- class JWEDecryptionFailed extends JOSEError {
28322
- constructor() {
28323
- let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'decryption operation failed';
28324
- let options = arguments.length > 1 ? arguments[1] : undefined;
28325
- super(message, options);
28326
- _defineProperty(this, "code", 'ERR_JWE_DECRYPTION_FAILED');
28260
+ isSuccess() {
28261
+ return !this.logs.some(l => l.level === 'error');
28327
28262
  }
28328
- }
28329
- _defineProperty(JWEDecryptionFailed, "code", 'ERR_JWE_DECRYPTION_FAILED');
28330
- class JWEInvalid extends JOSEError {
28331
- constructor() {
28332
- super(...arguments);
28333
- _defineProperty(this, "code", 'ERR_JWE_INVALID');
28263
+ connect(url) {
28264
+ return __awaiter(this, void 0, void 0, function* () {
28265
+ if (this.room.state === ConnectionState.Connected) {
28266
+ return this.room;
28267
+ }
28268
+ if (!url) {
28269
+ url = this.url;
28270
+ }
28271
+ yield this.room.connect(url, this.token, this.connectOptions);
28272
+ return this.room;
28273
+ });
28334
28274
  }
28335
- }
28336
- _defineProperty(JWEInvalid, "code", 'ERR_JWE_INVALID');
28337
- class JWSInvalid extends JOSEError {
28338
- constructor() {
28339
- super(...arguments);
28340
- _defineProperty(this, "code", 'ERR_JWS_INVALID');
28275
+ disconnect() {
28276
+ return __awaiter(this, void 0, void 0, function* () {
28277
+ if (this.room && this.room.state !== ConnectionState.Disconnected) {
28278
+ yield this.room.disconnect();
28279
+ // wait for it to go through
28280
+ yield new Promise(resolve => setTimeout(resolve, 500));
28281
+ }
28282
+ });
28341
28283
  }
28342
- }
28343
- _defineProperty(JWSInvalid, "code", 'ERR_JWS_INVALID');
28344
- class JWTInvalid extends JOSEError {
28345
- constructor() {
28346
- super(...arguments);
28347
- _defineProperty(this, "code", 'ERR_JWT_INVALID');
28284
+ skip() {
28285
+ this.setStatus(CheckStatus.SKIPPED);
28348
28286
  }
28349
- }
28350
- _defineProperty(JWTInvalid, "code", 'ERR_JWT_INVALID');
28351
- class JWKInvalid extends JOSEError {
28352
- constructor() {
28353
- super(...arguments);
28354
- _defineProperty(this, "code", 'ERR_JWK_INVALID');
28287
+ switchProtocol(protocol) {
28288
+ return __awaiter(this, void 0, void 0, function* () {
28289
+ let hasReconnecting = false;
28290
+ let hasReconnected = false;
28291
+ this.room.on(RoomEvent.Reconnecting, () => {
28292
+ hasReconnecting = true;
28293
+ });
28294
+ this.room.once(RoomEvent.Reconnected, () => {
28295
+ hasReconnected = true;
28296
+ });
28297
+ this.room.simulateScenario("force-".concat(protocol));
28298
+ yield new Promise(resolve => setTimeout(resolve, 1000));
28299
+ if (!hasReconnecting) {
28300
+ // no need to wait for reconnection
28301
+ return;
28302
+ }
28303
+ // wait for 10 seconds for reconnection
28304
+ const timeout = Date.now() + 10000;
28305
+ while (Date.now() < timeout) {
28306
+ if (hasReconnected) {
28307
+ return;
28308
+ }
28309
+ yield sleep(100);
28310
+ }
28311
+ throw new Error("Could not reconnect using ".concat(protocol, " protocol after 10 seconds"));
28312
+ });
28355
28313
  }
28356
- }
28357
- _defineProperty(JWKInvalid, "code", 'ERR_JWK_INVALID');
28358
- class JWKSInvalid extends JOSEError {
28359
- constructor() {
28360
- super(...arguments);
28361
- _defineProperty(this, "code", 'ERR_JWKS_INVALID');
28314
+ appendMessage(message) {
28315
+ this.logs.push({
28316
+ level: 'info',
28317
+ message
28318
+ });
28319
+ this.emit('update', this.getInfo());
28362
28320
  }
28363
- }
28364
- _defineProperty(JWKSInvalid, "code", 'ERR_JWKS_INVALID');
28365
- class JWKSNoMatchingKey extends JOSEError {
28366
- constructor() {
28367
- let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'no applicable key found in the JSON Web Key Set';
28368
- let options = arguments.length > 1 ? arguments[1] : undefined;
28369
- super(message, options);
28370
- _defineProperty(this, "code", 'ERR_JWKS_NO_MATCHING_KEY');
28321
+ appendWarning(message) {
28322
+ this.logs.push({
28323
+ level: 'warning',
28324
+ message
28325
+ });
28326
+ this.emit('update', this.getInfo());
28371
28327
  }
28372
- }
28373
- _defineProperty(JWKSNoMatchingKey, "code", 'ERR_JWKS_NO_MATCHING_KEY');
28374
- class JWKSMultipleMatchingKeys extends JOSEError {
28375
- constructor() {
28376
- let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'multiple matching keys found in the JSON Web Key Set';
28377
- let options = arguments.length > 1 ? arguments[1] : undefined;
28378
- super(message, options);
28379
- _defineProperty(this, Symbol.asyncIterator, void 0);
28380
- _defineProperty(this, "code", 'ERR_JWKS_MULTIPLE_MATCHING_KEYS');
28328
+ appendError(message) {
28329
+ this.logs.push({
28330
+ level: 'error',
28331
+ message
28332
+ });
28333
+ this.emit('update', this.getInfo());
28381
28334
  }
28382
- }
28383
- _defineProperty(JWKSMultipleMatchingKeys, "code", 'ERR_JWKS_MULTIPLE_MATCHING_KEYS');
28384
- class JWKSTimeout extends JOSEError {
28385
- constructor() {
28386
- let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'request timed out';
28387
- let options = arguments.length > 1 ? arguments[1] : undefined;
28388
- super(message, options);
28389
- _defineProperty(this, "code", 'ERR_JWKS_TIMEOUT');
28335
+ setStatus(status) {
28336
+ this.status = status;
28337
+ this.emit('update', this.getInfo());
28390
28338
  }
28391
- }
28392
- _defineProperty(JWKSTimeout, "code", 'ERR_JWKS_TIMEOUT');
28393
- class JWSSignatureVerificationFailed extends JOSEError {
28394
- constructor() {
28395
- let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'signature verification failed';
28396
- let options = arguments.length > 1 ? arguments[1] : undefined;
28397
- super(message, options);
28398
- _defineProperty(this, "code", 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED');
28339
+ get engine() {
28340
+ var _a;
28341
+ return (_a = this.room) === null || _a === void 0 ? void 0 : _a.engine;
28399
28342
  }
28400
- }
28401
- _defineProperty(JWSSignatureVerificationFailed, "code", 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED');const isObjectLike = value => typeof value === 'object' && value !== null;
28402
- function isObject(input) {
28403
- if (!isObjectLike(input) || Object.prototype.toString.call(input) !== '[object Object]') {
28404
- return false;
28343
+ getInfo() {
28344
+ return {
28345
+ logs: this.logs,
28346
+ name: this.name,
28347
+ status: this.status,
28348
+ description: this.description
28349
+ };
28405
28350
  }
28406
- if (Object.getPrototypeOf(input) === null) {
28407
- return true;
28351
+ }/**
28352
+ * Checks for connections quality to closests Cloud regions and determining the best quality
28353
+ */
28354
+ class CloudRegionCheck extends Checker {
28355
+ get description() {
28356
+ return 'Cloud regions';
28408
28357
  }
28409
- let proto = input;
28410
- while (Object.getPrototypeOf(proto) !== null) {
28411
- proto = Object.getPrototypeOf(proto);
28358
+ perform() {
28359
+ return __awaiter(this, void 0, void 0, function* () {
28360
+ const regionProvider = new RegionUrlProvider(this.url, this.token);
28361
+ if (!regionProvider.isCloud()) {
28362
+ this.skip();
28363
+ return;
28364
+ }
28365
+ const regionStats = [];
28366
+ const seenUrls = new Set();
28367
+ for (let i = 0; i < 3; i++) {
28368
+ const regionUrl = yield regionProvider.getNextBestRegionUrl();
28369
+ if (!regionUrl) {
28370
+ break;
28371
+ }
28372
+ if (seenUrls.has(regionUrl)) {
28373
+ continue;
28374
+ }
28375
+ seenUrls.add(regionUrl);
28376
+ const stats = yield this.checkCloudRegion(regionUrl);
28377
+ this.appendMessage("".concat(stats.region, " RTT: ").concat(stats.rtt, "ms, duration: ").concat(stats.duration, "ms"));
28378
+ regionStats.push(stats);
28379
+ }
28380
+ regionStats.sort((a, b) => {
28381
+ return (a.duration - b.duration) * 0.5 + (a.rtt - b.rtt) * 0.5;
28382
+ });
28383
+ const bestRegion = regionStats[0];
28384
+ this.bestStats = bestRegion;
28385
+ this.appendMessage("best Cloud region: ".concat(bestRegion.region));
28386
+ });
28412
28387
  }
28413
- return Object.getPrototypeOf(input) === proto;
28414
- }function decodeJwt(jwt) {
28415
- if (typeof jwt !== 'string') throw new JWTInvalid('JWTs must use Compact JWS serialization, JWT must be a string');
28416
- const {
28417
- 1: payload,
28418
- length
28419
- } = jwt.split('.');
28420
- if (length === 5) throw new JWTInvalid('Only JWTs using Compact JWS serialization can be decoded');
28421
- if (length !== 3) throw new JWTInvalid('Invalid JWT');
28422
- if (!payload) throw new JWTInvalid('JWTs must contain a payload');
28423
- let decoded;
28424
- try {
28425
- decoded = decode(payload);
28426
- } catch (_unused) {
28427
- throw new JWTInvalid('Failed to base64url decode the payload');
28388
+ getInfo() {
28389
+ const info = super.getInfo();
28390
+ info.data = this.bestStats;
28391
+ return info;
28428
28392
  }
28429
- let result;
28430
- try {
28431
- result = JSON.parse(decoder.decode(decoded));
28432
- } catch (_unused2) {
28433
- throw new JWTInvalid('Failed to parse the decoded payload as JSON');
28393
+ checkCloudRegion(url) {
28394
+ return __awaiter(this, void 0, void 0, function* () {
28395
+ var _a, _b;
28396
+ yield this.connect(url);
28397
+ if (this.options.protocol === 'tcp') {
28398
+ yield this.switchProtocol('tcp');
28399
+ }
28400
+ const region = (_a = this.room.serverInfo) === null || _a === void 0 ? void 0 : _a.region;
28401
+ if (!region) {
28402
+ throw new Error('Region not found');
28403
+ }
28404
+ const writer = yield this.room.localParticipant.streamText({
28405
+ topic: 'test'
28406
+ });
28407
+ const chunkSize = 1000; // each chunk is about 1000 bytes
28408
+ const totalSize = 1000000; // approximately 1MB of data
28409
+ const numChunks = totalSize / chunkSize; // will yield 1000 chunks
28410
+ const chunkData = 'A'.repeat(chunkSize); // create a string of 1000 'A' characters
28411
+ const startTime = Date.now();
28412
+ for (let i = 0; i < numChunks; i++) {
28413
+ yield writer.write(chunkData);
28414
+ }
28415
+ yield writer.close();
28416
+ const endTime = Date.now();
28417
+ const stats = yield (_b = this.room.engine.pcManager) === null || _b === void 0 ? void 0 : _b.publisher.getStats();
28418
+ const regionStats = {
28419
+ region: region,
28420
+ rtt: 10000,
28421
+ duration: endTime - startTime
28422
+ };
28423
+ stats === null || stats === void 0 ? void 0 : stats.forEach(stat => {
28424
+ if (stat.type === 'candidate-pair' && stat.nominated) {
28425
+ regionStats.rtt = stat.currentRoundTripTime * 1000;
28426
+ }
28427
+ });
28428
+ yield this.disconnect();
28429
+ return regionStats;
28430
+ });
28434
28431
  }
28435
- if (!isObject(result)) throw new JWTInvalid('Invalid JWT Claims Set');
28436
- return result;
28437
- }const ONE_SECOND_IN_MILLISECONDS = 1000;
28438
- const ONE_MINUTE_IN_MILLISECONDS = 60 * ONE_SECOND_IN_MILLISECONDS;
28439
- function isResponseTokenValid(response) {
28440
- const jwtPayload = decodeTokenPayload(response.participantToken);
28441
- if (!(jwtPayload === null || jwtPayload === void 0 ? void 0 : jwtPayload.nbf) || !(jwtPayload === null || jwtPayload === void 0 ? void 0 : jwtPayload.exp)) {
28442
- return true;
28432
+ }const TEST_DURATION = 10000;
28433
+ class ConnectionProtocolCheck extends Checker {
28434
+ get description() {
28435
+ return 'Connection via UDP vs TCP';
28443
28436
  }
28444
- const now = new Date();
28445
- const nbfInMilliseconds = jwtPayload.nbf * ONE_SECOND_IN_MILLISECONDS;
28446
- const nbfDate = new Date(nbfInMilliseconds);
28447
- const expInMilliseconds = jwtPayload.exp * ONE_SECOND_IN_MILLISECONDS;
28448
- const expDate = new Date(expInMilliseconds - ONE_MINUTE_IN_MILLISECONDS);
28449
- return nbfDate <= now && expDate > now;
28450
- }
28451
- /** Given a LiveKit generated participant token, decodes and returns the associated {@link TokenPayload} data. */
28452
- function decodeTokenPayload(token) {
28453
- const payload = decodeJwt(token);
28454
- const {
28455
- roomConfig
28456
- } = payload,
28457
- rest = __rest(payload, ["roomConfig"]);
28458
- const mappedPayload = Object.assign(Object.assign({}, rest), {
28459
- roomConfig: payload.roomConfig ? RoomConfiguration.fromJson(payload.roomConfig) : undefined
28460
- });
28461
- return mappedPayload;
28462
- }
28463
- /** Given two TokenSourceFetchOptions values, check to see if they are deep equal. */
28464
- function areTokenSourceFetchOptionsEqual(a, b) {
28465
- const allKeysSet = new Set([...Object.keys(a), ...Object.keys(b)]);
28466
- for (const key of allKeysSet) {
28467
- switch (key) {
28468
- case 'roomName':
28469
- case 'participantName':
28470
- case 'participantIdentity':
28471
- case 'participantMetadata':
28472
- case 'participantAttributes':
28473
- case 'agentName':
28474
- case 'agentMetadata':
28475
- if (a[key] !== b[key]) {
28476
- return false;
28477
- }
28478
- break;
28479
- default:
28480
- // ref: https://stackoverflow.com/a/58009992
28481
- const exhaustiveCheckedKey = key;
28482
- throw new Error("Options key ".concat(exhaustiveCheckedKey, " not being checked for equality!"));
28483
- }
28437
+ perform() {
28438
+ return __awaiter(this, void 0, void 0, function* () {
28439
+ const udpStats = yield this.checkConnectionProtocol('udp');
28440
+ const tcpStats = yield this.checkConnectionProtocol('tcp');
28441
+ this.bestStats = udpStats;
28442
+ // udp should is the better protocol typically. however, we'd prefer TCP when either of these conditions are true:
28443
+ // 1. the bandwidth limitation is worse on UDP by 500ms
28444
+ // 2. the packet loss is higher on UDP by 1%
28445
+ if (udpStats.qualityLimitationDurations.bandwidth - tcpStats.qualityLimitationDurations.bandwidth > 0.5 || (udpStats.packetsLost - tcpStats.packetsLost) / udpStats.packetsSent > 0.01) {
28446
+ this.appendMessage('best connection quality via tcp');
28447
+ this.bestStats = tcpStats;
28448
+ } else {
28449
+ this.appendMessage('best connection quality via udp');
28450
+ }
28451
+ const stats = this.bestStats;
28452
+ this.appendMessage("upstream bitrate: ".concat((stats.bitrateTotal / stats.count / 1000 / 1000).toFixed(2), " mbps"));
28453
+ this.appendMessage("RTT: ".concat((stats.rttTotal / stats.count * 1000).toFixed(2), " ms"));
28454
+ this.appendMessage("jitter: ".concat((stats.jitterTotal / stats.count * 1000).toFixed(2), " ms"));
28455
+ if (stats.packetsLost > 0) {
28456
+ this.appendWarning("packets lost: ".concat((stats.packetsLost / stats.packetsSent * 100).toFixed(2), "%"));
28457
+ }
28458
+ if (stats.qualityLimitationDurations.bandwidth > 1) {
28459
+ this.appendWarning("bandwidth limited ".concat((stats.qualityLimitationDurations.bandwidth / (TEST_DURATION / 1000) * 100).toFixed(2), "%"));
28460
+ }
28461
+ if (stats.qualityLimitationDurations.cpu > 0) {
28462
+ this.appendWarning("cpu limited ".concat((stats.qualityLimitationDurations.cpu / (TEST_DURATION / 1000) * 100).toFixed(2), "%"));
28463
+ }
28464
+ });
28484
28465
  }
28485
- return true;
28486
- }/** A TokenSourceCached is a TokenSource which caches the last {@link TokenSourceResponseObject} value and returns it
28487
- * until a) it expires or b) the {@link TokenSourceFetchOptions} provided to .fetch(...) change. */
28488
- class TokenSourceCached extends TokenSourceConfigurable {
28489
- constructor() {
28490
- super(...arguments);
28491
- this.cachedFetchOptions = null;
28492
- this.cachedResponse = null;
28493
- this.fetchMutex = new _();
28466
+ getInfo() {
28467
+ const info = super.getInfo();
28468
+ info.data = this.bestStats;
28469
+ return info;
28494
28470
  }
28495
- isSameAsCachedFetchOptions(options) {
28496
- if (!this.cachedFetchOptions) {
28497
- return false;
28498
- }
28499
- for (const key of Object.keys(this.cachedFetchOptions)) {
28500
- switch (key) {
28501
- case 'roomName':
28502
- case 'participantName':
28503
- case 'participantIdentity':
28504
- case 'participantMetadata':
28505
- case 'participantAttributes':
28506
- case 'agentName':
28507
- case 'agentMetadata':
28508
- if (this.cachedFetchOptions[key] !== options[key]) {
28509
- return false;
28510
- }
28511
- break;
28512
- default:
28513
- // ref: https://stackoverflow.com/a/58009992
28514
- const exhaustiveCheckedKey = key;
28515
- throw new Error("Options key ".concat(exhaustiveCheckedKey, " not being checked for equality!"));
28471
+ checkConnectionProtocol(protocol) {
28472
+ return __awaiter(this, void 0, void 0, function* () {
28473
+ yield this.connect();
28474
+ if (protocol === 'tcp') {
28475
+ yield this.switchProtocol('tcp');
28476
+ } else {
28477
+ yield this.switchProtocol('udp');
28516
28478
  }
28517
- }
28518
- return true;
28479
+ // create a canvas with animated content
28480
+ const canvas = document.createElement('canvas');
28481
+ canvas.width = 1280;
28482
+ canvas.height = 720;
28483
+ const ctx = canvas.getContext('2d');
28484
+ if (!ctx) {
28485
+ throw new Error('Could not get canvas context');
28486
+ }
28487
+ let hue = 0;
28488
+ const animate = () => {
28489
+ hue = (hue + 1) % 360;
28490
+ ctx.fillStyle = "hsl(".concat(hue, ", 100%, 50%)");
28491
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
28492
+ requestAnimationFrame(animate);
28493
+ };
28494
+ animate();
28495
+ // create video track from canvas
28496
+ const stream = canvas.captureStream(30); // 30fps
28497
+ const videoTrack = stream.getVideoTracks()[0];
28498
+ // publish to room
28499
+ const pub = yield this.room.localParticipant.publishTrack(videoTrack, {
28500
+ simulcast: false,
28501
+ degradationPreference: 'maintain-resolution',
28502
+ videoEncoding: {
28503
+ maxBitrate: 2000000
28504
+ }
28505
+ });
28506
+ const track = pub.track;
28507
+ const protocolStats = {
28508
+ protocol,
28509
+ packetsLost: 0,
28510
+ packetsSent: 0,
28511
+ qualityLimitationDurations: {},
28512
+ rttTotal: 0,
28513
+ jitterTotal: 0,
28514
+ bitrateTotal: 0,
28515
+ count: 0
28516
+ };
28517
+ // gather stats once a second
28518
+ const interval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
28519
+ const stats = yield track.getRTCStatsReport();
28520
+ stats === null || stats === void 0 ? void 0 : stats.forEach(stat => {
28521
+ if (stat.type === 'outbound-rtp') {
28522
+ protocolStats.packetsSent = stat.packetsSent;
28523
+ protocolStats.qualityLimitationDurations = stat.qualityLimitationDurations;
28524
+ protocolStats.bitrateTotal += stat.targetBitrate;
28525
+ protocolStats.count++;
28526
+ } else if (stat.type === 'remote-inbound-rtp') {
28527
+ protocolStats.packetsLost = stat.packetsLost;
28528
+ protocolStats.rttTotal += stat.roundTripTime;
28529
+ protocolStats.jitterTotal += stat.jitter;
28530
+ }
28531
+ });
28532
+ }), 1000);
28533
+ // wait a bit to gather stats
28534
+ yield new Promise(resolve => setTimeout(resolve, TEST_DURATION));
28535
+ clearInterval(interval);
28536
+ videoTrack.stop();
28537
+ canvas.remove();
28538
+ yield this.disconnect();
28539
+ return protocolStats;
28540
+ });
28519
28541
  }
28520
- shouldReturnCachedValueFromFetch(fetchOptions) {
28521
- if (!this.cachedResponse) {
28522
- return false;
28523
- }
28524
- if (!isResponseTokenValid(this.cachedResponse)) {
28525
- return false;
28526
- }
28527
- if (this.isSameAsCachedFetchOptions(fetchOptions)) {
28528
- return false;
28529
- }
28530
- return true;
28542
+ }class PublishAudioCheck extends Checker {
28543
+ get description() {
28544
+ return 'Can publish audio';
28531
28545
  }
28532
- getCachedResponseJwtPayload() {
28533
- if (!this.cachedResponse) {
28534
- return null;
28535
- }
28536
- return decodeTokenPayload(this.cachedResponse.participantToken);
28546
+ perform() {
28547
+ return __awaiter(this, void 0, void 0, function* () {
28548
+ var _a;
28549
+ const room = yield this.connect();
28550
+ const track = yield createLocalAudioTrack();
28551
+ const trackIsSilent = yield detectSilence(track, 1000);
28552
+ if (trackIsSilent) {
28553
+ throw new Error('unable to detect audio from microphone');
28554
+ }
28555
+ this.appendMessage('detected audio from microphone');
28556
+ room.localParticipant.publishTrack(track);
28557
+ // wait for a few seconds to publish
28558
+ yield new Promise(resolve => setTimeout(resolve, 3000));
28559
+ // verify RTC stats that it's publishing
28560
+ const stats = yield (_a = track.sender) === null || _a === void 0 ? void 0 : _a.getStats();
28561
+ if (!stats) {
28562
+ throw new Error('Could not get RTCStats');
28563
+ }
28564
+ let numPackets = 0;
28565
+ stats.forEach(stat => {
28566
+ if (stat.type === 'outbound-rtp' && (stat.kind === 'audio' || !stat.kind && stat.mediaType === 'audio')) {
28567
+ numPackets = stat.packetsSent;
28568
+ }
28569
+ });
28570
+ if (numPackets === 0) {
28571
+ throw new Error('Could not determine packets are sent');
28572
+ }
28573
+ this.appendMessage("published ".concat(numPackets, " audio packets"));
28574
+ });
28537
28575
  }
28538
- fetch(options) {
28576
+ }class PublishVideoCheck extends Checker {
28577
+ get description() {
28578
+ return 'Can publish video';
28579
+ }
28580
+ perform() {
28539
28581
  return __awaiter(this, void 0, void 0, function* () {
28540
- const unlock = yield this.fetchMutex.lock();
28541
- try {
28542
- if (this.shouldReturnCachedValueFromFetch(options)) {
28543
- return this.cachedResponse.toJson();
28582
+ var _a;
28583
+ const room = yield this.connect();
28584
+ const track = yield createLocalVideoTrack();
28585
+ // check if we have video from camera
28586
+ yield this.checkForVideo(track.mediaStreamTrack);
28587
+ room.localParticipant.publishTrack(track);
28588
+ // wait for a few seconds to publish
28589
+ yield new Promise(resolve => setTimeout(resolve, 5000));
28590
+ // verify RTC stats that it's publishing
28591
+ const stats = yield (_a = track.sender) === null || _a === void 0 ? void 0 : _a.getStats();
28592
+ if (!stats) {
28593
+ throw new Error('Could not get RTCStats');
28594
+ }
28595
+ let numPackets = 0;
28596
+ stats.forEach(stat => {
28597
+ if (stat.type === 'outbound-rtp' && (stat.kind === 'video' || !stat.kind && stat.mediaType === 'video')) {
28598
+ numPackets += stat.packetsSent;
28544
28599
  }
28545
- this.cachedFetchOptions = options;
28546
- const tokenResponse = yield this.update(options);
28547
- this.cachedResponse = tokenResponse;
28548
- return tokenResponse.toJson();
28549
- } finally {
28550
- unlock();
28600
+ });
28601
+ if (numPackets === 0) {
28602
+ throw new Error('Could not determine packets are sent');
28551
28603
  }
28604
+ this.appendMessage("published ".concat(numPackets, " video packets"));
28605
+ });
28606
+ }
28607
+ checkForVideo(track) {
28608
+ return __awaiter(this, void 0, void 0, function* () {
28609
+ const stream = new MediaStream();
28610
+ stream.addTrack(track.clone());
28611
+ // Create video element to check frames
28612
+ const video = document.createElement('video');
28613
+ video.srcObject = stream;
28614
+ video.muted = true;
28615
+ video.autoplay = true;
28616
+ video.playsInline = true;
28617
+ // For iOS Safari
28618
+ video.setAttribute('playsinline', 'true');
28619
+ document.body.appendChild(video);
28620
+ yield new Promise(resolve => {
28621
+ video.onplay = () => {
28622
+ setTimeout(() => {
28623
+ var _a, _b, _c, _d;
28624
+ const canvas = document.createElement('canvas');
28625
+ const settings = track.getSettings();
28626
+ const width = (_b = (_a = settings.width) !== null && _a !== void 0 ? _a : video.videoWidth) !== null && _b !== void 0 ? _b : 1280;
28627
+ const height = (_d = (_c = settings.height) !== null && _c !== void 0 ? _c : video.videoHeight) !== null && _d !== void 0 ? _d : 720;
28628
+ canvas.width = width;
28629
+ canvas.height = height;
28630
+ const ctx = canvas.getContext('2d');
28631
+ // Draw video frame to canvas
28632
+ ctx.drawImage(video, 0, 0);
28633
+ // Get image data and check if all pixels are black
28634
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
28635
+ const data = imageData.data;
28636
+ let isAllBlack = true;
28637
+ for (let i = 0; i < data.length; i += 4) {
28638
+ if (data[i] !== 0 || data[i + 1] !== 0 || data[i + 2] !== 0) {
28639
+ isAllBlack = false;
28640
+ break;
28641
+ }
28642
+ }
28643
+ if (isAllBlack) {
28644
+ this.appendError('camera appears to be producing only black frames');
28645
+ } else {
28646
+ this.appendMessage('received video frames');
28647
+ }
28648
+ resolve();
28649
+ }, 1000);
28650
+ };
28651
+ video.play();
28652
+ });
28653
+ stream.getTracks().forEach(t => t.stop());
28654
+ video.remove();
28552
28655
  });
28553
28656
  }
28554
- }
28555
- class TokenSourceLiteral extends TokenSourceFixed {
28556
- constructor(literalOrFn) {
28557
- super();
28558
- this.literalOrFn = literalOrFn;
28657
+ }class ReconnectCheck extends Checker {
28658
+ get description() {
28659
+ return 'Resuming connection after interruption';
28559
28660
  }
28560
- fetch() {
28661
+ perform() {
28561
28662
  return __awaiter(this, void 0, void 0, function* () {
28562
- if (typeof this.literalOrFn === 'function') {
28563
- return this.literalOrFn();
28564
- } else {
28565
- return this.literalOrFn;
28663
+ var _a;
28664
+ const room = yield this.connect();
28665
+ let reconnectingTriggered = false;
28666
+ let reconnected = false;
28667
+ let reconnectResolver;
28668
+ const reconnectTimeout = new Promise(resolve => {
28669
+ setTimeout(resolve, 5000);
28670
+ reconnectResolver = resolve;
28671
+ });
28672
+ const handleReconnecting = () => {
28673
+ reconnectingTriggered = true;
28674
+ };
28675
+ room.on(RoomEvent.SignalReconnecting, handleReconnecting).on(RoomEvent.Reconnecting, handleReconnecting).on(RoomEvent.Reconnected, () => {
28676
+ reconnected = true;
28677
+ reconnectResolver(true);
28678
+ });
28679
+ (_a = room.engine.client.ws) === null || _a === void 0 ? void 0 : _a.close();
28680
+ const onClose = room.engine.client.onClose;
28681
+ if (onClose) {
28682
+ onClose('');
28683
+ }
28684
+ yield reconnectTimeout;
28685
+ if (!reconnectingTriggered) {
28686
+ throw new Error('Did not attempt to reconnect');
28687
+ } else if (!reconnected || room.state !== ConnectionState.Connected) {
28688
+ this.appendWarning('reconnection is only possible in Redis-based configurations');
28689
+ throw new Error('Not able to reconnect');
28566
28690
  }
28567
28691
  });
28568
28692
  }
28569
- }
28570
- class TokenSourceCustom extends TokenSourceCached {
28571
- constructor(customFn) {
28572
- super();
28573
- this.customFn = customFn;
28693
+ }class TURNCheck extends Checker {
28694
+ get description() {
28695
+ return 'Can connect via TURN';
28574
28696
  }
28575
- update(options) {
28697
+ perform() {
28576
28698
  return __awaiter(this, void 0, void 0, function* () {
28577
- const resultMaybePromise = this.customFn(options);
28578
- let result;
28579
- if (resultMaybePromise instanceof Promise) {
28580
- result = yield resultMaybePromise;
28581
- } else {
28582
- result = resultMaybePromise;
28699
+ var _a, _b, _c;
28700
+ if (isCloud(new URL(this.url))) {
28701
+ this.appendMessage('Using region specific url');
28702
+ this.url = (_a = yield new RegionUrlProvider(this.url, this.token).getNextBestRegionUrl()) !== null && _a !== void 0 ? _a : this.url;
28583
28703
  }
28584
- return TokenSourceResponse.fromJson(result, {
28585
- // NOTE: it could be possible that the response body could contain more fields than just
28586
- // what's in TokenSourceResponse depending on the implementation
28587
- ignoreUnknownFields: true
28588
- });
28589
- });
28590
- }
28591
- }
28592
- class TokenSourceEndpoint extends TokenSourceCached {
28593
- constructor(url) {
28594
- let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
28595
- super();
28596
- this.url = url;
28597
- this.endpointOptions = options;
28598
- }
28599
- createRequestFromOptions(options) {
28600
- var _a, _b, _c;
28601
- const request = new TokenSourceRequest();
28602
- for (const key of Object.keys(options)) {
28603
- switch (key) {
28604
- case 'roomName':
28605
- case 'participantName':
28606
- case 'participantIdentity':
28607
- case 'participantMetadata':
28608
- request[key] = options[key];
28609
- break;
28610
- case 'participantAttributes':
28611
- request.participantAttributes = (_a = options.participantAttributes) !== null && _a !== void 0 ? _a : {};
28612
- break;
28613
- case 'agentName':
28614
- request.roomConfig = (_b = request.roomConfig) !== null && _b !== void 0 ? _b : new RoomConfiguration();
28615
- if (request.roomConfig.agents.length === 0) {
28616
- request.roomConfig.agents.push(new RoomAgentDispatch());
28704
+ const signalClient = new SignalClient();
28705
+ const joinRes = yield signalClient.join(this.url, this.token, {
28706
+ autoSubscribe: true,
28707
+ maxRetries: 0,
28708
+ e2eeEnabled: false,
28709
+ websocketTimeout: 15000
28710
+ }, undefined, true);
28711
+ let hasTLS = false;
28712
+ let hasTURN = false;
28713
+ let hasSTUN = false;
28714
+ for (let iceServer of joinRes.iceServers) {
28715
+ for (let url of iceServer.urls) {
28716
+ if (url.startsWith('turn:')) {
28717
+ hasTURN = true;
28718
+ hasSTUN = true;
28719
+ } else if (url.startsWith('turns:')) {
28720
+ hasTURN = true;
28721
+ hasSTUN = true;
28722
+ hasTLS = true;
28617
28723
  }
28618
- request.roomConfig.agents[0].agentName = options.agentName;
28619
- break;
28620
- case 'agentMetadata':
28621
- request.roomConfig = (_c = request.roomConfig) !== null && _c !== void 0 ? _c : new RoomConfiguration();
28622
- if (request.roomConfig.agents.length === 0) {
28623
- request.roomConfig.agents.push(new RoomAgentDispatch());
28724
+ if (url.startsWith('stun:')) {
28725
+ hasSTUN = true;
28624
28726
  }
28625
- request.roomConfig.agents[0].metadata = options.agentMetadata;
28626
- break;
28627
- default:
28628
- // ref: https://stackoverflow.com/a/58009992
28629
- const exhaustiveCheckedKey = key;
28630
- throw new Error("Options key ".concat(exhaustiveCheckedKey, " not being included in forming request!"));
28727
+ }
28631
28728
  }
28632
- }
28633
- return request;
28634
- }
28635
- update(options) {
28636
- return __awaiter(this, void 0, void 0, function* () {
28637
- var _a;
28638
- const request = this.createRequestFromOptions(options);
28639
- const response = yield fetch(this.url, Object.assign(Object.assign({}, this.endpointOptions), {
28640
- method: (_a = this.endpointOptions.method) !== null && _a !== void 0 ? _a : 'POST',
28641
- headers: Object.assign({
28642
- 'Content-Type': 'application/json'
28643
- }, this.endpointOptions.headers),
28644
- body: request.toJsonString({
28645
- useProtoFieldName: true
28646
- })
28647
- }));
28648
- if (!response.ok) {
28649
- throw new Error("Error generating token from endpoint ".concat(this.url, ": received ").concat(response.status, " / ").concat(yield response.text()));
28729
+ if (!hasSTUN) {
28730
+ this.appendWarning('No STUN servers configured on server side.');
28731
+ } else if (hasTURN && !hasTLS) {
28732
+ this.appendWarning('TURN is configured server side, but TURN/TLS is unavailable.');
28650
28733
  }
28651
- const body = yield response.json();
28652
- return TokenSourceResponse.fromJson(body, {
28653
- // NOTE: it could be possible that the response body could contain more fields than just
28654
- // what's in TokenSourceResponse depending on the implementation (ie, SandboxTokenServer)
28655
- ignoreUnknownFields: true
28656
- });
28657
- });
28658
- }
28659
- }
28660
- class TokenSourceSandboxTokenServer extends TokenSourceEndpoint {
28661
- constructor(sandboxId, options) {
28662
- const {
28663
- baseUrl = 'https://cloud-api.livekit.io'
28664
- } = options,
28665
- rest = __rest(options, ["baseUrl"]);
28666
- super("".concat(baseUrl, "/api/v2/sandbox/connection-details"), Object.assign(Object.assign({}, rest), {
28667
- headers: {
28668
- 'X-Sandbox-ID': sandboxId
28734
+ yield signalClient.close();
28735
+ if (((_c = (_b = this.connectOptions) === null || _b === void 0 ? void 0 : _b.rtcConfig) === null || _c === void 0 ? void 0 : _c.iceServers) || hasTURN) {
28736
+ yield this.room.connect(this.url, this.token, {
28737
+ rtcConfig: {
28738
+ iceTransportPolicy: 'relay'
28739
+ }
28740
+ });
28741
+ } else {
28742
+ this.appendWarning('No TURN servers configured.');
28743
+ this.skip();
28744
+ yield new Promise(resolve => setTimeout(resolve, 0));
28669
28745
  }
28670
- }));
28746
+ });
28671
28747
  }
28672
- }
28673
- const TokenSource = {
28674
- /** TokenSource.literal contains a single, literal set of {@link TokenSourceResponseObject}
28675
- * credentials, either provided directly or returned from a provided function. */
28676
- literal(literalOrFn) {
28677
- return new TokenSourceLiteral(literalOrFn);
28678
- },
28679
- /**
28680
- * TokenSource.custom allows a user to define a manual function which generates new
28681
- * {@link TokenSourceResponseObject} values on demand.
28682
- *
28683
- * Use this to get credentials from custom backends / etc.
28684
- */
28685
- custom(customFn) {
28686
- return new TokenSourceCustom(customFn);
28687
- },
28688
- /**
28689
- * TokenSource.endpoint creates a token source that fetches credentials from a given URL using
28690
- * the standard endpoint format:
28691
- * @see https://cloud.livekit.io/projects/p_/sandbox/templates/token-server
28692
- */
28693
- endpoint(url) {
28694
- let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
28695
- return new TokenSourceEndpoint(url, options);
28696
- },
28697
- /**
28698
- * TokenSource.sandboxTokenServer queries a sandbox token server for credentials,
28699
- * which supports quick prototyping / getting started types of use cases.
28700
- *
28701
- * This token provider is INSECURE and should NOT be used in production.
28702
- *
28703
- * For more info:
28704
- * @see https://cloud.livekit.io/projects/p_/sandbox/templates/token-server
28705
- */
28706
- sandboxTokenServer(sandboxId) {
28707
- let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
28708
- return new TokenSourceSandboxTokenServer(sandboxId, options);
28748
+ }class WebRTCCheck extends Checker {
28749
+ get description() {
28750
+ return 'Establishing WebRTC connection';
28709
28751
  }
28710
- };/**
28711
- * Try to analyze the local track to determine the facing mode of a track.
28712
- *
28713
- * @remarks
28714
- * There is no property supported by all browsers to detect whether a video track originated from a user- or environment-facing camera device.
28715
- * For this reason, we use the `facingMode` property when available, but will fall back on a string-based analysis of the device label to determine the facing mode.
28716
- * If both methods fail, the default facing mode will be used.
28717
- *
28718
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/facingMode | MDN docs on facingMode}
28719
- * @experimental
28720
- */
28721
- function facingModeFromLocalTrack(localTrack) {
28722
- let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
28723
- var _a;
28724
- const track = isLocalTrack(localTrack) ? localTrack.mediaStreamTrack : localTrack;
28725
- const trackSettings = track.getSettings();
28726
- let result = {
28727
- facingMode: (_a = options.defaultFacingMode) !== null && _a !== void 0 ? _a : 'user',
28728
- confidence: 'low'
28729
- };
28730
- // 1. Try to get facingMode from track settings.
28731
- if ('facingMode' in trackSettings) {
28732
- const rawFacingMode = trackSettings.facingMode;
28733
- livekitLogger.trace('rawFacingMode', {
28734
- rawFacingMode
28752
+ perform() {
28753
+ return __awaiter(this, void 0, void 0, function* () {
28754
+ let hasTcp = false;
28755
+ let hasIpv4Udp = false;
28756
+ this.room.on(RoomEvent.SignalConnected, () => {
28757
+ var _a;
28758
+ const prevTrickle = this.room.engine.client.onTrickle;
28759
+ this.room.engine.client.onTrickle = (sd, target) => {
28760
+ if (sd.candidate) {
28761
+ const candidate = new RTCIceCandidate(sd);
28762
+ let str = "".concat(candidate.protocol, " ").concat(candidate.address, ":").concat(candidate.port, " ").concat(candidate.type);
28763
+ if (candidate.address) {
28764
+ if (isIPPrivate(candidate.address)) {
28765
+ str += ' (private)';
28766
+ } else {
28767
+ if (candidate.protocol === 'tcp' && candidate.tcpType === 'passive') {
28768
+ hasTcp = true;
28769
+ str += ' (passive)';
28770
+ } else if (candidate.protocol === 'udp') {
28771
+ hasIpv4Udp = true;
28772
+ }
28773
+ }
28774
+ }
28775
+ this.appendMessage(str);
28776
+ }
28777
+ if (prevTrickle) {
28778
+ prevTrickle(sd, target);
28779
+ }
28780
+ };
28781
+ if ((_a = this.room.engine.pcManager) === null || _a === void 0 ? void 0 : _a.subscriber) {
28782
+ this.room.engine.pcManager.subscriber.onIceCandidateError = ev => {
28783
+ if (ev instanceof RTCPeerConnectionIceErrorEvent) {
28784
+ this.appendWarning("error with ICE candidate: ".concat(ev.errorCode, " ").concat(ev.errorText, " ").concat(ev.url));
28785
+ }
28786
+ };
28787
+ }
28788
+ });
28789
+ try {
28790
+ yield this.connect();
28791
+ livekitLogger.info('now the room is connected');
28792
+ } catch (err) {
28793
+ this.appendWarning('ports need to be open on firewall in order to connect.');
28794
+ throw err;
28795
+ }
28796
+ if (!hasTcp) {
28797
+ this.appendWarning('Server is not configured for ICE/TCP');
28798
+ }
28799
+ if (!hasIpv4Udp) {
28800
+ this.appendWarning('No public IPv4 UDP candidates were found. Your server is likely not configured correctly');
28801
+ }
28735
28802
  });
28736
- if (rawFacingMode && typeof rawFacingMode === 'string' && isFacingModeValue(rawFacingMode)) {
28737
- result = {
28738
- facingMode: rawFacingMode,
28739
- confidence: 'high'
28740
- };
28741
- }
28742
28803
  }
28743
- // 2. If we don't have a high confidence we try to get the facing mode from the device label.
28744
- if (['low', 'medium'].includes(result.confidence)) {
28745
- livekitLogger.trace("Try to get facing mode from device label: (".concat(track.label, ")"));
28746
- const labelAnalysisResult = facingModeFromDeviceLabel(track.label);
28747
- if (labelAnalysisResult !== undefined) {
28748
- result = labelAnalysisResult;
28804
+ }
28805
+ function isIPPrivate(address) {
28806
+ const parts = address.split('.');
28807
+ if (parts.length === 4) {
28808
+ if (parts[0] === '10') {
28809
+ return true;
28810
+ } else if (parts[0] === '192' && parts[1] === '168') {
28811
+ return true;
28812
+ } else if (parts[0] === '172') {
28813
+ const second = parseInt(parts[1], 10);
28814
+ if (second >= 16 && second <= 31) {
28815
+ return true;
28816
+ }
28749
28817
  }
28750
28818
  }
28751
- return result;
28752
- }
28753
- const knownDeviceLabels = new Map([['obs virtual camera', {
28754
- facingMode: 'environment',
28755
- confidence: 'medium'
28756
- }]]);
28757
- const knownDeviceLabelSections = new Map([['iphone', {
28758
- facingMode: 'environment',
28759
- confidence: 'medium'
28760
- }], ['ipad', {
28761
- facingMode: 'environment',
28762
- confidence: 'medium'
28763
- }]]);
28764
- /**
28765
- * Attempt to analyze the device label to determine the facing mode.
28766
- *
28767
- * @experimental
28768
- */
28769
- function facingModeFromDeviceLabel(deviceLabel) {
28770
- var _a;
28771
- const label = deviceLabel.trim().toLowerCase();
28772
- // Empty string is a valid device label but we can't infer anything from it.
28773
- if (label === '') {
28774
- return undefined;
28819
+ return false;
28820
+ }class WebSocketCheck extends Checker {
28821
+ get description() {
28822
+ return 'Connecting to signal connection via WebSocket';
28775
28823
  }
28776
- // Can we match against widely known device labels.
28777
- if (knownDeviceLabels.has(label)) {
28778
- return knownDeviceLabels.get(label);
28824
+ perform() {
28825
+ return __awaiter(this, void 0, void 0, function* () {
28826
+ var _a, _b, _c;
28827
+ if (this.url.startsWith('ws:') || this.url.startsWith('http:')) {
28828
+ this.appendWarning('Server is insecure, clients may block connections to it');
28829
+ }
28830
+ let signalClient = new SignalClient();
28831
+ let joinRes;
28832
+ try {
28833
+ joinRes = yield signalClient.join(this.url, this.token, {
28834
+ autoSubscribe: true,
28835
+ maxRetries: 0,
28836
+ e2eeEnabled: false,
28837
+ websocketTimeout: 15000
28838
+ }, undefined, true);
28839
+ } catch (e) {
28840
+ if (isCloud(new URL(this.url))) {
28841
+ this.appendMessage("Initial connection failed with error ".concat(e.message, ". Retrying with region fallback"));
28842
+ const regionProvider = new RegionUrlProvider(this.url, this.token);
28843
+ const regionUrl = yield regionProvider.getNextBestRegionUrl();
28844
+ if (regionUrl) {
28845
+ joinRes = yield signalClient.join(regionUrl, this.token, {
28846
+ autoSubscribe: true,
28847
+ maxRetries: 0,
28848
+ e2eeEnabled: false,
28849
+ websocketTimeout: 15000
28850
+ }, undefined, true);
28851
+ this.appendMessage("Fallback to region worked. To avoid initial connections failing, ensure you're calling room.prepareConnection() ahead of time");
28852
+ }
28853
+ }
28854
+ }
28855
+ if (joinRes) {
28856
+ this.appendMessage("Connected to server, version ".concat(joinRes.serverVersion, "."));
28857
+ if (((_a = joinRes.serverInfo) === null || _a === void 0 ? void 0 : _a.edition) === ServerInfo_Edition.Cloud && ((_b = joinRes.serverInfo) === null || _b === void 0 ? void 0 : _b.region)) {
28858
+ this.appendMessage("LiveKit Cloud: ".concat((_c = joinRes.serverInfo) === null || _c === void 0 ? void 0 : _c.region));
28859
+ }
28860
+ } else {
28861
+ this.appendError("Websocket connection could not be established");
28862
+ }
28863
+ yield signalClient.close();
28864
+ });
28779
28865
  }
28780
- // Can we match against sections of the device label.
28781
- return (_a = Array.from(knownDeviceLabelSections.entries()).find(_ref => {
28782
- let [section] = _ref;
28783
- return label.includes(section);
28784
- })) === null || _a === void 0 ? void 0 : _a[1];
28785
- }
28786
- function isFacingModeValue(item) {
28787
- const allowedValues = ['user', 'environment', 'left', 'right'];
28788
- return item === undefined || allowedValues.includes(item);
28789
- }const U16_MAX_SIZE = 0xffff;
28790
- /**
28791
- * A number of fields withing the data tracks packet specification assume wrap around behavior when
28792
- * an unsigned type is incremented beyond its max size (ie, the packet `sequence` field). This
28793
- * wrapper type manually reimplements this wrap around behavior given javascript's lack of fixed
28794
- * size integer types.
28795
- */
28796
- class WrapAroundUnsignedInt {
28797
- static u16(raw) {
28798
- return new WrapAroundUnsignedInt(raw, U16_MAX_SIZE);
28866
+ }class ConnectionCheck extends eventsExports.EventEmitter {
28867
+ constructor(url, token) {
28868
+ let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
28869
+ super();
28870
+ this.options = {};
28871
+ this.checkResults = new Map();
28872
+ this.url = url;
28873
+ this.token = token;
28874
+ this.options = options;
28799
28875
  }
28800
- constructor(raw, maxSize) {
28801
- this.value = raw;
28802
- if (raw < 0) {
28803
- throw new Error('WrapAroundUnsignedInt: cannot faithfully represent an integer smaller than 0');
28804
- }
28805
- if (maxSize > Number.MAX_SAFE_INTEGER) {
28806
- throw new Error('WrapAroundUnsignedInt: cannot faithfully represent an integer bigger than MAX_SAFE_INTEGER.');
28807
- }
28808
- this.maxSize = maxSize;
28809
- this.clamp();
28876
+ getNextCheckId() {
28877
+ const nextId = this.checkResults.size;
28878
+ this.checkResults.set(nextId, {
28879
+ logs: [],
28880
+ status: CheckStatus.IDLE,
28881
+ name: '',
28882
+ description: ''
28883
+ });
28884
+ return nextId;
28810
28885
  }
28811
- /** Manually clamp the given containing value according to the wrap around max size bounds. Use
28812
- * this after out of bounds modification to the contained value by external code. */
28813
- clamp() {
28814
- while (this.value > this.maxSize) {
28815
- this.value -= this.maxSize + 1;
28816
- }
28817
- while (this.value < 0) {
28818
- this.value += this.maxSize + 1;
28819
- }
28886
+ updateCheck(checkId, info) {
28887
+ this.checkResults.set(checkId, info);
28888
+ this.emit('checkUpdate', checkId, info);
28820
28889
  }
28821
- /** When called, maps the containing value to a new containing value. After mapping, the wrap
28822
- * around external max size bounds are applied. */
28823
- update(updateFn) {
28824
- this.value = updateFn(this.value);
28825
- this.clamp();
28890
+ isSuccess() {
28891
+ return Array.from(this.checkResults.values()).every(r => r.status !== CheckStatus.FAILED);
28826
28892
  }
28827
- }
28828
- class DataTrackTimestamp {
28829
- static fromRtpTicks(rtpTicks) {
28830
- return new DataTrackTimestamp(rtpTicks, 90000);
28893
+ getResults() {
28894
+ return Array.from(this.checkResults.values());
28831
28895
  }
28832
- asTicks() {
28833
- return this.timestamp;
28896
+ createAndRunCheck(check) {
28897
+ return __awaiter(this, void 0, void 0, function* () {
28898
+ const checkId = this.getNextCheckId();
28899
+ const test = new check(this.url, this.token, this.options);
28900
+ const handleUpdate = info => {
28901
+ this.updateCheck(checkId, info);
28902
+ };
28903
+ test.on('update', handleUpdate);
28904
+ const result = yield test.run();
28905
+ test.off('update', handleUpdate);
28906
+ return result;
28907
+ });
28834
28908
  }
28835
- constructor(raw, rateInHz) {
28836
- this.timestamp = raw;
28837
- this.rateInHz = rateInHz;
28909
+ checkWebsocket() {
28910
+ return __awaiter(this, void 0, void 0, function* () {
28911
+ return this.createAndRunCheck(WebSocketCheck);
28912
+ });
28838
28913
  }
28839
- }
28840
- function coerceToDataView(input) {
28841
- if (input instanceof DataView) {
28842
- return input;
28843
- } else if (input instanceof ArrayBuffer) {
28844
- return new DataView(input);
28845
- } else if (input instanceof Uint8Array) {
28846
- return new DataView(input.buffer, input.byteOffset, input.byteLength);
28847
- } else {
28848
- throw new Error("Error coercing ".concat(input, " to DataView - input was not DataView, ArrayBuffer, or Uint8Array."));
28914
+ checkWebRTC() {
28915
+ return __awaiter(this, void 0, void 0, function* () {
28916
+ return this.createAndRunCheck(WebRTCCheck);
28917
+ });
28849
28918
  }
28850
- }var DataTrackHandleErrorReason;
28851
- (function (DataTrackHandleErrorReason) {
28852
- DataTrackHandleErrorReason[DataTrackHandleErrorReason["Reserved"] = 0] = "Reserved";
28853
- DataTrackHandleErrorReason[DataTrackHandleErrorReason["TooLarge"] = 1] = "TooLarge";
28854
- })(DataTrackHandleErrorReason || (DataTrackHandleErrorReason = {}));
28855
- class DataTrackHandleError extends LivekitReasonedError {
28856
- constructor(message, reason) {
28857
- super(19, message);
28858
- this.name = 'DataTrackHandleError';
28859
- this.reason = reason;
28860
- this.reasonName = DataTrackHandleErrorReason[reason];
28919
+ checkTURN() {
28920
+ return __awaiter(this, void 0, void 0, function* () {
28921
+ return this.createAndRunCheck(TURNCheck);
28922
+ });
28861
28923
  }
28862
- isReason(reason) {
28863
- return this.reason === reason;
28924
+ checkReconnect() {
28925
+ return __awaiter(this, void 0, void 0, function* () {
28926
+ return this.createAndRunCheck(ReconnectCheck);
28927
+ });
28864
28928
  }
28865
- static tooLarge() {
28866
- return new DataTrackHandleError('Value too large to be a valid track handle', DataTrackHandleErrorReason.TooLarge);
28929
+ checkPublishAudio() {
28930
+ return __awaiter(this, void 0, void 0, function* () {
28931
+ return this.createAndRunCheck(PublishAudioCheck);
28932
+ });
28867
28933
  }
28868
- static reserved(value) {
28869
- return new DataTrackHandleError("0x".concat(value.toString(16), " is a reserved value."), DataTrackHandleErrorReason.Reserved);
28934
+ checkPublishVideo() {
28935
+ return __awaiter(this, void 0, void 0, function* () {
28936
+ return this.createAndRunCheck(PublishVideoCheck);
28937
+ });
28870
28938
  }
28871
- }
28872
- class DataTrackHandle {
28873
- static fromNumber(raw) {
28874
- if (raw === 0) {
28875
- throw DataTrackHandleError.reserved(raw);
28876
- }
28877
- if (raw > U16_MAX_SIZE) {
28878
- throw DataTrackHandleError.tooLarge();
28879
- }
28880
- return new DataTrackHandle(raw);
28939
+ checkConnectionProtocol() {
28940
+ return __awaiter(this, void 0, void 0, function* () {
28941
+ const info = yield this.createAndRunCheck(ConnectionProtocolCheck);
28942
+ if (info.data && 'protocol' in info.data) {
28943
+ const stats = info.data;
28944
+ this.options.protocol = stats.protocol;
28945
+ }
28946
+ return info;
28947
+ });
28881
28948
  }
28882
- constructor(raw) {
28883
- this.value = raw;
28949
+ checkCloudRegion() {
28950
+ return __awaiter(this, void 0, void 0, function* () {
28951
+ return this.createAndRunCheck(CloudRegionCheck);
28952
+ });
28884
28953
  }
28885
- }// Number type sizes
28886
- const U8_LENGTH_BYTES = 1;
28887
- const U16_LENGTH_BYTES = 2;
28888
- const U32_LENGTH_BYTES = 4;
28889
- const U64_LENGTH_BYTES = 8;
28890
- /// Constants used for serialization and deserialization.
28891
- const SUPPORTED_VERSION = 0;
28892
- const BASE_HEADER_LEN = 12;
28893
- // Bitfield shifts and masks for header flags
28894
- const VERSION_SHIFT = 5;
28895
- const VERSION_MASK = 0x07;
28896
- const FRAME_MARKER_SHIFT = 3;
28897
- const FRAME_MARKER_MASK = 0x3;
28898
- const FRAME_MARKER_START = 0x2;
28899
- const FRAME_MARKER_FINAL = 0x1;
28900
- const FRAME_MARKER_INTER = 0x0;
28901
- const FRAME_MARKER_SINGLE = 0x3;
28902
- const EXT_WORDS_INDICATOR_SIZE = 2;
28903
- const EXT_FLAG_SHIFT = 0x2;
28904
- const EXT_FLAG_MASK = 0x1;
28905
- const EXT_TAG_PADDING = 0;var DataTrackDeserializeErrorReason;
28906
- (function (DataTrackDeserializeErrorReason) {
28907
- DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["TooShort"] = 0] = "TooShort";
28908
- DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["HeaderOverrun"] = 1] = "HeaderOverrun";
28909
- DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["MissingExtWords"] = 2] = "MissingExtWords";
28910
- DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["UnsupportedVersion"] = 3] = "UnsupportedVersion";
28911
- DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["InvalidHandle"] = 4] = "InvalidHandle";
28912
- DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["MalformedExt"] = 5] = "MalformedExt";
28913
- })(DataTrackDeserializeErrorReason || (DataTrackDeserializeErrorReason = {}));
28914
- class DataTrackDeserializeError extends LivekitReasonedError {
28915
- constructor(message, reason, options) {
28916
- super(19, message, options);
28917
- this.name = 'DataTrackDeserializeError';
28918
- this.reason = reason;
28919
- this.reasonName = DataTrackDeserializeErrorReason[reason];
28954
+ }/** A Fixed TokenSource is a token source that takes no parameters and returns a completely
28955
+ * independently derived value on each fetch() call.
28956
+ *
28957
+ * The most common downstream implementer is {@link TokenSourceLiteral}.
28958
+ */
28959
+ class TokenSourceFixed {}
28960
+ /** A Configurable TokenSource is a token source that takes a
28961
+ * {@link TokenSourceFetchOptions} object as input and returns a deterministic
28962
+ * {@link TokenSourceResponseObject} output based on the options specified.
28963
+ *
28964
+ * For example, if options.participantName is set, it should be expected that
28965
+ * all tokens that are generated will have participant name field set to the
28966
+ * provided value.
28967
+ *
28968
+ * A few common downstream implementers are {@link TokenSourceEndpoint}
28969
+ * and {@link TokenSourceCustom}.
28970
+ */
28971
+ class TokenSourceConfigurable {}function _defineProperty(e, r, t) {
28972
+ return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
28973
+ value: t,
28974
+ enumerable: true,
28975
+ configurable: true,
28976
+ writable: true
28977
+ }) : e[r] = t, e;
28978
+ }
28979
+ function _toPrimitive(t, r) {
28980
+ if ("object" != typeof t || !t) return t;
28981
+ var e = t[Symbol.toPrimitive];
28982
+ if (void 0 !== e) {
28983
+ var i = e.call(t, r);
28984
+ if ("object" != typeof i) return i;
28985
+ throw new TypeError("@@toPrimitive must return a primitive value.");
28920
28986
  }
28921
- static tooShort() {
28922
- return new DataTrackDeserializeError('Too short to contain a valid header', DataTrackDeserializeErrorReason.TooShort);
28987
+ return ("string" === r ? String : Number)(t);
28988
+ }
28989
+ function _toPropertyKey(t) {
28990
+ var i = _toPrimitive(t, "string");
28991
+ return "symbol" == typeof i ? i : i + "";
28992
+ }new TextEncoder();
28993
+ const decoder = new TextDecoder();function decodeBase64(encoded) {
28994
+ if (Uint8Array.fromBase64) {
28995
+ return Uint8Array.fromBase64(encoded);
28923
28996
  }
28924
- static headerOverrun() {
28925
- return new DataTrackDeserializeError('Header exceeds total packet length', DataTrackDeserializeErrorReason.HeaderOverrun);
28997
+ const binary = atob(encoded);
28998
+ const bytes = new Uint8Array(binary.length);
28999
+ for (let i = 0; i < binary.length; i++) {
29000
+ bytes[i] = binary.charCodeAt(i);
28926
29001
  }
28927
- static missingExtWords() {
28928
- return new DataTrackDeserializeError('Extension word indicator is missing', DataTrackDeserializeErrorReason.MissingExtWords);
29002
+ return bytes;
29003
+ }function decode(input) {
29004
+ if (Uint8Array.fromBase64) {
29005
+ return Uint8Array.fromBase64(typeof input === 'string' ? input : decoder.decode(input), {
29006
+ alphabet: 'base64url'
29007
+ });
28929
29008
  }
28930
- static unsupportedVersion(version) {
28931
- return new DataTrackDeserializeError("Unsupported version ".concat(version), DataTrackDeserializeErrorReason.UnsupportedVersion);
29009
+ let encoded = input;
29010
+ if (encoded instanceof Uint8Array) {
29011
+ encoded = decoder.decode(encoded);
28932
29012
  }
28933
- static invalidHandle(cause) {
28934
- return new DataTrackDeserializeError("invalid track handle: ".concat(cause.message), DataTrackDeserializeErrorReason.InvalidHandle, {
28935
- cause
28936
- });
29013
+ encoded = encoded.replace(/-/g, '+').replace(/_/g, '/');
29014
+ try {
29015
+ return decodeBase64(encoded);
29016
+ } catch (_unused) {
29017
+ throw new TypeError('The input to be decoded is not correctly encoded.');
28937
29018
  }
28938
- static malformedExt(tag) {
28939
- return new DataTrackDeserializeError("Extension with tag ".concat(tag, " is malformed"), DataTrackDeserializeErrorReason.MalformedExt);
29019
+ }class JOSEError extends Error {
29020
+ constructor(message, options) {
29021
+ var _Error$captureStackTr;
29022
+ super(message, options);
29023
+ _defineProperty(this, "code", 'ERR_JOSE_GENERIC');
29024
+ this.name = this.constructor.name;
29025
+ (_Error$captureStackTr = Error.captureStackTrace) === null || _Error$captureStackTr === void 0 || _Error$captureStackTr.call(Error, this, this.constructor);
28940
29026
  }
28941
29027
  }
28942
- var DataTrackSerializeErrorReason;
28943
- (function (DataTrackSerializeErrorReason) {
28944
- DataTrackSerializeErrorReason[DataTrackSerializeErrorReason["TooSmallForHeader"] = 0] = "TooSmallForHeader";
28945
- DataTrackSerializeErrorReason[DataTrackSerializeErrorReason["TooSmallForPayload"] = 1] = "TooSmallForPayload";
28946
- })(DataTrackSerializeErrorReason || (DataTrackSerializeErrorReason = {}));
28947
- class DataTrackSerializeError extends LivekitReasonedError {
28948
- constructor(message, reason, options) {
28949
- super(19, message, options);
28950
- this.name = 'DataTrackSerializeError';
29028
+ _defineProperty(JOSEError, "code", 'ERR_JOSE_GENERIC');
29029
+ class JWTClaimValidationFailed extends JOSEError {
29030
+ constructor(message, payload) {
29031
+ let claim = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'unspecified';
29032
+ let reason = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'unspecified';
29033
+ super(message, {
29034
+ cause: {
29035
+ claim,
29036
+ reason,
29037
+ payload
29038
+ }
29039
+ });
29040
+ _defineProperty(this, "code", 'ERR_JWT_CLAIM_VALIDATION_FAILED');
29041
+ _defineProperty(this, "claim", void 0);
29042
+ _defineProperty(this, "reason", void 0);
29043
+ _defineProperty(this, "payload", void 0);
29044
+ this.claim = claim;
28951
29045
  this.reason = reason;
28952
- this.reasonName = DataTrackSerializeErrorReason[reason];
29046
+ this.payload = payload;
28953
29047
  }
28954
- static tooSmallForHeader() {
28955
- return new DataTrackSerializeError('Buffer cannot fit header', DataTrackSerializeErrorReason.TooSmallForHeader);
29048
+ }
29049
+ _defineProperty(JWTClaimValidationFailed, "code", 'ERR_JWT_CLAIM_VALIDATION_FAILED');
29050
+ class JWTExpired extends JOSEError {
29051
+ constructor(message, payload) {
29052
+ let claim = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'unspecified';
29053
+ let reason = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'unspecified';
29054
+ super(message, {
29055
+ cause: {
29056
+ claim,
29057
+ reason,
29058
+ payload
29059
+ }
29060
+ });
29061
+ _defineProperty(this, "code", 'ERR_JWT_EXPIRED');
29062
+ _defineProperty(this, "claim", void 0);
29063
+ _defineProperty(this, "reason", void 0);
29064
+ _defineProperty(this, "payload", void 0);
29065
+ this.claim = claim;
29066
+ this.reason = reason;
29067
+ this.payload = payload;
28956
29068
  }
28957
- static tooSmallForPayload() {
28958
- return new DataTrackSerializeError('Buffer cannot fit payload', DataTrackSerializeErrorReason.TooSmallForPayload);
29069
+ }
29070
+ _defineProperty(JWTExpired, "code", 'ERR_JWT_EXPIRED');
29071
+ class JOSEAlgNotAllowed extends JOSEError {
29072
+ constructor() {
29073
+ super(...arguments);
29074
+ _defineProperty(this, "code", 'ERR_JOSE_ALG_NOT_ALLOWED');
28959
29075
  }
28960
- }/** An abstract class implementing common behavior related to data track binary serialization. */
28961
- class Serializable {
28962
- /** Encodes the instance as binary and returns the data as a Uint8Array. */
28963
- toBinary() {
28964
- const lengthBytes = this.toBinaryLengthBytes();
28965
- const output = new ArrayBuffer(lengthBytes);
28966
- const view = new DataView(output);
28967
- const writtenBytes = this.toBinaryInto(view);
28968
- if (lengthBytes !== writtenBytes) {
28969
- // @throws-transformer ignore - this should be treated as a "panic" and not be caught
28970
- throw new Error("".concat(this.constructor.name, ".toBinary: written bytes (").concat(writtenBytes, " bytes) not equal to allocated array buffer length (").concat(lengthBytes, " bytes)."));
28971
- }
28972
- return new Uint8Array(output); // FIXME: return uint8array here? Or the arraybuffer?
29076
+ }
29077
+ _defineProperty(JOSEAlgNotAllowed, "code", 'ERR_JOSE_ALG_NOT_ALLOWED');
29078
+ class JOSENotSupported extends JOSEError {
29079
+ constructor() {
29080
+ super(...arguments);
29081
+ _defineProperty(this, "code", 'ERR_JOSE_NOT_SUPPORTED');
28973
29082
  }
28974
- }var DataTrackExtensionTag;
28975
- (function (DataTrackExtensionTag) {
28976
- DataTrackExtensionTag[DataTrackExtensionTag["UserTimestamp"] = 2] = "UserTimestamp";
28977
- DataTrackExtensionTag[DataTrackExtensionTag["E2ee"] = 1] = "E2ee";
28978
- })(DataTrackExtensionTag || (DataTrackExtensionTag = {}));
28979
- class DataTrackExtension extends Serializable {}
28980
- class DataTrackUserTimestampExtension extends DataTrackExtension {
28981
- constructor(timestamp) {
28982
- super();
28983
- this.timestamp = timestamp;
29083
+ }
29084
+ _defineProperty(JOSENotSupported, "code", 'ERR_JOSE_NOT_SUPPORTED');
29085
+ class JWEDecryptionFailed extends JOSEError {
29086
+ constructor() {
29087
+ let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'decryption operation failed';
29088
+ let options = arguments.length > 1 ? arguments[1] : undefined;
29089
+ super(message, options);
29090
+ _defineProperty(this, "code", 'ERR_JWE_DECRYPTION_FAILED');
28984
29091
  }
28985
- toBinaryLengthBytes() {
28986
- return U16_LENGTH_BYTES /* tag */ + U16_LENGTH_BYTES /* length */ + DataTrackUserTimestampExtension.lengthBytes;
29092
+ }
29093
+ _defineProperty(JWEDecryptionFailed, "code", 'ERR_JWE_DECRYPTION_FAILED');
29094
+ class JWEInvalid extends JOSEError {
29095
+ constructor() {
29096
+ super(...arguments);
29097
+ _defineProperty(this, "code", 'ERR_JWE_INVALID');
28987
29098
  }
28988
- toBinaryInto(dataView) {
28989
- let byteIndex = 0;
28990
- dataView.setUint16(byteIndex, DataTrackUserTimestampExtension.tag);
28991
- byteIndex += U16_LENGTH_BYTES;
28992
- const rtpOrientedLength = DataTrackUserTimestampExtension.lengthBytes - 1;
28993
- dataView.setUint16(byteIndex, rtpOrientedLength);
28994
- byteIndex += U16_LENGTH_BYTES;
28995
- dataView.setBigUint64(byteIndex, this.timestamp);
28996
- byteIndex += U64_LENGTH_BYTES;
28997
- const totalLengthBytes = this.toBinaryLengthBytes();
28998
- if (byteIndex !== totalLengthBytes) {
28999
- // @throws-transformer ignore - this should be treated as a "panic" and not be caught
29000
- throw new Error("DataTrackUserTimestampExtension.toBinaryInto: Wrote ".concat(byteIndex, " bytes but expected length was ").concat(totalLengthBytes, " bytes"));
29001
- }
29002
- return byteIndex;
29099
+ }
29100
+ _defineProperty(JWEInvalid, "code", 'ERR_JWE_INVALID');
29101
+ class JWSInvalid extends JOSEError {
29102
+ constructor() {
29103
+ super(...arguments);
29104
+ _defineProperty(this, "code", 'ERR_JWS_INVALID');
29003
29105
  }
29004
- toJSON() {
29005
- return {
29006
- tag: DataTrackUserTimestampExtension.tag,
29007
- lengthBytes: DataTrackUserTimestampExtension.lengthBytes,
29008
- timestamp: this.timestamp
29009
- };
29106
+ }
29107
+ _defineProperty(JWSInvalid, "code", 'ERR_JWS_INVALID');
29108
+ class JWTInvalid extends JOSEError {
29109
+ constructor() {
29110
+ super(...arguments);
29111
+ _defineProperty(this, "code", 'ERR_JWT_INVALID');
29010
29112
  }
29011
29113
  }
29012
- DataTrackUserTimestampExtension.tag = DataTrackExtensionTag.UserTimestamp;
29013
- DataTrackUserTimestampExtension.lengthBytes = 8;
29014
- class DataTrackE2eeExtension extends DataTrackExtension {
29015
- constructor(keyIndex, iv) {
29016
- super();
29017
- this.keyIndex = keyIndex;
29018
- this.iv = iv;
29114
+ _defineProperty(JWTInvalid, "code", 'ERR_JWT_INVALID');
29115
+ class JWKInvalid extends JOSEError {
29116
+ constructor() {
29117
+ super(...arguments);
29118
+ _defineProperty(this, "code", 'ERR_JWK_INVALID');
29019
29119
  }
29020
- toBinaryLengthBytes() {
29021
- return U16_LENGTH_BYTES /* tag */ + U16_LENGTH_BYTES /* length */ + DataTrackE2eeExtension.lengthBytes;
29120
+ }
29121
+ _defineProperty(JWKInvalid, "code", 'ERR_JWK_INVALID');
29122
+ class JWKSInvalid extends JOSEError {
29123
+ constructor() {
29124
+ super(...arguments);
29125
+ _defineProperty(this, "code", 'ERR_JWKS_INVALID');
29022
29126
  }
29023
- toBinaryInto(dataView) {
29024
- let byteIndex = 0;
29025
- dataView.setUint16(byteIndex, DataTrackE2eeExtension.tag);
29026
- byteIndex += U16_LENGTH_BYTES;
29027
- const rtpOrientedLength = DataTrackE2eeExtension.lengthBytes - 1;
29028
- dataView.setUint16(byteIndex, rtpOrientedLength);
29029
- byteIndex += U16_LENGTH_BYTES;
29030
- dataView.setUint8(byteIndex, this.keyIndex);
29031
- byteIndex += U8_LENGTH_BYTES;
29032
- for (let i = 0; i < this.iv.length; i += 1) {
29033
- dataView.setUint8(byteIndex, this.iv[i]);
29034
- byteIndex += U8_LENGTH_BYTES;
29035
- }
29036
- const totalLengthBytes = this.toBinaryLengthBytes();
29037
- if (byteIndex !== totalLengthBytes) {
29038
- // @throws-transformer ignore - this should be treated as a "panic" and not be caught
29039
- throw new Error("DataTrackE2eeExtension.toBinaryInto: Wrote ".concat(byteIndex, " bytes but expected length was ").concat(totalLengthBytes, " bytes"));
29040
- }
29041
- return byteIndex;
29127
+ }
29128
+ _defineProperty(JWKSInvalid, "code", 'ERR_JWKS_INVALID');
29129
+ class JWKSNoMatchingKey extends JOSEError {
29130
+ constructor() {
29131
+ let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'no applicable key found in the JSON Web Key Set';
29132
+ let options = arguments.length > 1 ? arguments[1] : undefined;
29133
+ super(message, options);
29134
+ _defineProperty(this, "code", 'ERR_JWKS_NO_MATCHING_KEY');
29042
29135
  }
29043
- toJSON() {
29044
- return {
29045
- tag: DataTrackE2eeExtension.tag,
29046
- lengthBytes: DataTrackE2eeExtension.lengthBytes,
29047
- keyIndex: this.keyIndex,
29048
- iv: this.iv
29049
- };
29136
+ }
29137
+ _defineProperty(JWKSNoMatchingKey, "code", 'ERR_JWKS_NO_MATCHING_KEY');
29138
+ class JWKSMultipleMatchingKeys extends JOSEError {
29139
+ constructor() {
29140
+ let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'multiple matching keys found in the JSON Web Key Set';
29141
+ let options = arguments.length > 1 ? arguments[1] : undefined;
29142
+ super(message, options);
29143
+ _defineProperty(this, Symbol.asyncIterator, void 0);
29144
+ _defineProperty(this, "code", 'ERR_JWKS_MULTIPLE_MATCHING_KEYS');
29050
29145
  }
29051
29146
  }
29052
- DataTrackE2eeExtension.tag = DataTrackExtensionTag.E2ee;
29053
- DataTrackE2eeExtension.lengthBytes = 13;
29054
- class DataTrackExtensions extends Serializable {
29147
+ _defineProperty(JWKSMultipleMatchingKeys, "code", 'ERR_JWKS_MULTIPLE_MATCHING_KEYS');
29148
+ class JWKSTimeout extends JOSEError {
29055
29149
  constructor() {
29056
- let opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
29057
- super();
29058
- this.userTimestamp = opts.userTimestamp;
29059
- this.e2ee = opts.e2ee;
29150
+ let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'request timed out';
29151
+ let options = arguments.length > 1 ? arguments[1] : undefined;
29152
+ super(message, options);
29153
+ _defineProperty(this, "code", 'ERR_JWKS_TIMEOUT');
29060
29154
  }
29061
- toBinaryLengthBytes() {
29062
- let lengthBytes = 0;
29063
- if (this.userTimestamp) {
29064
- lengthBytes += this.userTimestamp.toBinaryLengthBytes();
29065
- }
29066
- if (this.e2ee) {
29067
- lengthBytes += this.e2ee.toBinaryLengthBytes();
29068
- }
29069
- return lengthBytes;
29155
+ }
29156
+ _defineProperty(JWKSTimeout, "code", 'ERR_JWKS_TIMEOUT');
29157
+ class JWSSignatureVerificationFailed extends JOSEError {
29158
+ constructor() {
29159
+ let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'signature verification failed';
29160
+ let options = arguments.length > 1 ? arguments[1] : undefined;
29161
+ super(message, options);
29162
+ _defineProperty(this, "code", 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED');
29070
29163
  }
29071
- toBinaryInto(dataView) {
29072
- let byteIndex = 0;
29073
- if (this.e2ee) {
29074
- const e2eeBytes = this.e2ee.toBinaryInto(dataView);
29075
- byteIndex += e2eeBytes;
29076
- }
29077
- if (this.userTimestamp) {
29078
- const userTimestampBytes = this.userTimestamp.toBinaryInto(new DataView(dataView.buffer, dataView.byteOffset + byteIndex));
29079
- byteIndex += userTimestampBytes;
29080
- }
29081
- const totalLengthBytes = this.toBinaryLengthBytes();
29082
- if (byteIndex !== totalLengthBytes) {
29083
- // @throws-transformer ignore - this should be treated as a "panic" and not be caught
29084
- throw new Error("DataTrackExtensions.toBinaryInto: Wrote ".concat(byteIndex, " bytes but expected length was ").concat(totalLengthBytes, " bytes"));
29085
- }
29086
- return byteIndex;
29164
+ }
29165
+ _defineProperty(JWSSignatureVerificationFailed, "code", 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED');const isObjectLike = value => typeof value === 'object' && value !== null;
29166
+ function isObject(input) {
29167
+ if (!isObjectLike(input) || Object.prototype.toString.call(input) !== '[object Object]') {
29168
+ return false;
29087
29169
  }
29088
- static fromBinary(input) {
29089
- const dataView = coerceToDataView(input);
29090
- let userTimestamp;
29091
- let e2ee;
29092
- let byteIndex = 0;
29093
- while (dataView.byteLength - byteIndex >= U16_LENGTH_BYTES + U16_LENGTH_BYTES) {
29094
- const tag = dataView.getUint16(byteIndex);
29095
- byteIndex += U16_LENGTH_BYTES;
29096
- const rtpOrientedLength = dataView.getUint16(byteIndex);
29097
- const lengthBytes = rtpOrientedLength + 1;
29098
- byteIndex += U16_LENGTH_BYTES;
29099
- if (tag === EXT_TAG_PADDING) {
29100
- // Skip padding
29101
- continue;
29102
- }
29103
- switch (tag) {
29104
- case DataTrackExtensionTag.UserTimestamp:
29105
- if (dataView.byteLength - byteIndex < DataTrackUserTimestampExtension.lengthBytes) {
29106
- throw DataTrackDeserializeError.malformedExt(tag);
29107
- }
29108
- userTimestamp = new DataTrackUserTimestampExtension(dataView.getBigUint64(byteIndex));
29109
- byteIndex += lengthBytes;
29110
- break;
29111
- case DataTrackExtensionTag.E2ee:
29112
- if (dataView.byteLength - byteIndex < DataTrackE2eeExtension.lengthBytes) {
29113
- throw DataTrackDeserializeError.malformedExt(tag);
29114
- }
29115
- const keyIndex = dataView.getUint8(byteIndex);
29116
- const iv = new Uint8Array(12);
29117
- for (let i = 0; i < iv.length; i += 1) {
29118
- let byteIndexForIv = byteIndex;
29119
- byteIndexForIv += U8_LENGTH_BYTES; // key index
29120
- byteIndexForIv += i * U8_LENGTH_BYTES; // Index into iv array
29121
- iv[i] = dataView.getUint8(byteIndexForIv);
29170
+ if (Object.getPrototypeOf(input) === null) {
29171
+ return true;
29172
+ }
29173
+ let proto = input;
29174
+ while (Object.getPrototypeOf(proto) !== null) {
29175
+ proto = Object.getPrototypeOf(proto);
29176
+ }
29177
+ return Object.getPrototypeOf(input) === proto;
29178
+ }function decodeJwt(jwt) {
29179
+ if (typeof jwt !== 'string') throw new JWTInvalid('JWTs must use Compact JWS serialization, JWT must be a string');
29180
+ const {
29181
+ 1: payload,
29182
+ length
29183
+ } = jwt.split('.');
29184
+ if (length === 5) throw new JWTInvalid('Only JWTs using Compact JWS serialization can be decoded');
29185
+ if (length !== 3) throw new JWTInvalid('Invalid JWT');
29186
+ if (!payload) throw new JWTInvalid('JWTs must contain a payload');
29187
+ let decoded;
29188
+ try {
29189
+ decoded = decode(payload);
29190
+ } catch (_unused) {
29191
+ throw new JWTInvalid('Failed to base64url decode the payload');
29192
+ }
29193
+ let result;
29194
+ try {
29195
+ result = JSON.parse(decoder.decode(decoded));
29196
+ } catch (_unused2) {
29197
+ throw new JWTInvalid('Failed to parse the decoded payload as JSON');
29198
+ }
29199
+ if (!isObject(result)) throw new JWTInvalid('Invalid JWT Claims Set');
29200
+ return result;
29201
+ }const ONE_SECOND_IN_MILLISECONDS = 1000;
29202
+ const ONE_MINUTE_IN_MILLISECONDS = 60 * ONE_SECOND_IN_MILLISECONDS;
29203
+ function isResponseTokenValid(response) {
29204
+ const jwtPayload = decodeTokenPayload(response.participantToken);
29205
+ if (!(jwtPayload === null || jwtPayload === void 0 ? void 0 : jwtPayload.nbf) || !(jwtPayload === null || jwtPayload === void 0 ? void 0 : jwtPayload.exp)) {
29206
+ return true;
29207
+ }
29208
+ const now = new Date();
29209
+ const nbfInMilliseconds = jwtPayload.nbf * ONE_SECOND_IN_MILLISECONDS;
29210
+ const nbfDate = new Date(nbfInMilliseconds);
29211
+ const expInMilliseconds = jwtPayload.exp * ONE_SECOND_IN_MILLISECONDS;
29212
+ const expDate = new Date(expInMilliseconds - ONE_MINUTE_IN_MILLISECONDS);
29213
+ return nbfDate <= now && expDate > now;
29214
+ }
29215
+ /** Given a LiveKit generated participant token, decodes and returns the associated {@link TokenPayload} data. */
29216
+ function decodeTokenPayload(token) {
29217
+ const payload = decodeJwt(token);
29218
+ const {
29219
+ roomConfig
29220
+ } = payload,
29221
+ rest = __rest(payload, ["roomConfig"]);
29222
+ const mappedPayload = Object.assign(Object.assign({}, rest), {
29223
+ roomConfig: payload.roomConfig ? RoomConfiguration.fromJson(payload.roomConfig) : undefined
29224
+ });
29225
+ return mappedPayload;
29226
+ }
29227
+ /** Given two TokenSourceFetchOptions values, check to see if they are deep equal. */
29228
+ function areTokenSourceFetchOptionsEqual(a, b) {
29229
+ const allKeysSet = new Set([...Object.keys(a), ...Object.keys(b)]);
29230
+ for (const key of allKeysSet) {
29231
+ switch (key) {
29232
+ case 'roomName':
29233
+ case 'participantName':
29234
+ case 'participantIdentity':
29235
+ case 'participantMetadata':
29236
+ case 'participantAttributes':
29237
+ case 'agentName':
29238
+ case 'agentMetadata':
29239
+ if (a[key] !== b[key]) {
29240
+ return false;
29241
+ }
29242
+ break;
29243
+ default:
29244
+ // ref: https://stackoverflow.com/a/58009992
29245
+ const exhaustiveCheckedKey = key;
29246
+ throw new Error("Options key ".concat(exhaustiveCheckedKey, " not being checked for equality!"));
29247
+ }
29248
+ }
29249
+ return true;
29250
+ }/** A TokenSourceCached is a TokenSource which caches the last {@link TokenSourceResponseObject} value and returns it
29251
+ * until a) it expires or b) the {@link TokenSourceFetchOptions} provided to .fetch(...) change. */
29252
+ class TokenSourceCached extends TokenSourceConfigurable {
29253
+ constructor() {
29254
+ super(...arguments);
29255
+ this.cachedFetchOptions = null;
29256
+ this.cachedResponse = null;
29257
+ this.fetchMutex = new _();
29258
+ }
29259
+ isSameAsCachedFetchOptions(options) {
29260
+ if (!this.cachedFetchOptions) {
29261
+ return false;
29262
+ }
29263
+ for (const key of Object.keys(this.cachedFetchOptions)) {
29264
+ switch (key) {
29265
+ case 'roomName':
29266
+ case 'participantName':
29267
+ case 'participantIdentity':
29268
+ case 'participantMetadata':
29269
+ case 'participantAttributes':
29270
+ case 'agentName':
29271
+ case 'agentMetadata':
29272
+ if (this.cachedFetchOptions[key] !== options[key]) {
29273
+ return false;
29122
29274
  }
29123
- e2ee = new DataTrackE2eeExtension(keyIndex, iv);
29124
- byteIndex += lengthBytes;
29125
29275
  break;
29126
29276
  default:
29127
- // Skip over unknown extensions (forward compatible).
29128
- if (dataView.byteLength - byteIndex < lengthBytes) {
29129
- throw DataTrackDeserializeError.malformedExt(tag);
29130
- }
29131
- byteIndex += lengthBytes;
29132
- break;
29277
+ // ref: https://stackoverflow.com/a/58009992
29278
+ const exhaustiveCheckedKey = key;
29279
+ throw new Error("Options key ".concat(exhaustiveCheckedKey, " not being checked for equality!"));
29133
29280
  }
29134
29281
  }
29135
- // NOTE: padding bytes after extension data is intentionally not being processed
29136
- return [new DataTrackExtensions({
29137
- userTimestamp,
29138
- e2ee
29139
- }), dataView.byteLength];
29140
- }
29141
- toJSON() {
29142
- var _a, _b, _c, _d;
29143
- return {
29144
- userTimestamp: (_b = (_a = this.userTimestamp) === null || _a === void 0 ? void 0 : _a.toJSON()) !== null && _b !== void 0 ? _b : null,
29145
- e2ee: (_d = (_c = this.e2ee) === null || _c === void 0 ? void 0 : _c.toJSON()) !== null && _d !== void 0 ? _d : null
29146
- };
29147
- }
29148
- }/** A class for serializing / deserializing data track packet header sections. */
29149
- class DataTrackPacketHeader extends Serializable {
29150
- constructor(opts) {
29151
- var _a;
29152
- super();
29153
- this.marker = opts.marker;
29154
- this.trackHandle = opts.trackHandle;
29155
- this.sequence = opts.sequence;
29156
- this.frameNumber = opts.frameNumber;
29157
- this.timestamp = opts.timestamp;
29158
- this.extensions = (_a = opts.extensions) !== null && _a !== void 0 ? _a : new DataTrackExtensions();
29159
- }
29160
- extensionsMetrics() {
29161
- const lengthBytes = this.extensions.toBinaryLengthBytes();
29162
- const lengthWords = Math.ceil(lengthBytes / 4);
29163
- const paddingLengthBytes = lengthWords * 4 - lengthBytes;
29164
- return {
29165
- lengthBytes,
29166
- lengthWords,
29167
- paddingLengthBytes
29168
- };
29282
+ return true;
29169
29283
  }
29170
- toBinaryLengthBytes() {
29171
- const {
29172
- lengthBytes: extLengthBytes,
29173
- paddingLengthBytes: extPaddingLengthBytes
29174
- } = this.extensionsMetrics();
29175
- let totalLengthBytes = BASE_HEADER_LEN;
29176
- if (extLengthBytes > 0) {
29177
- totalLengthBytes += EXT_WORDS_INDICATOR_SIZE + extLengthBytes + extPaddingLengthBytes;
29284
+ shouldReturnCachedValueFromFetch(fetchOptions) {
29285
+ if (!this.cachedResponse) {
29286
+ return false;
29178
29287
  }
29179
- return totalLengthBytes;
29180
- }
29181
- toBinaryInto(dataView) {
29182
- if (dataView.byteLength < this.toBinaryLengthBytes()) {
29183
- throw DataTrackSerializeError.tooSmallForHeader();
29288
+ if (!isResponseTokenValid(this.cachedResponse)) {
29289
+ return false;
29184
29290
  }
29185
- let initial = SUPPORTED_VERSION << VERSION_SHIFT;
29186
- let marker;
29187
- switch (this.marker) {
29188
- case FrameMarker.Inter:
29189
- marker = FRAME_MARKER_INTER;
29190
- break;
29191
- case FrameMarker.Final:
29192
- marker = FRAME_MARKER_FINAL;
29193
- break;
29194
- case FrameMarker.Start:
29195
- marker = FRAME_MARKER_START;
29196
- break;
29197
- case FrameMarker.Single:
29198
- marker = FRAME_MARKER_SINGLE;
29199
- break;
29291
+ if (this.isSameAsCachedFetchOptions(fetchOptions)) {
29292
+ return false;
29200
29293
  }
29201
- initial |= marker << FRAME_MARKER_SHIFT;
29202
- const {
29203
- lengthBytes: extensionsLengthBytes,
29204
- lengthWords: extensionsLengthWords,
29205
- paddingLengthBytes: extensionsPaddingLengthBytes
29206
- } = this.extensionsMetrics();
29207
- if (extensionsLengthBytes > 0) {
29208
- initial |= 1 << EXT_FLAG_SHIFT;
29294
+ return true;
29295
+ }
29296
+ getCachedResponseJwtPayload() {
29297
+ if (!this.cachedResponse) {
29298
+ return null;
29209
29299
  }
29210
- let byteIndex = 0;
29211
- dataView.setUint8(byteIndex, initial);
29212
- byteIndex += U8_LENGTH_BYTES;
29213
- dataView.setUint8(byteIndex, 0); // Reserved
29214
- byteIndex += U8_LENGTH_BYTES;
29215
- dataView.setUint16(byteIndex, this.trackHandle.value);
29216
- byteIndex += U16_LENGTH_BYTES;
29217
- dataView.setUint16(byteIndex, this.sequence.value);
29218
- byteIndex += U16_LENGTH_BYTES;
29219
- dataView.setUint16(byteIndex, this.frameNumber.value);
29220
- byteIndex += U16_LENGTH_BYTES;
29221
- dataView.setUint32(byteIndex, this.timestamp.asTicks());
29222
- byteIndex += U32_LENGTH_BYTES;
29223
- if (extensionsLengthBytes > 0) {
29224
- // NOTE: The protocol is implemented in a way where if the extension bit is set, any
29225
- // deserializer assumes the extensions section is at least one byte long, and the "length"
29226
- // field represents the "number of additional bytes" long the extensions section is. This is
29227
- // potentially unintuitive so I wanted to call it out.
29228
- const rtpOrientedExtensionLengthWords = extensionsLengthWords - 1;
29229
- dataView.setUint16(byteIndex, rtpOrientedExtensionLengthWords);
29230
- byteIndex += U16_LENGTH_BYTES;
29231
- const extensionBytes = this.extensions.toBinaryInto(new DataView(dataView.buffer, dataView.byteOffset + byteIndex));
29232
- byteIndex += extensionBytes;
29233
- for (let i = 0; i < extensionsPaddingLengthBytes; i += 1) {
29234
- dataView.setUint8(byteIndex, 0);
29235
- byteIndex += U8_LENGTH_BYTES;
29300
+ return decodeTokenPayload(this.cachedResponse.participantToken);
29301
+ }
29302
+ fetch(options) {
29303
+ return __awaiter(this, void 0, void 0, function* () {
29304
+ const unlock = yield this.fetchMutex.lock();
29305
+ try {
29306
+ if (this.shouldReturnCachedValueFromFetch(options)) {
29307
+ return this.cachedResponse.toJson();
29308
+ }
29309
+ this.cachedFetchOptions = options;
29310
+ const tokenResponse = yield this.update(options);
29311
+ this.cachedResponse = tokenResponse;
29312
+ return tokenResponse.toJson();
29313
+ } finally {
29314
+ unlock();
29236
29315
  }
29237
- }
29238
- const totalLengthBytes = this.toBinaryLengthBytes();
29239
- if (byteIndex !== totalLengthBytes) {
29240
- // @throws-transformer ignore - this should be treated as a "panic" and not be caught
29241
- throw new Error("DataTrackPacketHeader.toBinaryInto: Wrote ".concat(byteIndex, " bytes but expected length was ").concat(totalLengthBytes, " bytes"));
29242
- }
29243
- return totalLengthBytes;
29316
+ });
29244
29317
  }
29245
- static fromBinary(input) {
29246
- const dataView = coerceToDataView(input);
29247
- if (dataView.byteLength < BASE_HEADER_LEN) {
29248
- throw DataTrackDeserializeError.tooShort();
29249
- }
29250
- let byteIndex = 0;
29251
- const initial = dataView.getUint8(byteIndex);
29252
- byteIndex += U8_LENGTH_BYTES;
29253
- const version = initial >> VERSION_SHIFT & VERSION_MASK;
29254
- if (version > SUPPORTED_VERSION) {
29255
- throw DataTrackDeserializeError.unsupportedVersion(version);
29256
- }
29257
- let marker;
29258
- switch (initial >> FRAME_MARKER_SHIFT & FRAME_MARKER_MASK) {
29259
- case FRAME_MARKER_START:
29260
- marker = FrameMarker.Start;
29261
- break;
29262
- case FRAME_MARKER_FINAL:
29263
- marker = FrameMarker.Final;
29264
- break;
29265
- case FRAME_MARKER_SINGLE:
29266
- marker = FrameMarker.Single;
29267
- break;
29268
- case FRAME_MARKER_INTER:
29269
- default:
29270
- marker = FrameMarker.Inter;
29271
- break;
29272
- }
29273
- const extensionsFlag = (initial >> EXT_FLAG_SHIFT & EXT_FLAG_MASK) > 0;
29274
- byteIndex += U8_LENGTH_BYTES; // Reserved
29275
- let trackHandle;
29276
- try {
29277
- trackHandle = DataTrackHandle.fromNumber(dataView.getUint16(byteIndex));
29278
- } catch (e) {
29279
- if (e instanceof DataTrackHandleError && (e.isReason(DataTrackHandleErrorReason.Reserved) || e.isReason(DataTrackHandleErrorReason.TooLarge))) {
29280
- throw DataTrackDeserializeError.invalidHandle(e);
29318
+ }
29319
+ class TokenSourceLiteral extends TokenSourceFixed {
29320
+ constructor(literalOrFn) {
29321
+ super();
29322
+ this.literalOrFn = literalOrFn;
29323
+ }
29324
+ fetch() {
29325
+ return __awaiter(this, void 0, void 0, function* () {
29326
+ if (typeof this.literalOrFn === 'function') {
29327
+ return this.literalOrFn();
29281
29328
  } else {
29282
- throw e;
29283
- }
29284
- }
29285
- byteIndex += U16_LENGTH_BYTES;
29286
- const sequence = WrapAroundUnsignedInt.u16(dataView.getUint16(byteIndex));
29287
- byteIndex += U16_LENGTH_BYTES;
29288
- const frameNumber = WrapAroundUnsignedInt.u16(dataView.getUint16(byteIndex));
29289
- byteIndex += U16_LENGTH_BYTES;
29290
- const timestamp = DataTrackTimestamp.fromRtpTicks(dataView.getUint32(byteIndex));
29291
- byteIndex += U32_LENGTH_BYTES;
29292
- let extensions = new DataTrackExtensions();
29293
- if (extensionsFlag) {
29294
- if (dataView.byteLength - byteIndex < U16_LENGTH_BYTES) {
29295
- throw DataTrackDeserializeError.missingExtWords();
29329
+ return this.literalOrFn;
29296
29330
  }
29297
- let rtpOrientedExtensionWords = dataView.getUint16(byteIndex);
29298
- byteIndex += U16_LENGTH_BYTES;
29299
- // NOTE: The protocol is implemented in a way where if the extension bit is set, any
29300
- // deserializer assumes the extensions section is at least one byte long, and the "length"
29301
- // field represents the "number of additional bytes" long the extensions section is. This is
29302
- // potentially unintuitive so I wanted to call it out.
29303
- const extensionWords = rtpOrientedExtensionWords + 1;
29304
- let extensionLengthBytes = 4 * extensionWords;
29305
- if (byteIndex + extensionLengthBytes > dataView.byteLength) {
29306
- throw DataTrackDeserializeError.headerOverrun();
29331
+ });
29332
+ }
29333
+ }
29334
+ class TokenSourceCustom extends TokenSourceCached {
29335
+ constructor(customFn) {
29336
+ super();
29337
+ this.customFn = customFn;
29338
+ }
29339
+ update(options) {
29340
+ return __awaiter(this, void 0, void 0, function* () {
29341
+ const resultMaybePromise = this.customFn(options);
29342
+ let result;
29343
+ if (resultMaybePromise instanceof Promise) {
29344
+ result = yield resultMaybePromise;
29345
+ } else {
29346
+ result = resultMaybePromise;
29347
+ }
29348
+ return TokenSourceResponse.fromJson(result, {
29349
+ // NOTE: it could be possible that the response body could contain more fields than just
29350
+ // what's in TokenSourceResponse depending on the implementation
29351
+ ignoreUnknownFields: true
29352
+ });
29353
+ });
29354
+ }
29355
+ }
29356
+ class TokenSourceEndpoint extends TokenSourceCached {
29357
+ constructor(url) {
29358
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
29359
+ super();
29360
+ this.url = url;
29361
+ this.endpointOptions = options;
29362
+ }
29363
+ createRequestFromOptions(options) {
29364
+ var _a, _b, _c;
29365
+ const request = new TokenSourceRequest();
29366
+ for (const key of Object.keys(options)) {
29367
+ switch (key) {
29368
+ case 'roomName':
29369
+ case 'participantName':
29370
+ case 'participantIdentity':
29371
+ case 'participantMetadata':
29372
+ request[key] = options[key];
29373
+ break;
29374
+ case 'participantAttributes':
29375
+ request.participantAttributes = (_a = options.participantAttributes) !== null && _a !== void 0 ? _a : {};
29376
+ break;
29377
+ case 'agentName':
29378
+ request.roomConfig = (_b = request.roomConfig) !== null && _b !== void 0 ? _b : new RoomConfiguration();
29379
+ if (request.roomConfig.agents.length === 0) {
29380
+ request.roomConfig.agents.push(new RoomAgentDispatch());
29381
+ }
29382
+ request.roomConfig.agents[0].agentName = options.agentName;
29383
+ break;
29384
+ case 'agentMetadata':
29385
+ request.roomConfig = (_c = request.roomConfig) !== null && _c !== void 0 ? _c : new RoomConfiguration();
29386
+ if (request.roomConfig.agents.length === 0) {
29387
+ request.roomConfig.agents.push(new RoomAgentDispatch());
29388
+ }
29389
+ request.roomConfig.agents[0].metadata = options.agentMetadata;
29390
+ break;
29391
+ default:
29392
+ // ref: https://stackoverflow.com/a/58009992
29393
+ const exhaustiveCheckedKey = key;
29394
+ throw new Error("Options key ".concat(exhaustiveCheckedKey, " not being included in forming request!"));
29307
29395
  }
29308
- let extensionDataView = new DataView(dataView.buffer, dataView.byteOffset + byteIndex, extensionLengthBytes);
29309
- const [result, readBytes] = DataTrackExtensions.fromBinary(extensionDataView);
29310
- extensions = result;
29311
- byteIndex += readBytes;
29312
29396
  }
29313
- return [new DataTrackPacketHeader({
29314
- marker,
29315
- trackHandle: trackHandle,
29316
- sequence,
29317
- frameNumber,
29318
- timestamp,
29319
- extensions
29320
- }), byteIndex];
29397
+ return request;
29321
29398
  }
29322
- toJSON() {
29323
- return {
29324
- marker: this.marker,
29325
- trackHandle: this.trackHandle.value,
29326
- sequence: this.sequence.value,
29327
- frameNumber: this.frameNumber.value,
29328
- timestamp: this.timestamp.timestamp,
29329
- extensions: this.extensions.toJSON()
29330
- };
29399
+ update(options) {
29400
+ return __awaiter(this, void 0, void 0, function* () {
29401
+ var _a;
29402
+ const request = this.createRequestFromOptions(options);
29403
+ const response = yield fetch(this.url, Object.assign(Object.assign({}, this.endpointOptions), {
29404
+ method: (_a = this.endpointOptions.method) !== null && _a !== void 0 ? _a : 'POST',
29405
+ headers: Object.assign({
29406
+ 'Content-Type': 'application/json'
29407
+ }, this.endpointOptions.headers),
29408
+ body: request.toJsonString({
29409
+ useProtoFieldName: true
29410
+ })
29411
+ }));
29412
+ if (!response.ok) {
29413
+ throw new Error("Error generating token from endpoint ".concat(this.url, ": received ").concat(response.status, " / ").concat(yield response.text()));
29414
+ }
29415
+ const body = yield response.json();
29416
+ return TokenSourceResponse.fromJson(body, {
29417
+ // NOTE: it could be possible that the response body could contain more fields than just
29418
+ // what's in TokenSourceResponse depending on the implementation (ie, SandboxTokenServer)
29419
+ ignoreUnknownFields: true
29420
+ });
29421
+ });
29331
29422
  }
29332
29423
  }
29333
- /** Marker indicating a packet's position in relation to a frame. */
29334
- var FrameMarker;
29335
- (function (FrameMarker) {
29336
- /** Packet is the first in a frame. */
29337
- FrameMarker[FrameMarker["Start"] = 0] = "Start";
29338
- /** Packet is within a frame. */
29339
- FrameMarker[FrameMarker["Inter"] = 1] = "Inter";
29340
- /** Packet is the last in a frame. */
29341
- FrameMarker[FrameMarker["Final"] = 2] = "Final";
29342
- /** Packet is the only one in a frame. */
29343
- FrameMarker[FrameMarker["Single"] = 3] = "Single";
29344
- })(FrameMarker || (FrameMarker = {}));
29345
- /** A class for serializing / deserializing data track packets. */
29346
- class DataTrackPacket extends Serializable {
29347
- constructor(header, payload) {
29348
- super();
29349
- this.header = header;
29350
- this.payload = payload;
29424
+ class TokenSourceSandboxTokenServer extends TokenSourceEndpoint {
29425
+ constructor(sandboxId, options) {
29426
+ const {
29427
+ baseUrl = 'https://cloud-api.livekit.io'
29428
+ } = options,
29429
+ rest = __rest(options, ["baseUrl"]);
29430
+ super("".concat(baseUrl, "/api/v2/sandbox/connection-details"), Object.assign(Object.assign({}, rest), {
29431
+ headers: {
29432
+ 'X-Sandbox-ID': sandboxId
29433
+ }
29434
+ }));
29351
29435
  }
29352
- toBinaryLengthBytes() {
29353
- return this.header.toBinaryLengthBytes() + this.payload.byteLength;
29436
+ }
29437
+ const TokenSource = {
29438
+ /** TokenSource.literal contains a single, literal set of {@link TokenSourceResponseObject}
29439
+ * credentials, either provided directly or returned from a provided function. */
29440
+ literal(literalOrFn) {
29441
+ return new TokenSourceLiteral(literalOrFn);
29442
+ },
29443
+ /**
29444
+ * TokenSource.custom allows a user to define a manual function which generates new
29445
+ * {@link TokenSourceResponseObject} values on demand.
29446
+ *
29447
+ * Use this to get credentials from custom backends / etc.
29448
+ */
29449
+ custom(customFn) {
29450
+ return new TokenSourceCustom(customFn);
29451
+ },
29452
+ /**
29453
+ * TokenSource.endpoint creates a token source that fetches credentials from a given URL using
29454
+ * the standard endpoint format:
29455
+ * @see https://cloud.livekit.io/projects/p_/sandbox/templates/token-server
29456
+ */
29457
+ endpoint(url) {
29458
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
29459
+ return new TokenSourceEndpoint(url, options);
29460
+ },
29461
+ /**
29462
+ * TokenSource.sandboxTokenServer queries a sandbox token server for credentials,
29463
+ * which supports quick prototyping / getting started types of use cases.
29464
+ *
29465
+ * This token provider is INSECURE and should NOT be used in production.
29466
+ *
29467
+ * For more info:
29468
+ * @see https://cloud.livekit.io/projects/p_/sandbox/templates/token-server
29469
+ */
29470
+ sandboxTokenServer(sandboxId) {
29471
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
29472
+ return new TokenSourceSandboxTokenServer(sandboxId, options);
29354
29473
  }
29355
- toBinaryInto(dataView) {
29356
- let byteIndex = 0;
29357
- const headerLengthBytes = this.header.toBinaryInto(dataView);
29358
- byteIndex += headerLengthBytes;
29359
- if (dataView.byteLength - byteIndex < this.payload.byteLength) {
29360
- throw DataTrackSerializeError.tooSmallForPayload();
29361
- }
29362
- const payloadBytes = new Uint8Array(this.payload);
29363
- for (let index = 0; index < payloadBytes.length; index += 1) {
29364
- dataView.setUint8(byteIndex, payloadBytes[index]);
29365
- byteIndex += U8_LENGTH_BYTES;
29474
+ };/**
29475
+ * Try to analyze the local track to determine the facing mode of a track.
29476
+ *
29477
+ * @remarks
29478
+ * There is no property supported by all browsers to detect whether a video track originated from a user- or environment-facing camera device.
29479
+ * For this reason, we use the `facingMode` property when available, but will fall back on a string-based analysis of the device label to determine the facing mode.
29480
+ * If both methods fail, the default facing mode will be used.
29481
+ *
29482
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/facingMode | MDN docs on facingMode}
29483
+ * @experimental
29484
+ */
29485
+ function facingModeFromLocalTrack(localTrack) {
29486
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
29487
+ var _a;
29488
+ const track = isLocalTrack(localTrack) ? localTrack.mediaStreamTrack : localTrack;
29489
+ const trackSettings = track.getSettings();
29490
+ let result = {
29491
+ facingMode: (_a = options.defaultFacingMode) !== null && _a !== void 0 ? _a : 'user',
29492
+ confidence: 'low'
29493
+ };
29494
+ // 1. Try to get facingMode from track settings.
29495
+ if ('facingMode' in trackSettings) {
29496
+ const rawFacingMode = trackSettings.facingMode;
29497
+ livekitLogger.trace('rawFacingMode', {
29498
+ rawFacingMode
29499
+ });
29500
+ if (rawFacingMode && typeof rawFacingMode === 'string' && isFacingModeValue(rawFacingMode)) {
29501
+ result = {
29502
+ facingMode: rawFacingMode,
29503
+ confidence: 'high'
29504
+ };
29366
29505
  }
29367
- const totalLengthBytes = this.toBinaryLengthBytes();
29368
- if (byteIndex !== totalLengthBytes) {
29369
- // @throws-transformer ignore - this should be treated as a "panic" and not be caught
29370
- throw new Error("DataTrackPacket.toBinaryInto: Wrote ".concat(byteIndex, " bytes but expected length was ").concat(totalLengthBytes, " bytes"));
29506
+ }
29507
+ // 2. If we don't have a high confidence we try to get the facing mode from the device label.
29508
+ if (['low', 'medium'].includes(result.confidence)) {
29509
+ livekitLogger.trace("Try to get facing mode from device label: (".concat(track.label, ")"));
29510
+ const labelAnalysisResult = facingModeFromDeviceLabel(track.label);
29511
+ if (labelAnalysisResult !== undefined) {
29512
+ result = labelAnalysisResult;
29371
29513
  }
29372
- return totalLengthBytes;
29373
29514
  }
29374
- static fromBinary(input) {
29375
- const dataView = coerceToDataView(input);
29376
- const [header, headerByteLength] = DataTrackPacketHeader.fromBinary(dataView);
29377
- const payload = dataView.buffer.slice(dataView.byteOffset + headerByteLength, dataView.byteOffset + dataView.byteLength);
29378
- return [new DataTrackPacket(header, payload), dataView.byteLength];
29515
+ return result;
29516
+ }
29517
+ const knownDeviceLabels = new Map([['obs virtual camera', {
29518
+ facingMode: 'environment',
29519
+ confidence: 'medium'
29520
+ }]]);
29521
+ const knownDeviceLabelSections = new Map([['iphone', {
29522
+ facingMode: 'environment',
29523
+ confidence: 'medium'
29524
+ }], ['ipad', {
29525
+ facingMode: 'environment',
29526
+ confidence: 'medium'
29527
+ }]]);
29528
+ /**
29529
+ * Attempt to analyze the device label to determine the facing mode.
29530
+ *
29531
+ * @experimental
29532
+ */
29533
+ function facingModeFromDeviceLabel(deviceLabel) {
29534
+ var _a;
29535
+ const label = deviceLabel.trim().toLowerCase();
29536
+ // Empty string is a valid device label but we can't infer anything from it.
29537
+ if (label === '') {
29538
+ return undefined;
29379
29539
  }
29380
- toJSON() {
29381
- return {
29382
- header: this.header.toJSON(),
29383
- payload: this.payload
29384
- };
29540
+ // Can we match against widely known device labels.
29541
+ if (knownDeviceLabels.has(label)) {
29542
+ return knownDeviceLabels.get(label);
29385
29543
  }
29544
+ // Can we match against sections of the device label.
29545
+ return (_a = Array.from(knownDeviceLabelSections.entries()).find(_ref => {
29546
+ let [section] = _ref;
29547
+ return label.includes(section);
29548
+ })) === null || _a === void 0 ? void 0 : _a[1];
29549
+ }
29550
+ function isFacingModeValue(item) {
29551
+ const allowedValues = ['user', 'environment', 'left', 'right'];
29552
+ return item === undefined || allowedValues.includes(item);
29386
29553
  }export{AudioPresets,BackupCodecPolicy,BaseKeyProvider,CheckStatus,Checker,ConnectionCheck,ConnectionError,ConnectionErrorReason,ConnectionQuality,ConnectionState,CriticalTimers,CryptorError,CryptorErrorReason,CryptorEvent,DataPacket_Kind,DataStreamError,DataStreamErrorReason,DataTrackPacket,DefaultReconnectPolicy,DeviceUnsupportedError,DisconnectReason,EncryptionEvent,Encryption_Type,EngineEvent,ExternalE2EEKeyProvider,KeyHandlerEvent,KeyProviderEvent,LivekitError,LivekitReasonedError,LocalAudioTrack,LocalParticipant,LocalTrack,LocalTrackPublication,LocalTrackRecorder,LocalVideoTrack,LogLevel,LoggerNames,MediaDeviceFailure,_ as Mutex,NegotiationError,Participant,ParticipantEvent,ParticipantInfo_Kind as ParticipantKind,PublishDataError,PublishTrackError,RemoteAudioTrack,RemoteParticipant,RemoteTrack,RemoteTrackPublication,RemoteVideoTrack,Room,RoomEvent,RpcError,ScreenSharePresets,SignalReconnectError,SignalRequestError,SimulatedError,SubscriptionError,TokenSource,TokenSourceConfigurable,TokenSourceFixed,Track,TrackEvent,TrackInvalidError,TrackPublication,TrackType,UnexpectedConnectionState,UnsupportedServer,VideoPreset,VideoPresets,VideoPresets43,VideoQuality,areTokenSourceFetchOptionsEqual,asEncryptablePacket,attachToElement,attributeTypings as attributes,audioCodecs,compareVersions,createAudioAnalyser,createE2EEKey,createKeyMaterialFromBuffer,createKeyMaterialFromString,createLocalAudioTrack,createLocalScreenTracks,createLocalTracks,createLocalVideoTrack,decodeTokenPayload,deriveKeys,detachTrack,facingModeFromDeviceLabel,facingModeFromLocalTrack,getBrowser,getEmptyAudioStreamTrack,getEmptyVideoStreamTrack,getLogger,importKey,isAudioCodec,isAudioTrack,isBackupCodec,isBackupVideoCodec,isBrowserSupported,isE2EESupported,isInsertableStreamSupported,isLocalParticipant,isLocalTrack,isRemoteParticipant,isRemoteTrack,isScriptTransformSupported,isVideoCodec,isVideoFrame,isVideoTrack,needsRbspUnescaping,parseRbsp,protocolVersion,ratchet,setLogExtension,setLogLevel,supportsAV1,supportsAdaptiveStream,supportsAudioOutputSelection,supportsDynacast,supportsVP9,version,videoCodecs,writeRbsp};//# sourceMappingURL=livekit-client.esm.mjs.map