livekit-client 1.13.0 → 1.13.1

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 +121 -104
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +161 -98
  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 +50 -36
  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 +4 -0
  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,11 +312,10 @@ 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
315
  this.emit(
301
316
  CryptorEvent.Error,
302
317
  new CryptorError(
303
- `invalid key for participant ${this.participantId}`,
318
+ `invalid key for participant ${this.participantIdentity}`,
304
319
  CryptorErrorReason.InvalidKey,
305
320
  ),
306
321
  );
@@ -316,7 +331,7 @@ export class FrameCryptor extends BaseFrameCryptor {
316
331
  this.emit(
317
332
  CryptorEvent.Error,
318
333
  new CryptorError(
319
- `missing key at index for participant ${this.participantId}`,
334
+ `missing key at index for participant ${this.participantIdentity}`,
320
335
  CryptorErrorReason.MissingKey,
321
336
  ),
322
337
  );
@@ -327,13 +342,16 @@ export class FrameCryptor extends BaseFrameCryptor {
327
342
  * Function that will decrypt the given encoded frame. If the decryption fails, it will
328
343
  * ratchet the key for up to RATCHET_WINDOW_SIZE times.
329
344
  */
330
- async decryptFrame(
345
+ private async decryptFrame(
331
346
  encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
332
347
  keyIndex: number,
333
348
  initialMaterial: KeySet | undefined = undefined,
334
349
  ratchetOpts: DecodeRatchetOptions = { ratchetCount: 0 },
335
350
  ): Promise<RTCEncodedVideoFrame | RTCEncodedAudioFrame | undefined> {
336
351
  const keySet = this.keys.getKeySet(keyIndex);
352
+ if (!ratchetOpts.encryptionKey && !keySet) {
353
+ throw new TypeError(`no encryption key found for decryption of ${this.participantIdentity}`);
354
+ }
337
355
 
338
356
  // Construct frame trailer. Similar to the frame header described in
339
357
  // https://tools.ietf.org/html/draft-omara-sframe-00#section-4.2
@@ -369,7 +387,7 @@ export class FrameCryptor extends BaseFrameCryptor {
369
387
  iv,
370
388
  additionalData: new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength),
371
389
  },
372
- ratchetOpts.encryptionKey ?? keySet.encryptionKey,
390
+ ratchetOpts.encryptionKey ?? keySet!.encryptionKey,
373
391
  new Uint8Array(encodedFrame.data, cipherTextStart, cipherTextLength),
374
392
  );
375
393
 
@@ -422,9 +440,9 @@ export class FrameCryptor extends BaseFrameCryptor {
422
440
  this.keys.setKeyFromMaterial(initialMaterial.material, keyIndex);
423
441
  }
424
442
 
425
- workerLogger.warn('maximum ratchet attempts exceeded, resetting key');
443
+ workerLogger.warn('maximum ratchet attempts exceeded');
426
444
  throw new CryptorError(
427
- `valid key missing for participant ${this.participantId}`,
445
+ `valid key missing for participant ${this.participantIdentity}`,
428
446
  CryptorErrorReason.InvalidKey,
429
447
  );
430
448
  }
@@ -477,7 +495,7 @@ export class FrameCryptor extends BaseFrameCryptor {
477
495
  return iv;
478
496
  }
479
497
 
480
- getUnencryptedBytes(frame: RTCEncodedVideoFrame | RTCEncodedAudioFrame): number {
498
+ private getUnencryptedBytes(frame: RTCEncodedVideoFrame | RTCEncodedAudioFrame): number {
481
499
  if (isVideoFrame(frame)) {
482
500
  let detectedCodec = this.getVideoCodec(frame) ?? this.videoCodec;
483
501
 
@@ -526,7 +544,7 @@ export class FrameCryptor extends BaseFrameCryptor {
526
544
  /**
527
545
  * inspects frame payloadtype if available and maps it to the codec specified in rtpMap
528
546
  */
529
- getVideoCodec(frame: RTCEncodedVideoFrame): VideoCodec | undefined {
547
+ private getVideoCodec(frame: RTCEncodedVideoFrame): VideoCodec | undefined {
530
548
  if (this.rtpMap.size === 0) {
531
549
  return undefined;
532
550
  }
@@ -535,10 +553,6 @@ export class FrameCryptor extends BaseFrameCryptor {
535
553
  const codec = payloadType ? this.rtpMap.get(payloadType) : undefined;
536
554
  return codec;
537
555
  }
538
-
539
- setSifTrailer(trailer: Uint8Array) {
540
- this.sifTrailer = trailer;
541
- }
542
556
  }
543
557
 
544
558
  /**
@@ -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) {