@stream-io/video-client 1.11.11 → 1.11.12
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 +7 -0
- package/dist/index.browser.es.js +68 -52
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +68 -52
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +68 -52
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +7 -0
- package/dist/src/StreamSfuClient.d.ts +1 -3
- package/dist/src/helpers/promise.d.ts +14 -0
- package/package.json +1 -1
- package/src/Call.ts +38 -3
- package/src/StreamSfuClient.ts +27 -28
- package/src/helpers/promise.ts +44 -0
- package/dist/src/helpers/withResolvers.d.ts +0 -14
- package/src/helpers/withResolvers.ts +0 -43
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [1.11.12](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.11...@stream-io/video-client-1.11.12) (2024-12-03)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* handle timeout on SFU WS connections ([#1600](https://github.com/GetStream/stream-video-js/issues/1600)) ([5f2db7b](https://github.com/GetStream/stream-video-js/commit/5f2db7bd5cfdf57cdc04d6a6ed752f43e5b06657))
|
|
11
|
+
|
|
5
12
|
## [1.11.11](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.10...@stream-io/video-client-1.11.11) (2024-11-29)
|
|
6
13
|
|
|
7
14
|
|
package/dist/index.browser.es.js
CHANGED
|
@@ -3297,7 +3297,7 @@ const retryable = async (rpc, signal) => {
|
|
|
3297
3297
|
return result;
|
|
3298
3298
|
};
|
|
3299
3299
|
|
|
3300
|
-
const version = "1.11.
|
|
3300
|
+
const version = "1.11.12";
|
|
3301
3301
|
const [major, minor, patch] = version.split('.');
|
|
3302
3302
|
let sdkInfo = {
|
|
3303
3303
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -6374,6 +6374,32 @@ const createWebSocketSignalChannel = (opts) => {
|
|
|
6374
6374
|
return ws;
|
|
6375
6375
|
};
|
|
6376
6376
|
|
|
6377
|
+
/**
|
|
6378
|
+
* Saving a long-lived reference to a promise that can reject can be unsafe,
|
|
6379
|
+
* since rejecting the promise causes an unhandled rejection error (even if the
|
|
6380
|
+
* rejection is handled everywhere promise result is expected).
|
|
6381
|
+
*
|
|
6382
|
+
* To avoid that, we add both resolution and rejection handlers to the promise.
|
|
6383
|
+
* That way, the saved promise never rejects. A callback is provided as return
|
|
6384
|
+
* value to build a *new* promise, that resolves and rejects along with
|
|
6385
|
+
* the original promise.
|
|
6386
|
+
* @param promise Promise to wrap, which possibly rejects
|
|
6387
|
+
* @returns Callback to build a new promise, which resolves and rejects along
|
|
6388
|
+
* with the original promise
|
|
6389
|
+
*/
|
|
6390
|
+
function makeSafePromise(promise) {
|
|
6391
|
+
let isPending = true;
|
|
6392
|
+
const safePromise = promise
|
|
6393
|
+
.then((result) => ({ status: 'resolved', result }), (error) => ({ status: 'rejected', error }))
|
|
6394
|
+
.finally(() => (isPending = false));
|
|
6395
|
+
const unwrapPromise = () => safePromise.then((fulfillment) => {
|
|
6396
|
+
if (fulfillment.status === 'rejected')
|
|
6397
|
+
throw fulfillment.error;
|
|
6398
|
+
return fulfillment.result;
|
|
6399
|
+
});
|
|
6400
|
+
unwrapPromise.checkPending = () => isPending;
|
|
6401
|
+
return unwrapPromise;
|
|
6402
|
+
}
|
|
6377
6403
|
/**
|
|
6378
6404
|
* Creates a new promise with resolvers.
|
|
6379
6405
|
*
|
|
@@ -6426,7 +6452,6 @@ class StreamSfuClient {
|
|
|
6426
6452
|
this.isLeaving = false;
|
|
6427
6453
|
this.pingIntervalInMs = 10 * 1000;
|
|
6428
6454
|
this.unhealthyTimeoutInMs = this.pingIntervalInMs + 5 * 1000;
|
|
6429
|
-
this.restoreWebSocketConcurrencyTag = Symbol('recoverWebSocket');
|
|
6430
6455
|
/**
|
|
6431
6456
|
* Promise that resolves when the JoinResponse is received.
|
|
6432
6457
|
* Rejects after a certain threshold if the response is not received.
|
|
@@ -6447,28 +6472,22 @@ class StreamSfuClient {
|
|
|
6447
6472
|
},
|
|
6448
6473
|
});
|
|
6449
6474
|
this.signalWs.addEventListener('close', this.handleWebSocketClose);
|
|
6450
|
-
this.
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6475
|
+
this.signalReady = makeSafePromise(Promise.race([
|
|
6476
|
+
new Promise((resolve) => {
|
|
6477
|
+
const onOpen = () => {
|
|
6478
|
+
this.signalWs.removeEventListener('open', onOpen);
|
|
6479
|
+
resolve(this.signalWs);
|
|
6480
|
+
};
|
|
6481
|
+
this.signalWs.addEventListener('open', onOpen);
|
|
6482
|
+
}),
|
|
6483
|
+
new Promise((resolve, reject) => {
|
|
6484
|
+
setTimeout(() => reject(new Error('SFU WS connection timed out')), this.joinResponseTimeout);
|
|
6485
|
+
}),
|
|
6486
|
+
]));
|
|
6458
6487
|
};
|
|
6459
6488
|
this.cleanUpWebSocket = () => {
|
|
6460
|
-
this.signalWs.removeEventListener('error', this.restoreWebSocket);
|
|
6461
6489
|
this.signalWs.removeEventListener('close', this.handleWebSocketClose);
|
|
6462
6490
|
};
|
|
6463
|
-
this.restoreWebSocket = () => {
|
|
6464
|
-
withoutConcurrency(this.restoreWebSocketConcurrencyTag, async () => {
|
|
6465
|
-
await this.networkAvailableTask?.promise;
|
|
6466
|
-
this.logger('debug', 'Restoring SFU WS connection');
|
|
6467
|
-
this.cleanUpWebSocket();
|
|
6468
|
-
await sleep(500);
|
|
6469
|
-
this.createWebSocket();
|
|
6470
|
-
}).catch((err) => this.logger('debug', `Can't restore WS connection`, err));
|
|
6471
|
-
};
|
|
6472
6491
|
this.handleWebSocketClose = () => {
|
|
6473
6492
|
this.signalWs.removeEventListener('close', this.handleWebSocketClose);
|
|
6474
6493
|
clearInterval(this.keepAliveInterval);
|
|
@@ -6562,7 +6581,7 @@ class StreamSfuClient {
|
|
|
6562
6581
|
};
|
|
6563
6582
|
this.join = async (data) => {
|
|
6564
6583
|
// wait for the signal web socket to be ready before sending "joinRequest"
|
|
6565
|
-
await this.signalReady;
|
|
6584
|
+
await this.signalReady();
|
|
6566
6585
|
if (this.joinResponseTask.isResolved || this.joinResponseTask.isRejected) {
|
|
6567
6586
|
// we need to lock the RPC requests until we receive a JoinResponse.
|
|
6568
6587
|
// that's why we have this primitive lock mechanism.
|
|
@@ -6617,7 +6636,7 @@ class StreamSfuClient {
|
|
|
6617
6636
|
}));
|
|
6618
6637
|
};
|
|
6619
6638
|
this.send = async (message) => {
|
|
6620
|
-
await this.signalReady; // wait for the signal ws to be open
|
|
6639
|
+
await this.signalReady(); // wait for the signal ws to be open
|
|
6621
6640
|
const msgJson = SfuRequest.toJson(message);
|
|
6622
6641
|
if (this.signalWs.readyState !== WebSocket.OPEN) {
|
|
6623
6642
|
this.logger('debug', 'Signal WS is not open. Skipping message', msgJson);
|
|
@@ -9834,6 +9853,7 @@ class Call {
|
|
|
9834
9853
|
this.reconnectAttempts = 0;
|
|
9835
9854
|
this.reconnectStrategy = WebsocketReconnectStrategy.UNSPECIFIED;
|
|
9836
9855
|
this.fastReconnectDeadlineSeconds = 0;
|
|
9856
|
+
this.disconnectionTimeoutSeconds = 0;
|
|
9837
9857
|
this.lastOfflineTimestamp = 0;
|
|
9838
9858
|
// maintain the order of publishing tracks to restore them after a reconnection
|
|
9839
9859
|
// it shouldn't contain duplicates
|
|
@@ -10350,6 +10370,10 @@ class Call {
|
|
|
10350
10370
|
*/
|
|
10351
10371
|
this.handleSfuSignalClose = (sfuClient) => {
|
|
10352
10372
|
this.logger('debug', '[Reconnect] SFU signal connection closed');
|
|
10373
|
+
// SFU WS closed before we finished current join, no need to schedule reconnect
|
|
10374
|
+
// because join operation will fail
|
|
10375
|
+
if (this.state.callingState === CallingState.JOINING)
|
|
10376
|
+
return;
|
|
10353
10377
|
// normal close, no need to reconnect
|
|
10354
10378
|
if (sfuClient.isLeaving)
|
|
10355
10379
|
return;
|
|
@@ -10365,10 +10389,21 @@ class Call {
|
|
|
10365
10389
|
* @param strategy the reconnection strategy to use.
|
|
10366
10390
|
*/
|
|
10367
10391
|
this.reconnect = async (strategy) => {
|
|
10392
|
+
if (this.state.callingState === CallingState.RECONNECTING ||
|
|
10393
|
+
this.state.callingState === CallingState.RECONNECTING_FAILED)
|
|
10394
|
+
return;
|
|
10368
10395
|
return withoutConcurrency(this.reconnectConcurrencyTag, async () => {
|
|
10369
10396
|
this.logger('info', `[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[strategy]}`);
|
|
10397
|
+
let reconnectStartTime = Date.now();
|
|
10370
10398
|
this.reconnectStrategy = strategy;
|
|
10371
10399
|
do {
|
|
10400
|
+
if (this.disconnectionTimeoutSeconds > 0 &&
|
|
10401
|
+
(Date.now() - reconnectStartTime) / 1000 >
|
|
10402
|
+
this.disconnectionTimeoutSeconds) {
|
|
10403
|
+
this.logger('warn', '[Reconnect] Stopping reconnection attempts after reaching disconnection timeout');
|
|
10404
|
+
this.state.setCallingState(CallingState.RECONNECTING_FAILED);
|
|
10405
|
+
return;
|
|
10406
|
+
}
|
|
10372
10407
|
// we don't increment reconnect attempts for the FAST strategy.
|
|
10373
10408
|
if (this.reconnectStrategy !== WebsocketReconnectStrategy.FAST) {
|
|
10374
10409
|
this.reconnectAttempts++;
|
|
@@ -10468,7 +10503,7 @@ class Call {
|
|
|
10468
10503
|
const currentPublisher = this.publisher;
|
|
10469
10504
|
currentSubscriber?.detachEventHandlers();
|
|
10470
10505
|
currentPublisher?.detachEventHandlers();
|
|
10471
|
-
const migrationTask = currentSfuClient.enterMigration();
|
|
10506
|
+
const migrationTask = makeSafePromise(currentSfuClient.enterMigration());
|
|
10472
10507
|
try {
|
|
10473
10508
|
const currentSfu = currentSfuClient.edgeName;
|
|
10474
10509
|
await this.join({ ...this.joinCallData, migrating_from: currentSfu });
|
|
@@ -10484,7 +10519,7 @@ class Call {
|
|
|
10484
10519
|
// Wait for the migration to complete, then close the previous SFU client
|
|
10485
10520
|
// and the peer connection instances. In case of failure, the migration
|
|
10486
10521
|
// task would throw an error and REJOIN would be attempted.
|
|
10487
|
-
await migrationTask;
|
|
10522
|
+
await migrationTask();
|
|
10488
10523
|
// in MIGRATE, we can consider the call as joined only after
|
|
10489
10524
|
// `participantMigrationComplete` event is received, signaled by
|
|
10490
10525
|
// the `migrationTask`
|
|
@@ -11340,6 +11375,14 @@ class Call {
|
|
|
11340
11375
|
this.dynascaleManager.setVideoTrackSubscriptionOverrides(enabled ? undefined : { enabled: false });
|
|
11341
11376
|
this.dynascaleManager.applyTrackSubscriptions();
|
|
11342
11377
|
};
|
|
11378
|
+
/**
|
|
11379
|
+
* Sets the maximum amount of time a user can remain waiting for a reconnect
|
|
11380
|
+
* after a network disruption
|
|
11381
|
+
* @param timeoutSeconds Timeout in seconds, or 0 to keep reconnecting indefinetely
|
|
11382
|
+
*/
|
|
11383
|
+
this.setDisconnectionTimeout = (timeoutSeconds) => {
|
|
11384
|
+
this.disconnectionTimeoutSeconds = timeoutSeconds;
|
|
11385
|
+
};
|
|
11343
11386
|
this.type = type;
|
|
11344
11387
|
this.id = id;
|
|
11345
11388
|
this.cid = `${type}:${id}`;
|
|
@@ -11495,33 +11538,6 @@ class Call {
|
|
|
11495
11538
|
|
|
11496
11539
|
var https = null;
|
|
11497
11540
|
|
|
11498
|
-
/**
|
|
11499
|
-
* Saving a long-lived reference to a promise that can reject can be unsafe,
|
|
11500
|
-
* since rejecting the promise causes an unhandled rejection error (even if the
|
|
11501
|
-
* rejection is handled everywhere promise result is expected).
|
|
11502
|
-
*
|
|
11503
|
-
* To avoid that, we add both resolution and rejection handlers to the promise.
|
|
11504
|
-
* That way, the saved promise never rejects. A callback is provided as return
|
|
11505
|
-
* value to build a *new* promise, that resolves and rejects along with
|
|
11506
|
-
* the original promise.
|
|
11507
|
-
* @param promise Promise to wrap, which possibly rejects
|
|
11508
|
-
* @returns Callback to build a new promise, which resolves and rejects along
|
|
11509
|
-
* with the original promise
|
|
11510
|
-
*/
|
|
11511
|
-
function makeSafePromise(promise) {
|
|
11512
|
-
let isPending = true;
|
|
11513
|
-
const safePromise = promise
|
|
11514
|
-
.then((result) => ({ status: 'resolved', result }), (error) => ({ status: 'rejected', error }))
|
|
11515
|
-
.finally(() => (isPending = false));
|
|
11516
|
-
const unwrapPromise = () => safePromise.then((fulfillment) => {
|
|
11517
|
-
if (fulfillment.status === 'rejected')
|
|
11518
|
-
throw fulfillment.error;
|
|
11519
|
-
return fulfillment.result;
|
|
11520
|
-
});
|
|
11521
|
-
unwrapPromise.checkPending = () => isPending;
|
|
11522
|
-
return unwrapPromise;
|
|
11523
|
-
}
|
|
11524
|
-
|
|
11525
11541
|
/**
|
|
11526
11542
|
* StableWSConnection - A WS connection that reconnects upon failure.
|
|
11527
11543
|
* - the browser will sometimes report that you're online or offline
|
|
@@ -12620,7 +12636,7 @@ class StreamClient {
|
|
|
12620
12636
|
return await this.wsConnection.connect(this.defaultWSTimeout);
|
|
12621
12637
|
};
|
|
12622
12638
|
this.getUserAgent = () => {
|
|
12623
|
-
const version = "1.11.
|
|
12639
|
+
const version = "1.11.12";
|
|
12624
12640
|
return (this.userAgent ||
|
|
12625
12641
|
`stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
|
|
12626
12642
|
};
|