livekit-client 1.11.4 → 1.12.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. package/README.md +13 -1
  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 +1545 -0
  5. package/dist/livekit-client.e2ee.worker.mjs.map +1 -0
  6. package/dist/livekit-client.esm.mjs +4786 -4065
  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 +174 -0
  29. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -0
  30. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +54 -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/participant/RemoteParticipant.d.ts +6 -4
  55. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  56. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  57. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  58. package/dist/src/room/track/TrackPublication.d.ts +3 -0
  59. package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
  60. package/dist/src/room/track/create.d.ts.map +1 -1
  61. package/dist/src/room/track/options.d.ts +2 -2
  62. package/dist/src/room/track/options.d.ts.map +1 -1
  63. package/dist/src/room/track/utils.d.ts +9 -0
  64. package/dist/src/room/track/utils.d.ts.map +1 -1
  65. package/dist/src/room/utils.d.ts +2 -0
  66. package/dist/src/room/utils.d.ts.map +1 -1
  67. package/dist/src/test/MockMediaStreamTrack.d.ts.map +1 -1
  68. package/dist/src/utils/browserParser.d.ts +2 -0
  69. package/dist/src/utils/browserParser.d.ts.map +1 -1
  70. package/dist/ts4.2/src/api/SignalClient.d.ts +4 -1
  71. package/dist/ts4.2/src/e2ee/E2eeManager.d.ts +45 -0
  72. package/dist/ts4.2/src/e2ee/KeyProvider.d.ts +42 -0
  73. package/dist/ts4.2/src/e2ee/constants.d.ts +14 -0
  74. package/dist/ts4.2/src/e2ee/errors.d.ts +11 -0
  75. package/dist/ts4.2/src/e2ee/index.d.ts +4 -0
  76. package/dist/ts4.2/src/e2ee/types.d.ts +129 -0
  77. package/dist/ts4.2/src/e2ee/utils.d.ts +24 -0
  78. package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +174 -0
  79. package/dist/ts4.2/src/e2ee/worker/ParticipantKeyHandler.d.ts +54 -0
  80. package/dist/ts4.2/src/e2ee/worker/e2ee.worker.d.ts +2 -0
  81. package/dist/ts4.2/src/index.d.ts +1 -0
  82. package/dist/ts4.2/src/logger.d.ts +4 -1
  83. package/dist/ts4.2/src/options.d.ts +5 -0
  84. package/dist/ts4.2/src/proto/livekit_models.d.ts +2 -2
  85. package/dist/ts4.2/src/room/PCTransport.d.ts +3 -1
  86. package/dist/ts4.2/src/room/RTCEngine.d.ts +17 -3
  87. package/dist/ts4.2/src/room/Room.d.ts +10 -0
  88. package/dist/ts4.2/src/room/events.d.ts +14 -2
  89. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +7 -2
  90. package/dist/ts4.2/src/room/participant/Participant.d.ts +1 -0
  91. package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +6 -4
  92. package/dist/ts4.2/src/room/track/TrackPublication.d.ts +3 -0
  93. package/dist/ts4.2/src/room/track/options.d.ts +6 -6
  94. package/dist/ts4.2/src/room/track/utils.d.ts +9 -0
  95. package/dist/ts4.2/src/room/utils.d.ts +2 -0
  96. package/dist/ts4.2/src/utils/browserParser.d.ts +2 -0
  97. package/package.json +17 -7
  98. package/src/api/SignalClient.ts +28 -9
  99. package/src/connectionHelper/checks/turn.ts +1 -0
  100. package/src/connectionHelper/checks/websocket.ts +1 -0
  101. package/src/e2ee/E2eeManager.ts +374 -0
  102. package/src/e2ee/KeyProvider.ts +77 -0
  103. package/src/e2ee/constants.ts +40 -0
  104. package/src/e2ee/errors.ts +16 -0
  105. package/src/e2ee/index.ts +3 -0
  106. package/src/e2ee/types.ts +160 -0
  107. package/src/e2ee/utils.ts +127 -0
  108. package/src/e2ee/worker/FrameCryptor.test.ts +21 -0
  109. package/src/e2ee/worker/FrameCryptor.ts +612 -0
  110. package/src/e2ee/worker/ParticipantKeyHandler.ts +144 -0
  111. package/src/e2ee/worker/e2ee.worker.ts +223 -0
  112. package/src/e2ee/worker/tsconfig.json +6 -0
  113. package/src/index.ts +1 -0
  114. package/src/logger.ts +10 -2
  115. package/src/options.ts +6 -0
  116. package/src/proto/livekit_models.ts +12 -12
  117. package/src/room/PCTransport.ts +39 -9
  118. package/src/room/RTCEngine.ts +127 -34
  119. package/src/room/Room.ts +94 -29
  120. package/src/room/defaults.ts +1 -1
  121. package/src/room/events.ts +14 -0
  122. package/src/room/participant/LocalParticipant.ts +52 -8
  123. package/src/room/participant/Participant.ts +4 -0
  124. package/src/room/participant/RemoteParticipant.ts +19 -15
  125. package/src/room/track/LocalTrack.ts +5 -4
  126. package/src/room/track/RemoteVideoTrack.ts +2 -2
  127. package/src/room/track/TrackPublication.ts +9 -1
  128. package/src/room/track/create.ts +9 -0
  129. package/src/room/track/options.ts +3 -2
  130. package/src/room/track/utils.ts +27 -0
  131. package/src/room/utils.ts +5 -0
  132. package/src/room/worker.d.ts +4 -0
  133. package/src/test/MockMediaStreamTrack.ts +1 -0
  134. package/src/utils/browserParser.ts +5 -0
