livekit-client 1.11.4 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. package/README.md +1 -3
  2. package/dist/livekit-client.e2ee.worker.js +2 -0
  3. package/dist/livekit-client.e2ee.worker.js.map +1 -0
  4. package/dist/livekit-client.e2ee.worker.mjs +1525 -0
  5. package/dist/livekit-client.e2ee.worker.mjs.map +1 -0
  6. package/dist/livekit-client.esm.mjs +4749 -4055
  7. package/dist/livekit-client.esm.mjs.map +1 -1
  8. package/dist/livekit-client.umd.js +1 -1
  9. package/dist/livekit-client.umd.js.map +1 -1
  10. package/dist/src/api/SignalClient.d.ts +4 -1
  11. package/dist/src/api/SignalClient.d.ts.map +1 -1
  12. package/dist/src/connectionHelper/checks/turn.d.ts.map +1 -1
  13. package/dist/src/connectionHelper/checks/websocket.d.ts.map +1 -1
  14. package/dist/src/e2ee/E2eeManager.d.ts +45 -0
  15. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -0
  16. package/dist/src/e2ee/KeyProvider.d.ts +42 -0
  17. package/dist/src/e2ee/KeyProvider.d.ts.map +1 -0
  18. package/dist/src/e2ee/constants.d.ts +14 -0
  19. package/dist/src/e2ee/constants.d.ts.map +1 -0
  20. package/dist/src/e2ee/errors.d.ts +11 -0
  21. package/dist/src/e2ee/errors.d.ts.map +1 -0
  22. package/dist/src/e2ee/index.d.ts +4 -0
  23. package/dist/src/e2ee/index.d.ts.map +1 -0
  24. package/dist/src/e2ee/types.d.ts +129 -0
  25. package/dist/src/e2ee/types.d.ts.map +1 -0
  26. package/dist/src/e2ee/utils.d.ts +24 -0
  27. package/dist/src/e2ee/utils.d.ts.map +1 -0
  28. package/dist/src/e2ee/worker/FrameCryptor.d.ts +175 -0
  29. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -0
  30. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +46 -0
  31. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -0
  32. package/dist/src/e2ee/worker/e2ee.worker.d.ts +2 -0
  33. package/dist/src/e2ee/worker/e2ee.worker.d.ts.map +1 -0
  34. package/dist/src/index.d.ts +1 -0
  35. package/dist/src/index.d.ts.map +1 -1
  36. package/dist/src/logger.d.ts +4 -1
  37. package/dist/src/logger.d.ts.map +1 -1
  38. package/dist/src/options.d.ts +5 -0
  39. package/dist/src/options.d.ts.map +1 -1
  40. package/dist/src/proto/livekit_models.d.ts +2 -2
  41. package/dist/src/proto/livekit_models.d.ts.map +1 -1
  42. package/dist/src/room/PCTransport.d.ts +3 -1
  43. package/dist/src/room/PCTransport.d.ts.map +1 -1
  44. package/dist/src/room/RTCEngine.d.ts +17 -3
  45. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  46. package/dist/src/room/Room.d.ts +10 -0
  47. package/dist/src/room/Room.d.ts.map +1 -1
  48. package/dist/src/room/events.d.ts +14 -2
  49. package/dist/src/room/events.d.ts.map +1 -1
  50. package/dist/src/room/participant/LocalParticipant.d.ts +7 -2
  51. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  52. package/dist/src/room/participant/Participant.d.ts +1 -0
  53. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  54. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  55. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  56. package/dist/src/room/track/TrackPublication.d.ts +3 -0
  57. package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
  58. package/dist/src/room/track/options.d.ts +2 -2
  59. package/dist/src/room/track/options.d.ts.map +1 -1
  60. package/dist/src/room/track/utils.d.ts +9 -0
  61. package/dist/src/room/track/utils.d.ts.map +1 -1
  62. package/dist/src/room/utils.d.ts +2 -0
  63. package/dist/src/room/utils.d.ts.map +1 -1
  64. package/dist/src/test/MockMediaStreamTrack.d.ts.map +1 -1
  65. package/dist/ts4.2/src/api/SignalClient.d.ts +4 -1
  66. package/dist/ts4.2/src/e2ee/E2eeManager.d.ts +45 -0
  67. package/dist/ts4.2/src/e2ee/KeyProvider.d.ts +42 -0
  68. package/dist/ts4.2/src/e2ee/constants.d.ts +14 -0
  69. package/dist/ts4.2/src/e2ee/errors.d.ts +11 -0
  70. package/dist/ts4.2/src/e2ee/index.d.ts +4 -0
  71. package/dist/ts4.2/src/e2ee/types.d.ts +129 -0
  72. package/dist/ts4.2/src/e2ee/utils.d.ts +24 -0
  73. package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +175 -0
  74. package/dist/ts4.2/src/e2ee/worker/ParticipantKeyHandler.d.ts +46 -0
  75. package/dist/ts4.2/src/e2ee/worker/e2ee.worker.d.ts +2 -0
  76. package/dist/ts4.2/src/index.d.ts +1 -0
  77. package/dist/ts4.2/src/logger.d.ts +4 -1
  78. package/dist/ts4.2/src/options.d.ts +5 -0
  79. package/dist/ts4.2/src/proto/livekit_models.d.ts +2 -2
  80. package/dist/ts4.2/src/room/PCTransport.d.ts +3 -1
  81. package/dist/ts4.2/src/room/RTCEngine.d.ts +17 -3
  82. package/dist/ts4.2/src/room/Room.d.ts +10 -0
  83. package/dist/ts4.2/src/room/events.d.ts +14 -2
  84. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +7 -2
  85. package/dist/ts4.2/src/room/participant/Participant.d.ts +1 -0
  86. package/dist/ts4.2/src/room/track/TrackPublication.d.ts +3 -0
  87. package/dist/ts4.2/src/room/track/options.d.ts +6 -6
  88. package/dist/ts4.2/src/room/track/utils.d.ts +9 -0
  89. package/dist/ts4.2/src/room/utils.d.ts +2 -0
  90. package/package.json +17 -7
  91. package/src/api/SignalClient.ts +28 -9
  92. package/src/connectionHelper/checks/turn.ts +1 -0
  93. package/src/connectionHelper/checks/websocket.ts +1 -0
  94. package/src/e2ee/E2eeManager.ts +374 -0
  95. package/src/e2ee/KeyProvider.ts +77 -0
  96. package/src/e2ee/constants.ts +40 -0
  97. package/src/e2ee/errors.ts +16 -0
  98. package/src/e2ee/index.ts +3 -0
  99. package/src/e2ee/types.ts +160 -0
  100. package/src/e2ee/utils.ts +127 -0
  101. package/src/e2ee/worker/FrameCryptor.test.ts +21 -0
  102. package/src/e2ee/worker/FrameCryptor.ts +614 -0
  103. package/src/e2ee/worker/ParticipantKeyHandler.ts +129 -0
  104. package/src/e2ee/worker/e2ee.worker.ts +217 -0
  105. package/src/e2ee/worker/tsconfig.json +6 -0
  106. package/src/index.ts +1 -0
  107. package/src/logger.ts +10 -2
  108. package/src/options.ts +6 -0
  109. package/src/proto/livekit_models.ts +12 -12
  110. package/src/room/PCTransport.ts +39 -9
  111. package/src/room/RTCEngine.ts +127 -34
  112. package/src/room/Room.ts +77 -22
  113. package/src/room/defaults.ts +1 -1
  114. package/src/room/events.ts +14 -0
  115. package/src/room/participant/LocalParticipant.ts +52 -8
  116. package/src/room/participant/Participant.ts +4 -0
  117. package/src/room/track/LocalTrack.ts +5 -4
  118. package/src/room/track/RemoteVideoTrack.ts +2 -2
  119. package/src/room/track/TrackPublication.ts +9 -1
  120. package/src/room/track/options.ts +3 -2
  121. package/src/room/track/utils.ts +27 -0
  122. package/src/room/utils.ts +5 -0
  123. package/src/room/worker.d.ts +4 -0
  124. package/src/test/MockMediaStreamTrack.ts +1 -0
