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