@stream-io/video-client 1.18.7 → 1.18.8

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 (51) hide show
  1. package/CHANGELOG.md +97 -169
  2. package/dist/index.browser.es.js +89 -52
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +89 -52
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.es.js +89 -52
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/Call.d.ts +10 -1
  9. package/dist/src/devices/ScreenShareManager.d.ts +1 -3
  10. package/dist/src/gen/video/sfu/event/events.d.ts +1 -19
  11. package/dist/src/gen/video/sfu/signal_rpc/signal.client.d.ts +2 -21
  12. package/dist/src/gen/video/sfu/signal_rpc/signal.d.ts +1 -9
  13. package/dist/src/helpers/promise.d.ts +2 -2
  14. package/package.json +10 -11
  15. package/src/Call.ts +61 -10
  16. package/src/StreamSfuClient.ts +9 -3
  17. package/src/StreamVideoClient.ts +4 -5
  18. package/src/__tests__/Call.test.ts +1 -1
  19. package/src/coordinator/connection/client.ts +2 -3
  20. package/src/coordinator/connection/connection.ts +14 -14
  21. package/src/coordinator/connection/signing.ts +1 -1
  22. package/src/devices/BrowserPermission.ts +3 -2
  23. package/src/devices/ScreenShareManager.ts +1 -3
  24. package/src/devices/__tests__/InputMediaDeviceManager.test.ts +1 -1
  25. package/src/devices/__tests__/MicrophoneManager.test.ts +4 -4
  26. package/src/devices/__tests__/MicrophoneManagerRN.test.ts +4 -4
  27. package/src/devices/devices.ts +3 -1
  28. package/src/events/__tests__/call.test.ts +42 -57
  29. package/src/events/__tests__/internal.test.ts +8 -13
  30. package/src/events/__tests__/mutes.test.ts +7 -3
  31. package/src/events/__tests__/participant.test.ts +16 -20
  32. package/src/events/__tests__/speaker.test.ts +6 -6
  33. package/src/gen/coordinator/index.ts +1 -1
  34. package/src/gen/video/sfu/event/events.ts +22 -20
  35. package/src/gen/video/sfu/models/models.ts +0 -1
  36. package/src/gen/video/sfu/signal_rpc/signal.client.ts +27 -23
  37. package/src/gen/video/sfu/signal_rpc/signal.ts +13 -11
  38. package/src/helpers/RNSpeechDetector.ts +3 -4
  39. package/src/helpers/__tests__/DynascaleManager.test.ts +27 -26
  40. package/src/helpers/__tests__/clientUtils.test.ts +0 -1
  41. package/src/helpers/client-details.ts +1 -1
  42. package/src/helpers/promise.ts +4 -4
  43. package/src/rtc/Dispatcher.ts +1 -1
  44. package/src/rtc/Publisher.ts +2 -2
  45. package/src/rtc/__tests__/Publisher.test.ts +8 -8
  46. package/src/rtc/__tests__/Subscriber.test.ts +9 -9
  47. package/src/rtc/__tests__/mocks/webrtc.mocks.ts +2 -2
  48. package/src/rtc/helpers/__tests__/sdp.test.ts +3 -3
  49. package/src/stats/CallStateStatsReporter.ts +2 -3
  50. package/src/store/__tests__/CallState.test.ts +59 -115
  51. package/src/timers/worker.ts +0 -4
package/dist/index.cjs.js CHANGED
@@ -10,6 +10,7 @@ var sdpTransform = require('sdp-transform');
10
10
  var uaParserJs = require('ua-parser-js');
11
11
  var https = require('https');
12
12
 
13
+ /* tslint:disable */
13
14
  /**
14
15
  * @export
15
16
  */