@@ -0,0 +1,129 @@
1
+ import EventEmitter from 'eventemitter3';
2
+ import { workerLogger } from '../../logger';
3
+ import { KEYRING_SIZE } from '../constants';
4
+ import type { KeyProviderOptions, KeySet, ParticipantKeyHandlerCallbacks } from '../types';
5
+ import { deriveKeys, importKey, ratchet } from '../utils';
6
+
7
+ // TODO ParticipantKeyHandlers currently don't get destroyed on participant disconnect
8
+ // we could do this by having a separate worker message on participant disconnected.
9
+
10
+ /**
11
+ * ParticipantKeyHandler is responsible for providing a cryptor instance with the
12
+ * en-/decryption key of a participant. It assumes that all tracks of a specific participant
13
+ * are encrypted with the same key.
14
+ * Additionally it exposes a method to ratchet a key which can be used by the cryptor either automatically
15
+ * if decryption fails or can be triggered manually on both sender and receiver side.
16
+ *
17
+ */
18
+ export class ParticipantKeyHandler extends EventEmitter<ParticipantKeyHandlerCallbacks> {
19
+ private currentKeyIndex: number;
20
+
21
+ private cryptoKeyRing: Array<KeySet>;
22
+
23
+ private enabled: boolean;
24
+
25
+ private keyProviderOptions: KeyProviderOptions;
26
+
27
+ private ratchetPromiseMap: Map<number, Promise<CryptoKey>>;
28
+
29
+ private participantId: string | undefined;
30
+
31
+ constructor(
32
+ participantId: string | undefined,
33
+ isEnabled: boolean,
34
+ keyProviderOptions: KeyProviderOptions,
35
+ ) {
36
+ super();
37
+ this.currentKeyIndex = 0;
38
+ this.cryptoKeyRing = new Array(KEYRING_SIZE);
39
+ this.enabled = isEnabled;
40
+ this.keyProviderOptions = keyProviderOptions;
41
+ this.ratchetPromiseMap = new Map();
42
+ this.participantId = participantId;
43
+ }
44
+
45
+ setEnabled(enabled: boolean) {
46
+ this.enabled = enabled;
47
+ }
48
+
49
+ /**
50
+ * Ratchets the current key (or the one at keyIndex if provided) and
51
+ * returns the ratcheted material
52
+ * if `setKey` is true (default), it will also set the ratcheted key directly on the crypto key ring
53
+ * @param keyIndex
54
+ * @param setKey
55
+ */
56
+ ratchetKey(keyIndex?: number, setKey = true): Promise<CryptoKey> {
57
+ const currentKeyIndex = (keyIndex ??= this.getCurrentKeyIndex());
58
+
59
+ const existingPromise = this.ratchetPromiseMap.get(currentKeyIndex);
60
+ if (typeof existingPromise !== 'undefined') {
61
+ return existingPromise;
62
+ }
63
+ const ratchetPromise = new Promise<CryptoKey>(async (resolve, reject) => {
64
+ try {
65
+ const currentMaterial = this.getKeySet(currentKeyIndex).material;
66
+ const newMaterial = await importKey(
67
+ await ratchet(currentMaterial, this.keyProviderOptions.ratchetSalt),
68
+ currentMaterial.algorithm.name,
69
+ 'derive',
70
+ );
71
+
72
+ if (setKey) {
73
+ this.setKeyFromMaterial(newMaterial, currentKeyIndex, true);
74
+ }
75
+ this.emit('keyRatcheted', newMaterial, keyIndex, this.participantId);
76
+ resolve(newMaterial);
77
+ } catch (e) {
78
+ reject(e);
79
+ } finally {
80
+ this.ratchetPromiseMap.delete(currentKeyIndex);
81
+ }
82
+ });
83
+ this.ratchetPromiseMap.set(currentKeyIndex, ratchetPromise);
84
+ return ratchetPromise;
85
+ }
86
+
87
+ /**
88
+ * takes in a key material with `deriveBits` and `deriveKey` set as key usages
89
+ * and derives encryption keys from the material and sets it on the key ring buffer
90
+ * together with the material
91
+ * also updates the currentKeyIndex
92
+ */
93
+ async setKeyFromMaterial(material: CryptoKey, keyIndex = 0, emitRatchetEvent = false) {
94
+ workerLogger.debug('setting new key');
95
+ if (keyIndex >= 0) {
96
+ this.currentKeyIndex = keyIndex % this.cryptoKeyRing.length;
97
+ }
98
+ const keySet = await deriveKeys(material, this.keyProviderOptions.ratchetSalt);
99
+ this.setKeySet(keySet, this.currentKeyIndex, emitRatchetEvent);
100
+ }
101
+
102
+ async setKeySet(keySet: KeySet, keyIndex: number, emitRatchetEvent = false) {
103
+ this.cryptoKeyRing[keyIndex % this.cryptoKeyRing.length] = keySet;
104
+ if (emitRatchetEvent) {
105
+ this.emit('keyRatcheted', keySet.material, keyIndex, this.participantId);
106
+ }
107
+ }
108
+
109
+ async setCurrentKeyIndex(index: number) {
110
+ this.currentKeyIndex = index % this.cryptoKeyRing.length;
111
+ }
112
+
113
+ isEnabled() {
114
+ return this.enabled;
115
+ }
116
+
117
+ getCurrentKeyIndex() {
118
+ return this.currentKeyIndex;
119
+ }
120
+
121
+ /**
122
+ * returns currently used KeySet or the one at `keyIndex` if provided
123
+ * @param keyIndex
124
+ * @returns
125
+ */
126
+ getKeySet(keyIndex?: number) {
127
+ return this.cryptoKeyRing[keyIndex ?? this.currentKeyIndex];
128
+ }
129
+ }
@@ -0,0 +1,217 @@
1
+ import { workerLogger } from '../../logger';
2
+ import { KEY_PROVIDER_DEFAULTS } from '../constants';
3
+ import { CryptorErrorReason } from '../errors';
4
+ import type {
5
+ E2EEWorkerMessage,
6
+ EnableMessage,
7
+ ErrorMessage,
8
+ KeyProviderOptions,
9
+ RatchetMessage,
10
+ } from '../types';
11
+ import { FrameCryptor } from './FrameCryptor';
12
+ import { ParticipantKeyHandler } from './ParticipantKeyHandler';
13
+
14
+ const participantCryptors: FrameCryptor[] = [];
15
+ const participantKeys: Map<string, ParticipantKeyHandler> = new Map();
16
+
17
+ let publishCryptors: FrameCryptor[] = [];
18
+ let publisherKeys: ParticipantKeyHandler;
19
+
20
+ let isEncryptionEnabled: boolean = false;
21
+
22
+ let useSharedKey: boolean = false;
23
+
24
+ let sharedKey: CryptoKey | undefined;
25
+
26
+ let keyProviderOptions: KeyProviderOptions = KEY_PROVIDER_DEFAULTS;
27
+
28
+ workerLogger.setDefaultLevel('info');
29
+
30
+ onmessage = (ev) => {
31
+ const { kind, data }: E2EEWorkerMessage = ev.data;
32
+
33
+ switch (kind) {
34
+ case 'init':
35
+ workerLogger.info('worker initialized');
36
+ keyProviderOptions = data.keyProviderOptions;
37
+ useSharedKey = !!data.keyProviderOptions.sharedKey;
38
+ // acknowledge init successful
39
+ const enableMsg: EnableMessage = {
40
+ kind: 'enable',
41
+ data: { enabled: isEncryptionEnabled },
42
+ };
43
+ publisherKeys = new ParticipantKeyHandler(undefined, isEncryptionEnabled, keyProviderOptions);
44
+ publisherKeys.on('keyRatcheted', emitRatchetedKeys);
45
+ postMessage(enableMsg);
46
+ break;
47
+ case 'enable':
48
+ setEncryptionEnabled(data.enabled, data.participantId);
49
+ workerLogger.info('updated e2ee enabled status');
50
+ // acknowledge enable call successful
51
+ postMessage(ev.data);
52
+ break;
53
+ case 'decode':
54
+ let cryptor = getTrackCryptor(data.participantId, data.trackId);
55
+ cryptor.setupTransform(
56
+ kind,
57
+ data.readableStream,
58
+ data.writableStream,
59
+ data.trackId,
60
+ data.codec,
61
+ );
62
+ break;
63
+ case 'encode':
64
+ let pubCryptor = getPublisherCryptor(data.trackId);
65
+ pubCryptor.setupTransform(
66
+ kind,
67
+ data.readableStream,
68
+ data.writableStream,
69
+ data.trackId,
70
+ data.codec,
71
+ );
72
+ break;
73
+ case 'setKey':
74
+ if (useSharedKey) {
75
+ workerLogger.debug('set shared key');
76
+ setSharedKey(data.key, data.keyIndex);
77
+ } else if (data.participantId) {
78
+ getParticipantKeyHandler(data.participantId).setKeyFromMaterial(data.key, data.keyIndex);
79
+ } else {
80
+ workerLogger.error('no participant Id was provided and shared key usage is disabled');
81
+ }
82
+ break;
83
+ case 'removeTransform':
84
+ unsetCryptorParticipant(data.trackId);
85
+ break;
86
+ case 'updateCodec':
87
+ getTrackCryptor(data.participantId, data.trackId).setVideoCodec(data.codec);
88
+ break;
89
+ case 'setRTPMap':
90
+ publishCryptors.forEach((cr) => {
91
+ cr.setRtpMap(data.map);
92
+ });
93
+ break;
94
+ case 'ratchetRequest':
95
+ getParticipantKeyHandler(data.participantId).ratchetKey(data.keyIndex);
96
+
97
+ default:
98
+ break;
99
+ }
100
+ };
101
+
102
+ function getTrackCryptor(participantId: string, trackId: string) {
103
+ let cryptor = participantCryptors.find((c) => c.getTrackId() === trackId);
104
+ if (!cryptor) {
105
+ workerLogger.info('creating new cryptor for', { participantId });
106
+ if (!keyProviderOptions) {
107
+ throw Error('Missing keyProvider options');
108
+ }
109
+ cryptor = new FrameCryptor({
110
+ participantId,
111
+ keys: getParticipantKeyHandler(participantId),
112
+ keyProviderOptions,
113
+ });
114
+
115
+ setupCryptorErrorEvents(cryptor);
116
+ participantCryptors.push(cryptor);
117
+ } else if (participantId !== cryptor.getParticipantId()) {
118
+ // assign new participant id to track cryptor and pass in correct key handler
119
+ cryptor.setParticipant(participantId, getParticipantKeyHandler(participantId));
120
+ }
121
+ if (sharedKey) {
122
+ }
123
+ return cryptor;
124
+ }
125
+
126
+ function getParticipantKeyHandler(participantId?: string) {
127
+ if (!participantId) {
128
+ return publisherKeys!;
129
+ }
130
+ let keys = participantKeys.get(participantId);
131
+ if (!keys) {
132
+ keys = new ParticipantKeyHandler(participantId, true, keyProviderOptions);
133
+ if (sharedKey) {
134
+ keys.setKeyFromMaterial(sharedKey);
135
+ }
136
+ participantKeys.set(participantId, keys);
137
+ }
138
+ return keys;
139
+ }
140
+
141
+ function unsetCryptorParticipant(trackId: string) {
142
+ participantCryptors.find((c) => c.getTrackId() === trackId)?.unsetParticipant();
143
+ }
144
+
145
+ function getPublisherCryptor(trackId: string) {
146
+ let publishCryptor = publishCryptors.find((cryptor) => cryptor.getTrackId() === trackId);
147
+ if (!publishCryptor) {
148
+ if (!keyProviderOptions) {
149
+ throw new TypeError('Missing keyProvider options');
150
+ }
151
+ publishCryptor = new FrameCryptor({
152
+ keys: publisherKeys!,
153
+ participantId: 'publisher',
154
+ keyProviderOptions,
155
+ });
156
+ setupCryptorErrorEvents(publishCryptor);
157
+ publishCryptors.push(publishCryptor);
158
+ }
159
+ return publishCryptor;
160
+ }
161
+
162
+ function setEncryptionEnabled(enable: boolean, participantId?: string) {
163
+ if (!participantId) {
164
+ isEncryptionEnabled = enable;
165
+ publisherKeys.setEnabled(enable);
166
+ } else {
167
+ getParticipantKeyHandler(participantId).setEnabled(enable);
168
+ }
169
+ }
170
+
171
+ function setSharedKey(key: CryptoKey, index?: number) {
172
+ workerLogger.debug('setting shared key');
173
+ sharedKey = key;
174
+ publisherKeys?.setKeyFromMaterial(key, index);
175
+ for (const [, keyHandler] of participantKeys) {
176
+ keyHandler.setKeyFromMaterial(key, index);
177
+ }
178
+ }
179
+
180
+ function setupCryptorErrorEvents(cryptor: FrameCryptor) {
181
+ cryptor.on('cryptorError', (error) => {
182
+ const msg: ErrorMessage = {
183
+ kind: 'error',
184
+ data: { error: new Error(`${CryptorErrorReason[error.reason]}: ${error.message}`) },
185
+ };
186
+ postMessage(msg);
187
+ });
188
+ }
189
+
190
+ function emitRatchetedKeys(material: CryptoKey, keyIndex?: number) {
191
+ const msg: RatchetMessage = {
192
+ kind: `ratchetKey`,
193
+ data: {
194
+ // participantId,
195
+ keyIndex,
196
+ material,
197
+ },
198
+ };
199
+ postMessage(msg);
200
+ }
201
+
202
+ // Operations using RTCRtpScriptTransform.
203
+ // @ts-ignore
204
+ if (self.RTCTransformEvent) {
205
+ workerLogger.debug('setup transform event');
206
+ // @ts-ignore
207
+ self.onrtctransform = (event) => {
208
+ const transformer = event.transformer;
209
+ workerLogger.debug('transformer', transformer);
210
+ transformer.handled = true;
211
+ const { kind, participantId, trackId, codec } = transformer.options;
212
+ const cryptor =
213
+ kind === 'encode' ? getPublisherCryptor(trackId) : getTrackCryptor(participantId, trackId);
214
+ workerLogger.debug('transform', { codec });
215
+ cryptor.setupTransform(kind, transformer.readable, transformer.writable, trackId, codec);
216
+ };
217
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "../../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "lib": ["DOM", "DOM.Iterable", "ES2017", "ES2018.Promise", "WebWorker"]
5
+ }
6
+ }
package/src/index.ts CHANGED
@@ -41,6 +41,7 @@ export { facingModeFromDeviceLabel, facingModeFromLocalTrack } from './room/trac
41
41
  export * from './room/track/types';
