@stream-io/video-client 1.18.3 → 1.18.5
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 +17 -0
- package/dist/index.browser.es.js +139 -111
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +139 -111
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +139 -111
- package/dist/index.es.js.map +1 -1
- package/dist/src/StreamVideoClient.d.ts +5 -3
- package/dist/src/coordinator/connection/client.d.ts +3 -4
- package/dist/src/coordinator/connection/connection.d.ts +3 -4
- package/dist/src/coordinator/connection/types.d.ts +14 -0
- package/dist/src/coordinator/connection/utils.d.ts +0 -7
- package/dist/src/devices/BrowserPermission.d.ts +5 -2
- package/dist/src/devices/InputMediaDeviceManagerState.d.ts +5 -0
- package/package.json +2 -2
- package/src/StreamVideoClient.ts +85 -43
- package/src/__tests__/StreamVideoClient.test.ts +82 -6
- package/src/coordinator/connection/client.ts +17 -37
- package/src/coordinator/connection/connection.ts +26 -28
- package/src/coordinator/connection/token_manager.ts +3 -9
- package/src/coordinator/connection/types.ts +17 -0
- package/src/coordinator/connection/utils.ts +15 -45
- package/src/devices/BrowserPermission.ts +22 -8
- package/src/devices/CameraManager.ts +2 -3
- package/src/devices/InputMediaDeviceManagerState.ts +10 -0
- package/src/devices/__tests__/mocks.ts +1 -0
- package/src/helpers/concurrency.ts +4 -1
package/dist/index.es.js
CHANGED
|
@@ -3303,7 +3303,8 @@ function isFunction(value) {
|
|
|
3303
3303
|
*/
|
|
3304
3304
|
const KnownCodes = {
|
|
3305
3305
|
TOKEN_EXPIRED: 40,
|
|
3306
|
-
WS_CLOSED_SUCCESS: 1000
|
|
3306
|
+
WS_CLOSED_SUCCESS: 1000,
|
|
3307
|
+
};
|
|
3307
3308
|
/**
|
|
3308
3309
|
* retryInterval - A retry interval which increases acc to number of failures
|
|
3309
3310
|
*
|
|
@@ -3315,9 +3316,6 @@ function retryInterval(numberOfFailures) {
|
|
|
3315
3316
|
const min = Math.min(Math.max(250, (numberOfFailures - 1) * 2000), 5000);
|
|
3316
3317
|
return Math.floor(Math.random() * (max - min) + min);
|
|
3317
3318
|
}
|
|
3318
|
-
function randomId() {
|
|
3319
|
-
return generateUUIDv4();
|
|
3320
|
-
}
|
|
3321
3319
|
function hex(bytes) {
|
|
3322
3320
|
let s = '';
|
|
3323
3321
|
for (let i = 0; i < bytes.length; i++) {
|
|
@@ -3330,33 +3328,24 @@ function generateUUIDv4() {
|
|
|
3330
3328
|
const bytes = getRandomBytes(16);
|
|
3331
3329
|
bytes[6] = (bytes[6] & 0x0f) | 0x40; // version
|
|
3332
3330
|
bytes[8] = (bytes[8] & 0xbf) | 0x80; // variant
|
|
3333
|
-
return
|
|
3334
|
-
|
|
3335
|
-
hex(bytes.subarray(4, 6))
|
|
3336
|
-
|
|
3337
|
-
hex(bytes.subarray(
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
'-' +
|
|
3341
|
-
hex(bytes.subarray(10, 16)));
|
|
3342
|
-
}
|
|
3343
|
-
function getRandomValuesWithMathRandom(bytes) {
|
|
3344
|
-
const max = Math.pow(2, (8 * bytes.byteLength) / bytes.length);
|
|
3345
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
3346
|
-
bytes[i] = Math.random() * max;
|
|
3347
|
-
}
|
|
3331
|
+
return [
|
|
3332
|
+
hex(bytes.subarray(0, 4)),
|
|
3333
|
+
hex(bytes.subarray(4, 6)),
|
|
3334
|
+
hex(bytes.subarray(6, 8)),
|
|
3335
|
+
hex(bytes.subarray(8, 10)),
|
|
3336
|
+
hex(bytes.subarray(10, 16)),
|
|
3337
|
+
].join('-');
|
|
3348
3338
|
}
|
|
3349
3339
|
const getRandomValues = (() => {
|
|
3350
|
-
if (typeof crypto !== 'undefined' &&
|
|
3351
|
-
typeof crypto?.getRandomValues !== 'undefined') {
|
|
3340
|
+
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
3352
3341
|
return crypto.getRandomValues.bind(crypto);
|
|
3353
3342
|
}
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
}
|
|
3343
|
+
return function getRandomValuesWithMathRandom(bytes) {
|
|
3344
|
+
const max = Math.pow(2, (8 * bytes.byteLength) / bytes.length);
|
|
3345
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
3346
|
+
bytes[i] = Math.random() * max;
|
|
3347
|
+
}
|
|
3348
|
+
};
|
|
3360
3349
|
})();
|
|
3361
3350
|
function getRandomBytes(length) {
|
|
3362
3351
|
const bytes = new Uint8Array(length);
|
|
@@ -3657,7 +3646,10 @@ function hasPending(tag) {
|
|
|
3657
3646
|
return pendingPromises.has(tag);
|
|
3658
3647
|
}
|
|
3659
3648
|
async function settled(tag) {
|
|
3660
|
-
|
|
3649
|
+
let pending;
|
|
3650
|
+
while ((pending = pendingPromises.get(tag))) {
|
|
3651
|
+
await pending.promise;
|
|
3652
|
+
}
|
|
3661
3653
|
}
|
|
3662
3654
|
/**
|
|
3663
3655
|
* Implements common functionality of running async functions serially, by chaining
|
|
@@ -7466,7 +7458,7 @@ const aggregate = (stats) => {
|
|
|
7466
7458
|
return report;
|
|
7467
7459
|
};
|
|
7468
7460
|
|
|
7469
|
-
const version = "1.18.
|
|
7461
|
+
const version = "1.18.5";
|
|
7470
7462
|
const [major, minor, patch] = version.split('.');
|
|
7471
7463
|
let sdkInfo = {
|
|
7472
7464
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -8400,6 +8392,7 @@ class BrowserPermission {
|
|
|
8400
8392
|
}
|
|
8401
8393
|
try {
|
|
8402
8394
|
this.wasPrompted = true;
|
|
8395
|
+
this.setState('prompting');
|
|
8403
8396
|
const stream = await navigator.mediaDevices.getUserMedia(this.permission.constraints);
|
|
8404
8397
|
disposeOfMediaStream(stream);
|
|
8405
8398
|
this.setState('granted');
|
|
@@ -8423,6 +8416,7 @@ class BrowserPermission {
|
|
|
8423
8416
|
error: e,
|
|
8424
8417
|
permission: this.permission,
|
|
8425
8418
|
});
|
|
8419
|
+
this.setState('prompt');
|
|
8426
8420
|
throw e;
|
|
8427
8421
|
}
|
|
8428
8422
|
});
|
|
@@ -8434,13 +8428,19 @@ class BrowserPermission {
|
|
|
8434
8428
|
return () => this.listeners.delete(cb);
|
|
8435
8429
|
}
|
|
8436
8430
|
asObservable() {
|
|
8437
|
-
return
|
|
8431
|
+
return this.getStateObservable().pipe(
|
|
8438
8432
|
// In some browsers, the 'change' event doesn't reliably emit and hence,
|
|
8439
8433
|
// permissionState stays in 'prompt' state forever.
|
|
8440
8434
|
// Typically, this happens when a user grants one-time permission.
|
|
8441
8435
|
// Instead of checking if a permission is granted, we check if it isn't denied
|
|
8442
8436
|
map((state) => state !== 'denied'));
|
|
8443
8437
|
}
|
|
8438
|
+
getIsPromptingObservable() {
|
|
8439
|
+
return this.getStateObservable().pipe(map((state) => state === 'prompting'));
|
|
8440
|
+
}
|
|
8441
|
+
getStateObservable() {
|
|
8442
|
+
return fromEventPattern((handler) => this.listen(handler), (handler, unlisten) => unlisten());
|
|
8443
|
+
}
|
|
8444
8444
|
setState(state) {
|
|
8445
8445
|
if (this.state !== state) {
|
|
8446
8446
|
this.state = state;
|
|
@@ -9196,6 +9196,9 @@ class InputMediaDeviceManagerState {
|
|
|
9196
9196
|
this.hasBrowserPermission$ = permission
|
|
9197
9197
|
? permission.asObservable().pipe(shareReplay(1))
|
|
9198
9198
|
: of(true);
|
|
9199
|
+
this.isPromptingPermission$ = permission
|
|
9200
|
+
? permission.getIsPromptingObservable().pipe(shareReplay(1))
|
|
9201
|
+
: of(false);
|
|
9199
9202
|
}
|
|
9200
9203
|
/**
|
|
9201
9204
|
* The device status
|
|
@@ -9345,10 +9348,9 @@ class CameraManager extends InputMediaDeviceManager {
|
|
|
9345
9348
|
this.logger('warn', 'No video track found to do direction selection');
|
|
9346
9349
|
return;
|
|
9347
9350
|
}
|
|
9348
|
-
|
|
9351
|
+
await videoTrack.applyConstraints({
|
|
9349
9352
|
facingMode: direction === 'front' ? 'user' : 'environment',
|
|
9350
|
-
};
|
|
9351
|
-
await videoTrack.applyConstraints(constraints);
|
|
9353
|
+
});
|
|
9352
9354
|
this.state.setDirection(direction);
|
|
9353
9355
|
this.state.setDevice(undefined);
|
|
9354
9356
|
}
|
|
@@ -12010,9 +12012,7 @@ class Call {
|
|
|
12010
12012
|
class StableWSConnection {
|
|
12011
12013
|
constructor(client) {
|
|
12012
12014
|
this._log = (msg, extra = {}, level = 'info') => {
|
|
12013
|
-
this.client.logger(level,
|
|
12014
|
-
...extra,
|
|
12015
|
-
});
|
|
12015
|
+
this.client.logger(level, `connection:${msg}`, extra);
|
|
12016
12016
|
};
|
|
12017
12017
|
this.setClient = (client) => {
|
|
12018
12018
|
this.client = client;
|
|
@@ -12066,7 +12066,7 @@ class StableWSConnection {
|
|
|
12066
12066
|
this.client.logger('error', `Token not set, can't connect authenticate`);
|
|
12067
12067
|
return;
|
|
12068
12068
|
}
|
|
12069
|
-
const authMessage = {
|
|
12069
|
+
const authMessage = JSON.stringify({
|
|
12070
12070
|
token,
|
|
12071
12071
|
user_details: {
|
|
12072
12072
|
id: user.id,
|
|
@@ -12074,8 +12074,9 @@ class StableWSConnection {
|
|
|
12074
12074
|
image: user.image,
|
|
12075
12075
|
custom: user.custom,
|
|
12076
12076
|
},
|
|
12077
|
-
};
|
|
12078
|
-
this.
|
|
12077
|
+
});
|
|
12078
|
+
this._log(`onopen() - Sending auth message ${authMessage}`, {}, 'trace');
|
|
12079
|
+
this.ws?.send(authMessage);
|
|
12079
12080
|
this._log('onopen() - onopen callback', { wsID });
|
|
12080
12081
|
};
|
|
12081
12082
|
this.onmessage = (wsID, event) => {
|
|
@@ -12088,10 +12089,12 @@ class StableWSConnection {
|
|
|
12088
12089
|
// we wait till the first message before we consider the connection open.
|
|
12089
12090
|
// the reason for this is that auth errors and similar errors trigger a ws.onopen and immediately
|
|
12090
12091
|
// after that a ws.onclose.
|
|
12091
|
-
if (!this.
|
|
12092
|
-
|
|
12092
|
+
if (!this.isConnectionOpenResolved &&
|
|
12093
|
+
data &&
|
|
12094
|
+
data.type === 'connection.error') {
|
|
12095
|
+
this.isConnectionOpenResolved = true;
|
|
12093
12096
|
if (data.error) {
|
|
12094
|
-
this.
|
|
12097
|
+
this.rejectConnectionOpen?.(this._errorFromWSEvent(data, false));
|
|
12095
12098
|
return;
|
|
12096
12099
|
}
|
|
12097
12100
|
}
|
|
@@ -12103,7 +12106,7 @@ class StableWSConnection {
|
|
|
12103
12106
|
this.scheduleNextPing();
|
|
12104
12107
|
}
|
|
12105
12108
|
if (data && data.type === 'connection.ok') {
|
|
12106
|
-
this.
|
|
12109
|
+
this.resolveConnectionOpen?.(data);
|
|
12107
12110
|
this._setHealth(true);
|
|
12108
12111
|
}
|
|
12109
12112
|
if (data && data.type === 'connection.error' && data.error) {
|
|
@@ -12140,7 +12143,7 @@ class StableWSConnection {
|
|
|
12140
12143
|
error.wasClean = event.wasClean;
|
|
12141
12144
|
// @ts-expect-error
|
|
12142
12145
|
error.target = event.target;
|
|
12143
|
-
this.
|
|
12146
|
+
this.rejectConnectionOpen?.(error);
|
|
12144
12147
|
this._log(`onclose() - WS connection reject with error ${event.reason}`, {
|
|
12145
12148
|
event,
|
|
12146
12149
|
});
|
|
@@ -12150,7 +12153,7 @@ class StableWSConnection {
|
|
|
12150
12153
|
this.totalFailures += 1;
|
|
12151
12154
|
this._setHealth(false);
|
|
12152
12155
|
this.isConnecting = false;
|
|
12153
|
-
this.
|
|
12156
|
+
this.rejectConnectionOpen?.(this._errorFromWSEvent(event));
|
|
12154
12157
|
this._log(`onclose() - WS connection closed. Calling reconnect ...`, {
|
|
12155
12158
|
event,
|
|
12156
12159
|
});
|
|
@@ -12165,7 +12168,7 @@ class StableWSConnection {
|
|
|
12165
12168
|
this.totalFailures += 1;
|
|
12166
12169
|
this._setHealth(false);
|
|
12167
12170
|
this.isConnecting = false;
|
|
12168
|
-
this.
|
|
12171
|
+
this.rejectConnectionOpen?.(new Error(`WebSocket error: ${event}`));
|
|
12169
12172
|
this._log(`onerror() - WS connection resulted into error`, { event });
|
|
12170
12173
|
this._reconnect();
|
|
12171
12174
|
};
|
|
@@ -12231,11 +12234,11 @@ class StableWSConnection {
|
|
|
12231
12234
|
* _setupPromise - sets up the this.connectOpen promise
|
|
12232
12235
|
*/
|
|
12233
12236
|
this._setupConnectionPromise = () => {
|
|
12234
|
-
this.
|
|
12237
|
+
this.isConnectionOpenResolved = false;
|
|
12235
12238
|
/** a promise that is resolved once ws.open is called */
|
|
12236
12239
|
this.connectionOpenSafe = makeSafePromise(new Promise((resolve, reject) => {
|
|
12237
|
-
this.
|
|
12238
|
-
this.
|
|
12240
|
+
this.resolveConnectionOpen = resolve;
|
|
12241
|
+
this.rejectConnectionOpen = reject;
|
|
12239
12242
|
}));
|
|
12240
12243
|
};
|
|
12241
12244
|
/**
|
|
@@ -12286,7 +12289,7 @@ class StableWSConnection {
|
|
|
12286
12289
|
/** To avoid reconnect if client is disconnected */
|
|
12287
12290
|
this.isDisconnected = false;
|
|
12288
12291
|
/** Boolean that indicates if the connection promise is resolved */
|
|
12289
|
-
this.
|
|
12292
|
+
this.isConnectionOpenResolved = false;
|
|
12290
12293
|
/** Boolean that indicates if we have a working connection to the server */
|
|
12291
12294
|
this.isHealthy = false;
|
|
12292
12295
|
/** Incremented when a new WS connection is made */
|
|
@@ -12431,9 +12434,8 @@ class StableWSConnection {
|
|
|
12431
12434
|
*/
|
|
12432
12435
|
async _connect() {
|
|
12433
12436
|
if (this.isConnecting)
|
|
12434
|
-
return; //
|
|
12437
|
+
return; // ignore _connect if it's currently trying to connect
|
|
12435
12438
|
this.isConnecting = true;
|
|
12436
|
-
this.requestID = randomId();
|
|
12437
12439
|
let isTokenReady = false;
|
|
12438
12440
|
try {
|
|
12439
12441
|
this._log(`_connect() - waiting for token`);
|
|
@@ -12453,10 +12455,7 @@ class StableWSConnection {
|
|
|
12453
12455
|
}
|
|
12454
12456
|
this._setupConnectionPromise();
|
|
12455
12457
|
const wsURL = this._buildUrl();
|
|
12456
|
-
this._log(`_connect() - Connecting to ${wsURL}
|
|
12457
|
-
wsURL,
|
|
12458
|
-
requestID: this.requestID,
|
|
12459
|
-
});
|
|
12458
|
+
this._log(`_connect() - Connecting to ${wsURL}`);
|
|
12460
12459
|
const WS = this.client.options.WebSocketImpl ?? WebSocket;
|
|
12461
12460
|
this.ws = new WS(wsURL);
|
|
12462
12461
|
this.ws.onopen = this.onopen.bind(this, this.wsID);
|
|
@@ -12600,6 +12599,8 @@ const decodeBase64 = (s) => {
|
|
|
12600
12599
|
*/
|
|
12601
12600
|
class TokenManager {
|
|
12602
12601
|
constructor(secret) {
|
|
12602
|
+
this.loadTokenPromise = null;
|
|
12603
|
+
this.type = 'static';
|
|
12603
12604
|
/**
|
|
12604
12605
|
* Set the static string token or token provider.
|
|
12605
12606
|
* Token provider should return a token string or a promise which resolves to string token.
|
|
@@ -12642,9 +12643,7 @@ class TokenManager {
|
|
|
12642
12643
|
if (!this.secret && !tokenOrProvider) {
|
|
12643
12644
|
throw new Error('User token can not be empty');
|
|
12644
12645
|
}
|
|
12645
|
-
if (tokenOrProvider &&
|
|
12646
|
-
typeof tokenOrProvider !== 'string' &&
|
|
12647
|
-
!isFunction(tokenOrProvider)) {
|
|
12646
|
+
if (typeof tokenOrProvider !== 'string' && !isFunction(tokenOrProvider)) {
|
|
12648
12647
|
throw new Error('User token should either be a string or a function');
|
|
12649
12648
|
}
|
|
12650
12649
|
if (typeof tokenOrProvider === 'string') {
|
|
@@ -12695,9 +12694,7 @@ class TokenManager {
|
|
|
12695
12694
|
throw new Error(`User token is not set. Either client.connectUser wasn't called or client.disconnect was called`);
|
|
12696
12695
|
};
|
|
12697
12696
|
this.isStatic = () => this.type === 'static';
|
|
12698
|
-
this.loadTokenPromise = null;
|
|
12699
12697
|
this.secret = secret;
|
|
12700
|
-
this.type = 'static';
|
|
12701
12698
|
}
|
|
12702
12699
|
}
|
|
12703
12700
|
|
|
@@ -12766,11 +12763,11 @@ class StreamClient {
|
|
|
12766
12763
|
* connectUser - Set the current user and open a WebSocket connection
|
|
12767
12764
|
*
|
|
12768
12765
|
* @param user Data about this user. IE {name: "john"}
|
|
12769
|
-
* @param {TokenOrProvider}
|
|
12766
|
+
* @param {TokenOrProvider} tokenOrProvider Token or provider
|
|
12770
12767
|
*
|
|
12771
12768
|
* @return {ConnectAPIResponse} Returns a promise that resolves when the connection is setup
|
|
12772
12769
|
*/
|
|
12773
|
-
this.connectUser = async (user,
|
|
12770
|
+
this.connectUser = async (user, tokenOrProvider) => {
|
|
12774
12771
|
if (!user.id) {
|
|
12775
12772
|
throw new Error('The "id" field on the user is missing');
|
|
12776
12773
|
}
|
|
@@ -12778,9 +12775,9 @@ class StreamClient {
|
|
|
12778
12775
|
* Calling connectUser multiple times is potentially the result of a bad integration, however,
|
|
12779
12776
|
* If the user id remains the same we don't throw error
|
|
12780
12777
|
*/
|
|
12781
|
-
if (this.userID === user.id && this.
|
|
12778
|
+
if (this.userID === user.id && this.connectUserTask) {
|
|
12782
12779
|
this.logger('warn', 'Consecutive calls to connectUser is detected, ideally you should only call this function once in your app.');
|
|
12783
|
-
return this.
|
|
12780
|
+
return this.connectUserTask;
|
|
12784
12781
|
}
|
|
12785
12782
|
if (this.userID) {
|
|
12786
12783
|
throw new Error('Use client.disconnect() before trying to connect as a different user. connectUser was called twice.');
|
|
@@ -12791,26 +12788,24 @@ class StreamClient {
|
|
|
12791
12788
|
// we generate the client id client side
|
|
12792
12789
|
this.userID = user.id;
|
|
12793
12790
|
this.anonymous = false;
|
|
12794
|
-
|
|
12791
|
+
await this.tokenManager.setTokenOrProvider(tokenOrProvider, user, false);
|
|
12795
12792
|
this._setUser(user);
|
|
12796
|
-
|
|
12797
|
-
this.setUserPromise = Promise.all([setTokenPromise, wsPromise]).then((result) => result[1]);
|
|
12793
|
+
this.connectUserTask = this.openConnection();
|
|
12798
12794
|
try {
|
|
12799
12795
|
addConnectionEventListeners(this.updateNetworkConnectionStatus);
|
|
12800
|
-
return await this.
|
|
12796
|
+
return await this.connectUserTask;
|
|
12801
12797
|
}
|
|
12802
12798
|
catch (err) {
|
|
12803
12799
|
if (this.persistUserOnConnectionFailure) {
|
|
12804
12800
|
// cleanup client to allow the user to retry connectUser again
|
|
12805
|
-
this.closeConnection();
|
|
12801
|
+
await this.closeConnection();
|
|
12806
12802
|
}
|
|
12807
12803
|
else {
|
|
12808
|
-
this.disconnectUser();
|
|
12804
|
+
await this.disconnectUser();
|
|
12809
12805
|
}
|
|
12810
12806
|
throw err;
|
|
12811
12807
|
}
|
|
12812
12808
|
};
|
|
12813
|
-
this._setToken = (user, userTokenOrProvider, isAnonymous) => this.tokenManager.setTokenOrProvider(userTokenOrProvider, user, isAnonymous);
|
|
12814
12809
|
this._setUser = (user) => {
|
|
12815
12810
|
/**
|
|
12816
12811
|
* This one is used by the frontend. This is a copy of the current user object stored on backend.
|
|
@@ -12854,7 +12849,7 @@ class StreamClient {
|
|
|
12854
12849
|
return;
|
|
12855
12850
|
}
|
|
12856
12851
|
this._setupConnectionIdPromise();
|
|
12857
|
-
this.clientID = `${this.userID}--${
|
|
12852
|
+
this.clientID = `${this.userID}--${generateUUIDv4()}`;
|
|
12858
12853
|
const newWsPromise = this.connect();
|
|
12859
12854
|
this.wsPromiseSafe = makeSafePromise(newWsPromise);
|
|
12860
12855
|
return await newWsPromise;
|
|
@@ -12892,7 +12887,7 @@ class StreamClient {
|
|
|
12892
12887
|
addConnectionEventListeners(this.updateNetworkConnectionStatus);
|
|
12893
12888
|
this._setupConnectionIdPromise();
|
|
12894
12889
|
this.anonymous = true;
|
|
12895
|
-
await this.
|
|
12890
|
+
await this.tokenManager.setTokenOrProvider(tokenOrProvider, user, true);
|
|
12896
12891
|
this._setUser(user);
|
|
12897
12892
|
// some endpoints require a connection_id to be resolved.
|
|
12898
12893
|
// as anonymous users aren't allowed to open WS connections, we just
|
|
@@ -13100,7 +13095,7 @@ class StreamClient {
|
|
|
13100
13095
|
this.getUserAgent = () => {
|
|
13101
13096
|
if (!this.cachedUserAgent) {
|
|
13102
13097
|
const { clientAppIdentifier = {} } = this.options;
|
|
13103
|
-
const { sdkName = 'js', sdkVersion = "1.18.
|
|
13098
|
+
const { sdkName = 'js', sdkVersion = "1.18.5", ...extras } = clientAppIdentifier;
|
|
13104
13099
|
this.cachedUserAgent = [
|
|
13105
13100
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
13106
13101
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|
|
@@ -13119,7 +13114,7 @@ class StreamClient {
|
|
|
13119
13114
|
if (!options.headers?.['x-client-request-id']) {
|
|
13120
13115
|
options.headers = {
|
|
13121
13116
|
...options.headers,
|
|
13122
|
-
'x-client-request-id':
|
|
13117
|
+
'x-client-request-id': generateUUIDv4(),
|
|
13123
13118
|
};
|
|
13124
13119
|
}
|
|
13125
13120
|
return {
|
|
@@ -13190,7 +13185,7 @@ class StreamClient {
|
|
|
13190
13185
|
// WS connection is initialized when setUser is called
|
|
13191
13186
|
this.wsConnection = null;
|
|
13192
13187
|
this.wsPromiseSafe = null;
|
|
13193
|
-
this.
|
|
13188
|
+
this.connectUserTask = null;
|
|
13194
13189
|
// mapping between channel groups and configs
|
|
13195
13190
|
this.anonymous = false;
|
|
13196
13191
|
this.persistUserOnConnectionFailure =
|
|
@@ -13199,7 +13194,7 @@ class StreamClient {
|
|
|
13199
13194
|
// generated from secret.
|
|
13200
13195
|
this.tokenManager = new TokenManager(this.secret);
|
|
13201
13196
|
this.consecutiveFailures = 0;
|
|
13202
|
-
this.defaultWSTimeout = 15000;
|
|
13197
|
+
this.defaultWSTimeout = this.options.defaultWsTimeout ?? 15000;
|
|
13203
13198
|
this.logger = isFunction(inputOptions.logger)
|
|
13204
13199
|
? inputOptions.logger
|
|
13205
13200
|
: () => null;
|
|
@@ -13276,6 +13271,7 @@ const createTokenOrProvider = (options) => {
|
|
|
13276
13271
|
*/
|
|
13277
13272
|
class StreamVideoClient {
|
|
13278
13273
|
constructor(apiKeyOrArgs, opts) {
|
|
13274
|
+
this.effectsRegistered = false;
|
|
13279
13275
|
this.eventHandlersToUnregister = [];
|
|
13280
13276
|
this.connectionConcurrencyTag = Symbol('connectionConcurrencyTag');
|
|
13281
13277
|
this.registerClientInstance = (apiKey, user) => {
|
|
@@ -13285,27 +13281,9 @@ class StreamVideoClient {
|
|
|
13285
13281
|
}
|
|
13286
13282
|
StreamVideoClient._instances.set(instanceKey, this);
|
|
13287
13283
|
};
|
|
13288
|
-
|
|
13289
|
-
|
|
13290
|
-
|
|
13291
|
-
* If the connection is successful, the connected user [state variable](#readonlystatestore) will be updated accordingly.
|
|
13292
|
-
*
|
|
13293
|
-
* @param user the user to connect.
|
|
13294
|
-
* @param token a token or a function that returns a token.
|
|
13295
|
-
*/
|
|
13296
|
-
this.connectUser = async (user, token) => {
|
|
13297
|
-
if (user.type === 'anonymous') {
|
|
13298
|
-
user.id = '!anon';
|
|
13299
|
-
return this.connectAnonymousUser(user, token);
|
|
13300
|
-
}
|
|
13301
|
-
const connectUser = user.type === 'guest'
|
|
13302
|
-
? () => this.streamClient.connectGuestUser(user)
|
|
13303
|
-
: () => this.streamClient.connectUser(user, token);
|
|
13304
|
-
const connectUserResponse = await withoutConcurrency(this.connectionConcurrencyTag, () => connectUser());
|
|
13305
|
-
// connectUserResponse will be void if connectUser called twice for the same user
|
|
13306
|
-
if (connectUserResponse?.me) {
|
|
13307
|
-
this.writeableStateStore.setConnectedUser(connectUserResponse.me);
|
|
13308
|
-
}
|
|
13284
|
+
this.registerEffects = () => {
|
|
13285
|
+
if (this.effectsRegistered)
|
|
13286
|
+
return;
|
|
13309
13287
|
this.eventHandlersToUnregister.push(this.on('connection.changed', (event) => {
|
|
13310
13288
|
if (!event.online)
|
|
13311
13289
|
return;
|
|
@@ -13325,7 +13303,7 @@ class StreamVideoClient {
|
|
|
13325
13303
|
}));
|
|
13326
13304
|
this.eventHandlersToUnregister.push(this.on('call.created', (event) => {
|
|
13327
13305
|
const { call, members } = event;
|
|
13328
|
-
if (
|
|
13306
|
+
if (this.state.connectedUser?.id === call.created_by.id) {
|
|
13329
13307
|
this.logger('warn', 'Received `call.created` sent by the current user');
|
|
13330
13308
|
return;
|
|
13331
13309
|
}
|
|
@@ -13342,7 +13320,7 @@ class StreamVideoClient {
|
|
|
13342
13320
|
}));
|
|
13343
13321
|
this.eventHandlersToUnregister.push(this.on('call.ring', async (event) => {
|
|
13344
13322
|
const { call, members } = event;
|
|
13345
|
-
if (
|
|
13323
|
+
if (this.state.connectedUser?.id === call.created_by.id) {
|
|
13346
13324
|
this.logger('debug', 'Received `call.ring` sent by the current user so ignoring the event');
|
|
13347
13325
|
return;
|
|
13348
13326
|
}
|
|
@@ -13366,6 +13344,53 @@ class StreamVideoClient {
|
|
|
13366
13344
|
await newCallInstance.get();
|
|
13367
13345
|
}
|
|
13368
13346
|
}));
|
|
13347
|
+
this.effectsRegistered = true;
|
|
13348
|
+
};
|
|
13349
|
+
/**
|
|
13350
|
+
* Connects the given user to the client.
|
|
13351
|
+
* Only one user can connect at a time, if you want to change users, call `disconnectUser` before connecting a new user.
|
|
13352
|
+
* If the connection is successful, the connected user [state variable](#readonlystatestore) will be updated accordingly.
|
|
13353
|
+
*
|
|
13354
|
+
* @param user the user to connect.
|
|
13355
|
+
* @param tokenOrProvider a token or a function that returns a token.
|
|
13356
|
+
*/
|
|
13357
|
+
this.connectUser = async (user, tokenOrProvider) => {
|
|
13358
|
+
if (user.type === 'anonymous') {
|
|
13359
|
+
user.id = '!anon';
|
|
13360
|
+
return this.connectAnonymousUser(user, tokenOrProvider);
|
|
13361
|
+
}
|
|
13362
|
+
const connectUserResponse = await withoutConcurrency(this.connectionConcurrencyTag, async () => {
|
|
13363
|
+
const client = this.streamClient;
|
|
13364
|
+
const { maxConnectUserRetries = 5, onConnectUserError, persistUserOnConnectionFailure, } = client.options;
|
|
13365
|
+
const errorQueue = [];
|
|
13366
|
+
for (let attempt = 0; attempt < maxConnectUserRetries; attempt++) {
|
|
13367
|
+
try {
|
|
13368
|
+
this.logger('trace', `Connecting user (${attempt})`, user);
|
|
13369
|
+
return user.type === 'guest'
|
|
13370
|
+
? await client.connectGuestUser(user)
|
|
13371
|
+
: await client.connectUser(user, tokenOrProvider);
|
|
13372
|
+
}
|
|
13373
|
+
catch (err) {
|
|
13374
|
+
this.logger('warn', `Failed to connect a user (${attempt})`, err);
|
|
13375
|
+
errorQueue.push(err);
|
|
13376
|
+
if (attempt === maxConnectUserRetries - 1) {
|
|
13377
|
+
onConnectUserError?.(err, errorQueue);
|
|
13378
|
+
throw err;
|
|
13379
|
+
}
|
|
13380
|
+
// we need to force to disconnect the user if the client is
|
|
13381
|
+
// configured to persist the user on connection failure
|
|
13382
|
+
if (persistUserOnConnectionFailure) {
|
|
13383
|
+
await client.disconnectUser();
|
|
13384
|
+
}
|
|
13385
|
+
await sleep(retryInterval(attempt));
|
|
13386
|
+
}
|
|
13387
|
+
}
|
|
13388
|
+
});
|
|
13389
|
+
// connectUserResponse will be void if connectUser called twice for the same user
|
|
13390
|
+
if (connectUserResponse?.me) {
|
|
13391
|
+
this.writeableStateStore.setConnectedUser(connectUserResponse.me);
|
|
13392
|
+
}
|
|
13393
|
+
this.registerEffects();
|
|
13369
13394
|
return connectUserResponse;
|
|
13370
13395
|
};
|
|
13371
13396
|
/**
|
|
@@ -13377,16 +13402,19 @@ class StreamVideoClient {
|
|
|
13377
13402
|
* https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
|
13378
13403
|
*/
|
|
13379
13404
|
this.disconnectUser = async (timeout) => {
|
|
13380
|
-
|
|
13381
|
-
|
|
13382
|
-
|
|
13383
|
-
|
|
13384
|
-
|
|
13385
|
-
|
|
13386
|
-
|
|
13387
|
-
|
|
13388
|
-
|
|
13389
|
-
|
|
13405
|
+
await withoutConcurrency(this.connectionConcurrencyTag, async () => {
|
|
13406
|
+
const { user, key } = this.streamClient;
|
|
13407
|
+
if (!user)
|
|
13408
|
+
return;
|
|
13409
|
+
await this.streamClient.disconnectUser(timeout);
|
|
13410
|
+
if (user.id) {
|
|
13411
|
+
StreamVideoClient._instances.delete(getInstanceKey(key, user));
|
|
13412
|
+
}
|
|
13413
|
+
this.eventHandlersToUnregister.forEach((unregister) => unregister());
|
|
13414
|
+
this.eventHandlersToUnregister = [];
|
|
13415
|
+
this.effectsRegistered = false;
|
|
13416
|
+
this.writeableStateStore.setConnectedUser(undefined);
|
|
13417
|
+
});
|
|
13390
13418
|
};
|
|
13391
13419
|
/**
|
|
13392
13420
|
* You can subscribe to WebSocket events provided by the API.
|