livekit-client 1.9.1 → 1.9.2

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.
@@ -14940,7 +14940,7 @@ var uaParser = {exports: {}};
14940
14940
  var uaParserExports = uaParser.exports;
14941
14941
  var UAParser = /*@__PURE__*/getDefaultExportFromCjs(uaParserExports);
14942
14942
 
14943
- var version$1 = "1.9.1";
14943
+ var version$1 = "1.9.2";
14944
14944
 
14945
14945
  const version = version$1;
14946
14946
  const protocolVersion = 9;
@@ -15509,13 +15509,7 @@ class SignalClient {
15509
15509
  this.handleSignalResponse(resp);
15510
15510
  };
15511
15511
  this.ws.onclose = ev => {
15512
- if (!this.isConnected) return;
15513
- livekitLogger.debug("websocket connection closed: ".concat(ev.reason));
15514
- this.isConnected = false;
15515
- if (this.onClose) {
15516
- this.onClose(ev.reason);
15517
- }
15518
- this.ws = undefined;
15512
+ this.handleOnClose(ev.reason);
15519
15513
  };
15520
15514
  });
15521
15515
  }
@@ -15784,6 +15778,17 @@ class SignalClient {
15784
15778
  }
15785
15779
  this.isReconnecting = false;
15786
15780
  }
15781
+ handleOnClose(reason) {
15782
+ if (!this.isConnected) return;
15783
+ this.clearPingInterval();
15784
+ this.clearPingTimeout();
15785
+ livekitLogger.debug("websocket connection closed: ".concat(reason));
15786
+ this.isConnected = false;
15787
+ if (this.onClose) {
15788
+ this.onClose(reason);
15789
+ }
15790
+ this.ws = undefined;
15791
+ }
15787
15792
  handleWSError(ev) {
15788
15793
  livekitLogger.error('websocket error', ev);
15789
15794
  }
@@ -15799,9 +15804,7 @@ class SignalClient {
15799
15804
  }
15800
15805
  this.pingTimeout = CriticalTimers.setTimeout(() => {
15801
15806
  livekitLogger.warn("ping timeout triggered. last pong received at: ".concat(new Date(Date.now() - this.pingTimeoutDuration * 1000).toUTCString()));
15802
- if (this.onClose) {
15803
- this.onClose('ping timeout');
15804
- }
15807
+ this.handleOnClose('ping timeout');
15805
15808
  }, this.pingTimeoutDuration * 1000);
15806
15809
  }
15807
15810
  /**
@@ -18571,7 +18574,7 @@ class RTCEngine extends eventsExports.EventEmitter {
18571
18574
  } else {
18572
18575
  livekitLogger.info("could not recover connection after ".concat(this.reconnectAttempts, " attempts, ").concat(Date.now() - this.reconnectStart, "ms. giving up"));
18573
18576
  this.emit(EngineEvent.Disconnected);
18574
- this.close();
18577
+ await this.close();
18575
18578
  }
18576
18579
  } finally {
18577
18580
  this.attemptingReconnect = false;
@@ -18792,6 +18795,30 @@ class RTCEngine extends eventsExports.EventEmitter {
18792
18795
  async ensurePublisherConnected(kind) {
18793
18796
  await this.ensureDataTransportConnected(kind, false);
18794
18797
  }
18798
+ /* @internal */
18799
+ verifyTransport() {
18800
+ // primary connection
18801
+ if (!this.primaryPC) {
18802
+ return false;
18803
+ }
18804
+ if (this.primaryPC.connectionState === 'closed' || this.primaryPC.connectionState === 'failed') {
18805
+ return false;
18806
+ }
18807
+ // also verify publisher connection if it's needed or different
18808
+ if (this.hasPublished && this.subscriberPrimary) {
18809
+ if (!this.publisher) {
18810
+ return false;
18811
+ }
18812
+ if (this.publisher.pc.connectionState === 'closed' || this.publisher.pc.connectionState === 'failed') {
18813
+ return false;
18814
+ }
18815
+ }
18816
+ // ensure signal is connected
18817
+ if (!this.client.ws || this.client.ws.readyState === WebSocket.CLOSED) {
18818
+ return false;
18819
+ }
18820
+ return true;
18821
+ }
18795
18822
  /** @internal */
18796
18823
  negotiate() {
18797
18824
  // observe signal state
@@ -22242,6 +22269,7 @@ var ConnectionState;
22242
22269
  ConnectionState["Connected"] = "connected";
22243
22270
  ConnectionState["Reconnecting"] = "reconnecting";
22244
22271
  })(ConnectionState || (ConnectionState = {}));