@@ -0,0 +1,144 @@
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
+ hasValidKey: boolean;
32
+
33
+ constructor(
34
+ participantId: string | undefined,
35
+ isEnabled: boolean,
36
+ keyProviderOptions: KeyProviderOptions,
37
+ ) {
38
+ super();
39
+ this.currentKeyIndex = 0;
40
+ this.cryptoKeyRing = new Array(KEYRING_SIZE);
41
+ this.enabled = isEnabled;
42
+ this.keyProviderOptions = keyProviderOptions;
43
+ this.ratchetPromiseMap = new Map();
44
+ this.participantId = participantId;
45
+ this.hasValidKey = false;
46
+ }
47
+
48
+ setEnabled(enabled: boolean) {
49
+ this.enabled = enabled;
50
+ }
51
+
52
+ /**
53
+ * Ratchets the current key (or the one at keyIndex if provided) and
54
+ * returns the ratcheted material
55
+ * if `setKey` is true (default), it will also set the ratcheted key directly on the crypto key ring
56
+ * @param keyIndex
57
+ * @param setKey
58
+ */
59
+ ratchetKey(keyIndex?: number, setKey = true): Promise<CryptoKey> {
60
+ const currentKeyIndex = (keyIndex ??= this.getCurrentKeyIndex());
61
+
62
+ const existingPromise = this.ratchetPromiseMap.get(currentKeyIndex);
63
+ if (typeof existingPromise !== 'undefined') {
64
+ return existingPromise;
65
+ }
66
+ const ratchetPromise = new Promise<CryptoKey>(async (resolve, reject) => {
67
+ try {
68
+ const currentMaterial = this.getKeySet(currentKeyIndex).material;
69
+ const newMaterial = await importKey(
70
+ await ratchet(currentMaterial, this.keyProviderOptions.ratchetSalt),
71
+ currentMaterial.algorithm.name,
72
+ 'derive',
73
+ );
74
+
75
+ if (setKey) {
76
+ this.setKeyFromMaterial(newMaterial, currentKeyIndex, true);
77
+ }
78
+ this.emit('keyRatcheted', newMaterial, keyIndex, this.participantId);
79
+ resolve(newMaterial);
80
+ } catch (e) {
81
+ reject(e);
82
+ } finally {
83
+ this.ratchetPromiseMap.delete(currentKeyIndex);
84
+ }
85
+ });
86
+ this.ratchetPromiseMap.set(currentKeyIndex, ratchetPromise);
87
+ return ratchetPromise;
88
+ }
89
+
90
+ /**
91
+ * takes in a key material with `deriveBits` and `deriveKey` set as key usages
92
+ * and derives encryption keys from the material and sets it on the key ring buffer
93
+ * together with the material
94
+ * also resets the valid key property and updates the currentKeyIndex
95
+ */
96
+ async setKey(material: CryptoKey, keyIndex = 0) {
97
+ await this.setKeyFromMaterial(material, keyIndex);
98
+ this.hasValidKey = true;
99
+ }
100
+
101
+ /**
102
+ * takes in a key material with `deriveBits` and `deriveKey` set as key usages
103
+ * and derives encryption keys from the material and sets it on the key ring buffer
104
+ * together with the material
105
+ * also updates the currentKeyIndex
106
+ */
107
+ async setKeyFromMaterial(material: CryptoKey, keyIndex = 0, emitRatchetEvent = false) {
108
+ workerLogger.debug('setting new key');
109
+ if (keyIndex >= 0) {
110
+ this.currentKeyIndex = keyIndex % this.cryptoKeyRing.length;
111
+ }
112
+ const keySet = await deriveKeys(material, this.keyProviderOptions.ratchetSalt);
113
+ this.setKeySet(keySet, this.currentKeyIndex, emitRatchetEvent);
114
+ }
115
+
116
+ async setKeySet(keySet: KeySet, keyIndex: number, emitRatchetEvent = false) {
117
+ this.cryptoKeyRing[keyIndex % this.cryptoKeyRing.length] = keySet;
118
+ if (emitRatchetEvent) {
119
+ this.emit('keyRatcheted', keySet.material, keyIndex, this.participantId);
120
+ }
121
+ }
122
+
123
+ async setCurrentKeyIndex(index: number) {
124
+ this.currentKeyIndex = index % this.cryptoKeyRing.length;
125
+ this.hasValidKey = true;
126
+ }
127
+
128
+ isEnabled() {
129
+ return this.enabled;
130
+ }
131
+
132
+ getCurrentKeyIndex() {
133
+ return this.currentKeyIndex;
134
+ }
135
+
136
+ /**
137
+ * returns currently used KeySet or the one at `keyIndex` if provided
138
+ * @param keyIndex
139
+ * @returns
140
+ */
141
+ getKeySet(keyIndex?: number) {
142
+ return this.cryptoKeyRing[keyIndex ?? this.currentKeyIndex];
143
+ }
144
+ }
@@ -0,0 +1,223 @@
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
+ RatchetRequestMessage,
11
+ } from '../types';
12
+ import { FrameCryptor } from './FrameCryptor';
13
+ import { ParticipantKeyHandler } from './ParticipantKeyHandler';
14
+
15
+ const participantCryptors: FrameCryptor[] = [];
16
+ const participantKeys: Map<string, ParticipantKeyHandler> = new Map();
17
+
18
+ let publishCryptors: FrameCryptor[] = [];
19
+ let publisherKeys: ParticipantKeyHandler;
20
+
21
+ let isEncryptionEnabled: boolean = false;
22
+
23
+ let useSharedKey: boolean = false;
24
+
25
+ let sharedKey: CryptoKey | undefined;
26
+
27
+ let keyProviderOptions: KeyProviderOptions = KEY_PROVIDER_DEFAULTS;
28
+
29
+ workerLogger.setDefaultLevel('info');
30
+
31
+ onmessage = (ev) => {
32
+ const { kind, data }: E2EEWorkerMessage = ev.data;
33
+
34
+ switch (kind) {
35
+ case 'init':
36
+ workerLogger.info('worker initialized');
37
+ keyProviderOptions = data.keyProviderOptions;
38
+ useSharedKey = !!data.keyProviderOptions.sharedKey;
39
+ // acknowledge init successful
40
+ const enableMsg: EnableMessage = {
41
+ kind: 'enable',
42
+ data: { enabled: isEncryptionEnabled },
43
+ };
44
+ publisherKeys = new ParticipantKeyHandler(undefined, isEncryptionEnabled, keyProviderOptions);
45
+ publisherKeys.on('keyRatcheted', emitRatchetedKeys);
46
+ postMessage(enableMsg);
47
+ break;
48
+ case 'enable':
49
+ setEncryptionEnabled(data.enabled, data.participantId);
50
+ workerLogger.info('updated e2ee enabled status');
51
+ // acknowledge enable call successful
52
+ postMessage(ev.data);
53
+ break;
54
+ case 'decode':
55
+ let cryptor = getTrackCryptor(data.participantId, data.trackId);
56
+ cryptor.setupTransform(
57
+ kind,
58
+ data.readableStream,
59
+ data.writableStream,
60
+ data.trackId,
61
+ data.codec,
62
+ );
63
+ break;
64
+ case 'encode':
65
+ let pubCryptor = getPublisherCryptor(data.trackId);
66
+ pubCryptor.setupTransform(
67
+ kind,
68
+ data.readableStream,
69
+ data.writableStream,
70
+ data.trackId,
71
+ data.codec,
72
+ );
73
+ break;
74
+ case 'setKey':
75
+ if (useSharedKey) {
76
+ workerLogger.debug('set shared key');
77
+ setSharedKey(data.key, data.keyIndex);
78
+ } else if (data.participantId) {
79
+ getParticipantKeyHandler(data.participantId).setKey(data.key, data.keyIndex);
80
+ } else {
81
+ workerLogger.error('no participant Id was provided and shared key usage is disabled');
82
+ }
83
+ break;
84
+ case 'removeTransform':
85
+ unsetCryptorParticipant(data.trackId);
86
+ break;
87
+ case 'updateCodec':
88
+ getTrackCryptor(data.participantId, data.trackId).setVideoCodec(data.codec);
89
+ break;
90
+ case 'setRTPMap':
91
+ publishCryptors.forEach((cr) => {
92
+ cr.setRtpMap(data.map);
93
+ });
94
+ break;
95
+ case 'ratchetRequest':
96
+ handleRatchetRequest(data);
97
+ default:
98
+ break;
99
+ }
100
+ };
101
+
102
+ async function handleRatchetRequest(data: RatchetRequestMessage['data']) {
103
+ const keyHandler = getParticipantKeyHandler(data.participantId);
104
+ await keyHandler.ratchetKey(data.keyIndex);
105
+ keyHandler.hasValidKey = true;
106
+ }
107
+
108
+ function getTrackCryptor(participantId: string, trackId: string) {
109
+ let cryptor = participantCryptors.find((c) => c.getTrackId() === trackId);
110
+ if (!cryptor) {
111
+ workerLogger.info('creating new cryptor for', { participantId });
112
+ if (!keyProviderOptions) {
113
+ throw Error('Missing keyProvider options');
114
+ }
115
+ cryptor = new FrameCryptor({
116
+ participantId,
117
+ keys: getParticipantKeyHandler(participantId),
118
+ keyProviderOptions,
119
+ });
120
+
121
+ setupCryptorErrorEvents(cryptor);
122
+ participantCryptors.push(cryptor);
123
+ } else if (participantId !== cryptor.getParticipantId()) {
124
+ // assign new participant id to track cryptor and pass in correct key handler
125
+ cryptor.setParticipant(participantId, getParticipantKeyHandler(participantId));
126
+ }
127
+ if (sharedKey) {
128
+ }
129
+ return cryptor;
130
+ }
131
+
132
+ function getParticipantKeyHandler(participantId?: string) {
133
+ if (!participantId) {
134
+ return publisherKeys!;
135
+ }
136
+ let keys = participantKeys.get(participantId);
137
+ if (!keys) {
138
+ keys = new ParticipantKeyHandler(participantId, true, keyProviderOptions);
139
+ if (sharedKey) {
140
+ keys.setKey(sharedKey);
141
+ }
142
+ participantKeys.set(participantId, keys);
143
+ }
144
+ return keys;
145
+ }
146
+
147
+ function unsetCryptorParticipant(trackId: string) {
148
+ participantCryptors.find((c) => c.getTrackId() === trackId)?.unsetParticipant();
149
+ }
150
+
151
+ function getPublisherCryptor(trackId: string) {
152
+ let publishCryptor = publishCryptors.find((cryptor) => cryptor.getTrackId() === trackId);
153
+ if (!publishCryptor) {
154
+ if (!keyProviderOptions) {
155
+ throw new TypeError('Missing keyProvider options');
156
+ }
157
+ publishCryptor = new FrameCryptor({
158
+ keys: publisherKeys!,
159
+ participantId: 'publisher',
160
+ keyProviderOptions,
161
+ });
162
+ setupCryptorErrorEvents(publishCryptor);
163
+ publishCryptors.push(publishCryptor);
164
+ }
165
+ return publishCryptor;
166
+ }
167
+
168
+ function setEncryptionEnabled(enable: boolean, participantId?: string) {
169
+ if (!participantId) {
170
+ isEncryptionEnabled = enable;
171
+ publisherKeys.setEnabled(enable);
172
+ } else {
173
+ getParticipantKeyHandler(participantId).setEnabled(enable);
174
+ }
175
+ }
176
+
177
+ function setSharedKey(key: CryptoKey, index?: number) {
178
+ workerLogger.debug('setting shared key');
179
+ sharedKey = key;
180
+ publisherKeys?.setKey(key, index);
181
+ for (const [, keyHandler] of participantKeys) {
182
+ keyHandler.setKey(key, index);
183
+ }
184
+ }
185
+
186
+ function setupCryptorErrorEvents(cryptor: FrameCryptor) {
187
+ cryptor.on('cryptorError', (error) => {
188
+ const msg: ErrorMessage = {
189
+ kind: 'error',
190
+ data: { error: new Error(`${CryptorErrorReason[error.reason]}: ${error.message}`) },
191
+ };
192
+ postMessage(msg);
193
+ });
194
+ }
195
+
196
+ function emitRatchetedKeys(material: CryptoKey, keyIndex?: number) {
197
+ const msg: RatchetMessage = {
198
+ kind: `ratchetKey`,
199
+ data: {
200
+ // participantId,
201
+ keyIndex,
202
+ material,
203
+ },
204
+ };
205
+ postMessage(msg);
206
+ }
207
+
208
+ // Operations using RTCRtpScriptTransform.
209
+ // @ts-ignore
210
+ if (self.RTCTransformEvent) {
211
+ workerLogger.debug('setup transform event');
212
+ // @ts-ignore
213
+ self.onrtctransform = (event) => {
214
+ const transformer = event.transformer;
215
+ workerLogger.debug('transformer', transformer);
216
+ transformer.handled = true;
217
+ const { kind, participantId, trackId, codec } = transformer.options;
218
+ const cryptor =
219
+ kind === 'encode' ? getPublisherCryptor(trackId) : getTrackCryptor(participantId, trackId);
220
+ workerLogger.debug('transform', { codec });
221
+ cryptor.setupTransform(kind, transformer.readable, transformer.writable, trackId, codec);
222
+ };
223
+ }
@@ -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) {