@stream-io/video-client 1.39.2 → 1.40.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
@@ -3717,7 +3717,35 @@ const withHeaders = (headers) => {
3717
3717
  },
3718
3718
  };
3719
3719
  };
3720
- const withRequestLogger = (logger, level) => {
3720
+ const TIMEOUT_SYMBOL = '@@stream-io/timeout';
3721
+ const withTimeout = (timeoutMs, trace) => {
3722
+ const scheduleTimeout = (methodName) => {
3723
+ const controller = new AbortController();
3724
+ // aborts with specially crafted error that can be reliably recognized by
3725
+ // @protobuf-ts/twirp-transport and our internal retry logic.
3726
+ // https://github.com/timostamm/protobuf-ts/blob/657e64e80009e503e94f608fda423fbcbf4fb5a7/packages/twirp-transport/src/twirp-transport.ts#L102-L107
3727
+ const timeoutId = setTimeout(() => {
3728
+ trace?.(`${methodName}Timeout`, [timeoutMs]);
3729
+ const error = new Error(TIMEOUT_SYMBOL);
3730
+ error.name = 'AbortError';
3731
+ controller.abort(error);
3732
+ }, timeoutMs);
3733
+ return [controller.signal, () => clearTimeout(timeoutId)];
3734
+ };
3735
+ return {
3736
+ interceptUnary(next, method, input, options) {
3737
+ // respect external abort signals if provided
3738
+ if (options.abort)
3739
+ return next(method, input, options);
3740
+ // set up a custom abort signal for the RPC call
3741
+ const [signal, cancel] = scheduleTimeout(method.name);
3742
+ const invocation = next(method, input, { ...options, abort: signal });
3743
+ invocation.then(cancel, cancel);
3744
+ return invocation;
3745
+ },
3746
+ };
3747
+ };
3748
+ const withRequestLogger = (logger, level = 'trace') => {
3721
3749
  return {
3722
3750
  interceptUnary: (next, method, input, options) => {
3723
3751
  const invocation = next(method, input, options);
@@ -3736,18 +3764,21 @@ const withRequestTracer = (trace) => {
3736
3764
  const traceError = (name, input, err) => trace(`${name}OnFailure`, [err, input]);
3737
3765
  return {
3738
3766
  interceptUnary(next, method, input, options) {
3739
- const name = method.name;
3740
- if (exclusions.has(name))
3767
+ const methodName = method.name;
3768
+ if (exclusions.has(methodName))
3741
3769
  return next(method, input, options);
3742
- trace(name, input);
3770
+ const { invocationMeta: { attempt = 0 } = {} } = options;
3771
+ const traceName = attempt === 0 ? methodName : `${methodName}(${attempt})`;
3772
+ trace(traceName, input);
3743
3773
  const unaryCall = next(method, input, options);
3744
3774
  unaryCall.then((invocation) => {
3745
3775
  const response = invocation.response;
3746
3776
  if (response.error)
3747
- traceError(name, input, response.error);
3748
- if (responseInclusions.has(name))
3749
- trace(`${name}Response`, response);
3750
- }, (error) => traceError(name, input, error));
3777
+ traceError(traceName, input, response.error);
3778
+ if (responseInclusions.has(methodName)) {
3779
+ trace(`${traceName}Response`, response);
3780
+ }
3781
+ }, (error) => traceError(methodName, input, error));
3751
3782
  return unaryCall;
3752
3783
  },
3753
3784
  };
@@ -3899,22 +3930,26 @@ const videoLoggerSystem = scopedLogger.createLoggerSystem();
3899
3930
  *
3900
3931
  * @param rpc the closure around the RPC call to execute.
3901
3932
  * @param signal the signal to abort the RPC call and retries loop.
3933
+ * @param maxRetries the maximum number of retries to perform. Defaults to `Number.POSITIVE_INFINITY`.
3902
3934
  */
3903
- const retryable = async (rpc, signal) => {
3935
+ const retryable = async (rpc, signal, maxRetries = Number.POSITIVE_INFINITY) => {
3904
3936
  let attempt = 0;
3905
3937
  let result = undefined;
3906
3938
  do {
3907
3939
  if (attempt > 0)
3908
3940
  await sleep(retryInterval(attempt));
3909
3941
  try {
3910
- result = await rpc();
3942
+ result = await rpc({ attempt });
3911
3943
  }
3912
3944
  catch (err) {
3913
3945
  const isRequestCancelled = err instanceof RpcError &&
3946
+ err.message !== TIMEOUT_SYMBOL &&
3914
3947
  err.code === TwirpErrorCode[TwirpErrorCode.cancelled];
3915
3948
  const isAborted = signal?.aborted ?? false;
3916
3949
  if (isRequestCancelled || isAborted)
3917
3950
  throw err;
3951
+ if (attempt + 1 >= maxRetries)
3952
+ throw err;
3918
3953
  videoLoggerSystem
3919
3954
  .getLogger('sfu-client', { tags: ['rpc'] })
3920
3955
  .debug(`rpc failed (${attempt})`, err);
@@ -6026,7 +6061,7 @@ const getSdkVersion = (sdk) => {
6026
6061
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
6027
6062
  };
6028
6063
 
6029
- const version = "1.39.2";
6064
+ const version = "1.40.0";
6030
6065
  const [major, minor, patch] = version.split('.');
6031
6066
  let sdkInfo = {
6032
6067
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -8264,7 +8299,7 @@ class StreamSfuClient {
8264
8299
  /**
8265
8300
  * Constructs a new SFU client.
8266
8301
  */
8267
- constructor({ dispatcher, credentials, sessionId, cid, tag, joinResponseTimeout = 5000, onSignalClose, streamClient, enableTracing, }) {
8302
+ constructor({ dispatcher, credentials, sessionId, cid, tag, joinResponseTimeout = 5000, rpcRequestTimeout = 5000, onSignalClose, streamClient, enableTracing, }) {
8268
8303
  /**
8269
8304
  * A buffer for ICE Candidates that are received before
8270
8305
  * the Publisher and Subscriber Peer Connections are ready to handle them.
@@ -8393,27 +8428,27 @@ class StreamSfuClient {
8393
8428
  };
8394
8429
  this.updateSubscriptions = async (tracks) => {
8395
8430
  await this.joinTask;
8396
- return retryable(() => this.rpc.updateSubscriptions({ sessionId: this.sessionId, tracks }), this.abortController.signal);
8431
+ return retryable((invocationMeta) => this.rpc.updateSubscriptions({ sessionId: this.sessionId, tracks }, { invocationMeta }), this.abortController.signal);
8397
8432
  };
8398
8433
  this.setPublisher = async (data) => {
8399
8434
  await this.joinTask;
8400
- return retryable(() => this.rpc.setPublisher({ ...data, sessionId: this.sessionId }), this.abortController.signal);
8435
+ return retryable((invocationMeta) => this.rpc.setPublisher({ ...data, sessionId: this.sessionId }, { invocationMeta }), this.abortController.signal, 3);
8401
8436
  };
8402
8437
  this.sendAnswer = async (data) => {
8403
8438
  await this.joinTask;
8404
- return retryable(() => this.rpc.sendAnswer({ ...data, sessionId: this.sessionId }), this.abortController.signal);
8439
+ return retryable((invocationMeta) => this.rpc.sendAnswer({ ...data, sessionId: this.sessionId }, { invocationMeta }), this.abortController.signal);
8405
8440
  };
8406
8441
  this.iceTrickle = async (data) => {
8407
8442
  await this.joinTask;
8408
- return retryable(() => this.rpc.iceTrickle({ ...data, sessionId: this.sessionId }), this.abortController.signal);
8443
+ return retryable((invocationMeta) => this.rpc.iceTrickle({ ...data, sessionId: this.sessionId }, { invocationMeta }), this.abortController.signal);
8409
8444
  };
8410
8445
  this.iceRestart = async (data) => {
8411
8446
  await this.joinTask;
8412
- return retryable(() => this.rpc.iceRestart({ ...data, sessionId: this.sessionId }), this.abortController.signal);
8447
+ return retryable((invocationMeta) => this.rpc.iceRestart({ ...data, sessionId: this.sessionId }, { invocationMeta }), this.abortController.signal);
8413
8448
  };
8414
8449
  this.updateMuteStates = async (muteStates) => {
8415
8450
  await this.joinTask;
8416
- return retryable(() => this.rpc.updateMuteStates({ muteStates, sessionId: this.sessionId }), this.abortController.signal);
8451
+ return retryable((invocationMeta) => this.rpc.updateMuteStates({ muteStates, sessionId: this.sessionId }, { invocationMeta }), this.abortController.signal);
8417
8452
  };
8418
8453
  this.sendStats = async (stats) => {
8419
8454
  await this.joinTask;
@@ -8422,11 +8457,11 @@ class StreamSfuClient {
8422
8457
  };
8423
8458
  this.startNoiseCancellation = async () => {
8424
8459
  await this.joinTask;
8425
- return retryable(() => this.rpc.startNoiseCancellation({ sessionId: this.sessionId }), this.abortController.signal);
8460
+ return retryable((invocationMeta) => this.rpc.startNoiseCancellation({ sessionId: this.sessionId }, { invocationMeta }), this.abortController.signal);
8426
8461
  };
8427
8462
  this.stopNoiseCancellation = async () => {
8428
8463
  await this.joinTask;
8429
- return retryable(() => this.rpc.stopNoiseCancellation({ sessionId: this.sessionId }), this.abortController.signal);
8464
+ return retryable((invocationMeta) => this.rpc.stopNoiseCancellation({ sessionId: this.sessionId }, { invocationMeta }), this.abortController.signal);
8430
8465
  };
8431
8466
  this.enterMigration = async (opts = {}) => {
8432
8467
  this.isLeaving = true;
@@ -8551,8 +8586,8 @@ class StreamSfuClient {
8551
8586
  interceptors: [
8552
8587
  withHeaders({ Authorization: `Bearer ${token}` }),
8553
8588
  this.tracer && withRequestTracer(this.tracer.trace),
8554
- this.logger.getLogLevel() === 'trace' &&
8555
- withRequestLogger(this.logger, 'trace'),
8589
+ this.logger.getLogLevel() === 'trace' && withRequestLogger(this.logger),
8590
+ withTimeout(rpcRequestTimeout, this.tracer?.trace),
8556
8591
  ].filter((v) => !!v),
8557
8592
  });
8558
8593
  // Special handling for the ICETrickle kind of events.
@@ -12362,12 +12397,14 @@ class Call {
12362
12397
  *
12363
12398
  * @returns a promise which resolves once the call join-flow has finished.
12364
12399
  */
12365
- this.join = async ({ maxJoinRetries = 3, ...data } = {}) => {
12400
+ this.join = async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, ...data } = {}) => {
12366
12401
  await this.setup();
12367
12402
  const callingState = this.state.callingState;
12368
12403
  if ([CallingState.JOINED, CallingState.JOINING].includes(callingState)) {
12369
12404
  throw new Error(`Illegal State: call.join() shall be called only once`);
12370
12405
  }
12406
+ this.joinResponseTimeout = joinResponseTimeout;
12407
+ this.rpcRequestTimeout = rpcRequestTimeout;
12371
12408
  // we will count the number of join failures per SFU.
12372
12409
  // once the number of failures reaches 2, we will piggyback on the `migrating_from`
12373
12410
  // field to force the coordinator to provide us another SFU
@@ -12448,6 +12485,8 @@ class Call {
12448
12485
  credentials: this.credentials,
12449
12486
  streamClient: this.streamClient,
12450
12487
  enableTracing: statsOptions.enable_rtc_stats,
12488
+ joinResponseTimeout: this.joinResponseTimeout,
12489
+ rpcRequestTimeout: this.rpcRequestTimeout,
12451
12490
  // a new session_id is necessary for the REJOIN strategy.
12452
12491
  // we use the previous session_id if available
12453
12492
  sessionId: performingRejoin ? undefined : previousSessionId,
@@ -13716,6 +13755,15 @@ class Call {
13716
13755
  },
13717
13756
  });
13718
13757
  };
13758
+ /**
13759
+ * Retrieves the call stats for the current call session in a format suitable
13760
+ * for displaying in map-like UIs.
13761
+ */
13762
+ this.getCallStatsMap = async (params = {}, callSessionId = this.state.session?.id) => {
13763
+ if (!callSessionId)
13764
+ throw new Error('callSessionId is required');
13765
+ return this.streamClient.get(`${this.streamClient.baseURL}/call_stats/${this.type}/${this.id}/${callSessionId}/map`, params);
13766
+ };
13719
13767
  /**
13720
13768
  * Sends a custom event to all call participants.
13721
13769
  *
@@ -15041,7 +15089,7 @@ class StreamClient {
15041
15089
  this.getUserAgent = () => {
15042
15090
  if (!this.cachedUserAgent) {
15043
15091
  const { clientAppIdentifier = {} } = this.options;
15044
- const { sdkName = 'js', sdkVersion = "1.39.2", ...extras } = clientAppIdentifier;
15092
+ const { sdkName = 'js', sdkVersion = "1.40.0", ...extras } = clientAppIdentifier;
15045
15093
  this.cachedUserAgent = [
15046
15094
  `stream-video-${sdkName}-v${sdkVersion}`,
15047
15095
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -15599,7 +15647,6 @@ class StreamVideoClient {
15599
15647
  return false;
15600
15648
  return this.state.calls.some((c) => c.cid !== currentCallId &&
15601
15649
  c.ringing &&
15602
- !c.isCreatedByMe &&
15603
15650
  c.state.callingState !== CallingState.IDLE &&
15604
15651
  c.state.callingState !== CallingState.LEFT &&
15605
15652
  c.state.callingState !== CallingState.RECONNECTING_FAILED);