@stream-io/video-client 1.31.0 → 1.32.0

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,17 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [1.32.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.31.0...@stream-io/video-client-1.32.0) (2025-09-29)
6
+
7
+ ### Features
8
+
9
+ - **react-native:** reject call when busy ([#1856](https://github.com/GetStream/stream-video-js/issues/1856)) ([b60bc7c](https://github.com/GetStream/stream-video-js/commit/b60bc7cd2dc2e09d52496d7b5cb593cac4b89485))
10
+
11
+ ### Bug Fixes
12
+
13
+ - restore calling state after unrecoverable join fail ([#1935](https://github.com/GetStream/stream-video-js/issues/1935)) ([8ab0168](https://github.com/GetStream/stream-video-js/commit/8ab01680d01cc47f9cf48078634358507f0c109d))
14
+ - send unifiedSessionId in the initial join request ([#1934](https://github.com/GetStream/stream-video-js/issues/1934)) ([e6a533d](https://github.com/GetStream/stream-video-js/commit/e6a533d7e926086ac5930ebfb4648dade449d15a))
15
+
5
16
  ## [1.31.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.30.1...@stream-io/video-client-1.31.0) (2025-09-17)
6
17
 
7
18
  ### Features
@@ -2949,6 +2949,12 @@ class JoinRequest$Type extends MessageType {
2949
2949
  super('stream.video.sfu.event.JoinRequest', [
2950
2950
  { no: 1, name: 'token', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
2951
2951
  { no: 2, name: 'session_id', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
2952
+ {
2953
+ no: 13,
2954
+ name: 'unified_session_id',
2955
+ kind: 'scalar',
2956
+ T: 9 /*ScalarType.STRING*/,
2957
+ },
2952
2958
  {
2953
2959
  no: 3,
2954
2960
  name: 'subscriber_sdp',
@@ -5758,7 +5764,7 @@ const getSdkVersion = (sdk) => {
5758
5764
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
5759
5765
  };
5760
5766
 
5761
- const version = "1.31.0";
5767
+ const version = "1.32.0";
5762
5768
  const [major, minor, patch] = version.split('.');
5763
5769
  let sdkInfo = {
5764
5770
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -11869,7 +11875,6 @@ class Call {
11869
11875
  if ([CallingState.JOINED, CallingState.JOINING].includes(callingState)) {
11870
11876
  throw new Error(`Illegal State: call.join() shall be called only once`);
11871
11877
  }
11872
- this.state.setCallingState(CallingState.JOINING);
11873
11878
  // we will count the number of join failures per SFU.
11874
11879
  // once the number of failures reaches 2, we will piggyback on the `migrating_from`
11875
11880
  // field to force the coordinator to provide us another SFU
@@ -11898,8 +11903,6 @@ class Call {
11898
11903
  joinData.migrating_from = sfuId;
11899
11904
  }
11900
11905
  if (attempt === maxJoinRetries - 1) {
11901
- // restore the previous call state if the join-flow fails
11902
- this.state.setCallingState(callingState);
11903
11906
  throw err;
11904
11907
  }
11905
11908
  }
@@ -11959,6 +11962,7 @@ class Call {
11959
11962
  })
11960
11963
  : previousSfuClient;
11961
11964
  this.sfuClient = sfuClient;
11965
+ this.unifiedSessionId ?? (this.unifiedSessionId = sfuClient.sessionId);
11962
11966
  this.dynascaleManager.setSfuClient(sfuClient);
11963
11967
  const clientDetails = await getClientDetails();
11964
11968
  // we don't need to send JoinRequest if we are re-using an existing healthy SFU client
@@ -11982,6 +11986,7 @@ class Call {
11982
11986
  : [];
11983
11987
  try {
11984
11988
  const { callState, fastReconnectDeadlineSeconds, publishOptions } = await sfuClient.join({
11989
+ unifiedSessionId: this.unifiedSessionId,
11985
11990
  subscriberSdp,
11986
11991
  publisherSdp,
11987
11992
  clientDetails,
@@ -12027,6 +12032,7 @@ class Call {
12027
12032
  statsOptions,
12028
12033
  publishOptions: this.currentPublishOptions || [],
12029
12034
  closePreviousInstances: !performingMigration,
12035
+ unifiedSessionId: this.unifiedSessionId,
12030
12036
  });
12031
12037
  }
12032
12038
  // make sure we only track connection timing if we are not calling this method as part of a reconnection flow
@@ -12153,7 +12159,7 @@ class Call {
12153
12159
  * @internal
12154
12160
  */
12155
12161
  this.initPublisherAndSubscriber = (opts) => {
12156
- const { sfuClient, connectionConfig, clientDetails, statsOptions, publishOptions, closePreviousInstances, } = opts;
12162
+ const { sfuClient, connectionConfig, clientDetails, statsOptions, publishOptions, closePreviousInstances, unifiedSessionId, } = opts;
12157
12163
  const { enable_rtc_stats: enableTracing } = statsOptions;
12158
12164
  if (closePreviousInstances && this.subscriber) {
12159
12165
  this.subscriber.dispose();
@@ -12208,7 +12214,6 @@ class Call {
12208
12214
  this.tracer.setEnabled(enableTracing);
12209
12215
  this.sfuStatsReporter?.stop();
12210
12216
  if (statsOptions?.reporting_interval_ms > 0) {
12211
- this.unifiedSessionId ?? (this.unifiedSessionId = sfuClient.sessionId);
12212
12217
  this.sfuStatsReporter = new SfuStatsReporter(sfuClient, {
12213
12218
  clientDetails,
12214
12219
  options: statsOptions,
@@ -12218,7 +12223,7 @@ class Call {
12218
12223
  camera: this.camera,
12219
12224
  state: this.state,
12220
12225
  tracer: this.tracer,
12221
- unifiedSessionId: this.unifiedSessionId,
12226
+ unifiedSessionId,
12222
12227
  });
12223
12228
  this.sfuStatsReporter.start();
12224
12229
  }
@@ -14540,7 +14545,7 @@ class StreamClient {
14540
14545
  this.getUserAgent = () => {
14541
14546
  if (!this.cachedUserAgent) {
14542
14547
  const { clientAppIdentifier = {} } = this.options;
14543
- const { sdkName = 'js', sdkVersion = "1.31.0", ...extras } = clientAppIdentifier;
14548
+ const { sdkName = 'js', sdkVersion = "1.32.0", ...extras } = clientAppIdentifier;
14544
14549
  this.cachedUserAgent = [
14545
14550
  `stream-video-${sdkName}-v${sdkVersion}`,
14546
14551
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -14729,6 +14734,7 @@ class StreamVideoClient {
14729
14734
  this.effectsRegistered = false;
14730
14735
  this.eventHandlersToUnregister = [];
14731
14736
  this.connectionConcurrencyTag = Symbol('connectionConcurrencyTag');
14737
+ this.rejectCallWhenBusy = false;
14732
14738
  this.registerClientInstance = (apiKey, user) => {
14733
14739
  const instanceKey = getInstanceKey(apiKey, user);
14734
14740
  if (StreamVideoClient._instances.has(instanceKey)) {
@@ -14774,7 +14780,16 @@ class StreamVideoClient {
14774
14780
  let call = this.writeableStateStore.findCall(e.call.type, e.call.id);
14775
14781
  if (call) {
14776
14782
  if (ringing) {
14777
- await call.updateFromRingingEvent(e);
14783
+ if (this.shouldRejectCall(call.cid)) {
14784
+ this.logger('info', `Leaving call with busy reject reason ${call.cid} because user is busy`);
14785
+ // remove the instance from the state store
14786
+ await call.leave();
14787
+ // explicitly reject the call with busy reason as calling state was not ringing before and leave would not call it therefore
14788
+ await call.reject('busy');
14789
+ }
14790
+ else {
14791
+ await call.updateFromRingingEvent(e);
14792
+ }
14778
14793
  }
14779
14794
  else {
14780
14795
  call.state.updateFromCallResponse(e.call);
@@ -14789,11 +14804,19 @@ class StreamVideoClient {
14789
14804
  clientStore: this.writeableStateStore,
14790
14805
  ringing,
14791
14806
  });
14792
- call.state.updateFromCallResponse(e.call);
14793
14807
  if (ringing) {
14794
- await call.get();
14808
+ if (this.shouldRejectCall(call.cid)) {
14809
+ this.logger('info', `Rejecting call ${call.cid} because user is busy`);
14810
+ // call is not in the state store yet, so just reject api is enough
14811
+ await call.reject('busy');
14812
+ }
14813
+ else {
14814
+ await call.updateFromRingingEvent(e);
14815
+ await call.get();
14816
+ }
14795
14817
  }
14796
14818
  else {
14819
+ call.state.updateFromCallResponse(e.call);
14797
14820
  this.writeableStateStore.registerCall(call);
14798
14821
  this.logger('info', `New call created and registered: ${call.cid}`);
14799
14822
  }
@@ -15060,6 +15083,16 @@ class StreamVideoClient {
15060
15083
  this.connectAnonymousUser = async (user, tokenOrProvider) => {
15061
15084
  return withoutConcurrency(this.connectionConcurrencyTag, () => this.streamClient.connectAnonymousUser(user, tokenOrProvider));
15062
15085
  };
15086
+ this.shouldRejectCall = (currentCallId) => {
15087
+ if (!this.rejectCallWhenBusy)
15088
+ return false;
15089
+ const hasOngoingRingingCall = this.state.calls.some((c) => c.cid !== currentCallId &&
15090
+ c.ringing &&
15091
+ c.state.callingState !== CallingState.IDLE &&
15092
+ c.state.callingState !== CallingState.LEFT &&
15093
+ c.state.callingState !== CallingState.RECONNECTING_FAILED);
15094
+ return hasOngoingRingingCall;
15095
+ };
15063
15096
  const apiKey = typeof apiKeyOrArgs === 'string' ? apiKeyOrArgs : apiKeyOrArgs.apiKey;
15064
15097
  const clientOptions = typeof apiKeyOrArgs === 'string' ? opts : apiKeyOrArgs.options;
15065
15098
  if (clientOptions?.enableTimerWorker)
@@ -15067,6 +15100,7 @@ class StreamVideoClient {
15067
15100
  const rootLogger = clientOptions?.logger || logToConsole;
15068
15101
  setLogger(rootLogger, clientOptions?.logLevel || 'warn');
15069
15102
  this.logger = getLogger(['client']);
15103
+ this.rejectCallWhenBusy = clientOptions?.rejectCallWhenBusy ?? false;
15070
15104
  this.streamClient = createCoordinatorClient(apiKey, clientOptions);
15071
15105
  this.writeableStateStore = new StreamVideoWriteableStateStore();
15072
15106
  this.readOnlyStateStore = new StreamVideoReadOnlyStateStore(this.writeableStateStore);