@stream-io/video-client 1.18.3 → 1.18.5

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/dist/index.es.js CHANGED
@@ -3303,7 +3303,8 @@ function isFunction(value) {
3303
3303
  */
3304
3304
  const KnownCodes = {
3305
3305
  TOKEN_EXPIRED: 40,
3306
- WS_CLOSED_SUCCESS: 1000};
3306
+ WS_CLOSED_SUCCESS: 1000,
3307
+ };
3307
3308
  /**
3308
3309
  * retryInterval - A retry interval which increases acc to number of failures
3309
3310
  *
@@ -3315,9 +3316,6 @@ function retryInterval(numberOfFailures) {
3315
3316
  const min = Math.min(Math.max(250, (numberOfFailures - 1) * 2000), 5000);
3316
3317
  return Math.floor(Math.random() * (max - min) + min);
3317
3318
  }
3318
- function randomId() {
3319
- return generateUUIDv4();
3320
- }
3321
3319
  function hex(bytes) {
3322
3320
  let s = '';
3323
3321
  for (let i = 0; i < bytes.length; i++) {
@@ -3330,33 +3328,24 @@ function generateUUIDv4() {
3330
3328
  const bytes = getRandomBytes(16);
3331
3329
  bytes[6] = (bytes[6] & 0x0f) | 0x40; // version
3332
3330
  bytes[8] = (bytes[8] & 0xbf) | 0x80; // variant
3333
- return (hex(bytes.subarray(0, 4)) +
3334
- '-' +
3335
- hex(bytes.subarray(4, 6)) +
3336
- '-' +
3337
- hex(bytes.subarray(6, 8)) +
3338
- '-' +
3339
- hex(bytes.subarray(8, 10)) +
3340
- '-' +
3341
- hex(bytes.subarray(10, 16)));
3342
- }
3343
- function getRandomValuesWithMathRandom(bytes) {
3344
- const max = Math.pow(2, (8 * bytes.byteLength) / bytes.length);
3345
- for (let i = 0; i < bytes.length; i++) {
3346
- bytes[i] = Math.random() * max;
3347
- }
3331
+ return [
3332
+ hex(bytes.subarray(0, 4)),
3333
+ hex(bytes.subarray(4, 6)),
3334
+ hex(bytes.subarray(6, 8)),
3335
+ hex(bytes.subarray(8, 10)),
3336
+ hex(bytes.subarray(10, 16)),
3337
+ ].join('-');
3348
3338
  }
3349
3339
  const getRandomValues = (() => {
3350
- if (typeof crypto !== 'undefined' &&
3351
- typeof crypto?.getRandomValues !== 'undefined') {
3340
+ if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
3352
3341
  return crypto.getRandomValues.bind(crypto);
3353
3342
  }
3354
- else if (typeof msCrypto !== 'undefined') {
3355
- return msCrypto.getRandomValues.bind(msCrypto);
3356
- }
3357
- else {
3358
- return getRandomValuesWithMathRandom;
3359
- }
3343
+ return function getRandomValuesWithMathRandom(bytes) {
3344
+ const max = Math.pow(2, (8 * bytes.byteLength) / bytes.length);
3345
+ for (let i = 0; i < bytes.length; i++) {
3346
+ bytes[i] = Math.random() * max;
3347
+ }
3348
+ };
3360
3349
  })();
3361
3350
  function getRandomBytes(length) {
3362
3351
  const bytes = new Uint8Array(length);
@@ -3657,7 +3646,10 @@ function hasPending(tag) {
3657
3646
  return pendingPromises.has(tag);
3658
3647
  }
3659
3648
  async function settled(tag) {
3660
- await pendingPromises.get(tag)?.promise;
3649
+ let pending;
3650
+ while ((pending = pendingPromises.get(tag))) {
3651
+ await pending.promise;
3652
+ }
3661
3653
  }
3662
3654
  /**
3663
3655
  * Implements common functionality of running async functions serially, by chaining
@@ -7466,7 +7458,7 @@ const aggregate = (stats) => {
7466
7458
  return report;
7467
7459
  };
7468
7460
 
7469
- const version = "1.18.3";
7461
+ const version = "1.18.5";
7470
7462
  const [major, minor, patch] = version.split('.');
7471
7463
  let sdkInfo = {
7472
7464
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -8400,6 +8392,7 @@ class BrowserPermission {
8400
8392
  }
8401
8393
  try {
8402
8394
  this.wasPrompted = true;
8395
+ this.setState('prompting');
8403
8396
  const stream = await navigator.mediaDevices.getUserMedia(this.permission.constraints);
8404
8397
  disposeOfMediaStream(stream);
8405
8398
  this.setState('granted');
@@ -8423,6 +8416,7 @@ class BrowserPermission {
8423
8416
  error: e,
8424
8417
  permission: this.permission,
8425
8418
  });
8419
+ this.setState('prompt');
8426
8420
  throw e;
8427
8421
  }
8428
8422
  });
@@ -8434,13 +8428,19 @@ class BrowserPermission {
8434
8428
  return () => this.listeners.delete(cb);
8435
8429
  }
8436
8430
  asObservable() {
8437
- return fromEventPattern((handler) => this.listen(handler), (handler, unlisten) => unlisten()).pipe(
8431
+ return this.getStateObservable().pipe(
8438
8432
  // In some browsers, the 'change' event doesn't reliably emit and hence,
8439
8433
  // permissionState stays in 'prompt' state forever.
8440
8434
  // Typically, this happens when a user grants one-time permission.
8441
8435
  // Instead of checking if a permission is granted, we check if it isn't denied
8442
8436
  map((state) => state !== 'denied'));
8443
8437
  }
8438
+ getIsPromptingObservable() {
8439
+ return this.getStateObservable().pipe(map((state) => state === 'prompting'));
8440
+ }
8441
+ getStateObservable() {
8442
+ return fromEventPattern((handler) => this.listen(handler), (handler, unlisten) => unlisten());
8443
+ }
8444
8444
  setState(state) {
8445
8445
  if (this.state !== state) {
8446
8446
  this.state = state;
@@ -9196,6 +9196,9 @@ class InputMediaDeviceManagerState {
9196
9196
  this.hasBrowserPermission$ = permission
9197
9197
  ? permission.asObservable().pipe(shareReplay(1))
9198
9198
  : of(true);
9199
+ this.isPromptingPermission$ = permission
9200
+ ? permission.getIsPromptingObservable().pipe(shareReplay(1))
9201
+ : of(false);
9199
9202
  }
9200
9203
  /**
9201
9204
  * The device status
@@ -9345,10 +9348,9 @@ class CameraManager extends InputMediaDeviceManager {
9345
9348
  this.logger('warn', 'No video track found to do direction selection');
9346
9349
  return;
9347
9350
  }
9348
- const constraints = {
9351
+ await videoTrack.applyConstraints({
9349
9352
  facingMode: direction === 'front' ? 'user' : 'environment',
9350
- };
9351
- await videoTrack.applyConstraints(constraints);
9353
+ });
9352
9354
  this.state.setDirection(direction);
9353
9355
  this.state.setDevice(undefined);
9354
9356
  }
@@ -12010,9 +12012,7 @@ class Call {
12010
12012
  class StableWSConnection {
12011
12013
  constructor(client) {
12012
12014
  this._log = (msg, extra = {}, level = 'info') => {
12013
- this.client.logger(level, 'connection:' + msg, {
12014
- ...extra,
12015
- });
12015
+ this.client.logger(level, `connection:${msg}`, extra);
12016
12016
  };
12017
12017
  this.setClient = (client) => {
12018
12018
  this.client = client;
@@ -12066,7 +12066,7 @@ class StableWSConnection {
12066
12066
  this.client.logger('error', `Token not set, can't connect authenticate`);
12067
12067
  return;
12068
12068
  }
12069
- const authMessage = {
12069
+ const authMessage = JSON.stringify({
12070
12070
  token,
12071
12071
  user_details: {
12072
12072
  id: user.id,
@@ -12074,8 +12074,9 @@ class StableWSConnection {
12074
12074
  image: user.image,
12075
12075
  custom: user.custom,
12076
12076
  },
12077
- };
12078
- this.ws?.send(JSON.stringify(authMessage));
12077
+ });
12078
+ this._log(`onopen() - Sending auth message ${authMessage}`, {}, 'trace');
12079
+ this.ws?.send(authMessage);
12079
12080
  this._log('onopen() - onopen callback', { wsID });
12080
12081
  };
12081
12082
  this.onmessage = (wsID, event) => {
@@ -12088,10 +12089,12 @@ class StableWSConnection {
12088
12089
  // we wait till the first message before we consider the connection open.
12089
12090
  // the reason for this is that auth errors and similar errors trigger a ws.onopen and immediately
12090
12091
  // after that a ws.onclose.
12091
- if (!this.isResolved && data && data.type === 'connection.error') {
12092
- this.isResolved = true;
12092
+ if (!this.isConnectionOpenResolved &&
12093
+ data &&
12094
+ data.type === 'connection.error') {
12095
+ this.isConnectionOpenResolved = true;
12093
12096
  if (data.error) {
12094
- this.rejectPromise?.(this._errorFromWSEvent(data, false));
12097
+ this.rejectConnectionOpen?.(this._errorFromWSEvent(data, false));
12095
12098
  return;
12096
12099
  }
12097
12100
  }
@@ -12103,7 +12106,7 @@ class StableWSConnection {
12103
12106
  this.scheduleNextPing();
12104
12107
  }
12105
12108
  if (data && data.type === 'connection.ok') {
12106
- this.resolvePromise?.(data);
12109
+ this.resolveConnectionOpen?.(data);
12107
12110
  this._setHealth(true);
12108
12111
  }
12109
12112
  if (data && data.type === 'connection.error' && data.error) {
@@ -12140,7 +12143,7 @@ class StableWSConnection {
12140
12143
  error.wasClean = event.wasClean;
12141
12144
  // @ts-expect-error
12142
12145
  error.target = event.target;
12143
- this.rejectPromise?.(error);
12146
+ this.rejectConnectionOpen?.(error);
12144
12147
  this._log(`onclose() - WS connection reject with error ${event.reason}`, {
12145
12148
  event,
12146
12149
  });
@@ -12150,7 +12153,7 @@ class StableWSConnection {
12150
12153
  this.totalFailures += 1;
12151
12154
  this._setHealth(false);
12152
12155
  this.isConnecting = false;
12153
- this.rejectPromise?.(this._errorFromWSEvent(event));
12156
+ this.rejectConnectionOpen?.(this._errorFromWSEvent(event));
12154
12157
  this._log(`onclose() - WS connection closed. Calling reconnect ...`, {
12155
12158
  event,
12156
12159
  });
@@ -12165,7 +12168,7 @@ class StableWSConnection {
12165
12168
  this.totalFailures += 1;
12166
12169
  this._setHealth(false);
12167
12170
  this.isConnecting = false;
12168
- this.rejectPromise?.(new Error(`WebSocket error: ${event}`));
12171
+ this.rejectConnectionOpen?.(new Error(`WebSocket error: ${event}`));
12169
12172
  this._log(`onerror() - WS connection resulted into error`, { event });
12170
12173
  this._reconnect();
12171
12174
  };
@@ -12231,11 +12234,11 @@ class StableWSConnection {
12231
12234
  * _setupPromise - sets up the this.connectOpen promise
12232
12235
  */
