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