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,1525 @@
1
+ var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
2
+
3
+ function getDefaultExportFromCjs (x) {
4
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
5
+ }
6
+
7
+ var loglevel = {exports: {}};
8
+
9
+ /*
10
+ * loglevel - https://github.com/pimterry/loglevel
11
+ *
12
+ * Copyright (c) 2013 Tim Perry
13
+ * Licensed under the MIT license.
14
+ */
15
+ (function (module) {
16
+ (function (root, definition) {
17
+
18
+ if (module.exports) {
19
+ module.exports = definition();
20
+ } else {
21
+ root.log = definition();
22
+ }
23
+ })(commonjsGlobal, function () {
24
+
25
+ // Slightly dubious tricks to cut down minimized file size
26
+ var noop = function () {};
27
+ var undefinedType = "undefined";
28
+ var isIE = typeof window !== undefinedType && typeof window.navigator !== undefinedType && /Trident\/|MSIE /.test(window.navigator.userAgent);
29
+ var logMethods = ["trace", "debug", "info", "warn", "error"];
30
+
31
+ // Cross-browser bind equivalent that works at least back to IE6
32
+ function bindMethod(obj, methodName) {
33
+ var method = obj[methodName];
34
+ if (typeof method.bind === 'function') {
35
+ return method.bind(obj);
36
+ } else {
37
+ try {
38
+ return Function.prototype.bind.call(method, obj);
39
+ } catch (e) {
40
+ // Missing bind shim or IE8 + Modernizr, fallback to wrapping
41
+ return function () {
42
+ return Function.prototype.apply.apply(method, [obj, arguments]);
43
+ };
44
+ }
45
+ }
46
+ }
47
+
48
+ // Trace() doesn't print the message in IE, so for that case we need to wrap it
49
+ function traceForIE() {
50
+ if (console.log) {
51
+ if (console.log.apply) {
52
+ console.log.apply(console, arguments);
53
+ } else {
54
+ // In old IE, native console methods themselves don't have apply().
55
+ Function.prototype.apply.apply(console.log, [console, arguments]);
56
+ }
57
+ }
58
+ if (console.trace) console.trace();
59
+ }
60
+
61
+ // Build the best logging method possible for this env
62
+ // Wherever possible we want to bind, not wrap, to preserve stack traces
63
+ function realMethod(methodName) {
64
+ if (methodName === 'debug') {
65
+ methodName = 'log';
66
+ }
67
+ if (typeof console === undefinedType) {
68
+ return false; // No method possible, for now - fixed later by enableLoggingWhenConsoleArrives
69
+ } else if (methodName === 'trace' && isIE) {
70
+ return traceForIE;
71
+ } else if (console[methodName] !== undefined) {
72
+ return bindMethod(console, methodName);
73
+ } else if (console.log !== undefined) {
74
+ return bindMethod(console, 'log');
75
+ } else {
76
+ return noop;
77
+ }
78
+ }
79
+
80
+ // These private functions always need `this` to be set properly
81
+
82
+ function replaceLoggingMethods(level, loggerName) {
83
+ /*jshint validthis:true */
84
+ for (var i = 0; i < logMethods.length; i++) {
85
+ var methodName = logMethods[i];
86
+ this[methodName] = i < level ? noop : this.methodFactory(methodName, level, loggerName);
87
+ }
88
+
89
+ // Define log.log as an alias for log.debug
90
+ this.log = this.debug;
91
+ }
92
+
93
+ // In old IE versions, the console isn't present until you first open it.
94
+ // We build realMethod() replacements here that regenerate logging methods
95
+ function enableLoggingWhenConsoleArrives(methodName, level, loggerName) {
96
+ return function () {
97
+ if (typeof console !== undefinedType) {
98
+ replaceLoggingMethods.call(this, level, loggerName);
99
+ this[methodName].apply(this, arguments);
100
+ }
101
+ };
102
+ }
103
+
104
+ // By default, we use closely bound real methods wherever possible, and
105
+ // otherwise we wait for a console to appear, and then try again.
106
+ function defaultMethodFactory(methodName, level, loggerName) {
107
+ /*jshint validthis:true */
108
+ return realMethod(methodName) || enableLoggingWhenConsoleArrives.apply(this, arguments);
109
+ }
110
+ function Logger(name, defaultLevel, factory) {
111
+ var self = this;
112
+ var currentLevel;
113
+ defaultLevel = defaultLevel == null ? "WARN" : defaultLevel;
114
+ var storageKey = "loglevel";
115
+ if (typeof name === "string") {
116
+ storageKey += ":" + name;
117
+ } else if (typeof name === "symbol") {
118
+ storageKey = undefined;
119
+ }
120
+ function persistLevelIfPossible(levelNum) {
121
+ var levelName = (logMethods[levelNum] || 'silent').toUpperCase();
122
+ if (typeof window === undefinedType || !storageKey) return;
123
+
124
+ // Use localStorage if available
125
+ try {
126
+ window.localStorage[storageKey] = levelName;
127
+ return;
128
+ } catch (ignore) {}
129
+
130
+ // Use session cookie as fallback
131
+ try {
132
+ window.document.cookie = encodeURIComponent(storageKey) + "=" + levelName + ";";
133
+ } catch (ignore) {}
134
+ }
135
+ function getPersistedLevel() {
136
+ var storedLevel;
137
+ if (typeof window === undefinedType || !storageKey) return;
138
+ try {
139
+ storedLevel = window.localStorage[storageKey];
140
+ } catch (ignore) {}
141
+
142
+ // Fallback to cookies if local storage gives us nothing
143
+ if (typeof storedLevel === undefinedType) {
144
+ try {
145
+ var cookie = window.document.cookie;
146
+ var location = cookie.indexOf(encodeURIComponent(storageKey) + "=");
147
+ if (location !== -1) {
148
+ storedLevel = /^([^;]+)/.exec(cookie.slice(location))[1];
149
+ }
150
+ } catch (ignore) {}
151
+ }
152
+
153
+ // If the stored level is not valid, treat it as if nothing was stored.
154
+ if (self.levels[storedLevel] === undefined) {
155
+ storedLevel = undefined;
156
+ }
157
+ return storedLevel;
158
+ }
159
+ function clearPersistedLevel() {
160
+ if (typeof window === undefinedType || !storageKey) return;
161
+
162
+ // Use localStorage if available
163
+ try {
164
+ window.localStorage.removeItem(storageKey);
165
+ return;
166
+ } catch (ignore) {}
167
+
168
+ // Use session cookie as fallback
169
+ try {
170
+ window.document.cookie = encodeURIComponent(storageKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC";
171
+ } catch (ignore) {}
172
+ }
173
+
174
+ /*
175
+ *
176
+ * Public logger API - see https://github.com/pimterry/loglevel for details
177
+ *
178
+ */
179
+
180
+ self.name = name;
181
+ self.levels = {
182
+ "TRACE": 0,
183
+ "DEBUG": 1,
184
+ "INFO": 2,
185
+ "WARN": 3,
186
+ "ERROR": 4,
187
+ "SILENT": 5
188
+ };
189
+ self.methodFactory = factory || defaultMethodFactory;
190
+ self.getLevel = function () {
191
+ return currentLevel;
192
+ };
193
+ self.setLevel = function (level, persist) {
194
+ if (typeof level === "string" && self.levels[level.toUpperCase()] !== undefined) {
195
+ level = self.levels[level.toUpperCase()];
196
+ }
197
+ if (typeof level === "number" && level >= 0 && level <= self.levels.SILENT) {
198
+ currentLevel = level;
199
+ if (persist !== false) {
200
+ // defaults to true
201
+ persistLevelIfPossible(level);
202
+ }
203
+ replaceLoggingMethods.call(self, level, name);
204
+ if (typeof console === undefinedType && level < self.levels.SILENT) {
205
+ return "No console available for logging";
206
+ }
207
+ } else {
208
+ throw "log.setLevel() called with invalid level: " + level;
209
+ }
210
+ };
211
+ self.setDefaultLevel = function (level) {
212
+ defaultLevel = level;
213
+ if (!getPersistedLevel()) {
214
+ self.setLevel(level, false);
215
+ }
216
+ };
217
+ self.resetLevel = function () {
218
+ self.setLevel(defaultLevel, false);
219
+ clearPersistedLevel();
220
+ };
221
+ self.enableAll = function (persist) {
222
+ self.setLevel(self.levels.TRACE, persist);
223
+ };
224
+ self.disableAll = function (persist) {
225
+ self.setLevel(self.levels.SILENT, persist);
226
+ };
227
+
228
+ // Initialize with the right level
229
+ var initialLevel = getPersistedLevel();
230
+ if (initialLevel == null) {
231
+ initialLevel = defaultLevel;
232
+ }
233
+ self.setLevel(initialLevel, false);
234
+ }
235
+
236
+ /*
237
+ *
238
+ * Top-level API
239
+ *
240
+ */
241
+
242
+ var defaultLogger = new Logger();
243
+ var _loggersByName = {};
244
+ defaultLogger.getLogger = function getLogger(name) {
245
+ if (typeof name !== "symbol" && typeof name !== "string" || name === "") {
246
+ throw new TypeError("You must supply a name when creating a logger.");
247
+ }
248
+ var logger = _loggersByName[name];
249
+ if (!logger) {
250
+ logger = _loggersByName[name] = new Logger(name, defaultLogger.getLevel(), defaultLogger.methodFactory);
251
+ }
252
+ return logger;
253
+ };
254
+
255
+ // Grab the current global log variable in case of overwrite
256
+ var _log = typeof window !== undefinedType ? window.log : undefined;
257
+ defaultLogger.noConflict = function () {
258
+ if (typeof window !== undefinedType && window.log === defaultLogger) {
259
+ window.log = _log;
260
+ }
261
+ return defaultLogger;
262
+ };
263
+ defaultLogger.getLoggers = function getLoggers() {
264
+ return _loggersByName;
265
+ };
266
+
267
+ // ES6 default export, for compatibility
268
+ defaultLogger['default'] = defaultLogger;
269
+ return defaultLogger;
270
+ });
271
+ })(loglevel);
272
+ var loglevelExports = loglevel.exports;
273
+
274
+ var LogLevel;
275
+ (function (LogLevel) {
276
+ LogLevel[LogLevel["trace"] = 0] = "trace";
277
+ LogLevel[LogLevel["debug"] = 1] = "debug";
278
+ LogLevel[LogLevel["info"] = 2] = "info";
279
+ LogLevel[LogLevel["warn"] = 3] = "warn";
280
+ LogLevel[LogLevel["error"] = 4] = "error";
281
+ LogLevel[LogLevel["silent"] = 5] = "silent";
282
+ })(LogLevel || (LogLevel = {}));
283
+ const livekitLogger = loglevelExports.getLogger('livekit');
284
+ livekitLogger.setDefaultLevel(LogLevel.info);
285
+
286
+ const workerLogger = loglevelExports.getLogger('lk-e2ee');
287
+
288
+ const ENCRYPTION_ALGORITHM = 'AES-GCM';
289
+ // We use a ringbuffer of keys so we can change them and still decode packets that were
290
+ // encrypted with an old key. We use a size of 16 which corresponds to the four bits
291
+ // in the frame trailer.
292
+ const KEYRING_SIZE = 16;
293
+ // We copy the first bytes of the VP8 payload unencrypted.
294
+ // For keyframes this is 10 bytes, for non-keyframes (delta) 3. See
295
+ // https://tools.ietf.org/html/rfc6386#section-9.1
296
+ // This allows the bridge to continue detecting keyframes (only one byte needed in the JVB)
297
+ // and is also a bit easier for the VP8 decoder (i.e. it generates funny garbage pictures
298
+ // instead of being unable to decode).
299
+ // This is a bit for show and we might want to reduce to 1 unconditionally in the final version.
300
+ //
301
+ // For audio (where frame.type is not set) we do not encrypt the opus TOC byte:
302
+ // https://tools.ietf.org/html/rfc6716#section-3.1
303
+ const UNENCRYPTED_BYTES = {
304
+ key: 10,
305
+ delta: 3,
306
+ audio: 1,
307
+ empty: 0
308
+ };
309
+ /* We use a 12 byte bit IV. This is signalled in plain together with the
310
+ packet. See https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt#parameters */
311
+ const IV_LENGTH = 12;
312
+ const SALT = 'LKFrameEncryptionKey';
313
+ const KEY_PROVIDER_DEFAULTS = {
314
+ sharedKey: false,
315
+ ratchetSalt: SALT,
316
+ ratchetWindowSize: 8
317
+ };
318
+
319
+ class LivekitError extends Error {
320
+ constructor(code, message) {
321
+ super(message || 'an error has occured');
322
+ this.code = code;
323
+ }
324
+ }
325
+ var MediaDeviceFailure;
326
+ (function (MediaDeviceFailure) {
327
+ // user rejected permissions
328
+ MediaDeviceFailure["PermissionDenied"] = "PermissionDenied";
329
+ // device is not available
330
+ MediaDeviceFailure["NotFound"] = "NotFound";
331
+ // device is in use. On Windows, only a single tab may get access to a device at a time.
332
+ MediaDeviceFailure["DeviceInUse"] = "DeviceInUse";
333
+ MediaDeviceFailure["Other"] = "Other";
334
+ })(MediaDeviceFailure || (MediaDeviceFailure = {}));
335
+ (function (MediaDeviceFailure) {
336
+ function getFailure(error) {
337
+ if (error && 'name' in error) {
338
+ if (error.name === 'NotFoundError' || error.name === 'DevicesNotFoundError') {
339
+ return MediaDeviceFailure.NotFound;
340
+ }
341
+ if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') {
342
+ return MediaDeviceFailure.PermissionDenied;
343
+ }
344
+ if (error.name === 'NotReadableError' || error.name === 'TrackStartError') {
345
+ return MediaDeviceFailure.DeviceInUse;
346
+ }
347
+ return MediaDeviceFailure.Other;
348
+ }
349
+ }
350
+ MediaDeviceFailure.getFailure = getFailure;
351
+ })(MediaDeviceFailure || (MediaDeviceFailure = {}));
352
+
353
+ var CryptorErrorReason;
354
+ (function (CryptorErrorReason) {
355
+ CryptorErrorReason[CryptorErrorReason["InvalidKey"] = 0] = "InvalidKey";
356
+ CryptorErrorReason[CryptorErrorReason["MissingKey"] = 1] = "MissingKey";
357
+ CryptorErrorReason[CryptorErrorReason["InternalError"] = 2] = "InternalError";
358
+ })(CryptorErrorReason || (CryptorErrorReason = {}));
359
+ class CryptorError extends LivekitError {
360
+ constructor(message) {
361
+ let reason = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : CryptorErrorReason.InternalError;
362
+ super(40, message);
363
+ this.reason = reason;
364
+ }
365
+ }
366
+
367
+ /******************************************************************************
368
+ Copyright (c) Microsoft Corporation.
369
+
370
+ Permission to use, copy, modify, and/or distribute this software for any
371
+ purpose with or without fee is hereby granted.
372
+
373
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
374
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
375
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
376
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
377
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
378
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
379
+ PERFORMANCE OF THIS SOFTWARE.
380
+ ***************************************************************************** */
381
+ /* global Reflect, Promise */
382
+
383
+
384
+ function __awaiter(thisArg, _arguments, P, generator) {
385
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
386
+ return new (P || (P = Promise))(function (resolve, reject) {
387
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
388
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
389
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
390
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
391
+ });
392
+ }
393
+
394
+ var eventemitter3 = {exports: {}};
395
+
396
+ (function (module) {
397
+
398
+ var has = Object.prototype.hasOwnProperty,
399
+ prefix = '~';
400
+
401
+ /**
402
+ * Constructor to create a storage for our `EE` objects.
403
+ * An `Events` instance is a plain object whose properties are event names.
404
+ *
405
+ * @constructor
406
+ * @private
407
+ */
408
+ function Events() {}
409
+
410
+ //
411
+ // We try to not inherit from `Object.prototype`. In some engines creating an
412
+ // instance in this way is faster than calling `Object.create(null)` directly.
413
+ // If `Object.create(null)` is not supported we prefix the event names with a
414
+ // character to make sure that the built-in object properties are not
415
+ // overridden or used as an attack vector.
416
+ //
417
+ if (Object.create) {
418
+ Events.prototype = Object.create(null);
419
+
420
+ //
421
+ // This hack is needed because the `__proto__` property is still inherited in
422
+ // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5.
423
+ //
424
+ if (!new Events().__proto__) prefix = false;
425
+ }
426
+
427
+ /**
428
+ * Representation of a single event listener.
429
+ *
430
+ * @param {Function} fn The listener function.
431
+ * @param {*} context The context to invoke the listener with.
432
+ * @param {Boolean} [once=false] Specify if the listener is a one-time listener.
433
+ * @constructor
434
+ * @private
435
+ */
436
+ function EE(fn, context, once) {
437
+ this.fn = fn;
438
+ this.context = context;
439
+ this.once = once || false;
440
+ }
441
+
442
+ /**
443
+ * Add a listener for a given event.
444
+ *
445
+ * @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
446
+ * @param {(String|Symbol)} event The event name.
447
+ * @param {Function} fn The listener function.
448
+ * @param {*} context The context to invoke the listener with.
449
+ * @param {Boolean} once Specify if the listener is a one-time listener.
450
+ * @returns {EventEmitter}
451
+ * @private
452
+ */
453
+ function addListener(emitter, event, fn, context, once) {
454
+ if (typeof fn !== 'function') {
455
+ throw new TypeError('The listener must be a function');
456
+ }
457
+ var listener = new EE(fn, context || emitter, once),
458
+ evt = prefix ? prefix + event : event;
459
+ if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++;else if (!emitter._events[evt].fn) emitter._events[evt].push(listener);else emitter._events[evt] = [emitter._events[evt], listener];
460
+ return emitter;
461
+ }
462
+
463
+ /**
464
+ * Clear event by name.
465
+ *
466
+ * @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
467
+ * @param {(String|Symbol)} evt The Event name.
468
+ * @private
469
+ */
470
+ function clearEvent(emitter, evt) {
471
+ if (--emitter._eventsCount === 0) emitter._events = new Events();else delete emitter._events[evt];
472
+ }
473
+
474
+ /**
475
+ * Minimal `EventEmitter` interface that is molded against the Node.js
476
+ * `EventEmitter` interface.
477
+ *
478
+ * @constructor
479
+ * @public
480
+ */
481
+ function EventEmitter() {
482
+ this._events = new Events();
483
+ this._eventsCount = 0;
484
+ }
485
+
486
+ /**
487
+ * Return an array listing the events for which the emitter has registered
488
+ * listeners.
489
+ *
490
+ * @returns {Array}
491
+ * @public
492
+ */
493
+ EventEmitter.prototype.eventNames = function eventNames() {
494
+ var names = [],
495
+ events,
496
+ name;
497
+ if (this._eventsCount === 0) return names;
498
+ for (name in events = this._events) {
499
+ if (has.call(events, name)) names.push(prefix ? name.slice(1) : name);
500
+ }
501
+ if (Object.getOwnPropertySymbols) {
502
+ return names.concat(Object.getOwnPropertySymbols(events));
503
+ }
504
+ return names;
505
+ };
506
+
507
+ /**
508
+ * Return the listeners registered for a given event.
509
+ *
510
+ * @param {(String|Symbol)} event The event name.
511
+ * @returns {Array} The registered listeners.
512
+ * @public
513
+ */
514
+ EventEmitter.prototype.listeners = function listeners(event) {
515
+ var evt = prefix ? prefix + event : event,
516
+ handlers = this._events[evt];
517
+ if (!handlers) return [];
518
+ if (handlers.fn) return [handlers.fn];
519
+ for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) {
520
+ ee[i] = handlers[i].fn;
521
+ }
522
+ return ee;
523
+ };
524
+
525
+ /**
526
+ * Return the number of listeners listening to a given event.
527
+ *
528
+ * @param {(String|Symbol)} event The event name.
529
+ * @returns {Number} The number of listeners.
530
+ * @public
531
+ */
532
+ EventEmitter.prototype.listenerCount = function listenerCount(event) {
533
+ var evt = prefix ? prefix + event : event,
534
+ listeners = this._events[evt];
535
+ if (!listeners) return 0;
536
+ if (listeners.fn) return 1;
537
+ return listeners.length;
538
+ };
539
+
540
+ /**
541
+ * Calls each of the listeners registered for a given event.
542
+ *
543
+ * @param {(String|Symbol)} event The event name.
544
+ * @returns {Boolean} `true` if the event had listeners, else `false`.
545
+ * @public
546
+ */
547
+ EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
548
+ var evt = prefix ? prefix + event : event;
549
+ if (!this._events[evt]) return false;
550
+ var listeners = this._events[evt],
551
+ len = arguments.length,
552
+ args,
553
+ i;
554
+ if (listeners.fn) {
555
+ if (listeners.once) this.removeListener(event, listeners.fn, undefined, true);
556
+ switch (len) {
557
+ case 1:
558
+ return listeners.fn.call(listeners.context), true;
559
+ case 2:
560
+ return listeners.fn.call(listeners.context, a1), true;
561
+ case 3:
562
+ return listeners.fn.call(listeners.context, a1, a2), true;
563
+ case 4:
564
+ return listeners.fn.call(listeners.context, a1, a2, a3), true;
565
+ case 5:
566
+ return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
567
+ case 6:
568
+ return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
569
+ }
570
+ for (i = 1, args = new Array(len - 1); i < len; i++) {
571
+ args[i - 1] = arguments[i];
572
+ }
573
+ listeners.fn.apply(listeners.context, args);
574
+ } else {
575
+ var length = listeners.length,
576
+ j;
577
+ for (i = 0; i < length; i++) {
578
+ if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true);
579
+ switch (len) {
580
+ case 1:
581
+ listeners[i].fn.call(listeners[i].context);
582
+ break;
583
+ case 2:
584
+ listeners[i].fn.call(listeners[i].context, a1);
585
+ break;
586
+ case 3:
587
+ listeners[i].fn.call(listeners[i].context, a1, a2);
588
+ break;
589
+ case 4:
590
+ listeners[i].fn.call(listeners[i].context, a1, a2, a3);
591
+ break;
592
+ default:
593
+ if (!args) for (j = 1, args = new Array(len - 1); j < len; j++) {
594
+ args[j - 1] = arguments[j];
595
+ }
596
+ listeners[i].fn.apply(listeners[i].context, args);
597
+ }
598
+ }
599
+ }
600
+ return true;
601
+ };
602
+
603
+ /**
604
+ * Add a listener for a given event.
605
+ *
606
+ * @param {(String|Symbol)} event The event name.
607
+ * @param {Function} fn The listener function.
608
+ * @param {*} [context=this] The context to invoke the listener with.
609
+ * @returns {EventEmitter} `this`.
610
+ * @public
611
+ */
612
+ EventEmitter.prototype.on = function on(event, fn, context) {
613
+ return addListener(this, event, fn, context, false);
614
+ };
615
+
616
+ /**
617
+ * Add a one-time listener for a given event.
618
+ *
619
+ * @param {(String|Symbol)} event The event name.
620
+ * @param {Function} fn The listener function.
621
+ * @param {*} [context=this] The context to invoke the listener with.
622
+ * @returns {EventEmitter} `this`.
623
+ * @public
624
+ */
625
+ EventEmitter.prototype.once = function once(event, fn, context) {
626
+ return addListener(this, event, fn, context, true);
627
+ };
628
+
629
+ /**
630
+ * Remove the listeners of a given event.
631
+ *
632
+ * @param {(String|Symbol)} event The event name.
633
+ * @param {Function} fn Only remove the listeners that match this function.
634
+ * @param {*} context Only remove the listeners that have this context.
635
+ * @param {Boolean} once Only remove one-time listeners.
636
+ * @returns {EventEmitter} `this`.
637
+ * @public
638
+ */
639
+ EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) {
640
+ var evt = prefix ? prefix + event : event;
641
+ if (!this._events[evt]) return this;
642
+ if (!fn) {
643
+ clearEvent(this, evt);
644
+ return this;
645
+ }
646
+ var listeners = this._events[evt];
647
+ if (listeners.fn) {
648
+ if (listeners.fn === fn && (!once || listeners.once) && (!context || listeners.context === context)) {
649
+ clearEvent(this, evt);
650
+ }
651
+ } else {
652
+ for (var i = 0, events = [], length = listeners.length; i < length; i++) {
653
+ if (listeners[i].fn !== fn || once && !listeners[i].once || context && listeners[i].context !== context) {
654
+ events.push(listeners[i]);
655
+ }
656
+ }
657
+
658
+ //
659
+ // Reset the array, or remove it completely if we have no more listeners.
660
+ //
661
+ if (events.length) this._events[evt] = events.length === 1 ? events[0] : events;else clearEvent(this, evt);
662
+ }
663
+ return this;
664
+ };
665
+
666
+ /**
667
+ * Remove all listeners, or those of the specified event.
668
+ *
669
+ * @param {(String|Symbol)} [event] The event name.
670
+ * @returns {EventEmitter} `this`.
671
+ * @public
672
+ */
673
+ EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) {
674
+ var evt;
675
+ if (event) {
676
+ evt = prefix ? prefix + event : event;
677
+ if (this._events[evt]) clearEvent(this, evt);
678
+ } else {
679
+ this._events = new Events();
680
+ this._eventsCount = 0;
681
+ }
682
+ return this;
683
+ };
684
+
685
+ //
686
+ // Alias methods names because people roll like that.
687
+ //
688
+ EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
689
+ EventEmitter.prototype.addListener = EventEmitter.prototype.on;
690
+
691
+ //
692
+ // Expose the prefix.
693
+ //
694
+ EventEmitter.prefixed = prefix;
695
+
696
+ //
697
+ // Allow `EventEmitter` to be imported as module namespace.
698
+ //
699
+ EventEmitter.EventEmitter = EventEmitter;
700
+
701
+ //
702
+ // Expose the module.
703
+ //
704
+ {
705
+ module.exports = EventEmitter;
706
+ }
707
+ })(eventemitter3);
708
+ var eventemitter3Exports = eventemitter3.exports;
709
+ var EventEmitter = /*@__PURE__*/getDefaultExportFromCjs(eventemitter3Exports);
710
+
711
+ const CryptorEvent = {
712
+ Error: 'cryptorError'
713
+ };
714
+
715
+ var AudioPresets;
716
+ (function (AudioPresets) {
717
+ AudioPresets.telephone = {
718
+ maxBitrate: 12000
719
+ };
720
+ AudioPresets.speech = {
721
+ maxBitrate: 20000
722
+ };
723
+ AudioPresets.music = {
724
+ maxBitrate: 32000
725
+ };
726
+ AudioPresets.musicStereo = {
727
+ maxBitrate: 48000
728
+ };
729
+ AudioPresets.musicHighQuality = {
730
+ maxBitrate: 64000
731
+ };
732
+ AudioPresets.musicHighQualityStereo = {
733
+ maxBitrate: 96000
734
+ };
735
+ })(AudioPresets || (AudioPresets = {}));
736
+
737
+ function isVideoFrame(frame) {
738
+ return 'type' in frame;
739
+ }
740
+ function importKey(keyBytes) {
741
+ let algorithm = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
742
+ name: ENCRYPTION_ALGORITHM
743
+ };
744
+ let usage = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'encrypt';
745
+ return __awaiter(this, void 0, void 0, function* () {
746
+ // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey
747
+ return crypto.subtle.importKey('raw', keyBytes, algorithm, false, usage === 'derive' ? ['deriveBits', 'deriveKey'] : ['encrypt', 'decrypt']);
748
+ });
749
+ }
750
+ function getAlgoOptions(algorithmName, salt) {
751
+ const textEncoder = new TextEncoder();
752
+ const encodedSalt = textEncoder.encode(salt);
753
+ switch (algorithmName) {
754
+ case 'HKDF':
755
+ return {
756
+ name: 'HKDF',
757
+ salt: encodedSalt,
758
+ hash: 'SHA-256',
759
+ info: new ArrayBuffer(128)
760
+ };
761
+ case 'PBKDF2':
762
+ {
763
+ return {
764
+ name: 'PBKDF2',
765
+ salt: encodedSalt,
766
+ hash: 'SHA-256',
767
+ iterations: 100000
768
+ };
769
+ }
770
+ default:
771
+ throw new Error("algorithm ".concat(algorithmName, " is currently unsupported"));
772
+ }
773
+ }
774
+ /**
775
+ * Derives a set of keys from the master key.
776
+ * See https://tools.ietf.org/html/draft-omara-sframe-00#section-4.3.1
777
+ */
778
+ function deriveKeys(material, salt) {
779
+ return __awaiter(this, void 0, void 0, function* () {
780
+ const algorithmOptions = getAlgoOptions(material.algorithm.name, salt);
781
+ // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey#HKDF
782
+ // https://developer.mozilla.org/en-US/docs/Web/API/HkdfParams
783
+ const encryptionKey = yield crypto.subtle.deriveKey(algorithmOptions, material, {
784
+ name: ENCRYPTION_ALGORITHM,
785
+ length: 128
786
+ }, false, ['encrypt', 'decrypt']);
787
+ return {
788
+ material,
789
+ encryptionKey
790
+ };
791
+ });
792
+ }
793
+ /**
794
+ * Ratchets a key. See
795
+ * https://tools.ietf.org/html/draft-omara-sframe-00#section-4.3.5.1
796
+ */
797
+ function ratchet(material, salt) {
798
+ return __awaiter(this, void 0, void 0, function* () {
799
+ const algorithmOptions = getAlgoOptions(material.algorithm.name, salt);
800
+ // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveBits
801
+ return crypto.subtle.deriveBits(algorithmOptions, material, 256);
802
+ });
803
+ }
804
+
805
+ class BaseFrameCryptor extends EventEmitter {
806
+ encodeFunction(encodedFrame, controller) {
807
+ throw Error('not implemented for subclass');
808
+ }
809
+ decodeFunction(encodedFrame, controller) {
810
+ throw Error('not implemented for subclass');
811
+ }
812
+ }
813
+ /**
814
+ * Cryptor is responsible for en-/decrypting media frames.
815
+ * Each Cryptor instance is responsible for en-/decrypting a single mediaStreamTrack.
816
+ */
817
+ class FrameCryptor extends BaseFrameCryptor {
818
+ constructor(opts) {
819
+ var _a;
820
+ super();
821
+ this.isKeyInvalid = false;
822
+ this.sendCounts = new Map();
823
+ this.keys = opts.keys;
824
+ this.participantId = opts.participantId;
825
+ this.rtpMap = new Map();
826
+ this.keyProviderOptions = opts.keyProviderOptions;
827
+ this.unencryptedFrameByteTrailer = (_a = opts.unencryptedFrameBytes) !== null && _a !== void 0 ? _a : new TextEncoder().encode('LKROCKS');
828
+ }
829
+ /**
830
+ * Assign a different participant to the cryptor.
831
+ * useful for transceiver re-use
832
+ * @param id
833
+ * @param keys
834
+ */
835
+ setParticipant(id, keys) {
836
+ this.participantId = id;
837
+ this.keys = keys;
838
+ }
839
+ unsetParticipant() {
840
+ this.participantId = undefined;
841
+ }
842
+ getParticipantId() {
843
+ return this.participantId;
844
+ }
845
+ getTrackId() {
846
+ return this.trackId;
847
+ }
848
+ /**
849
+ * Update the video codec used by the mediaStreamTrack
850
+ * @param codec
851
+ */
852
+ setVideoCodec(codec) {
853
+ this.videoCodec = codec;
854
+ }
855
+ /**
856
+ * rtp payload type map used for figuring out codec of payload type when encoding
857
+ * @param map
858
+ */
859
+ setRtpMap(map) {
860
+ this.rtpMap = map;
861
+ }
862
+ setupTransform(operation, readable, writable, trackId, codec) {
863
+ if (codec) {
864
+ console.info('setting codec on cryptor to', codec);
865
+ this.videoCodec = codec;
866
+ }
867
+ const transformFn = operation === 'encode' ? this.encodeFunction : this.decodeFunction;
868
+ const transformStream = new TransformStream({
869
+ transform: transformFn.bind(this)
870
+ });
871
+ readable.pipeThrough(transformStream).pipeTo(writable).catch(e => {
872
+ console.error(e);
873
+ this.emit('cryptorError', e instanceof CryptorError ? e : new CryptorError(e.message));
874
+ });
875
+ this.trackId = trackId;
876
+ }
877
+ /**
878
+ * Function that will be injected in a stream and will encrypt the given encoded frames.
879
+ *
880
+ * @param {RTCEncodedVideoFrame|RTCEncodedAudioFrame} encodedFrame - Encoded video frame.
881
+ * @param {TransformStreamDefaultController} controller - TransportStreamController.
882
+ *
883
+ * The VP8 payload descriptor described in
884
+ * https://tools.ietf.org/html/rfc7741#section-4.2
885
+ * is part of the RTP packet and not part of the frame and is not controllable by us.
886
+ * This is fine as the SFU keeps having access to it for routing.
887
+ *
888
+ * The encrypted frame is formed as follows:
889
+ * 1) Find unencrypted byte length, depending on the codec, frame type and kind.
890
+ * 2) Form the GCM IV for the frame as described above.
891
+ * 3) Encrypt the rest of the frame using AES-GCM.
892
+ * 4) Allocate space for the encrypted frame.
893
+ * 5) Copy the unencrypted bytes to the start of the encrypted frame.
894
+ * 6) Append the ciphertext to the encrypted frame.
895
+ * 7) Append the IV.
896
+ * 8) Append a single byte for the key identifier.
897
+ * 9) Enqueue the encrypted frame for sending.
898
+ */
899
+ encodeFunction(encodedFrame, controller) {
900
+ var _a;
901
+ return __awaiter(this, void 0, void 0, function* () {
902
+ if (!this.keys.isEnabled() ||
903
+ // skip for encryption for empty dtx frames
904
+ encodedFrame.data.byteLength === 0) {
905
+ return controller.enqueue(encodedFrame);
906
+ }
907
+ const {
908
+ encryptionKey
909
+ } = this.keys.getKeySet();
910
+ const keyIndex = this.keys.getCurrentKeyIndex();
911
+ if (encryptionKey) {
912
+ const iv = this.makeIV((_a = encodedFrame.getMetadata().synchronizationSource) !== null && _a !== void 0 ? _a : -1, encodedFrame.timestamp);
913
+ // Thіs is not encrypted and contains the VP8 payload descriptor or the Opus TOC byte.
914
+ const frameHeader = new Uint8Array(encodedFrame.data, 0, this.getUnencryptedBytes(encodedFrame));
915
+ // Frame trailer contains the R|IV_LENGTH and key index
916
+ const frameTrailer = new Uint8Array(2);
917
+ frameTrailer[0] = IV_LENGTH;
918
+ frameTrailer[1] = keyIndex;
919
+ // Construct frame trailer. Similar to the frame header described in
920
+ // https://tools.ietf.org/html/draft-omara-sframe-00#section-4.2
921
+ // but we put it at the end.
922
+ //
923
+ // ---------+-------------------------+-+---------+----
924
+ // payload |IV...(length = IV_LENGTH)|R|IV_LENGTH|KID |
925
+ // ---------+-------------------------+-+---------+----
926
+ try {
927
+ const cipherText = yield crypto.subtle.encrypt({
928
+ name: ENCRYPTION_ALGORITHM,
929
+ iv,
930
+ additionalData: new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength)
931
+ }, encryptionKey, new Uint8Array(encodedFrame.data, this.getUnencryptedBytes(encodedFrame)));
932
+ const newData = new ArrayBuffer(frameHeader.byteLength + cipherText.byteLength + iv.byteLength + frameTrailer.byteLength);
933
+ const newUint8 = new Uint8Array(newData);
934
+ newUint8.set(frameHeader); // copy first bytes.
935
+ newUint8.set(new Uint8Array(cipherText), frameHeader.byteLength); // add ciphertext.
936
+ newUint8.set(new Uint8Array(iv), frameHeader.byteLength + cipherText.byteLength); // append IV.
937
+ newUint8.set(frameTrailer, frameHeader.byteLength + cipherText.byteLength + iv.byteLength); // append frame trailer.
938
+ encodedFrame.data = newData;
939
+ return controller.enqueue(encodedFrame);
940
+ } catch (e) {
941
+ // TODO: surface this to the app.
942
+ workerLogger.error(e);
943
+ }
944
+ } else {
945
+ this.emit(CryptorEvent.Error, new CryptorError("encryption key missing for encoding", CryptorErrorReason.MissingKey));
946
+ }
947
+ });
948
+ }
949
+ /**
950
+ * Function that will be injected in a stream and will decrypt the given encoded frames.
951
+ *
952
+ * @param {RTCEncodedVideoFrame|RTCEncodedAudioFrame} encodedFrame - Encoded video frame.
953
+ * @param {TransformStreamDefaultController} controller - TransportStreamController.
954
+ */
955
+ decodeFunction(encodedFrame, controller) {
956
+ return __awaiter(this, void 0, void 0, function* () {
957
+ if (!this.keys.isEnabled() ||
958
+ // skip for decryption for empty dtx frames
959
+ encodedFrame.data.byteLength === 0 ||
960
+ // skip decryption if frame is server injected
961
+ isFrameServerInjected(encodedFrame.data, this.unencryptedFrameByteTrailer)) {
962
+ return controller.enqueue(encodedFrame);
963
+ }
964
+ const data = new Uint8Array(encodedFrame.data);
965
+ const keyIndex = data[encodedFrame.data.byteLength - 1];
966
+ if (this.keys.getKeySet(keyIndex)) {
967
+ try {
968
+ const decodedFrame = yield this.decryptFrame(encodedFrame, keyIndex);
969
+ if (decodedFrame) {
970
+ return controller.enqueue(decodedFrame);
971
+ }
972
+ this.isKeyInvalid = false;
973
+ } catch (error) {
974
+ if (error instanceof CryptorError && error.reason === CryptorErrorReason.InvalidKey) {
975
+ if (!this.isKeyInvalid) {
976
+ workerLogger.warn('invalid key');
977
+ this.emit(CryptorEvent.Error, new CryptorError("invalid key for participant ".concat(this.participantId), CryptorErrorReason.InvalidKey));
978
+ this.isKeyInvalid = true;
979
+ }
980
+ } else {
981
+ workerLogger.warn('decoding frame failed', {
982
+ error
983
+ });
984
+ }
985
+ }
986
+ } else {
987
+ this.emit(CryptorEvent.Error, new CryptorError("key missing for participant ".concat(this.participantId), CryptorErrorReason.MissingKey));
988
+ }
989
+ return controller.enqueue(encodedFrame);
990
+ });
991
+ }
992
+ /**
993
+ * Function that will decrypt the given encoded frame. If the decryption fails, it will
994
+ * ratchet the key for up to RATCHET_WINDOW_SIZE times.
995
+ */
996
+ decryptFrame(encodedFrame, keyIndex) {
997
+ let initialMaterial = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : undefined;
998
+ let ratchetOpts = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {
999
+ ratchetCount: 0
1000
+ };
1001
+ var _a;
1002
+ return __awaiter(this, void 0, void 0, function* () {
1003
+ const keySet = this.keys.getKeySet(keyIndex);
1004
+ // Construct frame trailer. Similar to the frame header described in
1005
+ // https://tools.ietf.org/html/draft-omara-sframe-00#section-4.2
1006
+ // but we put it at the end.
1007
+ //
1008
+ // ---------+-------------------------+-+---------+----
1009
+ // payload |IV...(length = IV_LENGTH)|R|IV_LENGTH|KID |
1010
+ // ---------+-------------------------+-+---------+----
1011
+ try {
1012
+ const frameHeader = new Uint8Array(encodedFrame.data, 0, this.getUnencryptedBytes(encodedFrame));
1013
+ const frameTrailer = new Uint8Array(encodedFrame.data, encodedFrame.data.byteLength - 2, 2);
1014
+ const ivLength = frameTrailer[0];
1015
+ const iv = new Uint8Array(encodedFrame.data, encodedFrame.data.byteLength - ivLength - frameTrailer.byteLength, ivLength);
1016
+ const cipherTextStart = frameHeader.byteLength;
1017
+ const cipherTextLength = encodedFrame.data.byteLength - (frameHeader.byteLength + ivLength + frameTrailer.byteLength);
1018
+ const plainText = yield crypto.subtle.decrypt({
1019
+ name: ENCRYPTION_ALGORITHM,
1020
+ iv,
1021
+ additionalData: new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength)
1022
+ }, (_a = ratchetOpts.encryptionKey) !== null && _a !== void 0 ? _a : keySet.encryptionKey, new Uint8Array(encodedFrame.data, cipherTextStart, cipherTextLength));
1023
+ const newData = new ArrayBuffer(frameHeader.byteLength + plainText.byteLength);
1024
+ const newUint8 = new Uint8Array(newData);
1025
+ newUint8.set(new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength));
1026
+ newUint8.set(new Uint8Array(plainText), frameHeader.byteLength);
1027
+ encodedFrame.data = newData;
1028
+ return encodedFrame;
1029
+ } catch (error) {
1030
+ if (this.keyProviderOptions.ratchetWindowSize > 0) {
1031
+ if (ratchetOpts.ratchetCount < this.keyProviderOptions.ratchetWindowSize) {
1032
+ workerLogger.debug("ratcheting key attempt ".concat(ratchetOpts.ratchetCount, " of ").concat(this.keyProviderOptions.ratchetWindowSize, ", for kind ").concat(encodedFrame instanceof RTCEncodedAudioFrame ? 'audio' : 'video'));
1033
+ let ratchetedKeySet;
1034
+ if (keySet === this.keys.getKeySet(keyIndex)) {
1035
+ // only ratchet if the currently set key is still the same as the one used to decrypt this frame
1036
+ // if not, it might be that a different frame has already ratcheted and we try with that one first
1037
+ const newMaterial = yield this.keys.ratchetKey(keyIndex, false);
1038
+ ratchetedKeySet = yield deriveKeys(newMaterial, this.keyProviderOptions.ratchetSalt);
1039
+ }
1040
+ const frame = yield this.decryptFrame(encodedFrame, keyIndex, initialMaterial || keySet, {
1041
+ ratchetCount: ratchetOpts.ratchetCount + 1,
1042
+ encryptionKey: ratchetedKeySet === null || ratchetedKeySet === void 0 ? void 0 : ratchetedKeySet.encryptionKey
1043
+ });
1044
+ if (frame && ratchetedKeySet) {
1045
+ this.keys.setKeySet(ratchetedKeySet, keyIndex, true);
1046
+ // decryption was successful, set the new key index to reflect the ratcheted key set
1047
+ this.keys.setCurrentKeyIndex(keyIndex);
1048
+ }
1049
+ return frame;
1050
+ } else {
1051
+ /**
1052
+ * Since the key it is first send and only afterwards actually used for encrypting, there were
1053
+ * situations when the decrypting failed due to the fact that the received frame was not encrypted
1054
+ * yet and ratcheting, of course, did not solve the problem. So if we fail RATCHET_WINDOW_SIZE times,
1055
+ * we come back to the initial key.
1056
+ */
1057
+ if (initialMaterial) {
1058
+ workerLogger.debug('resetting to initial material');
1059
+ this.keys.setKeyFromMaterial(initialMaterial.material, keyIndex);
1060
+ }
1061
+ workerLogger.warn('maximum ratchet attempts exceeded, resetting key');
1062
+ }
1063
+ } else {
1064
+ throw new CryptorError('Decryption failed, most likely because of an invalid key', CryptorErrorReason.InvalidKey);
1065
+ }
1066
+ }
1067
+ });
1068
+ }
1069
+ /**
1070
+ * Construct the IV used for AES-GCM and sent (in plain) with the packet similar to
1071
+ * https://tools.ietf.org/html/rfc7714#section-8.1
1072
+ * It concatenates
1073
+ * - the 32 bit synchronization source (SSRC) given on the encoded frame,
1074
+ * - the 32 bit rtp timestamp given on the encoded frame,
1075
+ * - a send counter that is specific to the SSRC. Starts at a random number.
1076
+ * The send counter is essentially the pictureId but we currently have to implement this ourselves.
1077
+ * There is no XOR with a salt. Note that this IV leaks the SSRC to the receiver but since this is
1078
+ * randomly generated and SFUs may not rewrite this is considered acceptable.
1079
+ * The SSRC is used to allow demultiplexing multiple streams with the same key, as described in
1080
+ * https://tools.ietf.org/html/rfc3711#section-4.1.1
1081
+ * The RTP timestamp is 32 bits and advances by the codec clock rate (90khz for video, 48khz for
1082
+ * opus audio) every second. For video it rolls over roughly every 13 hours.
1083
+ * The send counter will advance at the frame rate (30fps for video, 50fps for 20ms opus audio)
1084
+ * every second. It will take a long time to roll over.
1085
+ *
1086
+ * See also https://developer.mozilla.org/en-US/docs/Web/API/AesGcmParams
1087
+ */
1088
+ makeIV(synchronizationSource, timestamp) {
1089
+ var _a;
1090
+ const iv = new ArrayBuffer(IV_LENGTH);
1091
+ const ivView = new DataView(iv);
1092
+ // having to keep our own send count (similar to a picture id) is not ideal.
1093
+ if (!this.sendCounts.has(synchronizationSource)) {
1094
+ // Initialize with a random offset, similar to the RTP sequence number.
1095
+ this.sendCounts.set(synchronizationSource, Math.floor(Math.random() * 0xffff));
1096
+ }
1097
+ const sendCount = (_a = this.sendCounts.get(synchronizationSource)) !== null && _a !== void 0 ? _a : 0;
1098
+ ivView.setUint32(0, synchronizationSource);
1099
+ ivView.setUint32(4, timestamp);
1100
+ ivView.setUint32(8, timestamp - sendCount % 0xffff);
1101
+ this.sendCounts.set(synchronizationSource, sendCount + 1);
1102
+ return iv;
1103
+ }
1104
+ getUnencryptedBytes(frame) {
1105
+ var _a;
1106
+ if (isVideoFrame(frame)) {
1107
+ let detectedCodec = (_a = this.getVideoCodec(frame)) !== null && _a !== void 0 ? _a : this.videoCodec;
1108
+ if (detectedCodec === 'av1' || detectedCodec === 'vp9') {
1109
+ throw new Error("".concat(detectedCodec, " is not yet supported for end to end encryption"));
1110
+ }
1111
+ if (detectedCodec === 'vp8') {
1112
+ return UNENCRYPTED_BYTES[frame.type];
1113
+ }
1114
+ const data = new Uint8Array(frame.data);
1115
+ try {
1116
+ const naluIndices = findNALUIndices(data);
1117
+ // if the detected codec is undefined we test whether it _looks_ like a h264 frame as a best guess
1118
+ const isH264 = detectedCodec === 'h264' || naluIndices.some(naluIndex => [NALUType.SLICE_IDR, NALUType.SLICE_NON_IDR].includes(parseNALUType(data[naluIndex])));
1119
+ if (isH264) {
1120
+ for (const index of naluIndices) {
1121
+ let type = parseNALUType(data[index]);
1122
+ switch (type) {
1123
+ case NALUType.SLICE_IDR:
1124
+ case NALUType.SLICE_NON_IDR:
1125
+ return index + 2;
1126
+ default:
1127
+ break;
1128
+ }
1129
+ }
1130
+ throw new TypeError('Could not find NALU');
1131
+ }
1132
+ } catch (e) {
1133
+ // no op, we just continue and fallback to vp8
1134
+ }
1135
+ return UNENCRYPTED_BYTES[frame.type];
1136
+ } else {
1137
+ return UNENCRYPTED_BYTES.audio;
1138
+ }
1139
+ }
1140
+ /**
1141
+ * inspects frame payloadtype if available and maps it to the codec specified in rtpMap
1142
+ */
1143
+ getVideoCodec(frame) {
1144
+ if (this.rtpMap.size === 0) {
1145
+ return undefined;
1146
+ }
1147
+ // @ts-expect-error payloadType is not yet part of the typescript definition and currently not supported in Safari
1148
+ const payloadType = frame.getMetadata().payloadType;
1149
+ const codec = payloadType ? this.rtpMap.get(payloadType) : undefined;
1150
+ return codec;
1151
+ }
1152
+ }
1153
+ /**
1154
+ * Slice the NALUs present in the supplied buffer, assuming it is already byte-aligned
1155
+ * code adapted from https://github.com/medooze/h264-frame-parser/blob/main/lib/NalUnits.ts to return indices only
1156
+ */
1157
+ function findNALUIndices(stream) {
1158
+ const result = [];
1159
+ let start = 0,
1160
+ pos = 0,
1161
+ searchLength = stream.length - 2;
1162
+ while (pos < searchLength) {
1163
+ // skip until end of current NALU
1164
+ while (pos < searchLength && !(stream[pos] === 0 && stream[pos + 1] === 0 && stream[pos + 2] === 1)) pos++;
1165
+ if (pos >= searchLength) pos = stream.length;
1166
+ // remove trailing zeros from current NALU
1167
+ let end = pos;
1168
+ while (end > start && stream[end - 1] === 0) end--;
1169
+ // save current NALU
1170
+ if (start === 0) {
1171
+ if (end !== start) throw TypeError('byte stream contains leading data');
1172
+ } else {
1173
+ result.push(start);
1174
+ }
1175
+ // begin new NALU
1176
+ start = pos = pos + 3;
1177
+ }
1178
+ return result;
1179
+ }
1180
+ function parseNALUType(startByte) {
1181
+ return startByte & kNaluTypeMask;
1182
+ }
1183
+ const kNaluTypeMask = 0x1f;
1184
+ var NALUType;
1185
+ (function (NALUType) {
1186
+ /** Coded slice of a non-IDR picture */
1187
+ NALUType[NALUType["SLICE_NON_IDR"] = 1] = "SLICE_NON_IDR";
1188
+ /** Coded slice data partition A */
1189
+ NALUType[NALUType["SLICE_PARTITION_A"] = 2] = "SLICE_PARTITION_A";
1190
+ /** Coded slice data partition B */
1191
+ NALUType[NALUType["SLICE_PARTITION_B"] = 3] = "SLICE_PARTITION_B";
1192
+ /** Coded slice data partition C */
1193
+ NALUType[NALUType["SLICE_PARTITION_C"] = 4] = "SLICE_PARTITION_C";
1194
+ /** Coded slice of an IDR picture */
1195
+ NALUType[NALUType["SLICE_IDR"] = 5] = "SLICE_IDR";
1196
+ /** Supplemental enhancement information */
1197
+ NALUType[NALUType["SEI"] = 6] = "SEI";
1198
+ /** Sequence parameter set */
1199
+ NALUType[NALUType["SPS"] = 7] = "SPS";
1200
+ /** Picture parameter set */
1201
+ NALUType[NALUType["PPS"] = 8] = "PPS";
1202
+ /** Access unit delimiter */
1203
+ NALUType[NALUType["AUD"] = 9] = "AUD";
1204
+ /** End of sequence */
1205
+ NALUType[NALUType["END_SEQ"] = 10] = "END_SEQ";
1206
+ /** End of stream */
1207
+ NALUType[NALUType["END_STREAM"] = 11] = "END_STREAM";
1208
+ /** Filler data */
1209
+ NALUType[NALUType["FILLER_DATA"] = 12] = "FILLER_DATA";
1210
+ /** Sequence parameter set extension */
1211
+ NALUType[NALUType["SPS_EXT"] = 13] = "SPS_EXT";
1212
+ /** Prefix NAL unit */
1213
+ NALUType[NALUType["PREFIX_NALU"] = 14] = "PREFIX_NALU";
1214
+ /** Subset sequence parameter set */
1215
+ NALUType[NALUType["SUBSET_SPS"] = 15] = "SUBSET_SPS";
1216
+ /** Depth parameter set */
1217
+ NALUType[NALUType["DPS"] = 16] = "DPS";
1218
+ // 17, 18 reserved
1219
+ /** Coded slice of an auxiliary coded picture without partitioning */
1220
+ NALUType[NALUType["SLICE_AUX"] = 19] = "SLICE_AUX";
1221
+ /** Coded slice extension */
1222
+ NALUType[NALUType["SLICE_EXT"] = 20] = "SLICE_EXT";
1223
+ /** Coded slice extension for a depth view component or a 3D-AVC texture view component */
1224
+ NALUType[NALUType["SLICE_LAYER_EXT"] = 21] = "SLICE_LAYER_EXT";
1225
+ // 22, 23 reserved
1226
+ })(NALUType || (NALUType = {}));
1227
+ /**
1228
+ * we use a magic frame trailer to detect whether a frame is injected
1229
+ * by the livekit server and thus to be treated as unencrypted
1230
+ * @internal
1231
+ */
1232
+ function isFrameServerInjected(frameData, trailerBytes) {
1233
+ const frameTrailer = new Uint8Array(frameData.slice(frameData.byteLength - trailerBytes.byteLength));
1234
+ return trailerBytes.every((value, index) => value === frameTrailer[index]);
1235
+ }
1236
+
1237
+ // TODO ParticipantKeyHandlers currently don't get destroyed on participant disconnect
1238
+ // we could do this by having a separate worker message on participant disconnected.
1239
+ /**
1240
+ * ParticipantKeyHandler is responsible for providing a cryptor instance with the
1241
+ * en-/decryption key of a participant. It assumes that all tracks of a specific participant
1242
+ * are encrypted with the same key.
1243
+ * Additionally it exposes a method to ratchet a key which can be used by the cryptor either automatically
1244
+ * if decryption fails or can be triggered manually on both sender and receiver side.
1245
+ *
1246
+ */
1247
+ class ParticipantKeyHandler extends EventEmitter {
1248
+ constructor(participantId, isEnabled, keyProviderOptions) {
1249
+ super();
1250
+ this.currentKeyIndex = 0;
1251
+ this.cryptoKeyRing = new Array(KEYRING_SIZE);
1252
+ this.enabled = isEnabled;
1253
+ this.keyProviderOptions = keyProviderOptions;
1254
+ this.ratchetPromiseMap = new Map();
1255
+ this.participantId = participantId;
1256
+ }
1257
+ setEnabled(enabled) {
1258
+ this.enabled = enabled;
1259
+ }
1260
+ /**
1261
+ * Ratchets the current key (or the one at keyIndex if provided) and
1262
+ * returns the ratcheted material
1263
+ * if `setKey` is true (default), it will also set the ratcheted key directly on the crypto key ring
1264
+ * @param keyIndex
1265
+ * @param setKey
1266
+ */
1267
+ ratchetKey(keyIndex) {
1268
+ let setKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
1269
+ const currentKeyIndex = keyIndex !== null && keyIndex !== void 0 ? keyIndex : keyIndex = this.getCurrentKeyIndex();
1270
+ const existingPromise = this.ratchetPromiseMap.get(currentKeyIndex);
1271
+ if (typeof existingPromise !== 'undefined') {
1272
+ return existingPromise;
1273
+ }
1274
+ const ratchetPromise = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
1275
+ try {
1276
+ const currentMaterial = this.getKeySet(currentKeyIndex).material;
1277
+ const newMaterial = yield importKey(yield ratchet(currentMaterial, this.keyProviderOptions.ratchetSalt), currentMaterial.algorithm.name, 'derive');
1278
+ if (setKey) {
1279
+ this.setKeyFromMaterial(newMaterial, currentKeyIndex, true);
1280
+ }
1281
+ this.emit('keyRatcheted', newMaterial, keyIndex, this.participantId);
1282
+ resolve(newMaterial);
1283
+ } catch (e) {
1284
+ reject(e);
1285
+ } finally {
1286
+ this.ratchetPromiseMap.delete(currentKeyIndex);
1287
+ }
1288
+ }));
1289
+ this.ratchetPromiseMap.set(currentKeyIndex, ratchetPromise);
1290
+ return ratchetPromise;
1291
+ }
1292
+ /**
1293
+ * takes in a key material with `deriveBits` and `deriveKey` set as key usages
1294
+ * and derives encryption keys from the material and sets it on the key ring buffer
1295
+ * together with the material
1296
+ * also updates the currentKeyIndex
1297
+ */
1298
+ setKeyFromMaterial(material) {
1299
+ let keyIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
1300
+ let emitRatchetEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
1301
+ return __awaiter(this, void 0, void 0, function* () {
1302
+ workerLogger.debug('setting new key');
1303
+ if (keyIndex >= 0) {
1304
+ this.currentKeyIndex = keyIndex % this.cryptoKeyRing.length;
1305
+ }
1306
+ const keySet = yield deriveKeys(material, this.keyProviderOptions.ratchetSalt);
1307
+ this.setKeySet(keySet, this.currentKeyIndex, emitRatchetEvent);
1308
+ });
1309
+ }
1310
+ setKeySet(keySet, keyIndex) {
1311
+ let emitRatchetEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
1312
+ return __awaiter(this, void 0, void 0, function* () {
1313
+ this.cryptoKeyRing[keyIndex % this.cryptoKeyRing.length] = keySet;
1314
+ if (emitRatchetEvent) {
1315
+ this.emit('keyRatcheted', keySet.material, keyIndex, this.participantId);
1316
+ }
1317
+ });
1318
+ }
1319
+ setCurrentKeyIndex(index) {
1320
+ return __awaiter(this, void 0, void 0, function* () {
1321
+ this.currentKeyIndex = index % this.cryptoKeyRing.length;
1322
+ });
1323
+ }
1324
+ isEnabled() {
1325
+ return this.enabled;
1326
+ }
1327
+ getCurrentKeyIndex() {
1328
+ return this.currentKeyIndex;
1329
+ }
1330
+ /**
1331
+ * returns currently used KeySet or the one at `keyIndex` if provided
1332
+ * @param keyIndex
1333
+ * @returns
1334
+ */
1335
+ getKeySet(keyIndex) {
1336
+ return this.cryptoKeyRing[keyIndex !== null && keyIndex !== void 0 ? keyIndex : this.currentKeyIndex];
1337
+ }
1338
+ }
1339
+
1340
+ const participantCryptors = [];
1341
+ const participantKeys = new Map();
1342
+ let publishCryptors = [];
1343
+ let publisherKeys;
1344
+ let isEncryptionEnabled = false;
1345
+ let useSharedKey = false;
1346
+ let sharedKey;
1347
+ let keyProviderOptions = KEY_PROVIDER_DEFAULTS;
1348
+ workerLogger.setDefaultLevel('info');
1349
+ onmessage = ev => {
1350
+ const {
1351
+ kind,
1352
+ data
1353
+ } = ev.data;
1354
+ switch (kind) {
1355
+ case 'init':
1356
+ workerLogger.info('worker initialized');
1357
+ keyProviderOptions = data.keyProviderOptions;
1358
+ useSharedKey = !!data.keyProviderOptions.sharedKey;
1359
+ // acknowledge init successful
1360
+ const enableMsg = {
1361
+ kind: 'enable',
1362
+ data: {
1363
+ enabled: isEncryptionEnabled
1364
+ }
1365
+ };
1366
+ publisherKeys = new ParticipantKeyHandler(undefined, isEncryptionEnabled, keyProviderOptions);
1367
+ publisherKeys.on('keyRatcheted', emitRatchetedKeys);
1368
+ postMessage(enableMsg);
1369
+ break;
1370
+ case 'enable':
1371
+ setEncryptionEnabled(data.enabled, data.participantId);
1372
+ workerLogger.info('updated e2ee enabled status');
1373
+ // acknowledge enable call successful
1374
+ postMessage(ev.data);
1375
+ break;
1376
+ case 'decode':
1377
+ let cryptor = getTrackCryptor(data.participantId, data.trackId);
1378
+ cryptor.setupTransform(kind, data.readableStream, data.writableStream, data.trackId, data.codec);
1379
+ break;
1380
+ case 'encode':
1381
+ let pubCryptor = getPublisherCryptor(data.trackId);
1382
+ pubCryptor.setupTransform(kind, data.readableStream, data.writableStream, data.trackId, data.codec);
1383
+ break;
1384
+ case 'setKey':
1385
+ if (useSharedKey) {
1386
+ workerLogger.debug('set shared key');
1387
+ setSharedKey(data.key, data.keyIndex);
1388
+ } else if (data.participantId) {
1389
+ getParticipantKeyHandler(data.participantId).setKeyFromMaterial(data.key, data.keyIndex);
1390
+ } else {
1391
+ workerLogger.error('no participant Id was provided and shared key usage is disabled');
1392
+ }
1393
+ break;
1394
+ case 'removeTransform':
1395
+ unsetCryptorParticipant(data.trackId);
1396
+ break;
1397
+ case 'updateCodec':
1398
+ getTrackCryptor(data.participantId, data.trackId).setVideoCodec(data.codec);
1399
+ break;
1400
+ case 'setRTPMap':
1401
+ publishCryptors.forEach(cr => {
1402
+ cr.setRtpMap(data.map);
1403
+ });
1404
+ break;
1405
+ case 'ratchetRequest':
1406
+ getParticipantKeyHandler(data.participantId).ratchetKey(data.keyIndex);
1407
+ }
1408
+ };
1409
+ function getTrackCryptor(participantId, trackId) {
1410
+ let cryptor = participantCryptors.find(c => c.getTrackId() === trackId);
1411
+ if (!cryptor) {
1412
+ workerLogger.info('creating new cryptor for', {
1413
+ participantId
1414
+ });
1415
+ if (!keyProviderOptions) {
1416
+ throw Error('Missing keyProvider options');
1417
+ }
1418
+ cryptor = new FrameCryptor({
1419
+ participantId,
1420
+ keys: getParticipantKeyHandler(participantId),
1421
+ keyProviderOptions
1422
+ });
1423
+ setupCryptorErrorEvents(cryptor);
1424
+ participantCryptors.push(cryptor);
1425
+ } else if (participantId !== cryptor.getParticipantId()) {
1426
+ // assign new participant id to track cryptor and pass in correct key handler
1427
+ cryptor.setParticipant(participantId, getParticipantKeyHandler(participantId));
1428
+ }
1429
+ return cryptor;
1430
+ }
1431
+ function getParticipantKeyHandler(participantId) {
1432
+ if (!participantId) {
1433
+ return publisherKeys;
1434
+ }
1435
+ let keys = participantKeys.get(participantId);
1436
+ if (!keys) {
1437
+ keys = new ParticipantKeyHandler(participantId, true, keyProviderOptions);
1438
+ if (sharedKey) {
1439
+ keys.setKeyFromMaterial(sharedKey);
1440
+ }
1441
+ participantKeys.set(participantId, keys);
1442
+ }
1443
+ return keys;
1444
+ }
1445
+ function unsetCryptorParticipant(trackId) {
1446
+ var _a;
1447
+ (_a = participantCryptors.find(c => c.getTrackId() === trackId)) === null || _a === void 0 ? void 0 : _a.unsetParticipant();
1448
+ }
1449
+ function getPublisherCryptor(trackId) {
1450
+ let publishCryptor = publishCryptors.find(cryptor => cryptor.getTrackId() === trackId);
1451
+ if (!publishCryptor) {
1452
+ if (!keyProviderOptions) {
1453
+ throw new TypeError('Missing keyProvider options');
1454
+ }
1455
+ publishCryptor = new FrameCryptor({
1456
+ keys: publisherKeys,
1457
+ participantId: 'publisher',
1458
+ keyProviderOptions
1459
+ });
1460
+ setupCryptorErrorEvents(publishCryptor);
1461
+ publishCryptors.push(publishCryptor);
1462
+ }
1463
+ return publishCryptor;
1464
+ }
1465
+ function setEncryptionEnabled(enable, participantId) {
1466
+ if (!participantId) {
1467
+ isEncryptionEnabled = enable;
1468
+ publisherKeys.setEnabled(enable);
1469
+ } else {
1470
+ getParticipantKeyHandler(participantId).setEnabled(enable);
1471
+ }
1472
+ }
1473
+ function setSharedKey(key, index) {
1474
+ workerLogger.debug('setting shared key');
1475
+ sharedKey = key;
1476
+ publisherKeys === null || publisherKeys === void 0 ? void 0 : publisherKeys.setKeyFromMaterial(key, index);
1477
+ for (const [, keyHandler] of participantKeys) {
1478
+ keyHandler.setKeyFromMaterial(key, index);
1479
+ }
1480
+ }
1481
+ function setupCryptorErrorEvents(cryptor) {
1482
+ cryptor.on('cryptorError', error => {
1483
+ const msg = {
1484
+ kind: 'error',
1485
+ data: {
1486
+ error: new Error("".concat(CryptorErrorReason[error.reason], ": ").concat(error.message))
1487
+ }
1488
+ };
1489
+ postMessage(msg);
1490
+ });
1491
+ }
1492
+ function emitRatchetedKeys(material, keyIndex) {
1493
+ const msg = {
1494
+ kind: "ratchetKey",
1495
+ data: {
1496
+ // participantId,
1497
+ keyIndex,
1498
+ material
1499
+ }
1500
+ };
1501
+ postMessage(msg);
1502
+ }
1503
+ // Operations using RTCRtpScriptTransform.
1504
+ // @ts-ignore
1505
+ if (self.RTCTransformEvent) {
1506
+ workerLogger.debug('setup transform event');
1507
+ // @ts-ignore
1508
+ self.onrtctransform = event => {
1509
+ const transformer = event.transformer;
1510
+ workerLogger.debug('transformer', transformer);
1511
+ transformer.handled = true;
1512
+ const {
1513
+ kind,
1514
+ participantId,
1515
+ trackId,
1516
+ codec
1517
+ } = transformer.options;
1518
+ const cryptor = kind === 'encode' ? getPublisherCryptor(trackId) : getTrackCryptor(participantId, trackId);
1519
+ workerLogger.debug('transform', {
1520
+ codec
1521
+ });
1522
+ cryptor.setupTransform(kind, transformer.readable, transformer.writable, trackId, codec);
1523
+ };
1524
+ }
1525
+ //# sourceMappingURL=livekit-client.e2ee.worker.mjs.map