livekit-client 1.13.0 → 1.13.2

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 (55) 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 +122 -105
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +172 -109
  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/E2eeManager.d.ts +4 -3
  10. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  11. package/dist/src/e2ee/KeyProvider.d.ts +7 -6
  12. package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
  13. package/dist/src/e2ee/events.d.ts +34 -0
  14. package/dist/src/e2ee/events.d.ts.map +1 -0
  15. package/dist/src/e2ee/index.d.ts +1 -0
  16. package/dist/src/e2ee/index.d.ts.map +1 -1
  17. package/dist/src/e2ee/types.d.ts +17 -33
  18. package/dist/src/e2ee/types.d.ts.map +1 -1
  19. package/dist/src/e2ee/worker/FrameCryptor.d.ts +15 -12
  20. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  21. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +6 -8
  22. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
  23. package/dist/src/room/PCTransport.d.ts.map +1 -1
  24. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  25. package/dist/src/room/Room.d.ts.map +1 -1
  26. package/dist/src/room/participant/LocalParticipant.d.ts +1 -0
  27. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  28. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  29. package/dist/src/room/track/processor/types.d.ts +2 -1
  30. package/dist/src/room/track/processor/types.d.ts.map +1 -1
  31. package/dist/ts4.2/src/e2ee/E2eeManager.d.ts +4 -3
  32. package/dist/ts4.2/src/e2ee/KeyProvider.d.ts +7 -6
  33. package/dist/ts4.2/src/e2ee/events.d.ts +34 -0
  34. package/dist/ts4.2/src/e2ee/index.d.ts +1 -0
  35. package/dist/ts4.2/src/e2ee/types.d.ts +17 -33
  36. package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +15 -12
  37. package/dist/ts4.2/src/e2ee/worker/ParticipantKeyHandler.d.ts +6 -8
  38. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +1 -0
  39. package/dist/ts4.2/src/room/track/processor/types.d.ts +2 -1
  40. package/package.json +1 -1
  41. package/src/e2ee/E2eeManager.ts +105 -77
  42. package/src/e2ee/KeyProvider.ts +23 -13
  43. package/src/e2ee/events.ts +48 -0
  44. package/src/e2ee/index.ts +1 -0
  45. package/src/e2ee/types.ts +19 -41
  46. package/src/e2ee/worker/FrameCryptor.ts +51 -43
  47. package/src/e2ee/worker/ParticipantKeyHandler.ts +25 -27
  48. package/src/e2ee/worker/e2ee.worker.ts +61 -68
  49. package/src/room/PCTransport.ts +12 -2
  50. package/src/room/RTCEngine.ts +0 -1
  51. package/src/room/Room.ts +20 -15
  52. package/src/room/participant/LocalParticipant.ts +5 -1
  53. package/src/room/track/LocalTrack.ts +18 -10
  54. package/src/room/track/facingMode.ts +1 -1
  55. package/src/room/track/processor/types.ts +2 -1
@@ -6,17 +6,14 @@ 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 {
10
- CryptorCallbacks,
11
- CryptorEvent,
12
- DecodeRatchetOptions,
13
- KeyProviderOptions,
14
- KeySet,
15
- } from '../types';
9
+ import { CryptorCallbacks, CryptorEvent } from '../events';
10
+ import type { DecodeRatchetOptions, KeyProviderOptions, KeySet } from '../types';
16
11
  import { deriveKeys, isVideoFrame } from '../utils';
17
12
  import type { ParticipantKeyHandler } from './ParticipantKeyHandler';
18
13
  import { SifGuard } from './SifGuard';
19
14
 
15
+ export const encryptionEnabledMap: Map<string, boolean> = new Map();
16
+
20
17
  export interface FrameCryptorConstructor {
21
18
  new (opts?: unknown): BaseFrameCryptor;
22
19
  }
@@ -29,14 +26,14 @@ export interface TransformerInfo {
29
26
  }
30
27
 
