@stream-io/video-client 1.27.1 → 1.27.3
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 +12 -0
- package/dist/index.browser.es.js +79 -69
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +79 -69
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +79 -69
- package/dist/index.es.js.map +1 -1
- package/dist/src/StreamSfuClient.d.ts +0 -1
- package/dist/src/coordinator/connection/client.d.ts +1 -5
- package/dist/src/devices/devices.d.ts +0 -7
- package/dist/src/devices/index.d.ts +1 -0
- package/dist/src/devices/utils.d.ts +7 -0
- package/dist/src/rtc/signal.d.ts +2 -0
- package/package.json +1 -1
- package/src/Call.ts +6 -0
- package/src/StreamSfuClient.ts +22 -16
- package/src/coordinator/connection/client.ts +27 -46
- package/src/devices/BrowserPermission.ts +1 -1
- package/src/devices/devices.ts +0 -18
- package/src/devices/index.ts +1 -0
- package/src/devices/utils.ts +17 -0
- package/src/rtc/signal.ts +10 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [1.27.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.27.2...@stream-io/video-client-1.27.3) (2025-08-07)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- extended telemetry data for the signal websocket ([#1881](https://github.com/GetStream/stream-video-js/issues/1881)) ([984703d](https://github.com/GetStream/stream-video-js/commit/984703dabb8c6189eaf4d6925421568f6d0fd7fc))
|
|
10
|
+
|
|
11
|
+
## [1.27.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.27.1...@stream-io/video-client-1.27.2) (2025-08-05)
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
- improved logging and tracing ([#1874](https://github.com/GetStream/stream-video-js/issues/1874)) ([e450ce2](https://github.com/GetStream/stream-video-js/commit/e450ce2a294d6f80480fcc709591c13d9ede79e4))
|
|
16
|
+
|
|
5
17
|
## [1.27.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.27.0...@stream-io/video-client-1.27.1) (2025-07-25)
|
|
6
18
|
|
|
7
19
|
### Bug Fixes
|
package/dist/index.browser.es.js
CHANGED
|
@@ -5914,7 +5914,7 @@ const aggregate = (stats) => {
|
|
|
5914
5914
|
return report;
|
|
5915
5915
|
};
|
|
5916
5916
|
|
|
5917
|
-
const version = "1.27.
|
|
5917
|
+
const version = "1.27.3";
|
|
5918
5918
|
const [major, minor, patch] = version.split('.');
|
|
5919
5919
|
let sdkInfo = {
|
|
5920
5920
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -7633,19 +7633,22 @@ class Subscriber extends BasePeerConnection {
|
|
|
7633
7633
|
}
|
|
7634
7634
|
|
|
7635
7635
|
const createWebSocketSignalChannel = (opts) => {
|
|
7636
|
-
const { endpoint, onMessage, tag } = opts;
|
|
7636
|
+
const { endpoint, onMessage, tag, tracer } = opts;
|
|
7637
7637
|
const logger = getLogger(['SfuClientWS', tag]);
|
|
7638
7638
|
logger('debug', 'Creating signaling WS channel:', endpoint);
|
|
7639
7639
|
const ws = new WebSocket(endpoint);
|
|
7640
7640
|
ws.binaryType = 'arraybuffer'; // do we need this?
|
|
7641
7641
|
ws.addEventListener('error', (e) => {
|
|
7642
7642
|
logger('error', 'Signaling WS channel error', e);
|
|
7643
|
+
tracer?.trace('signal.ws.error', e);
|
|
7643
7644
|
});
|
|
7644
7645
|
ws.addEventListener('close', (e) => {
|
|
7645
7646
|
logger('info', 'Signaling WS channel is closed', e);
|
|
7647
|
+
tracer?.trace('signal.ws.close', e);
|
|
7646
7648
|
});
|
|
7647
7649
|
ws.addEventListener('open', (e) => {
|
|
7648
7650
|
logger('info', 'Signaling WS channel is open', e);
|
|
7651
|
+
tracer?.trace('signal.ws.open', e);
|
|
7649
7652
|
});
|
|
7650
7653
|
ws.addEventListener('message', (e) => {
|
|
7651
7654
|
try {
|
|
@@ -7655,7 +7658,9 @@ const createWebSocketSignalChannel = (opts) => {
|
|
|
7655
7658
|
onMessage(message);
|
|
7656
7659
|
}
|
|
7657
7660
|
catch (err) {
|
|
7658
|
-
|
|
7661
|
+
const message = 'Failed to decode a message. Check whether the Proto models match.';
|
|
7662
|
+
logger('error', message, { event: e, error: err });
|
|
7663
|
+
tracer?.trace('signal.ws.message.error', message);
|
|
7659
7664
|
}
|
|
7660
7665
|
});
|
|
7661
7666
|
return ws;
|
|
@@ -7927,6 +7932,7 @@ class StreamSfuClient {
|
|
|
7927
7932
|
this.signalWs = createWebSocketSignalChannel({
|
|
7928
7933
|
tag: this.tag,
|
|
7929
7934
|
endpoint: `${this.credentials.server.ws_endpoint}?${new URLSearchParams(params).toString()}`,
|
|
7935
|
+
tracer: this.tracer,
|
|
7930
7936
|
onMessage: (message) => {
|
|
7931
7937
|
this.lastMessageTimestamp = new Date();
|
|
7932
7938
|
this.scheduleConnectionCheck();
|
|
@@ -7937,9 +7943,13 @@ class StreamSfuClient {
|
|
|
7937
7943
|
this.dispatcher.dispatch(message, this.tag);
|
|
7938
7944
|
},
|
|
7939
7945
|
});
|
|
7946
|
+
let timeoutId;
|
|
7940
7947
|
this.signalReady = makeSafePromise(Promise.race([
|
|
7941
7948
|
new Promise((resolve, reject) => {
|
|
7949
|
+
let didOpen = false;
|
|
7942
7950
|
const onOpen = () => {
|
|
7951
|
+
didOpen = true;
|
|
7952
|
+
clearTimeout(timeoutId);
|
|
7943
7953
|
this.signalWs.removeEventListener('open', onOpen);
|
|
7944
7954
|
resolve(this.signalWs);
|
|
7945
7955
|
};
|
|
@@ -7947,19 +7957,25 @@ class StreamSfuClient {
|
|
|
7947
7957
|
this.signalWs.addEventListener('close', (e) => {
|
|
7948
7958
|
this.handleWebSocketClose(e);
|
|
7949
7959
|
// Normally, this shouldn't have any effect, because WS should never emit 'close'
|
|
7950
|
-
// before emitting 'open'. However,
|
|
7951
|
-
// want to leave signalReady in pending state.
|
|
7952
|
-
|
|
7960
|
+
// before emitting 'open'. However, stranger things have happened, and we don't
|
|
7961
|
+
// want to leave signalReady in a pending state.
|
|
7962
|
+
const message = didOpen
|
|
7963
|
+
? `SFU WS closed: ${e.code} ${e.reason}`
|
|
7964
|
+
: `SFU WS connection can't be established: ${e.code} ${e.reason}`;
|
|
7965
|
+
this.tracer?.trace('signal.close', message);
|
|
7966
|
+
clearTimeout(timeoutId);
|
|
7967
|
+
reject(new Error(message));
|
|
7953
7968
|
});
|
|
7954
7969
|
}),
|
|
7955
7970
|
new Promise((resolve, reject) => {
|
|
7956
|
-
setTimeout(() =>
|
|
7971
|
+
timeoutId = setTimeout(() => {
|
|
7972
|
+
const message = `SFU WS connection failed to open after ${this.joinResponseTimeout}ms`;
|
|
7973
|
+
this.tracer?.trace('signal.timeout', message);
|
|
7974
|
+
reject(new Error(message));
|
|
7975
|
+
}, this.joinResponseTimeout);
|
|
7957
7976
|
}),
|
|
7958
7977
|
]));
|
|
7959
7978
|
};
|
|
7960
|
-
this.cleanUpWebSocket = () => {
|
|
7961
|
-
this.signalWs.removeEventListener('close', this.handleWebSocketClose);
|
|
7962
|
-
};
|
|
7963
7979
|
this.handleWebSocketClose = (e) => {
|
|
7964
7980
|
this.signalWs.removeEventListener('close', this.handleWebSocketClose);
|
|
7965
7981
|
getTimers().clearInterval(this.keepAliveInterval);
|
|
@@ -7971,7 +7987,7 @@ class StreamSfuClient {
|
|
|
7971
7987
|
if (this.signalWs.readyState === WebSocket.OPEN) {
|
|
7972
7988
|
this.logger('debug', `Closing SFU WS connection: ${code} - ${reason}`);
|
|
7973
7989
|
this.signalWs.close(code, `js-client: ${reason}`);
|
|
7974
|
-
this.
|
|
7990
|
+
this.signalWs.removeEventListener('close', this.handleWebSocketClose);
|
|
7975
7991
|
}
|
|
7976
7992
|
this.dispose();
|
|
7977
7993
|
};
|
|
@@ -8069,7 +8085,6 @@ class StreamSfuClient {
|
|
|
8069
8085
|
const current = this.joinResponseTask;
|
|
8070
8086
|
let timeoutId = undefined;
|
|
8071
8087
|
const unsubscribe = this.dispatcher.on('joinResponse', (joinResponse) => {
|
|
8072
|
-
this.logger('debug', 'Received joinResponse', joinResponse);
|
|
8073
8088
|
clearTimeout(timeoutId);
|
|
8074
8089
|
unsubscribe();
|
|
8075
8090
|
this.keepAlive();
|
|
@@ -8077,7 +8092,9 @@ class StreamSfuClient {
|
|
|
8077
8092
|
});
|
|
8078
8093
|
timeoutId = setTimeout(() => {
|
|
8079
8094
|
unsubscribe();
|
|
8080
|
-
|
|
8095
|
+
const message = `Waiting for "joinResponse" has timed out after ${this.joinResponseTimeout}ms`;
|
|
8096
|
+
this.tracer?.trace('joinRequestTimeout', message);
|
|
8097
|
+
current.reject(new Error(message));
|
|
8081
8098
|
}, this.joinResponseTimeout);
|
|
8082
8099
|
const joinRequest = SfuRequest.create({
|
|
8083
8100
|
requestPayload: {
|
|
@@ -9421,6 +9438,25 @@ const CallTypes = new CallTypesRegistry([
|
|
|
9421
9438
|
}),
|
|
9422
9439
|
]);
|
|
9423
9440
|
|
|
9441
|
+
/**
|
|
9442
|
+
* Deactivates MediaStream (stops and removes tracks) to be later garbage collected
|
|
9443
|
+
*
|
|
9444
|
+
* @param stream MediaStream
|
|
9445
|
+
* @returns void
|
|
9446
|
+
*/
|
|
9447
|
+
const disposeOfMediaStream = (stream) => {
|
|
9448
|
+
if (!stream.active)
|
|
9449
|
+
return;
|
|
9450
|
+
stream.getTracks().forEach((track) => {
|
|
9451
|
+
track.stop();
|
|
9452
|
+
});
|
|
9453
|
+
// @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the stream
|
|
9454
|
+
if (typeof stream.release === 'function') {
|
|
9455
|
+
// @ts-expect-error - release() is present in react-native-webrtc
|
|
9456
|
+
stream.release();
|
|
9457
|
+
}
|
|
9458
|
+
};
|
|
9459
|
+
|
|
9424
9460
|
class BrowserPermission {
|
|
9425
9461
|
constructor(permission) {
|
|
9426
9462
|
this.permission = permission;
|
|
@@ -9818,24 +9854,6 @@ const deviceIds$ = typeof navigator !== 'undefined' &&
|
|
|
9818
9854
|
typeof navigator.mediaDevices !== 'undefined'
|
|
9819
9855
|
? getDeviceChangeObserver().pipe(startWith(undefined), concatMap(() => navigator.mediaDevices.enumerateDevices()), shareReplay(1))
|
|
9820
9856
|
: undefined;
|
|
9821
|
-
/**
|
|
9822
|
-
* Deactivates MediaStream (stops and removes tracks) to be later garbage collected
|
|
9823
|
-
*
|
|
9824
|
-
* @param stream MediaStream
|
|
9825
|
-
* @returns void
|
|
9826
|
-
*/
|
|
9827
|
-
const disposeOfMediaStream = (stream) => {
|
|
9828
|
-
if (!stream.active)
|
|
9829
|
-
return;
|
|
9830
|
-
stream.getTracks().forEach((track) => {
|
|
9831
|
-
track.stop();
|
|
9832
|
-
});
|
|
9833
|
-
// @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the stream
|
|
9834
|
-
if (typeof stream.release === 'function') {
|
|
9835
|
-
// @ts-expect-error - release() is present in react-native-webrtc
|
|
9836
|
-
stream.release();
|
|
9837
|
-
}
|
|
9838
|
-
};
|
|
9839
9857
|
/**
|
|
9840
9858
|
* Resolves `default` device id into the real device id. Some browsers (notably,
|
|
9841
9859
|
* Chromium-based) report device with id `default` among audio input and output
|
|
@@ -11849,6 +11867,12 @@ class Call {
|
|
|
11849
11867
|
}
|
|
11850
11868
|
catch (err) {
|
|
11851
11869
|
this.logger('warn', `Failed to join call (${attempt})`, this.cid);
|
|
11870
|
+
if (err instanceof ErrorFromResponse && err.unrecoverable) {
|
|
11871
|
+
// if the error is unrecoverable, we should not retry as that signals
|
|
11872
|
+
// that connectivity is good, but the coordinator doesn't allow the user
|
|
11873
|
+
// to join the call due to some reason (e.g., ended call, expired token...)
|
|
11874
|
+
throw err;
|
|
11875
|
+
}
|
|
11852
11876
|
const sfuId = this.credentials?.server.edge_name || '';
|
|
11853
11877
|
const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
|
|
11854
11878
|
sfuJoinFailures.set(sfuId, failures);
|
|
@@ -14360,12 +14384,6 @@ class StreamClient {
|
|
|
14360
14384
|
response,
|
|
14361
14385
|
});
|
|
14362
14386
|
};
|
|
14363
|
-
this._logApiError = (type, url, error) => {
|
|
14364
|
-
this.logger('error', `client:${type} - Error - url: ${url}`, {
|
|
14365
|
-
url,
|
|
14366
|
-
error,
|
|
14367
|
-
});
|
|
14368
|
-
};
|
|
14369
14387
|
this.doAxiosRequest = async (type, url, data, options = {}) => {
|
|
14370
14388
|
if (!options.publicEndpoint) {
|
|
14371
14389
|
await Promise.all([
|
|
@@ -14412,27 +14430,36 @@ class StreamClient {
|
|
|
14412
14430
|
}
|
|
14413
14431
|
this._logApiResponse(type, url, response);
|
|
14414
14432
|
this.consecutiveFailures = 0;
|
|
14415
|
-
return
|
|
14433
|
+
return response.data;
|
|
14416
14434
|
}
|
|
14417
14435
|
catch (e /**TODO: generalize error types */) {
|
|
14418
14436
|
e.client_request_id = requestConfig.headers?.['x-client-request-id'];
|
|
14419
14437
|
this.consecutiveFailures += 1;
|
|
14420
|
-
|
|
14421
|
-
|
|
14422
|
-
|
|
14423
|
-
|
|
14424
|
-
|
|
14425
|
-
|
|
14426
|
-
|
|
14427
|
-
|
|
14428
|
-
|
|
14429
|
-
|
|
14438
|
+
const { response } = e;
|
|
14439
|
+
if (!response || !isErrorResponse(response)) {
|
|
14440
|
+
this.logger('error', `client:${type} url: ${url}`, e);
|
|
14441
|
+
throw e;
|
|
14442
|
+
}
|
|
14443
|
+
const { data: responseData, status } = response;
|
|
14444
|
+
const isTokenExpired = responseData.code === KnownCodes.TOKEN_EXPIRED;
|
|
14445
|
+
if (isTokenExpired && !this.tokenManager.isStatic()) {
|
|
14446
|
+
this.logger('warn', `client:${type}: url: ${url}`, response);
|
|
14447
|
+
if (this.consecutiveFailures > 1) {
|
|
14448
|
+
await sleep(retryInterval(this.consecutiveFailures));
|
|
14430
14449
|
}
|
|
14431
|
-
|
|
14450
|
+
// refresh and retry the request
|
|
14451
|
+
await this.tokenManager.loadToken();
|
|
14452
|
+
return await this.doAxiosRequest(type, url, data, options);
|
|
14432
14453
|
}
|
|
14433
14454
|
else {
|
|
14434
|
-
this.
|
|
14435
|
-
throw
|
|
14455
|
+
this.logger('error', `client:${type} url: ${url}`, response);
|
|
14456
|
+
throw new ErrorFromResponse({
|
|
14457
|
+
message: `Stream error code ${responseData.code}: ${responseData.message}`,
|
|
14458
|
+
code: responseData.code ?? null,
|
|
14459
|
+
unrecoverable: responseData.unrecoverable ?? null,
|
|
14460
|
+
response: response,
|
|
14461
|
+
status: status,
|
|
14462
|
+
});
|
|
14436
14463
|
}
|
|
14437
14464
|
}
|
|
14438
14465
|
};
|
|
@@ -14455,23 +14482,6 @@ class StreamClient {
|
|
|
14455
14482
|
params,
|
|
14456
14483
|
});
|
|
14457
14484
|
};
|
|
14458
|
-
this.errorFromResponse = (response) => {
|
|
14459
|
-
const { data, status } = response;
|
|
14460
|
-
return new ErrorFromResponse({
|
|
14461
|
-
message: `Stream error code ${data.code}: ${data.message}`,
|
|
14462
|
-
code: data.code ?? null,
|
|
14463
|
-
unrecoverable: data.unrecoverable ?? null,
|
|
14464
|
-
response: response,
|
|
14465
|
-
status: status,
|
|
14466
|
-
});
|
|
14467
|
-
};
|
|
14468
|
-
this.handleResponse = (response) => {
|
|
14469
|
-
const data = response.data;
|
|
14470
|
-
if (isErrorResponse(response)) {
|
|
14471
|
-
throw this.errorFromResponse(response);
|
|
14472
|
-
}
|
|
14473
|
-
return data;
|
|
14474
|
-
};
|
|
14475
14485
|
this.dispatchEvent = (event) => {
|
|
14476
14486
|
this.logger('debug', `Dispatching event: ${event.type}`, event);
|
|
14477
14487
|
if (!this.listeners)
|
|
@@ -14504,7 +14514,7 @@ class StreamClient {
|
|
|
14504
14514
|
this.getUserAgent = () => {
|
|
14505
14515
|
if (!this.cachedUserAgent) {
|
|
14506
14516
|
const { clientAppIdentifier = {} } = this.options;
|
|
14507
|
-
const { sdkName = 'js', sdkVersion = "1.27.
|
|
14517
|
+
const { sdkName = 'js', sdkVersion = "1.27.3", ...extras } = clientAppIdentifier;
|
|
14508
14518
|
this.cachedUserAgent = [
|
|
14509
14519
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
14510
14520
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|