livekit-client 2.5.5 → 2.5.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. package/dist/livekit-client.e2ee.worker.js +1 -1
  2. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  3. package/dist/livekit-client.e2ee.worker.mjs +5443 -736
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +2129 -1954
  6. package/dist/livekit-client.esm.mjs.map +1 -1
  7. package/dist/livekit-client.umd.js +1 -1
  8. package/dist/livekit-client.umd.js.map +1 -1
  9. package/dist/src/e2ee/worker/FrameCryptor.d.ts +2 -2
  10. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  11. package/dist/src/index.d.ts +2 -2
  12. package/dist/src/index.d.ts.map +1 -1
  13. package/dist/src/room/Room.d.ts +2 -1
  14. package/dist/src/room/Room.d.ts.map +1 -1
  15. package/dist/src/room/events.d.ts +2 -2
  16. package/dist/src/room/participant/Participant.d.ts +1 -1
  17. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  18. package/dist/src/room/track/Track.d.ts +1 -1
  19. package/dist/src/room/track/Track.d.ts.map +1 -1
  20. package/dist/src/room/utils.d.ts +1 -1
  21. package/dist/src/room/utils.d.ts.map +1 -1
  22. package/dist/src/test/mocks.d.ts +1 -1
  23. package/dist/src/test/mocks.d.ts.map +1 -1
  24. package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +2 -2
  25. package/dist/ts4.2/src/index.d.ts +2 -2
  26. package/dist/ts4.2/src/room/Room.d.ts +2 -1
  27. package/dist/ts4.2/src/room/events.d.ts +2 -2
  28. package/dist/ts4.2/src/room/participant/Participant.d.ts +1 -1
  29. package/dist/ts4.2/src/room/track/Track.d.ts +1 -1
  30. package/dist/ts4.2/src/room/utils.d.ts +1 -1
  31. package/dist/ts4.2/src/test/mocks.d.ts +1 -1
  32. package/package.json +10 -11
  33. package/src/e2ee/E2eeManager.ts +8 -8
  34. package/src/e2ee/worker/FrameCryptor.test.ts +249 -2
  35. package/src/e2ee/worker/FrameCryptor.ts +13 -7
  36. package/src/e2ee/worker/ParticipantKeyHandler.test.ts +122 -0
  37. package/src/e2ee/worker/ParticipantKeyHandler.ts +1 -1
  38. package/src/e2ee/worker/e2ee.worker.ts +82 -78
  39. package/src/index.ts +2 -0
  40. package/src/room/Room.ts +16 -10
  41. package/src/room/events.ts +2 -2
  42. package/src/room/participant/Participant.ts +3 -2
  43. package/src/room/track/Track.ts +1 -1
  44. package/src/room/track/utils.ts +1 -1
  45. package/src/room/utils.ts +1 -1
  46. package/src/test/mocks.ts +1 -1
