livekit-client 2.15.8 → 2.15.10

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 (59) hide show
  1. package/dist/livekit-client.esm.mjs +589 -205
  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 +769 -0
  41. package/src/api/SignalClient.ts +319 -162
  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 +59 -17
  53. package/src/room/Room.ts +5 -2
  54. package/src/room/defaults.ts +1 -0
  55. package/src/room/participant/LocalParticipant.ts +2 -2
  56. package/src/room/token-source/TokenSource.ts +2 -2
  57. package/src/room/token-source/utils.test.ts +63 -0
  58. package/src/room/token-source/utils.ts +10 -5
  59. 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.10";
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,149 @@ 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;
14563
- 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));
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
+ // send leave if we have an active stream writer (connection is open)
14759
+ if (this.streamWriter) {
14760
+ this.sendLeave().then(() => this.close()).catch(e => {
14761
+ this.log.error(e);
14762
+ this.close();
14763
+ });
14764
+ } else {
14765
+ this.close();
14574
14766
  }
14575
- return;
14767
+ clearTimeout(wsTimeout);
14768
+ const target = event.currentTarget;
14769
+ reject(target instanceof AbortSignal ? target.reason : target);
14770
+ });
14771
+ combinedAbort.addEventListener('abort', abortHandler);
14772
+ const wsTimeout = setTimeout(() => {
14773
+ timeoutAbortController.abort(new ConnectionError('room connection has timed out (signal)', ConnectionErrorReason.ServerUnreachable));
14774
+ }, opts.websocketTimeout);
14775
+ const handleSignalConnected = (connection, firstMessage) => {
14776
+ this.handleSignalConnected(connection, wsTimeout, firstMessage);
14777
+ };
14778
+ const redactedUrl = new URL(rtcUrl);
14779
+ if (redactedUrl.searchParams.has('access_token')) {
14780
+ redactedUrl.searchParams.set('access_token', '<redacted>');
14576
14781
  }
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;
14782
+ this.log.debug("connecting to ".concat(redactedUrl), Object.assign({
14783
+ reconnect: opts.reconnect,
14784
+ reconnectReason: opts.reconnectReason
14785
+ }, this.logContext));
14786
+ if (this.ws) {
14787
+ yield this.close(false);
14594
14788
  }
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;
14789
+ this.ws = new WebSocketStream(rtcUrl, {
14790
+ signal: combinedAbort
14791
+ });
14792
+ try {
14793
+ this.ws.closed.then(closeInfo => {
14794
+ if (this.isEstablishingConnection) {
14795
+ reject(new ConnectionError("Websocket got closed during a (re)connection attempt: ".concat(closeInfo.reason), ConnectionErrorReason.InternalError));
14796
+ }
14797
+ if (closeInfo.closeCode !== 1000) {
14798
+ this.log.warn("websocket closed", Object.assign(Object.assign({}, this.logContext), {
14799
+ reason: closeInfo.reason,
14800
+ code: closeInfo.closeCode,
14801
+ wasClean: closeInfo.closeCode === 1000,
14802
+ state: this.state
14803
+ }));
14804
+ }
14805
+ return;
14806
+ }).catch(reason => {
14807
+ if (this.isEstablishingConnection) {
14808
+ reject(new ConnectionError("Websocket error during a (re)connection attempt: ".concat(reason), ConnectionErrorReason.InternalError));
14809
+ }
14810
+ });
14811
+ const connection = yield this.ws.opened.catch(reason => __awaiter(this, void 0, void 0, function* () {
14812
+ if (this.state !== SignalConnectionState.CONNECTED) {
14813
+ this.state = SignalConnectionState.DISCONNECTED;
14814
+ clearTimeout(wsTimeout);
14815
+ const error = yield this.handleConnectionError(reason, validateUrl);
14816
+ reject(error);
14817
+ return;
14818
+ }
14819
+ // other errors, handle
14820
+ this.handleWSError(reason);
14821
+ reject(reason);
14822
+ return;
14823
+ }));
14824
+ clearTimeout(wsTimeout);
14825
+ if (!connection) {
14826
+ return;
14827
+ }
14828
+ const signalReader = connection.readable.getReader();
14829
+ this.streamWriter = connection.writable.getWriter();
14830
+ const firstMessage = yield signalReader.read();
14831
+ signalReader.releaseLock();
14832
+ if (!firstMessage.value) {
14833
+ throw new ConnectionError('no message received as first message', ConnectionErrorReason.InternalError);
14834
+ }
14835
+ const firstSignalResponse = parseSignalResponse(firstMessage.value);
14836
+ // Validate the first message
14837
+ const validation = this.validateFirstMessage(firstSignalResponse, (_a = opts.reconnect) !== null && _a !== void 0 ? _a : false);
14838
+ if (!validation.isValid) {
14839
+ reject(validation.error);
14840
+ return;
14841
+ }
14842
+ // Handle join response - set up ping configuration
14843
+ if (((_b = firstSignalResponse.message) === null || _b === void 0 ? void 0 : _b.case) === 'join') {
14844
+ this.pingTimeoutDuration = firstSignalResponse.message.value.pingTimeout;
14845
+ this.pingIntervalDuration = firstSignalResponse.message.value.pingInterval;
14603
14846
  if (this.pingTimeoutDuration && this.pingTimeoutDuration > 0) {
14604
14847
  this.log.debug('ping config', Object.assign(Object.assign({}, this.logContext), {
14605
14848
  timeout: this.pingTimeoutDuration,
14606
14849
  interval: this.pingIntervalDuration
14607
14850
  }));
14608
- this.startPingInterval();
14609
14851
  }
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
14852
  }
14853
+ // Handle successful connection
14854
+ const firstMessageToProcess = validation.shouldProcessFirstMessage ? firstSignalResponse : undefined;
14855
+ handleSignalConnected(connection, firstMessageToProcess);
14856
+ resolve(validation.response);
14857
+ } catch (e) {
14858
+ clearTimeout(wsTimeout);
14859
+ reject(e);
14632
14860
  }
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();
14861
+ } finally {
14862
+ unlock();
14863
+ }
14864
+ }));
14865
+ });
14866
+ }
14867
+ startReadingLoop(signalReader, firstMessage) {
14868
+ return __awaiter(this, void 0, void 0, function* () {
14869
+ if (firstMessage) {
14870
+ this.handleSignalResponse(firstMessage);
14652
14871
  }
14653
- }));
14872
+ while (true) {
14873
+ if (this.signalLatency) {
14874
+ yield sleep(this.signalLatency);
14875
+ }
14876
+ const {
14877
+ done,
14878
+ value
14879
+ } = yield signalReader.read();
14880
+ if (done) {
14881
+ break;
14882
+ }
14883
+ const resp = parseSignalResponse(value);
14884
+ this.handleSignalResponse(resp);
14885
+ }
14886
+ });
14654
14887
  }
