@stream-io/video-client 1.27.4 → 1.27.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,12 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [1.27.5](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.27.4...@stream-io/video-client-1.27.5) (2025-08-15)
6
+
7
+ ### Bug Fixes
8
+
9
+ - synchronize ring events ([#1888](https://github.com/GetStream/stream-video-js/issues/1888)) ([0951e6d](https://github.com/GetStream/stream-video-js/commit/0951e6d4c825806937d6bdc548df9f186c531466))
10
+
5
11
  ## [1.27.4](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.27.3...@stream-io/video-client-1.27.4) (2025-08-13)
6
12
 
7
13
  ### Bug Fixes
@@ -4300,13 +4300,6 @@ class StreamVideoWriteableStateStore {
4300
4300
  */
4301
4301
  class StreamVideoReadOnlyStateStore {
4302
4302
  constructor(store) {
4303
- /**
4304
- * This method allows you the get the current value of a state variable.
4305
- *
4306
- * @param observable the observable to get the current value of.
4307
- * @returns the current value of the observable.
4308
- */
4309
- this.getCurrentValue = getCurrentValue;
4310
4303
  // convert and expose subjects as observables
4311
4304
  this.connectedUser$ = store.connectedUserSubject.asObservable();
4312
4305
  this.calls$ = store.callsSubject.asObservable();
@@ -5643,7 +5636,7 @@ const getSdkVersion = (sdk) => {
5643
5636
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
5644
5637
  };
5645
5638
 
5646
- const version = "1.27.4";
5639
+ const version = "1.27.5";
5647
5640
  const [major, minor, patch] = version.split('.');
5648
5641
  let sdkInfo = {
5649
5642
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -14545,7 +14538,7 @@ class StreamClient {
14545
14538
  this.getUserAgent = () => {
14546
14539
  if (!this.cachedUserAgent) {
14547
14540
  const { clientAppIdentifier = {} } = this.options;
14548
- const { sdkName = 'js', sdkVersion = "1.27.4", ...extras } = clientAppIdentifier;
14541
+ const { sdkName = 'js', sdkVersion = "1.27.5", ...extras } = clientAppIdentifier;
14549
14542
  this.cachedUserAgent = [
14550
14543
  `stream-video-${sdkName}-v${sdkVersion}`,
14551
14544
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -14666,6 +14659,13 @@ class StreamClient {
14666
14659
  const getInstanceKey = (apiKey, user) => {
14667
14660
  return `${apiKey}/${user.id}`;
14668
14661
  };
14662
+ /**
14663
+ * Returns a concurrency tag for call initialization.
14664
+ * @internal
14665
+ *
14666
+ * @param cid the call cid.
14667
+ */
14668
+ const getCallInitConcurrencyTag = (cid) => `call.init-${cid}`;
14669
14669
  /**
14670
14670
  * Utility function to get the client app identifier.
14671
14671
  */
@@ -14734,7 +14734,7 @@ class StreamVideoClient {
14734
14734
  this.registerEffects = () => {
14735
14735
  if (this.effectsRegistered)
14736
14736
  return;
14737
- this.eventHandlersToUnregister.push(this.on('connection.changed', (event) => {
14737
+ this.eventHandlersToUnregister.push(this.on('call.created', (event) => this.initCallFromEvent(event)), this.on('call.ring', (event) => this.initCallFromEvent(event)), this.on('connection.changed', (event) => {
14738
14738
  if (!event.online)
14739
14739
  return;
14740
14740
  const callsToReWatch = this.writeableStateStore.calls
@@ -14751,50 +14751,52 @@ class StreamVideoClient {
14751
14751
  this.logger('error', 'Failed to re-watch calls', err);
14752
14752
  });
14753
14753
  }));
14754
- this.eventHandlersToUnregister.push(this.on('call.created', (event) => {
14755
- const { call, members } = event;
14756
- if (this.state.connectedUser?.id === call.created_by.id) {
14757
- this.logger('warn', 'Received `call.created` sent by the current user');
14758
- return;
14759
- }
14760
- this.logger('info', `New call created and registered: ${call.cid}`);
14761
- const newCall = new Call({
14762
- streamClient: this.streamClient,
14763
- type: call.type,
14764
- id: call.id,
14765
- members,
14766
- clientStore: this.writeableStateStore,
14767
- });
14768
- newCall.state.updateFromCallResponse(call);
14769
- this.writeableStateStore.registerCall(newCall);
14770
- }));
14771
- this.eventHandlersToUnregister.push(this.on('call.ring', async (event) => {
14772
- const { call, members } = event;
14773
- if (this.state.connectedUser?.id === call.created_by.id) {
14774
- this.logger('debug', 'Received `call.ring` sent by the current user so ignoring the event');
14775
- return;
14776
- }
14777
- // if `call.created` was received before `call.ring`.
14778
- // the client already has the call instance and we just need to update the state
14779
- const theCall = this.writeableStateStore.findCall(call.type, call.id);
14780
- if (theCall) {
14781
- await theCall.updateFromRingingEvent(event);
14782
- }
14783
- else {
14784
- // if client doesn't have the call instance, create the instance and fetch the latest state
14785
- // Note: related - we also have onRingingCall method to handle this case from push notifications
14786
- const newCallInstance = new Call({
14754
+ this.effectsRegistered = true;
14755
+ };
14756
+ /**
14757
+ * Initializes a call from a call created or ringing event.
14758
+ * @param e the event.
14759
+ */
14760
+ this.initCallFromEvent = async (e) => {
14761
+ if (this.state.connectedUser?.id === e.call.created_by.id) {
14762
+ this.logger('debug', `Ignoring ${e.type} event sent by the current user`);
14763
+ return;
14764
+ }
14765
+ try {
14766
+ const concurrencyTag = getCallInitConcurrencyTag(e.call_cid);
14767
+ await withoutConcurrency(concurrencyTag, async () => {
14768
+ const ringing = e.type === 'call.ring';
14769
+ let call = this.writeableStateStore.findCall(e.call.type, e.call.id);
14770
+ if (call) {
14771
+ if (ringing) {
14772
+ await call.updateFromRingingEvent(e);
14773
+ }
14774
+ else {
14775
+ call.state.updateFromCallResponse(e.call);
14776
+ }
14777
+ return;
14778
+ }
14779
+ call = new Call({
14787
14780
  streamClient: this.streamClient,
14788
- type: call.type,
14789
- id: call.id,
14790
- members,
14781
+ type: e.call.type,
14782
+ id: e.call.id,
14783
+ members: e.members,
14791
14784
  clientStore: this.writeableStateStore,
14792
- ringing: true,
14785
+ ringing,
14793
14786
  });
14794
- await newCallInstance.get();
14795
- }
14796
- }));
14797
- this.effectsRegistered = true;
14787
+ call.state.updateFromCallResponse(e.call);
14788
+ if (ringing) {
14789
+ await call.get();
14790
+ }
14791
+ else {
14792
+ this.writeableStateStore.registerCall(call);
14793
+ this.logger('info', `New call created and registered: ${call.cid}`);
14794
+ }
14795
+ });
14796
+ }
14797
+ catch (err) {
14798
+ this.logger('error', `Failed to init call from event ${e.type}`, err);
14799
+ }
14798
14800
  };
14799
14801
  /**
14800
14802
  * Connects the given user to the client.
@@ -14894,6 +14896,7 @@ class StreamVideoClient {
14894
14896
  *
14895
14897
  * @param type the type of the call.
14896
14898
  * @param id the id of the call.
14899
+ * @param options additional options for call creation.
14897
14900
  */
14898
14901
  this.call = (type, id, options = {}) => {
14899
14902
  const call = options.reuseInstance
@@ -15024,22 +15027,24 @@ class StreamVideoClient {
15024
15027
  * @returns
15025
15028
  */
15026
15029
  this.onRingingCall = async (call_cid) => {
15027
- // if we find the call and is already ringing, we don't need to create a new call
15028
- // as client would have received the call.ring state because the app had WS alive when receiving push notifications
15029
- let call = this.state.calls.find((c) => c.cid === call_cid && c.ringing);
15030
- if (!call) {
15031
- // if not it means that WS is not alive when receiving the push notifications and we need to fetch the call
15032
- const [callType, callId] = call_cid.split(':');
15033
- call = new Call({
15034
- streamClient: this.streamClient,
15035
- type: callType,
15036
- id: callId,
15037
- clientStore: this.writeableStateStore,
15038
- ringing: true,
15039
- });
15040
- await call.get();
15041
- }
15042
- return call;
15030
+ return withoutConcurrency(getCallInitConcurrencyTag(call_cid), async () => {
15031
+ // if we find the call and is already ringing, we don't need to create a new call
15032
+ // as client would have received the call.ring state because the app had WS alive when receiving push notifications
15033
+ let call = this.state.calls.find((c) => c.cid === call_cid && c.ringing);
15034
+ if (!call) {
15035
+ // if not it means that WS is not alive when receiving the push notifications and we need to fetch the call
15036
+ const [callType, callId] = call_cid.split(':');
15037
+ call = new Call({
15038
+ streamClient: this.streamClient,
15039
+ type: callType,
15040
+ id: callId,
15041
+ clientStore: this.writeableStateStore,
15042
+ ringing: true,
15043
+ });
15044
+ await call.get();
15045
+ }
15046
+ return call;
15047
+ });
15043
15048
  };
15044
15049
  /**
15045
15050
  * Connects the given anonymous user to the client.