@stream-io/video-client 1.18.2 → 1.18.4

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [1.18.4](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.18.3...@stream-io/video-client-1.18.4) (2025-03-10)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * retryable client.connectUser() ([#1710](https://github.com/GetStream/stream-video-js/issues/1710)) ([10b6860](https://github.com/GetStream/stream-video-js/commit/10b6860e1d65c38d8eb0ba7d7ea18f0ca30f5abc))
11
+
12
+ ## [1.18.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.18.2...@stream-io/video-client-1.18.3) (2025-03-05)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * revert the release of cloned track on publisher dispose ([556fb61](https://github.com/GetStream/stream-video-js/commit/556fb610ae1c9a1965f38fc07e995683b5052544))
18
+
5
19
  ## [1.18.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.18.1...@stream-io/video-client-1.18.2) (2025-03-04)
6
20
 
7
21
 
@@ -3302,7 +3302,8 @@ function isFunction(value) {
3302
3302
  */
3303
3303
  const KnownCodes = {
3304
3304
  TOKEN_EXPIRED: 40,
3305
- WS_CLOSED_SUCCESS: 1000};
3305
+ WS_CLOSED_SUCCESS: 1000,
3306
+ };
3306
3307
  /**
3307
3308
  * retryInterval - A retry interval which increases acc to number of failures
3308
3309
  *
@@ -3314,9 +3315,6 @@ function retryInterval(numberOfFailures) {
3314
3315
  const min = Math.min(Math.max(250, (numberOfFailures - 1) * 2000), 5000);
3315
3316
  return Math.floor(Math.random() * (max - min) + min);
3316
3317
  }
3317
- function randomId() {
3318
- return generateUUIDv4();
3319
- }
3320
3318
  function hex(bytes) {
3321
3319
  let s = '';
3322
3320
  for (let i = 0; i < bytes.length; i++) {
@@ -3329,33 +3327,24 @@ function generateUUIDv4() {
3329
3327
  const bytes = getRandomBytes(16);
3330
3328
  bytes[6] = (bytes[6] & 0x0f) | 0x40; // version
3331
3329
  bytes[8] = (bytes[8] & 0xbf) | 0x80; // variant
3332
- return (hex(bytes.subarray(0, 4)) +
3333
- '-' +
3334
- hex(bytes.subarray(4, 6)) +
3335
- '-' +
3336
- hex(bytes.subarray(6, 8)) +
3337
- '-' +
3338
- hex(bytes.subarray(8, 10)) +
3339
- '-' +
3340
- hex(bytes.subarray(10, 16)));
3341
- }
3342
- function getRandomValuesWithMathRandom(bytes) {
3343
- const max = Math.pow(2, (8 * bytes.byteLength) / bytes.length);
3344
- for (let i = 0; i < bytes.length; i++) {
3345
- bytes[i] = Math.random() * max;
3346
- }
3330
+ return [
3331
+ hex(bytes.subarray(0, 4)),
3332
+ hex(bytes.subarray(4, 6)),
3333
+ hex(bytes.subarray(6, 8)),
3334
+ hex(bytes.subarray(8, 10)),
3335
+ hex(bytes.subarray(10, 16)),
3336
+ ].join('-');
3347
3337
  }
3348
3338
  const getRandomValues = (() => {
3349
- if (typeof crypto !== 'undefined' &&
3350
- typeof crypto?.getRandomValues !== 'undefined') {
3339
+ if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
3351
3340
  return crypto.getRandomValues.bind(crypto);
3352
3341
  }
3353
- else if (typeof msCrypto !== 'undefined') {
3354
- return msCrypto.getRandomValues.bind(msCrypto);
3355
- }
3356
- else {
3357
- return getRandomValuesWithMathRandom;
3358
- }
3342
+ return function getRandomValuesWithMathRandom(bytes) {
3343
+ const max = Math.pow(2, (8 * bytes.byteLength) / bytes.length);
3344
+ for (let i = 0; i < bytes.length; i++) {
3345
+ bytes[i] = Math.random() * max;
3346
+ }
3347
+ };
3359
3348
  })();
3360
3349
  function getRandomBytes(length) {
3361
3350
  const bytes = new Uint8Array(length);
@@ -5813,11 +5802,6 @@ class Publisher extends BasePeerConnection {
5813
5802
  }
5814
5803
  for (const track of this.clonedTracks) {
5815
5804
  this.stopTrack(track);
5816
- // @ts-expect-error release() is present in react-native-webrtc
5817
- if (track && typeof track.release === 'function') {
5818
- // @ts-expect-error
5819
- track.release();
5820
- }
5821
5805
  }
5822
5806
  };
5823
5807
  this.changePublishQuality = async (videoSender) => {
@@ -7470,7 +7454,7 @@ const aggregate = (stats) => {
7470
7454
  return report;
7471
7455
  };
7472
7456
 
7473
- const version = "1.18.2";
7457
+ const version = "1.18.4";
7474
7458
  const [major, minor, patch] = version.split('.');
7475
7459
  let sdkInfo = {
7476
7460
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -9349,10 +9333,9 @@ class CameraManager extends InputMediaDeviceManager {
9349
9333
  this.logger('warn', 'No video track found to do direction selection');
9350
9334
  return;
9351
9335
  }
9352
- const constraints = {
9336
+ await videoTrack.applyConstraints({
9353
9337
  facingMode: direction === 'front' ? 'user' : 'environment',
9354
- };
9355
- await videoTrack.applyConstraints(constraints);
9338
+ });
9356
9339
  this.state.setDirection(direction);
9357
9340
  this.state.setDevice(undefined);
9358
9341
  }
@@ -12016,9 +11999,7 @@ var https = null;
12016
11999
  class StableWSConnection {
12017
12000
  constructor(client) {
12018
12001
  this._log = (msg, extra = {}, level = 'info') => {
12019
- this.client.logger(level, 'connection:' + msg, {
12020
- ...extra,
12021
- });
12002
+ this.client.logger(level, `connection:${msg}`, extra);
12022
12003
  };
12023
12004
  this.setClient = (client) => {
12024
12005
  this.client = client;
@@ -12072,7 +12053,7 @@ class StableWSConnection {
12072
12053
  this.client.logger('error', `Token not set, can't connect authenticate`);
12073
12054
  return;
12074
12055
  }
12075
- const authMessage = {
12056
+ const authMessage = JSON.stringify({
12076
12057
  token,
12077
12058
  user_details: {
12078
12059
  id: user.id,
@@ -12080,8 +12061,9 @@ class StableWSConnection {
12080
12061
  image: user.image,
12081
12062
  custom: user.custom,
12082
12063
  },
12083
- };
12084
- this.ws?.send(JSON.stringify(authMessage));
12064
+ });
12065
+ this._log(`onopen() - Sending auth message ${authMessage}`, {}, 'trace');
12066
+ this.ws?.send(authMessage);
12085
12067
  this._log('onopen() - onopen callback', { wsID });
12086
12068
  };
12087
12069
  this.onmessage = (wsID, event) => {
@@ -12094,10 +12076,12 @@ class StableWSConnection {
12094
12076
  // we wait till the first message before we consider the connection open.
12095
12077
  // the reason for this is that auth errors and similar errors trigger a ws.onopen and immediately
12096
12078
  // after that a ws.onclose.
12097
- if (!this.isResolved && data && data.type === 'connection.error') {
12098
- this.isResolved = true;
12079
+ if (!this.isConnectionOpenResolved &&
12080
+ data &&
12081
+ data.type === 'connection.error') {
12082
+ this.isConnectionOpenResolved = true;
12099
12083
  if (data.error) {
12100
- this.rejectPromise?.(this._errorFromWSEvent(data, false));
12084
+ this.rejectConnectionOpen?.(this._errorFromWSEvent(data, false));
12101
12085
  return;
12102
12086
  }
12103
12087
  }
@@ -12109,7 +12093,7 @@ class StableWSConnection {
12109
12093
  this.scheduleNextPing();
12110
12094
  }
12111
12095
  if (data && data.type === 'connection.ok') {
12112
- this.resolvePromise?.(data);
12096
+ this.resolveConnectionOpen?.(data);
12113
12097
  this._setHealth(true);
12114
12098
  }
12115
12099
  if (data && data.type === 'connection.error' && data.error) {
@@ -12146,7 +12130,7 @@ class StableWSConnection {
12146
12130
  error.wasClean = event.wasClean;
12147
12131
  // @ts-expect-error
12148
12132
  error.target = event.target;
12149
- this.rejectPromise?.(error);
12133
+ this.rejectConnectionOpen?.(error);
12150
12134
  this._log(`onclose() - WS connection reject with error ${event.reason}`, {
12151
12135
  event,
12152
12136
  });
@@ -12156,7 +12140,7 @@ class StableWSConnection {
12156
12140
  this.totalFailures += 1;
12157
12141
  this._setHealth(false);
12158
12142
  this.isConnecting = false;
12159
- this.rejectPromise?.(this._errorFromWSEvent(event));
12143
+ this.rejectConnectionOpen?.(this._errorFromWSEvent(event));
12160
12144
  this._log(`onclose() - WS connection closed. Calling reconnect ...`, {
12161
12145
  event,
12162
12146
  });
@@ -12171,7 +12155,7 @@ class StableWSConnection {
12171
12155
  this.totalFailures += 1;
12172
12156
  this._setHealth(false);
12173
12157
  this.isConnecting = false;
12174
- this.rejectPromise?.(new Error(`WebSocket error: ${event}`));
12158
+ this.rejectConnectionOpen?.(new Error(`WebSocket error: ${event}`));
12175
12159
  this._log(`onerror() - WS connection resulted into error`, { event });
12176
12160
  this._reconnect();
12177
12161
  };
@@ -12237,11 +12221,11 @@ class StableWSConnection {
12237
12221
  * _setupPromise - sets up the this.connectOpen promise
12238
12222
  */
12239
12223
  this._setupConnectionPromise = () => {
12240
- this.isResolved = false;
12224
+ this.isConnectionOpenResolved = false;
12241
12225
  /** a promise that is resolved once ws.open is called */
12242
12226
  this.connectionOpenSafe = makeSafePromise(new Promise((resolve, reject) => {
12243
- this.resolvePromise = resolve;
12244
- this.rejectPromise = reject;
12227
+ this.resolveConnectionOpen = resolve;
12228
+ this.rejectConnectionOpen = reject;
12245
12229
  }));
12246
12230
  };
12247
12231
  /**
@@ -12292,7 +12276,7 @@ class StableWSConnection {
12292
12276
  /** To avoid reconnect if client is disconnected */
12293
12277
  this.isDisconnected = false;
12294
12278
  /** Boolean that indicates if the connection promise is resolved */
12295
- this.isResolved = false;
12279
+ this.isConnectionOpenResolved = false;
12296
12280
  /** Boolean that indicates if we have a working connection to the server */
12297
12281
  this.isHealthy = false;
12298
12282
  /** Incremented when a new WS connection is made */
@@ -12437,9 +12421,8 @@ class StableWSConnection {
12437
12421
  */
12438
12422
  async _connect() {
12439
12423
  if (this.isConnecting)
12440
- return; // simply ignore _connect if it's currently trying to connect
12424
+ return; // ignore _connect if it's currently trying to connect
12441
12425
  this.isConnecting = true;
12442
- this.requestID = randomId();
12443
12426
  let isTokenReady = false;
12444
12427
  try {
12445
12428
  this._log(`_connect() - waiting for token`);
@@ -12459,10 +12442,7 @@ class StableWSConnection {
12459
12442
  }
12460
12443
  this._setupConnectionPromise();
12461
12444
  const wsURL = this._buildUrl();
12462
- this._log(`_connect() - Connecting to ${wsURL}`, {
12463
- wsURL,
12464
- requestID: this.requestID,
12465
- });
12445
+ this._log(`_connect() - Connecting to ${wsURL}`);
12466
12446
  const WS = this.client.options.WebSocketImpl ?? WebSocket;
12467
12447
  this.ws = new WS(wsURL);
12468
12448
  this.ws.onopen = this.onopen.bind(this, this.wsID);
@@ -12606,6 +12586,8 @@ const decodeBase64 = (s) => {
12606
12586
  */
12607
12587
  class TokenManager {
12608
12588
  constructor(secret) {
12589
+ this.loadTokenPromise = null;
12590
+ this.type = 'static';
12609
12591
  /**
12610
12592
  * Set the static string token or token provider.
12611
12593
  * Token provider should return a token string or a promise which resolves to string token.
@@ -12648,9 +12630,7 @@ class TokenManager {
12648
12630
  if (!this.secret && !tokenOrProvider) {
12649
12631
  throw new Error('User token can not be empty');
12650
12632
  }
12651
- if (tokenOrProvider &&
12652
- typeof tokenOrProvider !== 'string' &&
12653
- !isFunction(tokenOrProvider)) {
12633
+ if (typeof tokenOrProvider !== 'string' && !isFunction(tokenOrProvider)) {
12654
12634
  throw new Error('User token should either be a string or a function');
12655
12635
  }
12656
12636
  if (typeof tokenOrProvider === 'string') {
@@ -12701,9 +12681,7 @@ class TokenManager {
12701
12681
  throw new Error(`User token is not set. Either client.connectUser wasn't called or client.disconnect was called`);
12702
12682
  };
12703
12683
  this.isStatic = () => this.type === 'static';
12704
- this.loadTokenPromise = null;
12705
12684
  this.secret = secret;
12706
- this.type = 'static';
12707
12685
  }
12708
12686
  }
12709
12687
 
@@ -12772,11 +12750,11 @@ class StreamClient {
12772
12750
  * connectUser - Set the current user and open a WebSocket connection
12773
12751
  *
12774
12752
  * @param user Data about this user. IE {name: "john"}
12775
- * @param {TokenOrProvider} userTokenOrProvider Token or provider
12753
+ * @param {TokenOrProvider} tokenOrProvider Token or provider
12776
12754
  *
12777
12755
  * @return {ConnectAPIResponse} Returns a promise that resolves when the connection is setup
12778
12756
  */
12779
- this.connectUser = async (user, userTokenOrProvider) => {
12757
+ this.connectUser = async (user, tokenOrProvider) => {
12780
12758
  if (!user.id) {
12781
12759
  throw new Error('The "id" field on the user is missing');
12782
12760
  }
@@ -12784,9 +12762,9 @@ class StreamClient {
12784
12762
  * Calling connectUser multiple times is potentially the result of a bad integration, however,
12785
12763
  * If the user id remains the same we don't throw error
12786
12764
  */
12787
- if (this.userID === user.id && this.setUserPromise) {
12765
+ if (this.userID === user.id && this.connectUserTask) {
12788
12766
  this.logger('warn', 'Consecutive calls to connectUser is detected, ideally you should only call this function once in your app.');
12789
- return this.setUserPromise;
12767
+ return this.connectUserTask;
12790
12768
  }
12791
12769
  if (this.userID) {
12792
12770
  throw new Error('Use client.disconnect() before trying to connect as a different user. connectUser was called twice.');
@@ -12797,26 +12775,24 @@ class StreamClient {
12797
12775
  // we generate the client id client side
12798
12776
  this.userID = user.id;
12799
12777
  this.anonymous = false;
12800
- const setTokenPromise = this._setToken(user, userTokenOrProvider, this.anonymous);
12778
+ await this.tokenManager.setTokenOrProvider(tokenOrProvider, user, false);
12801
12779
  this._setUser(user);
12802
- const wsPromise = this.openConnection();
12803
- this.setUserPromise = Promise.all([setTokenPromise, wsPromise]).then((result) => result[1]);
12780
+ this.connectUserTask = this.openConnection();
12804
12781
  try {
12805
12782
  addConnectionEventListeners(this.updateNetworkConnectionStatus);
12806
- return await this.setUserPromise;
12783
+ return await this.connectUserTask;
12807
12784
  }
12808
12785
  catch (err) {
12809
12786
  if (this.persistUserOnConnectionFailure) {
12810
12787
  // cleanup client to allow the user to retry connectUser again
12811
- this.closeConnection();
12788
+ await this.closeConnection();
12812
12789
  }
12813
12790
  else {
12814
- this.disconnectUser();
12791
+ await this.disconnectUser();
12815
12792
  }
12816
12793
  throw err;
12817
12794
  }
12818
12795
  };
12819
- this._setToken = (user, userTokenOrProvider, isAnonymous) => this.tokenManager.setTokenOrProvider(userTokenOrProvider, user, isAnonymous);
12820
12796
  this._setUser = (user) => {
12821
12797
  /**
12822
12798
  * This one is used by the frontend. This is a copy of the current user object stored on backend.
@@ -12860,7 +12836,7 @@ class StreamClient {
12860
12836
  return;
12861
12837
  }
12862
12838
  this._setupConnectionIdPromise();
12863
- this.clientID = `${this.userID}--${randomId()}`;
12839
+ this.clientID = `${this.userID}--${generateUUIDv4()}`;
12864
12840
  const newWsPromise = this.connect();
12865
12841
  this.wsPromiseSafe = makeSafePromise(newWsPromise);
12866
12842
  return await newWsPromise;
@@ -12898,7 +12874,7 @@ class StreamClient {
12898
12874
  addConnectionEventListeners(this.updateNetworkConnectionStatus);
12899
12875
  this._setupConnectionIdPromise();
12900
12876
  this.anonymous = true;
12901
- await this._setToken(user, tokenOrProvider, this.anonymous);
12877
+ await this.tokenManager.setTokenOrProvider(tokenOrProvider, user, true);
12902
12878
  this._setUser(user);
12903
12879
  // some endpoints require a connection_id to be resolved.
12904
12880
  // as anonymous users aren't allowed to open WS connections, we just
@@ -13106,7 +13082,7 @@ class StreamClient {
13106
13082
  this.getUserAgent = () => {
13107
13083
  if (!this.cachedUserAgent) {
13108
13084
  const { clientAppIdentifier = {} } = this.options;
13109
- const { sdkName = 'js', sdkVersion = "1.18.2", ...extras } = clientAppIdentifier;
13085
+ const { sdkName = 'js', sdkVersion = "1.18.4", ...extras } = clientAppIdentifier;
13110
13086
  this.cachedUserAgent = [
13111
13087
  `stream-video-${sdkName}-v${sdkVersion}`,
13112
13088
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -13125,7 +13101,7 @@ class StreamClient {
13125
13101
  if (!options.headers?.['x-client-request-id']) {
13126
13102
  options.headers = {
13127
13103
  ...options.headers,
13128
- 'x-client-request-id': randomId(),
13104
+ 'x-client-request-id': generateUUIDv4(),
13129
13105
  };
13130
13106
  }
13131
13107
  return {
@@ -13196,7 +13172,7 @@ class StreamClient {
13196
13172
  // WS connection is initialized when setUser is called
13197
13173
  this.wsConnection = null;
13198
13174
  this.wsPromiseSafe = null;
13199
- this.setUserPromise = null;
13175
+ this.connectUserTask = null;
13200
13176
  // mapping between channel groups and configs
13201
13177
  this.anonymous = false;
13202
13178
  this.persistUserOnConnectionFailure =
@@ -13205,7 +13181,7 @@ class StreamClient {
13205
13181
  // generated from secret.
13206
13182
  this.tokenManager = new TokenManager(this.secret);
13207
13183
  this.consecutiveFailures = 0;
13208
- this.defaultWSTimeout = 15000;
13184
+ this.defaultWSTimeout = this.options.defaultWsTimeout ?? 15000;
13209
13185
  this.logger = isFunction(inputOptions.logger)
13210
13186
  ? inputOptions.logger
13211
13187
  : () => null;
@@ -13282,6 +13258,7 @@ const createTokenOrProvider = (options) => {
13282
13258
  */
13283
13259
  class StreamVideoClient {
13284
13260
  constructor(apiKeyOrArgs, opts) {
13261
+ this.effectsRegistered = false;
13285
13262
  this.eventHandlersToUnregister = [];
13286
13263
  this.connectionConcurrencyTag = Symbol('connectionConcurrencyTag');
13287
13264
  this.registerClientInstance = (apiKey, user) => {
@@ -13291,27 +13268,9 @@ class StreamVideoClient {
13291
13268
  }
13292
13269
  StreamVideoClient._instances.set(instanceKey, this);
13293
13270
  };
13294
- /**
13295
- * Connects the given user to the client.
13296
- * Only one user can connect at a time, if you want to change users, call `disconnectUser` before connecting a new user.
13297
- * If the connection is successful, the connected user [state variable](#readonlystatestore) will be updated accordingly.
13298
- *
13299
- * @param user the user to connect.
13300
- * @param token a token or a function that returns a token.
13301
- */
13302
- this.connectUser = async (user, token) => {
13303
- if (user.type === 'anonymous') {
13304
- user.id = '!anon';
13305
- return this.connectAnonymousUser(user, token);
13306
- }
13307
- const connectUser = user.type === 'guest'
13308
- ? () => this.streamClient.connectGuestUser(user)
13309
- : () => this.streamClient.connectUser(user, token);
13310
- const connectUserResponse = await withoutConcurrency(this.connectionConcurrencyTag, () => connectUser());
13311
- // connectUserResponse will be void if connectUser called twice for the same user
13312
- if (connectUserResponse?.me) {
13313
- this.writeableStateStore.setConnectedUser(connectUserResponse.me);
13314
- }
13271
+ this.registerEffects = () => {
13272
+ if (this.effectsRegistered)
13273
+ return;
13315
13274
  this.eventHandlersToUnregister.push(this.on('connection.changed', (event) => {
13316
13275
  if (!event.online)
13317
13276
  return;
@@ -13331,7 +13290,7 @@ class StreamVideoClient {
13331
13290
  }));
13332
13291
  this.eventHandlersToUnregister.push(this.on('call.created', (event) => {
13333
13292
  const { call, members } = event;
13334
- if (user.id === call.created_by.id) {
13293
+ if (this.state.connectedUser?.id === call.created_by.id) {
13335
13294
  this.logger('warn', 'Received `call.created` sent by the current user');
13336
13295
  return;
13337
13296
  }
@@ -13348,7 +13307,7 @@ class StreamVideoClient {
13348
13307
  }));
13349
13308
  this.eventHandlersToUnregister.push(this.on('call.ring', async (event) => {
13350
13309
  const { call, members } = event;
13351
- if (user.id === call.created_by.id) {
13310
+ if (this.state.connectedUser?.id === call.created_by.id) {
13352
13311
  this.logger('debug', 'Received `call.ring` sent by the current user so ignoring the event');
13353
13312
  return;
13354
13313
  }
@@ -13372,6 +13331,53 @@ class StreamVideoClient {
13372
13331
  await newCallInstance.get();
13373
13332
  }
13374
13333
  }));
13334
+ this.effectsRegistered = true;
13335
+ };
13336
+ /**
13337
+ * Connects the given user to the client.
13338
+ * Only one user can connect at a time, if you want to change users, call `disconnectUser` before connecting a new user.
13339
+ * If the connection is successful, the connected user [state variable](#readonlystatestore) will be updated accordingly.
13340
+ *
13341
+ * @param user the user to connect.
13342
+ * @param tokenOrProvider a token or a function that returns a token.
13343
+ */
13344
+ this.connectUser = async (user, tokenOrProvider) => {
13345
+ if (user.type === 'anonymous') {
13346
+ user.id = '!anon';
13347
+ return this.connectAnonymousUser(user, tokenOrProvider);
13348
+ }
13349
+ const connectUserResponse = await withoutConcurrency(this.connectionConcurrencyTag, async () => {
13350
+ const client = this.streamClient;
13351
+ const { maxConnectUserRetries = 5, onConnectUserError, persistUserOnConnectionFailure, } = client.options;
13352
+ const errorQueue = [];
13353
+ for (let attempt = 0; attempt < maxConnectUserRetries; attempt++) {
13354
+ try {
13355
+ this.logger('trace', `Connecting user (${attempt})`, user);
13356
+ return user.type === 'guest'
13357
+ ? await client.connectGuestUser(user)
13358
+ : await client.connectUser(user, tokenOrProvider);
13359
+ }
13360
+ catch (err) {
13361
+ this.logger('warn', `Failed to connect a user (${attempt})`, err);
13362
+ errorQueue.push(err);
13363
+ if (attempt === maxConnectUserRetries - 1) {
13364
+ onConnectUserError?.(err, errorQueue);
13365
+ throw err;
13366
+ }
13367
+ // we need to force to disconnect the user if the client is
13368
+ // configured to persist the user on connection failure
13369
+ if (persistUserOnConnectionFailure) {
13370
+ await client.disconnectUser();
13371
+ }
13372
+ await sleep(retryInterval(attempt));
13373
+ }
13374
+ }
13375
+ });
13376
+ // connectUserResponse will be void if connectUser called twice for the same user
13377
+ if (connectUserResponse?.me) {
13378
+ this.writeableStateStore.setConnectedUser(connectUserResponse.me);
13379
+ }
13380
+ this.registerEffects();
13375
13381
  return connectUserResponse;
13376
13382
  };
13377
13383
  /**
@@ -13383,16 +13389,19 @@ class StreamVideoClient {
13383
13389
  * https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
13384
13390
  */
13385
13391
  this.disconnectUser = async (timeout) => {
13386
- const { user, key } = this.streamClient;
13387
- if (!user)
13388
- return;
13389
- await withoutConcurrency(this.connectionConcurrencyTag, () => this.streamClient.disconnectUser(timeout));
13390
- if (user.id) {
13391
- StreamVideoClient._instances.delete(getInstanceKey(key, user));
13392
- }
13393
- this.eventHandlersToUnregister.forEach((unregister) => unregister());
13394
- this.eventHandlersToUnregister = [];
13395
- this.writeableStateStore.setConnectedUser(undefined);
13392
+ await withoutConcurrency(this.connectionConcurrencyTag, async () => {
13393
+ const { user, key } = this.streamClient;
13394
+ if (!user)
13395
+ return;
13396
+ await this.streamClient.disconnectUser(timeout);
13397
+ if (user.id) {
13398
+ StreamVideoClient._instances.delete(getInstanceKey(key, user));
13399
+ }
13400
+ this.eventHandlersToUnregister.forEach((unregister) => unregister());
13401
+ this.eventHandlersToUnregister = [];
13402
+ this.effectsRegistered = false;
13403
+ this.writeableStateStore.setConnectedUser(undefined);
13404
+ });
13396
13405
  };
13397
13406
  /**
13398
13407
  * You can subscribe to WebSocket events provided by the API.