@stream-io/video-client 1.11.12 → 1.11.13
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 +7 -0
- package/dist/index.browser.es.js +166 -23
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +166 -23
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +166 -23
- package/dist/index.es.js.map +1 -1
- package/dist/src/coordinator/connection/connection.d.ts +1 -1
- package/dist/src/coordinator/connection/types.d.ts +5 -0
- package/dist/src/timers/index.d.ts +22 -0
- package/dist/src/timers/types.d.ts +12 -0
- package/dist/src/timers/worker.build.d.ts +3 -0
- package/dist/src/timers/worker.d.ts +1 -0
- package/package.json +4 -3
- package/src/StreamSfuClient.ts +6 -4
- package/src/StreamVideoClient.ts +4 -0
- package/src/coordinator/connection/connection.ts +14 -5
- package/src/coordinator/connection/types.ts +6 -0
- package/src/timers/index.ts +137 -0
- package/src/timers/types.ts +15 -0
- package/src/timers/worker.build.ts +26 -0
- package/src/timers/worker.ts +40 -0
|
@@ -23,7 +23,7 @@ export declare class StableWSConnection {
|
|
|
23
23
|
private connectionOpenSafe?;
|
|
24
24
|
consecutiveFailures: number;
|
|
25
25
|
pingInterval: number;
|
|
26
|
-
healthCheckTimeoutRef?:
|
|
26
|
+
healthCheckTimeoutRef?: number;
|
|
27
27
|
isConnecting: boolean;
|
|
28
28
|
isDisconnected: boolean;
|
|
29
29
|
isHealthy: boolean;
|
|
@@ -119,6 +119,11 @@ export type StreamClientOptions = Partial<AxiosRequestConfig> & {
|
|
|
119
119
|
* In Node.js environment, you can use the `ws` package.
|
|
120
120
|
*/
|
|
121
121
|
WebSocketImpl?: typeof WebSocket;
|
|
122
|
+
/**
|
|
123
|
+
* Create Web Worker to initiate timer events like health checks. Can possibly prevent
|
|
124
|
+
* timer throttling issues in inactive browser tabs.
|
|
125
|
+
*/
|
|
126
|
+
expertimental_enableTimerWorker?: boolean;
|
|
122
127
|
};
|
|
123
128
|
export type TokenProvider = () => Promise<string>;
|
|
124
129
|
export type TokenOrProvider = null | string | TokenProvider | undefined;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
declare class TimerWorker {
|
|
2
|
+
private currentTimerId;
|
|
3
|
+
private callbacks;
|
|
4
|
+
private worker;
|
|
5
|
+
private fallback;
|
|
6
|
+
setup({ useTimerWorker }?: {
|
|
7
|
+
useTimerWorker?: boolean;
|
|
8
|
+
}): void;
|
|
9
|
+
destroy(): void;
|
|
10
|
+
get ready(): boolean;
|
|
11
|
+
setInterval(callback: () => void, timeout: number): number;
|
|
12
|
+
clearInterval(id?: number): void;
|
|
13
|
+
setTimeout(callback: () => void, timeout: number): number;
|
|
14
|
+
clearTimeout(id?: number): void;
|
|
15
|
+
private setTimer;
|
|
16
|
+
private clearTimer;
|
|
17
|
+
private getTimerId;
|
|
18
|
+
private sendMessage;
|
|
19
|
+
}
|
|
20
|
+
export declare const enableTimerWorker: () => void;
|
|
21
|
+
export declare const getTimers: () => TimerWorker;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-io/video-client",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.13",
|
|
4
4
|
"packageManager": "yarn@3.2.4",
|
|
5
5
|
"main": "dist/index.cjs.js",
|
|
6
6
|
"module": "dist/index.es.js",
|
|
@@ -11,12 +11,13 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"clean": "rimraf dist",
|
|
13
13
|
"start": "rollup -w -c",
|
|
14
|
-
"build": "yarn clean && rollup -c",
|
|
14
|
+
"build": "yarn clean && ./generate-timer-worker.sh && rollup -c",
|
|
15
15
|
"test": "vitest",
|
|
16
16
|
"clean:docs": "rimraf generated-docs",
|
|
17
17
|
"test-ci": "vitest --coverage",
|
|
18
18
|
"generate:open-api": "./generate-openapi.sh protocol",
|
|
19
|
-
"generate:open-api:dev": "./generate-openapi.sh chat"
|
|
19
|
+
"generate:open-api:dev": "./generate-openapi.sh chat",
|
|
20
|
+
"generate:timer-worker": "./generate-timer-worker.sh"
|
|
20
21
|
},
|
|
21
22
|
"files": [
|
|
22
23
|
"dist",
|
package/src/StreamSfuClient.ts
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
makeSafePromise,
|
|
36
36
|
SafePromise,
|
|
37
37
|
} from './helpers/promise';
|
|
38
|
+
import { getTimers } from './timers';
|
|
38
39
|
|
|
39
40
|
export type StreamSfuClientConstructor = {
|
|
40
41
|
/**
|
|
@@ -111,7 +112,7 @@ export class StreamSfuClient {
|
|
|
111
112
|
isLeaving = false;
|
|
112
113
|
|
|
113
114
|
private readonly rpc: SignalServerClient;
|
|
114
|
-
private keepAliveInterval?:
|
|
115
|
+
private keepAliveInterval?: number;
|
|
115
116
|
private connectionCheckTimeout?: NodeJS.Timeout;
|
|
116
117
|
private migrateAwayTimeout?: NodeJS.Timeout;
|
|
117
118
|
private pingIntervalInMs = 10 * 1000;
|
|
@@ -262,7 +263,7 @@ export class StreamSfuClient {
|
|
|
262
263
|
|
|
263
264
|
private handleWebSocketClose = () => {
|
|
264
265
|
this.signalWs.removeEventListener('close', this.handleWebSocketClose);
|
|
265
|
-
clearInterval(this.keepAliveInterval);
|
|
266
|
+
getTimers().clearInterval(this.keepAliveInterval);
|
|
266
267
|
clearTimeout(this.connectionCheckTimeout);
|
|
267
268
|
this.onSignalClose?.();
|
|
268
269
|
};
|
|
@@ -488,8 +489,9 @@ export class StreamSfuClient {
|
|
|
488
489
|
};
|
|
489
490
|
|
|
490
491
|
private keepAlive = () => {
|
|
491
|
-
|
|
492
|
-
this.keepAliveInterval
|
|
492
|
+
const timers = getTimers();
|
|
493
|
+
timers.clearInterval(this.keepAliveInterval);
|
|
494
|
+
this.keepAliveInterval = timers.setInterval(() => {
|
|
493
495
|
this.ping().catch((e) => {
|
|
494
496
|
this.logger('error', 'Error sending healthCheckRequest to SFU', e);
|
|
495
497
|
});
|
package/src/StreamVideoClient.ts
CHANGED
|
@@ -31,6 +31,7 @@ import { getLogger, logToConsole, setLogger } from './logger';
|
|
|
31
31
|
import { getSdkInfo } from './client-details';
|
|
32
32
|
import { SdkType } from './gen/video/sfu/models/models';
|
|
33
33
|
import { withoutConcurrency } from './helpers/concurrency';
|
|
34
|
+
import { enableTimerWorker } from './timers';
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* A `StreamVideoClient` instance lets you communicate with our API, and authenticate users.
|
|
@@ -86,9 +87,12 @@ export class StreamVideoClient {
|
|
|
86
87
|
if (typeof apiKeyOrArgs === 'string') {
|
|
87
88
|
logLevel = opts?.logLevel || logLevel;
|
|
88
89
|
logger = opts?.logger || logger;
|
|
90
|
+
if (opts?.expertimental_enableTimerWorker) enableTimerWorker();
|
|
89
91
|
} else {
|
|
90
92
|
logLevel = apiKeyOrArgs.options?.logLevel || logLevel;
|
|
91
93
|
logger = apiKeyOrArgs.options?.logger || logger;
|
|
94
|
+
if (apiKeyOrArgs.options?.expertimental_enableTimerWorker)
|
|
95
|
+
enableTimerWorker();
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
setLogger(logger, logLevel);
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
WSAuthMessage,
|
|
16
16
|
} from '../../gen/coordinator';
|
|
17
17
|
import { makeSafePromise, type SafePromise } from '../../helpers/promise';
|
|
18
|
+
import { getTimers } from '../../timers';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* StableWSConnection - A WS connection that reconnects upon failure.
|
|
@@ -39,7 +40,7 @@ export class StableWSConnection {
|
|
|
39
40
|
private connectionOpenSafe?: SafePromise<ConnectedEvent>;
|
|
40
41
|
consecutiveFailures: number;
|
|
41
42
|
pingInterval: number;
|
|
42
|
-
healthCheckTimeoutRef?:
|
|
43
|
+
healthCheckTimeoutRef?: number;
|
|
43
44
|
isConnecting: boolean;
|
|
44
45
|
isDisconnected: boolean;
|
|
45
46
|
isHealthy: boolean;
|
|
@@ -224,8 +225,12 @@ export class StableWSConnection {
|
|
|
224
225
|
this.isDisconnected = true;
|
|
225
226
|
|
|
226
227
|
// start by removing all the listeners
|
|
227
|
-
|
|
228
|
-
|
|
228
|
+
if (this.healthCheckTimeoutRef) {
|
|
229
|
+
getTimers().clearInterval(this.healthCheckTimeoutRef);
|
|
230
|
+
}
|
|
231
|
+
if (this.connectionCheckTimeoutRef) {
|
|
232
|
+
clearInterval(this.connectionCheckTimeoutRef);
|
|
233
|
+
}
|
|
229
234
|
|
|
230
235
|
removeConnectionEventListeners(this.onlineStatusChanged);
|
|
231
236
|
|
|
@@ -689,9 +694,13 @@ export class StableWSConnection {
|
|
|
689
694
|
* Schedules a next health check ping for websocket.
|
|
690
695
|
*/
|
|
691
696
|
scheduleNextPing = () => {
|
|
697
|
+
const timers = getTimers();
|
|
698
|
+
if (this.healthCheckTimeoutRef) {
|
|
699
|
+
timers.clearTimeout(this.healthCheckTimeoutRef);
|
|
700
|
+
}
|
|
701
|
+
|
|
692
702
|
// 30 seconds is the recommended interval (messenger uses this)
|
|
693
|
-
|
|
694
|
-
this.healthCheckTimeoutRef = setTimeout(() => {
|
|
703
|
+
this.healthCheckTimeoutRef = timers.setTimeout(() => {
|
|
695
704
|
// send the healthcheck..., server replies with a health check event
|
|
696
705
|
const data = [{ type: 'health.check', client_id: this.client.clientID }];
|
|
697
706
|
// try to send on the connection
|
|
@@ -146,6 +146,12 @@ export type StreamClientOptions = Partial<AxiosRequestConfig> & {
|
|
|
146
146
|
* In Node.js environment, you can use the `ws` package.
|
|
147
147
|
*/
|
|
148
148
|
WebSocketImpl?: typeof WebSocket;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Create Web Worker to initiate timer events like health checks. Can possibly prevent
|
|
152
|
+
* timer throttling issues in inactive browser tabs.
|
|
153
|
+
*/
|
|
154
|
+
expertimental_enableTimerWorker?: boolean;
|
|
149
155
|
};
|
|
150
156
|
|
|
151
157
|
export type TokenProvider = () => Promise<string>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { lazy } from '../helpers/lazy';
|
|
2
|
+
import { getLogger } from '../logger';
|
|
3
|
+
import { TimerWorkerEvent, TimerWorkerRequest } from './types';
|
|
4
|
+
import { timerWorker } from './worker.build';
|
|
5
|
+
|
|
6
|
+
class TimerWorker {
|
|
7
|
+
private currentTimerId = 1;
|
|
8
|
+
private callbacks = new Map<number, () => void>();
|
|
9
|
+
private worker: Worker | undefined;
|
|
10
|
+
private fallback = false;
|
|
11
|
+
|
|
12
|
+
setup({ useTimerWorker = true }: { useTimerWorker?: boolean } = {}): void {
|
|
13
|
+
if (!useTimerWorker) {
|
|
14
|
+
this.fallback = true;
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const source = timerWorker.src;
|
|
20
|
+
const blob = new Blob([source], {
|
|
21
|
+
type: 'application/javascript; charset=utf-8',
|
|
22
|
+
});
|
|
23
|
+
const script = URL.createObjectURL(blob);
|
|
24
|
+
this.worker = new Worker(script, { name: 'str-timer-worker' });
|
|
25
|
+
this.worker.addEventListener('message', (event) => {
|
|
26
|
+
const { type, id } = event.data as TimerWorkerEvent;
|
|
27
|
+
if (type === 'tick') {
|
|
28
|
+
this.callbacks.get(id)?.();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
} catch (err: any) {
|
|
32
|
+
getLogger(['timer-worker'])('error', err);
|
|
33
|
+
this.fallback = true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
destroy(): void {
|
|
38
|
+
this.callbacks.clear();
|
|
39
|
+
this.worker?.terminate();
|
|
40
|
+
this.worker = undefined;
|
|
41
|
+
this.fallback = false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get ready() {
|
|
45
|
+
return this.fallback || Boolean(this.worker);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setInterval(callback: () => void, timeout: number): number {
|
|
49
|
+
return this.setTimer('setInterval', callback, timeout);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
clearInterval(id?: number): void {
|
|
53
|
+
this.clearTimer('clearInterval', id);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setTimeout(callback: () => void, timeout: number): number {
|
|
57
|
+
return this.setTimer('setTimeout', callback, timeout);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
clearTimeout(id?: number): void {
|
|
61
|
+
this.clearTimer('clearTimeout', id);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private setTimer(
|
|
65
|
+
type: 'setTimeout' | 'setInterval',
|
|
66
|
+
callback: () => void,
|
|
67
|
+
timeout: number,
|
|
68
|
+
) {
|
|
69
|
+
if (!this.ready) {
|
|
70
|
+
this.setup();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (this.fallback) {
|
|
74
|
+
return (type === 'setTimeout' ? setTimeout : setInterval)(
|
|
75
|
+
callback,
|
|
76
|
+
timeout,
|
|
77
|
+
) as unknown as number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const id = this.getTimerId();
|
|
81
|
+
|
|
82
|
+
this.callbacks.set(id, () => {
|
|
83
|
+
callback();
|
|
84
|
+
|
|
85
|
+
// Timeouts are one-off operations, so no need to keep callback reference
|
|
86
|
+
// after timer has fired
|
|
87
|
+
if (type === 'setTimeout') {
|
|
88
|
+
this.callbacks.delete(id);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
this.sendMessage({ type, id, timeout });
|
|
93
|
+
return id;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private clearTimer(type: 'clearTimeout' | 'clearInterval', id?: number) {
|
|
97
|
+
if (!id) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!this.ready) {
|
|
102
|
+
this.setup();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (this.fallback) {
|
|
106
|
+
(type === 'clearTimeout' ? clearTimeout : clearInterval)(id);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.callbacks.delete(id);
|
|
111
|
+
this.sendMessage({ type, id });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private getTimerId() {
|
|
115
|
+
return this.currentTimerId++;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private sendMessage(message: TimerWorkerRequest) {
|
|
119
|
+
if (!this.worker) {
|
|
120
|
+
throw new Error("Cannot use timer worker before it's set up");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
this.worker.postMessage(message);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let timerWorkerEnabled = false;
|
|
128
|
+
|
|
129
|
+
export const enableTimerWorker = () => {
|
|
130
|
+
timerWorkerEnabled = true;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const getTimers = lazy(() => {
|
|
134
|
+
const instance = new TimerWorker();
|
|
135
|
+
instance.setup({ useTimerWorker: timerWorkerEnabled });
|
|
136
|
+
return instance;
|
|
137
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type TimerWorkerRequest =
|
|
2
|
+
| {
|
|
3
|
+
type: 'setInterval' | 'setTimeout';
|
|
4
|
+
id: number;
|
|
5
|
+
timeout: number;
|
|
6
|
+
}
|
|
7
|
+
| {
|
|
8
|
+
type: 'clearInterval' | 'clearTimeout';
|
|
9
|
+
id: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type TimerWorkerEvent = {
|
|
13
|
+
type: 'tick';
|
|
14
|
+
id: number;
|
|
15
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const timerWorker = {
|
|
2
|
+
src: `var timerIdMapping = new Map();
|
|
3
|
+
self.addEventListener('message', function (event) {
|
|
4
|
+
var request = event.data;
|
|
5
|
+
switch (request.type) {
|
|
6
|
+
case 'setTimeout':
|
|
7
|
+
case 'setInterval':
|
|
8
|
+
timerIdMapping.set(request.id, (request.type === 'setTimeout' ? setTimeout : setInterval)(function () {
|
|
9
|
+
tick(request.id);
|
|
10
|
+
if (request.type === 'setTimeout') {
|
|
11
|
+
timerIdMapping.delete(request.id);
|
|
12
|
+
}
|
|
13
|
+
}, request.timeout));
|
|
14
|
+
break;
|
|
15
|
+
case 'clearTimeout':
|
|
16
|
+
case 'clearInterval':
|
|
17
|
+
(request.type === 'clearTimeout' ? clearTimeout : clearInterval)(timerIdMapping.get(request.id));
|
|
18
|
+
timerIdMapping.delete(request.id);
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
function tick(id) {
|
|
23
|
+
var message = { type: 'tick', id: id };
|
|
24
|
+
self.postMessage(message);
|
|
25
|
+
}`,
|
|
26
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
|
|
3
|
+
import type { TimerWorkerEvent, TimerWorkerRequest } from './types';
|
|
4
|
+
|
|
5
|
+
const timerIdMapping = new Map<number, NodeJS.Timeout>();
|
|
6
|
+
|
|
7
|
+
self.addEventListener('message', (event: MessageEvent) => {
|
|
8
|
+
const request = event.data as TimerWorkerRequest;
|
|
9
|
+
|
|
10
|
+
switch (request.type) {
|
|
11
|
+
case 'setTimeout':
|
|
12
|
+
case 'setInterval':
|
|
13
|
+
timerIdMapping.set(
|
|
14
|
+
request.id,
|
|
15
|
+
(request.type === 'setTimeout' ? setTimeout : setInterval)(() => {
|
|
16
|
+
tick(request.id);
|
|
17
|
+
|
|
18
|
+
if (request.type === 'setTimeout') {
|
|
19
|
+
timerIdMapping.delete(request.id);
|
|
20
|
+
}
|
|
21
|
+
}, request.timeout),
|
|
22
|
+
);
|
|
23
|
+
break;
|
|
24
|
+
|
|
25
|
+
case 'clearTimeout':
|
|
26
|
+
case 'clearInterval':
|
|
27
|
+
(request.type === 'clearTimeout' ? clearTimeout : clearInterval)(
|
|
28
|
+
timerIdMapping.get(request.id),
|
|
29
|
+
);
|
|
30
|
+
timerIdMapping.delete(request.id);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
function tick(id: number) {
|
|
36
|
+
const message: TimerWorkerEvent = { type: 'tick', id };
|
|
37
|
+
self.postMessage(message);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* eslint-enable */
|