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