@stream-io/video-client 1.27.3 → 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/dist/index.cjs.js CHANGED
@@ -4302,13 +4302,6 @@ class StreamVideoWriteableStateStore {
4302
4302
  */
4303
4303
  class StreamVideoReadOnlyStateStore {
4304
4304
  constructor(store) {
4305
- /**
4306
- * This method allows you the get the current value of a state variable.
4307
- *
4308
- * @param observable the observable to get the current value of.
4309
- * @returns the current value of the observable.
4310
- */
4311
- this.getCurrentValue = getCurrentValue;
4312
4305
  // convert and expose subjects as observables
4313
4306
  this.connectedUser$ = store.connectedUserSubject.asObservable();
4314
4307
  this.calls$ = store.callsSubject.asObservable();
@@ -5645,6 +5638,151 @@ const getSdkVersion = (sdk) => {
5645
5638
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
5646
5639
  };
5647
5640
 
5641
+ const version = "1.27.5";
5642
+ const [major, minor, patch] = version.split('.');
5643
+ let sdkInfo = {
5644
+ type: SdkType.PLAIN_JAVASCRIPT,
5645
+ major,
5646
+ minor,
5647
+ patch,
5648
+ };
5649
+ let osInfo;
5650
+ let deviceInfo;
5651
+ let webRtcInfo;
5652
+ let deviceState = { oneofKind: undefined };
5653
+ const setSdkInfo = (info) => {
5654
+ sdkInfo = info;
5655
+ };
5656
+ const getSdkInfo = () => {
5657
+ return sdkInfo;
5658
+ };
5659
+ const setOSInfo = (info) => {
5660
+ osInfo = info;
5661
+ };
5662
+ const setDeviceInfo = (info) => {
5663
+ deviceInfo = info;
5664
+ };
5665
+ const getWebRTCInfo = () => {
5666
+ return webRtcInfo;
5667
+ };
5668
+ const setWebRTCInfo = (info) => {
5669
+ webRtcInfo = info;
5670
+ };
5671
+ const setThermalState = (state) => {
5672
+ if (!osInfo) {
5673
+ deviceState = { oneofKind: undefined };
5674
+ return;
5675
+ }
5676
+ if (osInfo.name === 'android') {
5677
+ const thermalState = AndroidThermalState[state] ||
5678
+ AndroidThermalState.UNSPECIFIED;
5679
+ deviceState = {
5680
+ oneofKind: 'android',
5681
+ android: {
5682
+ thermalState,
5683
+ isPowerSaverMode: deviceState?.oneofKind === 'android' &&
5684
+ deviceState.android.isPowerSaverMode,
5685
+ },
5686
+ };
5687
+ }
5688
+ if (osInfo.name.toLowerCase() === 'ios') {
5689
+ const thermalState = AppleThermalState[state] ||
5690
+ AppleThermalState.UNSPECIFIED;
5691
+ deviceState = {
5692
+ oneofKind: 'apple',
5693
+ apple: {
5694
+ thermalState,
5695
+ isLowPowerModeEnabled: deviceState?.oneofKind === 'apple' &&
5696
+ deviceState.apple.isLowPowerModeEnabled,
5697
+ },
5698
+ };
5699
+ }
5700
+ };
5701
+ const setPowerState = (powerMode) => {
5702
+ if (!osInfo) {
5703
+ deviceState = { oneofKind: undefined };
5704
+ return;
5705
+ }
5706
+ if (osInfo.name === 'android') {
5707
+ deviceState = {
5708
+ oneofKind: 'android',
5709
+ android: {
5710
+ thermalState: deviceState?.oneofKind === 'android'
5711
+ ? deviceState.android.thermalState
5712
+ : AndroidThermalState.UNSPECIFIED,
5713
+ isPowerSaverMode: powerMode,
5714
+ },
5715
+ };
5716
+ }
5717
+ if (osInfo.name.toLowerCase() === 'ios') {
5718
+ deviceState = {
5719
+ oneofKind: 'apple',
5720
+ apple: {
5721
+ thermalState: deviceState?.oneofKind === 'apple'
5722
+ ? deviceState.apple.thermalState
5723
+ : AppleThermalState.UNSPECIFIED,
5724
+ isLowPowerModeEnabled: powerMode,
5725
+ },
5726
+ };
5727
+ }
5728
+ };
5729
+ const getDeviceState = () => {
5730
+ return deviceState;
5731
+ };
5732
+ const getClientDetails = async () => {
5733
+ if (isReactNative()) {
5734
+ // Since RN doesn't support web, sharing browser info is not required
5735
+ return {
5736
+ sdk: sdkInfo,
5737
+ os: osInfo,
5738
+ device: deviceInfo,
5739
+ };
5740
+ }
5741
+ // @ts-expect-error - userAgentData is not yet in the TS types
5742
+ const userAgentDataApi = navigator.userAgentData;
5743
+ let userAgentData;
5744
+ if (userAgentDataApi && userAgentDataApi.getHighEntropyValues) {
5745
+ try {
5746
+ userAgentData = await userAgentDataApi.getHighEntropyValues([
5747
+ 'platform',
5748
+ 'platformVersion',
5749
+ 'fullVersionList',
5750
+ ]);
5751
+ }
5752
+ catch {
5753
+ // Ignore the error
5754
+ }
5755
+ }
5756
+ const userAgent = new uaParserJs.UAParser(navigator.userAgent);
5757
+ const { browser, os, device, cpu } = userAgent.getResult();
5758
+ // If userAgentData is available, it means we are in a modern Chromium browser,
5759
+ // and we can use it to get more accurate browser information.
5760
+ // We hook into the fullVersionList to find the browser name and version and
5761
+ // eventually detect exotic browsers like Samsung Internet, AVG Secure Browser, etc.
5762
+ // who by default they identify themselves as "Chromium" in the user agent string.
5763
+ // Eliminates the generic "Chromium" name and "Not)A_Brand" name from the list.
5764
+ // https://wicg.github.io/ua-client-hints/#create-arbitrary-brands-section
5765
+ const uaBrowser = userAgentData?.fullVersionList?.find((v) => !v.brand.includes('Chromium') && !v.brand.match(/[()\-./:;=?_]/g));
5766
+ return {
5767
+ sdk: sdkInfo,
5768
+ browser: {
5769
+ name: uaBrowser?.brand || browser.name || navigator.userAgent,
5770
+ version: uaBrowser?.version || browser.version || '',
5771
+ },
5772
+ os: {
5773
+ name: userAgentData?.platform || os.name || '',
5774
+ version: userAgentData?.platformVersion || os.version || '',
5775
+ architecture: cpu.architecture || '',
5776
+ },
5777
+ device: {
5778
+ name: [device.vendor, device.model, device.type]
5779
+ .filter(Boolean)
5780
+ .join(' '),
5781
+ version: '',
5782
+ },
5783
+ };
5784
+ };
5785
+
5648
5786
  /**
5649
5787
  * Checks whether the current browser is Safari.
5650
5788
  */
@@ -5669,12 +5807,34 @@ const isChrome = () => {
5669
5807
  return false;
5670
5808
  return navigator.userAgent?.includes('Chrome');
5671
5809
  };
5810
+ /**
5811
+ * Checks whether the current browser is among the list of first-class supported browsers.
5812
+ * This includes Chrome, Edge, Firefox, and Safari.
5813
+ *
5814
+ * Although the Stream Video SDK may work in other browsers, these are the ones we officially support.
5815
+ */
5816
+ const isSupportedBrowser = async () => {
5817
+ const { browser } = await getClientDetails();
5818
+ if (!browser)
5819
+ return false; // we aren't running in a browser
5820
+ const name = browser.name.toLowerCase();
5821
+ const [major] = browser.version.split('.');
5822
+ const version = parseInt(major, 10);
5823
+ return ((name.includes('chrome') && version >= 124) ||
5824
+ (name.includes('edge') && version >= 124) ||
5825
+ (name.includes('firefox') && version >= 124) ||
5826
+ (name.includes('safari') && version >= 17) ||
5827
+ (name.includes('webkit') && version >= 605) || // WebView on iOS
5828
+ (name.includes('webview') && version >= 124) // WebView on Android
5829
+ );
5830
+ };
5672
5831
 
5673
5832
  var browsers = /*#__PURE__*/Object.freeze({
5674
5833
  __proto__: null,
5675
5834
  isChrome: isChrome,
5676
5835
  isFirefox: isFirefox,
5677
- isSafari: isSafari
5836
+ isSafari: isSafari,
5837
+ isSupportedBrowser: isSupportedBrowser
5678
5838
  });
5679
5839
 
5680
5840
  /**
@@ -5916,142 +6076,6 @@ const aggregate = (stats) => {
5916
6076
  return report;
5917
6077
  };
5918
6078
 
5919
- const version = "1.27.3";
5920
- const [major, minor, patch] = version.split('.');
5921
- let sdkInfo = {
5922
- type: SdkType.PLAIN_JAVASCRIPT,
5923
- major,
5924
- minor,
5925
- patch,
5926
- };
5927
- let osInfo;
5928
- let deviceInfo;
5929
- let webRtcInfo;
5930
- let deviceState = { oneofKind: undefined };
5931
- const setSdkInfo = (info) => {
5932
- sdkInfo = info;
5933
- };
5934
- const getSdkInfo = () => {
5935
- return sdkInfo;
5936
- };
5937
- const setOSInfo = (info) => {
5938
- osInfo = info;
5939
- };
5940
- const setDeviceInfo = (info) => {
5941
- deviceInfo = info;
5942
- };
5943
- const getWebRTCInfo = () => {
5944
- return webRtcInfo;
5945
- };
5946
- const setWebRTCInfo = (info) => {
5947
- webRtcInfo = info;
5948
- };
5949
- const setThermalState = (state) => {
5950
- if (!osInfo) {
5951
- deviceState = { oneofKind: undefined };
5952
- return;
5953
- }
5954
- if (osInfo.name === 'android') {
5955
- const thermalState = AndroidThermalState[state] ||
5956
- AndroidThermalState.UNSPECIFIED;
5957
- deviceState = {
5958
- oneofKind: 'android',
5959
- android: {
5960
- thermalState,
5961
- isPowerSaverMode: deviceState?.oneofKind === 'android' &&
5962
- deviceState.android.isPowerSaverMode,
5963
- },
5964
- };
5965
- }
5966
- if (osInfo.name.toLowerCase() === 'ios') {
5967
- const thermalState = AppleThermalState[state] ||
5968
- AppleThermalState.UNSPECIFIED;
5969
- deviceState = {
5970
- oneofKind: 'apple',
5971
- apple: {
5972
- thermalState,
5973
- isLowPowerModeEnabled: deviceState?.oneofKind === 'apple' &&
5974
- deviceState.apple.isLowPowerModeEnabled,
5975
- },
5976
- };
5977
- }
5978
- };
5979
- const setPowerState = (powerMode) => {
5980
- if (!osInfo) {
5981
- deviceState = { oneofKind: undefined };
5982
- return;
5983
- }
5984
- if (osInfo.name === 'android') {
5985
- deviceState = {
5986
- oneofKind: 'android',
5987
- android: {
5988
- thermalState: deviceState?.oneofKind === 'android'
5989
- ? deviceState.android.thermalState
5990
- : AndroidThermalState.UNSPECIFIED,
5991
- isPowerSaverMode: powerMode,
5992
- },
5993
- };
5994
- }
5995
- if (osInfo.name.toLowerCase() === 'ios') {
5996
- deviceState = {
5997
- oneofKind: 'apple',
5998
- apple: {
5999
- thermalState: deviceState?.oneofKind === 'apple'
6000
- ? deviceState.apple.thermalState
6001
- : AppleThermalState.UNSPECIFIED,
6002
- isLowPowerModeEnabled: powerMode,
6003
- },
6004
- };
6005
- }
6006
- };
6007
- const getDeviceState = () => {
6008
- return deviceState;
6009
- };
6010
- const getClientDetails = async () => {
6011
- if (isReactNative()) {
6012
- // Since RN doesn't support web, sharing browser info is not required
6013
- return {
6014
- sdk: sdkInfo,
6015
- os: osInfo,
6016
- device: deviceInfo,
6017
- };
6018
- }
6019
- // @ts-expect-error - userAgentData is not yet in the TS types
6020
- const userAgentDataApi = navigator.userAgentData;
6021
- let userAgentData;
6022
- if (userAgentDataApi && userAgentDataApi.getHighEntropyValues) {
6023
- try {
6024
- userAgentData = await userAgentDataApi.getHighEntropyValues([
6025
- 'platform',
6026
- 'platformVersion',
6027
- ]);
6028
- }
6029
- catch {
6030
- // Ignore the error
6031
- }
6032
- }
6033
- const userAgent = new uaParserJs.UAParser(navigator.userAgent);
6034
- const { browser, os, device, cpu } = userAgent.getResult();
6035
- return {
6036
- sdk: sdkInfo,
6037
- browser: {
6038
- name: browser.name || navigator.userAgent,
6039
- version: browser.version || '',
6040
- },
6041
- os: {
6042
- name: userAgentData?.platform || os.name || '',
6043
- version: userAgentData?.platformVersion || os.version || '',
6044
- architecture: cpu.architecture || '',
6045
- },
6046
- device: {
6047
- name: [device.vendor, device.model, device.type]
6048
- .filter(Boolean)
6049
- .join(' '),
6050
- version: '',
6051
- },
6052
- };
6053
- };
6054
-
6055
6079
  class SfuStatsReporter {
6056
6080
  constructor(sfuClient, { options, clientDetails, subscriber, publisher, microphone, camera, state, tracer, unifiedSessionId, }) {
6057
6081
  this.logger = getLogger(['SfuStatsReporter']);
@@ -14514,7 +14538,7 @@ class StreamClient {
14514
14538
  this.getUserAgent = () => {
14515
14539
  if (!this.cachedUserAgent) {
14516
14540
  const { clientAppIdentifier = {} } = this.options;
14517
- const { sdkName = 'js', sdkVersion = "1.27.3", ...extras } = clientAppIdentifier;
14541
+ const { sdkName = 'js', sdkVersion = "1.27.5", ...extras } = clientAppIdentifier;
14518
14542
  this.cachedUserAgent = [
14519
14543
  `stream-video-${sdkName}-v${sdkVersion}`,
14520
14544
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -14635,6 +14659,13 @@ class StreamClient {
14635
14659
  const getInstanceKey = (apiKey, user) => {
14636
14660
  return `${apiKey}/${user.id}`;
14637
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}`;
14638
14669
  /**
14639
14670
  * Utility function to get the client app identifier.
14640
14671
  */
@@ -14703,7 +14734,7 @@ class StreamVideoClient {
14703
14734
  this.registerEffects = () => {
14704
14735
  if (this.effectsRegistered)
14705
14736
  return;
14706
- 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) => {
14707
14738
  if (!event.online)
14708
14739
  return;
14709
14740
  const callsToReWatch = this.writeableStateStore.calls
@@ -14720,50 +14751,52 @@ class StreamVideoClient {
14720
14751
  this.logger('error', 'Failed to re-watch calls', err);
14721
14752
  });
14722
14753
  }));
14723
- this.eventHandlersToUnregister.push(this.on('call.created', (event) => {
14724
- const { call, members } = event;
14725
- if (this.state.connectedUser?.id === call.created_by.id) {
14726
- this.logger('warn', 'Received `call.created` sent by the current user');
14727
- return;
14728
- }
14729
- this.logger('info', `New call created and registered: ${call.cid}`);
14730
- const newCall = new Call({
14731
- streamClient: this.streamClient,
14732
- type: call.type,
14733
- id: call.id,
14734
- members,
14735
- clientStore: this.writeableStateStore,
14736
- });
14737
- newCall.state.updateFromCallResponse(call);
14738
- this.writeableStateStore.registerCall(newCall);
14739
- }));
14740
- this.eventHandlersToUnregister.push(this.on('call.ring', async (event) => {
14741
- const { call, members } = event;
14742
- if (this.state.connectedUser?.id === call.created_by.id) {
14743
- this.logger('debug', 'Received `call.ring` sent by the current user so ignoring the event');
14744
- return;
14745
- }
14746
- // if `call.created` was received before `call.ring`.
14747
- // the client already has the call instance and we just need to update the state
14748
- const theCall = this.writeableStateStore.findCall(call.type, call.id);
14749
- if (theCall) {
14750
- await theCall.updateFromRingingEvent(event);
14751
- }
14752
- else {
14753
- // if client doesn't have the call instance, create the instance and fetch the latest state
14754
- // Note: related - we also have onRingingCall method to handle this case from push notifications
14755
- 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({
14756
14780
  streamClient: this.streamClient,
14757
- type: call.type,
14758
- id: call.id,
14759
- members,
14781
+ type: e.call.type,
14782
+ id: e.call.id,
14783
+ members: e.members,
14760
14784
  clientStore: this.writeableStateStore,
14761
- ringing: true,
14785
+ ringing,
14762
14786
  });
14763
- await newCallInstance.get();
14764
- }
14765
- }));
14766
- 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
+ }
14767
14800
  };
