@stream-io/video-client 1.44.4 → 1.44.6-beta.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,12 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [1.44.5](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.44.4...@stream-io/video-client-1.44.5) (2026-03-27)
6
+
7
+ ### Bug Fixes
8
+
9
+ - make WebAudio opt-in, add AudioBindingsWatchdog ([#2171](https://github.com/GetStream/stream-video-js/issues/2171)) ([8d00f48](https://github.com/GetStream/stream-video-js/commit/8d00f485a37fec23dca340d32738a3cb1f7f325a))
10
+
5
11
  ## [1.44.4](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.44.3...@stream-io/video-client-1.44.4) (2026-03-20)
6
12
 
7
13
  - trace device permission state transitions ([#2168](https://github.com/GetStream/stream-video-js/issues/2168)) ([e4203a3](https://github.com/GetStream/stream-video-js/commit/e4203a34cad1c90d1bc5612fc379dd1f0f0ebe5d))
@@ -4784,7 +4784,7 @@ class StreamVideoWriteableStateStore {
4784
4784
  * The currently connected user.
4785
4785
  */
4786
4786
  get connectedUser() {
4787
- return getCurrentValue(this.connectedUserSubject);
4787
+ return this.connectedUserSubject.getValue();
4788
4788
  }
4789
4789
  /**
4790
4790
  * A list of {@link Call} objects created/tracked by this client.
@@ -6283,7 +6283,7 @@ const getSdkVersion = (sdk) => {
6283
6283
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
6284
6284
  };
6285
6285
 
6286
- const version = "1.44.4";
6286
+ const version = "1.44.6-beta.0";
6287
6287
  const [major, minor, patch] = version.split('.');
6288
6288
  let sdkInfo = {
6289
6289
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -8982,6 +8982,7 @@ const watchCallRejected = (call) => {
8982
8982
  else {
8983
8983
  if (rejectedBy[eventCall.created_by.id]) {
8984
8984
  call.logger.info('call creator rejected, leaving call');
8985
+ globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
8985
8986
  await call.leave({ message: 'ring: creator rejected' });
8986
8987
  }
8987
8988
  }
@@ -8992,6 +8993,7 @@ const watchCallRejected = (call) => {
8992
8993
  */
8993
8994
  const watchCallEnded = (call) => {
8994
8995
  return function onCallEnded() {
8996
+ globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
8995
8997
  const { callingState } = call.state;
8996
8998
  if (callingState !== CallingState.IDLE &&
8997
8999
  callingState !== CallingState.LEFT) {
@@ -9023,6 +9025,7 @@ const watchSfuCallEnded = (call) => {
9023
9025
  // update the call state to reflect the call has ended.
9024
9026
  call.state.setEndedAt(new Date());
9025
9027
  const reason = CallEndedReason[e.reason];
9028
+ globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
9026
9029
  await call.leave({ message: `callEnded received: ${reason}` });
9027
9030
  }
9028
9031
  catch (err) {
@@ -9491,6 +9494,96 @@ class ViewportTracker {
9491
9494
  }
9492
9495
  }
9493
9496
 
9497
+ const toBindingKey = (sessionId, trackType = 'audioTrack') => `${sessionId}/${trackType}`;
9498
+ /**
9499
+ * Tracks audio element bindings and periodically warns about
9500
+ * remote participants whose audio streams have no bound element.
9501
+ */
9502
+ class AudioBindingsWatchdog {
9503
+ constructor(state, tracer) {
9504
+ this.state = state;
9505
+ this.tracer = tracer;
9506
+ this.bindings = new Map();
9507
+ this.enabled = true;
9508
+ this.logger = videoLoggerSystem.getLogger('AudioBindingsWatchdog');
9509
+ /**
9510
+ * Registers an audio element binding for the given session and track type.
9511
+ * Warns if a different element is already bound to the same key.
9512
+ */
9513
+ this.register = (audioElement, sessionId, trackType) => {
9514
+ const key = toBindingKey(sessionId, trackType);
9515
+ const existing = this.bindings.get(key);
9516
+ if (existing && existing !== audioElement) {
9517
+ this.logger.warn(`Audio element already bound to ${sessionId} and ${trackType}`);
9518
+ this.tracer.trace('audioBinding.alreadyBoundWarning', trackType);
9519
+ }
9520
+ this.bindings.set(key, audioElement);
9521
+ };
9522
+ /**
9523
+ * Removes the audio element binding for the given session and track type.
9524
+ */
9525
+ this.unregister = (sessionId, trackType) => {
9526
+ this.bindings.delete(toBindingKey(sessionId, trackType));
9527
+ };
9528
+ /**
9529
+ * Enables or disables the watchdog.
9530
+ * When disabled, the periodic check stops but bindings are still tracked.
9531
+ */
9532
+ this.setEnabled = (enabled) => {
9533
+ this.enabled = enabled;
9534
+ if (enabled) {
9535
+ this.start();
9536
+ }
9537
+ else {
9538
+ this.stop();
9539
+ }
9540
+ };
9541
+ /**
9542
+ * Stops the watchdog and unsubscribes from callingState changes.
9543
+ */
9544
+ this.dispose = () => {
9545
+ this.stop();
9546
+ this.unsubscribeCallingState();
9547
+ };
9548
+ this.start = () => {
9549
+ clearInterval(this.watchdogInterval);
9550
+ this.watchdogInterval = setInterval(() => {
9551
+ const danglingUserIds = [];
9552
+ for (const p of this.state.participants) {
9553
+ if (p.isLocalParticipant)
9554
+ continue;
9555
+ const { audioStream, screenShareAudioStream, sessionId, userId } = p;
9556
+ if (audioStream && !this.bindings.has(toBindingKey(sessionId))) {
9557
+ danglingUserIds.push(userId);
9558
+ }
9559
+ if (screenShareAudioStream &&
9560
+ !this.bindings.has(toBindingKey(sessionId, 'screenShareAudioTrack'))) {
9561
+ danglingUserIds.push(userId);
9562
+ }
9563
+ }
9564
+ if (danglingUserIds.length > 0) {
9565
+ const key = 'audioBinding.danglingWarning';
9566
+ this.tracer.traceOnce(key, key, danglingUserIds);
9567
+ this.logger.warn(`Dangling audio bindings detected. Did you forget to bind the audio element? user_ids: ${danglingUserIds}.`);
9568
+ }
9569
+ }, 3000);
9570
+ };
9571
+ this.stop = () => {
9572
+ clearInterval(this.watchdogInterval);
9573
+ };
9574
+ this.unsubscribeCallingState = createSubscription(state.callingState$, (callingState) => {
9575
+ if (!this.enabled)
9576
+ return;
9577
+ if (callingState !== CallingState.JOINED) {
9578
+ this.stop();
9579
+ }
9580
+ else {
9581
+ this.start();
9582
+ }
9583
+ });
9584
+ }
9585
+ }
9586
+
9494
9587
  const DEFAULT_VIEWPORT_VISIBILITY_STATE = {
9495
9588
  videoTrack: VisibilityState.UNKNOWN,
9496
9589
  screenShareTrack: VisibilityState.UNKNOWN,
@@ -9516,7 +9609,7 @@ class DynascaleManager {
9516
9609
  */
9517
9610
  this.viewportTracker = new ViewportTracker();
9518
9611
  this.logger = videoLoggerSystem.getLogger('DynascaleManager');
9519
- this.useWebAudio = isSafari();
9612
+ this.useWebAudio = false;
9520
9613
  this.pendingSubscriptionsUpdate = null;
9521
9614
  this.videoTrackSubscriptionOverridesSubject = new BehaviorSubject({});
9522
9615
  this.videoTrackSubscriptionOverrides$ = this.videoTrackSubscriptionOverridesSubject.asObservable();
@@ -9548,7 +9641,8 @@ class DynascaleManager {
9548
9641
  if (this.pendingSubscriptionsUpdate) {
9549
9642
  clearTimeout(this.pendingSubscriptionsUpdate);
9550
9643
  }
9551
- const context = this.getOrCreateAudioContext();
9644
+ this.audioBindingsWatchdog?.dispose();
9645
+ const context = this.audioContext;
9552
9646
  if (context && context.state !== 'closed') {
9553
9647
  document.removeEventListener('click', this.resumeAudioContext);
9554
9648
  await context.close();
@@ -9747,12 +9841,13 @@ class DynascaleManager {
9747
9841
  lastDimensions = currentDimensions;
9748
9842
  });
9749
9843
  resizeObserver?.observe(videoElement);
9844
+ const isVideoTrack = trackType === 'videoTrack';
9750
9845
  // element renders and gets bound - track subscription gets
9751
9846
  // triggered first other ones get skipped on initial subscriptions
9752
9847
  const publishedTracksSubscription = boundParticipant.isLocalParticipant
9753
9848
  ? null
9754
9849
  : participant$
9755
- .pipe(distinctUntilKeyChanged('publishedTracks'), map((p) => trackType === 'videoTrack' ? hasVideo(p) : hasScreenShare(p)), distinctUntilChanged())
9850
+ .pipe(distinctUntilKeyChanged('publishedTracks'), map((p) => (isVideoTrack ? hasVideo(p) : hasScreenShare(p))), distinctUntilChanged())
9756
9851
  .subscribe((isPublishing) => {
9757
9852
  if (isPublishing) {
9758
9853
  // the participant just started to publish a track
@@ -9772,10 +9867,11 @@ class DynascaleManager {
9772
9867
  // without prior user interaction:
9773
9868
  // https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide
9774
9869
  videoElement.muted = true;
9870
+ const trackKey = isVideoTrack ? 'videoStream' : 'screenShareStream';
9775
9871
  const streamSubscription = participant$
9776
- .pipe(distinctUntilKeyChanged(trackType === 'videoTrack' ? 'videoStream' : 'screenShareStream'))
9872
+ .pipe(distinctUntilKeyChanged(trackKey))
9777
9873
  .subscribe((p) => {
9778
- const source = trackType === 'videoTrack' ? p.videoStream : p.screenShareStream;
9874
+ const source = isVideoTrack ? p.videoStream : p.screenShareStream;
9779
9875
  if (videoElement.srcObject === source)
9780
9876
  return;
9781
9877
  videoElement.srcObject = source ?? null;
@@ -9814,6 +9910,7 @@ class DynascaleManager {
9814
9910
  const participant = this.callState.findParticipantBySessionId(sessionId);
9815
9911
  if (!participant || participant.isLocalParticipant)
9816
9912
  return;
9913
+ this.audioBindingsWatchdog?.register(audioElement, sessionId, trackType);
9817
9914
  const participant$ = this.callState.participants$.pipe(map((ps) => ps.find((p) => p.sessionId === sessionId)), takeWhile((p) => !!p), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }));
9818
9915
  const updateSinkId = (deviceId, audioContext) => {
9819
9916
  if (!deviceId)
@@ -9832,14 +9929,12 @@ class DynascaleManager {
9832
9929
  };
9833
9930
  let sourceNode = undefined;
9834
9931
  let gainNode = undefined;
9932
+ const isAudioTrack = trackType === 'audioTrack';
9933
+ const trackKey = isAudioTrack ? 'audioStream' : 'screenShareAudioStream';
9835
9934
  const updateMediaStreamSubscription = participant$
9836
- .pipe(distinctUntilKeyChanged(trackType === 'screenShareAudioTrack'
9837
- ? 'screenShareAudioStream'
9838
- : 'audioStream'))
9935
+ .pipe(distinctUntilKeyChanged(trackKey))
9839
9936
  .subscribe((p) => {
9840
- const source = trackType === 'screenShareAudioTrack'
9841
- ? p.screenShareAudioStream
9842
- : p.audioStream;
9937
+ const source = isAudioTrack ? p.audioStream : p.screenShareAudioStream;
9843
9938
  if (audioElement.srcObject === source)
9844
9939
  return;
9845
9940
  setTimeout(() => {
@@ -9894,6 +9989,7 @@ class DynascaleManager {
9894
9989
  });
9895
9990
  audioElement.autoplay = true;
9896
9991
  return () => {
9992
+ this.audioBindingsWatchdog?.unregister(sessionId, trackType);
9897
9993
  sinkIdSubscription?.unsubscribe();
9898
9994
  volumeSubscription.unsubscribe();
9899
9995
  updateMediaStreamSubscription.unsubscribe();
@@ -9954,6 +10050,9 @@ class DynascaleManager {
9954
10050
  this.callState = callState;
9955
10051
  this.speaker = speaker;
9956
10052
  this.tracer = tracer;
10053
+ if (!isReactNative()) {
10054
+ this.audioBindingsWatchdog = new AudioBindingsWatchdog(callState, tracer);
10055
+ }
9957
10056
  }
9958
10057
  setSfuClient(sfuClient) {
9959
10058
  this.sfuClient = sfuClient;
@@ -10342,9 +10441,6 @@ const getDevices = (permission, kind, tracer) => {
10342
10441
  const checkIfAudioOutputChangeSupported = () => {
10343
10442
  if (typeof document === 'undefined')
10344
10443
  return false;
10345
- // Safari uses WebAudio API for playing audio, so we check the AudioContext prototype
10346
- if (isSafari())
10347
- return 'setSinkId' in AudioContext.prototype;
10348
10444
  const element = document.createElement('audio');
10349
10445
  return 'setSinkId' in element;
10350
10446
  };
@@ -12533,6 +12629,7 @@ class SpeakerManager {
12533
12629
  this.defaultDevice = defaultDevice;
12534
12630
  globalThis.streamRNVideoSDK?.callManager.setup({
12535
12631
  defaultDevice,
12632
+ isRingingTypeCall: this.call.ringing,
12536
12633
  });
12537
12634
  }
12538
12635
  }
@@ -12732,6 +12829,7 @@ class Call {
12732
12829
  const currentUserId = this.currentUserId;
12733
12830
  if (currentUserId && blockedUserIds.includes(currentUserId)) {
12734
12831
  this.logger.info('Leaving call because of being blocked');
12832
+ globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'restricted');
12735
12833
  await this.leave({ message: 'user blocked' }).catch((err) => {
12736
12834
  this.logger.error('Error leaving call after being blocked', err);
12737
12835
  });
@@ -12768,6 +12866,7 @@ class Call {
12768
12866
  const isAcceptedElsewhere = isAcceptedByMe && this.state.callingState === CallingState.RINGING;
12769
12867
  if ((isAcceptedElsewhere || isRejectedByMe) &&
12770
12868
  !hasPending(this.joinLeaveConcurrencyTag)) {
12869
+ globalThis.streamRNVideoSDK?.callingX?.endCall(this, isAcceptedElsewhere ? 'answeredElsewhere' : 'rejected');
12771
12870
  this.leave().catch(() => {
12772
12871
  this.logger.error('Could not leave a call that was accepted or rejected elsewhere');
12773
12872
  });
@@ -12779,6 +12878,9 @@ class Call {
12779
12878
  const receiver_id = this.clientStore.connectedUser?.id;
12780
12879
  const ended_at = callSession?.ended_at;
12781
12880
  const created_by_id = this.state.createdBy?.id;
12881
+ if (this.currentUserId && created_by_id === this.currentUserId) {
12882
+ globalThis.streamRNVideoSDK?.callingX?.registerOutgoingCall(this);
12883
+ }
12782
12884
  const rejected_by = callSession?.rejected_by;
12783
12885
  const accepted_by = callSession?.accepted_by;
12784
12886
  let leaveCallIdle = false;
@@ -12917,17 +13019,28 @@ class Call {
12917
13019
  }
12918
13020
  if (callingState === CallingState.RINGING && reject !== false) {
12919
13021
  if (reject) {
12920
- await this.reject(reason ?? 'decline');
13022
+ const reasonToEndCallReason = {
13023
+ timeout: 'missed',
13024
+ cancel: 'canceled',
13025
+ busy: 'busy',
13026
+ decline: 'rejected',
13027
+ };
13028
+ const rejectReason = reason ?? 'decline';
13029
+ const endCallReason = reasonToEndCallReason[rejectReason] ?? 'rejected';
13030
+ globalThis.streamRNVideoSDK?.callingX?.endCall(this, endCallReason);
13031
+ await this.reject(rejectReason);
12921
13032
  }
12922
13033
  else {
12923
13034
  // if reject was undefined, we still have to cancel the call automatically
12924
13035
  // when I am the creator and everyone else left the call
12925
13036
  const hasOtherParticipants = this.state.remoteParticipants.length > 0;
12926
13037
  if (this.isCreatedByMe && !hasOtherParticipants) {
13038
+ globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'canceled');
12927
13039
  await this.reject('cancel');
12928
13040
  }
12929
13041
  }
12930
13042
  }
13043
+ globalThis.streamRNVideoSDK?.callingX?.endCall(this);
12931
13044
  this.statsReporter?.stop();
12932
13045
  this.statsReporter = undefined;
12933
13046
  const leaveReason = message ?? reason ?? 'user is leaving the call';
@@ -12954,7 +13067,9 @@ class Call {
12954
13067
  this.ringingSubject.next(false);
12955
13068
  this.cancelAutoDrop();
12956
13069
  this.clientStore.unregisterCall(this);
12957
- globalThis.streamRNVideoSDK?.callManager.stop();
13070
+ globalThis.streamRNVideoSDK?.callManager.stop({
13071
+ isRingingTypeCall: this.ringing,
13072
+ });
12958
13073
  this.camera.dispose();
12959
13074
  this.microphone.dispose();
12960
13075
  this.screenShare.dispose();
@@ -13120,11 +13235,19 @@ class Call {
13120
13235
  * @returns a promise which resolves once the call join-flow has finished.
13121
13236
  */
13122
13237
  this.join = async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, ...data } = {}) => {
13123
- await this.setup();
13124
13238
  const callingState = this.state.callingState;
13125
13239
  if ([CallingState.JOINED, CallingState.JOINING].includes(callingState)) {
13126
13240
  throw new Error(`Illegal State: call.join() shall be called only once`);
13127
13241
  }
13242
+ if (data?.ring) {
13243
+ this.ringingSubject.next(true);
13244
+ }
13245
+ const callingX = globalThis.streamRNVideoSDK?.callingX;
13246
+ if (callingX) {
13247
+ // for Android/iOS, we need to start the call in the callingx library as soon as possible
13248
+ await callingX.joinCall(this, this.clientStore.calls);
13249
+ }
13250
+ await this.setup();
13128
13251
  this.joinResponseTimeout = joinResponseTimeout;
13129
13252
  this.rpcRequestTimeout = rpcRequestTimeout;
13130
13253
  // we will count the number of join failures per SFU.
@@ -13133,38 +13256,44 @@ class Call {
13133
13256
  const sfuJoinFailures = new Map();
13134
13257
  const joinData = data;
13135
13258
  maxJoinRetries = Math.max(maxJoinRetries, 1);
13136
- for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
13137
- try {
13138
- this.logger.trace(`Joining call (${attempt})`, this.cid);
13139
- await this.doJoin(data);
13140
- delete joinData.migrating_from;
13141
- delete joinData.migrating_from_list;
13142
- break;
13143
- }
13144
- catch (err) {
13145
- this.logger.warn(`Failed to join call (${attempt})`, this.cid);
13146
- if ((err instanceof ErrorFromResponse && err.unrecoverable) ||
13147
- (err instanceof SfuJoinError && err.unrecoverable)) {
13148
- // if the error is unrecoverable, we should not retry as that signals
13149
- // that connectivity is good, but the coordinator doesn't allow the user
13150
- // to join the call due to some reason (e.g., ended call, expired token...)
13151
- throw err;
13152
- }
13153
- // immediately switch to a different SFU in case of recoverable join error
13154
- const switchSfu = err instanceof SfuJoinError &&
13155
- SfuJoinError.isJoinErrorCode(err.errorEvent);
13156
- const sfuId = this.credentials?.server.edge_name || '';
13157
- const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
13158
- sfuJoinFailures.set(sfuId, failures);
13159
- if (switchSfu || failures >= 2) {
13160
- joinData.migrating_from = sfuId;
13161
- joinData.migrating_from_list = Array.from(sfuJoinFailures.keys());
13259
+ try {
13260
+ for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
13261
+ try {
13262
+ this.logger.trace(`Joining call (${attempt})`, this.cid);
13263
+ await this.doJoin(data);
13264
+ delete joinData.migrating_from;
13265
+ delete joinData.migrating_from_list;
13266
+ break;
13162
13267
  }
13163
- if (attempt === maxJoinRetries - 1) {
13164
- throw err;
13268
+ catch (err) {
13269
+ this.logger.warn(`Failed to join call (${attempt})`, this.cid);
13270
+ if ((err instanceof ErrorFromResponse && err.unrecoverable) ||
13271
+ (err instanceof SfuJoinError && err.unrecoverable)) {
13272
+ // if the error is unrecoverable, we should not retry as that signals
13273
+ // that connectivity is good, but the coordinator doesn't allow the user
13274
+ // to join the call due to some reason (e.g., ended call, expired token...)
13275
+ throw err;
13276
+ }
13277
+ // immediately switch to a different SFU in case of recoverable join error
13278
+ const switchSfu = err instanceof SfuJoinError &&
13279
+ SfuJoinError.isJoinErrorCode(err.errorEvent);
13280
+ const sfuId = this.credentials?.server.edge_name || '';
13281
+ const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
13282
+ sfuJoinFailures.set(sfuId, failures);
13283
+ if (switchSfu || failures >= 2) {
13284
+ joinData.migrating_from = sfuId;
13285
+ joinData.migrating_from_list = Array.from(sfuJoinFailures.keys());
13286
+ }
13287
+ if (attempt === maxJoinRetries - 1) {
13288
+ throw err;
13289
+ }
13165
13290
  }
13291
+ await sleep(retryInterval(attempt));
13166
13292
  }
13167
- await sleep(retryInterval(attempt));
13293
+ }
13294
+ catch (error) {
13295
+ callingX?.endCall(this, 'error');
13296
+ throw error;
13168
13297
  }
13169
13298
  };
13170
13299
  /**
@@ -13311,7 +13440,9 @@ class Call {
13311
13440
  // re-apply them on later reconnections or server-side data fetches
13312
13441
  if (!this.deviceSettingsAppliedOnce && this.state.settings) {
13313
13442
  await this.applyDeviceConfig(this.state.settings, true, false);
13314
- globalThis.streamRNVideoSDK?.callManager.start();
13443
+ globalThis.streamRNVideoSDK?.callManager.start({
13444
+ isRingingTypeCall: this.ringing,
13445
+ });
13315
13446
  this.deviceSettingsAppliedOnce = true;
13316
13447
  }
13317
13448
  // We shouldn't persist the `ring` and `notify` state after joining the call
@@ -13739,6 +13870,7 @@ class Call {
13739
13870
  if (strategy === WebsocketReconnectStrategy.UNSPECIFIED)
13740
13871
  return;
13741
13872
  if (strategy === WebsocketReconnectStrategy.DISCONNECT) {
13873
+ globalThis.streamRNVideoSDK?.callingX?.endCall(this, 'error');
13742
13874
  this.leave({ message: 'SFU instructed to disconnect' }).catch((err) => {
13743
13875
  this.logger.warn(`Can't leave call after disconnect request`, err);
13744
13876
  });
@@ -14760,7 +14892,7 @@ class Call {
14760
14892
  * A flag indicating whether the call was created by the current user.
14761
14893
  */
14762
14894
  get isCreatedByMe() {
14763
- return this.state.createdBy?.id === this.currentUserId;
14895
+ return (this.currentUserId && this.state.createdBy?.id === this.currentUserId);
14764
14896
  }
14765
14897
  }
14766
14898
 
@@ -15886,7 +16018,7 @@ class StreamClient {
15886
16018
  this.getUserAgent = () => {
15887
16019
  if (!this.cachedUserAgent) {
15888
16020
  const { clientAppIdentifier = {} } = this.options;
15889
- const { sdkName = 'js', sdkVersion = "1.44.4", ...extras } = clientAppIdentifier;
16021
+ const { sdkName = 'js', sdkVersion = "1.44.6-beta.0", ...extras } = clientAppIdentifier;
15890
16022
  this.cachedUserAgent = [
15891
16023
  `stream-video-${sdkName}-v${sdkVersion}`,
15892
16024
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),