@stream-io/video-client 1.11.4 → 1.11.6

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.11.6](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.5...@stream-io/video-client-1.11.6) (2024-11-22)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * force single codec preference in the SDP ([#1588](https://github.com/GetStream/stream-video-js/issues/1588)) ([4afff09](https://github.com/GetStream/stream-video-js/commit/4afff09a778f8567176d22bcc22d36001dca7cd3)), closes [#1581](https://github.com/GetStream/stream-video-js/issues/1581)
11
+
12
+ ## [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)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * 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)
18
+
5
19
  ## [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
20
 
7
21
 
@@ -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.6";
3339
3332
  const [major, minor, patch] = version.split('.');
3340
3333
  let sdkInfo = {
3341
3334
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -3439,11 +3432,13 @@ var browsers = /*#__PURE__*/Object.freeze({
3439
3432
  * @param kind the kind of codec to get.
3440
3433
  * @param preferredCodec the codec to prioritize (vp8, h264, vp9, av1...).
3441
3434
  * @param codecToRemove the codec to exclude from the list.
3435
+ * @param codecPreferencesSource the source of the codec preferences.
3442
3436
  */
3443
- const getPreferredCodecs = (kind, preferredCodec, codecToRemove) => {
3444
- if (!('getCapabilities' in RTCRtpReceiver))
3437
+ const getPreferredCodecs = (kind, preferredCodec, codecToRemove, codecPreferencesSource = 'receiver') => {
3438
+ const source = codecPreferencesSource === 'receiver' ? RTCRtpReceiver : RTCRtpSender;
3439
+ if (!('getCapabilities' in source))
3445
3440
  return;
3446
- const capabilities = RTCRtpReceiver.getCapabilities(kind);
3441
+ const capabilities = source.getCapabilities(kind);
3447
3442
  if (!capabilities)
3448
3443
  return;
3449
3444
  const preferred = [];
@@ -5881,9 +5876,9 @@ class Publisher {
5881
5876
  this.getStats = (selector) => {
5882
5877
  return this.pc.getStats(selector);
5883
5878
  };
5884
- this.getCodecPreferences = (trackType, preferredCodec) => {
5879
+ this.getCodecPreferences = (trackType, preferredCodec, codecPreferencesSource) => {
5885
5880
  if (trackType === TrackType.VIDEO) {
5886
- return getPreferredCodecs('video', preferredCodec || 'vp8');
5881
+ return getPreferredCodecs('video', preferredCodec || 'vp8', codecPreferencesSource);
5887
5882
  }
5888
5883
  if (trackType === TrackType.AUDIO) {
5889
5884
  const defaultAudioCodec = this.isRedEnabled ? 'red' : 'opus';
@@ -6115,8 +6110,8 @@ class Publisher {
6115
6110
  const opts = this.publishOptsForTrack.get(trackType);
6116
6111
  if (!opts || !opts.forceSingleCodec)
6117
6112
  return sdp;
6118
- const codec = opts.forceCodec || opts.preferredCodec;
6119
- const orderedCodecs = this.getCodecPreferences(trackType, codec);
6113
+ const codec = opts.forceCodec || getOptimalVideoCodec(opts.preferredCodec);
6114
+ const orderedCodecs = this.getCodecPreferences(trackType, codec, 'sender');
6120
6115
  if (!orderedCodecs || orderedCodecs.length === 0)
6121
6116
  return sdp;
6122
6117
  const transceiver = this.transceiverCache.get(trackType);
@@ -11569,6 +11564,33 @@ function buildWsSuccessAfterFailureInsight(connection) {
11569
11564
  return buildWsBaseInsight(connection);
11570
11565
  }
11571
11566
 
11567
+ /**
11568
+ * Saving a long-lived reference to a promise that can reject can be unsafe,
11569
+ * since rejecting the promise causes an unhandled rejection error (even if the
11570
+ * rejection is handled everywhere promise result is expected).
11571
+ *
11572
+ * To avoid that, we add both resolution and rejection handlers to the promise.
11573
+ * That way, the saved promise never rejects. A callback is provided as return
11574
+ * value to build a *new* promise, that resolves and rejects along with
11575
+ * the original promise.
11576
+ * @param promise Promise to wrap, which possibly rejects
11577
+ * @returns Callback to build a new promise, which resolves and rejects along
11578
+ * with the original promise
11579
+ */
11580
+ function makeSafePromise(promise) {
11581
+ let isPending = true;
11582
+ const safePromise = promise
11583
+ .then((result) => ({ status: 'resolved', result }), (error) => ({ status: 'rejected', error }))
11584
+ .finally(() => (isPending = false));
11585
+ const unwrapPromise = () => safePromise.then((fulfillment) => {
11586
+ if (fulfillment.status === 'rejected')
11587
+ throw fulfillment.error;
11588
+ return fulfillment.result;
11589
+ });
11590
+ unwrapPromise.checkPending = () => isPending;
11591
+ return unwrapPromise;
11592
+ }
11593
+
11572
11594
  // Type guards to check WebSocket error type
11573
11595
  const isCloseEvent = (res) => res.code !== undefined;
11574
11596
  const isErrorEvent = (res) => res.error !== undefined;
@@ -11817,10 +11839,10 @@ class StableWSConnection {
11817
11839
  this._setupConnectionPromise = () => {
11818
11840
  this.isResolved = false;
11819
11841
  /** a promise that is resolved once ws.open is called */
11820
- this.connectionOpen = new Promise((resolve, reject) => {
11842
+ this.connectionOpenSafe = makeSafePromise(new Promise((resolve, reject) => {
11821
11843
  this.resolvePromise = resolve;
11822
11844
  this.rejectPromise = reject;
11823
- });
11845
+ }));
11824
11846
  };
11825
11847
  /**
11826
11848
  * Schedules a next health check ping for websocket.
@@ -12041,13 +12063,7 @@ class StableWSConnection {
12041
12063
  this._log(`_connect() - tokenProvider failed before, so going to retry`);
12042
12064
  await this.client.tokenManager.loadToken();
12043
12065
  }
12044
- let mustSetupConnectionIdPromise = true;
12045
- if (this.client.connectionIdPromise) {
12046
- if (await isPromisePending(this.client.connectionIdPromise)) {
12047
- mustSetupConnectionIdPromise = false;
12048
- }
12049
- }
12050
- if (mustSetupConnectionIdPromise) {
12066
+ if (!this.client.isConnectionIsPromisePending) {
12051
12067
  this.client._setupConnectionIdPromise();
12052
12068
  }
12053
12069
  this._setupConnectionPromise();
@@ -12167,6 +12183,9 @@ class StableWSConnection {
12167
12183
  // we don't care
12168
12184
  }
12169
12185
  }
12186
+ get connectionOpen() {
12187
+ return this.connectionOpenSafe?.();
12188
+ }
12170
12189
  }
12171
12190
 
12172
12191
  function isString(arrayOrString) {
@@ -12730,19 +12749,21 @@ class StreamClient {
12730
12749
  if (!this.userID) {
12731
12750
  throw Error('UserWithId is not set on client, use client.connectUser or client.connectAnonymousUser instead');
12732
12751
  }
12733
- if (this.wsConnection?.isConnecting && this.wsPromise) {
12752
+ const wsPromise = this.wsPromiseSafe?.();
12753
+ if (this.wsConnection?.isConnecting && wsPromise) {
12734
12754
  this.logger('info', 'client:openConnection() - connection already in progress');
12735
- return this.wsPromise;
12755
+ return await wsPromise;
12736
12756
  }
12737
12757
  if ((this.wsConnection?.isHealthy || this.wsFallback?.isHealthy()) &&
12738
12758
  this._hasConnectionID()) {
12739
12759
  this.logger('info', 'client:openConnection() - openConnection called twice, healthy connection already exists');
12740
- return Promise.resolve();
12760
+ return;
12741
12761
  }
12742
12762
  this._setupConnectionIdPromise();
12743
12763
  this.clientID = `${this.userID}--${randomId()}`;
12744
- this.wsPromise = this.connect();
12745
- return this.wsPromise;
12764
+ const newWsPromise = this.connect();
12765
+ this.wsPromiseSafe = makeSafePromise(newWsPromise);
12766
+ return await newWsPromise;
12746
12767
  };
12747
12768
  /**
12748
12769
  * Disconnects the websocket and removes the user from client.
@@ -12760,7 +12781,7 @@ class StreamClient {
12760
12781
  await this.closeConnection(timeout);
12761
12782
  removeConnectionEventListeners(this.updateNetworkConnectionStatus);
12762
12783
  this.tokenManager.reset();
12763
- this.connectionIdPromise = undefined;
12784
+ this.connectionIdPromiseSafe = undefined;
12764
12785
  this.rejectConnectionId = undefined;
12765
12786
  this.resolveConnectionId = undefined;
12766
12787
  };
@@ -12821,12 +12842,12 @@ class StreamClient {
12821
12842
  /**
12822
12843
  * sets up the this.connectionIdPromise
12823
12844
  */
12824
- this._setupConnectionIdPromise = async () => {
12845
+ this._setupConnectionIdPromise = () => {
12825
12846
  /** a promise that is resolved once connection id is set */
12826
- this.connectionIdPromise = new Promise((resolve, reject) => {
12847
+ this.connectionIdPromiseSafe = makeSafePromise(new Promise((resolve, reject) => {
12827
12848
  this.resolveConnectionId = resolve;
12828
12849
  this.rejectConnectionId = reject;
12829
- });
12850
+ }));
12830
12851
  };
12831
12852
  this._logApiRequest = (type, url, data, config) => {
12832
12853
  this.logger('trace', `client: ${type} - Request - ${url}`, {
@@ -13044,7 +13065,7 @@ class StreamClient {
13044
13065
  });
13045
13066
  };
13046
13067
  this.getUserAgent = () => {
13047
- const version = "1.11.4";
13068
+ const version = "1.11.6";
13048
13069
  return (this.userAgent ||
13049
13070
  `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
13050
13071
  };
@@ -13158,7 +13179,7 @@ class StreamClient {
13158
13179
  });
13159
13180
  // WS connection is initialized when setUser is called
13160
13181
  this.wsConnection = null;
13161
- this.wsPromise = null;
13182
+ this.wsPromiseSafe = null;
13162
13183
  this.setUserPromise = null;
13163
13184
  // mapping between channel groups and configs
13164
13185
  this.anonymous = false;
@@ -13175,6 +13196,15 @@ class StreamClient {
13175
13196
  ? inputOptions.logger
13176
13197
  : () => null;
13177
13198
  }
13199
+ get connectionIdPromise() {
13200
+ return this.connectionIdPromiseSafe?.();
13201
+ }
13202
+ get isConnectionIsPromisePending() {
13203
+ return this.connectionIdPromiseSafe?.checkPending() ?? false;
13204
+ }
13205
+ get wsPromise() {
13206
+ return this.wsPromiseSafe?.();
13207
+ }
13178
13208
  }
13179
13209
 
13180
13210
  /**
@@ -13184,6 +13214,7 @@ class StreamVideoClient {
13184
13214
  constructor(apiKeyOrArgs, opts) {
13185
13215
  this.logLevel = 'warn';
13186
13216
  this.eventHandlersToUnregister = [];
13217
+ this.connectionConcurrencyTag = Symbol('connectionConcurrencyTag');
13187
13218
  /**
13188
13219
  * Connects the given user to the client.
13189
13220
  * Only one user can connect at a time, if you want to change users, call `disconnectUser` before connecting a new user.
@@ -13205,11 +13236,7 @@ class StreamVideoClient {
13205
13236
  return this.streamClient.connectGuestUser(user);
13206
13237
  };
13207
13238
  }
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;
13239
+ const connectUserResponse = await withoutConcurrency(this.connectionConcurrencyTag, () => connectUser());
13213
13240
  // connectUserResponse will be void if connectUser called twice for the same user
13214
13241
  if (connectUserResponse?.me) {
13215
13242
  this.writeableStateStore.setConnectedUser(connectUserResponse.me);
@@ -13286,17 +13313,13 @@ class StreamVideoClient {
13286
13313
  * https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
13287
13314
  */
13288
13315
  this.disconnectUser = async (timeout) => {
13289
- if (!this.streamClient.user && !this.connectionPromise) {
13316
+ if (!this.streamClient.user) {
13290
13317
  return;
13291
13318
  }
13292
13319
  const userId = this.streamClient.user?.id;
13293
13320
  const apiKey = this.streamClient.key;
13294
13321
  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;
13322
+ await withoutConcurrency(this.connectionConcurrencyTag, () => disconnectUser());
13300
13323
  if (userId) {
13301
13324
  StreamVideoClient._instanceMap.delete(apiKey + userId);
13302
13325
  }
@@ -13471,11 +13494,7 @@ class StreamVideoClient {
13471
13494
  */
13472
13495
  this.connectAnonymousUser = async (user, tokenOrProvider) => {
13473
13496
  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;
13497
+ return await withoutConcurrency(this.connectionConcurrencyTag, () => connectAnonymousUser());
13479
13498
  };
13480
13499
  let logger = logToConsole;
13481
13500
  let logLevel = 'warn';