livekit-client 1.13.0 → 1.13.1

Sign up to get free protection for your applications and to get access to all the features.
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) {