@livekit/rtc-node 0.13.32 → 0.13.34

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.
Files changed (141) hide show
  1. package/dist/async_queue.d.ts +2 -3
  2. package/dist/audio_filter.d.ts +2 -3
  3. package/dist/audio_frame.d.ts +5 -6
  4. package/dist/audio_mixer.d.ts +3 -6
  5. package/dist/audio_resampler.d.ts +3 -6
  6. package/dist/audio_source.d.ts +5 -6
  7. package/dist/{audio_stream-4M8XlO4-.d.cts → audio_stream-BNVaONuH.d.cts} +36 -10
  8. package/dist/audio_stream.d.cts +2 -2
  9. package/dist/audio_stream.d.ts +61 -18
  10. package/dist/data_streams/index.d.cts +1 -1
  11. package/dist/data_streams/index.d.ts +4 -3
  12. package/dist/data_streams/stream_reader.cjs +97 -58
  13. package/dist/data_streams/stream_reader.cjs.map +1 -1
  14. package/dist/data_streams/stream_reader.d.cts +1 -1
  15. package/dist/data_streams/stream_reader.d.ts +65 -2
  16. package/dist/data_streams/stream_reader.d.ts.map +1 -1
  17. package/dist/data_streams/stream_writer.d.cts +1 -1
  18. package/dist/data_streams/stream_writer.d.ts +5 -7
  19. package/dist/data_streams/types.cjs.map +1 -1
  20. package/dist/data_streams/types.d.cts +1 -1
  21. package/dist/data_streams/types.d.ts +47 -2
  22. package/dist/data_streams/types.d.ts.map +1 -1
  23. package/dist/e2ee.cjs +5 -1
  24. package/dist/e2ee.cjs.map +1 -1
  25. package/dist/e2ee.d.cts +3 -1
  26. package/dist/e2ee.d.ts +11 -11
  27. package/dist/e2ee.d.ts.map +1 -1
  28. package/dist/ffi_client.d.ts +8 -10
  29. package/dist/frame_processor.d.ts +8 -11
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +2 -2
  32. package/dist/index.d.ts +22 -17
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +1 -107
  35. package/dist/log.d.ts +5 -7
  36. package/dist/participant.cjs +179 -192
  37. package/dist/participant.cjs.map +1 -1
  38. package/dist/participant.d.cts +2 -2
  39. package/dist/participant.d.ts +169 -18
  40. package/dist/participant.d.ts.map +1 -1
  41. package/dist/room.cjs +166 -120
  42. package/dist/room.cjs.map +1 -1
  43. package/dist/room.d.cts +2 -2
  44. package/dist/room.d.ts +228 -18
  45. package/dist/room.d.ts.map +1 -1
  46. package/dist/rpc.d.ts +7 -9
  47. package/dist/tests/e2e_common.cjs +157 -0
  48. package/dist/tests/e2e_common.cjs.map +1 -0
  49. package/dist/tests/e2e_common.d.cts +56 -0
  50. package/dist/tests/e2e_common.d.ts +37 -0
  51. package/dist/tests/e2e_common.d.ts.map +1 -0
  52. package/dist/track.d.cts +2 -2
  53. package/dist/track.d.ts +67 -18
  54. package/dist/track_publication.d.cts +2 -2
  55. package/dist/track_publication.d.ts +45 -18
  56. package/dist/transcription.d.ts +3 -4
  57. package/dist/{types-_PdPVish.d.ts → types-f7wTlFjs.d.cts} +34 -11
  58. package/dist/types.d.ts +2 -3
  59. package/dist/utils.cjs +27 -2
  60. package/dist/utils.cjs.map +1 -1
  61. package/dist/utils.d.cts +8 -1
  62. package/dist/utils.d.ts +10 -5
  63. package/dist/utils.d.ts.map +1 -1
  64. package/dist/version.cjs +1 -1
  65. package/dist/version.cjs.map +1 -1
  66. package/dist/version.d.cts +1 -1
  67. package/dist/version.d.ts +2 -3
  68. package/dist/video_frame.d.ts +4 -5
  69. package/dist/video_source.d.ts +6 -8
  70. package/dist/video_stream.d.cts +2 -2
  71. package/dist/video_stream.d.ts +5 -22
  72. package/package.json +6 -7
  73. package/src/audio_stream_room_lifecycle.test.ts +4 -1
  74. package/src/data_streams/stream_reader.ts +132 -82
  75. package/src/data_streams/types.ts +6 -0
  76. package/src/e2ee.ts +7 -0
  77. package/src/index.ts +8 -1
  78. package/src/participant.ts +252 -223
  79. package/src/room.ts +233 -134
  80. package/src/tests/e2e.test.ts +50 -139
  81. package/src/tests/e2e_common.ts +163 -0
  82. package/src/tests/e2e_data_streams.test.ts +639 -0
  83. package/src/utils.ts +30 -0
  84. package/src/version.ts +1 -1
  85. package/dist/async_queue.js +0 -56
  86. package/dist/async_queue.js.map +0 -1
  87. package/dist/audio_filter.js +0 -24
  88. package/dist/audio_filter.js.map +0 -1
  89. package/dist/audio_frame.js +0 -78
  90. package/dist/audio_frame.js.map +0 -1
  91. package/dist/audio_mixer.js +0 -269
  92. package/dist/audio_mixer.js.map +0 -1
  93. package/dist/audio_resampler.js +0 -154
  94. package/dist/audio_resampler.js.map +0 -1
  95. package/dist/audio_source.js +0 -134
  96. package/dist/audio_source.js.map +0 -1
  97. package/dist/audio_stream-CTyN1Ldy.d.ts +0 -523
  98. package/dist/audio_stream.js +0 -122
  99. package/dist/audio_stream.js.map +0 -1
  100. package/dist/data_streams/index.js +0 -3
  101. package/dist/data_streams/index.js.map +0 -1
  102. package/dist/data_streams/stream_reader.js +0 -124
  103. package/dist/data_streams/stream_reader.js.map +0 -1
  104. package/dist/data_streams/stream_writer.js +0 -26
  105. package/dist/data_streams/stream_writer.js.map +0 -1
  106. package/dist/data_streams/types.js +0 -1
  107. package/dist/data_streams/types.js.map +0 -1
  108. package/dist/e2ee.js +0 -251
  109. package/dist/e2ee.js.map +0 -1
  110. package/dist/ffi_client.js +0 -89
  111. package/dist/ffi_client.js.map +0 -1
  112. package/dist/frame_processor.js +0 -29
  113. package/dist/frame_processor.js.map +0 -1
  114. package/dist/index.js.map +0 -1
  115. package/dist/log.js +0 -9
  116. package/dist/log.js.map +0 -1
  117. package/dist/participant.js +0 -715
  118. package/dist/participant.js.map +0 -1
  119. package/dist/room.js +0 -900
  120. package/dist/room.js.map +0 -1
  121. package/dist/rpc.js +0 -65
  122. package/dist/rpc.js.map +0 -1
  123. package/dist/track.js +0 -234
  124. package/dist/track.js.map +0 -1
  125. package/dist/track_publication.js +0 -100
  126. package/dist/track_publication.js.map +0 -1
  127. package/dist/transcription.js +0 -1
  128. package/dist/transcription.js.map +0 -1
  129. package/dist/types-_PdPVish.d.cts +0 -87
  130. package/dist/types.js +0 -1
  131. package/dist/types.js.map +0 -1
  132. package/dist/utils.js +0 -35
  133. package/dist/utils.js.map +0 -1
  134. package/dist/version.js +0 -5
  135. package/dist/version.js.map +0 -1
  136. package/dist/video_frame.js +0 -207
  137. package/dist/video_frame.js.map +0 -1
  138. package/dist/video_source.js +0 -53
  139. package/dist/video_source.js.map +0 -1
  140. package/dist/video_stream.js +0 -70
  141. package/dist/video_stream.js.map +0 -1
