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