@stream-io/video-client 1.18.2 → 1.18.4
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 +14 -0
- package/dist/index.browser.es.js +123 -114
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +123 -114
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +123 -114
- 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/package.json +1 -1
- 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/CameraManager.ts +2 -3
- package/src/rtc/Publisher.ts +0 -5
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
addConnectionEventListeners,
|
|
4
4
|
isCloseEvent,
|
|
5
5
|
KnownCodes,
|
|
6
|
-
randomId,
|
|
7
6
|
removeConnectionEventListeners,
|
|
8
7
|
retryInterval,
|
|
9
8
|
sleep,
|
|
@@ -44,19 +43,18 @@ export class StableWSConnection {
|
|
|
44
43
|
isConnecting: boolean;
|
|
45
44
|
isDisconnected: boolean;
|
|
46
45
|
isHealthy: boolean;
|
|
47
|
-
|
|
46
|
+
isConnectionOpenResolved?: boolean;
|
|
48
47
|
lastEvent: Date | null;
|
|
49
48
|
connectionCheckTimeout: number;
|
|
50
49
|
connectionCheckTimeoutRef?: NodeJS.Timeout;
|
|
51
|
-
|
|
50
|
+
rejectConnectionOpen?: (
|
|
52
51
|
reason?: Error & {
|
|
53
52
|
code?: string | number;
|
|
54
53
|
isWSFailure?: boolean;
|
|
55
54
|
StatusCode?: string | number;
|
|
56
55
|
},
|
|
57
56
|
) => void;
|
|
58
|
-
|
|
59
|
-
resolvePromise?: (value: ConnectedEvent) => void;
|
|
57
|
+
resolveConnectionOpen?: (value: ConnectedEvent) => void;
|
|
60
58
|
totalFailures: number;
|
|
61
59
|
ws?: WebSocket;
|
|
62
60
|
wsID: number;
|
|
@@ -74,7 +72,7 @@ export class StableWSConnection {
|
|
|
74
72
|
/** To avoid reconnect if client is disconnected */
|
|
75
73
|
this.isDisconnected = false;
|
|
76
74
|
/** Boolean that indicates if the connection promise is resolved */
|
|
77
|
-
this.
|
|
75
|
+
this.isConnectionOpenResolved = false;
|
|
78
76
|
/** Boolean that indicates if we have a working connection to the server */
|
|
79
77
|
this.isHealthy = false;
|
|
80
78
|
/** Incremented when a new WS connection is made */
|
|
@@ -89,9 +87,7 @@ export class StableWSConnection {
|
|
|
89
87
|
}
|
|
90
88
|
|
|
91
89
|
_log = (msg: string, extra: UR = {}, level: LogLevel = 'info') => {
|
|
92
|
-
this.client.logger(level,
|
|
93
|
-
...extra,
|
|
94
|
-
});
|
|
90
|
+
this.client.logger(level, `connection:${msg}`, extra);
|
|
95
91
|
};
|
|
96
92
|
|
|
97
93
|
setClient = (client: StreamClient) => {
|
|
@@ -285,9 +281,8 @@ export class StableWSConnection {
|
|
|
285
281
|
* @return {ConnectAPIResponse<ConnectedEvent>} Promise that completes once the first health check message is received
|
|
286
282
|
*/
|
|
287
283
|
async _connect() {
|
|
288
|
-
if (this.isConnecting) return; //
|
|
284
|
+
if (this.isConnecting) return; // ignore _connect if it's currently trying to connect
|
|
289
285
|
this.isConnecting = true;
|
|
290
|
-
this.requestID = randomId();
|
|
291
286
|
let isTokenReady = false;
|
|
292
287
|
try {
|
|
293
288
|
this._log(`_connect() - waiting for token`);
|
|
@@ -310,10 +305,7 @@ export class StableWSConnection {
|
|
|
310
305
|
}
|
|
311
306
|
this._setupConnectionPromise();
|
|
312
307
|
const wsURL = this._buildUrl();
|
|
313
|
-
this._log(`_connect() - Connecting to ${wsURL}
|
|
314
|
-
wsURL,
|
|
315
|
-
requestID: this.requestID,
|
|
316
|
-
});
|
|
308
|
+
this._log(`_connect() - Connecting to ${wsURL}`);
|
|
317
309
|
const WS = this.client.options.WebSocketImpl ?? WebSocket;
|
|
318
310
|
this.ws = new WS(wsURL);
|
|
319
311
|
this.ws.onopen = this.onopen.bind(this, this.wsID);
|
|
@@ -459,7 +451,7 @@ export class StableWSConnection {
|
|
|
459
451
|
return;
|
|
460
452
|
}
|
|
461
453
|
|
|
462
|
-
const authMessage
|
|
454
|
+
const authMessage = JSON.stringify({
|
|
463
455
|
token,
|
|
464
456
|
user_details: {
|
|
465
457
|
id: user.id,
|
|
@@ -467,9 +459,11 @@ export class StableWSConnection {
|
|
|
467
459
|
image: user.image,
|
|
468
460
|
custom: user.custom,
|
|
469
461
|
},
|
|
470
|
-
};
|
|
462
|
+
} as WSAuthMessage);
|
|
463
|
+
|
|
464
|
+
this._log(`onopen() - Sending auth message ${authMessage}`, {}, 'trace');
|
|
471
465
|
|
|
472
|
-
this.ws?.send(
|
|
466
|
+
this.ws?.send(authMessage);
|
|
473
467
|
this._log('onopen() - onopen callback', { wsID });
|
|
474
468
|
};
|
|
475
469
|
|
|
@@ -485,10 +479,14 @@ export class StableWSConnection {
|
|
|
485
479
|
// we wait till the first message before we consider the connection open.
|
|
486
480
|
// the reason for this is that auth errors and similar errors trigger a ws.onopen and immediately
|
|
487
481
|
// after that a ws.onclose.
|
|
488
|
-
if (
|
|
489
|
-
this.
|
|
482
|
+
if (
|
|
483
|
+
!this.isConnectionOpenResolved &&
|
|
484
|
+
data &&
|
|
485
|
+
data.type === 'connection.error'
|
|
486
|
+
) {
|
|
487
|
+
this.isConnectionOpenResolved = true;
|
|
490
488
|
if (data.error) {
|
|
491
|
-
this.
|
|
489
|
+
this.rejectConnectionOpen?.(this._errorFromWSEvent(data, false));
|
|
492
490
|
return;
|
|
493
491
|
}
|
|
494
492
|
}
|
|
@@ -505,7 +503,7 @@ export class StableWSConnection {
|
|
|
505
503
|
}
|
|
506
504
|
|
|
507
505
|
if (data && data.type === 'connection.ok') {
|
|
508
|
-
this.
|
|
506
|
+
this.resolveConnectionOpen?.(data);
|
|
509
507
|
this._setHealth(true);
|
|
510
508
|
}
|
|
511
509
|
|
|
@@ -554,7 +552,7 @@ export class StableWSConnection {
|
|
|
554
552
|
// @ts-expect-error
|
|
555
553
|
error.target = event.target;
|
|
556
554
|
|
|
557
|
-
this.
|
|
555
|
+
this.rejectConnectionOpen?.(error);
|
|
558
556
|
this._log(`onclose() - WS connection reject with error ${event.reason}`, {
|
|
559
557
|
event,
|
|
560
558
|
});
|
|
@@ -564,7 +562,7 @@ export class StableWSConnection {
|
|
|
564
562
|
this._setHealth(false);
|
|
565
563
|
this.isConnecting = false;
|
|
566
564
|
|
|
567
|
-
this.
|
|
565
|
+
this.rejectConnectionOpen?.(this._errorFromWSEvent(event));
|
|
568
566
|
|
|
569
567
|
this._log(`onclose() - WS connection closed. Calling reconnect ...`, {
|
|
570
568
|
event,
|
|
@@ -582,7 +580,7 @@ export class StableWSConnection {
|
|
|
582
580
|
this.totalFailures += 1;
|
|
583
581
|
this._setHealth(false);
|
|
584
582
|
this.isConnecting = false;
|
|
585
|
-
this.
|
|
583
|
+
this.rejectConnectionOpen?.(new Error(`WebSocket error: ${event}`));
|
|
586
584
|
this._log(`onerror() - WS connection resulted into error`, { event });
|
|
587
585
|
|
|
588
586
|
this._reconnect();
|
|
@@ -676,12 +674,12 @@ export class StableWSConnection {
|
|
|
676
674
|
* _setupPromise - sets up the this.connectOpen promise
|
|
677
675
|
*/
|
|
678
676
|
_setupConnectionPromise = () => {
|
|
679
|
-
this.
|
|
677
|
+
this.isConnectionOpenResolved = false;
|
|
680
678
|
/** a promise that is resolved once ws.open is called */
|
|
681
679
|
this.connectionOpenSafe = makeSafePromise(
|
|
682
680
|
new Promise<ConnectedEvent>((resolve, reject) => {
|
|
683
|
-
this.
|
|
684
|
-
this.
|
|
681
|
+
this.resolveConnectionOpen = resolve;
|
|
682
|
+
this.rejectConnectionOpen = reject;
|
|
685
683
|
}),
|
|
686
684
|
);
|
|
687
685
|
};
|
|
@@ -8,8 +8,8 @@ import type { TokenOrProvider, UserWithId } from './types';
|
|
|
8
8
|
* Handles all the operations around user token.
|
|
9
9
|
*/
|
|
10
10
|
export class TokenManager {
|
|
11
|
-
private loadTokenPromise: Promise<string> | null;
|
|
12
|
-
private type: 'static' | 'provider';
|
|
11
|
+
private loadTokenPromise: Promise<string> | null = null;
|
|
12
|
+
private type: 'static' | 'provider' = 'static';
|
|
13
13
|
private readonly secret?: string;
|
|
14
14
|
private token?: string;
|
|
15
15
|
private tokenProvider?: TokenOrProvider;
|
|
@@ -17,9 +17,7 @@ export class TokenManager {
|
|
|
17
17
|
private isAnonymous?: boolean;
|
|
18
18
|
|
|
19
19
|
constructor(secret?: string) {
|
|
20
|
-
this.loadTokenPromise = null;
|
|
21
20
|
this.secret = secret;
|
|
22
|
-
this.type = 'static';
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
/**
|
|
@@ -74,11 +72,7 @@ export class TokenManager {
|
|
|
74
72
|
throw new Error('User token can not be empty');
|
|
75
73
|
}
|
|
76
74
|
|
|
77
|
-
if (
|
|
78
|
-
tokenOrProvider &&
|
|
79
|
-
typeof tokenOrProvider !== 'string' &&
|
|
80
|
-
!isFunction(tokenOrProvider)
|
|
81
|
-
) {
|
|
75
|
+
if (typeof tokenOrProvider !== 'string' && !isFunction(tokenOrProvider)) {
|
|
82
76
|
throw new Error('User token should either be a string or a function');
|
|
83
77
|
}
|
|
84
78
|
|
|
@@ -157,6 +157,23 @@ export type StreamClientOptions = Partial<AxiosRequestConfig> & {
|
|
|
157
157
|
* The client app identifier.
|
|
158
158
|
*/
|
|
159
159
|
clientAppIdentifier?: ClientAppIdentifier;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The default timeout for WebSocket connections.
|
|
163
|
+
*/
|
|
164
|
+
defaultWsTimeout?: number;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The maximum number of retries to connect a user.
|
|
168
|
+
*/
|
|
169
|
+
maxConnectUserRetries?: number;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* A callback to be called one the maxUserConnectRetries is exhausted.
|
|
173
|
+
* @param lastError the last error.
|
|
174
|
+
* @param allErrors all errors.
|
|
175
|
+
*/
|
|
176
|
+
onConnectUserError?: (lastError: Error, allErrors: Error[]) => void;
|
|
160
177
|
};
|
|
161
178
|
|
|
162
179
|
export type ClientAppIdentifier = {
|
|
@@ -19,8 +19,6 @@ export function isFunction<T>(value: Function | T): value is Function {
|
|
|
19
19
|
export const KnownCodes = {
|
|
20
20
|
TOKEN_EXPIRED: 40,
|
|
21
21
|
WS_CLOSED_SUCCESS: 1000,
|
|
22
|
-
WS_CLOSED_ABRUPTLY: 1006,
|
|
23
|
-
WS_POLICY_VIOLATION: 1008,
|
|
24
22
|
};
|
|
25
23
|
|
|
26
24
|
/**
|
|
@@ -28,17 +26,13 @@ export const KnownCodes = {
|
|
|
28
26
|
*
|
|
29
27
|
* @return {number} Duration to wait in milliseconds
|
|
30
28
|
*/
|
|
31
|
-
export function retryInterval(numberOfFailures: number) {
|
|
29
|
+
export function retryInterval(numberOfFailures: number): number {
|
|
32
30
|
// try to reconnect in 0.25-5 seconds (random to spread out the load from failures)
|
|
33
31
|
const max = Math.min(500 + numberOfFailures * 2000, 5000);
|
|
34
32
|
const min = Math.min(Math.max(250, (numberOfFailures - 1) * 2000), 5000);
|
|
35
33
|
return Math.floor(Math.random() * (max - min) + min);
|
|
36
34
|
}
|
|
37
35
|
|
|
38
|
-
export function randomId() {
|
|
39
|
-
return generateUUIDv4();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
36
|
function hex(bytes: Uint8Array): string {
|
|
43
37
|
let s = '';
|
|
44
38
|
for (let i = 0; i < bytes.length; i++) {
|
|
@@ -53,38 +47,25 @@ export function generateUUIDv4() {
|
|
|
53
47
|
bytes[6] = (bytes[6] & 0x0f) | 0x40; // version
|
|
54
48
|
bytes[8] = (bytes[8] & 0xbf) | 0x80; // variant
|
|
55
49
|
|
|
56
|
-
return
|
|
57
|
-
hex(bytes.subarray(0, 4))
|
|
58
|
-
|
|
59
|
-
hex(bytes.subarray(
|
|
60
|
-
|
|
61
|
-
hex(bytes.subarray(
|
|
62
|
-
|
|
63
|
-
hex(bytes.subarray(8, 10)) +
|
|
64
|
-
'-' +
|
|
65
|
-
hex(bytes.subarray(10, 16))
|
|
66
|
-
);
|
|
50
|
+
return [
|
|
51
|
+
hex(bytes.subarray(0, 4)),
|
|
52
|
+
hex(bytes.subarray(4, 6)),
|
|
53
|
+
hex(bytes.subarray(6, 8)),
|
|
54
|
+
hex(bytes.subarray(8, 10)),
|
|
55
|
+
hex(bytes.subarray(10, 16)),
|
|
56
|
+
].join('-');
|
|
67
57
|
}
|
|
68
58
|
|
|
69
|
-
function getRandomValuesWithMathRandom(bytes: Uint8Array): void {
|
|
70
|
-
const max = Math.pow(2, (8 * bytes.byteLength) / bytes.length);
|
|
71
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
72
|
-
bytes[i] = Math.random() * max;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
declare const msCrypto: Crypto;
|
|
76
|
-
|
|
77
59
|
const getRandomValues = (() => {
|
|
78
|
-
if (
|
|
79
|
-
typeof crypto !== 'undefined' &&
|
|
80
|
-
typeof crypto?.getRandomValues !== 'undefined'
|
|
81
|
-
) {
|
|
60
|
+
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
82
61
|
return crypto.getRandomValues.bind(crypto);
|
|
83
|
-
} else if (typeof msCrypto !== 'undefined') {
|
|
84
|
-
return msCrypto.getRandomValues.bind(msCrypto);
|
|
85
|
-
} else {
|
|
86
|
-
return getRandomValuesWithMathRandom;
|
|
87
62
|
}
|
|
63
|
+
return function getRandomValuesWithMathRandom(bytes: Uint8Array): void {
|
|
64
|
+
const max = Math.pow(2, (8 * bytes.byteLength) / bytes.length);
|
|
65
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
66
|
+
bytes[i] = Math.random() * max;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
88
69
|
})();
|
|
89
70
|
|
|
90
71
|
function getRandomBytes(length: number): Uint8Array {
|
|
@@ -93,17 +74,6 @@ function getRandomBytes(length: number): Uint8Array {
|
|
|
93
74
|
return bytes;
|
|
94
75
|
}
|
|
95
76
|
|
|
96
|
-
/**
|
|
97
|
-
* Informs if a promise is yet to be resolved or rejected
|
|
98
|
-
*/
|
|
99
|
-
export async function isPromisePending<T>(promise: Promise<T>) {
|
|
100
|
-
const emptyObj = {};
|
|
101
|
-
return Promise.race([promise, emptyObj]).then(
|
|
102
|
-
(value) => value === emptyObj,
|
|
103
|
-
() => false,
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
77
|
/**
|
|
108
78
|
* listenForConnectionChanges - Adds an event listener fired on browser going online or offline
|
|
109
79
|
*/
|
|
@@ -40,10 +40,9 @@ export class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
|
|
|
40
40
|
this.logger('warn', 'No video track found to do direction selection');
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
await videoTrack.applyConstraints({
|
|
44
44
|
facingMode: direction === 'front' ? 'user' : 'environment',
|
|
45
|
-
};
|
|
46
|
-
await videoTrack.applyConstraints(constraints);
|
|
45
|
+
});
|
|
47
46
|
this.state.setDirection(direction);
|
|
48
47
|
this.state.setDevice(undefined);
|
|
49
48
|
} else {
|
package/src/rtc/Publisher.ts
CHANGED
|
@@ -227,11 +227,6 @@ export class Publisher extends BasePeerConnection {
|
|
|
227
227
|
}
|
|
228
228
|
for (const track of this.clonedTracks) {
|
|
229
229
|
this.stopTrack(track);
|
|
230
|
-
// @ts-expect-error release() is present in react-native-webrtc
|
|
231
|
-
if (track && typeof track.release === 'function') {
|
|
232
|
-
// @ts-expect-error
|
|
233
|
-
track.release();
|
|
234
|
-
}
|
|
235
230
|
}
|
|
236
231
|
};
|
|
237
232
|
|