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,1545 @@
1
+ /******************************************************************************
2
+ Copyright (c) Microsoft Corporation.
3
+
4
+ Permission to use, copy, modify, and/or distribute this software for any
5
+ purpose with or without fee is hereby granted.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
12
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13
+ PERFORMANCE OF THIS SOFTWARE.
14
+ ***************************************************************************** */
15
+ /* global Reflect, Promise */
16
+
17
+
18
+ function __awaiter(thisArg, _arguments, P, generator) {
19
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
20
+ return new (P || (P = Promise))(function (resolve, reject) {
21
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
22
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
23
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
24
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
25
+ });
26
+ }
27
+
28
+ var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
29
+
30
+ function getDefaultExportFromCjs (x) {
31
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
32
+ }
33
+
34
+ var loglevel = {exports: {}};
35
+
36
+ /*
37
+ * loglevel - https://github.com/pimterry/loglevel
38
+ *
39
+ * Copyright (c) 2013 Tim Perry
40
+ * Licensed under the MIT license.
41
+ */
42
+ (function (module) {
43
+ (function (root, definition) {
44
+
45
+ if (module.exports) {
46
+ module.exports = definition();
47
+ } else {
48
+ root.log = definition();
49
+ }
50
+ })(commonjsGlobal, function () {
51
+
52
+ // Slightly dubious tricks to cut down minimized file size
53
+ var noop = function () {};
54
+ var undefinedType = "undefined";
55
+ var isIE = typeof window !== undefinedType && typeof window.navigator !== undefinedType && /Trident\/|MSIE /.test(window.navigator.userAgent);
56
+ var logMethods = ["trace", "debug", "info", "warn", "error"];
57
+
58
+ // Cross-browser bind equivalent that works at least back to IE6
59
+ function bindMethod(obj, methodName) {
60
+ var method = obj[methodName];
61
+ if (typeof method.bind === 'function') {
62
+ return method.bind(obj);
63
+ } else {
64
+ try {
65
+ return Function.prototype.bind.call(method, obj);
66
+ } catch (e) {
67
+ // Missing bind shim or IE8 + Modernizr, fallback to wrapping
68
+ return function () {
69
+ return Function.prototype.apply.apply(method, [obj, arguments]);
70
+ };
71
+ }
72
+ }
73
+ }
74
+
75
+ // Trace() doesn't print the message in IE, so for that case we need to wrap it
76
+ function traceForIE() {
77
+ if (console.log) {
78
+ if (console.log.apply) {
79
+ console.log.apply(console, arguments);
80
+ } else {
81
+ // In old IE, native console methods themselves don't have apply().
82
+ Function.prototype.apply.apply(console.log, [console, arguments]);
83
+ }
84
+ }
85
+ if (console.trace) console.trace();
86
+ }
87
+
88
+ // Build the best logging method possible for this env
89
+ // Wherever possible we want to bind, not wrap, to preserve stack traces
90
+ function realMethod(methodName) {
91
+ if (methodName === 'debug') {
92
+ methodName = 'log';
93
+ }
94
+ if (typeof console === undefinedType) {
95
+ return false; // No method possible, for now - fixed later by enableLoggingWhenConsoleArrives
96
+ } else if (methodName === 'trace' && isIE) {
97
+ return traceForIE;
98
+ } else if (console[methodName] !== undefined) {
99
+ return bindMethod(console, methodName);
100
+ } else if (console.log !== undefined) {
101
+ return bindMethod(console, 'log');
102
+ } else {
103
+ return noop;
104
+ }
105
+ }
106
+
107
+ // These private functions always need `this` to be set properly
108
+
109
+ function replaceLoggingMethods(level, loggerName) {
110
+ /*jshint validthis:true */
111
+ for (var i = 0; i < logMethods.length; i++) {
112
+ var methodName = logMethods[i];
113
+ this[methodName] = i < level ? noop : this.methodFactory(methodName, level, loggerName);
114
+ }
115
+
116
+ // Define log.log as an alias for log.debug
117
+ this.log = this.debug;
118
+ }
119
+
120
+ // In old IE versions, the console isn't present until you first open it.
121
+ // We build realMethod() replacements here that regenerate logging methods
122
+ function enableLoggingWhenConsoleArrives(methodName, level, loggerName) {
123
+ return function () {
124
+ if (typeof console !== undefinedType) {
125
+ replaceLoggingMethods.call(this, level, loggerName);
126
+ this[methodName].apply(this, arguments);
127
+ }
128
+ };
129
+ }
130
+
131
+ // By default, we use closely bound real methods wherever possible, and
132
+ // otherwise we wait for a console to appear, and then try again.
133
+ function defaultMethodFactory(methodName, level, loggerName) {
134
+ /*jshint validthis:true */
135
+ return realMethod(methodName) || enableLoggingWhenConsoleArrives.apply(this, arguments);
136
+ }
137
+ function Logger(name, defaultLevel, factory) {
138
+ var self = this;
139
+ var currentLevel;
140
+ defaultLevel = defaultLevel == null ? "WARN" : defaultLevel;
141
+ var storageKey = "loglevel";
142
+ if (typeof name === "string") {
143
+ storageKey += ":" + name;
144
+ } else if (typeof name === "symbol") {
145
+ storageKey = undefined;
146
+ }
147
+ function persistLevelIfPossible(levelNum) {
148
+ var levelName = (logMethods[levelNum] || 'silent').toUpperCase();
149
+ if (typeof window === undefinedType || !storageKey) return;
150
+
151
+ // Use localStorage if available
152
+ try {
153
+ window.localStorage[storageKey] = levelName;
154
+ return;
155
+ } catch (ignore) {}
156
+
157
+ // Use session cookie as fallback
158
+ try {
159
+ window.document.cookie = encodeURIComponent(storageKey) + "=" + levelName + ";";
160
+ } catch (ignore) {}
161
+ }
162
+ function getPersistedLevel() {
163
+ var storedLevel;
164
+ if (typeof window === undefinedType || !storageKey) return;
165
+ try {
166
+ storedLevel = window.localStorage[storageKey];
167
+ } catch (ignore) {}
168
+
169
+ // Fallback to cookies if local storage gives us nothing
170
+ if (typeof storedLevel === undefinedType) {
171
+ try {
172
+ var cookie = window.document.cookie;
173
+ var location = cookie.indexOf(encodeURIComponent(storageKey) + "=");
174
+ if (location !== -1) {
175
+ storedLevel = /^([^;]+)/.exec(cookie.slice(location))[1];
176
+ }
177
+ } catch (ignore) {}
178
+ }
179
+
180
+ // If the stored level is not valid, treat it as if nothing was stored.
181
+ if (self.levels[storedLevel] === undefined) {
182
+ storedLevel = undefined;
183
+ }
184
+ return storedLevel;
185
+ }
186
+ function clearPersistedLevel() {
187
+ if (typeof window === undefinedType || !storageKey) return;
188
+
189
+ // Use localStorage if available
190
+ try {
191
+ window.localStorage.removeItem(storageKey);
192
+ return;
193
+ } catch (ignore) {}
194
+
195
+ // Use session cookie as fallback
196
+ try {
197
+ window.document.cookie = encodeURIComponent(storageKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC";
198
+ } catch (ignore) {}
199
+ }
200
+
201
+ /*
202
+ *
203
+ * Public logger API - see https://github.com/pimterry/loglevel for details
204
+ *
205
+ */
206
+
207
+ self.name = name;
208
+ self.levels = {
209
+ "TRACE": 0,
210
+ "DEBUG": 1,
211
+ "INFO": 2,
212
+ "WARN": 3,
213
+ "ERROR": 4,
214
+ "SILENT": 5
215
+ };
216
+ self.methodFactory = factory || defaultMethodFactory;
217
+ self.getLevel = function () {
218
+ return currentLevel;
219
+ };
220
+ self.setLevel = function (level, persist) {
221
+ if (typeof level === "string" && self.levels[level.toUpperCase()] !== undefined) {
222
+ level = self.levels[level.toUpperCase()];
223
+ }
224
+ if (typeof level === "number" && level >= 0 && level <= self.levels.SILENT) {
225
+ currentLevel = level;
226
+ if (persist !== false) {
227
+ // defaults to true
228
+ persistLevelIfPossible(level);
229
+ }
230
+ replaceLoggingMethods.call(self, level, name);
231
+ if (typeof console === undefinedType && level < self.levels.SILENT) {
232
+ return "No console available for logging";
233
+ }
234
+ } else {
235
+ throw "log.setLevel() called with invalid level: " + level;
236
+ }
237
+ };
238
+ self.setDefaultLevel = function (level) {
239
+ defaultLevel = level;
240
+ if (!getPersistedLevel()) {
241
+ self.setLevel(level, false);
242
+ }
243
+ };
244
+ self.resetLevel = function () {
245
+ self.setLevel(defaultLevel, false);
246
+ clearPersistedLevel();
247
+ };
248
+ self.enableAll = function (persist) {
249
+ self.setLevel(self.levels.TRACE, persist);
250
+ };
251
+ self.disableAll = function (persist) {
252
+ self.setLevel(self.levels.SILENT, persist);
253
+ };
254
+
255
+ // Initialize with the right level
256
+ var initialLevel = getPersistedLevel();
257
+ if (initialLevel == null) {
258
+ initialLevel = defaultLevel;
259
+ }
260
+ self.setLevel(initialLevel, false);
261
+ }
262
+
263
+ /*
264
+ *
265
+ * Top-level API
266
+ *
267
+ */
268
+
269
+ var defaultLogger = new Logger();
270
+ var _loggersByName = {};
271
+ defaultLogger.getLogger = function getLogger(name) {
272
+ if (typeof name !== "symbol" && typeof name !== "string" || name === "") {
273
+ throw new TypeError("You must supply a name when creating a logger.");
274
+ }
275
+ var logger = _loggersByName[name];
276
+ if (!logger) {
277
+ logger = _loggersByName[name] = new Logger(name, defaultLogger.getLevel(), defaultLogger.methodFactory);
278
+ }
279
+ return logger;
280
+ };
281
+
282
+ // Grab the current global log variable in case of overwrite
283
+ var _log = typeof window !== undefinedType ? window.log : undefined;
284
+ defaultLogger.noConflict = function () {
285
+ if (typeof window !== undefinedType && window.log === defaultLogger) {
286
+ window.log = _log;
287
+ }
288
+ return defaultLogger;
289
+ };
290
+ defaultLogger.getLoggers = function getLoggers() {
291
+ return _loggersByName;
292
+ };
293
+
294
+ // ES6 default export, for compatibility
295
+ defaultLogger['default'] = defaultLogger;
296
+ return defaultLogger;
297
+ });
298
+ })(loglevel);
299
+ var loglevelExports = loglevel.exports;
300
+
301
+ var LogLevel;
302
+ (function (LogLevel) {
303
+ LogLevel[LogLevel["trace"] = 0] = "trace";
304
+ LogLevel[LogLevel["debug"] = 1] = "debug";
305
+ LogLevel[LogLevel["info"] = 2] = "info";
306
+ LogLevel[LogLevel["warn"] = 3] = "warn";
307
+ LogLevel[LogLevel["error"] = 4] = "error";
308
+ LogLevel[LogLevel["silent"] = 5] = "silent";
309
+ })(LogLevel || (LogLevel = {}));
310
+ const livekitLogger = loglevelExports.getLogger('livekit');
311
+ livekitLogger.setDefaultLevel(LogLevel.info);
312
+
313
+ const workerLogger = loglevelExports.getLogger('lk-e2ee');
314
+
315
+ const ENCRYPTION_ALGORITHM = 'AES-GCM';
316
+ // We use a ringbuffer of keys so we can change them and still decode packets that were
317
+ // encrypted with an old key. We use a size of 16 which corresponds to the four bits
318
+ // in the frame trailer.
319
+ const KEYRING_SIZE = 16;
320
+ // We copy the first bytes of the VP8 payload unencrypted.
321
+ // For keyframes this is 10 bytes, for non-keyframes (delta) 3. See
322
+ // https://tools.ietf.org/html/rfc6386#section-9.1
323
+ // This allows the bridge to continue detecting keyframes (only one byte needed in the JVB)
324
+ // and is also a bit easier for the VP8 decoder (i.e. it generates funny garbage pictures
325
+ // instead of being unable to decode).
326
+ // This is a bit for show and we might want to reduce to 1 unconditionally in the final version.
327
+ //
328
+ // For audio (where frame.type is not set) we do not encrypt the opus TOC byte:
329
+ // https://tools.ietf.org/html/rfc6716#section-3.1
330
+ const UNENCRYPTED_BYTES = {
331
+ key: 10,
332
+ delta: 3,
333
+ audio: 1,
334
+ empty: 0
335
+ };
336
+ /* We use a 12 byte bit IV. This is signalled in plain together with the
337
+ packet. See https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt#parameters */
338
+ const IV_LENGTH = 12;
339
+ const SALT = 'LKFrameEncryptionKey';
340
+ const KEY_PROVIDER_DEFAULTS = {
341
+ sharedKey: false,
342
+ ratchetSalt: SALT,
343
+ ratchetWindowSize: 8
344
+ };
345
+
346
+ class LivekitError extends Error {
347
+ constructor(code, message) {
348
+ super(message || 'an error has occured');
349
+ this.code = code;
350
+ }
351
+ }
352
+ var MediaDeviceFailure;
353
+ (function (MediaDeviceFailure) {
354
+ // user rejected permissions
355
+ MediaDeviceFailure["PermissionDenied"] = "PermissionDenied";
356
+ // device is not available
357
+ MediaDeviceFailure["NotFound"] = "NotFound";
358
+ // device is in use. On Windows, only a single tab may get access to a device at a time.
359
+ MediaDeviceFailure["DeviceInUse"] = "DeviceInUse";
360
+ MediaDeviceFailure["Other"] = "Other";
361
+ })(MediaDeviceFailure || (MediaDeviceFailure = {}));
362
+ (function (MediaDeviceFailure) {
363
+ function getFailure(error) {
364
+ if (error && 'name' in error) {
365
+ if (error.name === 'NotFoundError' || error.name === 'DevicesNotFoundError') {
366
+ return MediaDeviceFailure.NotFound;
367
+ }
368
+ if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') {
369
+ return MediaDeviceFailure.PermissionDenied;
370
+ }
371
+ if (error.name === 'NotReadableError' || error.name === 'TrackStartError') {
372
+ return MediaDeviceFailure.DeviceInUse;
373
+ }
374
+ return MediaDeviceFailure.Other;
375
+ }
376
+ }
377
+ MediaDeviceFailure.getFailure = getFailure;
378
+ })(MediaDeviceFailure || (MediaDeviceFailure = {}));
379
+
380
+ var CryptorErrorReason;
381
+ (function (CryptorErrorReason) {
382
+ CryptorErrorReason[CryptorErrorReason["InvalidKey"] = 0] = "InvalidKey";
383
+ CryptorErrorReason[CryptorErrorReason["MissingKey"] = 1] = "MissingKey";
384
+ CryptorErrorReason[CryptorErrorReason["InternalError"] = 2] = "InternalError";
385
+ })(CryptorErrorReason || (CryptorErrorReason = {}));
386
+ class CryptorError extends LivekitError {
387
+ constructor(message) {
388
+ let reason = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : CryptorErrorReason.InternalError;
389
+ super(40, message);
390
+ this.reason = reason;
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.sendCounts = new Map();
822
+ this.keys = opts.keys;
823
+ this.participantId = opts.participantId;
824
+ this.rtpMap = new Map();
825
+ this.keyProviderOptions = opts.keyProviderOptions;
826
+ this.unencryptedFrameByteTrailer = (_a = opts.unencryptedFrameBytes) !== null && _a !== void 0 ? _a : new TextEncoder().encode('LKROCKS');
827
+ }
828
+ /**
829
+ * Assign a different participant to the cryptor.
830
+ * useful for transceiver re-use
831
+ * @param id
832
+ * @param keys
833
+ */
834
+ setParticipant(id, keys) {
835
+ this.participantId = id;
836
+ this.keys = keys;
837
+ }
838
+ unsetParticipant() {
839
+ this.participantId = undefined;
840
+ }
841
+ getParticipantId() {
842
+ return this.participantId;
843
+ }
844
+ getTrackId() {
845
+ return this.trackId;
846
+ }
847
+ /**
848
+ * Update the video codec used by the mediaStreamTrack
849
+ * @param codec
850
+ */
851
+ setVideoCodec(codec) {
852
+ this.videoCodec = codec;
853
+ }
854
+ /**
855
+ * rtp payload type map used for figuring out codec of payload type when encoding
856
+ * @param map
857
+ */
858
+ setRtpMap(map) {
859
+ this.rtpMap = map;
860
+ }
861
+ setupTransform(operation, readable, writable, trackId, codec) {
862
+ if (codec) {
863
+ console.info('setting codec on cryptor to', codec);
864
+ this.videoCodec = codec;
865
+ }
866
+ const transformFn = operation === 'encode' ? this.encodeFunction : this.decodeFunction;
867
+ const transformStream = new TransformStream({
868
+ transform: transformFn.bind(this)
869
+ });
870
+ readable.pipeThrough(transformStream).pipeTo(writable).catch(e => {
871
+ console.error(e);
872
+ this.emit('cryptorError', e instanceof CryptorError ? e : new CryptorError(e.message));
873
+ });
874
+ this.trackId = trackId;
875
+ }
876
+ /**
877
+ * Function that will be injected in a stream and will encrypt the given encoded frames.
878
+ *
879
+ * @param {RTCEncodedVideoFrame|RTCEncodedAudioFrame} encodedFrame - Encoded video frame.
880
+ * @param {TransformStreamDefaultController} controller - TransportStreamController.
881
+ *
882
+ * The VP8 payload descriptor described in
883
+ * https://tools.ietf.org/html/rfc7741#section-4.2
884
+ * is part of the RTP packet and not part of the frame and is not controllable by us.
885
+ * This is fine as the SFU keeps having access to it for routing.
886
+ *
887
+ * The encrypted frame is formed as follows:
888
+ * 1) Find unencrypted byte length, depending on the codec, frame type and kind.
889
+ * 2) Form the GCM IV for the frame as described above.
890
+ * 3) Encrypt the rest of the frame using AES-GCM.
891
+ * 4) Allocate space for the encrypted frame.
892
+ * 5) Copy the unencrypted bytes to the start of the encrypted frame.
893
+ * 6) Append the ciphertext to the encrypted frame.
894
+ * 7) Append the IV.
895
+ * 8) Append a single byte for the key identifier.
896
+ * 9) Enqueue the encrypted frame for sending.
897
+ */
898
+ encodeFunction(encodedFrame, controller) {
899
+ var _a;
900
+ return __awaiter(this, void 0, void 0, function* () {
901
+ if (!this.keys.isEnabled() ||
902
+ // skip for encryption for empty dtx frames
903
+ encodedFrame.data.byteLength === 0) {
904
+ return controller.enqueue(encodedFrame);
905
+ }
906
+ const {
907
+ encryptionKey
908
+ } = this.keys.getKeySet();
909
+ const keyIndex = this.keys.getCurrentKeyIndex();
910
+ if (encryptionKey) {
911
+ const iv = this.makeIV((_a = encodedFrame.getMetadata().synchronizationSource) !== null && _a !== void 0 ? _a : -1, encodedFrame.timestamp);
912
+ // Thіs is not encrypted and contains the VP8 payload descriptor or the Opus TOC byte.
913
+ const frameHeader = new Uint8Array(encodedFrame.data, 0, this.getUnencryptedBytes(encodedFrame));
914
+ // Frame trailer contains the R|IV_LENGTH and key index
915
+ const frameTrailer = new Uint8Array(2);
916
+ frameTrailer[0] = IV_LENGTH;
917
+ frameTrailer[1] = keyIndex;
918
+ // Construct frame trailer. Similar to the frame header described in
919
+ // https://tools.ietf.org/html/draft-omara-sframe-00#section-4.2
920
+ // but we put it at the end.
921
+ //
922
+ // ---------+-------------------------+-+---------+----
923
+ // payload |IV...(length = IV_LENGTH)|R|IV_LENGTH|KID |
924
+ // ---------+-------------------------+-+---------+----
925
+ try {
926
+ const cipherText = yield crypto.subtle.encrypt({
927
+ name: ENCRYPTION_ALGORITHM,
928
+ iv,
929
+ additionalData: new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength)
930
+ }, encryptionKey, new Uint8Array(encodedFrame.data, this.getUnencryptedBytes(encodedFrame)));
931
+ const newData = new ArrayBuffer(frameHeader.byteLength + cipherText.byteLength + iv.byteLength + frameTrailer.byteLength);
932
+ const newUint8 = new Uint8Array(newData);
933
+ newUint8.set(frameHeader); // copy first bytes.
934
+ newUint8.set(new Uint8Array(cipherText), frameHeader.byteLength); // add ciphertext.
935
+ newUint8.set(new Uint8Array(iv), frameHeader.byteLength + cipherText.byteLength); // append IV.
936
+ newUint8.set(frameTrailer, frameHeader.byteLength + cipherText.byteLength + iv.byteLength); // append frame trailer.
937
+ encodedFrame.data = newData;
938
+ return controller.enqueue(encodedFrame);
939
+ } catch (e) {
940
+ // TODO: surface this to the app.
941
+ workerLogger.error(e);
942
+ }
943
+ } else {
944
+ this.emit(CryptorEvent.Error, new CryptorError("encryption key missing for encoding", CryptorErrorReason.MissingKey));
945
+ }
946
+ });
947
+ }
948
+ /**
949
+ * Function that will be injected in a stream and will decrypt the given encoded frames.
950
+ *
951
+ * @param {RTCEncodedVideoFrame|RTCEncodedAudioFrame} encodedFrame - Encoded video frame.
952
+ * @param {TransformStreamDefaultController} controller - TransportStreamController.
953
+ */
954
+ decodeFunction(encodedFrame, controller) {
955
+ return __awaiter(this, void 0, void 0, function* () {
956
+ if (!this.keys.isEnabled() ||
957
+ // skip for decryption for empty dtx frames
958
+ encodedFrame.data.byteLength === 0 ||
959
+ // skip decryption if frame is server injected
960
+ isFrameServerInjected(encodedFrame.data, this.unencryptedFrameByteTrailer)) {
961
+ return controller.enqueue(encodedFrame);
962
+ }
963
+ const data = new Uint8Array(encodedFrame.data);
964
+ const keyIndex = data[encodedFrame.data.byteLength - 1];
965
+ if (this.keys.getKeySet(keyIndex) && this.keys.hasValidKey) {
966
+ try {
967
+ const decodedFrame = yield this.decryptFrame(encodedFrame, keyIndex);
968
+ if (decodedFrame) {
969
+ return controller.enqueue(decodedFrame);
970
+ }
971
+ } catch (error) {
972
+ if (error instanceof CryptorError && error.reason === CryptorErrorReason.InvalidKey) {
973
+ if (this.keys.hasValidKey) {
974
+ workerLogger.warn('invalid key');
975
+ this.emit(CryptorEvent.Error, new CryptorError("invalid key for participant ".concat(this.participantId), CryptorErrorReason.InvalidKey));
976
+ this.keys.hasValidKey = false;
977
+ }
978
+ } else {
979
+ workerLogger.warn('decoding frame failed', {
980
+ error
981
+ });
982
+ }
983
+ }
984
+ }
985
+ return controller.enqueue(encodedFrame);
986
+ });
987
+ }
988
+ /**
989
+ * Function that will decrypt the given encoded frame. If the decryption fails, it will
990
+ * ratchet the key for up to RATCHET_WINDOW_SIZE times.
991
+ */
992
+ decryptFrame(encodedFrame, keyIndex) {
993
+ let initialMaterial = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : undefined;
994
+ let ratchetOpts = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {
995
+ ratchetCount: 0
996
+ };
997
+ var _a;
998
+ return __awaiter(this, void 0, void 0, function* () {
999
+ const keySet = this.keys.getKeySet(keyIndex);
1000
+ // Construct frame trailer. Similar to the frame header described in
1001
+ // https://tools.ietf.org/html/draft-omara-sframe-00#section-4.2
1002
+ // but we put it at the end.
1003
+ //
1004
+ // ---------+-------------------------+-+---------+----
1005
+ // payload |IV...(length = IV_LENGTH)|R|IV_LENGTH|KID |
1006
+ // ---------+-------------------------+-+---------+----
1007
+ try {
1008
+ const frameHeader = new Uint8Array(encodedFrame.data, 0, this.getUnencryptedBytes(encodedFrame));
1009
+ const frameTrailer = new Uint8Array(encodedFrame.data, encodedFrame.data.byteLength - 2, 2);
1010
+ const ivLength = frameTrailer[0];
1011
+ const iv = new Uint8Array(encodedFrame.data, encodedFrame.data.byteLength - ivLength - frameTrailer.byteLength, ivLength);
1012
+ const cipherTextStart = frameHeader.byteLength;
1013
+ const cipherTextLength = encodedFrame.data.byteLength - (frameHeader.byteLength + ivLength + frameTrailer.byteLength);
1014
+ const plainText = yield crypto.subtle.decrypt({
1015
+ name: ENCRYPTION_ALGORITHM,
1016
+ iv,
1017
+ additionalData: new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength)
1018
+ }, (_a = ratchetOpts.encryptionKey) !== null && _a !== void 0 ? _a : keySet.encryptionKey, new Uint8Array(encodedFrame.data, cipherTextStart, cipherTextLength));
1019
+ const newData = new ArrayBuffer(frameHeader.byteLength + plainText.byteLength);
1020
+ const newUint8 = new Uint8Array(newData);
1021
+ newUint8.set(new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength));
1022
+ newUint8.set(new Uint8Array(plainText), frameHeader.byteLength);
1023
+ encodedFrame.data = newData;
1024
+ return encodedFrame;
1025
+ } catch (error) {
1026
+ if (this.keyProviderOptions.ratchetWindowSize > 0) {
1027
+ if (ratchetOpts.ratchetCount < this.keyProviderOptions.ratchetWindowSize) {
1028
+ workerLogger.debug("ratcheting key attempt ".concat(ratchetOpts.ratchetCount, " of ").concat(this.keyProviderOptions.ratchetWindowSize, ", for kind ").concat(encodedFrame instanceof RTCEncodedAudioFrame ? 'audio' : 'video'));
1029
+ let ratchetedKeySet;
1030
+ if (keySet === this.keys.getKeySet(keyIndex)) {
1031
+ // only ratchet if the currently set key is still the same as the one used to decrypt this frame
1032
+ // if not, it might be that a different frame has already ratcheted and we try with that one first
1033
+ const newMaterial = yield this.keys.ratchetKey(keyIndex, false);
1034
+ ratchetedKeySet = yield deriveKeys(newMaterial, this.keyProviderOptions.ratchetSalt);
1035
+ }
1036
+ const frame = yield this.decryptFrame(encodedFrame, keyIndex, initialMaterial || keySet, {
1037
+ ratchetCount: ratchetOpts.ratchetCount + 1,
1038
+ encryptionKey: ratchetedKeySet === null || ratchetedKeySet === void 0 ? void 0 : ratchetedKeySet.encryptionKey
1039
+ });
1040
+ if (frame && ratchetedKeySet) {
1041
+ this.keys.setKeySet(ratchetedKeySet, keyIndex, true);
1042
+ // decryption was successful, set the new key index to reflect the ratcheted key set
1043
+ this.keys.setCurrentKeyIndex(keyIndex);
1044
+ }
1045
+ return frame;
1046
+ } else {
1047
+ /**
1048
+ * Since the key it is first send and only afterwards actually used for encrypting, there were
1049
+ * situations when the decrypting failed due to the fact that the received frame was not encrypted
1050
+ * yet and ratcheting, of course, did not solve the problem. So if we fail RATCHET_WINDOW_SIZE times,
1051
+ * we come back to the initial key.
1052
+ */
1053
+ if (initialMaterial) {
1054
+ workerLogger.debug('resetting to initial material');
1055
+ this.keys.setKeyFromMaterial(initialMaterial.material, keyIndex);
1056
+ }
1057
+ this.keys.hasValidKey = false;
1058
+ workerLogger.warn('maximum ratchet attempts exceeded, resetting key');
1059
+ this.emit(CryptorEvent.Error, new CryptorError("valid key missing for participant ".concat(this.participantId), CryptorErrorReason.MissingKey));
1060
+ }
1061
+ } else {
1062
+ throw new CryptorError('Decryption failed, most likely because of an invalid key', CryptorErrorReason.InvalidKey);
1063
+ }
1064
+ }
1065
+ });
1066
+ }
1067
+ /**
1068
+ * Construct the IV used for AES-GCM and sent (in plain) with the packet similar to
1069
+ * https://tools.ietf.org/html/rfc7714#section-8.1
1070
+ * It concatenates
1071
+ * - the 32 bit synchronization source (SSRC) given on the encoded frame,
1072
+ * - the 32 bit rtp timestamp given on the encoded frame,
1073
+ * - a send counter that is specific to the SSRC. Starts at a random number.
1074
+ * The send counter is essentially the pictureId but we currently have to implement this ourselves.
1075
+ * There is no XOR with a salt. Note that this IV leaks the SSRC to the receiver but since this is
1076
+ * randomly generated and SFUs may not rewrite this is considered acceptable.
1077
+ * The SSRC is used to allow demultiplexing multiple streams with the same key, as described in
1078
+ * https://tools.ietf.org/html/rfc3711#section-4.1.1
1079
+ * The RTP timestamp is 32 bits and advances by the codec clock rate (90khz for video, 48khz for
1080
+ * opus audio) every second. For video it rolls over roughly every 13 hours.
1081
+ * The send counter will advance at the frame rate (30fps for video, 50fps for 20ms opus audio)
1082
+ * every second. It will take a long time to roll over.
1083
+ *
1084
+ * See also https://developer.mozilla.org/en-US/docs/Web/API/AesGcmParams
1085
+ */
1086
+ makeIV(synchronizationSource, timestamp) {
1087
+ var _a;
1088
+ const iv = new ArrayBuffer(IV_LENGTH);
1089
+ const ivView = new DataView(iv);
1090
+ // having to keep our own send count (similar to a picture id) is not ideal.
1091
+ if (!this.sendCounts.has(synchronizationSource)) {
1092
+ // Initialize with a random offset, similar to the RTP sequence number.
1093
+ this.sendCounts.set(synchronizationSource, Math.floor(Math.random() * 0xffff));
1094
+ }
1095
+ const sendCount = (_a = this.sendCounts.get(synchronizationSource)) !== null && _a !== void 0 ? _a : 0;
1096
+ ivView.setUint32(0, synchronizationSource);
1097
+ ivView.setUint32(4, timestamp);
1098
+ ivView.setUint32(8, timestamp - sendCount % 0xffff);
1099
+ this.sendCounts.set(synchronizationSource, sendCount + 1);
1100
+ return iv;
1101
+ }
1102
+ getUnencryptedBytes(frame) {
1103
+ var _a;
1104
+ if (isVideoFrame(frame)) {
1105
+ let detectedCodec = (_a = this.getVideoCodec(frame)) !== null && _a !== void 0 ? _a : this.videoCodec;
1106
+ if (detectedCodec === 'av1' || detectedCodec === 'vp9') {
1107
+ throw new Error("".concat(detectedCodec, " is not yet supported for end to end encryption"));
1108
+ }
1109
+ if (detectedCodec === 'vp8') {
1110
+ return UNENCRYPTED_BYTES[frame.type];
1111
+ }
1112
+ const data = new Uint8Array(frame.data);
1113
+ try {
1114
+ const naluIndices = findNALUIndices(data);
1115
+ // if the detected codec is undefined we test whether it _looks_ like a h264 frame as a best guess
1116
+ const isH264 = detectedCodec === 'h264' || naluIndices.some(naluIndex => [NALUType.SLICE_IDR, NALUType.SLICE_NON_IDR].includes(parseNALUType(data[naluIndex])));
1117
+ if (isH264) {
1118
+ for (const index of naluIndices) {
1119
+ let type = parseNALUType(data[index]);
1120
+ switch (type) {
1121
+ case NALUType.SLICE_IDR:
1122
+ case NALUType.SLICE_NON_IDR:
1123
+ return index + 2;
1124
+ default:
1125
+ break;
1126
+ }
1127
+ }
1128
+ throw new TypeError('Could not find NALU');
1129
+ }
1130
+ } catch (e) {
1131
+ // no op, we just continue and fallback to vp8
1132
+ }
1133
+ return UNENCRYPTED_BYTES[frame.type];
1134
+ } else {
1135
+ return UNENCRYPTED_BYTES.audio;
1136
+ }
1137
+ }
1138
+ /**
1139
+ * inspects frame payloadtype if available and maps it to the codec specified in rtpMap
1140
+ */
1141
+ getVideoCodec(frame) {
1142
+ if (this.rtpMap.size === 0) {
1143
+ return undefined;
1144
+ }
1145
+ // @ts-expect-error payloadType is not yet part of the typescript definition and currently not supported in Safari
1146
+ const payloadType = frame.getMetadata().payloadType;
1147
+ const codec = payloadType ? this.rtpMap.get(payloadType) : undefined;
1148
+ return codec;
1149
+ }
1150
+ }
1151
+ /**
1152
+ * Slice the NALUs present in the supplied buffer, assuming it is already byte-aligned
1153
+ * code adapted from https://github.com/medooze/h264-frame-parser/blob/main/lib/NalUnits.ts to return indices only
1154
+ */
1155
+ function findNALUIndices(stream) {
1156
+ const result = [];
1157
+ let start = 0,
1158
+ pos = 0,
1159
+ searchLength = stream.length - 2;
1160
+ while (pos < searchLength) {
1161
+ // skip until end of current NALU
1162
+ while (pos < searchLength && !(stream[pos] === 0 && stream[pos + 1] === 0 && stream[pos + 2] === 1)) pos++;
1163
+ if (pos >= searchLength) pos = stream.length;
1164
+ // remove trailing zeros from current NALU
1165
+ let end = pos;
1166
+ while (end > start && stream[end - 1] === 0) end--;
1167
+ // save current NALU
1168
+ if (start === 0) {
1169
+ if (end !== start) throw TypeError('byte stream contains leading data');
1170
+ } else {
1171
+ result.push(start);
1172
+ }
1173
+ // begin new NALU
1174
+ start = pos = pos + 3;
1175
+ }
1176
+ return result;
1177
+ }
1178
+ function parseNALUType(startByte) {
1179
+ return startByte & kNaluTypeMask;
1180
+ }
1181
+ const kNaluTypeMask = 0x1f;
1182
+ var NALUType;
1183
+ (function (NALUType) {
1184
+ /** Coded slice of a non-IDR picture */
1185
+ NALUType[NALUType["SLICE_NON_IDR"] = 1] = "SLICE_NON_IDR";
1186
+ /** Coded slice data partition A */
1187
+ NALUType[NALUType["SLICE_PARTITION_A"] = 2] = "SLICE_PARTITION_A";
1188
+ /** Coded slice data partition B */
1189
+ NALUType[NALUType["SLICE_PARTITION_B"] = 3] = "SLICE_PARTITION_B";
1190
+ /** Coded slice data partition C */
1191
+ NALUType[NALUType["SLICE_PARTITION_C"] = 4] = "SLICE_PARTITION_C";
1192
+ /** Coded slice of an IDR picture */
1193
+ NALUType[NALUType["SLICE_IDR"] = 5] = "SLICE_IDR";
1194
+ /** Supplemental enhancement information */
1195
+ NALUType[NALUType["SEI"] = 6] = "SEI";
1196
+ /** Sequence parameter set */
1197
+ NALUType[NALUType["SPS"] = 7] = "SPS";
1198
+ /** Picture parameter set */
1199
+ NALUType[NALUType["PPS"] = 8] = "PPS";
1200
+ /** Access unit delimiter */
1201
+ NALUType[NALUType["AUD"] = 9] = "AUD";
1202
+ /** End of sequence */
1203
+ NALUType[NALUType["END_SEQ"] = 10] = "END_SEQ";
1204
+ /** End of stream */
1205
+ NALUType[NALUType["END_STREAM"] = 11] = "END_STREAM";
1206
+ /** Filler data */
1207
+ NALUType[NALUType["FILLER_DATA"] = 12] = "FILLER_DATA";
1208
+ /** Sequence parameter set extension */
1209
+ NALUType[NALUType["SPS_EXT"] = 13] = "SPS_EXT";
1210
+ /** Prefix NAL unit */
1211
+ NALUType[NALUType["PREFIX_NALU"] = 14] = "PREFIX_NALU";
1212
+ /** Subset sequence parameter set */
1213
+ NALUType[NALUType["SUBSET_SPS"] = 15] = "SUBSET_SPS";
1214
+ /** Depth parameter set */
1215
+ NALUType[NALUType["DPS"] = 16] = "DPS";
1216
+ // 17, 18 reserved
1217
+ /** Coded slice of an auxiliary coded picture without partitioning */
1218
+ NALUType[NALUType["SLICE_AUX"] = 19] = "SLICE_AUX";
1219
+ /** Coded slice extension */
1220
+ NALUType[NALUType["SLICE_EXT"] = 20] = "SLICE_EXT";
1221
+ /** Coded slice extension for a depth view component or a 3D-AVC texture view component */
1222
+ NALUType[NALUType["SLICE_LAYER_EXT"] = 21] = "SLICE_LAYER_EXT";
1223
+ // 22, 23 reserved
1224
+ })(NALUType || (NALUType = {}));
1225
+ /**
1226
+ * we use a magic frame trailer to detect whether a frame is injected
1227
+ * by the livekit server and thus to be treated as unencrypted
1228
+ * @internal
1229
+ */
1230
+ function isFrameServerInjected(frameData, trailerBytes) {
1231
+ const frameTrailer = new Uint8Array(frameData.slice(frameData.byteLength - trailerBytes.byteLength));
1232
+ return trailerBytes.every((value, index) => value === frameTrailer[index]);
1233
+ }
1234
+
1235
+ // TODO ParticipantKeyHandlers currently don't get destroyed on participant disconnect
1236
+ // we could do this by having a separate worker message on participant disconnected.
1237
+ /**
1238
+ * ParticipantKeyHandler is responsible for providing a cryptor instance with the
1239
+ * en-/decryption key of a participant. It assumes that all tracks of a specific participant
1240
+ * are encrypted with the same key.
1241
+ * Additionally it exposes a method to ratchet a key which can be used by the cryptor either automatically
1242
+ * if decryption fails or can be triggered manually on both sender and receiver side.
1243
+ *
1244
+ */
1245
+ class ParticipantKeyHandler extends EventEmitter {
1246
+ constructor(participantId, isEnabled, keyProviderOptions) {
1247
+ super();
1248
+ this.currentKeyIndex = 0;
1249
+ this.cryptoKeyRing = new Array(KEYRING_SIZE);
1250
+ this.enabled = isEnabled;
1251
+ this.keyProviderOptions = keyProviderOptions;
1252
+ this.ratchetPromiseMap = new Map();
1253
+ this.participantId = participantId;
1254
+ this.hasValidKey = false;
1255
+ }
1256
+ setEnabled(enabled) {
1257
+ this.enabled = enabled;
1258
+ }
1259
+ /**
1260
+ * Ratchets the current key (or the one at keyIndex if provided) and
1261
+ * returns the ratcheted material
1262
+ * if `setKey` is true (default), it will also set the ratcheted key directly on the crypto key ring
1263
+ * @param keyIndex
1264
+ * @param setKey
1265
+ */
1266
+ ratchetKey(keyIndex) {
1267
+ let setKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
1268
+ const currentKeyIndex = keyIndex !== null && keyIndex !== void 0 ? keyIndex : keyIndex = this.getCurrentKeyIndex();
1269
+ const existingPromise = this.ratchetPromiseMap.get(currentKeyIndex);
1270
+ if (typeof existingPromise !== 'undefined') {
1271
+ return existingPromise;
1272
+ }
1273
+ const ratchetPromise = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
1274
+ try {
1275
+ const currentMaterial = this.getKeySet(currentKeyIndex).material;
1276
+ const newMaterial = yield importKey(yield ratchet(currentMaterial, this.keyProviderOptions.ratchetSalt), currentMaterial.algorithm.name, 'derive');
1277
+ if (setKey) {
1278
+ this.setKeyFromMaterial(newMaterial, currentKeyIndex, true);
1279
+ }
1280
+ this.emit('keyRatcheted', newMaterial, keyIndex, this.participantId);
1281
+ resolve(newMaterial);
1282
+ } catch (e) {
1283
+ reject(e);
1284
+ } finally {
1285
+ this.ratchetPromiseMap.delete(currentKeyIndex);
1286
+ }
1287
+ }));
1288
+ this.ratchetPromiseMap.set(currentKeyIndex, ratchetPromise);
1289
+ return ratchetPromise;
1290
+ }
1291
+ /**
1292
+ * takes in a key material with `deriveBits` and `deriveKey` set as key usages
1293
+ * and derives encryption keys from the material and sets it on the key ring buffer
1294
+ * together with the material
1295
+ * also resets the valid key property and updates the currentKeyIndex
1296
+ */
1297
+ setKey(material) {
1298
+ let keyIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
1299
+ return __awaiter(this, void 0, void 0, function* () {
1300
+ yield this.setKeyFromMaterial(material, keyIndex);
1301
+ this.hasValidKey = true;
1302
+ });
1303
+ }
1304
+ /**
1305
+ * takes in a key material with `deriveBits` and `deriveKey` set as key usages
1306
+ * and derives encryption keys from the material and sets it on the key ring buffer
1307
+ * together with the material
1308
+ * also updates the currentKeyIndex
1309
+ */
1310
+ setKeyFromMaterial(material) {
1311
+ let keyIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
1312
+ let emitRatchetEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
1313
+ return __awaiter(this, void 0, void 0, function* () {
1314
+ workerLogger.debug('setting new key');
1315
+ if (keyIndex >= 0) {
1316
+ this.currentKeyIndex = keyIndex % this.cryptoKeyRing.length;
1317
+ }
1318
+ const keySet = yield deriveKeys(material, this.keyProviderOptions.ratchetSalt);
1319
+ this.setKeySet(keySet, this.currentKeyIndex, emitRatchetEvent);
1320
+ });
1321
+ }
1322
+ setKeySet(keySet, keyIndex) {
1323
+ let emitRatchetEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
1324
+ return __awaiter(this, void 0, void 0, function* () {
1325
+ this.cryptoKeyRing[keyIndex % this.cryptoKeyRing.length] = keySet;
1326
+ if (emitRatchetEvent) {
1327
+ this.emit('keyRatcheted', keySet.material, keyIndex, this.participantId);
1328
+ }
1329
+ });
1330
+ }
1331
+ setCurrentKeyIndex(index) {
1332
+ return __awaiter(this, void 0, void 0, function* () {
1333
+ this.currentKeyIndex = index % this.cryptoKeyRing.length;
1334
+ this.hasValidKey = true;
1335
+ });
1336
+ }
1337
+ isEnabled() {
1338
+ return this.enabled;
1339
+ }
1340
+ getCurrentKeyIndex() {
1341
+ return this.currentKeyIndex;
1342
+ }
1343
+ /**
1344
+ * returns currently used KeySet or the one at `keyIndex` if provided
1345
+ * @param keyIndex
1346
+ * @returns
1347
+ */
1348
+ getKeySet(keyIndex) {
1349
+ return this.cryptoKeyRing[keyIndex !== null && keyIndex !== void 0 ? keyIndex : this.currentKeyIndex];
1350
+ }
1351
+ }
1352
+
1353
+ const participantCryptors = [];
1354
+ const participantKeys = new Map();
1355
+ let publishCryptors = [];
1356
+ let publisherKeys;
1357
+ let isEncryptionEnabled = false;
1358
+ let useSharedKey = false;
1359
+ let sharedKey;
1360
+ let keyProviderOptions = KEY_PROVIDER_DEFAULTS;
1361
+ workerLogger.setDefaultLevel('info');
1362
+ onmessage = ev => {
1363
+ const {
1364
+ kind,
1365
+ data
1366
+ } = ev.data;
1367
+ switch (kind) {
1368
+ case 'init':
1369
+ workerLogger.info('worker initialized');
1370
+ keyProviderOptions = data.keyProviderOptions;
1371
+ useSharedKey = !!data.keyProviderOptions.sharedKey;
1372
+ // acknowledge init successful
1373
+ const enableMsg = {
1374
+ kind: 'enable',
1375
+ data: {
1376
+ enabled: isEncryptionEnabled
1377
+ }
1378
+ };
1379
+ publisherKeys = new ParticipantKeyHandler(undefined, isEncryptionEnabled, keyProviderOptions);
1380
+ publisherKeys.on('keyRatcheted', emitRatchetedKeys);
1381
+ postMessage(enableMsg);
1382
+ break;
1383
+ case 'enable':
1384
+ setEncryptionEnabled(data.enabled, data.participantId);
1385
+ workerLogger.info('updated e2ee enabled status');
1386
+ // acknowledge enable call successful
1387
+ postMessage(ev.data);
1388
+ break;
1389
+ case 'decode':
1390
+ let cryptor = getTrackCryptor(data.participantId, data.trackId);
1391
+ cryptor.setupTransform(kind, data.readableStream, data.writableStream, data.trackId, data.codec);
1392
+ break;
1393
+ case 'encode':
1394
+ let pubCryptor = getPublisherCryptor(data.trackId);
1395
+ pubCryptor.setupTransform(kind, data.readableStream, data.writableStream, data.trackId, data.codec);
1396
+ break;
1397
+ case 'setKey':
1398
+ if (useSharedKey) {
1399
+ workerLogger.debug('set shared key');
1400
+ setSharedKey(data.key, data.keyIndex);
1401
+ } else if (data.participantId) {
1402
+ getParticipantKeyHandler(data.participantId).setKey(data.key, data.keyIndex);
1403
+ } else {
1404
+ workerLogger.error('no participant Id was provided and shared key usage is disabled');
1405
+ }
1406
+ break;
1407
+ case 'removeTransform':
1408
+ unsetCryptorParticipant(data.trackId);
1409
+ break;
1410
+ case 'updateCodec':
1411
+ getTrackCryptor(data.participantId, data.trackId).setVideoCodec(data.codec);
1412
+ break;
1413
+ case 'setRTPMap':
1414
+ publishCryptors.forEach(cr => {
1415
+ cr.setRtpMap(data.map);
1416
+ });
1417
+ break;
1418
+ case 'ratchetRequest':
1419
+ handleRatchetRequest(data);
1420
+ }
1421
+ };
1422
+ function handleRatchetRequest(data) {
1423
+ return __awaiter(this, void 0, void 0, function* () {
1424
+ const keyHandler = getParticipantKeyHandler(data.participantId);
1425
+ yield keyHandler.ratchetKey(data.keyIndex);
1426
+ keyHandler.hasValidKey = true;
1427
+ });
1428
+ }
1429
+ function getTrackCryptor(participantId, trackId) {
1430
+ let cryptor = participantCryptors.find(c => c.getTrackId() === trackId);
1431
+ if (!cryptor) {
1432
+ workerLogger.info('creating new cryptor for', {
1433
+ participantId
1434
+ });
1435
+ if (!keyProviderOptions) {
1436
+ throw Error('Missing keyProvider options');
1437
+ }
1438
+ cryptor = new FrameCryptor({
1439
+ participantId,
1440
+ keys: getParticipantKeyHandler(participantId),
1441
+ keyProviderOptions
1442
+ });
1443
+ setupCryptorErrorEvents(cryptor);
1444
+ participantCryptors.push(cryptor);
1445
+ } else if (participantId !== cryptor.getParticipantId()) {
1446
+ // assign new participant id to track cryptor and pass in correct key handler
1447
+ cryptor.setParticipant(participantId, getParticipantKeyHandler(participantId));
1448
+ }
1449
+ return cryptor;
1450
+ }
1451
+ function getParticipantKeyHandler(participantId) {
1452
+ if (!participantId) {
1453
+ return publisherKeys;
1454
+ }
1455
+ let keys = participantKeys.get(participantId);
1456
+ if (!keys) {
1457
+ keys = new ParticipantKeyHandler(participantId, true, keyProviderOptions);
1458
+ if (sharedKey) {
1459
+ keys.setKey(sharedKey);
1460
+ }
1461
+ participantKeys.set(participantId, keys);
1462
+ }
1463
+ return keys;
1464
+ }
1465
+ function unsetCryptorParticipant(trackId) {
1466
+ var _a;
1467
+ (_a = participantCryptors.find(c => c.getTrackId() === trackId)) === null || _a === void 0 ? void 0 : _a.unsetParticipant();
1468
+ }
1469
+ function getPublisherCryptor(trackId) {
1470
+ let publishCryptor = publishCryptors.find(cryptor => cryptor.getTrackId() === trackId);
1471
+ if (!publishCryptor) {
1472
+ if (!keyProviderOptions) {
1473
+ throw new TypeError('Missing keyProvider options');
1474
+ }
1475
+ publishCryptor = new FrameCryptor({
1476
+ keys: publisherKeys,
1477
+ participantId: 'publisher',
1478
+ keyProviderOptions
1479
+ });
1480
+ setupCryptorErrorEvents(publishCryptor);
1481
+ publishCryptors.push(publishCryptor);
1482
+ }
1483
+ return publishCryptor;
1484
+ }
1485
+ function setEncryptionEnabled(enable, participantId) {
1486
+ if (!participantId) {
1487
+ isEncryptionEnabled = enable;
1488
+ publisherKeys.setEnabled(enable);
1489
+ } else {
1490
+ getParticipantKeyHandler(participantId).setEnabled(enable);
1491
+ }
1492
+ }
1493
+ function setSharedKey(key, index) {
1494
+ workerLogger.debug('setting shared key');
1495
+ sharedKey = key;
1496
+ publisherKeys === null || publisherKeys === void 0 ? void 0 : publisherKeys.setKey(key, index);
1497
+ for (const [, keyHandler] of participantKeys) {
1498
+ keyHandler.setKey(key, index);
1499
+ }
1500
+ }
1501
+ function setupCryptorErrorEvents(cryptor) {
1502
+ cryptor.on('cryptorError', error => {
1503
+ const msg = {
1504
+ kind: 'error',
1505
+ data: {
1506
+ error: new Error("".concat(CryptorErrorReason[error.reason], ": ").concat(error.message))
1507
+ }
1508
+ };
1509
+ postMessage(msg);
1510
+ });
1511
+ }
1512
+ function emitRatchetedKeys(material, keyIndex) {
1513
+ const msg = {
1514
+ kind: "ratchetKey",
1515
+ data: {
1516
+ // participantId,
1517
+ keyIndex,
1518
+ material
1519
+ }
1520
+ };
1521
+ postMessage(msg);
1522
+ }
1523
+ // Operations using RTCRtpScriptTransform.
1524
+ // @ts-ignore
1525
+ if (self.RTCTransformEvent) {
1526
+ workerLogger.debug('setup transform event');
1527
+ // @ts-ignore
1528
+ self.onrtctransform = event => {
1529
+ const transformer = event.transformer;
1530
+ workerLogger.debug('transformer', transformer);
1531
+ transformer.handled = true;
1532
+ const {
1533
+ kind,
1534
+ participantId,
1535
+ trackId,
1536
+ codec
1537
+ } = transformer.options;
1538
+ const cryptor = kind === 'encode' ? getPublisherCryptor(trackId) : getTrackCryptor(participantId, trackId);
1539
+ workerLogger.debug('transform', {
1540
+ codec
1541
+ });
1542
+ cryptor.setupTransform(kind, transformer.readable, transformer.writable, trackId, codec);
1543
+ };
1544
+ }
1545
+ //# sourceMappingURL=livekit-client.e2ee.worker.mjs.map