22272
+ const connectionReconcileFrequency = 2 * 1000;
22245
22273
  /** @deprecated RoomState has been renamed to [[ConnectionState]] */
22246
22274
  const RoomState = ConnectionState;
22247
22275
  /**
@@ -22421,6 +22449,7 @@ class Room extends eventsExports.EventEmitter {
22421
22449
  }
22422
22450
  this.setAndEmitConnectionState(ConnectionState.Connected);
22423
22451
  this.emit(RoomEvent.Connected);
22452
+ this.registerConnectionReconcile();
22424
22453
  };
22425
22454
  /**
22426
22455
  * disconnects the room, emits [[RoomEvent.Disconnected]]
@@ -22464,6 +22493,7 @@ class Room extends eventsExports.EventEmitter {
22464
22493
  await this.disconnect();
22465
22494
  };
22466
22495
  this.handleRestarting = () => {
22496
+ this.clearConnectionReconcile();
22467
22497
  // also unwind existing participants & existing subscriptions
22468
22498
  for (const p of this.participants.values()) {
22469
22499
  this.handleParticipantDisconnected(p.sid, p);
@@ -22520,6 +22550,7 @@ class Room extends eventsExports.EventEmitter {
22520
22550
  }
22521
22551
  this.setAndEmitConnectionState(ConnectionState.Connected);
22522
22552
  this.emit(RoomEvent.Reconnected);
22553
+ this.registerConnectionReconcile();
22523
22554
  // emit participant connected events after connection has been re-established
22524
22555
  this.participants.forEach(participant => {
22525
22556
  this.emit(RoomEvent.ParticipantConnected, participant);
@@ -22761,7 +22792,7 @@ class Room extends eventsExports.EventEmitter {
22761
22792
  return (_b = (_a = this.roomInfo) === null || _a === void 0 ? void 0 : _a.numPublishers) !== null && _b !== void 0 ? _b : 0;
22762
22793
  }
22763
22794
  maybeCreateEngine() {
22764
- if (this.engine) {
22795
+ if (this.engine && !this.engine.isClosed) {
22765
22796
  return;
22766
22797
  }
22767
22798
  this.engine = new RTCEngine(this.options);
@@ -22776,6 +22807,7 @@ class Room extends eventsExports.EventEmitter {
22776
22807
  }).on(EngineEvent.Disconnected, reason => {
22777
22808
  this.handleDisconnect(this.options.stopLocalTrackOnUnpublish, reason);
22778
22809
  }).on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate).on(EngineEvent.DataPacketReceived, this.handleDataPacket).on(EngineEvent.Resuming, () => {
22810
+ this.clearConnectionReconcile();
22779
22811
  if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
22780
22812
  this.emit(RoomEvent.Reconnecting);
22781
22813
  }
@@ -22783,6 +22815,7 @@ class Room extends eventsExports.EventEmitter {
22783
22815
  }).on(EngineEvent.Resumed, () => {
22784
22816
  this.setAndEmitConnectionState(ConnectionState.Connected);
22785
22817
  this.emit(RoomEvent.Reconnected);
22818
+ this.registerConnectionReconcile();
22786
22819
  this.updateSubscriptions();
22787
22820
  // once reconnected, figure out if any participants connected during reconnect and emit events for it
22788
22821
  const diffParticipants = Array.from(this.participants.values()).filter(p => !this.cachedParticipantSids.includes(p.sid));
@@ -23093,41 +23126,45 @@ class Room extends eventsExports.EventEmitter {
23093
23126
  let shouldStopTracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
23094
23127
  let reason = arguments.length > 1 ? arguments[1] : undefined;
23095
23128
  var _a;
23129
+ this.clearConnectionReconcile();
23096
23130
  if (this.state === ConnectionState.Disconnected) {
23097
23131
  return;
23098
23132
  }
23099
- this.participants.forEach(p => {
23100
- p.tracks.forEach(pub => {
23101
- p.unpublishTrack(pub.trackSid);
23133
+ try {
23134
+ this.participants.forEach(p => {
23135
+ p.tracks.forEach(pub => {
23136
+ p.unpublishTrack(pub.trackSid);
23137
+ });
23102
23138
  });
23103
- });
23104
- this.localParticipant.tracks.forEach(pub => {
23105
- var _a, _b;
23106
- if (pub.track) {
23107
- this.localParticipant.unpublishTrack(pub.track, shouldStopTracks);
23108
- }
23109
- if (shouldStopTracks) {
23110
- (_a = pub.track) === null || _a === void 0 ? void 0 : _a.detach();
23111
- (_b = pub.track) === null || _b === void 0 ? void 0 : _b.stop();
23139
+ this.localParticipant.tracks.forEach(pub => {
23140
+ var _a, _b;
23141
+ if (pub.track) {
23142
+ this.localParticipant.unpublishTrack(pub.track, shouldStopTracks);
23143
+ }
23144
+ if (shouldStopTracks) {
23145
+ (_a = pub.track) === null || _a === void 0 ? void 0 : _a.detach();
23146
+ (_b = pub.track) === null || _b === void 0 ? void 0 : _b.stop();
23147
+ }
23148
+ });
23149
+ this.localParticipant.off(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged).off(ParticipantEvent.ParticipantNameChanged, this.onLocalParticipantNameChanged).off(ParticipantEvent.TrackMuted, this.onLocalTrackMuted).off(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted).off(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished).off(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished).off(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged).off(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError).off(ParticipantEvent.ParticipantPermissionsChanged, this.onLocalParticipantPermissionsChanged);
23150
+ this.localParticipant.tracks.clear();
23151
+ this.localParticipant.videoTracks.clear();
23152
+ this.localParticipant.audioTracks.clear();
23153
+ this.participants.clear();
23154
+ this.activeSpeakers = [];
23155
+ if (this.audioContext && typeof this.options.expWebAudioMix === 'boolean') {
23156
+ this.audioContext.close();
23157
+ this.audioContext = undefined;
23158
+ }
23159
+ if (isWeb()) {
23160
+ window.removeEventListener('beforeunload', this.onPageLeave);
23161
+ window.removeEventListener('pagehide', this.onPageLeave);
23162
+ (_a = navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.removeEventListener('devicechange', this.handleDeviceChange);
23112
23163
  }
23113
- });
23114
- this.localParticipant.off(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged).off(ParticipantEvent.ParticipantNameChanged, this.onLocalParticipantNameChanged).off(ParticipantEvent.TrackMuted, this.onLocalTrackMuted).off(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted).off(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished).off(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished).off(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged).off(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError).off(ParticipantEvent.ParticipantPermissionsChanged, this.onLocalParticipantPermissionsChanged);
23115
- this.localParticipant.tracks.clear();
23116
- this.localParticipant.videoTracks.clear();
23117
- this.localParticipant.audioTracks.clear();
23118
- this.participants.clear();
23119
- this.activeSpeakers = [];
23120
- if (this.audioContext && typeof this.options.expWebAudioMix === 'boolean') {
23121
- this.audioContext.close();
23122
- this.audioContext = undefined;
23123
- }
23124
- if (isWeb()) {
23125
- window.removeEventListener('beforeunload', this.onPageLeave);
23126
- window.removeEventListener('pagehide', this.onPageLeave);
23127
- (_a = navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.removeEventListener('devicechange', this.handleDeviceChange);
23164
+ } finally {
23165
+ this.setAndEmitConnectionState(ConnectionState.Disconnected);
23166
+ this.emit(RoomEvent.Disconnected, reason);
23128
23167
  }
23129
- this.setAndEmitConnectionState(ConnectionState.Disconnected);
23130
- this.emit(RoomEvent.Disconnected, reason);
23131
23168
  }
23132
23169
  handleParticipantDisconnected(sid, participant) {
23133
23170
  // remove and send event
@@ -23280,6 +23317,32 @@ class Room extends eventsExports.EventEmitter {
23280
23317
  }
23281
23318
  }
23282
23319
  }
23320
+ registerConnectionReconcile() {
23321
+ this.clearConnectionReconcile();
23322
+ let consecutiveFailures = 0;
23323
+ this.connectionReconcileInterval = CriticalTimers.setInterval(() => {
23324
+ if (
23325
+ // ensure we didn't tear it down
23326
+ !this.engine ||
23327
+ // engine detected close, but Room missed it
23328
+ this.engine.isClosed ||
23329
+ // transports failed without notifying engine
23330
+ !this.engine.verifyTransport()) {
23331
+ consecutiveFailures++;
23332
+ livekitLogger.warn('detected connection state mismatch', {
23333
+ numFailures: consecutiveFailures
23334
+ });
23335
+ if (consecutiveFailures >= 3) this.handleDisconnect(this.options.stopLocalTrackOnUnpublish, DisconnectReason.UNKNOWN_REASON);
23336
+ } else {
23337
+ consecutiveFailures = 0;
23338
+ }
23339
+ }, connectionReconcileFrequency);
23340
+ }
23341
+ clearConnectionReconcile() {
23342
+ if (this.connectionReconcileInterval) {
23343
+ CriticalTimers.clearInterval(this.connectionReconcileInterval);
23344
+ }
23345
+ }
23283
23346
  setAndEmitConnectionState(state) {
23284
23347
  if (state === this.state) {
23285
23348
  // unchanged