42
42
  export type { DataPublishOptions, SimulationScenario } from './room/types';
43
43
  export * from './version';
44
+ export * from './e2ee';
44
45
  export * from './room/track/processor/types';
45
46
  export {
46
47
  setLogLevel,
package/src/logger.ts CHANGED
@@ -17,6 +17,7 @@ type StructuredLogger = {
17
17
  info: (msg: string, context?: object) => void;
18
18
  warn: (msg: string, context?: object) => void;
19
19
  error: (msg: string, context?: object) => void;
20
+ setDefaultLevel: (level: log.LogLevelDesc) => void;
20
21
  };
21
22
 
22
23
  const livekitLogger = log.getLogger('livekit');
@@ -25,8 +26,13 @@ livekitLogger.setDefaultLevel(LogLevel.info);
25
26
 
26
27
  export default livekitLogger as StructuredLogger;
27
28
 
28
- export function setLogLevel(level: LogLevel | LogLevelString) {
29
- livekitLogger.setLevel(level);
29
+ export function setLogLevel(level: LogLevel | LogLevelString, loggerName?: 'livekit' | 'lk-e2ee') {
30
+ if (loggerName) {
31
+ log.getLogger(loggerName).setLevel(level);
32
+ }
33
+ for (const logger of Object.values(log.getLoggers())) {
34
+ logger.setLevel(level);
35
+ }
30
36
  }
31
37
 
32
38
  export type LogExtension = (level: LogLevel, msg: string, context?: object) => void;
@@ -54,3 +60,5 @@ export function setLogExtension(extension: LogExtension) {
54
60
  };
55
61
  livekitLogger.setLevel(livekitLogger.getLevel()); // Be sure to call setLevel method in order to apply plugin
56
62
  }
63
+
64
+ export const workerLogger = log.getLogger('lk-e2ee') as StructuredLogger;
package/src/options.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { E2EEOptions } from './e2ee/types';
1
2
  import type { ReconnectPolicy } from './room/ReconnectPolicy';
2
3
  import type {
3
4
  AudioCaptureOptions,
@@ -83,6 +84,11 @@ export interface InternalRoomOptions {
83
84
  */
84
85
 
85
86
  expWebAudioMix: boolean | WebAudioSettings;
87
+
88
+ /**
89
+ * @experimental
90
+ */
91
+ e2ee?: E2EEOptions;
86
92
  }
87
93
 
88
94
  /**
@@ -378,7 +378,7 @@ export function disconnectReasonToJSON(object: DisconnectReason): string {
378
378
  }
379
379
 
380
380
  export enum ReconnectReason {
381
- RR_UNKOWN = 0,
381
+ RR_UNKNOWN = 0,
382
382
  RR_SIGNAL_DISCONNECTED = 1,
383
383
  RR_PUBLISHER_FAILED = 2,
384
384
  RR_SUBSCRIBER_FAILED = 3,
@@ -389,8 +389,8 @@ export enum ReconnectReason {
389
389
  export function reconnectReasonFromJSON(object: any): ReconnectReason {
390
390
  switch (object) {
391
391
  case 0:
392
- case "RR_UNKOWN":
393
- return ReconnectReason.RR_UNKOWN;
392
+ case "RR_UNKNOWN":
393
+ return ReconnectReason.RR_UNKNOWN;
394
394
  case 1:
395
395
  case "RR_SIGNAL_DISCONNECTED":
396
396
  return ReconnectReason.RR_SIGNAL_DISCONNECTED;
@@ -412,8 +412,8 @@ export function reconnectReasonFromJSON(object: any): ReconnectReason {
412
412
 
413
413
  export function reconnectReasonToJSON(object: ReconnectReason): string {
414
414
  switch (object) {
415
- case ReconnectReason.RR_UNKOWN:
416
- return "RR_UNKOWN";
415
+ case ReconnectReason.RR_UNKNOWN:
416
+ return "RR_UNKNOWN";
417
417
  case ReconnectReason.RR_SIGNAL_DISCONNECTED:
418
418
  return "RR_SIGNAL_DISCONNECTED";
419
419
  case ReconnectReason.RR_PUBLISHER_FAILED:
@@ -429,7 +429,7 @@ export function reconnectReasonToJSON(object: ReconnectReason): string {
429
429
  }
430
430
 
431
431
  export enum SubscriptionError {
432
- SE_UNKOWN = 0,
432
+ SE_UNKNOWN = 0,
433
433
  SE_CODEC_UNSUPPORTED = 1,
434
434
  SE_TRACK_NOTFOUND = 2,
435
435
  UNRECOGNIZED = -1,
@@ -438,8 +438,8 @@ export enum SubscriptionError {
438
438
  export function subscriptionErrorFromJSON(object: any): SubscriptionError {
439
439
  switch (object) {
440
440
  case 0:
441
- case "SE_UNKOWN":
442
- return SubscriptionError.SE_UNKOWN;
441
+ case "SE_UNKNOWN":
442
+ return SubscriptionError.SE_UNKNOWN;
443
443
  case 1:
444
444
  case "SE_CODEC_UNSUPPORTED":
445
445
  return SubscriptionError.SE_CODEC_UNSUPPORTED;
@@ -455,8 +455,8 @@ export function subscriptionErrorFromJSON(object: any): SubscriptionError {
455
455
 
456
456
  export function subscriptionErrorToJSON(object: SubscriptionError): string {
457
457
  switch (object) {
458
- case SubscriptionError.SE_UNKOWN:
459
- return "SE_UNKOWN";
458
+ case SubscriptionError.SE_UNKNOWN:
459
+ return "SE_UNKNOWN";
460
460
  case SubscriptionError.SE_CODEC_UNSUPPORTED:
461
461
  return "SE_CODEC_UNSUPPORTED";
462
462
  case SubscriptionError.SE_TRACK_NOTFOUND:
@@ -3970,8 +3970,8 @@ function toTimestamp(date: Date): Timestamp {
3970
3970
  }
3971
3971
 
3972
3972
  function fromTimestamp(t: Timestamp): Date {
3973
- let millis = t.seconds * 1_000;
3974
- millis += t.nanos / 1_000_000;
3973
+ let millis = (t.seconds || 0) * 1_000;
3974
+ millis += (t.nanos || 0) / 1_000_000;
3975
3975
  return new Date(millis);
3976
3976
  }
3977
3977
 
@@ -3,7 +3,7 @@ import { parse, write } from 'sdp-transform';
3
3
  import type { MediaDescription } from 'sdp-transform';
4
4
  import { debounce } from 'ts-debounce';
5
5
  import log from '../logger';
6
- import { NegotiationError } from './errors';
6
+ import { NegotiationError, UnexpectedConnectionState } from './errors';
7
7
  import { ddExtensionURI, isChromiumBased, isSVCCodec } from './utils';
8
8
 
9
9
  /** @internal */
@@ -25,11 +25,17 @@ const startBitrateForSVC = 0.7;
25
25
  export const PCEvents = {
26
26
  NegotiationStarted: 'negotiationStarted',
27
27
  NegotiationComplete: 'negotiationComplete',
28
+ RTPVideoPayloadTypes: 'rtpVideoPayloadTypes',
28
29
  } as const;
29
30
 
30
31
  /** @internal */
31
32
  export default class PCTransport extends EventEmitter {
32
- pc: RTCPeerConnection;
33
+ private _pc: RTCPeerConnection | null;
34
+
35
+ public get pc() {
36
+ if (this._pc) return this._pc;
37
+ throw new UnexpectedConnectionState('Expected peer connection to be available');
38
+ }
33
39
 
34
40
  pendingCandidates: RTCIceCandidateInit[] = [];
35
41
 
@@ -47,14 +53,17 @@ export default class PCTransport extends EventEmitter {
47
53
 
48
54
  constructor(config?: RTCConfiguration, mediaConstraints: Record<string, unknown> = {}) {
49
55
  super();
50
- this.pc = isChromiumBased()
56
+ this._pc = isChromiumBased()
51
57
  ? // @ts-expect-error chrome allows additional media constraints to be passed into the RTCPeerConnection constructor
52
58
  new RTCPeerConnection(config, mediaConstraints)
53
59
  : new RTCPeerConnection(config);
54
60
  }
55
61
 
56
62
  get isICEConnected(): boolean {
57
- return this.pc.iceConnectionState === 'connected' || this.pc.iceConnectionState === 'completed';
63
+ return (
64
+ this._pc !== null &&
65
+ (this.pc.iceConnectionState === 'connected' || this.pc.iceConnectionState === 'completed')
66
+ );
58
67
  }
59
68
 
60
69
  async addIceCandidate(candidate: RTCIceCandidateInit): Promise<void> {
@@ -136,6 +145,14 @@ export default class PCTransport extends EventEmitter {
136
145
  this.createAndSendOffer();
137
146
  } else if (sd.type === 'answer') {
138
147
  this.emit(PCEvents.NegotiationComplete);
148
+ if (sd.sdp) {
149
+ const sdpParsed = parse(sd.sdp);
150
+ sdpParsed.media.forEach((media) => {
151
+ if (media.type === 'video') {
152
+ this.emit(PCEvents.RTPVideoPayloadTypes, media.rtp);
153
+ }
154
+ });
155
+ }
139
156
  }
140
157
  }
141
158
 
@@ -163,7 +180,7 @@ export default class PCTransport extends EventEmitter {
163
180
  this.restartingIce = true;
164
181
  }
165
182
 
166
- if (this.pc.signalingState === 'have-local-offer') {
183
+ if (this._pc && this._pc.signalingState === 'have-local-offer') {
167
184
  // we're waiting for the peer to accept our offer, so we'll just wait
168
185
  // the only exception to this is when ICE restart is needed
169
186
  const currentSD = this.pc.remoteDescription;
@@ -175,7 +192,7 @@ export default class PCTransport extends EventEmitter {
175
192
  this.renegotiate = true;
176
193
  return;
177
194
  }
178
- } else if (this.pc.signalingState === 'closed') {
195
+ } else if (!this._pc || this._pc.signalingState === 'closed') {
179
196
  log.warn('could not createOffer with closed peer connection');
180
197
  return;
181
198
  }
@@ -258,9 +275,22 @@ export default class PCTransport extends EventEmitter {
258
275
  }
259
276
 
260
277
  close() {
261
- this.pc.onconnectionstatechange = null;
262
- this.pc.oniceconnectionstatechange = null;
263
- this.pc.close();
278
+ if (!this._pc) {
279
+ return;
280
+ }
281
+ this._pc.close();
282
+ this._pc.onconnectionstatechange = null;
283
+ this._pc.oniceconnectionstatechange = null;
284
+ this._pc.onicegatheringstatechange = null;
285
+ this._pc.ondatachannel = null;
286
+ this._pc.onnegotiationneeded = null;
287
+ this._pc.onsignalingstatechange = null;
288
+ this._pc.onicecandidate = null;
289
+ this._pc.ondatachannel = null;
290
+ this._pc.ontrack = null;
291
+ this._pc.onconnectionstatechange = null;
292
+ this._pc.oniceconnectionstatechange = null;
293
+ this._pc = null;
264
294
  }
265
295
 
266
296
  private async setMungedSDP(sd: RTCSessionDescriptionInit, munged?: string, remote?: boolean) {