@@ -686,7 +687,6 @@ class Timestamp$Type extends runtime.MessageType {
686
687
  */
687
688
  const Timestamp = new Timestamp$Type();
688
689
 
689
- /* eslint-disable */
690
690
  // @generated by protobuf-ts 2.9.4 with parameter long_type_string,client_generic,server_none,eslint_disable,optimize_code_size
691
691
  // @generated from protobuf file "video/sfu/models/models.proto" (package "stream.video.sfu.models", syntax proto3)
692
692
  // tslint:disable
@@ -1729,7 +1729,6 @@ var models = /*#__PURE__*/Object.freeze({
1729
1729
  get WebsocketReconnectStrategy () { return WebsocketReconnectStrategy; }
1730
1730
  });
1731
1731
 
1732
- /* eslint-disable */
1733
1732
  // @generated by protobuf-ts 2.9.4 with parameter long_type_string,client_generic,server_none,eslint_disable,optimize_code_size
1734
1733
  // @generated from protobuf file "video/sfu/signal_rpc/signal.proto" (package "stream.video.sfu.signal", syntax proto3)
1735
1734
  // tslint:disable
@@ -2194,7 +2193,6 @@ const SignalServer = new runtimeRpc.ServiceType('stream.video.sfu.signal.SignalS
2194
2193
  },
2195
2194
  ]);
2196
2195
 
2197
- /* eslint-disable */
2198
2196
  // @generated by protobuf-ts 2.9.4 with parameter long_type_string,client_generic,server_none,eslint_disable,optimize_code_size
2199
2197
  // @generated from protobuf file "video/sfu/event/events.proto" (package "stream.video.sfu.event", syntax proto3)
2200
2198
  // tslint:disable