@@ -1,13 +1,109 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { isFrameServerInjected } from './FrameCryptor';
1
+ import { afterEach, describe, expect, it, vitest } from 'vitest';
2
+ import { IV_LENGTH, KEY_PROVIDER_DEFAULTS } from '../constants';
3
+ import { CryptorEvent } from '../events';
4
+ import type { KeyProviderOptions } from '../types';
5
+ import { createKeyMaterialFromString } from '../utils';
6
+ import { FrameCryptor, encryptionEnabledMap, isFrameServerInjected } from './FrameCryptor';
7
+ import { ParticipantKeyHandler } from './ParticipantKeyHandler';
8
+
9
+ function mockEncryptedRTCEncodedVideoFrame(keyIndex: number): RTCEncodedVideoFrame {
10
+ const trailer = mockFrameTrailer(keyIndex);
11
+ const data = new Uint8Array(trailer.length + 10);
12
+ data.set(trailer, 10);
13
+ return mockRTCEncodedVideoFrame(data);
14
+ }
15
+
16
+ function mockRTCEncodedVideoFrame(data: Uint8Array): RTCEncodedVideoFrame {
17
+ return {
18
+ data: data.buffer,
19
+ timestamp: vitest.getMockedSystemTime()?.getTime() ?? 0,
20
+ type: 'key',
21
+ getMetadata(): RTCEncodedVideoFrameMetadata {
22
+ return {};
23
+ },
24
+ };
25
+ }
26
+
27
+ function mockFrameTrailer(keyIndex: number): Uint8Array {
28
+ const frameTrailer = new Uint8Array(2);
29
+
30
+ frameTrailer[0] = IV_LENGTH;
31
+ frameTrailer[1] = keyIndex;
32
+
33
+ return frameTrailer;
34
+ }
35
+
36
+ class TestUnderlyingSource<T> implements UnderlyingSource<T> {
37
+ controller: ReadableStreamController<T>;
38
+
39
+ start(controller: ReadableStreamController<T>): void {
40
+ this.controller = controller;
41
+ }
42
+
43
+ write(chunk: T): void {
44
+ this.controller.enqueue(chunk as any);
45
+ }
46
+
47
+ close(): void {
48
+ this.controller.close();
49
+ }
50
+ }
51
+
52
+ class TestUnderlyingSink<T> implements UnderlyingSink<T> {
53
+ public chunks: T[] = [];
54
+
55
+ write(chunk: T): void {
56
+ this.chunks.push(chunk);
57
+ }
58
+ }
59
+
60
+ function prepareParticipantTestDecoder(
61
+ participantIdentity: string,
62
+ partialKeyProviderOptions: Partial<KeyProviderOptions>,
63
+ ): {
64
+ keys: ParticipantKeyHandler;
65
+ cryptor: FrameCryptor;
66
+ input: TestUnderlyingSource<RTCEncodedVideoFrame>;
67
+ output: TestUnderlyingSink<RTCEncodedVideoFrame>;
68
+ } {
69
+ const keyProviderOptions = { ...KEY_PROVIDER_DEFAULTS, ...partialKeyProviderOptions };
70
+ const keys = new ParticipantKeyHandler(participantIdentity, keyProviderOptions);
71
+
72
+ encryptionEnabledMap.set(participantIdentity, true);
73
+
74
+ const cryptor = new FrameCryptor({
75
+ participantIdentity,
76
+ keys,
77
+ keyProviderOptions,
78
+ sifTrailer: new Uint8Array(),
79
+ });
80
+
81
+ const input = new TestUnderlyingSource<RTCEncodedVideoFrame>();
82
+ const output = new TestUnderlyingSink<RTCEncodedVideoFrame>();
83
+ cryptor.setupTransform(
84
+ 'decode',
85
+ new ReadableStream(input),
86
+ new WritableStream(output),
87
+ 'testTrack',
88
+ );
89
+
90
+ return { keys, cryptor, input, output };
91
+ }
3
92
 
4
93
  describe('FrameCryptor', () => {
94
+ const participantIdentity = 'testParticipant';
95
+
96
+ afterEach(() => {
97
+ encryptionEnabledMap.clear();
98
+ });
99
+
5
100
  it('identifies server injected frame correctly', () => {
6
101
  const frameTrailer = new TextEncoder().encode('LKROCKS');
7
102
  const frameData = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, ...frameTrailer]).buffer;
8
103
 
9
104
  expect(isFrameServerInjected(frameData, frameTrailer)).toBe(true);
10
105
  });
106
+
11
107
  it('identifies server non server injected frame correctly', () => {
12
108
  const frameTrailer = new TextEncoder().encode('LKROCKS');
13
109
  const frameData = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, ...frameTrailer, 10]);
@@ -16,4 +112,155 @@ describe('FrameCryptor', () => {
16
112
  frameData.fill(0);
17
113
  expect(isFrameServerInjected(frameData.buffer, frameTrailer)).toBe(false);
18
114
  });
