livekit-client 2.15.8 → 2.15.9

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 (58) hide show
  1. package/dist/livekit-client.esm.mjs +577 -202
  2. package/dist/livekit-client.esm.mjs.map +1 -1
  3. package/dist/livekit-client.umd.js +1 -1
  4. package/dist/livekit-client.umd.js.map +1 -1
  5. package/dist/src/api/SignalClient.d.ts +31 -2
  6. package/dist/src/api/SignalClient.d.ts.map +1 -1
  7. package/dist/src/api/WebSocketStream.d.ts +29 -0
  8. package/dist/src/api/WebSocketStream.d.ts.map +1 -0
  9. package/dist/src/api/utils.d.ts +2 -0
  10. package/dist/src/api/utils.d.ts.map +1 -1
  11. package/dist/src/connectionHelper/checks/turn.d.ts.map +1 -1
  12. package/dist/src/connectionHelper/checks/websocket.d.ts.map +1 -1
  13. package/dist/src/index.d.ts +2 -2
  14. package/dist/src/index.d.ts.map +1 -1
  15. package/dist/src/options.d.ts +6 -0
  16. package/dist/src/options.d.ts.map +1 -1
  17. package/dist/src/room/PCTransport.d.ts +1 -0
  18. package/dist/src/room/PCTransport.d.ts.map +1 -1
  19. package/dist/src/room/PCTransportManager.d.ts +6 -4
  20. package/dist/src/room/PCTransportManager.d.ts.map +1 -1
  21. package/dist/src/room/RTCEngine.d.ts +1 -1
  22. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  23. package/dist/src/room/Room.d.ts.map +1 -1
  24. package/dist/src/room/defaults.d.ts.map +1 -1
  25. package/dist/src/room/token-source/utils.d.ts +1 -1
  26. package/dist/src/room/token-source/utils.d.ts.map +1 -1
  27. package/dist/src/room/utils.d.ts +6 -0
  28. package/dist/src/room/utils.d.ts.map +1 -1
  29. package/dist/ts4.2/api/SignalClient.d.ts +31 -2
  30. package/dist/ts4.2/api/WebSocketStream.d.ts +29 -0
  31. package/dist/ts4.2/api/utils.d.ts +2 -0
  32. package/dist/ts4.2/index.d.ts +2 -2
  33. package/dist/ts4.2/options.d.ts +6 -0
  34. package/dist/ts4.2/room/PCTransport.d.ts +1 -0
  35. package/dist/ts4.2/room/PCTransportManager.d.ts +6 -4
  36. package/dist/ts4.2/room/RTCEngine.d.ts +1 -1
  37. package/dist/ts4.2/room/token-source/utils.d.ts +1 -1
  38. package/dist/ts4.2/room/utils.d.ts +6 -0
  39. package/package.json +1 -1
  40. package/src/api/SignalClient.test.ts +688 -0
  41. package/src/api/SignalClient.ts +308 -161
  42. package/src/api/WebSocketStream.test.ts +625 -0
  43. package/src/api/WebSocketStream.ts +118 -0
  44. package/src/api/utils.ts +10 -0
  45. package/src/connectionHelper/checks/turn.ts +1 -0
  46. package/src/connectionHelper/checks/webrtc.ts +1 -1
  47. package/src/connectionHelper/checks/websocket.ts +1 -0
  48. package/src/index.ts +2 -0
  49. package/src/options.ts +7 -0
  50. package/src/room/PCTransport.ts +7 -3
  51. package/src/room/PCTransportManager.ts +39 -35
  52. package/src/room/RTCEngine.ts +54 -16
  53. package/src/room/Room.ts +5 -2
  54. package/src/room/defaults.ts +1 -0
  55. package/src/room/token-source/TokenSource.ts +2 -2
  56. package/src/room/token-source/utils.test.ts +63 -0
  57. package/src/room/token-source/utils.ts +10 -5
  58. package/src/room/utils.ts +29 -0
@@ -7373,6 +7373,110 @@ const TrackSubscribed = /* @__PURE__ */proto3.makeMessageType("livekit.TrackSubs
7373
7373
  T: 9
7374
7374
  /* ScalarType.STRING */
7375
7375
  }]);