12233
12236
  this._setupConnectionPromise = () => {
12234
- this.isResolved = false;
12237
+ this.isConnectionOpenResolved = false;
12235
12238
  /** a promise that is resolved once ws.open is called */
12236
12239
  this.connectionOpenSafe = makeSafePromise(new Promise((resolve, reject) => {
12237
- this.resolvePromise = resolve;
12238
- this.rejectPromise = reject;
12240
+ this.resolveConnectionOpen = resolve;
12241
+ this.rejectConnectionOpen = reject;
12239
12242
  }));
12240
12243
  };
12241
12244
  /**
@@ -12286,7 +12289,7 @@ class StableWSConnection {
12286
12289
  /** To avoid reconnect if client is disconnected */
12287
12290
  this.isDisconnected = false;
12288
12291
  /** Boolean that indicates if the connection promise is resolved */
12289
- this.isResolved = false;
12292
+ this.isConnectionOpenResolved = false;
12290
12293
  /** Boolean that indicates if we have a working connection to the server */
12291
12294
  this.isHealthy = false;
12292
12295
  /** Incremented when a new WS connection is made */
@@ -12431,9 +12434,8 @@ class StableWSConnection {
12431
12434
  */
12432
12435
  async _connect() {
12433
12436
  if (this.isConnecting)
12434
- return; // simply ignore _connect if it's currently trying to connect
12437
+ return; // ignore _connect if it's currently trying to connect
12435
12438
  this.isConnecting = true;
12436
- this.requestID = randomId();
12437
12439
  let isTokenReady = false;
12438
12440
  try {
12439
12441
  this._log(`_connect() - waiting for token`);
@@ -12453,10 +12455,7 @@ class StableWSConnection {
12453
12455
  }
12454
12456
  this._setupConnectionPromise();
12455
12457
  const wsURL = this._buildUrl();
12456
- this._log(`_connect() - Connecting to ${wsURL}`, {
12457
- wsURL,
12458
- requestID: this.requestID,
12459
- });
12458
+ this._log(`_connect() - Connecting to ${wsURL}`);
12460
12459
  const WS = this.client.options.WebSocketImpl ?? WebSocket;
12461
12460
  this.ws = new WS(wsURL);
12462
12461
  this.ws.onopen = this.onopen.bind(this, this.wsID);
@@ -12600,6 +12599,8 @@ const decodeBase64 = (s) => {
12600
12599
  */
12601
12600
  class TokenManager {
12602
12601
  constructor(secret) {
12602
+ this.loadTokenPromise = null;
12603
+ this.type = 'static';
12603
12604
  /**
12604
12605
  * Set the static string token or token provider.
12605
12606
  * Token provider should return a token string or a promise which resolves to string token.
@@ -12642,9 +12643,7 @@ class TokenManager {
12642
12643
  if (!this.secret && !tokenOrProvider) {
12643
12644
  throw new Error('User token can not be empty');
12644
12645
  }
12645
- if (tokenOrProvider &&
12646
- typeof tokenOrProvider !== 'string' &&
12647
- !isFunction(tokenOrProvider)) {
12646
+ if (typeof tokenOrProvider !== 'string' && !isFunction(tokenOrProvider)) {
12648
12647
  throw new Error('User token should either be a string or a function');
12649
12648
  }
12650
12649
  if (typeof tokenOrProvider === 'string') {
@@ -12695,9 +12694,7 @@ class TokenManager {
12695
12694
  throw new Error(`User token is not set. Either client.connectUser wasn't called or client.disconnect was called`);
12696
12695
  };
12697
12696
  this.isStatic = () => this.type === 'static';
12698
- this.loadTokenPromise = null;
12699
12697
  this.secret = secret;
12700
- this.type = 'static';
12701
12698
  }
12702
12699
  }
12703
12700
 
@@ -12766,11 +12763,11 @@ class StreamClient {
12766
12763
  * connectUser - Set the current user and open a WebSocket connection
12767
12764
  *
12768
12765
  * @param user Data about this user. IE {name: "john"}
12769
- * @param {TokenOrProvider} userTokenOrProvider Token or provider
12766
+ * @param {TokenOrProvider} tokenOrProvider Token or provider
12770
12767
  *
12771
12768
  * @return {ConnectAPIResponse} Returns a promise that resolves when the connection is setup
12772
12769
  */
12773
- this.connectUser = async (user, userTokenOrProvider) => {
12770
+ this.connectUser = async (user, tokenOrProvider) => {
12774
12771
  if (!user.id) {
12775
12772
  throw new Error('The "id" field on the user is missing');
12776
12773
  }
@@ -12778,9 +12775,9 @@ class StreamClient {
12778
12775
  * Calling connectUser multiple times is potentially the result of a bad integration, however,
12779
12776
  * If the user id remains the same we don't throw error
12780
12777
  */
12781
- if (this.userID === user.id && this.setUserPromise) {
12778
+ if (this.userID === user.id && this.connectUserTask) {
12782
12779
  this.logger('warn', 'Consecutive calls to connectUser is detected, ideally you should only call this function once in your app.');
12783
- return this.setUserPromise;
12780
+ return this.connectUserTask;
12784
12781
  }
12785
12782
  if (this.userID) {
12786
12783
  throw new Error('Use client.disconnect() before trying to connect as a different user. connectUser was called twice.');
@@ -12791,26 +12788,24 @@ class StreamClient {
12791
12788
  // we generate the client id client side
12792
12789
  this.userID = user.id;
12793
12790
  this.anonymous = false;
12794
- const setTokenPromise = this._setToken(user, userTokenOrProvider, this.anonymous);
12791
+ await this.tokenManager.setTokenOrProvider(tokenOrProvider, user, false);
12795
12792
  this._setUser(user);
12796
- const wsPromise = this.openConnection();
12797
- this.setUserPromise = Promise.all([setTokenPromise, wsPromise]).then((result) => result[1]);
12793
+ this.connectUserTask = this.openConnection();
12798
12794
  try {
12799
12795
  addConnectionEventListeners(this.updateNetworkConnectionStatus);
12800
- return await this.setUserPromise;
12796
+ return await this.connectUserTask;
12801
12797
  }
12802
12798
  catch (err) {
12803
12799
  if (this.persistUserOnConnectionFailure) {
12804
12800
  // cleanup client to allow the user to retry connectUser again
12805
- this.closeConnection();
12801
+ await this.closeConnection();
12806
12802
  }
12807
12803
  else {
12808
- this.disconnectUser();
12804
+ await this.disconnectUser();
12809
12805
  }
12810
12806
  throw err;
12811
12807
  }
12812
12808
  };
12813
- this._setToken = (user, userTokenOrProvider, isAnonymous) => this.tokenManager.setTokenOrProvider(userTokenOrProvider, user, isAnonymous);
12814
12809
  this._setUser = (user) => {
12815
12810
  /**
12816
12811
  * This one is used by the frontend. This is a copy of the current user object stored on backend.
@@ -12854,7 +12849,7 @@ class StreamClient {
12854
12849
  return;
12855
12850
  }
12856
12851
  this._setupConnectionIdPromise();
12857
- this.clientID = `${this.userID}--${randomId()}`;
12852
+ this.clientID = `${this.userID}--${generateUUIDv4()}`;
12858
12853
  const newWsPromise = this.connect();
12859
12854
  this.wsPromiseSafe = makeSafePromise(newWsPromise);
12860
12855
  return await newWsPromise;
@@ -12892,7 +12887,7 @@ class StreamClient {
12892
12887
  addConnectionEventListeners(this.updateNetworkConnectionStatus);
12893
12888
  this._setupConnectionIdPromise();
12894
12889
  this.anonymous = true;
12895
- await this._setToken(user, tokenOrProvider, this.anonymous);
12890
+ await this.tokenManager.setTokenOrProvider(tokenOrProvider, user, true);
12896
12891
  this._setUser(user);
12897
12892
  // some endpoints require a connection_id to be resolved.
12898
12893
  // as anonymous users aren't allowed to open WS connections, we just
@@ -13100,7 +13095,7 @@ class StreamClient {
13100
13095
  this.getUserAgent = () => {
13101
13096
  if (!this.cachedUserAgent) {
13102
13097
  const { clientAppIdentifier = {} } = this.options;
13103
- const { sdkName = 'js', sdkVersion = "1.18.3", ...extras } = clientAppIdentifier;
13098
+ const { sdkName = 'js', sdkVersion = "1.18.5", ...extras } = clientAppIdentifier;
13104
13099
  this.cachedUserAgent = [
13105
13100
  `stream-video-${sdkName}-v${sdkVersion}`,
13106
13101
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -13119,7 +13114,7 @@ class StreamClient {
13119
13114
  if (!options.headers?.['x-client-request-id']) {
13120
13115
  options.headers = {
13121
13116
  ...options.headers,
13122
- 'x-client-request-id': randomId(),
13117
+ 'x-client-request-id': generateUUIDv4(),
13123
13118
  };
13124
13119
  }
13125
13120
  return {
@@ -13190,7 +13185,7 @@ class StreamClient {
13190
13185
  // WS connection is initialized when setUser is called
13191
13186
  this.wsConnection = null;
13192
13187
  this.wsPromiseSafe = null;
13193
- this.setUserPromise = null;
13188
+ this.connectUserTask = null;
13194
13189
  // mapping between channel groups and configs
13195
13190
  this.anonymous = false;
13196
13191
  this.persistUserOnConnectionFailure =
@@ -13199,7 +13194,7 @@ class StreamClient {
13199
13194
  // generated from secret.
13200
13195
  this.tokenManager = new TokenManager(this.secret);
13201
13196
  this.consecutiveFailures = 0;
13202
- this.defaultWSTimeout = 15000;
13197
+ this.defaultWSTimeout = this.options.defaultWsTimeout ?? 15000;
13203
13198
  this.logger = isFunction(inputOptions.logger)
13204
13199
  ? inputOptions.logger
13205
13200
  : () => null;
@@ -13276,6 +13271,7 @@ const createTokenOrProvider = (options) => {
13276
13271
  */
13277
13272
  class StreamVideoClient {
13278
13273
  constructor(apiKeyOrArgs, opts) {
13274
+ this.effectsRegistered = false;
13279
13275
  this.eventHandlersToUnregister = [];
13280
13276
  this.connectionConcurrencyTag = Symbol('connectionConcurrencyTag');
13281
13277
  this.registerClientInstance = (apiKey, user) => {
@@ -13285,27 +13281,9 @@ class StreamVideoClient {
13285
13281
  }
13286
13282
  StreamVideoClient._instances.set(instanceKey, this);
13287
13283
  };
13288
- /**
13289
- * Connects the given user to the client.
13290
- * Only one user can connect at a time, if you want to change users, call `disconnectUser` before connecting a new user.
13291
- * If the connection is successful, the connected user [state variable](#readonlystatestore) will be updated accordingly.
13292
- *
13293
- * @param user the user to connect.
13294
- * @param token a token or a function that returns a token.
13295
- */
13296
- this.connectUser = async (user, token) => {
13297
- if (user.type === 'anonymous') {
13298
- user.id = '!anon';
13299
- return this.connectAnonymousUser(user, token);
13300
- }
13301
- const connectUser = user.type === 'guest'
13302
- ? () => this.streamClient.connectGuestUser(user)
13303
- : () => this.streamClient.connectUser(user, token);
13304
- const connectUserResponse = await withoutConcurrency(this.connectionConcurrencyTag, () => connectUser());
13305
- // connectUserResponse will be void if connectUser called twice for the same user
13306
- if (connectUserResponse?.me) {
13307
- this.writeableStateStore.setConnectedUser(connectUserResponse.me);
13308
- }
13284
+ this.registerEffects = () => {
13285
+ if (this.effectsRegistered)
13286
+ return;
13309
13287
  this.eventHandlersToUnregister.push(this.on('connection.changed', (event) => {
13310
13288
  if (!event.online)
13311
13289
  return;
@@ -13325,7 +13303,7 @@ class StreamVideoClient {
13325
13303
  }));
13326
13304
  this.eventHandlersToUnregister.push(this.on('call.created', (event) => {
13327
13305
  const { call, members } = event;
13328
- if (user.id === call.created_by.id) {
13306
+ if (this.state.connectedUser?.id === call.created_by.id) {
13329
13307
  this.logger('warn', 'Received `call.created` sent by the current user');
13330
13308
  return;
13331
13309
  }
@@ -13342,7 +13320,7 @@ class StreamVideoClient {
13342
13320
  }));
13343
13321
  this.eventHandlersToUnregister.push(this.on('call.ring', async (event) => {
13344
13322
  const { call, members } = event;
13345
- if (user.id === call.created_by.id) {
13323
+ if (this.state.connectedUser?.id === call.created_by.id) {
13346
13324
  this.logger('debug', 'Received `call.ring` sent by the current user so ignoring the event');
13347
13325
  return;
13348
13326
  }
@@ -13366,6 +13344,53 @@ class StreamVideoClient {
13366
13344
  await newCallInstance.get();
13367
13345
  }
13368
13346
  }));
13347
+ this.effectsRegistered = true;
13348
+ };
13349
+ /**
13350
+ * Connects the given user to the client.
13351
+ * Only one user can connect at a time, if you want to change users, call `disconnectUser` before connecting a new user.
13352
+ * If the connection is successful, the connected user [state variable](#readonlystatestore) will be updated accordingly.
13353
+ *
13354
+ * @param user the user to connect.
13355
+ * @param tokenOrProvider a token or a function that returns a token.
13356
+ */
13357
+ this.connectUser = async (user, tokenOrProvider) => {
13358
+ if (user.type === 'anonymous') {
13359
+ user.id = '!anon';
13360
+ return this.connectAnonymousUser(user, tokenOrProvider);
13361
+ }
13362
+ const connectUserResponse = await withoutConcurrency(this.connectionConcurrencyTag, async () => {
13363
+ const client = this.streamClient;
13364
+ const { maxConnectUserRetries = 5, onConnectUserError, persistUserOnConnectionFailure, } = client.options;
13365
+ const errorQueue = [];
13366
+ for (let attempt = 0; attempt < maxConnectUserRetries; attempt++) {
13367
+ try {
13368
+ this.logger('trace', `Connecting user (${attempt})`, user);
13369
+ return user.type === 'guest'
13370
+ ? await client.connectGuestUser(user)
13371
+ : await client.connectUser(user, tokenOrProvider);
13372
+ }
13373
+ catch (err) {
13374
+ this.logger('warn', `Failed to connect a user (${attempt})`, err);
13375
+ errorQueue.push(err);
13376
+ if (attempt === maxConnectUserRetries - 1) {
13377
+ onConnectUserError?.(err, errorQueue);
13378
+ throw err;
13379
+ }
13380
+ // we need to force to disconnect the user if the client is
13381
+ // configured to persist the user on connection failure
13382
+ if (persistUserOnConnectionFailure) {
13383
+ await client.disconnectUser();
13384
+ }
13385
+ await sleep(retryInterval(attempt));
13386
+ }
13387
+ }
13388
+ });
13389
+ // connectUserResponse will be void if connectUser called twice for the same user
13390
+ if (connectUserResponse?.me) {
13391
+ this.writeableStateStore.setConnectedUser(connectUserResponse.me);
13392
+ }
13393
+ this.registerEffects();
13369
13394
  return connectUserResponse;
13370
13395
  };
13371
13396
  /**
@@ -13377,16 +13402,19 @@ class StreamVideoClient {
13377
13402
  * https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
13378
13403
  */
13379
13404
  this.disconnectUser = async (timeout) => {
13380
- const { user, key } = this.streamClient;
13381
- if (!user)
13382
- return;
13383
- await withoutConcurrency(this.connectionConcurrencyTag, () => this.streamClient.disconnectUser(timeout));
13384
- if (user.id) {
13385
- StreamVideoClient._instances.delete(getInstanceKey(key, user));
13386
- }
13387
- this.eventHandlersToUnregister.forEach((unregister) => unregister());
13388
- this.eventHandlersToUnregister = [];
13389
- this.writeableStateStore.setConnectedUser(undefined);
13405
+ await withoutConcurrency(this.connectionConcurrencyTag, async () => {
13406
+ const { user, key } = this.streamClient;
13407
+ if (!user)
13408
+ return;
13409
+ await this.streamClient.disconnectUser(timeout);
13410
+ if (user.id) {
13411
+ StreamVideoClient._instances.delete(getInstanceKey(key, user));
13412
+ }
13413
+ this.eventHandlersToUnregister.forEach((unregister) => unregister());
13414
+ this.eventHandlersToUnregister = [];
13415
+ this.effectsRegistered = false;
13416
+ this.writeableStateStore.setConnectedUser(undefined);
13417
+ });
13390
13418
  };
13391
13419
  /**
13392
13420
  * You can subscribe to WebSocket events provided by the API.