@@ -5825,8 +5823,8 @@ class Publisher extends BasePeerConnection {
5825
5823
  ? // for SVC, we only have one layer (q) and often rid is omitted
5826
5824
  enabledLayers[0]
5827
5825
  : // for non-SVC, we need to find the layer by rid (simulcast)
5828
- enabledLayers.find((l) => l.name === encoder.rid) ??
5829
- (params.encodings.length === 1 ? enabledLayers[0] : undefined);
5826
+ (enabledLayers.find((l) => l.name === encoder.rid) ??
5827
+ (params.encodings.length === 1 ? enabledLayers[0] : undefined));
5830
5828
  // flip 'active' flag only when necessary
5831
5829
  const shouldActivate = !!layer?.active;
5832
5830
  if (shouldActivate !== encoder.active) {
@@ -6229,8 +6227,8 @@ const promiseWithResolvers = () => {
6229
6227
  promise,
6230
6228
  resolve: resolver,
6231
6229
  reject: rejecter,
6232
- isResolved,
6233
- isRejected,
6230
+ isResolved: () => isResolved,
6231
+ isRejected: () => isRejected,
6234
6232
  };
6235
6233
  };
6236
6234
 
@@ -6534,7 +6532,8 @@ class StreamSfuClient {
6534
6532
  this.join = async (data) => {
6535
6533
  // wait for the signal web socket to be ready before sending "joinRequest"
6536
6534
  await this.signalReady();
6537
- if (this.joinResponseTask.isResolved || this.joinResponseTask.isRejected) {
6535
+ if (this.joinResponseTask.isResolved() ||
6536
+ this.joinResponseTask.isRejected()) {
6538
6537
  // we need to lock the RPC requests until we receive a JoinResponse.
6539
6538
  // that's why we have this primitive lock mechanism.
6540
6539
  // the client starts with already initialized joinResponseTask,
@@ -6544,7 +6543,7 @@ class StreamSfuClient {
6544
6543
  // capture a reference to the current joinResponseTask as it might
6545
6544
  // be replaced with a new one in case a second join request is made
6546
6545
  const current = this.joinResponseTask;
6547
- let timeoutId;
6546
+ let timeoutId = undefined;
6548
6547
  const unsubscribe = this.dispatcher.on('joinResponse', (joinResponse) => {
6549
6548
  this.logger('debug', 'Received joinResponse', joinResponse);
6550
6549
  clearTimeout(timeoutId);
@@ -6657,7 +6656,8 @@ class StreamSfuClient {
6657
6656
  this.createWebSocket();
6658
6657
  }
6659
6658
  get isHealthy() {
6660
- return this.signalWs.readyState === WebSocket.OPEN;
6659
+ return (this.signalWs.readyState === WebSocket.OPEN &&
6660
+ this.joinResponseTask.isResolved());
6661
6661
  }
6662
6662
  get joinTask() {
6663
6663
  return this.joinResponseTask.promise;
@@ -7209,9 +7209,9 @@ const createStatsReporter = ({ subscriber, publisher, state, datacenter, polling
7209
7209
  for (const track of tracks) {
7210
7210
  const report = await pc.getStats(track);
7211
7211
  const stats = transform(report, {
7212
- // @ts-ignore
7213
7212
  trackKind: track.kind,
7214
7213
  kind,
7214
+ publisher: undefined,
7215
7215
  });
7216
7216
  statsForStream.push(stats);
7217
7217
  }
@@ -7353,7 +7353,6 @@ const transform = (report, opts) => {
7353
7353
  jitter: rtcStreamStats.jitter,
7354
7354
  kind: rtcStreamStats.kind,
7355
7355
  mediaSourceId: rtcStreamStats.mediaSourceId,
7356
- // @ts-ignore: available in Chrome only, TS doesn't recognize this
7357
7356
  qualityLimitationReason: rtcStreamStats.qualityLimitationReason,
7358
7357
  rid: rtcStreamStats.rid,
7359
7358
  ssrc: rtcStreamStats.ssrc,
@@ -7434,7 +7433,7 @@ const aggregate = (stats) => {
7434
7433
  return report;
7435
7434
  };
7436
7435
 
7437
- const version = "1.18.7";
7436
+ const version = "1.18.8";
7438
7437
  const [major, minor, patch] = version.split('.');
7439
7438
  let sdkInfo = {
7440
7439
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -7544,7 +7543,7 @@ const getClientDetails = async () => {
7544
7543
  'platformVersion',
7545
7544
  ]);
7546
7545
  }
7547
- catch (e) {
7546
+ catch {
7548
7547
  // Ignore the error
7549
7548
  }
7550
7549
  }
@@ -8323,7 +8322,7 @@ class BrowserPermission {
8323
8322
  this.logger = getLogger(['permissions']);
8324
8323
  const signal = this.disposeController.signal;
8325
8324
  this.ready = (async () => {
8326
- const assumeGranted = (error) => {
8325
+ const assumeGranted = () => {
8327
8326
  this.setState('prompt');
8328
8327
  };
8329
8328
  if (!canQueryPermissions()) {
@@ -8341,6 +8340,7 @@ class BrowserPermission {
8341
8340
  }
8342
8341
  }
8343
8342
  catch (err) {
8343
+ this.logger('debug', 'Failed to query permission status', err);
8344
8344
  assumeGranted();
8345
8345
  }
8346
8346
  })();
@@ -8582,6 +8582,7 @@ const getAudioStream = async (trackConstraints) => {
8582
8582
  }
8583
8583
  catch (error) {
8584
8584
  if (isNotFoundOrOverconstrainedError(error) && trackConstraints?.deviceId) {
8585
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
8585
8586
  const { deviceId, ...relaxedConstraints } = trackConstraints;
8586
8587
  getLogger(['devices'])('warn', 'Failed to get audio stream, will try again with relaxed constraints', { error, constraints, relaxedConstraints });
8587
8588
  return getAudioStream(relaxedConstraints);
@@ -8617,6 +8618,7 @@ const getVideoStream = async (trackConstraints) => {
8617
8618
  }
8618
8619
  catch (error) {
8619
8620
  if (isNotFoundOrOverconstrainedError(error) && trackConstraints?.deviceId) {
8621
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
8620
8622
  const { deviceId, ...relaxedConstraints } = trackConstraints;
8621
8623
  getLogger(['devices'])('warn', 'Failed to get video stream, will try again with relaxed constraints', { error, constraints, relaxedConstraints });
8622
8624
  return getVideoStream(relaxedConstraints);
@@ -8678,7 +8680,7 @@ const disposeOfMediaStream = (stream) => {
8678
8680
  });
8679
8681
  // @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the stream
8680
8682
  if (typeof stream.release === 'function') {
8681
- // @ts-expect-error
8683
+ // @ts-expect-error - release() is present in react-native-webrtc
8682
8684
  stream.release();
8683
8685
  }
8684
8686
  };
@@ -9537,7 +9539,7 @@ class RNSpeechDetector {
9537
9539
  e.streams[0].getTracks().forEach((track) => {
9538
9540
  // In RN, the remote track is automatically added to the audio output device
9539
9541
  // so we need to mute it to avoid hearing the audio back
9540
- // @ts-ignore _setVolume is a private method in react-native-webrtc
9542
+ // @ts-expect-error _setVolume is a private method in react-native-webrtc
9541
9543
  track._setVolume(0);
9542
9544
  });
9543
9545
  });
@@ -9577,10 +9579,9 @@ class RNSpeechDetector {
9577
9579
  const initialBaselineNoiseLevel = 0.13;
9578
9580
  let baselineNoiseLevel = initialBaselineNoiseLevel;
9579
9581
  let speechDetected = false;
9580
- let intervalId;
9581
9582
  let speechTimer;
9582
9583
  let silenceTimer;
9583
- let audioLevelHistory = []; // Store recent audio levels for smoother detection
9584
+ const audioLevelHistory = []; // Store recent audio levels for smoother detection
9584
9585
  const historyLength = 10;
9585
9586
  const silenceThreshold = 1.1;
9586
9587
  const resetThreshold = 0.9;
@@ -9643,7 +9644,7 @@ class RNSpeechDetector {
9643
9644
  }
9644
9645
  };
9645
9646
  // Call checkAudioLevel periodically (every 100ms)
9646
- intervalId = setInterval(checkAudioLevel, 100);
9647
+ const intervalId = setInterval(checkAudioLevel, 100);
9647
9648
  return () => {
9648
9649
  clearInterval(intervalId);
9649
9650
  clearTimeout(speechTimer);
@@ -9989,10 +9990,8 @@ class ScreenShareManager extends InputMediaDeviceManager {
9989
9990
  }
9990
9991
  /**
9991
9992
  * Overrides the default `select` method to throw an error.
9992
- *
9993
- * @param deviceId ignored.
9994
9993
  */
9995
- async select(deviceId) {
9994
+ async select() {
9996
9995
  throw new Error('This method is not supported in for Screen Share');
9997
9996
  }
9998
9997
  }
@@ -10582,13 +10581,39 @@ class Call {
10582
10581
  *
10583
10582
  * @returns a promise which resolves once the call join-flow has finished.
10584
10583
  */
10585
- this.join = async (data) => {
10586
- const connectStartTime = Date.now();
10584
+ this.join = async ({ maxJoinRetries = 3, ...data } = {}) => {
10587
10585
  await this.setup();
10588
10586
  const callingState = this.state.callingState;
10589
10587
  if ([exports.CallingState.JOINED, exports.CallingState.JOINING].includes(callingState)) {
10590
10588
  throw new Error(`Illegal State: call.join() shall be called only once`);
10591
10589
  }
10590
+ this.state.setCallingState(exports.CallingState.JOINING);
10591
+ maxJoinRetries = Math.max(maxJoinRetries, 1);
10592
+ for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
10593
+ try {
10594
+ this.logger('trace', `Joining call (${attempt})`, this.cid);
10595
+ return await this.doJoin(data);
10596
+ }
10597
+ catch (err) {
10598
+ this.logger('warn', `Failed to join call (${attempt})`, this.cid);
10599
+ if (attempt === maxJoinRetries - 1) {
10600
+ // restore the previous call state if the join-flow fails
10601
+ this.state.setCallingState(callingState);
10602
+ throw err;
10603
+ }
10604
+ }
10605
+ await sleep(retryInterval(attempt));
10606
+ }
10607
+ };
10608
+ /**
10609
+ * Will make a single attempt to watch for call related WebSocket events
10610
+ * and initiate a call session with the server.
10611
+ *
10612
+ * @returns a promise which resolves once the call join-flow has finished.
10613
+ */
10614
+ this.doJoin = async (data) => {
10615
+ const connectStartTime = Date.now();
10616
+ const callingState = this.state.callingState;
10592
10617
  this.joinCallData = data;
10593
10618
  this.logger('debug', 'Starting join flow');
10594
10619
  this.state.setCallingState(exports.CallingState.JOINING);
@@ -10669,6 +10694,8 @@ class Call {
10669
10694
  }
10670
10695
  }
10671
10696
  catch (error) {
10697
+ this.logger('warn', 'Join SFU request failed', error);
10698
+ sfuClient.close(StreamSfuClient.ERROR_CONNECTION_UNHEALTHY, 'Join request failed, connection considered unhealthy');
10672
10699
  // restore the previous call state if the join-flow fails
10673
10700
  this.state.setCallingState(callingState);
10674
10701
  throw error;
@@ -10913,9 +10940,18 @@ class Call {
10913
10940
  */
10914
10941
  this.handleSfuSignalClose = (sfuClient) => {
10915
10942
  this.logger('debug', '[Reconnect] SFU signal connection closed');
10916
- // SFU WS closed before we finished current join, no need to schedule reconnect
10917
- // because join operation will fail
10918
- if (this.state.callingState === exports.CallingState.JOINING)
10943
+ const { callingState } = this.state;
10944
+ if (
10945
+ // SFU WS closed before we finished current join,
10946
+ // no need to schedule reconnecting
10947
+ callingState === exports.CallingState.JOINING ||
10948
+ // we are already in the process of reconnecting,
10949
+ // no need to schedule another one
10950
+ callingState === exports.CallingState.RECONNECTING ||
10951
+ // SFU WS closed as a result of unsuccessful join,
10952
+ // and no further retries need to be made
10953
+ callingState === exports.CallingState.IDLE ||
10954
+ callingState === exports.CallingState.LEFT)
10919
10955
  return;
10920
10956
  // normal close, no need to reconnect
10921
10957
  if (sfuClient.isLeaving)
@@ -10937,7 +10973,7 @@ class Call {
10937
10973
  return;
10938
10974
  return withoutConcurrency(this.reconnectConcurrencyTag, async () => {
10939
10975
  this.logger('info', `[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[strategy]}`);
10940
- let reconnectStartTime = Date.now();
10976
+ const reconnectStartTime = Date.now();
10941
10977
  this.reconnectStrategy = strategy;
10942
10978
  do {
10943
10979
  if (this.disconnectionTimeoutSeconds > 0 &&
@@ -11004,7 +11040,7 @@ class Call {
11004
11040
  const reconnectStartTime = Date.now();
11005
11041
  this.reconnectStrategy = WebsocketReconnectStrategy.FAST;
11006
11042
  this.state.setCallingState(exports.CallingState.RECONNECTING);
11007
- await this.join(this.joinCallData);
11043
+ await this.doJoin(this.joinCallData);
11008
11044
  this.sfuStatsReporter?.sendReconnectionTime(WebsocketReconnectStrategy.FAST, (Date.now() - reconnectStartTime) / 1000);
11009
11045
  };
11010
11046
  /**
@@ -11015,7 +11051,7 @@ class Call {
11015
11051
  const reconnectStartTime = Date.now();
11016
11052
  this.reconnectStrategy = WebsocketReconnectStrategy.REJOIN;
11017
11053
  this.state.setCallingState(exports.CallingState.RECONNECTING);
11018
- await this.join(this.joinCallData);
11054
+ await this.doJoin(this.joinCallData);
11019
11055
  await this.restorePublishedTracks();
11020
11056
  this.restoreSubscribedTracks();
11021
11057
  this.sfuStatsReporter?.sendReconnectionTime(WebsocketReconnectStrategy.REJOIN, (Date.now() - reconnectStartTime) / 1000);
@@ -11039,7 +11075,7 @@ class Call {
11039
11075
  const migrationTask = makeSafePromise(currentSfuClient.enterMigration());
11040
11076
  try {
11041
11077
  const currentSfu = currentSfuClient.edgeName;
11042
- await this.join({ ...this.joinCallData, migrating_from: currentSfu });
11078
+ await this.doJoin({ ...this.joinCallData, migrating_from: currentSfu });
11043
11079
  }
11044
11080
  finally {
11045
11081
  // cleanup the migration_from field after the migration is complete or failed
@@ -12112,13 +12148,13 @@ class StableWSConnection {
12112
12148
  // this is a permanent error raised by stream..
12113
12149
  // usually caused by invalid auth details
12114
12150
  const error = new Error(`WS connection reject with error ${event.reason}`);
12115
- // @ts-expect-error
12151
+ // @ts-expect-error type issue
12116
12152
  error.reason = event.reason;
12117
- // @ts-expect-error
12153
+ // @ts-expect-error type issue
12118
12154
  error.code = event.code;
12119
- // @ts-expect-error
12155
+ // @ts-expect-error type issue
12120
12156
  error.wasClean = event.wasClean;
12121
- // @ts-expect-error
12157
+ // @ts-expect-error type issue
12122
12158
  error.target = event.target;
12123
12159
  this.rejectConnectionOpen?.(error);
12124
12160
  this._log(`onclose() - WS connection reject with error ${event.reason}`, {
@@ -12234,7 +12270,7 @@ class StableWSConnection {
12234
12270
  try {
12235
12271
  this.ws?.send(JSON.stringify(data));
12236
12272
  }
12237
- catch (e) {
12273
+ catch {
12238
12274
  // error will already be detected elsewhere
12239
12275
  }
12240
12276
  }, this.pingInterval);
@@ -12297,24 +12333,24 @@ class StableWSConnection {
12297
12333
  this.isHealthy = false;
12298
12334
  this.consecutiveFailures += 1;
12299
12335
  if (
12300
- // @ts-ignore
12336
+ // @ts-expect-error type issue
12301
12337
  error.code === KnownCodes.TOKEN_EXPIRED &&
12302
12338
  !this.client.tokenManager.isStatic()) {
12303
12339
  this._log('connect() - WS failure due to expired token, so going to try to reload token and reconnect');
12304
12340
  this._reconnect({ refreshToken: true });
12305
12341
  }
12306
12342
  else {
12307
- // @ts-ignore
12343
+ // @ts-expect-error type issue
12308
12344
  if (!error.isWSFailure) {
12309
12345
  // API rejected the connection and we should not retry
12310
12346
  throw new Error(JSON.stringify({
12311
- // @ts-ignore
12347
+ // @ts-expect-error type issue
12312
12348
  code: error.code,
12313
- // @ts-ignore
12349
+ // @ts-expect-error type issue
12314
12350
  StatusCode: error.StatusCode,
12315
- // @ts-ignore
12351
+ // @ts-expect-error type issue
12316
12352
  message: error.message,
12317
- // @ts-ignore
12353
+ // @ts-expect-error type issue
12318
12354
  isWSFailure: error.isWSFailure,
12319
12355
  }));
12320
12356
  }
@@ -12419,7 +12455,7 @@ class StableWSConnection {
12419
12455
  await this.client.tokenManager.tokenReady();
12420
12456
  isTokenReady = true;
12421
12457
  }
12422
- catch (e) {
12458
+ catch {
12423
12459
  // token provider has failed before, so try again
12424
12460
  }
12425
12461
  try {
@@ -12450,7 +12486,7 @@ class StableWSConnection {
12450
12486
  catch (err) {
12451
12487
  this.client._setupConnectionIdPromise();
12452
12488
  this.isConnecting = false;
12453
- // @ts-ignore
12489
+ // @ts-expect-error type issue
12454
12490
  this._log(`_connect() - Error - `, err);
12455
12491
  this.client.rejectConnectionId?.(err);
12456
12492
  throw err;
@@ -12529,7 +12565,7 @@ class StableWSConnection {
12529
12565
  try {
12530
12566
  this?.ws?.close();
12531
12567
  }
12532
- catch (e) {
12568
+ catch {
12533
12569
  // we don't care
12534
12570
  }
12535
12571
  }
@@ -12563,7 +12599,8 @@ const decodeBase64 = (s) => {
12563
12599
  b = (b << 6) + c;
12564
12600
  l += 6;
12565
12601
  while (l >= 8) {
12566
- ((a = (b >>> (l -= 8)) & 0xff) || x < L - 2) && (r += w(a));
12602
+ if ((a = (b >>> (l -= 8)) & 0xff) || x < L - 2)
12603
+ r += w(a);
12567
12604
  }
12568
12605
  }
12569
12606
  return r;
@@ -12942,7 +12979,7 @@ class StreamClient {
12942
12979
  try {
12943
12980
  await this.connectionIdPromise;
12944
12981
  }
12945
- catch (e) {
12982
+ catch {
12946
12983
  // in case connection id was rejected
12947
12984
  // reconnection maybe in progress
12948
12985
  // we can wait for healthy connection to resolve, which rejects when 15s timeout is reached
@@ -12979,7 +13016,6 @@ class StreamClient {
12979
13016
  this._logApiResponse(type, url, response);
12980
13017
  this.consecutiveFailures = 0;
12981
13018
  return this.handleResponse(response);
12982
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
12983
13019
  }
12984
13020
  catch (e /**TODO: generalize error types */) {
12985
13021
  e.client_request_id = requestConfig.headers?.['x-client-request-id'];
@@ -12999,7 +13035,6 @@ class StreamClient {
12999
13035
  }
13000
13036
  else {
13001
13037
  this._logApiError(type, url, e);
13002
- // eslint-disable-next-line no-throw-literal
13003
13038
  throw e;
13004
13039
  }
13005
13040
  }
@@ -13072,7 +13107,7 @@ class StreamClient {
13072
13107
  this.getUserAgent = () => {
13073
13108
  if (!this.cachedUserAgent) {
13074
13109
  const { clientAppIdentifier = {} } = this.options;
13075
- const { sdkName = 'js', sdkVersion = "1.18.7", ...extras } = clientAppIdentifier;
13110
+ const { sdkName = 'js', sdkVersion = "1.18.8", ...extras } = clientAppIdentifier;
13076
13111
  this.cachedUserAgent = [
13077
13112
  `stream-video-${sdkName}-v${sdkVersion}`,
13078
13113
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -13338,7 +13373,9 @@ class StreamVideoClient {
13338
13373
  }
13339
13374
  const connectUserResponse = await withoutConcurrency(this.connectionConcurrencyTag, async () => {
13340
13375
  const client = this.streamClient;
13341
- const { maxConnectUserRetries = 5, onConnectUserError, persistUserOnConnectionFailure, } = client.options;
13376
+ const { onConnectUserError, persistUserOnConnectionFailure } = client.options;
13377
+ let { maxConnectUserRetries = 5 } = client.options;
13378
+ maxConnectUserRetries = Math.max(maxConnectUserRetries, 1);
13342
13379
  const errorQueue = [];
13343
13380
  for (let attempt = 0; attempt < maxConnectUserRetries; attempt++) {
13344
13381
  try {