7376
+ const ConnectionSettings = /* @__PURE__ */proto3.makeMessageType("livekit.ConnectionSettings", () => [{
7377
+ no: 1,
7378
+ name: "auto_subscribe",
7379
+ kind: "scalar",
7380
+ T: 8
7381
+ /* ScalarType.BOOL */
7382
+ }, {
7383
+ no: 2,
7384
+ name: "adaptive_stream",
7385
+ kind: "scalar",
7386
+ T: 8
7387
+ /* ScalarType.BOOL */
7388
+ }, {
7389
+ no: 3,
7390
+ name: "subscriber_allow_pause",
7391
+ kind: "scalar",
7392
+ T: 8,
7393
+ opt: true
7394
+ }, {
7395
+ no: 4,
7396
+ name: "disable_ice_lite",
7397
+ kind: "scalar",
7398
+ T: 8
7399
+ /* ScalarType.BOOL */
7400
+ }]);
7401
+ const JoinRequest = /* @__PURE__ */proto3.makeMessageType("livekit.JoinRequest", () => [{
7402
+ no: 1,
7403
+ name: "client_info",
7404
+ kind: "message",
7405
+ T: ClientInfo
7406
+ }, {
7407
+ no: 2,
7408
+ name: "connection_settings",
7409
+ kind: "message",
7410
+ T: ConnectionSettings
7411
+ }, {
7412
+ no: 3,
7413
+ name: "metadata",
7414
+ kind: "scalar",
7415
+ T: 9
7416
+ /* ScalarType.STRING */
7417
+ }, {
7418
+ no: 4,
7419
+ name: "participant_attributes",
7420
+ kind: "map",
7421
+ K: 9,
7422
+ V: {
7423
+ kind: "scalar",
7424
+ T: 9
7425
+ /* ScalarType.STRING */
7426
+ }
7427
+ }, {
7428
+ no: 5,
7429
+ name: "add_track_requests",
7430
+ kind: "message",
7431
+ T: AddTrackRequest,
7432
+ repeated: true
7433
+ }, {
7434
+ no: 6,
7435
+ name: "publisher_offer",
7436
+ kind: "message",
7437
+ T: SessionDescription
7438
+ }, {
7439
+ no: 7,
7440
+ name: "reconnect",
7441
+ kind: "scalar",
7442
+ T: 8
7443
+ /* ScalarType.BOOL */
7444
+ }, {
7445
+ no: 8,
7446
+ name: "reconnect_reason",
7447
+ kind: "enum",
7448
+ T: proto3.getEnumType(ReconnectReason)
7449
+ }, {
7450
+ no: 9,
7451
+ name: "participant_sid",
7452
+ kind: "scalar",
7453
+ T: 9
7454
+ /* ScalarType.STRING */
7455
+ }, {
7456
+ no: 10,
7457
+ name: "sync_state",
7458
+ kind: "message",
7459
+ T: SyncState
7460
+ }]);
7461
+ const WrappedJoinRequest = /* @__PURE__ */proto3.makeMessageType("livekit.WrappedJoinRequest", () => [{
7462
+ no: 1,
7463
+ name: "compression",
7464
+ kind: "enum",
7465
+ T: proto3.getEnumType(WrappedJoinRequest_Compression)
7466
+ }, {
7467
+ no: 2,
7468
+ name: "join_request",
7469
+ kind: "scalar",
7470
+ T: 12
7471
+ /* ScalarType.BYTES */
7472
+ }]);
7473
+ const WrappedJoinRequest_Compression = /* @__PURE__ */proto3.makeEnum("livekit.WrappedJoinRequest.Compression", [{
7474
+ no: 0,
7475
+ name: "NONE"
7476
+ }, {
7477
+ no: 1,
7478
+ name: "GZIP"
7479
+ }]);
7376
7480
  const MediaSectionsRequirement = /* @__PURE__ */proto3.makeMessageType("livekit.MediaSectionsRequirement", () => [{
7377
7481
  no: 1,
7378
7482
  name: "num_audios",
@@ -12466,7 +12570,7 @@ function getOSVersion(ua) {
12466
12570
  return ua.includes('mac os') ? getMatch(/\(.+?(\d+_\d+(:?_\d+)?)/, ua, 1).replace(/_/g, '.') : undefined;
12467
12571
  }
12468
12572
 
12469
- var version$1 = "2.15.8";
12573
+ var version$1 = "2.15.9";
12470
12574
 
12471
12575
  const version = version$1;
12472
12576
  const protocolVersion = 16;
@@ -13078,6 +13182,14 @@ function supportsSetSinkId(elm) {
13078
13182
  }
13079
13183
  return 'setSinkId' in elm;
13080
13184
  }
13185
+ /**
13186
+ * Checks whether or not setting an audio output via {@link Room#setActiveDevice}
13187
+ * is supported for the current browser.
13188
+ */
13189
+ function supportsAudioOutputSelection() {
13190
+ // Note: this is method publicly exported under a user friendly name and currently only proxying `supportsSetSinkId`
13191
+ return supportsSetSinkId();
13192
+ }
13081
13193
  function isBrowserSupported() {
13082
13194
  if (typeof RTCPeerConnection === 'undefined') {
13083
13195
  return false;
@@ -14405,6 +14517,102 @@ class AsyncQueue {
14405
14517
  }
14406
14518
  }
14407
14519
 
14520
+ /**
14521
+ * [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) with [Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API)
14522
+ *
14523
+ * @see https://web.dev/websocketstream/
14524
+ */
14525
+ class WebSocketStream {
14526
+ get readyState() {
14527
+ return this.ws.readyState;
14528
+ }
14529
+ constructor(url) {
14530
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
14531
+ var _a, _b;
14532
+ if ((_a = options.signal) === null || _a === void 0 ? void 0 : _a.aborted) {
14533
+ throw new DOMException('This operation was aborted', 'AbortError');
14534
+ }
14535
+ this.url = url;
14536
+ const ws = new WebSocket(url, (_b = options.protocols) !== null && _b !== void 0 ? _b : []);
14537
+ ws.binaryType = 'arraybuffer';
14538
+ this.ws = ws;
14539
+ const closeWithInfo = function () {
14540
+ let {
14541
+ closeCode: code,
14542
+ reason
14543
+ } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
14544
+ return ws.close(code, reason);
14545
+ };
14546
+ this.opened = new Promise((resolve, reject) => {
14547
+ ws.onopen = () => {
14548
+ resolve({
14549
+ readable: new ReadableStream({
14550
+ start(controller) {
14551
+ ws.onmessage = _ref => {
14552
+ let {
14553
+ data
14554
+ } = _ref;
14555
+ return controller.enqueue(data);
14556
+ };
14557
+ ws.onerror = e => controller.error(e);
14558
+ },
14559
+ cancel: closeWithInfo
14560
+ }),
14561
+ writable: new WritableStream({
14562
+ write(chunk) {
14563
+ ws.send(chunk);
14564
+ },
14565
+ abort() {
14566
+ ws.close();
14567
+ },
14568
+ close: closeWithInfo
14569
+ }),
14570
+ protocol: ws.protocol,
14571
+ extensions: ws.extensions
14572
+ });
14573
+ ws.removeEventListener('error', reject);
14574
+ };
14575
+ ws.addEventListener('error', reject);
14576
+ });
14577
+ this.closed = new Promise((resolve, reject) => {
14578
+ const rejectHandler = () => __awaiter(this, void 0, void 0, function* () {
14579
+ const closePromise = new Promise(res => {
14580
+ if (ws.readyState === WebSocket.CLOSED) return;else {
14581
+ ws.addEventListener('close', closeEv => {
14582
+ res(closeEv);
14583
+ }, {
14584
+ once: true
14585
+ });
14586
+ }
14587
+ });
14588
+ const reason = yield Promise.race([sleep(250), closePromise]);
14589
+ if (!reason) {
14590
+ reject(new Error('Encountered unspecified websocket error without a timely close event'));
14591
+ } else {
14592
+ // if we can infer the close reason from the close event then resolve the promise, we don't need to throw
14593
+ resolve(reason);
14594
+ }
14595
+ });
14596
+ ws.onclose = _ref2 => {
14597
+ let {
14598
+ code,
14599
+ reason
14600
+ } = _ref2;
14601
+ resolve({
14602
+ closeCode: code,
14603
+ reason
14604
+ });
14605
+ ws.removeEventListener('error', rejectHandler);
14606
+ };
14607
+ ws.addEventListener('error', rejectHandler);
14608
+ });
14609
+ if (options.signal) {
14610
+ options.signal.onabort = () => ws.close();
14611
+ }
14612
+ this.close = closeWithInfo;
14613
+ }
14614
+ }
14615
+
14408
14616
  function createRtcUrl(url, searchParams) {
14409
14617
  const urlObj = new URL(toWebsocketUrl(url));
14410
14618
  searchParams.forEach((value, key) => {
@@ -14423,6 +14631,16 @@ function appendUrlPath(urlObj, path) {
14423
14631
  urlObj.pathname = "".concat(ensureTrailingSlash(urlObj.pathname)).concat(path);
14424
14632
  return urlObj.toString();
14425
14633
  }
14634
+ function parseSignalResponse(value) {
14635
+ if (typeof value === 'string') {
14636
+ return SignalResponse.fromJson(JSON.parse(value), {
14637
+ ignoreUnknownFields: true
14638
+ });
14639
+ } else if (value instanceof ArrayBuffer) {
14640
+ return SignalResponse.fromBinary(new Uint8Array(value));
14641
+ }
14642
+ throw new Error("could not decode websocket message: ".concat(typeof value));
14643
+ }
14426
14644
 
14427
14645
  const passThroughQueueSignals = ['syncState', 'trickle', 'offer', 'answer', 'simulate', 'leave'];
14428
14646
  function canPassThroughQueue(req) {
@@ -14441,6 +14659,8 @@ var SignalConnectionState;
14441
14659
  SignalConnectionState[SignalConnectionState["DISCONNECTING"] = 3] = "DISCONNECTING";
14442
14660
  SignalConnectionState[SignalConnectionState["DISCONNECTED"] = 4] = "DISCONNECTED";
14443
14661
  })(SignalConnectionState || (SignalConnectionState = {}));
14662
+ /** specifies how much time (in ms) we allow for the ws to close its connection gracefully before continuing */
14663
+ const MAX_WS_CLOSE_TIME = 250;
14444
14664
  /** @internal */
14445
14665
  class SignalClient {
14446
14666
  get currentState() {
@@ -14478,6 +14698,7 @@ class SignalClient {
14478
14698
  this.onTokenRefresh = undefined;
14479
14699
  this.onTrickle = undefined;
14480
14700
  this.onClose = undefined;
14701
+ this.onMediaSectionsRequirement = undefined;
14481
14702
  };
14482
14703
  this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.Signal);
14483
14704
  this.loggerContextCb = loggerOptions.loggerContextCb;
@@ -14520,137 +14741,140 @@ class SignalClient {
14520
14741
  });
14521
14742
  }
14522
14743
  connect(url, token, opts, abortSignal) {
14523
- this.connectOptions = opts;
14524
- const clientInfo = getClientInfo();
14525
- const params = createConnectionParams(token, clientInfo, opts);
14526
- const rtcUrl = createRtcUrl(url, params);
14527
- const validateUrl = createValidateUrl(rtcUrl);
14528
- return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
14744
+ return __awaiter(this, void 0, void 0, function* () {
14529
14745
  const unlock = yield this.connectionLock.lock();
14530
- try {
14531
- const abortHandler = () => __awaiter(this, void 0, void 0, function* () {
14532
- this.close();
14533
- clearTimeout(wsTimeout);
14534
- reject(new ConnectionError('room connection has been cancelled (signal)', ConnectionErrorReason.Cancelled));
14535
- });
14536
- const wsTimeout = setTimeout(() => {
14537
- this.close();
14538
- reject(new ConnectionError('room connection has timed out (signal)', ConnectionErrorReason.ServerUnreachable));
14539
- }, opts.websocketTimeout);
14540
- if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) {
14541
- abortHandler();
14542
- }
14543
- abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.addEventListener('abort', abortHandler);
14544
- const redactedUrl = new URL(rtcUrl);
14545
- if (redactedUrl.searchParams.has('access_token')) {
14546
- redactedUrl.searchParams.set('access_token', '<redacted>');
14547
- }
14548
- this.log.debug("connecting to ".concat(redactedUrl), Object.assign({
14549
- reconnect: opts.reconnect,
14550
- reconnectReason: opts.reconnectReason
14551
- }, this.logContext));
14552
- if (this.ws) {
14553
- yield this.close(false);
14554
- }
14555
- this.ws = new WebSocket(rtcUrl);
14556
- this.ws.binaryType = 'arraybuffer';
14557
- this.ws.onopen = () => {
14558
- clearTimeout(wsTimeout);
14559
- };
14560
- this.ws.onerror = ev => __awaiter(this, void 0, void 0, function* () {
14561
- if (this.state !== SignalConnectionState.CONNECTED) {
14562
- this.state = SignalConnectionState.DISCONNECTED;
14746
+ this.connectOptions = opts;
14747
+ const clientInfo = getClientInfo();
14748
+ const params = opts.singlePeerConnection ? createJoinRequestConnectionParams(token, clientInfo, opts) : createConnectionParams(token, clientInfo, opts);
14749
+ const rtcUrl = createRtcUrl(url, params);
14750
+ const validateUrl = createValidateUrl(rtcUrl);
14751
+ return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
14752
+ var _a, _b;
14753
+ try {
14754
+ const timeoutAbortController = new AbortController();
14755
+ const signals = abortSignal ? [timeoutAbortController.signal, abortSignal] : [timeoutAbortController.signal];
14756
+ const combinedAbort = AbortSignal.any(signals);
14757
+ const abortHandler = event => __awaiter(this, void 0, void 0, function* () {
14758
+ this.close();
14563
14759
  clearTimeout(wsTimeout);
14564
- try {
14565
- const resp = yield fetch(validateUrl);
14566
- if (resp.status.toFixed(0).startsWith('4')) {
14567
- const msg = yield resp.text();
14568
- reject(new ConnectionError(msg, ConnectionErrorReason.NotAllowed, resp.status));
14569
- } else {
14570
- reject(new ConnectionError("Encountered unknown websocket error during connection: ".concat(ev.toString()), ConnectionErrorReason.InternalError, resp.status));
14571
- }
14572
- } catch (e) {
14573
- reject(new ConnectionError(e instanceof Error ? e.message : 'server was not reachable', ConnectionErrorReason.ServerUnreachable));
14574
- }
14575
- return;
14760
+ const target = event.currentTarget;
14761
+ reject(target instanceof AbortSignal ? target.reason : target);
14762
+ });
14763
+ combinedAbort.addEventListener('abort', abortHandler);
14764
+ const wsTimeout = setTimeout(() => {
14765
+ timeoutAbortController.abort(new ConnectionError('room connection has timed out (signal)', ConnectionErrorReason.ServerUnreachable));
14766
+ }, opts.websocketTimeout);
14767
+ const handleSignalConnected = (connection, firstMessage) => {
14768
+ this.handleSignalConnected(connection, wsTimeout, firstMessage);
14769
+ };
14770
+ const redactedUrl = new URL(rtcUrl);
14771
+ if (redactedUrl.searchParams.has('access_token')) {
14772
+ redactedUrl.searchParams.set('access_token', '<redacted>');
14576
14773
  }
14577
- // other errors, handle
14578
- this.handleWSError(ev);
14579
- });
14580
- this.ws.onmessage = ev => __awaiter(this, void 0, void 0, function* () {
14581
- var _a, _b, _c;
14582
- // not considered connected until JoinResponse is received
14583
- let resp;
14584
- if (typeof ev.data === 'string') {
14585
- const json = JSON.parse(ev.data);
14586
- resp = SignalResponse.fromJson(json, {
14587
- ignoreUnknownFields: true
14588
- });
14589
- } else if (ev.data instanceof ArrayBuffer) {
14590
- resp = SignalResponse.fromBinary(new Uint8Array(ev.data));
14591
- } else {
14592
- this.log.error("could not decode websocket message: ".concat(typeof ev.data), this.logContext);
14593
- return;
14774
+ this.log.debug("connecting to ".concat(redactedUrl), Object.assign({
14775
+ reconnect: opts.reconnect,
14776
+ reconnectReason: opts.reconnectReason
14777
+ }, this.logContext));
14778
+ if (this.ws) {
14779
+ yield this.close(false);
14594
14780
  }
14595
- if (this.state !== SignalConnectionState.CONNECTED) {
14596
- let shouldProcessMessage = false;
14597
- // handle join message only
14598
- if (((_a = resp.message) === null || _a === void 0 ? void 0 : _a.case) === 'join') {
14599
- this.state = SignalConnectionState.CONNECTED;
14600
- abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.removeEventListener('abort', abortHandler);
14601
- this.pingTimeoutDuration = resp.message.value.pingTimeout;
14602
- this.pingIntervalDuration = resp.message.value.pingInterval;
14781
+ this.ws = new WebSocketStream(rtcUrl, {
14782
+ signal: combinedAbort
14783
+ });
14784
+ try {
14785
+ this.ws.closed.then(closeInfo => {
14786
+ if (this.isEstablishingConnection) {
14787
+ reject(new ConnectionError("Websocket got closed during a (re)connection attempt: ".concat(closeInfo.reason), ConnectionErrorReason.InternalError));
14788
+ }
14789
+ if (closeInfo.closeCode !== 1000) {
14790
+ this.log.warn("websocket closed", Object.assign(Object.assign({}, this.logContext), {
14791
+ reason: closeInfo.reason,
14792
+ code: closeInfo.closeCode,
14793
+ wasClean: closeInfo.closeCode === 1000,
14794
+ state: this.state
14795
+ }));
14796
+ }
14797
+ return;
14798
+ }).catch(reason => {
14799
+ if (this.isEstablishingConnection) {
14800
+ reject(new ConnectionError("Websocket error during a (re)connection attempt: ".concat(reason), ConnectionErrorReason.InternalError));
14801
+ }
14802
+ });
14803
+ const connection = yield this.ws.opened.catch(reason => __awaiter(this, void 0, void 0, function* () {
14804
+ if (this.state !== SignalConnectionState.CONNECTED) {
14805
+ this.state = SignalConnectionState.DISCONNECTED;
14806
+ clearTimeout(wsTimeout);
14807
+ const error = yield this.handleConnectionError(reason, validateUrl);
14808
+ reject(error);
14809
+ return;
14810
+ }
14811
+ // other errors, handle
14812
+ this.handleWSError(reason);
14813
+ reject(reason);
14814
+ return;
14815
+ }));
14816
+ clearTimeout(wsTimeout);
14817
+ if (!connection) {
14818
+ return;
14819
+ }
14820
+ const signalReader = connection.readable.getReader();
14821
+ const firstMessage = yield signalReader.read();
14822
+ signalReader.releaseLock();
14823
+ if (!firstMessage.value) {
14824
+ throw new ConnectionError('no message received as first message', ConnectionErrorReason.InternalError);
14825
+ }
14826
+ const firstSignalResponse = parseSignalResponse(firstMessage.value);
14827
+ // Validate the first message
14828
+ const validation = this.validateFirstMessage(firstSignalResponse, (_a = opts.reconnect) !== null && _a !== void 0 ? _a : false);
14829
+ if (!validation.isValid) {
14830
+ reject(validation.error);
14831
+ return;
14832
+ }
14833
+ // Handle join response - set up ping configuration
14834
+ if (((_b = firstSignalResponse.message) === null || _b === void 0 ? void 0 : _b.case) === 'join') {
14835
+ this.pingTimeoutDuration = firstSignalResponse.message.value.pingTimeout;
14836
+ this.pingIntervalDuration = firstSignalResponse.message.value.pingInterval;
14603
14837
  if (this.pingTimeoutDuration && this.pingTimeoutDuration > 0) {
14604
14838
  this.log.debug('ping config', Object.assign(Object.assign({}, this.logContext), {
14605
14839
  timeout: this.pingTimeoutDuration,
14606
14840
  interval: this.pingIntervalDuration
14607
14841
  }));
14608
- this.startPingInterval();
14609
14842
  }
14610
- resolve(resp.message.value);
14611
- } else if (this.state === SignalConnectionState.RECONNECTING && resp.message.case !== 'leave') {
14612
- // in reconnecting, any message received means signal reconnected
14613
- this.state = SignalConnectionState.CONNECTED;
14614
- abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.removeEventListener('abort', abortHandler);
14615
- this.startPingInterval();
14616
- if (((_b = resp.message) === null || _b === void 0 ? void 0 : _b.case) === 'reconnect') {
14617
- resolve(resp.message.value);
14618
- } else {
14619
- this.log.debug('declaring signal reconnected without reconnect response received', this.logContext);
14620
- resolve(undefined);
14621
- shouldProcessMessage = true;
14622
- }
14623
- } else if (this.isEstablishingConnection && resp.message.case === 'leave') {
14624
- reject(new ConnectionError('Received leave request while trying to (re)connect', ConnectionErrorReason.LeaveRequest, undefined, resp.message.value.reason));
14625
- } else if (!opts.reconnect) {
14626
- // non-reconnect case, should receive join response first
14627
- reject(new ConnectionError("did not receive join response, got ".concat((_c = resp.message) === null || _c === void 0 ? void 0 : _c.case, " instead"), ConnectionErrorReason.InternalError));
14628
- }
14629
- if (!shouldProcessMessage) {
14630
- return;
14631
14843
  }
14844
+ // Handle successful connection
14845
+ const firstMessageToProcess = validation.shouldProcessFirstMessage ? firstSignalResponse : undefined;
14846
+ handleSignalConnected(connection, firstMessageToProcess);
14847
+ resolve(validation.response);
14848
+ } catch (e) {
14849
+ clearTimeout(wsTimeout);
14850
+ reject(e);
14632
14851
  }
14633
- if (this.signalLatency) {
14634
- yield sleep(this.signalLatency);
14635
- }
14636
- this.handleSignalResponse(resp);
14637
- });
14638
- this.ws.onclose = ev => {
14639
- if (this.isEstablishingConnection) {
14640
- reject(new ConnectionError('Websocket got closed during a (re)connection attempt', ConnectionErrorReason.InternalError));
14641
- }
14642
- this.log.warn("websocket closed", Object.assign(Object.assign({}, this.logContext), {
14643
- reason: ev.reason,
14644
- code: ev.code,
14645
- wasClean: ev.wasClean,
14646
- state: this.state
14647
- }));
14648
- this.handleOnClose(ev.reason);
14649
- };
14650
- } finally {
14651
- unlock();
14852
+ } finally {
14853
+ unlock();
14854
+ }
14855
+ }));
14856
+ });
14857
+ }
14858
+ startReadingLoop(signalReader, firstMessage) {
14859
+ return __awaiter(this, void 0, void 0, function* () {
14860
+ if (firstMessage) {
14861
+ this.handleSignalResponse(firstMessage);
14652
14862
  }
14653
- }));
14863
+ while (true) {
14864
+ if (this.signalLatency) {
14865
+ yield sleep(this.signalLatency);
14866
+ }
14867
+ const {
14868
+ done,
14869
+ value
14870
+ } = yield signalReader.read();
14871
+ if (done) {
14872
+ break;
14873
+ }
14874
+ const resp = parseSignalResponse(value);
14875
+ this.handleSignalResponse(resp);
14876
+ }
14877
+ });
14654
14878
  }
14655
14879
  close() {
14656
14880
  return __awaiter(this, arguments, void 0, function () {
@@ -14664,26 +14888,20 @@ class SignalClient {
14664
14888
  _this.state = SignalConnectionState.DISCONNECTING;
14665
14889
  }
14666
14890
  if (_this.ws) {
14667
- _this.ws.onmessage = null;
14668
- _this.ws.onopen = null;
14669
- _this.ws.onclose = null;
14670
- // calling `ws.close()` only starts the closing handshake (CLOSING state), prefer to wait until state is actually CLOSED
14671
- const closePromise = new Promise(resolve => {
14672
- if (_this.ws) {
14673
- _this.ws.onclose = () => {
14674
- resolve();
14675
- };
14676
- } else {
14677
- resolve();
14678
- }
14891
+ _this.ws.close({
14892
+ closeCode: 1000,
14893
+ reason: 'Close method called on signal client'
14679
14894
  });
14680
- if (_this.ws.readyState < _this.ws.CLOSING) {
14681
- _this.ws.close();
14682
- // 250ms grace period for ws to close gracefully
14683
- yield Promise.race([closePromise, sleep(250)]);
14684
- }
14895
+ // calling `ws.close()` only starts the closing handshake (CLOSING state), prefer to wait until state is actually CLOSED
14896
+ const closePromise = _this.ws.closed;
14685
14897
  _this.ws = undefined;
14898
+ _this.streamWriter = undefined;
14899
+ yield Promise.race([closePromise, sleep(MAX_WS_CLOSE_TIME)]);
14686
14900
  }
14901
+ } catch (e) {
14902
+ _this.log.debug('websocket error while closing', Object.assign(Object.assign({}, _this.logContext), {
14903
+ error: e
14904
+ }));
14687
14905
  } finally {
14688
14906
  if (updateState) {
14689
14907
  _this.state = SignalConnectionState.DISCONNECTED;
@@ -14860,7 +15078,7 @@ class SignalClient {
14860
15078
  _this3.log.debug("skipping signal request (type: ".concat(message.case, ") - SignalClient disconnected"));
14861
15079
  return;
14862
15080
  }
14863
- if (!_this3.ws || _this3.ws.readyState !== _this3.ws.OPEN) {
15081
+ if (!_this3.streamWriter) {
14864
15082
  _this3.log.error("cannot send signal request before connected, type: ".concat(message === null || message === void 0 ? void 0 : message.case), _this3.logContext);
14865
15083
  return;
14866
15084
  }
@@ -14869,9 +15087,9 @@ class SignalClient {
14869
15087
  });
14870
15088
  try {
14871
15089
  if (_this3.useJSON) {
14872
- _this3.ws.send(req.toJsonString());
15090
+ yield _this3.streamWriter.write(req.toJsonString());
14873
15091
  } else {
14874
- _this3.ws.send(req.toBinary());
15092
+ yield _this3.streamWriter.write(req.toBinary());
14875
15093
  }
14876
15094
  } catch (e) {
14877
15095
  _this3.log.error('error sending signal message', Object.assign(Object.assign({}, _this3.logContext), {
@@ -14975,6 +15193,10 @@ class SignalClient {
14975
15193
  if (this.onRoomMoved) {
14976
15194
  this.onRoomMoved(msg.value);
14977
15195
  }
15196
+ } else if (msg.case === 'mediaSectionsRequirement') {
15197
+ if (this.onMediaSectionsRequirement) {
15198
+ this.onMediaSectionsRequirement(msg.value);
15199
+ }
14978
15200
  } else {
14979
15201
  this.log.debug('unsupported message', Object.assign(Object.assign({}, this.logContext), {
14980
15202
  msgCase: msg.case
@@ -15005,9 +15227,9 @@ class SignalClient {
15005
15227
  }
15006
15228
  });
15007
15229
  }
15008
- handleWSError(ev) {
15230
+ handleWSError(error) {
15009
15231
  this.log.error('websocket error', Object.assign(Object.assign({}, this.logContext), {
15010
- error: ev
15232
+ error
15011
15233
  }));
15012
15234
  }
15013
15235
  /**
@@ -15052,6 +15274,90 @@ class SignalClient {
15052
15274
  CriticalTimers.clearInterval(this.pingInterval);
15053
15275
  }
15054
15276
  }
15277
+ /**
15278
+ * Handles the successful connection to the signal server
15279
+ * @param connection The WebSocket connection
15280
+ * @param timeoutHandle The timeout handle to clear
15281
+ * @param firstMessage Optional first message to process
15282
+ * @internal
15283
+ */
15284
+ handleSignalConnected(connection, timeoutHandle, firstMessage) {
15285
+ this.state = SignalConnectionState.CONNECTED;
15286
+ clearTimeout(timeoutHandle);
15287
+ this.startPingInterval();
15288
+ this.startReadingLoop(connection.readable.getReader(), firstMessage);
15289
+ this.streamWriter = connection.writable.getWriter();
15290
+ }
15291
+ /**
15292
+ * Validates the first message received from the signal server
15293
+ * @param firstSignalResponse The first signal response received
15294
+ * @param isReconnect Whether this is a reconnection attempt
15295
+ * @returns Validation result with response or error
15296
+ * @internal
15297
+ */
15298
+ validateFirstMessage(firstSignalResponse, isReconnect) {
15299
+ var _a, _b, _c, _d, _e;
15300
+ if (((_a = firstSignalResponse.message) === null || _a === void 0 ? void 0 : _a.case) === 'join') {
15301
+ return {
15302
+ isValid: true,
15303
+ response: firstSignalResponse.message.value
15304
+ };
15305
+ } else if (this.state === SignalConnectionState.RECONNECTING && ((_b = firstSignalResponse.message) === null || _b === void 0 ? void 0 : _b.case) !== 'leave') {
15306
+ if (((_c = firstSignalResponse.message) === null || _c === void 0 ? void 0 : _c.case) === 'reconnect') {
15307
+ return {
15308
+ isValid: true,
15309
+ response: firstSignalResponse.message.value
15310
+ };
15311
+ } else {
15312
+ // in reconnecting, any message received means signal reconnected and we still need to process it
15313
+ this.log.debug('declaring signal reconnected without reconnect response received', this.logContext);
15314
+ return {
15315
+ isValid: true,
15316
+ response: undefined,
15317
+ shouldProcessFirstMessage: true
15318
+ };
15319
+ }
15320
+ } else if (this.isEstablishingConnection && ((_d = firstSignalResponse.message) === null || _d === void 0 ? void 0 : _d.case) === 'leave') {
15321
+ return {
15322
+ isValid: false,
15323
+ error: new ConnectionError('Received leave request while trying to (re)connect', ConnectionErrorReason.LeaveRequest, undefined, firstSignalResponse.message.value.reason)
15324
+ };
15325
+ } else if (!isReconnect) {
15326
+ // non-reconnect case, should receive join response first
15327
+ return {
15328
+ isValid: false,
15329
+ error: new ConnectionError("did not receive join response, got ".concat((_e = firstSignalResponse.message) === null || _e === void 0 ? void 0 : _e.case, " instead"), ConnectionErrorReason.InternalError)
15330
+ };
15331
+ }
15332
+ return {
15333
+ isValid: false,
15334
+ error: new ConnectionError('Unexpected first message', ConnectionErrorReason.InternalError)
15335
+ };
15336
+ }
15337
+ /**
15338
+ * Handles WebSocket connection errors by validating with the server
15339
+ * @param reason The error that occurred
15340
+ * @param validateUrl The URL to validate the connection with
15341
+ * @returns A ConnectionError with appropriate reason and status
15342
+ * @internal
15343
+ */
15344
+ handleConnectionError(reason, validateUrl) {
15345
+ return __awaiter(this, void 0, void 0, function* () {
15346
+ try {
15347
+ const resp = yield fetch(validateUrl);
15348
+ if (resp.status.toFixed(0).startsWith('4')) {
15349
+ const msg = yield resp.text();
15350
+ return new ConnectionError(msg, ConnectionErrorReason.NotAllowed, resp.status);
15351
+ } else if (reason instanceof ConnectionError) {
15352
+ return reason;
15353
+ } else {
15354
+ return new ConnectionError("Encountered unknown websocket error during connection: ".concat(reason), ConnectionErrorReason.InternalError, resp.status);
15355
+ }
15356
+ } catch (e) {
15357
+ return e instanceof ConnectionError ? e : new ConnectionError(e instanceof Error ? e.message : 'server was not reachable', ConnectionErrorReason.ServerUnreachable);
15358
+ }
15359
+ });
15360
+ }
15055
15361
  }
15056
15362
  function fromProtoSessionDescription(sd) {
15057
15363
  const rsd = {
@@ -15120,6 +15426,27 @@ function createConnectionParams(token, info, opts) {
15120
15426
  }
15121
15427
  return params;
15122
15428
  }
15429
+ function createJoinRequestConnectionParams(token, info, opts) {
15430
+ const params = new URLSearchParams();
15431
+ params.set('access_token', token);
15432
+ const joinRequest = new JoinRequest({
15433
+ clientInfo: info,
15434
+ connectionSettings: new ConnectionSettings({
15435
+ autoSubscribe: !!opts.autoSubscribe,
15436
+ adaptiveStream: !!opts.adaptiveStream
15437
+ }),
15438
+ reconnect: !!opts.reconnect,
15439
+ participantSid: opts.sid ? opts.sid : undefined
15440
+ });
15441
+ if (opts.reconnectReason) {
15442
+ joinRequest.reconnectReason = opts.reconnectReason;
15443
+ }
15444
+ const wrappedJoinRequest = new WrappedJoinRequest({
15445
+ joinRequest: joinRequest.toBinary()
15446
+ });
15447
+ params.set('join_request', btoa(new TextDecoder('utf-8').decode(wrappedJoinRequest.toBinary())));
15448
+ return params;
15449
+ }
15123
15450
 
15124
15451
  class DataPacketBuffer {
15125
15452
  constructor() {
@@ -16132,7 +16459,7 @@ class PCTransport extends eventsExports.EventEmitter {
16132
16459
  sdpParsed.media.forEach(media => {
16133
16460
  const mid = getMidString(media.mid);
16134
16461
  if (media.type === 'audio') {
16135
- // mung sdp for opus bitrate settings
16462
+ // munge sdp for opus bitrate settings
16136
16463
  this.trackBitrates.some(trackbr => {
16137
16464
  if (!trackbr.transceiver || mid != trackbr.transceiver.mid) {
16138
16465
  return false;
@@ -16237,7 +16564,7 @@ class PCTransport extends eventsExports.EventEmitter {
16237
16564
  sdpParsed.media.forEach(media => {
16238
16565
  ensureIPAddrMatchVersion(media);
16239
16566
  if (media.type === 'audio') {
16240
- ensureAudioNackAndStereo(media, [], []);
16567
+ ensureAudioNackAndStereo(media, ['all'], []);
16241
16568
  } else if (media.type === 'video') {
16242
16569
  this.trackBitrates.some(trackbr => {
16243
16570
  if (!media.msid || !trackbr.cid || !media.msid.includes(trackbr.cid)) {
@@ -16313,6 +16640,9 @@ class PCTransport extends eventsExports.EventEmitter {
16313
16640
  addTransceiver(mediaStreamTrack, transceiverInit) {
16314
16641
  return this.pc.addTransceiver(mediaStreamTrack, transceiverInit);
16315
16642
  }
16643
+ addTransceiverOfKind(kind, transceiverInit) {
16644
+ return this.pc.addTransceiver(kind, transceiverInit);
16645
+ }
16316
16646
  addTrack(track) {
16317
16647
  if (!this._pc) {
16318
16648
  throw new UnexpectedConnectionState('PC closed, cannot add track');
@@ -16507,7 +16837,7 @@ function ensureAudioNackAndStereo(media, stereoMids, nackMids) {
16507
16837
  type: 'nack'
16508
16838
  });
16509
16839
  }
16510
- if (stereoMids.includes(mid)) {
16840
+ if (stereoMids.includes(mid) || stereoMids.length === 1 && stereoMids[0] === 'all') {
16511
16841
  media.fmtp.some(fmtp => {
16512
16842
  if (fmtp.payload === opusPayload) {
16513
16843
  if (!fmtp.config.includes('stereo=1')) {
@@ -16607,7 +16937,8 @@ const roomOptionDefaults = {
16607
16937
  stopLocalTrackOnUnpublish: true,
16608
16938
  reconnectPolicy: new DefaultReconnectPolicy(),
16609
16939
  disconnectOnPageLeave: true,
16610
- webAudioMix: false
16940
+ webAudioMix: false,
16941
+ singlePeerConnection: false
16611
16942
  };
16612
16943
  const roomConnectOptionDefaults = {
16613
16944
  autoSubscribe: true,
@@ -16635,12 +16966,12 @@ class PCTransportManager {
16635
16966
  get currentState() {
16636
16967
  return this.state;
16637
16968
  }
16638
- constructor(rtcConfig, subscriberPrimary, loggerOptions) {
16969
+ constructor(rtcConfig, mode, loggerOptions) {
16639
16970
  var _a;
16640
16971
  this.peerConnectionTimeout = roomConnectOptionDefaults.peerConnectionTimeout;
16641
16972
  this.log = livekitLogger;
16642
16973
  this.updateState = () => {
16643
- var _a;
16974
+ var _a, _b;
16644
16975
  const previousState = this.state;
16645
16976
  const connectionStates = this.requiredTransports.map(tr => tr.getConnectionState());
16646
16977
  if (connectionStates.every(st => st === 'connected')) {
@@ -16658,35 +16989,41 @@ class PCTransportManager {
16658
16989
  }
16659
16990
  if (previousState !== this.state) {
16660
16991
  this.log.debug("pc state change: from ".concat(PCTransportState[previousState], " to ").concat(PCTransportState[this.state]), this.logContext);
16661
- (_a = this.onStateChange) === null || _a === void 0 ? void 0 : _a.call(this, this.state, this.publisher.getConnectionState(), this.subscriber.getConnectionState());
16992
+ (_a = this.onStateChange) === null || _a === void 0 ? void 0 : _a.call(this, this.state, this.publisher.getConnectionState(), (_b = this.subscriber) === null || _b === void 0 ? void 0 : _b.getConnectionState());
16662
16993
  }
16663
16994
  };
16664
16995
  this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.PCManager);
16665
16996
  this.loggerOptions = loggerOptions;
16666
- this.isPublisherConnectionRequired = !subscriberPrimary;
16667
- this.isSubscriberConnectionRequired = subscriberPrimary;
16997
+ this.isPublisherConnectionRequired = mode !== 'subscriber-primary';
16998
+ this.isSubscriberConnectionRequired = mode === 'subscriber-primary';
16668
16999
  this.publisher = new PCTransport(rtcConfig, loggerOptions);
16669
- this.subscriber = new PCTransport(rtcConfig, loggerOptions);
17000
+ if (mode !== 'publisher-only') {
17001
+ this.subscriber = new PCTransport(rtcConfig, loggerOptions);
17002
+ this.subscriber.onConnectionStateChange = this.updateState;
17003
+ this.subscriber.onIceConnectionStateChange = this.updateState;
17004
+ this.subscriber.onSignalingStatechange = this.updateState;
17005
+ this.subscriber.onIceCandidate = candidate => {
17006
+ var _a;
17007
+ (_a = this.onIceCandidate) === null || _a === void 0 ? void 0 : _a.call(this, candidate, SignalTarget.SUBSCRIBER);
17008
+ };
17009
+ // in subscriber primary mode, server side opens sub data channels.
17010
+ this.subscriber.onDataChannel = ev => {
17011
+ var _a;
17012
+ (_a = this.onDataChannel) === null || _a === void 0 ? void 0 : _a.call(this, ev);
17013
+ };
17014
+ this.subscriber.onTrack = ev => {
17015
+ var _a;
17016
+ (_a = this.onTrack) === null || _a === void 0 ? void 0 : _a.call(this, ev);
17017
+ };
17018
+ }
16670
17019
  this.publisher.onConnectionStateChange = this.updateState;
16671
- this.subscriber.onConnectionStateChange = this.updateState;
16672
17020
  this.publisher.onIceConnectionStateChange = this.updateState;
16673
- this.subscriber.onIceConnectionStateChange = this.updateState;
16674
17021
  this.publisher.onSignalingStatechange = this.updateState;
16675
- this.subscriber.onSignalingStatechange = this.updateState;
16676
17022
  this.publisher.onIceCandidate = candidate => {
16677
17023
  var _a;
16678
17024
  (_a = this.onIceCandidate) === null || _a === void 0 ? void 0 : _a.call(this, candidate, SignalTarget.PUBLISHER);
16679
17025
  };
16680
- this.subscriber.onIceCandidate = candidate => {
16681
- var _a;
16682
- (_a = this.onIceCandidate) === null || _a === void 0 ? void 0 : _a.call(this, candidate, SignalTarget.SUBSCRIBER);
16683
- };
16684
- // in subscriber primary mode, server side opens sub data channels.
16685
- this.subscriber.onDataChannel = ev => {
16686
- var _a;
16687
- (_a = this.onDataChannel) === null || _a === void 0 ? void 0 : _a.call(this, ev);
16688
- };
16689
- this.subscriber.onTrack = ev => {
17026
+ this.publisher.onTrack = ev => {
16690
17027
  var _a;
16691
17028
  (_a = this.onTrack) === null || _a === void 0 ? void 0 : _a.call(this, ev);
16692
17029
  };
@@ -16707,11 +17044,6 @@ class PCTransportManager {
16707
17044
  this.isPublisherConnectionRequired = require;
16708
17045
  this.updateState();
16709
17046
  }
16710
- requireSubscriber() {
16711
- let require = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
16712
- this.isSubscriberConnectionRequired = require;
16713
- this.updateState();
16714
- }
16715
17047
  createAndSendPublisherOffer(options) {
16716
17048
  return this.publisher.createAndSendOffer(options);
16717
17049
  }
@@ -16723,6 +17055,7 @@ class PCTransportManager {
16723
17055
  }
16724
17056
  close() {
16725
17057
  return __awaiter(this, void 0, void 0, function* () {
17058
+ var _a;
16726
17059
  if (this.publisher && this.publisher.getSignallingState() !== 'closed') {
16727
17060
  const publisher = this.publisher;
16728
17061
  for (const sender of publisher.getSenders()) {
@@ -16738,13 +17071,15 @@ class PCTransportManager {
16738
17071
  }
16739
17072
  }
16740
17073
  }
16741
- yield Promise.all([this.publisher.close(), this.subscriber.close()]);
17074
+ yield Promise.all([this.publisher.close(), (_a = this.subscriber) === null || _a === void 0 ? void 0 : _a.close()]);
16742
17075
  this.updateState();
16743
17076
  });
16744
17077
  }
16745
17078
  triggerIceRestart() {
16746
17079
  return __awaiter(this, void 0, void 0, function* () {
16747
- this.subscriber.restartingIce = true;
17080
+ if (this.subscriber) {
17081
+ this.subscriber.restartingIce = true;
17082
+ }
16748
17083
  // only restart publisher if it's needed
16749
17084
  if (this.needsPublisher) {
16750
17085
  yield this.createAndSendPublisherOffer({
@@ -16755,28 +17090,30 @@ class PCTransportManager {
16755
17090
  }
16756
17091
  addIceCandidate(candidate, target) {
16757
17092
  return __awaiter(this, void 0, void 0, function* () {
17093
+ var _a;
16758
17094
  if (target === SignalTarget.PUBLISHER) {
16759
17095
  yield this.publisher.addIceCandidate(candidate);
16760
17096
  } else {
16761
- yield this.subscriber.addIceCandidate(candidate);
17097
+ yield (_a = this.subscriber) === null || _a === void 0 ? void 0 : _a.addIceCandidate(candidate);
16762
17098
  }
16763
17099
  });
16764
17100
  }
16765
17101
  createSubscriberAnswerFromOffer(sd, offerId) {
16766
17102
  return __awaiter(this, void 0, void 0, function* () {
17103
+ var _a, _b, _c;
16767
17104
  this.log.debug('received server offer', Object.assign(Object.assign({}, this.logContext), {
16768
17105
  RTCSdpType: sd.type,
16769
17106
  sdp: sd.sdp,
16770
- signalingState: this.subscriber.getSignallingState().toString()
17107
+ signalingState: (_a = this.subscriber) === null || _a === void 0 ? void 0 : _a.getSignallingState().toString()
16771
17108
  }));
16772
17109
  const unlock = yield this.remoteOfferLock.lock();
16773
17110
  try {
16774
- const success = yield this.subscriber.setRemoteDescription(sd, offerId);
17111
+ const success = yield (_b = this.subscriber) === null || _b === void 0 ? void 0 : _b.setRemoteDescription(sd, offerId);
16775
17112
  if (!success) {
16776
17113
  return undefined;
16777
17114
  }
16778
17115
  // answer the offer
16779
- const answer = yield this.subscriber.createAndSetAnswer();
17116
+ const answer = yield (_c = this.subscriber) === null || _c === void 0 ? void 0 : _c.createAndSetAnswer();
16780
17117
  return answer;
16781
17118
  } finally {
16782
17119
  unlock();
@@ -16784,8 +17121,9 @@ class PCTransportManager {
16784
17121
  });
16785
17122
  }
16786
17123
  updateConfiguration(config, iceRestart) {
17124
+ var _a;
16787
17125
  this.publisher.setConfiguration(config);
16788
- this.subscriber.setConfiguration(config);
17126
+ (_a = this.subscriber) === null || _a === void 0 ? void 0 : _a.setConfiguration(config);
16789
17127
  if (iceRestart) {
16790
17128
  this.triggerIceRestart();
16791
17129
  }
@@ -16835,6 +17173,9 @@ class PCTransportManager {
16835
17173
  addPublisherTransceiver(track, transceiverInit) {
16836
17174
  return this.publisher.addTransceiver(track, transceiverInit);
16837
17175
  }
17176
+ addPublisherTransceiverOfKind(kind, transceiverInit) {
17177
+ return this.publisher.addTransceiverOfKind(kind, transceiverInit);
17178
+ }
16838
17179
  addPublisherTrack(track) {
16839
17180
  return this.publisher.addTrack(track);
16840
17181
  }
@@ -16857,7 +17198,7 @@ class PCTransportManager {
16857
17198
  if (this.isPublisherConnectionRequired) {
16858
17199
  transports.push(this.publisher);
16859
17200
  }
16860
- if (this.isSubscriberConnectionRequired) {
17201
+ if (this.isSubscriberConnectionRequired && this.subscriber) {
16861
17202
  transports.push(this.subscriber);
16862
17203
  }
16863
17204
  return transports;
@@ -19052,7 +19393,9 @@ class RTCEngine extends eventsExports.EventEmitter {
19052
19393
  const decryptedData = yield (_c = this.e2eeManager) === null || _c === void 0 ? void 0 : _c.handleEncryptedData(dp.value.value.encryptedValue, dp.value.value.iv, dp.participantIdentity, dp.value.value.keyIndex);
19053
19394
  const decryptedPacket = EncryptedPacketPayload.fromBinary(decryptedData.payload);
19054
19395
  const newDp = new DataPacket({
19055
- value: decryptedPacket.value
19396
+ value: decryptedPacket.value,
19397
+ participantIdentity: dp.participantIdentity,
19398
+ participantSid: dp.participantSid
19056
19399
  });
19057
19400
  if (((_d = newDp.value) === null || _d === void 0 ? void 0 : _d.case) === 'user') {
19058
19401
  // compatibility
@@ -19367,7 +19710,7 @@ class RTCEngine extends eventsExports.EventEmitter {
19367
19710
  }
19368
19711
  this.participantSid = (_a = joinResponse.participant) === null || _a === void 0 ? void 0 : _a.sid;
19369
19712
  const rtcConfig = this.makeRTCConfiguration(joinResponse);
19370
- this.pcManager = new PCTransportManager(rtcConfig, joinResponse.subscriberPrimary, this.loggerOptions);
19713
+ this.pcManager = new PCTransportManager(rtcConfig, this.options.singlePeerConnection ? 'publisher-only' : joinResponse.subscriberPrimary ? 'subscriber-primary' : 'publisher-primary', this.loggerOptions);
19371
19714
  this.emit(EngineEvent.TransportsCreated, this.pcManager.publisher, this.pcManager.subscriber);
19372
19715
  this.pcManager.onIceCandidate = (candidate, target) => {
19373
19716
  this.client.sendIceCandidate(candidate, target);
@@ -19403,6 +19746,9 @@ class RTCEngine extends eventsExports.EventEmitter {
19403
19746
  }
19404
19747
  });
19405
19748
  this.pcManager.onTrack = ev => {
19749
+ // this fires after the underlying transceiver is stopped and potentially
19750
+ // peer connection closed, so do not bubble up if there are no streams
19751
+ if (ev.streams.length === 0) return;
19406
19752
  this.emit(EngineEvent.MediaTrackAdded, ev.track, ev.streams[0], ev.receiver);
19407
19753
  };
19408
19754
  if (!supportOptionalDatachannel((_b = joinResponse.serverInfo) === null || _b === void 0 ? void 0 : _b.protocol)) {
@@ -19484,6 +19830,19 @@ class RTCEngine extends eventsExports.EventEmitter {
19484
19830
  }
19485
19831
  this.emit(EngineEvent.RoomMoved, res);
19486
19832
  };
19833
+ this.client.onMediaSectionsRequirement = requirement => {
19834
+ var _a, _b;
19835
+ const transceiverInit = {
19836
+ direction: 'recvonly'
19837
+ };
19838
+ for (let i = 0; i < requirement.numAudios; i++) {
19839
+ (_a = this.pcManager) === null || _a === void 0 ? void 0 : _a.addPublisherTransceiverOfKind('audio', transceiverInit);
19840
+ }
19841
+ for (let i = 0; i < requirement.numVideos; i++) {
19842
+ (_b = this.pcManager) === null || _b === void 0 ? void 0 : _b.addPublisherTransceiverOfKind('video', transceiverInit);
19843
+ }
19844
+ this.negotiate();
19845
+ };
19487
19846
  this.client.onClose = () => {
19488
19847
  this.handleDisconnect('signal', ReconnectReason.RR_SIGNAL_DISCONNECTED);
19489
19848
  };
@@ -20113,19 +20472,21 @@ class RTCEngine extends eventsExports.EventEmitter {
20113
20472
  }
20114
20473
  /** @internal */
20115
20474
  sendSyncState(remoteTracks, localTracks) {
20116
- var _a, _b;
20475
+ var _a, _b, _c, _d;
20117
20476
  if (!this.pcManager) {
20118
20477
  this.log.warn('sync state cannot be sent without peer connection setup', this.logContext);
20119
20478
  return;
20120
20479
  }
20121
- const previousAnswer = this.pcManager.subscriber.getLocalDescription();
20122
- const previousOffer = this.pcManager.subscriber.getRemoteDescription();
20480
+ const previousPublisherOffer = this.pcManager.publisher.getLocalDescription();
20481
+ const previousPublisherAnswer = this.pcManager.publisher.getRemoteDescription();
20482
+ const previousSubscriberOffer = (_a = this.pcManager.subscriber) === null || _a === void 0 ? void 0 : _a.getRemoteDescription();
20483
+ const previousSubscriberAnswer = (_b = this.pcManager.subscriber) === null || _b === void 0 ? void 0 : _b.getLocalDescription();
20123
20484
  /* 1. autosubscribe on, so subscribed tracks = all tracks - unsub tracks,
20124
20485
  in this case, we send unsub tracks, so server add all tracks to this
20125
20486
  subscribe pc and unsub special tracks from it.
20126
20487
  2. autosubscribe off, we send subscribed tracks.
20127
20488
  */
20128
- const autoSubscribe = (_b = (_a = this.signalOpts) === null || _a === void 0 ? void 0 : _a.autoSubscribe) !== null && _b !== void 0 ? _b : true;
20489
+ const autoSubscribe = (_d = (_c = this.signalOpts) === null || _c === void 0 ? void 0 : _c.autoSubscribe) !== null && _d !== void 0 ? _d : true;
20129
20490
  const trackSids = new Array();
20130
20491
  const trackSidsDisabled = new Array();
20131
20492
  remoteTracks.forEach(track => {
@@ -20137,13 +20498,19 @@ class RTCEngine extends eventsExports.EventEmitter {
20137
20498
  }
20138
20499
  });
20139
20500
  this.client.sendSyncState(new SyncState({
20140
- answer: previousAnswer ? toProtoSessionDescription({
20141
- sdp: previousAnswer.sdp,
20142
- type: previousAnswer.type
20501
+ answer: this.options.singlePeerConnection ? previousPublisherAnswer ? toProtoSessionDescription({
20502
+ sdp: previousPublisherAnswer.sdp,
20503
+ type: previousPublisherAnswer.type
20504
+ }) : undefined : previousSubscriberAnswer ? toProtoSessionDescription({
20505
+ sdp: previousSubscriberAnswer.sdp,
20506
+ type: previousSubscriberAnswer.type
20143
20507
  }) : undefined,
20144
- offer: previousOffer ? toProtoSessionDescription({
20145
- sdp: previousOffer.sdp,
20146
- type: previousOffer.type
20508
+ offer: this.options.singlePeerConnection ? previousPublisherOffer ? toProtoSessionDescription({
20509
+ sdp: previousPublisherOffer.sdp,
20510
+ type: previousPublisherOffer.type
20511
+ }) : undefined : previousSubscriberOffer ? toProtoSessionDescription({
20512
+ sdp: previousSubscriberOffer.sdp,
20513
+ type: previousSubscriberOffer.type
20147
20514
  }) : undefined,
20148
20515
  subscription: new UpdateSubscription({
20149
20516
  trackSids,
@@ -24803,7 +25170,8 @@ class Room extends eventsExports.EventEmitter {
24803
25170
  adaptiveStream: typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream,
24804
25171
  maxRetries: connectOptions.maxRetries,
24805
25172
  e2eeEnabled: !!this.e2eeManager,
24806
- websocketTimeout: connectOptions.websocketTimeout
25173
+ websocketTimeout: connectOptions.websocketTimeout,
25174
+ singlePeerConnection: roomOptions.singlePeerConnection
24807
25175
  }, abortController.signal);
24808
25176
  let serverInfo = joinResponse.serverInfo;
24809
25177
  if (!serverInfo) {
@@ -24940,8 +25308,9 @@ class Room extends eventsExports.EventEmitter {
24940
25308
  _this2.log.info('disconnect from room', Object.assign({}, _this2.logContext));
24941
25309
  if (_this2.state === ConnectionState.Connecting || _this2.state === ConnectionState.Reconnecting || _this2.isResuming) {
24942
25310
  // try aborting pending connection attempt
24943
- _this2.log.warn('abort connection attempt', _this2.logContext);
24944
- (_a = _this2.abortController) === null || _a === void 0 ? void 0 : _a.abort();
25311
+ const msg = 'Abort connection attempt due to user initiated disconnect';
25312
+ _this2.log.warn(msg, _this2.logContext);
25313
+ (_a = _this2.abortController) === null || _a === void 0 ? void 0 : _a.abort(msg);
24945
25314
  // in case the abort controller didn't manage to cancel the connection attempt, reject the connect promise explicitly
24946
25315
  (_c = (_b = _this2.connectFuture) === null || _b === void 0 ? void 0 : _b.reject) === null || _c === void 0 ? void 0 : _c.call(_b, new ConnectionError('Client initiated disconnect', ConnectionErrorReason.Cancelled));
24947
25316
  _this2.connectFuture = undefined;
@@ -25513,6 +25882,7 @@ class Room extends eventsExports.EventEmitter {
25513
25882
  if (e2eeOptions) {
25514
25883
  if ('e2eeManager' in e2eeOptions) {
25515
25884
  this.e2eeManager = e2eeOptions.e2eeManager;
25885
+ this.e2eeManager.isDataChannelEncryptionEnabled = dcEncryptionEnabled;
25516
25886
  } else {
25517
25887
  this.e2eeManager = new E2EEManager(e2eeOptions, dcEncryptionEnabled);
25518
25888
  }
@@ -27087,7 +27457,8 @@ class TURNCheck extends Checker {
27087
27457
  autoSubscribe: true,
27088
27458
  maxRetries: 0,
27089
27459
  e2eeEnabled: false,
27090
- websocketTimeout: 15000
27460
+ websocketTimeout: 15000,
27461
+ singlePeerConnection: false
27091
27462
  });
27092
27463
  let hasTLS = false;
27093
27464
  let hasTURN = false;
@@ -27137,6 +27508,7 @@ class WebRTCCheck extends Checker {
27137
27508
  let hasTcp = false;
27138
27509
  let hasIpv4Udp = false;
27139
27510
  this.room.on(RoomEvent.SignalConnected, () => {
27511
+ var _a;
27140
27512
  const prevTrickle = this.room.engine.client.onTrickle;
27141
27513
  this.room.engine.client.onTrickle = (sd, target) => {
27142
27514
  if (sd.candidate) {
@@ -27160,7 +27532,7 @@ class WebRTCCheck extends Checker {
27160
27532
  prevTrickle(sd, target);
27161
27533
  }
27162
27534
  };
27163
- if (this.room.engine.pcManager) {
27535
+ if ((_a = this.room.engine.pcManager) === null || _a === void 0 ? void 0 : _a.subscriber) {
27164
27536
  this.room.engine.pcManager.subscriber.onIceCandidateError = ev => {
27165
27537
  if (ev instanceof RTCPeerConnectionIceErrorEvent) {
27166
27538
  this.appendWarning("error with ICE candidate: ".concat(ev.errorCode, " ").concat(ev.errorText, " ").concat(ev.url));
@@ -27216,7 +27588,8 @@ class WebSocketCheck extends Checker {
27216
27588
  autoSubscribe: true,
27217
27589
  maxRetries: 0,
27218
27590
  e2eeEnabled: false,
27219
- websocketTimeout: 15000
27591
+ websocketTimeout: 15000,
27592
+ singlePeerConnection: false
27220
27593
  });
27221
27594
  this.appendMessage("Connected to server, version ".concat(joinRes.serverVersion, "."));
27222
27595
  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)) {
@@ -27584,15 +27957,17 @@ function decodeJwt(jwt) {
27584
27957
 
27585
27958
  const ONE_SECOND_IN_MILLISECONDS = 1000;
27586
27959
  const ONE_MINUTE_IN_MILLISECONDS = 60 * ONE_SECOND_IN_MILLISECONDS;
27587
- function isResponseExpired(response) {
27960
+ function isResponseTokenValid(response) {
27588
27961
  const jwtPayload = decodeTokenPayload(response.participantToken);
27589
- if (!(jwtPayload === null || jwtPayload === void 0 ? void 0 : jwtPayload.exp)) {
27962
+ if (!(jwtPayload === null || jwtPayload === void 0 ? void 0 : jwtPayload.nbf) || !(jwtPayload === null || jwtPayload === void 0 ? void 0 : jwtPayload.exp)) {
27590
27963
  return true;
27591
27964
  }
27592
- const expInMilliseconds = jwtPayload.exp * ONE_SECOND_IN_MILLISECONDS;
27593
- const expiresAt = new Date(expInMilliseconds - ONE_MINUTE_IN_MILLISECONDS);
27594
27965
  const now = new Date();
27595
- return expiresAt >= now;
27966
+ const nbfInMilliseconds = jwtPayload.nbf * ONE_SECOND_IN_MILLISECONDS;
27967
+ const nbfDate = new Date(nbfInMilliseconds);
27968
+ const expInMilliseconds = jwtPayload.exp * ONE_SECOND_IN_MILLISECONDS;
27969
+ const expDate = new Date(expInMilliseconds - ONE_MINUTE_IN_MILLISECONDS);
27970
+ return nbfDate <= now && expDate > now;
27596
27971
  }
27597
27972
  function decodeTokenPayload(token) {
27598
27973
  const payload = decodeJwt(token);
@@ -27644,7 +28019,7 @@ class TokenSourceCached extends TokenSourceConfigurable {
27644
28019
  if (!this.cachedResponse) {
27645
28020
  return false;
27646
28021
  }
27647
- if (isResponseExpired(this.cachedResponse)) {
28022
+ if (!isResponseTokenValid(this.cachedResponse)) {
27648
28023
  return false;
27649
28024
  }
27650
28025
  if (this.isSameAsCachedFetchOptions(fetchOptions)) {
@@ -27913,5 +28288,5 @@ function isFacingModeValue(item) {
27913
28288
  return item === undefined || allowedValues.includes(item);
27914
28289
  }
27915
28290
 
27916
- export { AudioPresets, BackupCodecPolicy, BaseKeyProvider, CheckStatus, Checker, ConnectionCheck, ConnectionError, ConnectionErrorReason, ConnectionQuality, ConnectionState, CriticalTimers, CryptorError, CryptorErrorReason, CryptorEvent, DataPacket_Kind, DataStreamError, DataStreamErrorReason, DefaultReconnectPolicy, DeviceUnsupportedError, DisconnectReason, EncryptionEvent, Encryption_Type, EngineEvent, ExternalE2EEKeyProvider, KeyHandlerEvent, KeyProviderEvent, LivekitError, 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, SignalRequestError, SubscriptionError, TokenSource, TokenSourceConfigurable, TokenSourceCustom, TokenSourceEndpoint, TokenSourceFixed, TokenSourceLiteral, TokenSourceSandboxTokenServer, Track, TrackEvent, TrackInvalidError, TrackPublication, TrackType, UnexpectedConnectionState, UnsupportedServer, VideoPreset, VideoPresets, VideoPresets43, VideoQuality, asEncryptablePacket, attachToElement, attributeTypings as attributes, audioCodecs, compareVersions, createAudioAnalyser, createE2EEKey, createKeyMaterialFromBuffer, createKeyMaterialFromString, createLocalAudioTrack, createLocalScreenTracks, createLocalTracks, createLocalVideoTrack, 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, supportsDynacast, supportsVP9, version, videoCodecs, writeRbsp };
28291
+ export { AudioPresets, BackupCodecPolicy, BaseKeyProvider, CheckStatus, Checker, ConnectionCheck, ConnectionError, ConnectionErrorReason, ConnectionQuality, ConnectionState, CriticalTimers, CryptorError, CryptorErrorReason, CryptorEvent, DataPacket_Kind, DataStreamError, DataStreamErrorReason, DefaultReconnectPolicy, DeviceUnsupportedError, DisconnectReason, EncryptionEvent, Encryption_Type, EngineEvent, ExternalE2EEKeyProvider, KeyHandlerEvent, KeyProviderEvent, LivekitError, 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, SignalRequestError, SubscriptionError, TokenSource, TokenSourceConfigurable, TokenSourceCustom, TokenSourceEndpoint, TokenSourceFixed, TokenSourceLiteral, TokenSourceSandboxTokenServer, Track, TrackEvent, TrackInvalidError, TrackPublication, TrackType, UnexpectedConnectionState, UnsupportedServer, VideoPreset, VideoPresets, VideoPresets43, VideoQuality, asEncryptablePacket, attachToElement, attributeTypings as attributes, audioCodecs, compareVersions, createAudioAnalyser, createE2EEKey, createKeyMaterialFromBuffer, createKeyMaterialFromString, createLocalAudioTrack, createLocalScreenTracks, createLocalTracks, createLocalVideoTrack, 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 };
27917
28292
  //# sourceMappingURL=livekit-client.esm.mjs.map