115
+
116
+ it('passthrough if participant encryption disabled', async () => {
117
+ vitest.useFakeTimers();
118
+ try {
119
+ const { input, output } = prepareParticipantTestDecoder(participantIdentity, {});
120
+
121
+ // disable encryption for participant
122
+ encryptionEnabledMap.set(participantIdentity, false);
123
+
124
+ const frame = mockEncryptedRTCEncodedVideoFrame(1);
125
+
126
+ input.write(frame);
127
+ await vitest.advanceTimersToNextTimerAsync();
128
+
129
+ expect(output.chunks).toEqual([frame]);
130
+ } finally {
131
+ vitest.useRealTimers();
132
+ }
133
+ });
134
+
135
+ it('passthrough for empty frame', async () => {
136
+ vitest.useFakeTimers();
137
+ try {
138
+ const { input, output } = prepareParticipantTestDecoder(participantIdentity, {});
139
+
140
+ // empty frame
141
+ const frame = mockRTCEncodedVideoFrame(new Uint8Array(0));
142
+
143
+ input.write(frame);
144
+ await vitest.advanceTimersToNextTimerAsync();
145
+
146
+ expect(output.chunks).toEqual([frame]);
147
+ } finally {
148
+ vitest.useRealTimers();
149
+ }
150
+ });
151
+
152
+ it('drops frames when invalid key', async () => {
153
+ vitest.useFakeTimers();
154
+ try {
155
+ const { keys, input, output } = prepareParticipantTestDecoder(participantIdentity, {
156
+ failureTolerance: 0,
157
+ });
158
+
159
+ expect(keys.hasValidKey).toBe(true);
160
+
161
+ await keys.setKey(await createKeyMaterialFromString('password'), 0);
162
+
163
+ input.write(mockEncryptedRTCEncodedVideoFrame(1));
164
+ await vitest.advanceTimersToNextTimerAsync();
165
+
166
+ expect(output.chunks).toEqual([]);
167
+ expect(keys.hasValidKey).toBe(false);
168
+
169
+ // this should still fail as keys are all marked as invalid
170
+ input.write(mockEncryptedRTCEncodedVideoFrame(0));
171
+ await vitest.advanceTimersToNextTimerAsync();
172
+
173
+ expect(output.chunks).toEqual([]);
174
+ expect(keys.hasValidKey).toBe(false);
175
+ } finally {
176
+ vitest.useRealTimers();
177
+ }
178
+ });
179
+
180
+ it('marks key invalid after too many failures', async () => {
181
+ const { keys, cryptor, input } = prepareParticipantTestDecoder(participantIdentity, {
182
+ failureTolerance: 1,
183
+ });
184
+
185
+ expect(keys.hasValidKey).toBe(true);
186
+
187
+ await keys.setKey(await createKeyMaterialFromString('password'), 0);
188
+
189
+ vitest.spyOn(keys, 'getKeySet');
190
+ vitest.spyOn(keys, 'decryptionFailure');
191
+
192
+ const errorListener = vitest.fn().mockImplementation((e) => {
193
+ console.log('error', e);
194
+ });
195
+ cryptor.on(CryptorEvent.Error, errorListener);
196
+
197
+ input.write(mockEncryptedRTCEncodedVideoFrame(1));
198
+
199
+ await vitest.waitFor(() => expect(keys.decryptionFailure).toHaveBeenCalled());
200
+ expect(errorListener).toHaveBeenCalled();
201
+ expect(keys.decryptionFailure).toHaveBeenCalledTimes(1);
202
+ expect(keys.getKeySet).toHaveBeenCalled();
203
+ expect(keys.getKeySet).toHaveBeenLastCalledWith(1);
204
+ expect(keys.hasValidKey).toBe(true);
205
+
206
+ vitest.clearAllMocks();
207
+
208
+ input.write(mockEncryptedRTCEncodedVideoFrame(1));
209
+
210
+ await vitest.waitFor(() => expect(keys.decryptionFailure).toHaveBeenCalled());
211
+ expect(errorListener).toHaveBeenCalled();
212
+ expect(keys.decryptionFailure).toHaveBeenCalledTimes(1);
213
+ expect(keys.getKeySet).toHaveBeenCalled();
214
+ expect(keys.getKeySet).toHaveBeenLastCalledWith(1);
215
+ expect(keys.hasValidKey).toBe(false);
216
+
217
+ vitest.clearAllMocks();
218
+
219
+ // this should still fail as keys are all marked as invalid
220
+ input.write(mockEncryptedRTCEncodedVideoFrame(0));
221
+
222
+ await vitest.waitFor(() => expect(keys.getKeySet).toHaveBeenCalled());
223
+ // decryptionFailure() isn't called in this case
224
+ expect(keys.getKeySet).toHaveBeenCalled();
225
+ expect(keys.getKeySet).toHaveBeenLastCalledWith(0);
226
+ expect(keys.hasValidKey).toBe(false);
227
+ });
228
+
229
+ it('mark as valid when a new key is set on same index', async () => {
230
+ const { keys, input } = prepareParticipantTestDecoder(participantIdentity, {
231
+ failureTolerance: 0,
232
+ });
233
+
234
+ const material = await createKeyMaterialFromString('password');
235
+ await keys.setKey(material, 0);
236
+
237
+ expect(keys.hasValidKey).toBe(true);
238
+
239
+ input.write(mockEncryptedRTCEncodedVideoFrame(1));
240
+
241
+ expect(keys.hasValidKey).toBe(false);
242
+
243
+ await keys.setKey(material, 0);
244
+
245
+ expect(keys.hasValidKey).toBe(true);
246
+ });
247
+
248
+ it('mark as valid when a new key is set on new index', async () => {
249
+ const { keys, input } = prepareParticipantTestDecoder(participantIdentity, {
250
+ failureTolerance: 0,
251
+ });
252
+
253
+ const material = await createKeyMaterialFromString('password');
254
+ await keys.setKey(material, 0);
255
+
256
+ expect(keys.hasValidKey).toBe(true);
257
+
258
+ input.write(mockEncryptedRTCEncodedVideoFrame(1));
259
+
260
+ expect(keys.hasValidKey).toBe(false);
261
+
262
+ await keys.setKey(material, 1);
263
+
264
+ expect(keys.hasValidKey).toBe(true);
265
+ });
19
266
  });