@@ -1,10 +1,8 @@
1
1
  // SPDX-FileCopyrightText: 2026 LiveKit, Inc.
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
- import { AccessToken } from 'livekit-server-sdk';
5
- import { randomUUID } from 'node:crypto';
6
4
  import { setTimeout as delay } from 'node:timers/promises';
7
- import { afterAll, describe, expect, it as itRaw } from 'vitest';
5
+ import { afterAll, expect, it as itRaw } from 'vitest';
8
6
  import {
9
7
  AudioFrame,
10
8
  AudioSource,
@@ -12,7 +10,6 @@ import {
12
10
  ConnectionState,
13
11
  LocalAudioTrack,
14
12
  ParticipantKind,
15
- Room,
16
13
  RoomEvent,
17
14
  RpcError,
18
15
  SimulateScenarioKind,
@@ -21,134 +18,19 @@ import {
21
18
  TrackSource,
22
19
  dispose,
23
20
  } from '../index.js';
21
+ import {
22
+ concatUint8,
23
+ connectTestRooms,
24
+ describeE2E,
25
+ testTimeoutMs,
26
+ waitFor,
27
+ waitForRoomEvent,
28
+ withTimeout,
29
+ } from './e2e_common.js';
24
30
 
25
31
  // use concurrent testing if available on the runner (currently not supported by bun's api)
26
32
  const it = typeof itRaw.concurrent === 'function' ? itRaw.concurrent : itRaw;
27
33
 
28
- const hasE2EEnv =
29
- !!process.env.LIVEKIT_URL && !!process.env.LIVEKIT_API_KEY && !!process.env.LIVEKIT_API_SECRET;
30
- const describeE2E = hasE2EEnv ? describe : describe.skip;
31
- const testTimeoutMs = 10_000;
32
-
33
- type TestEnv = {
34
- url: string;
35
- apiKey: string;
36
- apiSecret: string;
37
- };
38
-
39
- function normalizeLiveKitUrl(url: string): string {
40
- if (url.startsWith('http://')) return `ws://${url.slice('http://'.length)}`;
41
- if (url.startsWith('https://')) return `wss://${url.slice('https://'.length)}`;
42
- return url;
43
- }
44
-
45
- function getTestEnv(): TestEnv {
46
- if (!hasE2EEnv) {
47
- throw new Error(
48
- 'Missing required env vars for e2e: LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET',
49
- );
50
- }
51
- return {
52
- url: normalizeLiveKitUrl(process.env.LIVEKIT_URL!),
53
- apiKey: process.env.LIVEKIT_API_KEY!,
54
- apiSecret: process.env.LIVEKIT_API_SECRET!,
55
- };
56
- }
57
-
58
- async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
59
- return await Promise.race([
60
- promise,
61
- (async () => {
62
- await delay(timeoutMs);
63
- throw new Error(message);
64
- })(),
65
- ]);
66
- }
67
-
68
- async function waitFor(
69
- condition: () => boolean,
70
- opts: { timeoutMs: number; intervalMs?: number; debugName?: string },
71
- ): Promise<void> {
72
- const intervalMs = opts.intervalMs ?? 50;
73
- const deadline = Date.now() + opts.timeoutMs;
74
- while (Date.now() < deadline) {
75
- if (condition()) return;
76
- await delay(intervalMs);
77
- }
78
- throw new Error(`Timed out waiting for condition${opts.debugName ? ` (${opts.debugName})` : ''}`);
79
- }
80
-
81
- async function createJoinToken(params: {
82
- env: TestEnv;
83
- roomName: string;
84
- identity: string;
85
- name: string;
86
- }): Promise<string> {
87
- const token = new AccessToken(params.env.apiKey, params.env.apiSecret, {
88
- identity: params.identity,
89
- name: params.name,
90
- ttl: '30m',
91
- });
92
- token.addGrant({
93
- room: params.roomName,
94
- roomJoin: true,
95
- roomCreate: true,
96
- canPublish: true,
97
- canSubscribe: true,
98
- });
99
- return await token.toJwt();
100
- }
101
-
102
- async function connectTestRooms(count: number): Promise<{ roomName: string; rooms: Room[] }> {
103
- const env = getTestEnv();
104
- const roomName = `test_room_${randomUUID()}`;
105
- const rooms = await Promise.all(
106
- Array.from({ length: count }, async (_, i) => {
107
- const token = await createJoinToken({
108
- env,
109
- roomName,
110
- identity: `p${i}`,
111
- name: `Participant ${i}`,
112
- });
113
- const room = new Room();
114
- await room.connect(env.url, token, { autoSubscribe: true, dynacast: false });
115
- return room;
116
- }),
117
- );
118
-
119
- const start = Date.now();
120
- await waitFor(() => rooms.every((r) => r.remoteParticipants.size === count - 1), {
121
- timeoutMs: 5000,
122
- debugName: `participant visibility (${Date.now() - start}ms)`,
123
- });
124
-
125
- return { roomName, rooms };
126
- }
127
-
128
- function waitForRoomEvent<R>(
129
- room: Room,
130
- event: RoomEvent,
131
- timeoutMs: number,
132
- take: (...args: any[]) => R,
133
- match?: (...args: unknown[]) => boolean,
134
- ): Promise<R> {
135
- return withTimeout(
136
- new Promise<R>((resolve) => {
137
- const handler = (...args: any[]) => {
138
- if (match && !match(...args)) {
139
- return;
140
- }
141
- // typed-emitter doesn't expose `.once` in the type surface, so do manual once+cleanup.
142
- room.off(event as any, handler as any);
143
- resolve(take(...args));
144
- };
145
- room.on(event as any, handler as any);
146
- }),
147
- timeoutMs,
148
- `Timed out waiting for ${event}`,
149
- );
150
- }
151
-
152
34
  /**
153
35
  * Only tracks published by `identity` are the test's business. Anything else in
154
36
  * the room — an agent the project dispatches, a stray participant — publishes
@@ -182,17 +64,6 @@ function expectTimestampFromCall(timestamp: number, calledAt: number, what: stri
182
64
  expect(timestamp, detail).toBeLessThanOrEqual(now + clockToleranceMs);
183
65
  }
184
66
 
185
- function concatUint8(chunks: Uint8Array[]): Uint8Array {
186
- const len = chunks.reduce((acc, c) => acc + c.byteLength, 0);
187
- const out = new Uint8Array(len);
188
- let offset = 0;
189
- for (const c of chunks) {
190
- out.set(c, offset);
191
- offset += c.byteLength;
192
- }
193
- return out;
194
- }
195
-
196
67
  function channelSamples(frame: AudioFrame, channelIndex: number): Int16Array {
197
68
  const { data, channels, samplesPerChannel } = frame;
198
69
  const out = new Int16Array(samplesPerChannel);
@@ -296,6 +167,46 @@ describeE2E('livekit-rtc e2e', () => {
296
167
  testTimeoutMs,
297
168
  );
298
169
 
170
+ it(
171
+ 'sends and receives a data message between participants sharing a key',
172
+ async () => {
173
+ // A shared-key `keyProviderOptions` with only `sharedKey` set relies on
174
+ // the SDK filling in the remaining (proto-required) provider defaults;
175
+ // regression guard for connect failing to encode KeyProviderOptions.
176
+ const encryption = {
177
+ keyProviderOptions: { sharedKey: new TextEncoder().encode('test-shared-key') },
178
+ };
179
+ const { rooms } = await connectTestRooms(2, { encryption });
180
+ const [receivingRoom, sendingRoom] = rooms;
181
+ const senderIdentity = sendingRoom!.localParticipant!.identity;
182
+ const receiverIdentity = receivingRoom!.localParticipant!.identity;
183
+
184
+ const payload = new TextEncoder().encode('e2ee-hello');
185
+ const received = withTimeout(
186
+ new Promise<Uint8Array>((resolve) => {
187
+ receivingRoom!.on(RoomEvent.DataReceived, (data, participant) => {
188
+ if (participant?.identity !== senderIdentity) return;
189
+ resolve(data);
190
+ });
191
+ }),
192
+ testTimeoutMs,
193
+ 'Timed out waiting for encrypted data message',
194
+ );
195
+
196
+ await sendingRoom!.localParticipant!.publishData(payload, {
197
+ reliable: true,
198
+ destination_identities: [receiverIdentity],
199
+ });
200
+
201
+ // The message round-trips intact only if both sides brought a working
202
+ // shared-key provider through connect and encrypt/decrypt lined up.
203
+ expect(await received).toEqual(payload);
204
+
205
+ await Promise.all(rooms.map((r) => r.disconnect()));
206
+ },
207
+ testTimeoutMs,
208
+ );
209
+
299
210
  it(
300
211
  'emits participantDisconnected when a participant leaves',
301
212
  async () => {
@@ -0,0 +1,163 @@
1
+ // SPDX-FileCopyrightText: 2026 LiveKit, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ import { AccessToken } from 'livekit-server-sdk';
5
+ import { randomUUID } from 'node:crypto';
6
+ import { setTimeout as delay } from 'node:timers/promises';
7
+ import { describe } from 'vitest';
8
+ import { Room, type RoomEvent, type RoomOptions } from '../index.js';
9
+
10
+ export const hasE2EEnv =
11
+ !!process.env.LIVEKIT_URL && !!process.env.LIVEKIT_API_KEY && !!process.env.LIVEKIT_API_SECRET;
12
+ // Explicitly typed so declaration emit doesn't reference vitest's
13
+ // non-exported internal suite types (TS4023).
14
+ export const describeE2E: typeof describe = hasE2EEnv
15
+ ? describe
16
+ : (describe.skip as typeof describe);
17
+ export const testTimeoutMs = 10_000;
18
+
19
+ export type TestEnv = {
20
+ url: string;
21
+ apiKey: string;
22
+ apiSecret: string;
23
+ };
24
+
25
+ function normalizeLiveKitUrl(url: string): string {
26
+ if (url.startsWith('http://')) return `ws://${url.slice('http://'.length)}`;
27
+ if (url.startsWith('https://')) return `wss://${url.slice('https://'.length)}`;
28
+ return url;
29
+ }
30
+
31
+ export function getTestEnv(): TestEnv {
32
+ if (!hasE2EEnv) {
33
+ throw new Error(
34
+ 'Missing required env vars for e2e: LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET',
35
+ );
36
+ }
37
+ return {
38
+ url: normalizeLiveKitUrl(process.env.LIVEKIT_URL!),
39
+ apiKey: process.env.LIVEKIT_API_KEY!,
40
+ apiSecret: process.env.LIVEKIT_API_SECRET!,
41
+ };
42
+ }
43
+
44
+ export async function withTimeout<T>(
45
+ promise: Promise<T>,
46
+ timeoutMs: number,
47
+ message: string,
48
+ ): Promise<T> {
49
+ return await Promise.race([
50
+ promise,
51
+ (async () => {
52
+ await delay(timeoutMs);
53
+ throw new Error(message);
54
+ })(),
55
+ ]);
56
+ }
57
+
58
+ export async function waitFor(
59
+ condition: () => boolean,
60
+ opts: { timeoutMs: number; intervalMs?: number; debugName?: string },
61
+ ): Promise<void> {
62
+ const intervalMs = opts.intervalMs ?? 50;
63
+ const deadline = Date.now() + opts.timeoutMs;
64
+ while (Date.now() < deadline) {
65
+ if (condition()) return;
66
+ await delay(intervalMs);
67
+ }
68
+ throw new Error(`Timed out waiting for condition${opts.debugName ? ` (${opts.debugName})` : ''}`);
69
+ }
70
+
71
+ export async function createJoinToken(params: {
72
+ env: TestEnv;
73
+ roomName: string;
74
+ identity: string;
75
+ name: string;
76
+ }): Promise<string> {
77
+ const token = new AccessToken(params.env.apiKey, params.env.apiSecret, {
78
+ identity: params.identity,
79
+ name: params.name,
80
+ ttl: '30m',
81
+ });
82
+ token.addGrant({
83
+ room: params.roomName,
84
+ roomJoin: true,
85
+ roomCreate: true,
86
+ canPublish: true,
87
+ canSubscribe: true,
88
+ });
89
+ return await token.toJwt();
90
+ }
91
+
92
+ export async function connectTestRooms(
93
+ count: number,
94
+ /**
95
+ * Extra room options applied to every room, e.g. data stream limits or
96
+ * `encryption` to bring up the whole set with a shared e2ee key.
97
+ */
98
+ options?: Partial<RoomOptions>,
99
+ ): Promise<{ roomName: string; rooms: Room[] }> {
100
+ const env = getTestEnv();
101
+ const roomName = `test_room_${randomUUID()}`;
102
+ const rooms = await Promise.all(
103
+ Array.from({ length: count }, async (_, i) => {
104
+ const token = await createJoinToken({
105
+ env,
106
+ roomName,
107
+ identity: `p${i}`,
108
+ name: `Participant ${i}`,
109
+ });
110
+ const room = new Room();
111
+ await room.connect(env.url, token, {
112
+ autoSubscribe: true,
113
+ dynacast: false,
114
+ ...options,
115
+ });
116
+ return room;
117
+ }),
118
+ );
119
+
120
+ const start = Date.now();
121
+ await waitFor(() => rooms.every((r) => r.remoteParticipants.size === count - 1), {
122
+ timeoutMs: 5000,
123
+ debugName: `participant visibility (${Date.now() - start}ms)`,
124
+ });
125
+
126
+ return { roomName, rooms };
127
+ }
128
+
129
+ export function waitForRoomEvent<R>(
130
+ room: Room,
131
+ event: RoomEvent,
132
+ timeoutMs: number,
133
+ take: (...args: any[]) => R,
134
+ /** Ignore emissions that don't match, e.g. events for another participant. */
135
+ match?: (...args: unknown[]) => boolean,
136
+ ): Promise<R> {
137
+ return withTimeout(
138
+ new Promise<R>((resolve) => {
139
+ const handler = (...args: any[]) => {
140
+ if (match && !match(...args)) {
141
+ return;
142
+ }
143
+ // typed-emitter doesn't expose `.once` in the type surface, so do manual once+cleanup.
144
+ room.off(event as any, handler as any);
145
+ resolve(take(...args));
146
+ };
147
+ room.on(event as any, handler as any);
148
+ }),
149
+ timeoutMs,
150
+ `Timed out waiting for ${event}`,
151
+ );
152
+ }
153
+
154
+ export function concatUint8(chunks: Uint8Array[]): Uint8Array {
155
+ const len = chunks.reduce((acc, c) => acc + c.byteLength, 0);
156
+ const out = new Uint8Array(len);
157
+ let offset = 0;
158
+ for (const c of chunks) {
159
+ out.set(c, offset);
160
+ offset += c.byteLength;
161
+ }
162
+ return out;
163
+ }