livekit-client 1.9.3 → 1.9.5
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +2 -0
- package/dist/livekit-client.esm.mjs +125 -908
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts +2 -3
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +1 -2
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +1 -0
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +0 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/timers.d.ts +4 -5
- package/dist/src/room/timers.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/utils/AsyncQueue.d.ts +23 -0
- package/dist/src/utils/AsyncQueue.d.ts.map +1 -0
- package/dist/src/utils/browserParser.d.ts +10 -0
- package/dist/src/utils/browserParser.d.ts.map +1 -0
- package/dist/ts4.2/src/api/SignalClient.d.ts +2 -3
- package/dist/ts4.2/src/room/PCTransport.d.ts +1 -2
- package/dist/ts4.2/src/room/Room.d.ts +1 -0
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +0 -1
- package/dist/ts4.2/src/room/timers.d.ts +4 -5
- package/dist/ts4.2/src/utils/AsyncQueue.d.ts +23 -0
- package/dist/ts4.2/src/utils/browserParser.d.ts +10 -0
- package/package.json +11 -22
- package/src/api/SignalClient.ts +4 -5
- package/src/connectionHelper/ConnectionCheck.ts +1 -1
- package/src/room/PCTransport.ts +1 -1
- package/src/room/Room.ts +5 -2
- package/src/room/participant/LocalParticipant.ts +0 -1
- package/src/room/utils.ts +17 -16
- package/src/utils/AsyncQueue.test.ts +99 -0
- package/src/utils/AsyncQueue.ts +57 -0
- package/src/utils/browserParser.test.ts +58 -0
- package/src/utils/browserParser.ts +72 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
import { sleep } from '../room/utils';
|
2
|
+
import { AsyncQueue } from './AsyncQueue';
|
3
|
+
|
4
|
+
describe('asyncQueue', () => {
|
5
|
+
it('runs multiple tasks in order', async () => {
|
6
|
+
const queue = new AsyncQueue();
|
7
|
+
const tasksExecuted: number[] = [];
|
8
|
+
|
9
|
+
for (let i = 0; i < 5; i++) {
|
10
|
+
queue.run(async () => {
|
11
|
+
await sleep(50);
|
12
|
+
tasksExecuted.push(i);
|
13
|
+
});
|
14
|
+
}
|
15
|
+
await queue.flush();
|
16
|
+
expect(tasksExecuted).toMatchObject([0, 1, 2, 3, 4]);
|
17
|
+
});
|
18
|
+
it('runs tasks sequentially and not in parallel', async () => {
|
19
|
+
const queue = new AsyncQueue();
|
20
|
+
const results: number[] = [];
|
21
|
+
for (let i = 0; i < 5; i++) {
|
22
|
+
queue.run(async () => {
|
23
|
+
results.push(i);
|
24
|
+
await sleep(10);
|
25
|
+
results.push(i);
|
26
|
+
});
|
27
|
+
}
|
28
|
+
await queue.flush();
|
29
|
+
expect(results).toMatchObject([0, 0, 1, 1, 2, 2, 3, 3, 4, 4]);
|
30
|
+
});
|
31
|
+
it('continues executing tasks if one task throws an error', async () => {
|
32
|
+
const queue = new AsyncQueue();
|
33
|
+
|
34
|
+
let task1threw = false;
|
35
|
+
let task2Executed = false;
|
36
|
+
|
37
|
+
queue
|
38
|
+
.run(async () => {
|
39
|
+
await sleep(100);
|
40
|
+
throw Error('task 1 throws');
|
41
|
+
})
|
42
|
+
.catch(() => {
|
43
|
+
task1threw = true;
|
44
|
+
});
|
45
|
+
|
46
|
+
await queue
|
47
|
+
.run(async () => {
|
48
|
+
task2Executed = true;
|
49
|
+
})
|
50
|
+
.catch(() => {
|
51
|
+
fail('task 2 should not have thrown');
|
52
|
+
});
|
53
|
+
|
54
|
+
expect(task1threw).toBeTruthy();
|
55
|
+
expect(task2Executed).toBeTruthy();
|
56
|
+
});
|
57
|
+
it('returns the result of the task', async () => {
|
58
|
+
const queue = new AsyncQueue();
|
59
|
+
|
60
|
+
const result = await queue.run(async () => {
|
61
|
+
await sleep(10);
|
62
|
+
return 'result';
|
63
|
+
});
|
64
|
+
|
65
|
+
expect(result).toBe('result');
|
66
|
+
});
|
67
|
+
it('returns only when the enqueued task and all previous tasks have completed', async () => {
|
68
|
+
const queue = new AsyncQueue();
|
69
|
+
const tasksExecuted: number[] = [];
|
70
|
+
for (let i = 0; i < 10; i += 1) {
|
71
|
+
queue.run(async () => {
|
72
|
+
await sleep(10);
|
73
|
+
tasksExecuted.push(i);
|
74
|
+
return i;
|
75
|
+
});
|
76
|
+
}
|
77
|
+
|
78
|
+
const result = await queue.run(async () => {
|
79
|
+
await sleep(10);
|
80
|
+
tasksExecuted.push(999);
|
81
|
+
return 'result';
|
82
|
+
});
|
83
|
+
|
84
|
+
expect(result).toBe('result');
|
85
|
+
expect(tasksExecuted).toMatchObject([...new Array(10).fill(0).map((_, idx) => idx), 999]);
|
86
|
+
});
|
87
|
+
it('can handle queue sizes of up to 10_000 tasks', async () => {
|
88
|
+
const queue = new AsyncQueue();
|
89
|
+
const tasksExecuted: number[] = [];
|
90
|
+
|
91
|
+
for (let i = 0; i < 10_000; i++) {
|
92
|
+
queue.run(async () => {
|
93
|
+
tasksExecuted.push(i);
|
94
|
+
});
|
95
|
+
}
|
96
|
+
await queue.flush();
|
97
|
+
expect(tasksExecuted).toMatchObject(new Array(10_000).fill(0).map((_, idx) => idx));
|
98
|
+
});
|
99
|
+
});
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import { Mutex } from '../room/utils';
|
2
|
+
|
3
|
+
type QueueTask<T> = () => PromiseLike<T>;
|
4
|
+
|
5
|
+
enum QueueTaskStatus {
|
6
|
+
'WAITING',
|
7
|
+
'RUNNING',
|
8
|
+
'COMPLETED',
|
9
|
+
}
|
10
|
+
|
11
|
+
type QueueTaskInfo = {
|
12
|
+
id: number;
|
13
|
+
enqueuedAt: number;
|
14
|
+
executedAt?: number;
|
15
|
+
status: QueueTaskStatus;
|
16
|
+
};
|
17
|
+
|
18
|
+
export class AsyncQueue {
|
19
|
+
private pendingTasks: Map<number, QueueTaskInfo>;
|
20
|
+
|
21
|
+
private taskMutex: Mutex;
|
22
|
+
|
23
|
+
private nextTaskIndex: number;
|
24
|
+
|
25
|
+
constructor() {
|
26
|
+
this.pendingTasks = new Map();
|
27
|
+
this.taskMutex = new Mutex();
|
28
|
+
this.nextTaskIndex = 0;
|
29
|
+
}
|
30
|
+
|
31
|
+
async run<T>(task: QueueTask<T>) {
|
32
|
+
const taskInfo: QueueTaskInfo = {
|
33
|
+
id: this.nextTaskIndex++,
|
34
|
+
enqueuedAt: Date.now(),
|
35
|
+
status: QueueTaskStatus.WAITING,
|
36
|
+
};
|
37
|
+
this.pendingTasks.set(taskInfo.id, taskInfo);
|
38
|
+
const unlock = await this.taskMutex.lock();
|
39
|
+
try {
|
40
|
+
taskInfo.executedAt = Date.now();
|
41
|
+
taskInfo.status = QueueTaskStatus.RUNNING;
|
42
|
+
return await task();
|
43
|
+
} finally {
|
44
|
+
taskInfo.status = QueueTaskStatus.COMPLETED;
|
45
|
+
this.pendingTasks.delete(taskInfo.id);
|
46
|
+
unlock();
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
async flush() {
|
51
|
+
return this.run(async () => {});
|
52
|
+
}
|
53
|
+
|
54
|
+
snapshot() {
|
55
|
+
return Array.from(this.pendingTasks.values());
|
56
|
+
}
|
57
|
+
}
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import { compareVersions } from '../room/utils';
|
2
|
+
import { getBrowser } from './browserParser';
|
3
|
+
|
4
|
+
describe('browser parser', () => {
|
5
|
+
const safariUA =
|
6
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.3 Safari/605.1.15';
|
7
|
+
const firefoxUA =
|
8
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/112.0';
|
9
|
+
|
10
|
+
const chromeUA =
|
11
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36';
|
12
|
+
|
13
|
+
const braveUA =
|
14
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36';
|
15
|
+
|
16
|
+
it('parses Safari correctly', () => {
|
17
|
+
const details = getBrowser(safariUA, true);
|
18
|
+
expect(details?.name).toBe('Safari');
|
19
|
+
expect(details?.version).toBe('16.3');
|
20
|
+
});
|
21
|
+
it('parses Firefox correctly', () => {
|
22
|
+
const details = getBrowser(firefoxUA, true);
|
23
|
+
expect(details?.name).toBe('Firefox');
|
24
|
+
expect(details?.version).toBe('112.0');
|
25
|
+
});
|
26
|
+
it('parses Chrome correctly', () => {
|
27
|
+
const details = getBrowser(chromeUA, true);
|
28
|
+
expect(details?.name).toBe('Chrome');
|
29
|
+
expect(details?.version).toBe('112.0.0.0');
|
30
|
+
});
|
31
|
+
it('detects brave as chromium based', () => {
|
32
|
+
const details = getBrowser(braveUA, true);
|
33
|
+
expect(details?.name).toBe('Chrome');
|
34
|
+
expect(details?.version).toBe('103.0.5060.134');
|
35
|
+
});
|
36
|
+
});
|
37
|
+
|
38
|
+
describe('version compare', () => {
|
39
|
+
it('compares versions correctly', () => {
|
40
|
+
expect(compareVersions('12.3.5', '11.8.9')).toBe(1);
|
41
|
+
expect(compareVersions('12.3.5', '12.3.5')).toBe(0);
|
42
|
+
expect(compareVersions('12.3.5', '14.1.5')).toBe(-1);
|
43
|
+
});
|
44
|
+
it('can handle different version lengths', () => {
|
45
|
+
expect(compareVersions('12', '11.8.9')).toBe(1);
|
46
|
+
expect(compareVersions('12', '12.0.0')).toBe(0);
|
47
|
+
expect(compareVersions('12', '14.1.5')).toBe(-1);
|
48
|
+
|
49
|
+
expect(compareVersions('12.3.5', '11')).toBe(1);
|
50
|
+
expect(compareVersions('12.0.0', '12')).toBe(0);
|
51
|
+
expect(compareVersions('12.3.5', '14')).toBe(-1);
|
52
|
+
});
|
53
|
+
|
54
|
+
it('treats empty strings as smaller', () => {
|
55
|
+
expect(compareVersions('12', '')).toBe(1);
|
56
|
+
expect(compareVersions('', '12.0.0')).toBe(-1);
|
57
|
+
});
|
58
|
+
});
|
@@ -0,0 +1,72 @@
|
|
1
|
+
// tiny, simplified version of https://github.com/lancedikson/bowser/blob/master/src/parser-browsers.js
|
2
|
+
// reduced to only differentiate Chrome(ium) based browsers / Firefox / Safari
|
3
|
+
|
4
|
+
const commonVersionIdentifier = /version\/(\d+(\.?_?\d+)+)/i;
|
5
|
+
|
6
|
+
export type DetectableBrowser = 'Chrome' | 'Firefox' | 'Safari';
|
7
|
+
|
8
|
+
export type BrowserDetails = {
|
9
|
+
name: DetectableBrowser;
|
10
|
+
version: string;
|
11
|
+
};
|
12
|
+
|
13
|
+
let browserDetails: BrowserDetails | undefined;
|
14
|
+
|
15
|
+
/**
|
16
|
+
* @internal
|
17
|
+
*/
|
18
|
+
export function getBrowser(userAgent?: string, force = true) {
|
19
|
+
if (
|
20
|
+
userAgent === undefined &&
|
21
|
+
(typeof document !== 'undefined' || typeof navigator === 'undefined')
|
22
|
+
) {
|
23
|
+
return;
|
24
|
+
}
|
25
|
+
const ua = (userAgent ?? navigator.userAgent).toLowerCase();
|
26
|
+
if (browserDetails === undefined || force) {
|
27
|
+
const browser = browsersList.find(({ test }) => test.test(ua));
|
28
|
+
browserDetails = browser?.describe(ua);
|
29
|
+
}
|
30
|
+
return browserDetails;
|
31
|
+
}
|
32
|
+
|
33
|
+
const browsersList = [
|
34
|
+
{
|
35
|
+
test: /firefox|iceweasel|fxios/i,
|
36
|
+
describe(ua: string) {
|
37
|
+
const browser: BrowserDetails = {
|
38
|
+
name: 'Firefox',
|
39
|
+
version: getMatch(/(?:firefox|iceweasel|fxios)[\s/](\d+(\.?_?\d+)+)/i, ua),
|
40
|
+
};
|
41
|
+
return browser;
|
42
|
+
},
|
43
|
+
},
|
44
|
+
{
|
45
|
+
test: /chrom|crios|crmo/i,
|
46
|
+
describe(ua: string) {
|
47
|
+
const browser: BrowserDetails = {
|
48
|
+
name: 'Chrome',
|
49
|
+
version: getMatch(/(?:chrome|chromium|crios|crmo)\/(\d+(\.?_?\d+)+)/i, ua),
|
50
|
+
};
|
51
|
+
|
52
|
+
return browser;
|
53
|
+
},
|
54
|
+
},
|
55
|
+
/* Safari */
|
56
|
+
{
|
57
|
+
test: /safari|applewebkit/i,
|
58
|
+
describe(ua: string) {
|
59
|
+
const browser: BrowserDetails = {
|
60
|
+
name: 'Safari',
|
61
|
+
version: getMatch(commonVersionIdentifier, ua),
|
62
|
+
};
|
63
|
+
|
64
|
+
return browser;
|
65
|
+
},
|
66
|
+
},
|
67
|
+
];
|
68
|
+
|
69
|
+
function getMatch(exp: RegExp, ua: string, id = 1) {
|
70
|
+
const match = ua.match(exp);
|
71
|
+
return (match && match.length >= id && match[id]) || '';
|
72
|
+
}
|