@@ -6,7 +6,7 @@ import { workerLogger } from '../../logger';
6
6
  import type { VideoCodec } from '../../room/track/options';
7
7
  import { ENCRYPTION_ALGORITHM, IV_LENGTH, UNENCRYPTED_BYTES } from '../constants';
8
8
  import { CryptorError, CryptorErrorReason } from '../errors';
9
- import { CryptorCallbacks, CryptorEvent } from '../events';
9
+ import { type CryptorCallbacks, CryptorEvent } from '../events';
10
10
  import type { DecodeRatchetOptions, KeyProviderOptions, KeySet } from '../types';
11
11
  import { deriveKeys, isVideoFrame, needsRbspUnescaping, parseRbsp, writeRbsp } from '../utils';
12
12
  import type { ParticipantKeyHandler } from './ParticipantKeyHandler';
@@ -156,8 +156,8 @@ export class FrameCryptor extends BaseFrameCryptor {
156
156
 
157
157
  setupTransform(
158
158
  operation: 'encode' | 'decode',
159
- readable: ReadableStream,
160
- writable: WritableStream,
159
+ readable: ReadableStream<RTCEncodedVideoFrame | RTCEncodedAudioFrame>,
160
+ writable: WritableStream<RTCEncodedVideoFrame | RTCEncodedAudioFrame>,
161
161
  trackId: string,
162
162
  codec?: VideoCodec,
163
163
  ) {
@@ -233,11 +233,17 @@ export class FrameCryptor extends BaseFrameCryptor {
233
233
  }
234
234
  const keySet = this.keys.getKeySet();
235
235
  if (!keySet) {
236
- throw new TypeError(
237
- `key set not found for ${
238
- this.participantIdentity
239
- } at index ${this.keys.getCurrentKeyIndex()}`,
236
+ this.emit(
237
+ CryptorEvent.Error,
238
+ new CryptorError(
239
+ `key set not found for ${
240
+ this.participantIdentity
241
+ } at index ${this.keys.getCurrentKeyIndex()}`,
242
+ CryptorErrorReason.MissingKey,
243
+ this.participantIdentity,
244
+ ),
240
245
  );
246
+ return;
241
247
  }
242
248
  const { encryptionKey } = keySet;
243
249
  const keyIndex = this.keys.getCurrentKeyIndex();
@@ -0,0 +1,122 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { KEY_PROVIDER_DEFAULTS } from '../constants';
3
+ import { createKeyMaterialFromString } from '../utils';
4
+ import { ParticipantKeyHandler } from './ParticipantKeyHandler';
5
+
6
+ describe('ParticipantKeyHandler', () => {
7
+ const participantIdentity = 'testParticipant';
8
+
9
+ it('keyringSize must be greater than 0', () => {
10
+ expect(() => {
11
+ new ParticipantKeyHandler(participantIdentity, { ...KEY_PROVIDER_DEFAULTS, keyringSize: 0 });
12
+ }).toThrowError(TypeError);
13
+ });
14
+
15
+ it('keyringSize must be max 256', () => {
16
+ expect(() => {
17
+ new ParticipantKeyHandler(participantIdentity, {
18
+ ...KEY_PROVIDER_DEFAULTS,
19
+ keyringSize: 257,
20
+ });
21
+ }).toThrowError(TypeError);
22
+ });
23
+
24
+ it('get and sets keys at an index', async () => {
25
+ const keyHandler = new ParticipantKeyHandler(participantIdentity, {
26
+ ...KEY_PROVIDER_DEFAULTS,
27
+ keyringSize: 128,
28
+ });
29
+ const materialA = await createKeyMaterialFromString('passwordA');
30
+ const materialB = await createKeyMaterialFromString('passwordB');
31
+ await keyHandler.setKey(materialA, 0);
32
+ expect(keyHandler.getKeySet(0)).toBeDefined();
33
+ expect(keyHandler.getKeySet(0)?.material).toEqual(materialA);
34
+ await keyHandler.setKey(materialB, 0);
35
+ expect(keyHandler.getKeySet(0)?.material).toEqual(materialB);
36
+ });
37
+
38
+ it('marks invalid if more than failureTolerance failures', async () => {
39
+ const keyHandler = new ParticipantKeyHandler(participantIdentity, {
40
+ ...KEY_PROVIDER_DEFAULTS,
41
+ failureTolerance: 2,
42
+ });
43
+ expect(keyHandler.hasValidKey).toBe(true);
44
+
45
+ // 1
46
+ keyHandler.decryptionFailure();
47
+ expect(keyHandler.hasValidKey).toBe(true);
48
+
49
+ // 2
50
+ keyHandler.decryptionFailure();
51
+ expect(keyHandler.hasValidKey).toBe(true);
52
+
53
+ // 3
54
+ keyHandler.decryptionFailure();
55
+ expect(keyHandler.hasValidKey).toBe(false);
56
+ });
57
+
58
+ it('marks valid on encryption success', async () => {
59
+ const keyHandler = new ParticipantKeyHandler(participantIdentity, {
60
+ ...KEY_PROVIDER_DEFAULTS,
61
+ failureTolerance: 0,
62
+ });
63
+
64
+ expect(keyHandler.hasValidKey).toBe(true);
65
+
66
+ keyHandler.decryptionFailure();
67
+
68
+ expect(keyHandler.hasValidKey).toBe(false);
69
+
70
+ keyHandler.decryptionSuccess();
71
+
72
+ expect(keyHandler.hasValidKey).toBe(true);
73
+ });
74
+
75
+ it('marks valid on new key', async () => {
76
+ const keyHandler = new ParticipantKeyHandler(participantIdentity, {
77
+ ...KEY_PROVIDER_DEFAULTS,
78
+ failureTolerance: 0,
79
+ });
80
+
81
+ expect(keyHandler.hasValidKey).toBe(true);
82
+
83
+ keyHandler.decryptionFailure();
84
+
85
+ expect(keyHandler.hasValidKey).toBe(false);
86
+
87
+ await keyHandler.setKey(await createKeyMaterialFromString('passwordA'));
88
+
89
+ expect(keyHandler.hasValidKey).toBe(true);
90
+ });
91
+
92
+ it('updates currentKeyIndex on new key', async () => {
93
+ const keyHandler = new ParticipantKeyHandler(participantIdentity, KEY_PROVIDER_DEFAULTS);
94
+ const material = await createKeyMaterialFromString('password');
95
+
96
+ expect(keyHandler.getCurrentKeyIndex()).toBe(0);
97
+
98
+ // default is zero
99
+ await keyHandler.setKey(material);
100
+ expect(keyHandler.getCurrentKeyIndex()).toBe(0);
101
+
102
+ // should go to next index
103
+ await keyHandler.setKey(material, 1);
104
+ expect(keyHandler.getCurrentKeyIndex()).toBe(1);
105
+
106
+ // should be able to jump ahead
107
+ await keyHandler.setKey(material, 10);
108
+ expect(keyHandler.getCurrentKeyIndex()).toBe(10);
109
+ });
110
+
111
+ it('allows many failures if failureTolerance is -1', async () => {
112
+ const keyHandler = new ParticipantKeyHandler(participantIdentity, {
113
+ ...KEY_PROVIDER_DEFAULTS,
114
+ failureTolerance: -1,
115
+ });
116
+ expect(keyHandler.hasValidKey).toBe(true);
117
+ for (let i = 0; i < 100; i++) {
118
+ keyHandler.decryptionFailure();
119
+ expect(keyHandler.hasValidKey).toBe(true);
120
+ }
121
+ });
122
+ });
@@ -38,7 +38,7 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent
38
38
  constructor(participantIdentity: string, keyProviderOptions: KeyProviderOptions) {
39
39
  super();
40
40
  this.currentKeyIndex = 0;
41
- if (keyProviderOptions.keyringSize < 1 || keyProviderOptions.keyringSize > 255) {
41
+ if (keyProviderOptions.keyringSize < 1 || keyProviderOptions.keyringSize > 256) {
42
42
  throw new TypeError('Keyring size needs to be between 1 and 256');
43
43
  }
44
44
  this.cryptoKeyRing = new Array(keyProviderOptions.keyringSize).fill(undefined);
@@ -1,5 +1,6 @@
1
1
  import { workerLogger } from '../../logger';
2
- import { VideoCodec } from '../../room/track/options';
2
+ import type { VideoCodec } from '../../room/track/options';
3
+ import { AsyncQueue } from '../../utils/AsyncQueue';
3
4
  import { KEY_PROVIDER_DEFAULTS } from '../constants';
4
5
  import { CryptorErrorReason } from '../errors';
5
6
  import { CryptorEvent, KeyHandlerEvent } from '../events';
@@ -17,6 +18,7 @@ import { ParticipantKeyHandler } from './ParticipantKeyHandler';
17
18
  const participantCryptors: FrameCryptor[] = [];
18
19
  const participantKeys: Map<string, ParticipantKeyHandler> = new Map();
19
20
  let sharedKeyHandler: ParticipantKeyHandler | undefined;
21
+ let messageQueue = new AsyncQueue();
20
22
 
21
23
  let isEncryptionEnabled: boolean = false;
22
24
 
@@ -31,85 +33,87 @@ let rtpMap: Map<number, VideoCodec> = new Map();
31
33
  workerLogger.setDefaultLevel('info');
32
34
 
33
35
  onmessage = (ev) => {
34
- const { kind, data }: E2EEWorkerMessage = ev.data;
36
+ messageQueue.run(async () => {
37
+ const { kind, data }: E2EEWorkerMessage = ev.data;
35
38
 
36
- switch (kind) {
37
- case 'init':
38
- workerLogger.setLevel(data.loglevel);
39
- workerLogger.info('worker initialized');
40
- keyProviderOptions = data.keyProviderOptions;
41
- useSharedKey = !!data.keyProviderOptions.sharedKey;
42
- // acknowledge init successful
43
- const ackMsg: InitAck = {
44
- kind: 'initAck',
45
- data: { enabled: isEncryptionEnabled },
46
- };
47
- postMessage(ackMsg);
48
- break;
49
- case 'enable':
50
- setEncryptionEnabled(data.enabled, data.participantIdentity);
51
- workerLogger.info(
52
- `updated e2ee enabled status for ${data.participantIdentity} to ${data.enabled}`,
53
- );
54
- // acknowledge enable call successful
55
- postMessage(ev.data);
56
- break;
57
- case 'decode':
58
- let cryptor = getTrackCryptor(data.participantIdentity, data.trackId);
59
- cryptor.setupTransform(
60
- kind,
61
- data.readableStream,
62
- data.writableStream,
63
- data.trackId,
64
- data.codec,
65
- );
66
- break;
67
- case 'encode':
68
- let pubCryptor = getTrackCryptor(data.participantIdentity, data.trackId);
69
- pubCryptor.setupTransform(
70
- kind,
71
- data.readableStream,
72
- data.writableStream,
73
- data.trackId,
74
- data.codec,
75
- );
76
- break;
77
- case 'setKey':
78
- if (useSharedKey) {
79
- setSharedKey(data.key, data.keyIndex);
80
- } else if (data.participantIdentity) {
39
+ switch (kind) {
40
+ case 'init':
41
+ workerLogger.setLevel(data.loglevel);
42
+ workerLogger.info('worker initialized');
43
+ keyProviderOptions = data.keyProviderOptions;
44
+ useSharedKey = !!data.keyProviderOptions.sharedKey;
45
+ // acknowledge init successful
46
+ const ackMsg: InitAck = {
47
+ kind: 'initAck',
48
+ data: { enabled: isEncryptionEnabled },
49
+ };
50
+ postMessage(ackMsg);
51
+ break;
52
+ case 'enable':
53
+ setEncryptionEnabled(data.enabled, data.participantIdentity);
81
54
  workerLogger.info(
82
- `set participant sender key ${data.participantIdentity} index ${data.keyIndex}`,
55
+ `updated e2ee enabled status for ${data.participantIdentity} to ${data.enabled}`,
83
56
  );
84
- getParticipantKeyHandler(data.participantIdentity).setKey(data.key, data.keyIndex);
85
- } else {
86
- workerLogger.error('no participant Id was provided and shared key usage is disabled');
87
- }
88
- break;
89
- case 'removeTransform':
90
- unsetCryptorParticipant(data.trackId, data.participantIdentity);
91
- break;
92
- case 'updateCodec':
93
- getTrackCryptor(data.participantIdentity, data.trackId).setVideoCodec(data.codec);
94
- break;
95
- case 'setRTPMap':
96
- // this is only used for the local participant
97
- rtpMap = data.map;
98
- participantCryptors.forEach((cr) => {
99
- if (cr.getParticipantIdentity() === data.participantIdentity) {
100
- cr.setRtpMap(data.map);
57
+ // acknowledge enable call successful
58
+ postMessage(ev.data);
59
+ break;
60
+ case 'decode':
61
+ let cryptor = getTrackCryptor(data.participantIdentity, data.trackId);
62
+ cryptor.setupTransform(
63
+ kind,
64
+ data.readableStream,
65
+ data.writableStream,
66
+ data.trackId,
67
+ data.codec,
68
+ );
69
+ break;
70
+ case 'encode':
71
+ let pubCryptor = getTrackCryptor(data.participantIdentity, data.trackId);
72
+ pubCryptor.setupTransform(
73
+ kind,
74
+ data.readableStream,
75
+ data.writableStream,
76
+ data.trackId,
77
+ data.codec,
78
+ );
79
+ break;
80
+ case 'setKey':
81
+ if (useSharedKey) {
82
+ await setSharedKey(data.key, data.keyIndex);
83
+ } else if (data.participantIdentity) {
84
+ workerLogger.info(
85
+ `set participant sender key ${data.participantIdentity} index ${data.keyIndex}`,
86
+ );
87
+ await getParticipantKeyHandler(data.participantIdentity).setKey(data.key, data.keyIndex);
88
+ } else {
89
+ workerLogger.error('no participant Id was provided and shared key usage is disabled');
101
90
  }
102
- });
103
- break;
104
- case 'ratchetRequest':
105
- handleRatchetRequest(data);
106
- break;
107
- case 'setSifTrailer':
108
- handleSifTrailer(data.trailer);
109
- break;
110
- default:
111
- break;
112
- }
91
+ break;
92
+ case 'removeTransform':
93
+ unsetCryptorParticipant(data.trackId, data.participantIdentity);
94
+ break;
95
+ case 'updateCodec':
96
+ getTrackCryptor(data.participantIdentity, data.trackId).setVideoCodec(data.codec);
97
+ break;
98
+ case 'setRTPMap':
99
+ // this is only used for the local participant
100
+ rtpMap = data.map;
101
+ participantCryptors.forEach((cr) => {
102
+ if (cr.getParticipantIdentity() === data.participantIdentity) {
103
+ cr.setRtpMap(data.map);
104
+ }
105
+ });
106
+ break;
107
+ case 'ratchetRequest':
108
+ handleRatchetRequest(data);
109
+ break;
110
+ case 'setSifTrailer':
111
+ handleSifTrailer(data.trailer);
112
+ break;
113
+ default:
114
+ break;
115
+ }
116
+ });
113
117
  };
114
118
 
115
119
  async function handleRatchetRequest(data: RatchetRequestMessage['data']) {
@@ -210,9 +214,9 @@ function setEncryptionEnabled(enable: boolean, participantIdentity: string) {
210
214
  encryptionEnabledMap.set(participantIdentity, enable);
211
215
  }
212
216
 
213
- function setSharedKey(key: CryptoKey, index?: number) {
217
+ async function setSharedKey(key: CryptoKey, index?: number) {
214
218
  workerLogger.info('set shared key', { index });
215
- getSharedKeyHandler().setKey(key, index);
219
+ await getSharedKeyHandler().setKey(key, index);
216
220
  }
217
221
 
218
222
  function setupCryptorErrorEvents(cryptor: FrameCryptor) {
package/src/index.ts CHANGED
@@ -27,6 +27,7 @@ import type { LiveKitReactNativeInfo } from './room/types';
27
27
  import type { AudioAnalyserOptions } from './room/utils';
28
28
  import {
29
29
  Mutex,
30
+ compareVersions,
30
31
  createAudioAnalyser,
31
32
  getEmptyAudioStreamTrack,
32
33
  getEmptyVideoStreamTrack,
@@ -81,6 +82,7 @@ export {
81
82
  Room,
82
83
  SubscriptionError,
83
84
  TrackPublication,
85
+ compareVersions,
84
86
  createAudioAnalyser,
85
87
  getBrowser,
86
88
  getEmptyAudioStreamTrack,