@stream-io/video-client 1.11.4 → 1.11.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/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [1.11.5](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.4...@stream-io/video-client-1.11.5) (2024-11-22)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * unhandled promise rejections during reconnect ([#1585](https://github.com/GetStream/stream-video-js/issues/1585)) ([920c4ea](https://github.com/GetStream/stream-video-js/commit/920c4ea3b3f622430b35ac1bade74a6206ee17e5)), closes [/github.com/GetStream/stream-video-js/pull/1585/files#diff-420f6ddab47c1be72fd9ce8c99e1fa2b9f5f0495b7c367546ee0ff634beaed81](https://github.com/GetStream//github.com/GetStream/stream-video-js/pull/1585/files/issues/diff-420f6ddab47c1be72fd9ce8c99e1fa2b9f5f0495b7c367546ee0ff634beaed81)
11
+
5
12
  ## [1.11.4](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.3...@stream-io/video-client-1.11.4) (2024-11-21)
6
13
 
7
14
 
@@ -3190,13 +3190,6 @@ function convertErrorToJson(err) {
3190
3190
  }
3191
3191
  return jsonObj;
3192
3192
  }
3193
- /**
3194
- * Informs if a promise is yet to be resolved or rejected
3195
- */
3196
- async function isPromisePending(promise) {
3197
- const emptyObj = {};
3198
- return Promise.race([promise, emptyObj]).then((value) => (value === emptyObj ? true : false), () => false);
3199
- }
3200
3193
  /**
3201
3194
  * isOnline safely return the navigator.online value for browser env
3202
3195
  * if navigator is not in global object, it always return true
@@ -3335,7 +3328,7 @@ const retryable = async (rpc, signal) => {
3335
3328
  return result;
3336
3329
  };
3337
3330
 
3338
- const version = "1.11.4";
3331
+ const version = "1.11.5";
3339
3332
  const [major, minor, patch] = version.split('.');
3340
3333
  let sdkInfo = {
3341
3334
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -11569,6 +11562,33 @@ function buildWsSuccessAfterFailureInsight(connection) {
11569
11562
  return buildWsBaseInsight(connection);
11570
11563
  }
11571
11564
 
11565
+ /**
11566
+ * Saving a long-lived reference to a promise that can reject can be unsafe,
11567
+ * since rejecting the promise causes an unhandled rejection error (even if the
11568
+ * rejection is handled everywhere promise result is expected).
11569
+ *
11570
+ * To avoid that, we add both resolution and rejection handlers to the promise.
11571
+ * That way, the saved promise never rejects. A callback is provided as return
11572
+ * value to build a *new* promise, that resolves and rejects along with
11573
+ * the original promise.
11574
+ * @param promise Promise to wrap, which possibly rejects
11575
+ * @returns Callback to build a new promise, which resolves and rejects along
11576
+ * with the original promise
11577
+ */
11578
+ function makeSafePromise(promise) {
11579
+ let isPending = true;
11580
+ const safePromise = promise
11581
+ .then((result) => ({ status: 'resolved', result }), (error) => ({ status: 'rejected', error }))
11582
+ .finally(() => (isPending = false));
11583
+ const unwrapPromise = () => safePromise.then((fulfillment) => {
11584
+ if (fulfillment.status === 'rejected')
11585
+ throw fulfillment.error;
11586
+ return fulfillment.result;
11587
+ });
11588
+ unwrapPromise.checkPending = () => isPending;
11589
+ return unwrapPromise;
11590
+ }
11591
+
11572
11592
  // Type guards to check WebSocket error type
11573
11593
  const isCloseEvent = (res) => res.code !== undefined;
11574
11594
  const isErrorEvent = (res) => res.error !== undefined;
@@ -11817,10 +11837,10 @@ class StableWSConnection {
11817
11837
  this._setupConnectionPromise = () => {
11818
11838
  this.isResolved = false;
11819
11839
  /** a promise that is resolved once ws.open is called */
11820
- this.connectionOpen = new Promise((resolve, reject) => {
11840
+ this.connectionOpenSafe = makeSafePromise(new Promise((resolve, reject) => {
11821
11841
  this.resolvePromise = resolve;
11822
11842
  this.rejectPromise = reject;
11823
- });
11843
+ }));
11824
11844
  };
11825
11845
  /**
11826
11846
  * Schedules a next health check ping for websocket.
@@ -12041,13 +12061,7 @@ class StableWSConnection {
12041
12061
  this._log(`_connect() - tokenProvider failed before, so going to retry`);
12042
12062
  await this.client.tokenManager.loadToken();
12043
12063
  }
12044
- let mustSetupConnectionIdPromise = true;
12045
- if (this.client.connectionIdPromise) {
12046
- if (await isPromisePending(this.client.connectionIdPromise)) {
12047
- mustSetupConnectionIdPromise = false;
12048
- }
12049
- }
12050
- if (mustSetupConnectionIdPromise) {
12064
+ if (!this.client.isConnectionIsPromisePending) {
12051
12065
  this.client._setupConnectionIdPromise();
12052
12066
  }
12053
12067
  this._setupConnectionPromise();
@@ -12167,6 +12181,9 @@ class StableWSConnection {
12167
12181
  // we don't care
12168
12182
  }
12169
12183
  }
12184
+ get connectionOpen() {
12185
+ return this.connectionOpenSafe?.();
12186
+ }
12170
12187
  }
12171
12188
 
12172
12189
  function isString(arrayOrString) {
@@ -12730,19 +12747,21 @@ class StreamClient {
12730
12747
  if (!this.userID) {
12731
12748
  throw Error('UserWithId is not set on client, use client.connectUser or client.connectAnonymousUser instead');
12732
12749
  }
12733
- if (this.wsConnection?.isConnecting && this.wsPromise) {
12750
+ const wsPromise = this.wsPromiseSafe?.();
12751
+ if (this.wsConnection?.isConnecting && wsPromise) {
12734
12752
  this.logger('info', 'client:openConnection() - connection already in progress');
12735
- return this.wsPromise;
12753
+ return await wsPromise;
12736
12754
  }
12737
12755
  if ((this.wsConnection?.isHealthy || this.wsFallback?.isHealthy()) &&
12738
12756
  this._hasConnectionID()) {
12739
12757
  this.logger('info', 'client:openConnection() - openConnection called twice, healthy connection already exists');
12740
- return Promise.resolve();
12758
+ return;
12741
12759
  }
12742
12760
  this._setupConnectionIdPromise();
12743
12761
  this.clientID = `${this.userID}--${randomId()}`;
12744
- this.wsPromise = this.connect();
12745
- return this.wsPromise;
12762
+ const newWsPromise = this.connect();
12763
+ this.wsPromiseSafe = makeSafePromise(newWsPromise);
12764
+ return await newWsPromise;
12746
12765
  };
12747
12766
  /**
12748
12767
  * Disconnects the websocket and removes the user from client.
@@ -12760,7 +12779,7 @@ class StreamClient {
12760
12779
  await this.closeConnection(timeout);
12761
12780
  removeConnectionEventListeners(this.updateNetworkConnectionStatus);
12762
12781
  this.tokenManager.reset();
12763
- this.connectionIdPromise = undefined;
12782
+ this.connectionIdPromiseSafe = undefined;
12764
12783
  this.rejectConnectionId = undefined;
12765
12784
  this.resolveConnectionId = undefined;
12766
12785
  };
@@ -12821,12 +12840,12 @@ class StreamClient {
12821
12840
  /**
12822
12841
  * sets up the this.connectionIdPromise
12823
12842
  */
12824
- this._setupConnectionIdPromise = async () => {
12843
+ this._setupConnectionIdPromise = () => {
12825
12844
  /** a promise that is resolved once connection id is set */
12826
- this.connectionIdPromise = new Promise((resolve, reject) => {
12845
+ this.connectionIdPromiseSafe = makeSafePromise(new Promise((resolve, reject) => {
12827
12846
  this.resolveConnectionId = resolve;
12828
12847
  this.rejectConnectionId = reject;
12829
- });
12848
+ }));
12830
12849
  };
12831
12850
  this._logApiRequest = (type, url, data, config) => {
12832
12851
  this.logger('trace', `client: ${type} - Request - ${url}`, {
@@ -13044,7 +13063,7 @@ class StreamClient {
13044
13063
  });
13045
13064
  };
13046
13065
  this.getUserAgent = () => {
13047
- const version = "1.11.4";
13066
+ const version = "1.11.5";
13048
13067
  return (this.userAgent ||
13049
13068
  `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
13050
13069
  };
@@ -13158,7 +13177,7 @@ class StreamClient {
13158
13177
  });
13159
13178
  // WS connection is initialized when setUser is called
13160
13179
  this.wsConnection = null;
13161
- this.wsPromise = null;
13180
+ this.wsPromiseSafe = null;
13162
13181
  this.setUserPromise = null;
13163
13182
  // mapping between channel groups and configs
13164
13183
  this.anonymous = false;
@@ -13175,6 +13194,15 @@ class StreamClient {
13175
13194
  ? inputOptions.logger
13176
13195
  : () => null;
13177
13196
  }
13197
+ get connectionIdPromise() {
13198
+ return this.connectionIdPromiseSafe?.();
13199
+ }
13200
+ get isConnectionIsPromisePending() {
13201
+ return this.connectionIdPromiseSafe?.checkPending() ?? false;
13202
+ }
13203
+ get wsPromise() {
13204
+ return this.wsPromiseSafe?.();
13205
+ }
13178
13206
  }
13179
13207
 
13180
13208
  /**
@@ -13184,6 +13212,7 @@ class StreamVideoClient {
13184
13212
  constructor(apiKeyOrArgs, opts) {
13185
13213
  this.logLevel = 'warn';
13186
13214
  this.eventHandlersToUnregister = [];
13215
+ this.connectionConcurrencyTag = Symbol('connectionConcurrencyTag');
13187
13216
  /**
13188
13217
  * Connects the given user to the client.
13189
13218
  * Only one user can connect at a time, if you want to change users, call `disconnectUser` before connecting a new user.
@@ -13205,11 +13234,7 @@ class StreamVideoClient {
13205
13234
  return this.streamClient.connectGuestUser(user);
13206
13235
  };
13207
13236
  }
13208
- this.connectionPromise = this.disconnectionPromise
13209
- ? this.disconnectionPromise.then(() => connectUser())
13210
- : connectUser();
13211
- this.connectionPromise?.finally(() => (this.connectionPromise = undefined));
13212
- const connectUserResponse = await this.connectionPromise;
13237
+ const connectUserResponse = await withoutConcurrency(this.connectionConcurrencyTag, () => connectUser());
13213
13238
  // connectUserResponse will be void if connectUser called twice for the same user
13214
13239
  if (connectUserResponse?.me) {
13215
13240
  this.writeableStateStore.setConnectedUser(connectUserResponse.me);
@@ -13286,17 +13311,13 @@ class StreamVideoClient {
13286
13311
  * https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
13287
13312
  */
13288
13313
  this.disconnectUser = async (timeout) => {
13289
- if (!this.streamClient.user && !this.connectionPromise) {
13314
+ if (!this.streamClient.user) {
13290
13315
  return;
13291
13316
  }
13292
13317
  const userId = this.streamClient.user?.id;
13293
13318
  const apiKey = this.streamClient.key;
13294
13319
  const disconnectUser = () => this.streamClient.disconnectUser(timeout);
13295
- this.disconnectionPromise = this.connectionPromise
13296
- ? this.connectionPromise.then(() => disconnectUser())
13297
- : disconnectUser();
13298
- this.disconnectionPromise.finally(() => (this.disconnectionPromise = undefined));
13299
- await this.disconnectionPromise;
13320
+ await withoutConcurrency(this.connectionConcurrencyTag, () => disconnectUser());
13300
13321
  if (userId) {
13301
13322
  StreamVideoClient._instanceMap.delete(apiKey + userId);
13302
13323
  }
@@ -13471,11 +13492,7 @@ class StreamVideoClient {
13471
13492
  */
13472
13493
  this.connectAnonymousUser = async (user, tokenOrProvider) => {
13473
13494
  const connectAnonymousUser = () => this.streamClient.connectAnonymousUser(user, tokenOrProvider);
13474
- this.connectionPromise = this.disconnectionPromise
13475
- ? this.disconnectionPromise.then(() => connectAnonymousUser())
13476
- : connectAnonymousUser();
13477
- this.connectionPromise.finally(() => (this.connectionPromise = undefined));
13478
- return this.connectionPromise;
13495
+ return await withoutConcurrency(this.connectionConcurrencyTag, () => connectAnonymousUser());
13479
13496
  };
13480
13497
  let logger = logToConsole;
13481
13498
  let logLevel = 'warn';