31
28
  export class BaseFrameCryptor extends (EventEmitter as new () => TypedEventEmitter<CryptorCallbacks>) {
32
- encodeFunction(
29
+ protected encodeFunction(
33
30
  encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
34
31
  controller: TransformStreamDefaultController,
35
32
  ): Promise<any> {
36
33
  throw Error('not implemented for subclass');
37
34
  }
38
35
 
39
- decodeFunction(
36
+ protected decodeFunction(
40
37
  encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
41
38
  controller: TransformStreamDefaultController,
42
39
  ): Promise<any> {
@@ -51,7 +48,7 @@ export class BaseFrameCryptor extends (EventEmitter as new () => TypedEventEmitt
51
48
  export class FrameCryptor extends BaseFrameCryptor {
52
49
  private sendCounts: Map<number, number>;
53
50
 
54
- private participantId: string | undefined;
51
+ private participantIdentity: string | undefined;
55
52
 
56
53
  private trackId: string | undefined;
57
54
 
@@ -72,14 +69,14 @@ export class FrameCryptor extends BaseFrameCryptor {
72
69
 
73
70
  constructor(opts: {
74
71
  keys: ParticipantKeyHandler;
75
- participantId: string;
72
+ participantIdentity: string;
76
73
  keyProviderOptions: KeyProviderOptions;
77
74
  sifTrailer?: Uint8Array;
78
75
  }) {
79
76
  super();
80
77
  this.sendCounts = new Map();
81
78
  this.keys = opts.keys;
82
- this.participantId = opts.participantId;
79
+ this.participantIdentity = opts.participantIdentity;
83
80
  this.rtpMap = new Map();
84
81
  this.keyProviderOptions = opts.keyProviderOptions;
85
82
  this.sifTrailer = opts.sifTrailer ?? Uint8Array.from([]);
@@ -93,17 +90,25 @@ export class FrameCryptor extends BaseFrameCryptor {
93
90
  * @param keys
94
91
  */
95
92
  setParticipant(id: string, keys: ParticipantKeyHandler) {
96
- this.participantId = id;
93
+ this.participantIdentity = id;
97
94
  this.keys = keys;
98
95
  this.sifGuard.reset();
99
96
  }
100
97
 
101
98
  unsetParticipant() {
102
- this.participantId = undefined;
99
+ this.participantIdentity = undefined;
103
100
  }
104
101
 
105
- getParticipantId() {
106
- return this.participantId;
102
+ isEnabled() {
103
+ if (this.participantIdentity) {
104
+ return encryptionEnabledMap.get(this.participantIdentity);
105
+ } else {
106
+ return undefined;
107
+ }
108
+ }
109
+
110
+ getParticipantIdentity() {
111
+ return this.participantIdentity;
107
112
  }
108
113
 
109
114
  getTrackId() {
@@ -148,11 +153,15 @@ export class FrameCryptor extends BaseFrameCryptor {
148
153
  .pipeTo(writable)
149
154
  .catch((e) => {
150
155
  workerLogger.warn(e);
151
- this.emit('cryptorError', e instanceof CryptorError ? e : new CryptorError(e.message));
156
+ this.emit(CryptorEvent.Error, e instanceof CryptorError ? e : new CryptorError(e.message));
152
157
  });
153
158
  this.trackId = trackId;
154
159
  }
155
160
 
161
+ setSifTrailer(trailer: Uint8Array) {
162
+ this.sifTrailer = trailer;
163
+ }
164
+
156
165
  /**
157
166
  * Function that will be injected in a stream and will encrypt the given encoded frames.
158
167
  *
@@ -175,19 +184,26 @@ export class FrameCryptor extends BaseFrameCryptor {
175
184
  * 8) Append a single byte for the key identifier.
176
185
  * 9) Enqueue the encrypted frame for sending.
177
186
  */
178
- async encodeFunction(
187
+ protected async encodeFunction(
179
188
  encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
180
189
  controller: TransformStreamDefaultController,
181
190
  ) {
182
191
  if (
183
- !this.keys.isEnabled() ||
192
+ !this.isEnabled() ||
184
193
  // skip for encryption for empty dtx frames
185
194
  encodedFrame.data.byteLength === 0
186
195
  ) {
187
196
  return controller.enqueue(encodedFrame);
188
197
  }
189
-
190
- const { encryptionKey } = this.keys.getKeySet();
198
+ const keySet = this.keys.getKeySet();
199
+ if (!keySet) {
200
+ throw new TypeError(
201
+ `key set not found for ${
202
+ this.participantIdentity
203
+ } at index ${this.keys.getCurrentKeyIndex()}`,
204
+ );
205
+ }
206
+ const { encryptionKey } = keySet;
191
207
  const keyIndex = this.keys.getCurrentKeyIndex();
192
208
 
193
209
  if (encryptionKey) {
@@ -258,12 +274,12 @@ export class FrameCryptor extends BaseFrameCryptor {
258
274
  * @param {RTCEncodedVideoFrame|RTCEncodedAudioFrame} encodedFrame - Encoded video frame.
259
275
  * @param {TransformStreamDefaultController} controller - TransportStreamController.
260
276
  */
261
- async decodeFunction(
277
+ protected async decodeFunction(
262
278
  encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
263
279
  controller: TransformStreamDefaultController,
264
280
  ) {
265
281
  if (
266
- !this.keys.isEnabled() ||
282
+ !this.isEnabled() ||
267
283
  // skip for decryption for empty dtx frames
268
284
  encodedFrame.data.byteLength === 0
269
285
  ) {
@@ -296,14 +312,7 @@ export class FrameCryptor extends BaseFrameCryptor {
296
312
  } catch (error) {
297
313
  if (error instanceof CryptorError && error.reason === CryptorErrorReason.InvalidKey) {
298
314
  if (this.keys.hasValidKey) {
299
- workerLogger.warn('invalid key');
300
- this.emit(
301
- CryptorEvent.Error,
302
- new CryptorError(
303
- `invalid key for participant ${this.participantId}`,
304
- CryptorErrorReason.InvalidKey,
305
- ),
306
- );
315
+ this.emit(CryptorEvent.Error, error);
307
316
  this.keys.decryptionFailure();
308
317
  }
309
318
  } else {
@@ -316,7 +325,7 @@ export class FrameCryptor extends BaseFrameCryptor {
316
325
  this.emit(
317
326
  CryptorEvent.Error,
318
327
  new CryptorError(
319
- `missing key at index for participant ${this.participantId}`,
328
+ `missing key at index for participant ${this.participantIdentity}`,
320
329
  CryptorErrorReason.MissingKey,
321
330
  ),
322
331
  );
@@ -327,13 +336,16 @@ export class FrameCryptor extends BaseFrameCryptor {
327
336
  * Function that will decrypt the given encoded frame. If the decryption fails, it will
328
337
  * ratchet the key for up to RATCHET_WINDOW_SIZE times.
329
338
  */
330
- async decryptFrame(
339
+ private async decryptFrame(
331
340
  encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
332
341
  keyIndex: number,
333
342
  initialMaterial: KeySet | undefined = undefined,
334
343
  ratchetOpts: DecodeRatchetOptions = { ratchetCount: 0 },
335
344
  ): Promise<RTCEncodedVideoFrame | RTCEncodedAudioFrame | undefined> {
336
345
  const keySet = this.keys.getKeySet(keyIndex);
346
+ if (!ratchetOpts.encryptionKey && !keySet) {
347
+ throw new TypeError(`no encryption key found for decryption of ${this.participantIdentity}`);
348
+ }
337
349
 
338
350
  // Construct frame trailer. Similar to the frame header described in
339
351
  // https://tools.ietf.org/html/draft-omara-sframe-00#section-4.2
@@ -369,7 +381,7 @@ export class FrameCryptor extends BaseFrameCryptor {
369
381
  iv,
370
382
  additionalData: new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength),
371
383
  },
372
- ratchetOpts.encryptionKey ?? keySet.encryptionKey,
384
+ ratchetOpts.encryptionKey ?? keySet!.encryptionKey,
373
385
  new Uint8Array(encodedFrame.data, cipherTextStart, cipherTextLength),
374
386
  );
375
387
 
@@ -422,15 +434,15 @@ export class FrameCryptor extends BaseFrameCryptor {
422
434
  this.keys.setKeyFromMaterial(initialMaterial.material, keyIndex);
423
435
  }
424
436
 
425
- workerLogger.warn('maximum ratchet attempts exceeded, resetting key');
437
+ workerLogger.warn('maximum ratchet attempts exceeded');
426
438
  throw new CryptorError(
427
- `valid key missing for participant ${this.participantId}`,
439
+ `valid key missing for participant ${this.participantIdentity}`,
428
440
  CryptorErrorReason.InvalidKey,
429
441
  );
430
442
  }
431
443
  } else {
432
444
  throw new CryptorError(
433
- 'Decryption failed, most likely because of an invalid key',
445
+ `Decryption failed: ${error.message}`,
434
446
  CryptorErrorReason.InvalidKey,
435
447
  );
436
448
  }
@@ -477,7 +489,7 @@ export class FrameCryptor extends BaseFrameCryptor {
477
489
  return iv;
478
490
  }
479
491
 
480
- getUnencryptedBytes(frame: RTCEncodedVideoFrame | RTCEncodedAudioFrame): number {
492
+ private getUnencryptedBytes(frame: RTCEncodedVideoFrame | RTCEncodedAudioFrame): number {
481
493
  if (isVideoFrame(frame)) {
482
494
  let detectedCodec = this.getVideoCodec(frame) ?? this.videoCodec;
483
495
 
@@ -526,7 +538,7 @@ export class FrameCryptor extends BaseFrameCryptor {
526
538
  /**
527
539
  * inspects frame payloadtype if available and maps it to the codec specified in rtpMap
528
540
  */
529
- getVideoCodec(frame: RTCEncodedVideoFrame): VideoCodec | undefined {
541
+ private getVideoCodec(frame: RTCEncodedVideoFrame): VideoCodec | undefined {
530
542
  if (this.rtpMap.size === 0) {
531
543
  return undefined;
532
544
  }
@@ -535,10 +547,6 @@ export class FrameCryptor extends BaseFrameCryptor {
535
547
  const codec = payloadType ? this.rtpMap.get(payloadType) : undefined;
536
548
  return codec;
537
549
  }
538
-
539
- setSifTrailer(trailer: Uint8Array) {
540
- this.sifTrailer = trailer;
541
- }
542
550
  }
543
551
 
544
552
  /**
@@ -2,7 +2,8 @@ import { EventEmitter } from 'events';
2
2
  import type TypedEventEmitter from 'typed-emitter';
3
3
  import { workerLogger } from '../../logger';
4
4
  import { KEYRING_SIZE } from '../constants';
5
- import type { KeyProviderOptions, KeySet, ParticipantKeyHandlerCallbacks } from '../types';
5
+ import { KeyHandlerEvent, type ParticipantKeyHandlerCallbacks } from '../events';
6
+ import type { KeyProviderOptions, KeySet } from '../types';
6
7
  import { deriveKeys, importKey, ratchet } from '../utils';
7
8
 
8
9
  // TODO ParticipantKeyHandlers currently don't get destroyed on participant disconnect
@@ -19,15 +20,13 @@ import { deriveKeys, importKey, ratchet } from '../utils';
19
20
  export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEventEmitter<ParticipantKeyHandlerCallbacks>) {
20
21
  private currentKeyIndex: number;
21
22
 
22
- private cryptoKeyRing: Array<KeySet>;
23
-
24
- private enabled: boolean;
23
+ private cryptoKeyRing: Array<KeySet | undefined>;
25
24
 
26
25
  private keyProviderOptions: KeyProviderOptions;
27
26
 
28
27
  private ratchetPromiseMap: Map<number, Promise<CryptoKey>>;
29
28
 
30
- private participantId: string | undefined;
29
+ private participantIdentity: string;
31
30
 
32
31
  private decryptionFailureCount = 0;
33
32
 
@@ -37,25 +36,16 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent
37
36
  return this._hasValidKey;
38
37
  }
39
38
 
40
- constructor(
41
- participantId: string | undefined,
42
- isEnabled: boolean,
43
- keyProviderOptions: KeyProviderOptions,
44
- ) {
39
+ constructor(participantIdentity: string, keyProviderOptions: KeyProviderOptions) {
45
40
  super();
46
41
  this.currentKeyIndex = 0;
47
- this.cryptoKeyRing = new Array(KEYRING_SIZE);
48
- this.enabled = isEnabled;
42
+ this.cryptoKeyRing = new Array(KEYRING_SIZE).fill(undefined);
49
43
  this.keyProviderOptions = keyProviderOptions;
50
44
  this.ratchetPromiseMap = new Map();
51
- this.participantId = participantId;
45
+ this.participantIdentity = participantIdentity;
52
46
  this.resetKeyStatus();
53
47
  }
54
48
 
55
- setEnabled(enabled: boolean) {
56
- this.enabled = enabled;
57
- }
58
-
59
49
  decryptionFailure() {
60
50
  if (this.keyProviderOptions.failureTolerance < 0) {
61
51
  return;
@@ -63,7 +53,7 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent
63
53
  this.decryptionFailureCount += 1;
64
54
 
65
55
  if (this.decryptionFailureCount > this.keyProviderOptions.failureTolerance) {
66
- workerLogger.warn(`key for ${this.participantId} is being marked as invalid`);
56
+ workerLogger.warn(`key for ${this.participantIdentity} is being marked as invalid`);
67
57
  this._hasValidKey = false;
68
58
  }
69
59
  }
@@ -89,7 +79,7 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent
89
79
  * @param setKey
90
80
  */
91
81
  ratchetKey(keyIndex?: number, setKey = true): Promise<CryptoKey> {
92
- const currentKeyIndex = (keyIndex ??= this.getCurrentKeyIndex());
82
+ const currentKeyIndex = keyIndex ?? this.getCurrentKeyIndex();
93
83
 
94
84
  const existingPromise = this.ratchetPromiseMap.get(currentKeyIndex);
95
85
  if (typeof existingPromise !== 'undefined') {
@@ -97,7 +87,13 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent
97
87
  }
98
88
  const ratchetPromise = new Promise<CryptoKey>(async (resolve, reject) => {
99
89
  try {
100
- const currentMaterial = this.getKeySet(currentKeyIndex).material;
90
+ const keySet = this.getKeySet(currentKeyIndex);
91
+ if (!keySet) {
92
+ throw new TypeError(
93
+ `Cannot ratchet key without a valid keyset of participant ${this.participantIdentity}`,
94
+ );
95
+ }
96
+ const currentMaterial = keySet.material;
101
97
  const newMaterial = await importKey(
102
98
  await ratchet(currentMaterial, this.keyProviderOptions.ratchetSalt),
103
99
  currentMaterial.algorithm.name,
@@ -106,8 +102,13 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent
106
102
 
107
103
  if (setKey) {
108
104
  this.setKeyFromMaterial(newMaterial, currentKeyIndex, true);
105
+ this.emit(
106
+ KeyHandlerEvent.KeyRatcheted,
107
+ newMaterial,
108
+ this.participantIdentity,
109
+ currentKeyIndex,
110
+ );
109
111
  }
110
- this.emit('keyRatcheted', newMaterial, keyIndex, this.participantId);
111
112
  resolve(newMaterial);
112
113
  } catch (e) {
113
114
  reject(e);
@@ -145,10 +146,11 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent
145
146
  this.setKeySet(keySet, this.currentKeyIndex, emitRatchetEvent);
146
147
  }
147
148
 
148
- async setKeySet(keySet: KeySet, keyIndex: number, emitRatchetEvent = false) {
149
+ setKeySet(keySet: KeySet, keyIndex: number, emitRatchetEvent = false) {
149
150
  this.cryptoKeyRing[keyIndex % this.cryptoKeyRing.length] = keySet;
151
+
150
152
  if (emitRatchetEvent) {
151
- this.emit('keyRatcheted', keySet.material, keyIndex, this.participantId);
153
+ this.emit(KeyHandlerEvent.KeyRatcheted, keySet.material, this.participantIdentity, keyIndex);
152
154
  }
153
155
  }
154
156
 
@@ -157,10 +159,6 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent
157
159
  this.resetKeyStatus();
158
160
  }
159
161
 
160
- isEnabled() {
161
- return this.enabled;
162
- }
163
-
164
162
  getCurrentKeyIndex() {
165
163
  return this.currentKeyIndex;
166
164
  }
@@ -1,22 +1,21 @@
1
1
  import { workerLogger } from '../../logger';
2
2
  import { KEY_PROVIDER_DEFAULTS } from '../constants';
3
3
  import { CryptorErrorReason } from '../errors';
4
+ import { CryptorEvent, KeyHandlerEvent } from '../events';
4
5
  import type {
5
6
  E2EEWorkerMessage,
6
- EnableMessage,
7
7
  ErrorMessage,
8
+ InitAck,
8
9
  KeyProviderOptions,
9
10
  RatchetMessage,
10
11
  RatchetRequestMessage,
11
12
  } from '../types';
12
- import { FrameCryptor } from './FrameCryptor';
13
+ import { FrameCryptor, encryptionEnabledMap } from './FrameCryptor';
13
14
  import { ParticipantKeyHandler } from './ParticipantKeyHandler';
14
15
 
15
16
  const participantCryptors: FrameCryptor[] = [];
16
17
  const participantKeys: Map<string, ParticipantKeyHandler> = new Map();
17
-
18
- let publishCryptors: FrameCryptor[] = [];
19
- let publisherKeys: ParticipantKeyHandler;
18
+ let sharedKeyHandler: ParticipantKeyHandler | undefined;
20
19
 
21
20
  let isEncryptionEnabled: boolean = false;
22
21
 
@@ -39,22 +38,20 @@ onmessage = (ev) => {
39
38
  keyProviderOptions = data.keyProviderOptions;
40
39
  useSharedKey = !!data.keyProviderOptions.sharedKey;
41
40
  // acknowledge init successful
42
- const enableMsg: EnableMessage = {
43
- kind: 'enable',
41
+ const ackMsg: InitAck = {
42
+ kind: 'initAck',
44
43
  data: { enabled: isEncryptionEnabled },
45
44
  };
46
- publisherKeys = new ParticipantKeyHandler(undefined, isEncryptionEnabled, keyProviderOptions);
47
- publisherKeys.on('keyRatcheted', emitRatchetedKeys);
48
- postMessage(enableMsg);
45
+ postMessage(ackMsg);
49
46
  break;
50
47
  case 'enable':
51
- setEncryptionEnabled(data.enabled, data.participantId);
48
+ setEncryptionEnabled(data.enabled, data.participantIdentity);
52
49
  workerLogger.info('updated e2ee enabled status');
53
50
  // acknowledge enable call successful
54
51
  postMessage(ev.data);
55
52
  break;
56
53
  case 'decode':
57
- let cryptor = getTrackCryptor(data.participantId, data.trackId);
54
+ let cryptor = getTrackCryptor(data.participantIdentity, data.trackId);
58
55
  cryptor.setupTransform(
59
56
  kind,
60
57
  data.readableStream,
@@ -64,7 +61,7 @@ onmessage = (ev) => {
64
61
  );
65
62
  break;
66
63
  case 'encode':
67
- let pubCryptor = getPublisherCryptor(data.trackId);
64
+ let pubCryptor = getTrackCryptor(data.participantIdentity, data.trackId);
68
65
  pubCryptor.setupTransform(
69
66
  kind,
70
67
  data.readableStream,
@@ -75,10 +72,11 @@ onmessage = (ev) => {
75
72
  break;
76
73
  case 'setKey':
77
74
  if (useSharedKey) {
78
- workerLogger.debug('set shared key');
75
+ workerLogger.warn('set shared key');
79
76
  setSharedKey(data.key, data.keyIndex);
80
- } else if (data.participantId) {
81
- getParticipantKeyHandler(data.participantId).setKey(data.key, data.keyIndex);
77
+ } else if (data.participantIdentity) {
78
+ workerLogger.warn(`set participant sender key ${data.participantIdentity}`);
79
+ getParticipantKeyHandler(data.participantIdentity).setKey(data.key, data.keyIndex);
82
80
  } else {
83
81
  workerLogger.error('no participant Id was provided and shared key usage is disabled');
84
82
  }
@@ -87,11 +85,14 @@ onmessage = (ev) => {
87
85
  unsetCryptorParticipant(data.trackId);
88
86
  break;
89
87
  case 'updateCodec':
90
- getTrackCryptor(data.participantId, data.trackId).setVideoCodec(data.codec);
88
+ getTrackCryptor(data.participantIdentity, data.trackId).setVideoCodec(data.codec);
91
89
  break;
92
90
  case 'setRTPMap':
93
- publishCryptors.forEach((cr) => {
94
- cr.setRtpMap(data.map);
91
+ // this is only used for the local participant
92
+ participantCryptors.forEach((cr) => {
93
+ if (cr.getParticipantIdentity() === data.participantIdentity) {
94
+ cr.setRtpMap(data.map);
95
+ }
95
96
  });
96
97
  break;
97
98
  case 'ratchetRequest':
@@ -106,92 +107,85 @@ onmessage = (ev) => {
106
107
  };
107
108
 
108
109
  async function handleRatchetRequest(data: RatchetRequestMessage['data']) {
109
- const keyHandler = getParticipantKeyHandler(data.participantId);
110
- await keyHandler.ratchetKey(data.keyIndex);
111
- keyHandler.resetKeyStatus();
110
+ if (useSharedKey) {
111
+ const keyHandler = getSharedKeyHandler();
112
+ await keyHandler.ratchetKey(data.keyIndex);
113
+ keyHandler.resetKeyStatus();
114
+ } else if (data.participantIdentity) {
115
+ const keyHandler = getParticipantKeyHandler(data.participantIdentity);
116
+ await keyHandler.ratchetKey(data.keyIndex);
117
+ keyHandler.resetKeyStatus();
118
+ } else {
119
+ workerLogger.error(
120
+ 'no participant Id was provided for ratchet request and shared key usage is disabled',
121
+ );
122
+ }
112
123
  }
113
124
 
114
- function getTrackCryptor(participantId: string, trackId: string) {
125
+ function getTrackCryptor(participantIdentity: string, trackId: string) {
115
126
  let cryptor = participantCryptors.find((c) => c.getTrackId() === trackId);
116
127
  if (!cryptor) {
117
- workerLogger.info('creating new cryptor for', { participantId });
128
+ workerLogger.info('creating new cryptor for', { participantIdentity });
118
129
  if (!keyProviderOptions) {
119
130
  throw Error('Missing keyProvider options');
120
131
  }
121
132
  cryptor = new FrameCryptor({
122
- participantId,
123
- keys: getParticipantKeyHandler(participantId),
133
+ participantIdentity,
134
+ keys: getParticipantKeyHandler(participantIdentity),
124
135
  keyProviderOptions,
125
136
  sifTrailer,
126
137
  });
127
138
 
128
139
  setupCryptorErrorEvents(cryptor);
129
140
  participantCryptors.push(cryptor);
130
- } else if (participantId !== cryptor.getParticipantId()) {
141
+ } else if (participantIdentity !== cryptor.getParticipantIdentity()) {
131
142
  // assign new participant id to track cryptor and pass in correct key handler
132
- cryptor.setParticipant(participantId, getParticipantKeyHandler(participantId));
143
+ cryptor.setParticipant(participantIdentity, getParticipantKeyHandler(participantIdentity));
133
144
  }
134
145
  if (sharedKey) {
135
146
  }
136
147
  return cryptor;
137
148
  }
138
149
 
139
- function getParticipantKeyHandler(participantId?: string) {
140
- if (!participantId) {
141
- return publisherKeys!;
150
+ function getParticipantKeyHandler(participantIdentity: string) {
151
+ if (useSharedKey) {
152
+ return getSharedKeyHandler();
142
153
  }
143
- let keys = participantKeys.get(participantId);
154
+ let keys = participantKeys.get(participantIdentity);
144
155
  if (!keys) {
145
- keys = new ParticipantKeyHandler(participantId, true, keyProviderOptions);
156
+ keys = new ParticipantKeyHandler(participantIdentity, keyProviderOptions);
146
157
  if (sharedKey) {
147
158
  keys.setKey(sharedKey);
148
159
  }
149
- participantKeys.set(participantId, keys);
160
+ keys.on(KeyHandlerEvent.KeyRatcheted, emitRatchetedKeys);
161
+ participantKeys.set(participantIdentity, keys);
150
162
  }
151
163
  return keys;
152
164
  }
153
165
 
154
- function unsetCryptorParticipant(trackId: string) {
155
- participantCryptors.find((c) => c.getTrackId() === trackId)?.unsetParticipant();
166
+ function getSharedKeyHandler() {
167
+ if (!sharedKeyHandler) {
168
+ sharedKeyHandler = new ParticipantKeyHandler('shared-key', keyProviderOptions);
169
+ }
170
+ return sharedKeyHandler;
156
171
  }
157
172
 
158
- function getPublisherCryptor(trackId: string) {
159
- let publishCryptor = publishCryptors.find((cryptor) => cryptor.getTrackId() === trackId);
160
- if (!publishCryptor) {
161
- if (!keyProviderOptions) {
162
- throw new TypeError('Missing keyProvider options');
163
- }
164
- publishCryptor = new FrameCryptor({
165
- keys: publisherKeys!,
166
- participantId: 'publisher',
167
- keyProviderOptions,
168
- });
169
- setupCryptorErrorEvents(publishCryptor);
170
- publishCryptors.push(publishCryptor);
171
- }
172
- return publishCryptor;
173
+ function unsetCryptorParticipant(trackId: string) {
174
+ participantCryptors.find((c) => c.getTrackId() === trackId)?.unsetParticipant();
173
175
  }
174
176
 
175
- function setEncryptionEnabled(enable: boolean, participantId?: string) {
176
- if (!participantId) {
177
- isEncryptionEnabled = enable;
178
- publisherKeys.setEnabled(enable);
179
- } else {
180
- getParticipantKeyHandler(participantId).setEnabled(enable);
181
- }
177
+ function setEncryptionEnabled(enable: boolean, participantIdentity: string) {
178
+ encryptionEnabledMap.set(participantIdentity, enable);
182
179
  }
183
180
 
184
181
  function setSharedKey(key: CryptoKey, index?: number) {
185
182
  workerLogger.debug('setting shared key');
186
183
  sharedKey = key;
187
- publisherKeys?.setKey(key, index);
188
- for (const [, keyHandler] of participantKeys) {
189
- keyHandler.setKey(key, index);
190
- }
184
+ getSharedKeyHandler().setKey(key, index);
191
185
  }
192
186
 
193
187
  function setupCryptorErrorEvents(cryptor: FrameCryptor) {
194
- cryptor.on('cryptorError', (error) => {
188
+ cryptor.on(CryptorEvent.Error, (error) => {
195
189
  const msg: ErrorMessage = {
196
190
  kind: 'error',
197
191
  data: { error: new Error(`${CryptorErrorReason[error.reason]}: ${error.message}`) },
@@ -200,11 +194,11 @@ function setupCryptorErrorEvents(cryptor: FrameCryptor) {
200
194
  });
201
195
  }
202
196
 
203
- function emitRatchetedKeys(material: CryptoKey, keyIndex?: number) {
197
+ function emitRatchetedKeys(material: CryptoKey, participantIdentity: string, keyIndex?: number) {
204
198
  const msg: RatchetMessage = {
205
199
  kind: `ratchetKey`,
206
200
  data: {
207
- // participantId,
201
+ participantIdentity,
208
202
  keyIndex,
209
203
  material,
210
204
  },
@@ -228,9 +222,8 @@ if (self.RTCTransformEvent) {
228
222
  const transformer = event.transformer;
229
223
  workerLogger.debug('transformer', transformer);
230
224
  transformer.handled = true;
231
- const { kind, participantId, trackId, codec } = transformer.options;
232
- const cryptor =
233
- kind === 'encode' ? getPublisherCryptor(trackId) : getTrackCryptor(participantId, trackId);
225
+ const { kind, participantIdentity, trackId, codec } = transformer.options;
226
+ const cryptor = getTrackCryptor(participantIdentity, trackId);
234
227
  workerLogger.debug('transform', { codec });
235
228
  cryptor.setupTransform(kind, transformer.readable, transformer.writable, trackId, codec);
236
229
  };
@@ -1,6 +1,6 @@
1
1
  import { EventEmitter } from 'events';
2
- import { parse, write } from 'sdp-transform';
3
2
  import type { MediaDescription } from 'sdp-transform';
3
+ import { parse, write } from 'sdp-transform';
4
4
  import { debounce } from 'ts-debounce';
5
5
  import log from '../logger';
6
6
  import { NegotiationError, UnexpectedConnectionState } from './errors';
@@ -16,7 +16,7 @@ interface TrackBitrateInfo {
16
16
 
17
17
  /* The svc codec (av1/vp9) would use a very low bitrate at the begining and
18
18
  increase slowly by the bandwidth estimator until it reach the target bitrate. The
19
- process commonly cost more than 10 seconds cause subscriber will get blur video at
19
+ process commonly cost more than 10 seconds cause subscriber will get blur video at
20
20
  the first few seconds. So we use a 70% of target bitrate here as the start bitrate to
21
21
  eliminate this issue.
22
22
  */
@@ -308,6 +308,7 @@ export default class PCTransport extends EventEmitter {
308
308
  } catch (e) {
309
309
  log.warn(`not able to set ${sd.type}, falling back to unmodified sdp`, {
310
310
  error: e,
311
+ sdp: munged,
311
312
  });
312
313
  sd.sdp = originalSdp;
313
314
  }
@@ -328,6 +329,15 @@ export default class PCTransport extends EventEmitter {
328
329
  } else if (typeof e === 'string') {
329
330
  msg = e;
330
331
  }
332
+
333
+ const fields: any = {
334
+ error: msg,
335
+ sdp: sd.sdp,
336
+ };
337
+ if (!remote && this.pc.remoteDescription) {
338
+ fields.remoteSdp = this.pc.remoteDescription;
339
+ }
340
+ log.error(`unable to set ${sd.type}`, fields);
331
341
  throw new NegotiationError(msg);
332
342
  }
333
343
  }
@@ -211,7 +211,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
211
211
  }
212
212
 
213
213
  this.clientConfiguration = joinResponse.clientConfiguration;
214
-
215
214
  return joinResponse;
216
215
  } catch (e) {
217
216
  if (e instanceof ConnectionError) {