14655
14888
  close() {
14656
14889
  return __awaiter(this, arguments, void 0, function () {
@@ -14664,26 +14897,20 @@ class SignalClient {
14664
14897
  _this.state = SignalConnectionState.DISCONNECTING;
14665
14898
  }
14666
14899
  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
- }
14900
+ _this.ws.close({
14901
+ closeCode: 1000,
14902
+ reason: 'Close method called on signal client'
14679
14903
  });
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
- }
14904
+ // calling `ws.close()` only starts the closing handshake (CLOSING state), prefer to wait until state is actually CLOSED
14905
+ const closePromise = _this.ws.closed;
14685
14906
  _this.ws = undefined;
14907
+ _this.streamWriter = undefined;
14908
+ yield Promise.race([closePromise, sleep(MAX_WS_CLOSE_TIME)]);
14686
14909
  }
14910
+ } catch (e) {
14911
+ _this.log.debug('websocket error while closing', Object.assign(Object.assign({}, _this.logContext), {
14912
+ error: e
14913
+ }));
14687
14914
  } finally {
14688
14915
  if (updateState) {
14689
14916
  _this.state = SignalConnectionState.DISCONNECTED;
@@ -14860,7 +15087,7 @@ class SignalClient {
14860
15087
  _this3.log.debug("skipping signal request (type: ".concat(message.case, ") - SignalClient disconnected"));
14861
15088
  return;
14862
15089
  }
14863
- if (!_this3.ws || _this3.ws.readyState !== _this3.ws.OPEN) {
15090
+ if (!_this3.streamWriter) {
14864
15091
  _this3.log.error("cannot send signal request before connected, type: ".concat(message === null || message === void 0 ? void 0 : message.case), _this3.logContext);
14865
15092
  return;
14866
15093
  }
@@ -14869,9 +15096,9 @@ class SignalClient {
14869
15096
  });
14870
15097
  try {
14871
15098
  if (_this3.useJSON) {
14872
- _this3.ws.send(req.toJsonString());
15099
+ yield _this3.streamWriter.write(req.toJsonString());
14873
15100
  } else {
14874
- _this3.ws.send(req.toBinary());
15101
+ yield _this3.streamWriter.write(req.toBinary());
14875
15102
  }
14876
15103
  } catch (e) {
14877
15104
  _this3.log.error('error sending signal message', Object.assign(Object.assign({}, _this3.logContext), {
@@ -14975,6 +15202,10 @@ class SignalClient {
14975
15202
  if (this.onRoomMoved) {
14976
15203
  this.onRoomMoved(msg.value);
14977
15204
  }
15205
+ } else if (msg.case === 'mediaSectionsRequirement') {
15206
+ if (this.onMediaSectionsRequirement) {
15207
+ this.onMediaSectionsRequirement(msg.value);
15208
+ }
14978
15209
  } else {
14979
15210
  this.log.debug('unsupported message', Object.assign(Object.assign({}, this.logContext), {
14980
15211
  msgCase: msg.case
@@ -15005,9 +15236,9 @@ class SignalClient {
15005
15236
  }
15006
15237
  });
15007
15238
  }
15008
- handleWSError(ev) {
15239
+ handleWSError(error) {
15009
15240
  this.log.error('websocket error', Object.assign(Object.assign({}, this.logContext), {
15010
- error: ev
15241
+ error
15011
15242
  }));
15012
15243
  }
15013
15244
  /**
@@ -15052,6 +15283,89 @@ class SignalClient {
15052
15283
  CriticalTimers.clearInterval(this.pingInterval);
15053
15284
  }
15054
15285
  }
15286
+ /**
15287
+ * Handles the successful connection to the signal server
15288
+ * @param connection The WebSocket connection
15289
+ * @param timeoutHandle The timeout handle to clear
15290
+ * @param firstMessage Optional first message to process
15291
+ * @internal
15292
+ */
15293
+ handleSignalConnected(connection, timeoutHandle, firstMessage) {
15294
+ this.state = SignalConnectionState.CONNECTED;
15295
+ clearTimeout(timeoutHandle);
15296
+ this.startPingInterval();
15297
+ this.startReadingLoop(connection.readable.getReader(), firstMessage);
15298
+ }
15299
+ /**
15300
+ * Validates the first message received from the signal server
15301
+ * @param firstSignalResponse The first signal response received
15302
+ * @param isReconnect Whether this is a reconnection attempt
15303
+ * @returns Validation result with response or error
15304
+ * @internal
15305
+ */
15306
+ validateFirstMessage(firstSignalResponse, isReconnect) {
15307
+ var _a, _b, _c, _d, _e;
15308
+ if (((_a = firstSignalResponse.message) === null || _a === void 0 ? void 0 : _a.case) === 'join') {
15309
+ return {
15310
+ isValid: true,
15311
+ response: firstSignalResponse.message.value
15312
+ };
15313
+ } else if (this.state === SignalConnectionState.RECONNECTING && ((_b = firstSignalResponse.message) === null || _b === void 0 ? void 0 : _b.case) !== 'leave') {
15314
+ if (((_c = firstSignalResponse.message) === null || _c === void 0 ? void 0 : _c.case) === 'reconnect') {
15315
+ return {
15316
+ isValid: true,
15317
+ response: firstSignalResponse.message.value
15318
+ };
15319
+ } else {
15320
+ // in reconnecting, any message received means signal reconnected and we still need to process it
15321
+ this.log.debug('declaring signal reconnected without reconnect response received', this.logContext);
15322
+ return {
15323
+ isValid: true,
15324
+ response: undefined,
15325
+ shouldProcessFirstMessage: true
15326
+ };
15327
+ }
15328
+ } else if (this.isEstablishingConnection && ((_d = firstSignalResponse.message) === null || _d === void 0 ? void 0 : _d.case) === 'leave') {
15329
+ return {
15330
+ isValid: false,
15331
+ error: new ConnectionError('Received leave request while trying to (re)connect', ConnectionErrorReason.LeaveRequest, undefined, firstSignalResponse.message.value.reason)
15332
+ };
15333
+ } else if (!isReconnect) {
15334
+ // non-reconnect case, should receive join response first
15335
+ return {
15336
+ isValid: false,
15337
+ error: new ConnectionError("did not receive join response, got ".concat((_e = firstSignalResponse.message) === null || _e === void 0 ? void 0 : _e.case, " instead"), ConnectionErrorReason.InternalError)
15338
+ };
15339
+ }
15340
+ return {
15341
+ isValid: false,
15342
+ error: new ConnectionError('Unexpected first message', ConnectionErrorReason.InternalError)
15343
+ };
15344
+ }
15345
+ /**
15346
+ * Handles WebSocket connection errors by validating with the server
15347
+ * @param reason The error that occurred
15348
+ * @param validateUrl The URL to validate the connection with
15349
+ * @returns A ConnectionError with appropriate reason and status
15350
+ * @internal
15351
+ */
15352
+ handleConnectionError(reason, validateUrl) {
15353
+ return __awaiter(this, void 0, void 0, function* () {
15354
+ try {
15355
+ const resp = yield fetch(validateUrl);
15356
+ if (resp.status.toFixed(0).startsWith('4')) {
15357
+ const msg = yield resp.text();
15358
+ return new ConnectionError(msg, ConnectionErrorReason.NotAllowed, resp.status);
15359
+ } else if (reason instanceof ConnectionError) {
15360
+ return reason;
15361
+ } else {
15362
+ return new ConnectionError("Encountered unknown websocket error during connection: ".concat(reason), ConnectionErrorReason.InternalError, resp.status);
15363
+ }
15364
+ } catch (e) {
15365
+ return e instanceof ConnectionError ? e : new ConnectionError(e instanceof Error ? e.message : 'server was not reachable', ConnectionErrorReason.ServerUnreachable);
15366
+ }
15367
+ });
15368
+ }
15055
15369
  }
15056
15370
  function fromProtoSessionDescription(sd) {
15057
15371
  const rsd = {
@@ -15120,6 +15434,27 @@ function createConnectionParams(token, info, opts) {
15120
15434
  }
15121
15435
  return params;
15122
15436
  }
15437
+ function createJoinRequestConnectionParams(token, info, opts) {
15438
+ const params = new URLSearchParams();
15439
+ params.set('access_token', token);
15440
+ const joinRequest = new JoinRequest({
15441
+ clientInfo: info,
15442
+ connectionSettings: new ConnectionSettings({
15443
+ autoSubscribe: !!opts.autoSubscribe,
15444
+ adaptiveStream: !!opts.adaptiveStream
15445
+ }),
15446
+ reconnect: !!opts.reconnect,
15447
+ participantSid: opts.sid ? opts.sid : undefined
15448
+ });
15449
+ if (opts.reconnectReason) {
15450
+ joinRequest.reconnectReason = opts.reconnectReason;
15451
+ }
15452
+ const wrappedJoinRequest = new WrappedJoinRequest({
15453
+ joinRequest: joinRequest.toBinary()
15454
+ });
15455
+ params.set('join_request', btoa(new TextDecoder('utf-8').decode(wrappedJoinRequest.toBinary())));
15456
+ return params;
15457
+ }
15123
15458
 
15124
15459
  class DataPacketBuffer {
15125
15460
  constructor() {
@@ -16132,7 +16467,7 @@ class PCTransport extends eventsExports.EventEmitter {
16132
16467
  sdpParsed.media.forEach(media => {
16133
16468
  const mid = getMidString(media.mid);
16134
16469
  if (media.type === 'audio') {
16135
- // mung sdp for opus bitrate settings
16470
+ // munge sdp for opus bitrate settings
16136
16471
  this.trackBitrates.some(trackbr => {
16137
16472
  if (!trackbr.transceiver || mid != trackbr.transceiver.mid) {
16138
16473
  return false;
@@ -16237,7 +16572,7 @@ class PCTransport extends eventsExports.EventEmitter {
16237
16572
  sdpParsed.media.forEach(media => {
16238
16573
  ensureIPAddrMatchVersion(media);
16239
16574
  if (media.type === 'audio') {
16240
- ensureAudioNackAndStereo(media, [], []);
16575
+ ensureAudioNackAndStereo(media, ['all'], []);
16241
16576
  } else if (media.type === 'video') {
16242
16577
  this.trackBitrates.some(trackbr => {
16243
16578
  if (!media.msid || !trackbr.cid || !media.msid.includes(trackbr.cid)) {
@@ -16313,6 +16648,9 @@ class PCTransport extends eventsExports.EventEmitter {
16313
16648
  addTransceiver(mediaStreamTrack, transceiverInit) {
16314
16649
  return this.pc.addTransceiver(mediaStreamTrack, transceiverInit);
16315
16650
  }
16651
+ addTransceiverOfKind(kind, transceiverInit) {
16652
+ return this.pc.addTransceiver(kind, transceiverInit);
16653
+ }
16316
16654
  addTrack(track) {
16317
16655
  if (!this._pc) {
16318
16656
  throw new UnexpectedConnectionState('PC closed, cannot add track');
@@ -16507,7 +16845,7 @@ function ensureAudioNackAndStereo(media, stereoMids, nackMids) {
16507
16845
  type: 'nack'
16508
16846
  });
16509
16847
  }
16510
- if (stereoMids.includes(mid)) {
16848
+ if (stereoMids.includes(mid) || stereoMids.length === 1 && stereoMids[0] === 'all') {
16511
16849
  media.fmtp.some(fmtp => {
16512
16850
  if (fmtp.payload === opusPayload) {
16513
16851
  if (!fmtp.config.includes('stereo=1')) {
@@ -16607,7 +16945,8 @@ const roomOptionDefaults = {
16607
16945
  stopLocalTrackOnUnpublish: true,
16608
16946
  reconnectPolicy: new DefaultReconnectPolicy(),
16609
16947
  disconnectOnPageLeave: true,
16610
- webAudioMix: false
16948
+ webAudioMix: false,
16949
+ singlePeerConnection: false
16611
16950
  };
16612
16951
  const roomConnectOptionDefaults = {
16613
16952
  autoSubscribe: true,
@@ -16635,12 +16974,12 @@ class PCTransportManager {
16635
16974
  get currentState() {
16636
16975
  return this.state;
16637
16976
  }
16638
- constructor(rtcConfig, subscriberPrimary, loggerOptions) {
16977
+ constructor(rtcConfig, mode, loggerOptions) {
16639
16978
  var _a;
16640
16979
  this.peerConnectionTimeout = roomConnectOptionDefaults.peerConnectionTimeout;
16641
16980
  this.log = livekitLogger;
16642
16981
  this.updateState = () => {
16643
- var _a;
16982
+ var _a, _b;
16644
16983
  const previousState = this.state;
16645
16984
  const connectionStates = this.requiredTransports.map(tr => tr.getConnectionState());
16646
16985
  if (connectionStates.every(st => st === 'connected')) {
@@ -16658,35 +16997,41 @@ class PCTransportManager {
16658
16997
  }
16659
16998
  if (previousState !== this.state) {
16660
16999
  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());
17000
+ (_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
17001
  }
16663
17002
  };
16664
17003
  this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.PCManager);
16665
17004
  this.loggerOptions = loggerOptions;
16666
- this.isPublisherConnectionRequired = !subscriberPrimary;
16667
- this.isSubscriberConnectionRequired = subscriberPrimary;
17005
+ this.isPublisherConnectionRequired = mode !== 'subscriber-primary';
17006
+ this.isSubscriberConnectionRequired = mode === 'subscriber-primary';
16668
17007
  this.publisher = new PCTransport(rtcConfig, loggerOptions);
16669
- this.subscriber = new PCTransport(rtcConfig, loggerOptions);
17008
+ if (mode !== 'publisher-only') {
17009
+ this.subscriber = new PCTransport(rtcConfig, loggerOptions);
17010
+ this.subscriber.onConnectionStateChange = this.updateState;
17011
+ this.subscriber.onIceConnectionStateChange = this.updateState;
17012
+ this.subscriber.onSignalingStatechange = this.updateState;
17013
+ this.subscriber.onIceCandidate = candidate => {
17014
+ var _a;
17015
+ (_a = this.onIceCandidate) === null || _a === void 0 ? void 0 : _a.call(this, candidate, SignalTarget.SUBSCRIBER);
17016
+ };
17017
+ // in subscriber primary mode, server side opens sub data channels.
17018
+ this.subscriber.onDataChannel = ev => {
17019
+ var _a;
17020
+ (_a = this.onDataChannel) === null || _a === void 0 ? void 0 : _a.call(this, ev);
17021
+ };
17022
+ this.subscriber.onTrack = ev => {
17023
+ var _a;
17024
+ (_a = this.onTrack) === null || _a === void 0 ? void 0 : _a.call(this, ev);
17025
+ };
17026
+ }
16670
17027
  this.publisher.onConnectionStateChange = this.updateState;
16671
- this.subscriber.onConnectionStateChange = this.updateState;
16672
17028
  this.publisher.onIceConnectionStateChange = this.updateState;
16673
- this.subscriber.onIceConnectionStateChange = this.updateState;
16674
17029
  this.publisher.onSignalingStatechange = this.updateState;
16675
- this.subscriber.onSignalingStatechange = this.updateState;
16676
17030
  this.publisher.onIceCandidate = candidate => {
16677
17031
  var _a;
16678
17032
  (_a = this.onIceCandidate) === null || _a === void 0 ? void 0 : _a.call(this, candidate, SignalTarget.PUBLISHER);
16679
17033
  };
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 => {
17034
+ this.publisher.onTrack = ev => {
16690
17035
  var _a;
16691
17036
  (_a = this.onTrack) === null || _a === void 0 ? void 0 : _a.call(this, ev);
16692
17037
  };
@@ -16707,11 +17052,6 @@ class PCTransportManager {
16707
17052
  this.isPublisherConnectionRequired = require;
16708
17053
  this.updateState();
16709
17054
  }
16710
- requireSubscriber() {
16711
- let require = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
16712
- this.isSubscriberConnectionRequired = require;
16713
- this.updateState();
16714
- }
16715
17055
  createAndSendPublisherOffer(options) {
16716
17056
  return this.publisher.createAndSendOffer(options);
16717
17057
  }
@@ -16723,6 +17063,7 @@ class PCTransportManager {
16723
17063
  }
16724
17064
  close() {
16725
17065
  return __awaiter(this, void 0, void 0, function* () {
17066
+ var _a;
16726
17067
  if (this.publisher && this.publisher.getSignallingState() !== 'closed') {
16727
17068
  const publisher = this.publisher;
16728
17069
  for (const sender of publisher.getSenders()) {
@@ -16738,13 +17079,15 @@ class PCTransportManager {
16738
17079
  }
16739
17080
  }
16740
17081
  }
16741
- yield Promise.all([this.publisher.close(), this.subscriber.close()]);
17082
+ yield Promise.all([this.publisher.close(), (_a = this.subscriber) === null || _a === void 0 ? void 0 : _a.close()]);
16742
17083
  this.updateState();
16743
17084
  });
16744
17085
  }
16745
17086
  triggerIceRestart() {
16746
17087
  return __awaiter(this, void 0, void 0, function* () {
16747
- this.subscriber.restartingIce = true;
17088
+ if (this.subscriber) {
17089
+ this.subscriber.restartingIce = true;
17090
+ }
16748
17091
  // only restart publisher if it's needed
16749
17092
  if (this.needsPublisher) {
16750
17093
  yield this.createAndSendPublisherOffer({
@@ -16755,28 +17098,30 @@ class PCTransportManager {
16755
17098
  }
16756
17099
  addIceCandidate(candidate, target) {
16757
17100
  return __awaiter(this, void 0, void 0, function* () {
17101
+ var _a;
16758
17102
  if (target === SignalTarget.PUBLISHER) {
16759
17103
  yield this.publisher.addIceCandidate(candidate);
16760
17104
  } else {
16761
- yield this.subscriber.addIceCandidate(candidate);
17105
+ yield (_a = this.subscriber) === null || _a === void 0 ? void 0 : _a.addIceCandidate(candidate);
16762
17106
  }
16763
17107
  });
16764
17108
  }
16765
17109
  createSubscriberAnswerFromOffer(sd, offerId) {
16766
17110
  return __awaiter(this, void 0, void 0, function* () {
17111
+ var _a, _b, _c;
16767
17112
  this.log.debug('received server offer', Object.assign(Object.assign({}, this.logContext), {
16768
17113
  RTCSdpType: sd.type,
16769
17114
  sdp: sd.sdp,
16770
- signalingState: this.subscriber.getSignallingState().toString()
17115
+ signalingState: (_a = this.subscriber) === null || _a === void 0 ? void 0 : _a.getSignallingState().toString()
16771
17116
  }));
16772
17117
  const unlock = yield this.remoteOfferLock.lock();
16773
17118
  try {
16774
- const success = yield this.subscriber.setRemoteDescription(sd, offerId);
17119
+ const success = yield (_b = this.subscriber) === null || _b === void 0 ? void 0 : _b.setRemoteDescription(sd, offerId);
16775
17120
  if (!success) {
16776
17121
  return undefined;
16777
17122
  }
16778
17123
  // answer the offer
16779
- const answer = yield this.subscriber.createAndSetAnswer();
17124
+ const answer = yield (_c = this.subscriber) === null || _c === void 0 ? void 0 : _c.createAndSetAnswer();
16780
17125
  return answer;
16781
17126
  } finally {
16782
17127
  unlock();
@@ -16784,8 +17129,9 @@ class PCTransportManager {
16784
17129
  });
16785
17130
  }
16786
17131
  updateConfiguration(config, iceRestart) {
17132
+ var _a;
16787
17133
  this.publisher.setConfiguration(config);
16788
- this.subscriber.setConfiguration(config);
17134
+ (_a = this.subscriber) === null || _a === void 0 ? void 0 : _a.setConfiguration(config);
16789
17135
  if (iceRestart) {
16790
17136
  this.triggerIceRestart();
16791
17137
  }
@@ -16835,6 +17181,9 @@ class PCTransportManager {
16835
17181
  addPublisherTransceiver(track, transceiverInit) {
16836
17182
  return this.publisher.addTransceiver(track, transceiverInit);
16837
17183
  }
17184
+ addPublisherTransceiverOfKind(kind, transceiverInit) {
17185
+ return this.publisher.addTransceiverOfKind(kind, transceiverInit);
17186
+ }
16838
17187
  addPublisherTrack(track) {
16839
17188
  return this.publisher.addTrack(track);
16840
17189
  }
@@ -16857,7 +17206,7 @@ class PCTransportManager {
16857
17206
  if (this.isPublisherConnectionRequired) {
16858
17207
  transports.push(this.publisher);
16859
17208
  }
16860
- if (this.isSubscriberConnectionRequired) {
17209
+ if (this.isSubscriberConnectionRequired && this.subscriber) {
16861
17210
  transports.push(this.subscriber);
16862
17211
  }
16863
17212
  return transports;
@@ -19052,7 +19401,9 @@ class RTCEngine extends eventsExports.EventEmitter {
19052
19401
  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
19402
  const decryptedPacket = EncryptedPacketPayload.fromBinary(decryptedData.payload);
19054
19403
  const newDp = new DataPacket({
19055
- value: decryptedPacket.value
19404
+ value: decryptedPacket.value,
19405
+ participantIdentity: dp.participantIdentity,
19406
+ participantSid: dp.participantSid
19056
19407
  });
19057
19408
  if (((_d = newDp.value) === null || _d === void 0 ? void 0 : _d.case) === 'user') {
19058
19409
  // compatibility
@@ -19367,7 +19718,7 @@ class RTCEngine extends eventsExports.EventEmitter {
19367
19718
  }
19368
19719
  this.participantSid = (_a = joinResponse.participant) === null || _a === void 0 ? void 0 : _a.sid;
19369
19720
  const rtcConfig = this.makeRTCConfiguration(joinResponse);
19370
- this.pcManager = new PCTransportManager(rtcConfig, joinResponse.subscriberPrimary, this.loggerOptions);
19721
+ this.pcManager = new PCTransportManager(rtcConfig, this.options.singlePeerConnection ? 'publisher-only' : joinResponse.subscriberPrimary ? 'subscriber-primary' : 'publisher-primary', this.loggerOptions);
19371
19722
  this.emit(EngineEvent.TransportsCreated, this.pcManager.publisher, this.pcManager.subscriber);
19372
19723
  this.pcManager.onIceCandidate = (candidate, target) => {
19373
19724
  this.client.sendIceCandidate(candidate, target);
@@ -19403,6 +19754,9 @@ class RTCEngine extends eventsExports.EventEmitter {
19403
19754
  }
19404
19755
  });
19405
19756
  this.pcManager.onTrack = ev => {
19757
+ // this fires after the underlying transceiver is stopped and potentially
19758
+ // peer connection closed, so do not bubble up if there are no streams
19759
+ if (ev.streams.length === 0) return;
19406
19760
  this.emit(EngineEvent.MediaTrackAdded, ev.track, ev.streams[0], ev.receiver);
19407
19761
  };
19408
19762
  if (!supportOptionalDatachannel((_b = joinResponse.serverInfo) === null || _b === void 0 ? void 0 : _b.protocol)) {
@@ -19417,7 +19771,8 @@ class RTCEngine extends eventsExports.EventEmitter {
19417
19771
  return;
19418
19772
  }
19419
19773
  this.log.debug('received server answer', Object.assign(Object.assign({}, this.logContext), {
19420
- RTCSdpType: sd.type
19774
+ RTCSdpType: sd.type,
19775
+ sdp: sd.sdp
19421
19776
  }));
19422
19777
  yield this.pcManager.setPublisherAnswer(sd, offerId);
19423
19778
  });
@@ -19484,6 +19839,19 @@ class RTCEngine extends eventsExports.EventEmitter {
19484
19839
  }
19485
19840
  this.emit(EngineEvent.RoomMoved, res);
19486
19841
  };
19842
+ this.client.onMediaSectionsRequirement = requirement => {
19843
+ var _a, _b;
19844
+ const transceiverInit = {
19845
+ direction: 'recvonly'
19846
+ };
19847
+ for (let i = 0; i < requirement.numAudios; i++) {
19848
+ (_a = this.pcManager) === null || _a === void 0 ? void 0 : _a.addPublisherTransceiverOfKind('audio', transceiverInit);
19849
+ }
19850
+ for (let i = 0; i < requirement.numVideos; i++) {
19851
+ (_b = this.pcManager) === null || _b === void 0 ? void 0 : _b.addPublisherTransceiverOfKind('video', transceiverInit);
19852
+ }
19853
+ this.negotiate();
19854
+ };
19487
19855
  this.client.onClose = () => {
19488
19856
  this.handleDisconnect('signal', ReconnectReason.RR_SIGNAL_DISCONNECTED);
19489
19857
  };
@@ -20113,19 +20481,21 @@ class RTCEngine extends eventsExports.EventEmitter {
20113
20481
  }
20114
20482
  /** @internal */
20115
20483
  sendSyncState(remoteTracks, localTracks) {
20116
- var _a, _b;
20484
+ var _a, _b, _c, _d;
20117
20485
  if (!this.pcManager) {
20118
20486
  this.log.warn('sync state cannot be sent without peer connection setup', this.logContext);
20119
20487
  return;
20120
20488
  }
20121
- const previousAnswer = this.pcManager.subscriber.getLocalDescription();
20122
- const previousOffer = this.pcManager.subscriber.getRemoteDescription();
20489
+ const previousPublisherOffer = this.pcManager.publisher.getLocalDescription();
20490
+ const previousPublisherAnswer = this.pcManager.publisher.getRemoteDescription();
20491
+ const previousSubscriberOffer = (_a = this.pcManager.subscriber) === null || _a === void 0 ? void 0 : _a.getRemoteDescription();
20492
+ const previousSubscriberAnswer = (_b = this.pcManager.subscriber) === null || _b === void 0 ? void 0 : _b.getLocalDescription();
20123
20493
  /* 1. autosubscribe on, so subscribed tracks = all tracks - unsub tracks,
20124
20494
  in this case, we send unsub tracks, so server add all tracks to this
20125
20495
  subscribe pc and unsub special tracks from it.
20126
20496
  2. autosubscribe off, we send subscribed tracks.
20127
20497
  */
20128
- const autoSubscribe = (_b = (_a = this.signalOpts) === null || _a === void 0 ? void 0 : _a.autoSubscribe) !== null && _b !== void 0 ? _b : true;
20498
+ const autoSubscribe = (_d = (_c = this.signalOpts) === null || _c === void 0 ? void 0 : _c.autoSubscribe) !== null && _d !== void 0 ? _d : true;
20129
20499
  const trackSids = new Array();
20130
20500
  const trackSidsDisabled = new Array();
20131
20501
  remoteTracks.forEach(track => {
@@ -20137,13 +20507,19 @@ class RTCEngine extends eventsExports.EventEmitter {
20137
20507
  }
20138
20508
  });
20139
20509
  this.client.sendSyncState(new SyncState({
20140
- answer: previousAnswer ? toProtoSessionDescription({
20141
- sdp: previousAnswer.sdp,
20142
- type: previousAnswer.type
20510
+ answer: this.options.singlePeerConnection ? previousPublisherAnswer ? toProtoSessionDescription({
20511
+ sdp: previousPublisherAnswer.sdp,
20512
+ type: previousPublisherAnswer.type
20513
+ }) : undefined : previousSubscriberAnswer ? toProtoSessionDescription({
20514
+ sdp: previousSubscriberAnswer.sdp,
20515
+ type: previousSubscriberAnswer.type
20143
20516
  }) : undefined,
20144
- offer: previousOffer ? toProtoSessionDescription({
20145
- sdp: previousOffer.sdp,
20146
- type: previousOffer.type
20517
+ offer: this.options.singlePeerConnection ? previousPublisherOffer ? toProtoSessionDescription({
20518
+ sdp: previousPublisherOffer.sdp,
20519
+ type: previousPublisherOffer.type
20520
+ }) : undefined : previousSubscriberOffer ? toProtoSessionDescription({
20521
+ sdp: previousSubscriberOffer.sdp,
20522
+ type: previousSubscriberOffer.type
20147
20523
  }) : undefined,
20148
20524
  subscription: new UpdateSubscription({
20149
20525
  trackSids,
@@ -23879,10 +24255,10 @@ class LocalParticipant extends Participant {
23879
24255
  destinationIdentity,
23880
24256
  method,
23881
24257
  payload,
23882
- responseTimeout = 10000
24258
+ responseTimeout = 15000
23883
24259
  } = _ref3;
23884
24260
  return function* () {
23885
- const maxRoundTripLatency = 2000;
24261
+ const maxRoundTripLatency = 7000;
23886
24262
  return new Promise((resolve, reject) => __awaiter(_this5, void 0, void 0, function* () {
23887
24263
  var _a, _b, _c, _d;
23888
24264
  if (byteLength(payload) > MAX_PAYLOAD_BYTES) {
@@ -24803,7 +25179,8 @@ class Room extends eventsExports.EventEmitter {
24803
25179
  adaptiveStream: typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream,
24804
25180
  maxRetries: connectOptions.maxRetries,
24805
25181
  e2eeEnabled: !!this.e2eeManager,
24806
- websocketTimeout: connectOptions.websocketTimeout
25182
+ websocketTimeout: connectOptions.websocketTimeout,
25183
+ singlePeerConnection: roomOptions.singlePeerConnection
24807
25184
  }, abortController.signal);
24808
25185
  let serverInfo = joinResponse.serverInfo;
24809
25186
  if (!serverInfo) {
@@ -24940,8 +25317,9 @@ class Room extends eventsExports.EventEmitter {
24940
25317
  _this2.log.info('disconnect from room', Object.assign({}, _this2.logContext));
24941
25318
  if (_this2.state === ConnectionState.Connecting || _this2.state === ConnectionState.Reconnecting || _this2.isResuming) {
24942
25319
  // 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();
25320
+ const msg = 'Abort connection attempt due to user initiated disconnect';
25321
+ _this2.log.warn(msg, _this2.logContext);
25322
+ (_a = _this2.abortController) === null || _a === void 0 ? void 0 : _a.abort(msg);
24945
25323
  // in case the abort controller didn't manage to cancel the connection attempt, reject the connect promise explicitly
24946
25324
  (_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
25325
  _this2.connectFuture = undefined;
@@ -25513,6 +25891,7 @@ class Room extends eventsExports.EventEmitter {
25513
25891
  if (e2eeOptions) {
25514
25892
  if ('e2eeManager' in e2eeOptions) {
25515
25893
  this.e2eeManager = e2eeOptions.e2eeManager;
25894
+ this.e2eeManager.isDataChannelEncryptionEnabled = dcEncryptionEnabled;
25516
25895
  } else {
25517
25896
  this.e2eeManager = new E2EEManager(e2eeOptions, dcEncryptionEnabled);
25518
25897
  }
@@ -27087,7 +27466,8 @@ class TURNCheck extends Checker {
27087
27466
  autoSubscribe: true,
27088
27467
  maxRetries: 0,
27089
27468
  e2eeEnabled: false,
27090
- websocketTimeout: 15000
27469
+ websocketTimeout: 15000,
27470
+ singlePeerConnection: false
27091
27471
  });
27092
27472
  let hasTLS = false;
27093
27473
  let hasTURN = false;
@@ -27137,6 +27517,7 @@ class WebRTCCheck extends Checker {
27137
27517
  let hasTcp = false;
27138
27518
  let hasIpv4Udp = false;
27139
27519
  this.room.on(RoomEvent.SignalConnected, () => {
27520
+ var _a;
27140
27521
  const prevTrickle = this.room.engine.client.onTrickle;
27141
27522
  this.room.engine.client.onTrickle = (sd, target) => {
27142
27523
  if (sd.candidate) {
@@ -27160,7 +27541,7 @@ class WebRTCCheck extends Checker {
27160
27541
  prevTrickle(sd, target);
27161
27542
  }
27162
27543
  };
27163
- if (this.room.engine.pcManager) {
27544
+ if ((_a = this.room.engine.pcManager) === null || _a === void 0 ? void 0 : _a.subscriber) {
27164
27545
  this.room.engine.pcManager.subscriber.onIceCandidateError = ev => {
27165
27546
  if (ev instanceof RTCPeerConnectionIceErrorEvent) {
27166
27547
  this.appendWarning("error with ICE candidate: ".concat(ev.errorCode, " ").concat(ev.errorText, " ").concat(ev.url));
@@ -27216,7 +27597,8 @@ class WebSocketCheck extends Checker {
27216
27597
  autoSubscribe: true,
27217
27598
  maxRetries: 0,
27218
27599
  e2eeEnabled: false,
27219
- websocketTimeout: 15000
27600
+ websocketTimeout: 15000,
27601
+ singlePeerConnection: false
27220
27602
  });
27221
27603
  this.appendMessage("Connected to server, version ".concat(joinRes.serverVersion, "."));
27222
27604
  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 +27966,17 @@ function decodeJwt(jwt) {
27584
27966
 
27585
27967
  const ONE_SECOND_IN_MILLISECONDS = 1000;
27586
27968
  const ONE_MINUTE_IN_MILLISECONDS = 60 * ONE_SECOND_IN_MILLISECONDS;
27587
- function isResponseExpired(response) {
27969
+ function isResponseTokenValid(response) {
27588
27970
  const jwtPayload = decodeTokenPayload(response.participantToken);
27589
- if (!(jwtPayload === null || jwtPayload === void 0 ? void 0 : jwtPayload.exp)) {
27971
+ if (!(jwtPayload === null || jwtPayload === void 0 ? void 0 : jwtPayload.nbf) || !(jwtPayload === null || jwtPayload === void 0 ? void 0 : jwtPayload.exp)) {
27590
27972
  return true;
27591
27973
  }
27592
- const expInMilliseconds = jwtPayload.exp * ONE_SECOND_IN_MILLISECONDS;
27593
- const expiresAt = new Date(expInMilliseconds - ONE_MINUTE_IN_MILLISECONDS);
27594
27974
  const now = new Date();
27595
- return expiresAt >= now;
27975
+ const nbfInMilliseconds = jwtPayload.nbf * ONE_SECOND_IN_MILLISECONDS;
27976
+ const nbfDate = new Date(nbfInMilliseconds);
27977
+ const expInMilliseconds = jwtPayload.exp * ONE_SECOND_IN_MILLISECONDS;
27978
+ const expDate = new Date(expInMilliseconds - ONE_MINUTE_IN_MILLISECONDS);
27979
+ return nbfDate <= now && expDate > now;
27596
27980
  }
27597
27981
  function decodeTokenPayload(token) {
27598
27982
  const payload = decodeJwt(token);
@@ -27644,7 +28028,7 @@ class TokenSourceCached extends TokenSourceConfigurable {
27644
28028
  if (!this.cachedResponse) {
27645
28029
  return false;
27646
28030
  }
27647
- if (isResponseExpired(this.cachedResponse)) {
28031
+ if (!isResponseTokenValid(this.cachedResponse)) {
27648
28032
  return false;
27649
28033
  }
27650
28034
  if (this.isSameAsCachedFetchOptions(fetchOptions)) {
@@ -27913,5 +28297,5 @@ function isFacingModeValue(item) {
27913
28297
  return item === undefined || allowedValues.includes(item);
27914
28298
  }
27915
28299
 
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 };
28300
+ 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
28301
  //# sourceMappingURL=livekit-client.esm.mjs.map