@stream-io/video-client 1.30.1 → 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/dist/index.es.js CHANGED
@@ -7,6 +7,7 @@ import { TwirpFetchTransport, TwirpErrorCode } from '@protobuf-ts/twirp-transpor
7
7
  import { ReplaySubject, combineLatest, BehaviorSubject, shareReplay, map, distinctUntilChanged, startWith, takeWhile, distinctUntilKeyChanged, fromEventPattern, concatMap, merge, from, fromEvent, tap, debounceTime, pairwise, of } from 'rxjs';
8
8
  import { UAParser } from 'ua-parser-js';
9
9
  import { parse, write } from 'sdp-transform';
10
+ import { WorkerTimer } from '@stream-io/worker-timer';
10
11
  import https from 'https';
11
12
 
12
13
  /* tslint:disable */
@@ -2949,6 +2950,12 @@ class JoinRequest$Type extends MessageType {
2949
2950
  super('stream.video.sfu.event.JoinRequest', [
2950
2951
  { no: 1, name: 'token', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
2951
2952
  { no: 2, name: 'session_id', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
2953
+ {
2954
+ no: 13,
2955
+ name: 'unified_session_id',
2956
+ kind: 'scalar',
2957
+ T: 9 /*ScalarType.STRING*/,
2958
+ },
2952
2959
  {
2953
2960
  no: 3,
2954
2961
  name: 'subscriber_sdp',
@@ -5758,7 +5765,7 @@ const getSdkVersion = (sdk) => {
5758
5765
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
5759
5766
  };
5760
5767
 
5761
- const version = "1.30.1";
5768
+ const version = "1.32.0";
5762
5769
  const [major, minor, patch] = version.split('.');
5763
5770
  let sdkInfo = {
5764
5771
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -7895,137 +7902,12 @@ function lazy(factory) {
7895
7902
  };
7896
7903
  }
7897
7904
 
7898
- // Do not modify this file manually. Instead, edit worker.ts
7899
- // and the run ./generate-timer-worker.sh
7900
- const timerWorker = {
7901
- src: `const timerIdMapping = new Map();
7902
- self.addEventListener('message', (event) => {
7903
- const request = event.data;
7904
- switch (request.type) {
7905
- case 'setTimeout':
7906
- case 'setInterval':
7907
- timerIdMapping.set(request.id, (request.type === 'setTimeout' ? setTimeout : setInterval)(() => {
7908
- tick(request.id);
7909
- if (request.type === 'setTimeout') {
7910
- timerIdMapping.delete(request.id);
7911
- }
7912
- }, request.timeout));
7913
- break;
7914
- case 'clearTimeout':
7915
- case 'clearInterval':
7916
- (request.type === 'clearTimeout' ? clearTimeout : clearInterval)(timerIdMapping.get(request.id));
7917
- timerIdMapping.delete(request.id);
7918
- break;
7919
- }
7920
- });
7921
- function tick(id) {
7922
- const message = { type: 'tick', id };
7923
- self.postMessage(message);
7924
- }`,
7925
- };
7926
-
7927
- class TimerWorker {
7928
- constructor() {
7929
- this.currentTimerId = 1;
7930
- this.callbacks = new Map();
7931
- this.fallback = false;
7932
- }
7933
- setup({ useTimerWorker = true } = {}) {
7934
- if (!useTimerWorker) {
7935
- this.fallback = true;
7936
- return;
7937
- }
7938
- try {
7939
- const source = timerWorker.src;
7940
- const blob = new Blob([source], {
7941
- type: 'application/javascript; charset=utf-8',
7942
- });
7943
- const script = URL.createObjectURL(blob);
7944
- this.worker = new Worker(script, { name: 'str-timer-worker' });
7945
- this.worker.addEventListener('message', (event) => {
7946
- const { type, id } = event.data;
7947
- if (type === 'tick') {
7948
- this.callbacks.get(id)?.();
7949
- }
7950
- });
7951
- }
7952
- catch (err) {
7953
- getLogger(['timer-worker'])('error', err);
7954
- this.fallback = true;
7955
- }
7956
- }
7957
- destroy() {
7958
- this.callbacks.clear();
7959
- this.worker?.terminate();
7960
- this.worker = undefined;
7961
- this.fallback = false;
7962
- }
7963
- get ready() {
7964
- return this.fallback || Boolean(this.worker);
7965
- }
7966
- setInterval(callback, timeout) {
7967
- return this.setTimer('setInterval', callback, timeout);
7968
- }
7969
- clearInterval(id) {
7970
- this.clearTimer('clearInterval', id);
7971
- }
7972
- setTimeout(callback, timeout) {
7973
- return this.setTimer('setTimeout', callback, timeout);
7974
- }
7975
- clearTimeout(id) {
7976
- this.clearTimer('clearTimeout', id);
7977
- }
7978
- setTimer(type, callback, timeout) {
7979
- if (!this.ready) {
7980
- this.setup();
7981
- }
7982
- if (this.fallback) {
7983
- return (type === 'setTimeout' ? setTimeout : setInterval)(callback, timeout);
7984
- }
7985
- const id = this.getTimerId();
7986
- this.callbacks.set(id, () => {
7987
- callback();
7988
- // Timeouts are one-off operations, so no need to keep callback reference
7989
- // after timer has fired
7990
- if (type === 'setTimeout') {
7991
- this.callbacks.delete(id);
7992
- }
7993
- });
7994
- this.sendMessage({ type, id, timeout });
7995
- return id;
7996
- }
7997
- clearTimer(type, id) {
7998
- if (!id) {
7999
- return;
8000
- }
8001
- if (!this.ready) {
8002
- this.setup();
8003
- }
8004
- if (this.fallback) {
8005
- (type === 'clearTimeout' ? clearTimeout : clearInterval)(id);
8006
- return;
8007
- }
8008
- this.callbacks.delete(id);
8009
- this.sendMessage({ type, id });
8010
- }
8011
- getTimerId() {
8012
- return this.currentTimerId++;
8013
- }
8014
- sendMessage(message) {
8015
- if (!this.worker) {
8016
- throw new Error("Cannot use timer worker before it's set up");
8017
- }
8018
- this.worker.postMessage(message);
8019
- }
8020
- }
8021
7905
  let timerWorkerEnabled = false;
8022
7906
  const enableTimerWorker = () => {
8023
7907
  timerWorkerEnabled = true;
8024
7908
  };
8025
7909
  const getTimers = lazy(() => {
8026
- const instance = new TimerWorker();
8027
- instance.setup({ useTimerWorker: timerWorkerEnabled });
8028
- return instance;
7910
+ return new WorkerTimer({ useWorker: timerWorkerEnabled });
8029
7911
  });
8030
7912
 
8031
7913
  /**
@@ -11994,7 +11876,6 @@ class Call {
11994
11876
  if ([CallingState.JOINED, CallingState.JOINING].includes(callingState)) {
11995
11877
  throw new Error(`Illegal State: call.join() shall be called only once`);
11996
11878
  }
11997
- this.state.setCallingState(CallingState.JOINING);
11998
11879
  // we will count the number of join failures per SFU.
11999
11880
  // once the number of failures reaches 2, we will piggyback on the `migrating_from`
12000
11881
  // field to force the coordinator to provide us another SFU
@@ -12023,8 +11904,6 @@ class Call {
12023
11904
  joinData.migrating_from = sfuId;
12024
11905
  }
12025
11906
  if (attempt === maxJoinRetries - 1) {
12026
- // restore the previous call state if the join-flow fails
12027
- this.state.setCallingState(callingState);
12028
11907
  throw err;
12029
11908
  }
12030
11909
  }
@@ -12084,6 +11963,7 @@ class Call {
12084
11963
  })
12085
11964
  : previousSfuClient;
12086
11965
  this.sfuClient = sfuClient;
11966
+ this.unifiedSessionId ?? (this.unifiedSessionId = sfuClient.sessionId);
12087
11967
  this.dynascaleManager.setSfuClient(sfuClient);
12088
11968
  const clientDetails = await getClientDetails();
12089
11969
  // we don't need to send JoinRequest if we are re-using an existing healthy SFU client
@@ -12107,6 +11987,7 @@ class Call {
12107
11987
  : [];
12108
11988
  try {
12109
11989
  const { callState, fastReconnectDeadlineSeconds, publishOptions } = await sfuClient.join({
11990
+ unifiedSessionId: this.unifiedSessionId,
12110
11991
  subscriberSdp,
12111
11992
  publisherSdp,
12112
11993
  clientDetails,
@@ -12152,6 +12033,7 @@ class Call {
12152
12033
  statsOptions,
12153
12034
  publishOptions: this.currentPublishOptions || [],
12154
12035
  closePreviousInstances: !performingMigration,
12036
+ unifiedSessionId: this.unifiedSessionId,
12155
12037
  });
12156
12038
  }
12157
12039
  // make sure we only track connection timing if we are not calling this method as part of a reconnection flow
@@ -12278,7 +12160,7 @@ class Call {
12278
12160
  * @internal
12279
12161
  */
12280
12162
  this.initPublisherAndSubscriber = (opts) => {
12281
- const { sfuClient, connectionConfig, clientDetails, statsOptions, publishOptions, closePreviousInstances, } = opts;
12163
+ const { sfuClient, connectionConfig, clientDetails, statsOptions, publishOptions, closePreviousInstances, unifiedSessionId, } = opts;
12282
12164
  const { enable_rtc_stats: enableTracing } = statsOptions;
12283
12165
  if (closePreviousInstances && this.subscriber) {
12284
12166
  this.subscriber.dispose();
@@ -12333,7 +12215,6 @@ class Call {
12333
12215
  this.tracer.setEnabled(enableTracing);
12334
12216
  this.sfuStatsReporter?.stop();
12335
12217
  if (statsOptions?.reporting_interval_ms > 0) {
12336
- this.unifiedSessionId ?? (this.unifiedSessionId = sfuClient.sessionId);
12337
12218
  this.sfuStatsReporter = new SfuStatsReporter(sfuClient, {
12338
12219
  clientDetails,
12339
12220
  options: statsOptions,
@@ -12343,7 +12224,7 @@ class Call {
12343
12224
  camera: this.camera,
12344
12225
  state: this.state,
12345
12226
  tracer: this.tracer,
12346
- unifiedSessionId: this.unifiedSessionId,
12227
+ unifiedSessionId,
12347
12228
  });
12348
12229
  this.sfuStatsReporter.start();
12349
12230
  }
@@ -14663,7 +14544,7 @@ class StreamClient {
14663
14544
  this.getUserAgent = () => {
14664
14545
  if (!this.cachedUserAgent) {
14665
14546
  const { clientAppIdentifier = {} } = this.options;
14666
- const { sdkName = 'js', sdkVersion = "1.30.1", ...extras } = clientAppIdentifier;
14547
+ const { sdkName = 'js', sdkVersion = "1.32.0", ...extras } = clientAppIdentifier;
14667
14548
  this.cachedUserAgent = [
14668
14549
  `stream-video-${sdkName}-v${sdkVersion}`,
14669
14550
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -14852,6 +14733,7 @@ class StreamVideoClient {
14852
14733
  this.effectsRegistered = false;
14853
14734
  this.eventHandlersToUnregister = [];
14854
14735
  this.connectionConcurrencyTag = Symbol('connectionConcurrencyTag');
14736
+ this.rejectCallWhenBusy = false;
14855
14737
  this.registerClientInstance = (apiKey, user) => {
14856
14738
  const instanceKey = getInstanceKey(apiKey, user);
14857
14739
  if (StreamVideoClient._instances.has(instanceKey)) {
@@ -14897,7 +14779,16 @@ class StreamVideoClient {
14897
14779
  let call = this.writeableStateStore.findCall(e.call.type, e.call.id);
14898
14780
  if (call) {
14899
14781
  if (ringing) {
14900
- await call.updateFromRingingEvent(e);
14782
+ if (this.shouldRejectCall(call.cid)) {
14783
+ this.logger('info', `Leaving call with busy reject reason ${call.cid} because user is busy`);
14784
+ // remove the instance from the state store
14785
+ await call.leave();
14786
+ // explicitly reject the call with busy reason as calling state was not ringing before and leave would not call it therefore
14787
+ await call.reject('busy');
14788
+ }
14789
+ else {
14790
+ await call.updateFromRingingEvent(e);
14791
+ }
14901
14792
  }
14902
14793
  else {
14903
14794
  call.state.updateFromCallResponse(e.call);
@@ -14912,11 +14803,19 @@ class StreamVideoClient {
14912
14803
  clientStore: this.writeableStateStore,
14913
14804
  ringing,
14914
14805
  });
14915
- call.state.updateFromCallResponse(e.call);
14916
14806
  if (ringing) {
14917
- await call.get();
14807
+ if (this.shouldRejectCall(call.cid)) {
14808
+ this.logger('info', `Rejecting call ${call.cid} because user is busy`);
14809
+ // call is not in the state store yet, so just reject api is enough
14810
+ await call.reject('busy');
14811
+ }
14812
+ else {
14813
+ await call.updateFromRingingEvent(e);
14814
+ await call.get();
14815
+ }
14918
14816
  }
14919
14817
  else {
14818
+ call.state.updateFromCallResponse(e.call);
14920
14819
  this.writeableStateStore.registerCall(call);
14921
14820
  this.logger('info', `New call created and registered: ${call.cid}`);
14922
14821
  }
@@ -15183,6 +15082,16 @@ class StreamVideoClient {
15183
15082
  this.connectAnonymousUser = async (user, tokenOrProvider) => {
15184
15083
  return withoutConcurrency(this.connectionConcurrencyTag, () => this.streamClient.connectAnonymousUser(user, tokenOrProvider));
15185
15084
  };
15085
+ this.shouldRejectCall = (currentCallId) => {
15086
+ if (!this.rejectCallWhenBusy)
15087
+ return false;
15088
+ const hasOngoingRingingCall = this.state.calls.some((c) => c.cid !== currentCallId &&
15089
+ c.ringing &&
15090
+ c.state.callingState !== CallingState.IDLE &&
15091
+ c.state.callingState !== CallingState.LEFT &&
15092
+ c.state.callingState !== CallingState.RECONNECTING_FAILED);
15093
+ return hasOngoingRingingCall;
15094
+ };
15186
15095
  const apiKey = typeof apiKeyOrArgs === 'string' ? apiKeyOrArgs : apiKeyOrArgs.apiKey;
15187
15096
  const clientOptions = typeof apiKeyOrArgs === 'string' ? opts : apiKeyOrArgs.options;
15188
15097
  if (clientOptions?.enableTimerWorker)
@@ -15190,6 +15099,7 @@ class StreamVideoClient {
15190
15099
  const rootLogger = clientOptions?.logger || logToConsole;
15191
15100
  setLogger(rootLogger, clientOptions?.logLevel || 'warn');
15192
15101
  this.logger = getLogger(['client']);
15102
+ this.rejectCallWhenBusy = clientOptions?.rejectCallWhenBusy ?? false;
15193
15103
  this.streamClient = createCoordinatorClient(apiKey, clientOptions);
15194
15104
  this.writeableStateStore = new StreamVideoWriteableStateStore();
15195
15105
  this.readOnlyStateStore = new StreamVideoReadOnlyStateStore(this.writeableStateStore);