@livekit/rtc-node 0.13.33 → 0.13.35
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/README.md +0 -5
- package/dist/{audio_stream-CnODFAmo.d.cts → audio_stream-BNVaONuH.d.cts} +36 -11
- package/dist/audio_stream.d.cts +2 -2
- package/dist/data_streams/index.d.cts +1 -1
- package/dist/data_streams/stream_reader.cjs +97 -58
- package/dist/data_streams/stream_reader.cjs.map +1 -1
- package/dist/data_streams/stream_reader.d.cts +1 -1
- package/dist/data_streams/stream_reader.d.ts +28 -11
- package/dist/data_streams/stream_reader.d.ts.map +1 -1
- package/dist/data_streams/stream_writer.d.cts +1 -1
- package/dist/data_streams/types.cjs.map +1 -1
- package/dist/data_streams/types.d.cts +1 -1
- package/dist/data_streams/types.d.ts +6 -0
- package/dist/data_streams/types.d.ts.map +1 -1
- package/dist/e2ee.cjs +5 -1
- package/dist/e2ee.cjs.map +1 -1
- package/dist/e2ee.d.cts +3 -1
- package/dist/e2ee.d.ts +3 -1
- package/dist/e2ee.d.ts.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/participant.cjs +178 -196
- package/dist/participant.cjs.map +1 -1
- package/dist/participant.d.cts +2 -2
- package/dist/participant.d.ts +22 -5
- package/dist/participant.d.ts.map +1 -1
- package/dist/room.cjs +166 -126
- package/dist/room.cjs.map +1 -1
- package/dist/room.d.cts +3 -3
- package/dist/room.d.ts +16 -8
- package/dist/room.d.ts.map +1 -1
- package/dist/tests/e2e_common.cjs +157 -0
- package/dist/tests/e2e_common.cjs.map +1 -0
- package/dist/tests/e2e_common.d.cts +56 -0
- package/dist/tests/e2e_common.d.ts +37 -0
- package/dist/tests/e2e_common.d.ts.map +1 -0
- package/dist/track.d.cts +2 -2
- package/dist/track_publication.d.cts +2 -2
- package/dist/{types-_PdPVish.d.cts → types-f7wTlFjs.d.cts} +34 -11
- package/dist/utils.cjs +27 -2
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +8 -1
- package/dist/utils.d.ts +6 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.cts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/video_stream.d.cts +2 -2
- package/package.json +2 -2
- package/src/audio_stream_room_lifecycle.test.ts +4 -1
- package/src/data_streams/stream_reader.ts +132 -82
- package/src/data_streams/types.ts +6 -0
- package/src/e2ee.ts +7 -0
- package/src/participant.ts +251 -228
- package/src/room.ts +233 -144
- package/src/tests/e2e.test.ts +50 -139
- package/src/tests/e2e_common.ts +163 -0
- package/src/tests/e2e_data_streams.test.ts +639 -0
- package/src/utils.ts +30 -0
- package/src/version.ts +1 -1
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
import type { DataStream_Chunk } from '@livekit/rtc-ffi-bindings';
|
|
5
5
|
import { log } from '../log.js';
|
|
6
|
-
import { bigIntToNumber } from '../utils.js';
|
|
7
6
|
import type { BaseStreamInfo, ByteStreamInfo, TextStreamInfo } from './types.js';
|
|
8
7
|
|
|
9
8
|
abstract class BaseStreamReader<T extends BaseStreamInfo> {
|
|
@@ -15,6 +14,12 @@ abstract class BaseStreamReader<T extends BaseStreamInfo> {
|
|
|
15
14
|
|
|
16
15
|
protected bytesReceived: number;
|
|
17
16
|
|
|
17
|
+
private closed = false;
|
|
18
|
+
|
|
19
|
+
// The reader held by an in-progress iteration. Cancelling has to go through
|
|
20
|
+
// it, since it holds the stream's lock.
|
|
21
|
+
private activeReader: ReadableStreamDefaultReader<DataStream_Chunk> | null = null;
|
|
22
|
+
|
|
18
23
|
get info() {
|
|
19
24
|
return this._info;
|
|
20
25
|
}
|
|
@@ -26,7 +31,68 @@ abstract class BaseStreamReader<T extends BaseStreamInfo> {
|
|
|
26
31
|
this.bytesReceived = 0;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
protected
|
|
34
|
+
protected handleChunkReceived(chunk: DataStream_Chunk) {
|
|
35
|
+
this.bytesReceived += chunk.content!.byteLength;
|
|
36
|
+
const currentProgress = this.totalByteSize
|
|
37
|
+
? this.bytesReceived / this.totalByteSize
|
|
38
|
+
: undefined;
|
|
39
|
+
this.onProgress?.(currentProgress);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Takes the stream's reader for an iteration, remembering it so that
|
|
43
|
+
* close() can cancel a read that is still in flight. */
|
|
44
|
+
protected acquireReader(): ReadableStreamDefaultReader<DataStream_Chunk> {
|
|
45
|
+
const reader = this.reader.getReader();
|
|
46
|
+
this.activeReader = reader;
|
|
47
|
+
return reader;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Releases the stream lock after an iteration ended on its own — because a
|
|
51
|
+
* read reported end-of-stream, or because the stream itself errored. Either
|
|
52
|
+
* way the stream is in a terminal state and the FFI subscription behind it
|
|
53
|
+
* was already dropped by that terminal event, so there is nothing to cancel.
|
|
54
|
+
*
|
|
55
|
+
* An error raised *around* a read rather than by it — a chunk that failed to
|
|
56
|
+
* process — leaves the stream live, so it has to go through close() instead. */
|
|
57
|
+
protected finishIteration(reader: ReadableStreamDefaultReader<DataStream_Chunk>) {
|
|
58
|
+
this.closed = true;
|
|
59
|
+
if (this.activeReader === reader) {
|
|
60
|
+
this.activeReader = null;
|
|
61
|
+
}
|
|
62
|
+
reader.releaseLock();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Stops receiving this stream and releases the resources behind it.
|
|
67
|
+
*
|
|
68
|
+
* A reader is subscribed to FFI events from the moment it is handed to a
|
|
69
|
+
* stream handler, and only unsubscribes once a read consumes the stream's
|
|
70
|
+
* end-of-stream event. So a reader that is abandoned part-way through, or
|
|
71
|
+
* that a handler never reads at all, has to be closed here or its
|
|
72
|
+
* subscription lives for the rest of the process. Reading to completion — or
|
|
73
|
+
* breaking out of a `for await` — closes the reader for you, and closing an
|
|
74
|
+
* already-closed reader is a no-op.
|
|
75
|
+
*/
|
|
76
|
+
async close(): Promise<void> {
|
|
77
|
+
if (this.closed) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.closed = true;
|
|
81
|
+
const active = this.activeReader;
|
|
82
|
+
this.activeReader = null;
|
|
83
|
+
try {
|
|
84
|
+
if (active) {
|
|
85
|
+
await active.cancel();
|
|
86
|
+
active.releaseLock();
|
|
87
|
+
} else {
|
|
88
|
+
await this.reader.cancel();
|
|
89
|
+
}
|
|
90
|
+
} catch (error: unknown) {
|
|
91
|
+
// The stream was already closed or errored (e.g. the room disconnected
|
|
92
|
+
// mid-stream), in which case its subscription is already gone.
|
|
93
|
+
log.debug('error closing stream reader: %s', error);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
30
96
|
|
|
31
97
|
onProgress?: (progress: number | undefined) => void;
|
|
32
98
|
|
|
@@ -37,41 +103,48 @@ abstract class BaseStreamReader<T extends BaseStreamInfo> {
|
|
|
37
103
|
* A class to read chunks from a ReadableStream and provide them in a structured format.
|
|
38
104
|
*/
|
|
39
105
|
export class ByteStreamReader extends BaseStreamReader<ByteStreamInfo> {
|
|
40
|
-
protected handleChunkReceived(chunk: DataStream_Chunk) {
|
|
41
|
-
this.bytesReceived += chunk.content!.byteLength;
|
|
42
|
-
const currentProgress = this.totalByteSize
|
|
43
|
-
? this.bytesReceived / this.totalByteSize
|
|
44
|
-
: undefined;
|
|
45
|
-
this.onProgress?.(currentProgress);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
106
|
[Symbol.asyncIterator]() {
|
|
49
|
-
const reader = this.
|
|
107
|
+
const reader = this.acquireReader();
|
|
50
108
|
|
|
51
109
|
return {
|
|
52
110
|
next: async (): Promise<IteratorResult<Uint8Array>> => {
|
|
111
|
+
let read: Awaited<ReturnType<typeof reader.read>>;
|
|
53
112
|
try {
|
|
54
|
-
|
|
55
|
-
if (done) {
|
|
56
|
-
// Release the lock when the stream is exhausted so the
|
|
57
|
-
// underlying ReadableStream can be garbage-collected.
|
|
58
|
-
reader.releaseLock();
|
|
59
|
-
return { done: true, value: undefined as unknown };
|
|
60
|
-
} else {
|
|
61
|
-
this.handleChunkReceived(value);
|
|
62
|
-
return { done: false, value: value.content! };
|
|
63
|
-
}
|
|
113
|
+
read = await reader.read();
|
|
64
114
|
} catch (error: unknown) {
|
|
65
|
-
//
|
|
66
|
-
// consumer never calls return() (
|
|
67
|
-
|
|
68
|
-
|
|
115
|
+
// The stream errored, which is terminal: release the lock so it
|
|
116
|
+
// doesn't stay held when the consumer never calls return() (a
|
|
117
|
+
// rejecting next() doesn't trigger it).
|
|
118
|
+
this.finishIteration(reader);
|
|
119
|
+
log.error('error reading stream: %s', error);
|
|
120
|
+
// Propagate abnormal termination (e.g. remote abort, payload over
|
|
121
|
+
// the receiver's size limit) instead of presenting the truncated
|
|
122
|
+
// payload as a clean EOF.
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (read.done) {
|
|
127
|
+
// Release the lock when the stream is exhausted so the
|
|
128
|
+
// underlying ReadableStream can be garbage-collected.
|
|
129
|
+
this.finishIteration(reader);
|
|
69
130
|
return { done: true, value: undefined as unknown };
|
|
70
131
|
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
this.handleChunkReceived(read.value);
|
|
135
|
+
return { done: false, value: read.value.content! };
|
|
136
|
+
} catch (error: unknown) {
|
|
137
|
+
// The chunk arrived fine but handling it threw (e.g. a consumer's
|
|
138
|
+
// onProgress callback). The stream is still live, so it has to be
|
|
139
|
+
// cancelled to release its FFI subscription.
|
|
140
|
+
await this.close();
|
|
141
|
+
log.error('error processing stream update: %s', error);
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
71
144
|
},
|
|
72
145
|
|
|
73
|
-
return(): IteratorResult<Uint8Array
|
|
74
|
-
|
|
146
|
+
return: async (): Promise<IteratorResult<Uint8Array>> => {
|
|
147
|
+
await this.close();
|
|
75
148
|
return { done: true, value: undefined };
|
|
76
149
|
},
|
|
77
150
|
};
|
|
@@ -90,77 +163,54 @@ export class ByteStreamReader extends BaseStreamReader<ByteStreamInfo> {
|
|
|
90
163
|
* A class to read chunks from a ReadableStream and provide them in a structured format.
|
|
91
164
|
*/
|
|
92
165
|
export class TextStreamReader extends BaseStreamReader<TextStreamInfo> {
|
|
93
|
-
private receivedChunks: Map<number, DataStream_Chunk>;
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* A TextStreamReader instance can be used as an AsyncIterator that returns the entire string
|
|
97
|
-
* that has been received up to the current point in time.
|
|
98
|
-
*/
|
|
99
|
-
constructor(
|
|
100
|
-
info: TextStreamInfo,
|
|
101
|
-
stream: ReadableStream<DataStream_Chunk>,
|
|
102
|
-
totalChunkCount?: number,
|
|
103
|
-
) {
|
|
104
|
-
super(info, stream, totalChunkCount);
|
|
105
|
-
this.receivedChunks = new Map();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
protected handleChunkReceived(chunk: DataStream_Chunk) {
|
|
109
|
-
const index = bigIntToNumber(chunk.chunkIndex!);
|
|
110
|
-
const previousChunkAtIndex = this.receivedChunks.get(index!);
|
|
111
|
-
if (previousChunkAtIndex && previousChunkAtIndex.version! > chunk.version!) {
|
|
112
|
-
// we have a newer version already, dropping the old one
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
this.receivedChunks.set(index, chunk);
|
|
116
|
-
const currentProgress = this.totalByteSize
|
|
117
|
-
? this.receivedChunks.size / this.totalByteSize
|
|
118
|
-
: undefined;
|
|
119
|
-
this.onProgress?.(currentProgress);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
166
|
/**
|
|
123
167
|
* Async iterator implementation to allow usage of `for await...of` syntax.
|
|
124
168
|
* Yields structured chunks from the stream.
|
|
125
169
|
*
|
|
126
170
|
*/
|
|
127
171
|
[Symbol.asyncIterator]() {
|
|
128
|
-
const reader = this.
|
|
172
|
+
const reader = this.acquireReader();
|
|
129
173
|
const decoder = new TextDecoder();
|
|
130
|
-
const receivedChunks = this.receivedChunks;
|
|
131
174
|
|
|
132
175
|
return {
|
|
133
176
|
next: async (): Promise<IteratorResult<string>> => {
|
|
177
|
+
let read: Awaited<ReturnType<typeof reader.read>>;
|
|
134
178
|
try {
|
|
135
|
-
|
|
136
|
-
if (done) {
|
|
137
|
-
// Release the lock when the stream is exhausted so the
|
|
138
|
-
// underlying ReadableStream can be garbage-collected.
|
|
139
|
-
reader.releaseLock();
|
|
140
|
-
// Clear received chunks so the buffered data can be GC'd.
|
|
141
|
-
receivedChunks.clear();
|
|
142
|
-
return { done: true, value: undefined };
|
|
143
|
-
} else {
|
|
144
|
-
this.handleChunkReceived(value);
|
|
145
|
-
return {
|
|
146
|
-
done: false,
|
|
147
|
-
value: decoder.decode(value.content!),
|
|
148
|
-
};
|
|
149
|
-
}
|
|
179
|
+
read = await reader.read();
|
|
150
180
|
} catch (error: unknown) {
|
|
151
|
-
//
|
|
152
|
-
// consumer never calls return() (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
log.error('error
|
|
181
|
+
// The stream errored, which is terminal: release the lock so it
|
|
182
|
+
// doesn't stay held when the consumer never calls return() (a
|
|
183
|
+
// rejecting next() doesn't trigger it).
|
|
184
|
+
this.finishIteration(reader);
|
|
185
|
+
log.error('error reading stream: %s', error);
|
|
186
|
+
// Propagate abnormal termination (e.g. remote abort, payload over
|
|
187
|
+
// the receiver's size limit) instead of presenting the truncated
|
|
188
|
+
// payload as a clean EOF.
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (read.done) {
|
|
193
|
+
// Release the lock when the stream is exhausted so the
|
|
194
|
+
// underlying ReadableStream can be garbage-collected.
|
|
195
|
+
this.finishIteration(reader);
|
|
156
196
|
return { done: true, value: undefined };
|
|
157
197
|
}
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
this.handleChunkReceived(read.value);
|
|
201
|
+
return { done: false, value: decoder.decode(read.value.content!) };
|
|
202
|
+
} catch (error: unknown) {
|
|
203
|
+
// The chunk arrived fine but handling it threw (e.g. a consumer's
|
|
204
|
+
// onProgress callback). The stream is still live, so it has to be
|
|
205
|
+
// cancelled to release its FFI subscription.
|
|
206
|
+
await this.close();
|
|
207
|
+
log.error('error processing stream update: %s', error);
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
158
210
|
},
|
|
159
211
|
|
|
160
|
-
return(): IteratorResult<string
|
|
161
|
-
|
|
162
|
-
// Clear received chunks so the buffered data can be GC'd.
|
|
163
|
-
receivedChunks.clear();
|
|
212
|
+
return: async (): Promise<IteratorResult<string>> => {
|
|
213
|
+
await this.close();
|
|
164
214
|
return { done: true, value: undefined };
|
|
165
215
|
},
|
|
166
216
|
};
|
|
@@ -42,6 +42,12 @@ export interface TextStreamOptions extends DataStreamOptions {
|
|
|
42
42
|
export interface ByteStreamOptions extends DataStreamOptions {
|
|
43
43
|
name?: string;
|
|
44
44
|
onProgress?: (progress: number) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Whether the payload may be compressed on the wire. Defaults to true;
|
|
47
|
+
* compression is only applied when it actually reduces the payload size
|
|
48
|
+
* and every recipient supports it.
|
|
49
|
+
*/
|
|
50
|
+
compress?: boolean;
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
export type ByteStreamHandler = (
|
package/src/e2ee.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
FrameCryptorSetKeyIndexRequest,
|
|
18
18
|
GetKeyRequest,
|
|
19
19
|
GetSharedKeyRequest,
|
|
20
|
+
KeyDerivationFunction,
|
|
20
21
|
RatchetKeyRequest,
|
|
21
22
|
RatchetSharedKeyRequest,
|
|
22
23
|
SetKeyRequest,
|
|
@@ -27,18 +28,24 @@ import { FfiClient } from './ffi_client.js';
|
|
|
27
28
|
const DEFAULT_RATCHET_SALT = new TextEncoder().encode('LKFrameEncryptionKey');
|
|
28
29
|
const DEFAULT_RATCHET_WINDOW_SIZE = 16;
|
|
29
30
|
const DEFAULT_FAILURE_TOLERANCE = -1;
|
|
31
|
+
const DEFAULT_KEY_RING_SIZE = 16;
|
|
32
|
+
const DEFAULT_KEY_DERIVATION_FUNCTION = KeyDerivationFunction.PBKDF2;
|
|
30
33
|
|
|
31
34
|
export interface KeyProviderOptions {
|
|
32
35
|
sharedKey?: Uint8Array;
|
|
33
36
|
ratchetSalt?: Uint8Array;
|
|
34
37
|
ratchetWindowSize?: number;
|
|
35
38
|
failureTolerance?: number;
|
|
39
|
+
keyRingSize?: number;
|
|
40
|
+
keyDerivationFunction?: KeyDerivationFunction;
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
export const defaultKeyProviderOptions: KeyProviderOptions = {
|
|
39
44
|
ratchetSalt: DEFAULT_RATCHET_SALT,
|
|
40
45
|
ratchetWindowSize: DEFAULT_RATCHET_WINDOW_SIZE,
|
|
41
46
|
failureTolerance: DEFAULT_FAILURE_TOLERANCE,
|
|
47
|
+
keyRingSize: DEFAULT_KEY_RING_SIZE,
|
|
48
|
+
keyDerivationFunction: DEFAULT_KEY_DERIVATION_FUNCTION,
|
|
42
49
|
};
|
|
43
50
|
|
|
44
51
|
export interface E2EEOptions {
|