14768
14801
  /**
14769
14802
  * Connects the given user to the client.
@@ -14863,6 +14896,7 @@ class StreamVideoClient {
14863
14896
  *
14864
14897
  * @param type the type of the call.
14865
14898
  * @param id the id of the call.
14899
+ * @param options additional options for call creation.
14866
14900
  */
14867
14901
  this.call = (type, id, options = {}) => {
14868
14902
  const call = options.reuseInstance
@@ -14993,22 +15027,24 @@ class StreamVideoClient {
14993
15027
  * @returns
14994
15028
  */
14995
15029
  this.onRingingCall = async (call_cid) => {
14996
- // if we find the call and is already ringing, we don't need to create a new call
14997
- // as client would have received the call.ring state because the app had WS alive when receiving push notifications
14998
- let call = this.state.calls.find((c) => c.cid === call_cid && c.ringing);
14999
- if (!call) {
15000
- // if not it means that WS is not alive when receiving the push notifications and we need to fetch the call
15001
- const [callType, callId] = call_cid.split(':');
15002
- call = new Call({
15003
- streamClient: this.streamClient,
15004
- type: callType,
15005
- id: callId,
15006
- clientStore: this.writeableStateStore,
15007
- ringing: true,
15008
- });
15009
- await call.get();
15010
- }
15011
- 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
+ });
15012
15048
  };
15013
15049
  /**
15014
15050
  * Connects the given anonymous user to the client.