livekit-client 2.22.1 → 2.22.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/livekit-client.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +663 -476
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +480 -157
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.fm.worker.js +1 -1
- package/dist/livekit-client.fm.worker.js.map +1 -1
- package/dist/livekit-client.fm.worker.mjs +8 -1
- package/dist/livekit-client.fm.worker.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/api/WebSocketStream.d.ts.map +1 -1
- package/dist/src/api/utils.d.ts +1 -0
- package/dist/src/api/utils.d.ts.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts +33 -0
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/constants.d.ts +5 -0
- package/dist/src/e2ee/constants.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +24 -3
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/ErrorRateLimiter.d.ts +21 -0
- package/dist/src/e2ee/worker/ErrorRateLimiter.d.ts.map +1 -0
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +43 -19
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/logger.d.ts +4 -0
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts +12 -0
- package/dist/src/room/PCTransportManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +1 -0
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/data-stream/incoming/StreamReader.d.ts +17 -17
- package/dist/src/room/data-stream/incoming/StreamReader.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts +16 -0
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +7 -0
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +1 -1
- package/dist/src/room/utils.d.ts +34 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/ts4.2/api/utils.d.ts +1 -0
- package/dist/ts4.2/e2ee/E2eeManager.d.ts +33 -0
- package/dist/ts4.2/e2ee/constants.d.ts +5 -0
- package/dist/ts4.2/e2ee/types.d.ts +24 -3
- package/dist/ts4.2/e2ee/worker/ErrorRateLimiter.d.ts +21 -0
- package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +43 -19
- package/dist/ts4.2/logger.d.ts +4 -0
- package/dist/ts4.2/room/PCTransportManager.d.ts +12 -0
- package/dist/ts4.2/room/RTCEngine.d.ts +1 -0
- package/dist/ts4.2/room/data-stream/incoming/StreamReader.d.ts +17 -17
- package/dist/ts4.2/room/participant/publishUtils.d.ts +16 -0
- package/dist/ts4.2/room/track/LocalVideoTrack.d.ts +7 -0
- package/dist/ts4.2/room/track/options.d.ts +1 -1
- package/dist/ts4.2/room/utils.d.ts +34 -0
- package/package.json +1 -1
- package/src/api/SignalClient.ts +2 -1
- package/src/api/WebSocketStream.ts +3 -8
- package/src/api/utils.ts +10 -0
- package/src/e2ee/E2eeManager.test.ts +196 -0
- package/src/e2ee/E2eeManager.ts +150 -31
- package/src/e2ee/constants.ts +6 -0
- package/src/e2ee/subscriberBlackScreen.test.ts +544 -0
- package/src/e2ee/types.ts +28 -3
- package/src/e2ee/worker/DataCryptor.ts +2 -1
- package/src/e2ee/worker/ErrorRateLimiter.test.ts +53 -0
- package/src/e2ee/worker/ErrorRateLimiter.ts +52 -0
- package/src/e2ee/worker/FrameCryptor.race.test.ts +9 -26
- package/src/e2ee/worker/FrameCryptor.test.ts +0 -1
- package/src/e2ee/worker/FrameCryptor.ts +202 -119
- package/src/e2ee/worker/e2ee.worker.ts +72 -17
- package/src/logger.ts +22 -0
- package/src/room/PCTransportManager.test.ts +35 -0
- package/src/room/PCTransportManager.ts +12 -4
- package/src/room/RTCEngine.ts +28 -7
- package/src/room/Room.ts +1 -1
- package/src/room/data-stream/incoming/IncomingDataStreamManager.test.ts +171 -0
- package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +17 -18
- package/src/room/data-stream/incoming/StreamReader.ts +20 -50
- package/src/room/participant/LocalParticipant.ts +30 -14
- package/src/room/participant/publishUtils.test.ts +133 -0
- package/src/room/participant/publishUtils.ts +54 -19
- package/src/room/track/LocalVideoTrack.ts +15 -5
- package/src/room/track/options.ts +1 -1
- package/src/room/utils.test.ts +87 -0
- package/src/room/utils.ts +59 -0
|
@@ -424,6 +424,13 @@ function wrapWithContext(base, ctxFn) {
|
|
|
424
424
|
return proxy;
|
|
425
425
|
}
|
|
426
426
|
const workerLogger = loglevelExports.getLogger(LoggerNames.E2EE);
|
|
427
|
+
const workerLogLevelListeners = new Set();
|
|
428
|
+
const originalWorkerSetLevel = workerLogger.setLevel.bind(workerLogger);
|
|
429
|
+
workerLogger.setLevel = (level, persist) => {
|
|
430
|
+
originalWorkerSetLevel(level, persist);
|
|
431
|
+
const numeric = workerLogger.getLevel();
|
|
432
|
+
workerLogLevelListeners.forEach(cb => cb(numeric));
|
|
433
|
+
};
|
|
427
434
|
|
|
428
435
|
var e = Object.defineProperty;
|
|
429
436
|
var h = (i, s, t) => s in i ? e(i, s, {
|
|
@@ -4105,285 +4112,163 @@ var CryptorEvent;
|
|
|
4105
4112
|
CryptorEvent["Error"] = "cryptorError";
|
|
4106
4113
|
})(CryptorEvent || (CryptorEvent = {}));
|
|
4107
4114
|
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
}
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
const encodedSalt = textEncoder.encode(salt);
|
|
4126
|
-
switch (algorithmName) {
|
|
4127
|
-
case 'HKDF':
|
|
4128
|
-
return {
|
|
4129
|
-
name: 'HKDF',
|
|
4130
|
-
salt: encodedSalt,
|
|
4131
|
-
hash: 'SHA-256',
|
|
4132
|
-
info: new ArrayBuffer(128)
|
|
4133
|
-
};
|
|
4134
|
-
case 'PBKDF2':
|
|
4135
|
-
{
|
|
4136
|
-
return {
|
|
4137
|
-
name: 'PBKDF2',
|
|
4138
|
-
salt: encodedSalt,
|
|
4139
|
-
hash: 'SHA-256',
|
|
4140
|
-
iterations: 100000
|
|
4141
|
-
};
|
|
4142
|
-
}
|
|
4143
|
-
default:
|
|
4144
|
-
throw new Error("algorithm ".concat(algorithmName, " is currently unsupported"));
|
|
4115
|
+
var _a, _b;
|
|
4116
|
+
class TypedPromise extends (_b = Promise) {
|
|
4117
|
+
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
|
4118
|
+
constructor(executor) {
|
|
4119
|
+
super(executor);
|
|
4120
|
+
}
|
|
4121
|
+
catch(onrejected) {
|
|
4122
|
+
return super.catch(onrejected);
|
|
4123
|
+
}
|
|
4124
|
+
static reject(reason) {
|
|
4125
|
+
return super.reject(reason);
|
|
4126
|
+
}
|
|
4127
|
+
static all(values) {
|
|
4128
|
+
return super.all(values);
|
|
4129
|
+
}
|
|
4130
|
+
static race(values) {
|
|
4131
|
+
return super.race(values);
|
|
4145
4132
|
}
|
|
4146
4133
|
}
|
|
4134
|
+
_a = TypedPromise;
|
|
4135
|
+
TypedPromise.resolve = value => {
|
|
4136
|
+
return Reflect.get(_b, "resolve", _a).call(_a, value);
|
|
4137
|
+
};
|
|
4138
|
+
|
|
4139
|
+
// tiny, simplified version of https://github.com/lancedikson/bowser/blob/master/src/parser-browsers.js
|
|
4140
|
+
// reduced to only differentiate Chrome(ium) based browsers / Firefox / Safari
|
|
4141
|
+
const commonVersionIdentifier = /version\/(\d+(\.?_?\d+)+)/i;
|
|
4142
|
+
let browserDetails;
|
|
4147
4143
|
/**
|
|
4148
|
-
*
|
|
4149
|
-
* See https://tools.ietf.org/html/draft-omara-sframe-00#section-4.3.1
|
|
4144
|
+
* @internal
|
|
4150
4145
|
*/
|
|
4151
|
-
function
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4146
|
+
function getBrowser(userAgent) {
|
|
4147
|
+
let force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
|
4148
|
+
if (typeof navigator === 'undefined') {
|
|
4149
|
+
return;
|
|
4150
|
+
}
|
|
4151
|
+
const ua = (navigator.userAgent).toLowerCase();
|
|
4152
|
+
if (browserDetails === undefined || force) {
|
|
4153
|
+
const browser = browsersList.find(_ref => {
|
|
4154
|
+
let test = _ref.test;
|
|
4155
|
+
return test.test(ua);
|
|
4156
|
+
});
|
|
4157
|
+
browserDetails = browser === null || browser === void 0 ? void 0 : browser.describe(ua);
|
|
4158
|
+
}
|
|
4159
|
+
return browserDetails;
|
|
4160
|
+
}
|
|
4161
|
+
const browsersList = [{
|
|
4162
|
+
test: /firefox|iceweasel|fxios/i,
|
|
4163
|
+
describe(ua) {
|
|
4164
|
+
const browser = {
|
|
4165
|
+
name: 'Firefox',
|
|
4166
|
+
version: getMatch(/(?:firefox|iceweasel|fxios)[\s/](\d+(\.?_?\d+)+)/i, ua),
|
|
4167
|
+
os: ua.toLowerCase().includes('fxios') ? 'iOS' : undefined,
|
|
4168
|
+
osVersion: getOSVersion(ua)
|
|
4163
4169
|
};
|
|
4164
|
-
|
|
4170
|
+
return browser;
|
|
4171
|
+
}
|
|
4172
|
+
}, {
|
|
4173
|
+
test: /chrom|crios|crmo/i,
|
|
4174
|
+
describe(ua) {
|
|
4175
|
+
const browser = {
|
|
4176
|
+
name: 'Chrome',
|
|
4177
|
+
version: getMatch(/(?:chrome|chromium|crios|crmo)\/(\d+(\.?_?\d+)+)/i, ua),
|
|
4178
|
+
os: ua.toLowerCase().includes('crios') ? 'iOS' : undefined,
|
|
4179
|
+
osVersion: getOSVersion(ua)
|
|
4180
|
+
};
|
|
4181
|
+
return browser;
|
|
4182
|
+
}
|
|
4183
|
+
}, /* Safari */
|
|
4184
|
+
{
|
|
4185
|
+
test: /safari|applewebkit/i,
|
|
4186
|
+
describe(ua) {
|
|
4187
|
+
const browser = {
|
|
4188
|
+
name: 'Safari',
|
|
4189
|
+
version: getMatch(commonVersionIdentifier, ua),
|
|
4190
|
+
os: ua.includes('mobile/') ? 'iOS' : 'macOS',
|
|
4191
|
+
osVersion: getOSVersion(ua)
|
|
4192
|
+
};
|
|
4193
|
+
return browser;
|
|
4194
|
+
}
|
|
4195
|
+
}];
|
|
4196
|
+
function getMatch(exp, ua) {
|
|
4197
|
+
let id = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
|
|
4198
|
+
const match = ua.match(exp);
|
|
4199
|
+
return match && match.length >= id && match[id] || '';
|
|
4165
4200
|
}
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
* https://tools.ietf.org/html/draft-omara-sframe-00#section-4.3.5.1
|
|
4169
|
-
*/
|
|
4170
|
-
function ratchet(material, salt) {
|
|
4171
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
4172
|
-
const algorithmOptions = getAlgoOptions(material.algorithm.name, salt);
|
|
4173
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveBits
|
|
4174
|
-
return crypto.subtle.deriveBits(algorithmOptions, material, 256);
|
|
4175
|
-
});
|
|
4201
|
+
function getOSVersion(ua) {
|
|
4202
|
+
return ua.includes('mac os') ? getMatch(/\(.+?(\d+_\d+(:?_\d+)?)/, ua, 1).replace(/_/g, '.') : undefined;
|
|
4176
4203
|
}
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4204
|
+
|
|
4205
|
+
var events = {exports: {}};
|
|
4206
|
+
|
|
4207
|
+
var hasRequiredEvents;
|
|
4208
|
+
function requireEvents() {
|
|
4209
|
+
if (hasRequiredEvents) return events.exports;
|
|
4210
|
+
hasRequiredEvents = 1;
|
|
4211
|
+
var R = typeof Reflect === 'object' ? Reflect : null;
|
|
4212
|
+
var ReflectApply = R && typeof R.apply === 'function' ? R.apply : function ReflectApply(target, receiver, args) {
|
|
4213
|
+
return Function.prototype.apply.call(target, receiver, args);
|
|
4214
|
+
};
|
|
4215
|
+
var ReflectOwnKeys;
|
|
4216
|
+
if (R && typeof R.ownKeys === 'function') {
|
|
4217
|
+
ReflectOwnKeys = R.ownKeys;
|
|
4218
|
+
} else if (Object.getOwnPropertySymbols) {
|
|
4219
|
+
ReflectOwnKeys = function ReflectOwnKeys(target) {
|
|
4220
|
+
return Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target));
|
|
4221
|
+
};
|
|
4222
|
+
} else {
|
|
4223
|
+
ReflectOwnKeys = function ReflectOwnKeys(target) {
|
|
4224
|
+
return Object.getOwnPropertyNames(target);
|
|
4225
|
+
};
|
|
4180
4226
|
}
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4227
|
+
function ProcessEmitWarning(warning) {
|
|
4228
|
+
if (console && console.warn) console.warn(warning);
|
|
4229
|
+
}
|
|
4230
|
+
var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) {
|
|
4231
|
+
return value !== value;
|
|
4232
|
+
};
|
|
4233
|
+
function EventEmitter() {
|
|
4234
|
+
EventEmitter.init.call(this);
|
|
4235
|
+
}
|
|
4236
|
+
events.exports = EventEmitter;
|
|
4237
|
+
events.exports.once = once;
|
|
4238
|
+
|
|
4239
|
+
// Backwards-compat with node 0.10.x
|
|
4240
|
+
EventEmitter.EventEmitter = EventEmitter;
|
|
4241
|
+
EventEmitter.prototype._events = undefined;
|
|
4242
|
+
EventEmitter.prototype._eventsCount = 0;
|
|
4243
|
+
EventEmitter.prototype._maxListeners = undefined;
|
|
4244
|
+
|
|
4245
|
+
// By default EventEmitters will print a warning if more than 10 listeners are
|
|
4246
|
+
// added to it. This is a useful default which helps finding memory leaks.
|
|
4247
|
+
var defaultMaxListeners = 10;
|
|
4248
|
+
function checkListener(listener) {
|
|
4249
|
+
if (typeof listener !== 'function') {
|
|
4250
|
+
throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);
|
|
4200
4251
|
}
|
|
4201
4252
|
}
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
// Need to escape.
|
|
4213
|
-
dataOut.push(kEmulationByte);
|
|
4214
|
-
numConsecutiveZeros = 0;
|
|
4253
|
+
Object.defineProperty(EventEmitter, 'defaultMaxListeners', {
|
|
4254
|
+
enumerable: true,
|
|
4255
|
+
get: function () {
|
|
4256
|
+
return defaultMaxListeners;
|
|
4257
|
+
},
|
|
4258
|
+
set: function (arg) {
|
|
4259
|
+
if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {
|
|
4260
|
+
throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.');
|
|
4261
|
+
}
|
|
4262
|
+
defaultMaxListeners = arg;
|
|
4215
4263
|
}
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4264
|
+
});
|
|
4265
|
+
EventEmitter.init = function () {
|
|
4266
|
+
if (this._events === undefined || this._events === Object.getPrototypeOf(this)._events) {
|
|
4267
|
+
this._events = Object.create(null);
|
|
4268
|
+
this._eventsCount = 0;
|
|
4221
4269
|
}
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
}
|
|
4225
|
-
|
|
4226
|
-
class DataCryptor {
|
|
4227
|
-
static makeIV(timestamp) {
|
|
4228
|
-
const iv = new ArrayBuffer(12);
|
|
4229
|
-
const ivView = new DataView(iv);
|
|
4230
|
-
const randomBytes = crypto.getRandomValues(new Uint32Array(1));
|
|
4231
|
-
ivView.setUint32(0, randomBytes[0]);
|
|
4232
|
-
ivView.setUint32(4, timestamp);
|
|
4233
|
-
ivView.setUint32(8, timestamp - DataCryptor.sendCount % 0xffff);
|
|
4234
|
-
DataCryptor.sendCount++;
|
|
4235
|
-
return iv;
|
|
4236
|
-
}
|
|
4237
|
-
static encrypt(data, keys) {
|
|
4238
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
4239
|
-
const iv = DataCryptor.makeIV(performance.now());
|
|
4240
|
-
const keySet = yield keys.getKeySet();
|
|
4241
|
-
if (!keySet) {
|
|
4242
|
-
throw new Error('No key set found');
|
|
4243
|
-
}
|
|
4244
|
-
const cipherText = yield crypto.subtle.encrypt({
|
|
4245
|
-
name: ENCRYPTION_ALGORITHM,
|
|
4246
|
-
iv
|
|
4247
|
-
}, keySet.encryptionKey, new Uint8Array(data));
|
|
4248
|
-
return {
|
|
4249
|
-
payload: new Uint8Array(cipherText),
|
|
4250
|
-
iv: new Uint8Array(iv),
|
|
4251
|
-
keyIndex: keys.getCurrentKeyIndex()
|
|
4252
|
-
};
|
|
4253
|
-
});
|
|
4254
|
-
}
|
|
4255
|
-
static decrypt(data_1, iv_1, keys_1) {
|
|
4256
|
-
return __awaiter(this, arguments, void 0, function (data, iv, keys) {
|
|
4257
|
-
let keyIndex = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
|
|
4258
|
-
let initialMaterial = arguments.length > 4 ? arguments[4] : undefined;
|
|
4259
|
-
let ratchetOpts = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {
|
|
4260
|
-
ratchetCount: 0
|
|
4261
|
-
};
|
|
4262
|
-
return function* () {
|
|
4263
|
-
const keySet = yield keys.getKeySet(keyIndex);
|
|
4264
|
-
if (!keySet) {
|
|
4265
|
-
throw new Error('No key set found');
|
|
4266
|
-
}
|
|
4267
|
-
try {
|
|
4268
|
-
const plainText = yield crypto.subtle.decrypt({
|
|
4269
|
-
name: ENCRYPTION_ALGORITHM,
|
|
4270
|
-
iv
|
|
4271
|
-
}, keySet.encryptionKey, new Uint8Array(data));
|
|
4272
|
-
return {
|
|
4273
|
-
payload: new Uint8Array(plainText)
|
|
4274
|
-
};
|
|
4275
|
-
} catch (error) {
|
|
4276
|
-
if (keys.keyProviderOptions.ratchetWindowSize > 0) {
|
|
4277
|
-
if (ratchetOpts.ratchetCount < keys.keyProviderOptions.ratchetWindowSize) {
|
|
4278
|
-
workerLogger.debug("DataCryptor: ratcheting key attempt ".concat(ratchetOpts.ratchetCount, " of ").concat(keys.keyProviderOptions.ratchetWindowSize, ", for data packet"));
|
|
4279
|
-
let ratchetedKeySet;
|
|
4280
|
-
let ratchetResult;
|
|
4281
|
-
if ((initialMaterial !== null && initialMaterial !== void 0 ? initialMaterial : keySet) === keys.getKeySet(keyIndex)) {
|
|
4282
|
-
// only ratchet if the currently set key is still the same as the one used to decrypt this frame
|
|
4283
|
-
// if not, it might be that a different frame has already ratcheted and we try with that one first
|
|
4284
|
-
ratchetResult = yield keys.ratchetKey(keyIndex, false);
|
|
4285
|
-
ratchetedKeySet = yield deriveKeys(ratchetResult.cryptoKey, keys.keyProviderOptions);
|
|
4286
|
-
}
|
|
4287
|
-
const decryptedData = yield DataCryptor.decrypt(data, iv, keys, keyIndex, initialMaterial, {
|
|
4288
|
-
ratchetCount: ratchetOpts.ratchetCount + 1,
|
|
4289
|
-
encryptionKey: ratchetedKeySet === null || ratchetedKeySet === void 0 ? void 0 : ratchetedKeySet.encryptionKey
|
|
4290
|
-
});
|
|
4291
|
-
if (decryptedData && ratchetedKeySet) {
|
|
4292
|
-
// before updating the keys, make sure that the keySet used for this frame is still the same as the currently set key
|
|
4293
|
-
// if it's not, a new key might have been set already, which we don't want to override
|
|
4294
|
-
if ((initialMaterial !== null && initialMaterial !== void 0 ? initialMaterial : keySet) === keys.getKeySet(keyIndex)) {
|
|
4295
|
-
keys.setKeySet(ratchetedKeySet, keyIndex, ratchetResult);
|
|
4296
|
-
// decryption was successful, set the new key index to reflect the ratcheted key set
|
|
4297
|
-
keys.setCurrentKeyIndex(keyIndex);
|
|
4298
|
-
}
|
|
4299
|
-
}
|
|
4300
|
-
return decryptedData;
|
|
4301
|
-
} else {
|
|
4302
|
-
/**
|
|
4303
|
-
* Because we only set a new key once decryption has been successful,
|
|
4304
|
-
* we can be sure that we don't need to reset the key to the initial material at this point
|
|
4305
|
-
* as the key has not been updated on the keyHandler instance
|
|
4306
|
-
*/
|
|
4307
|
-
workerLogger.warn('DataCryptor: maximum ratchet attempts exceeded');
|
|
4308
|
-
throw new CryptorError("DataCryptor: valid key missing for participant ".concat(keys.participantIdentity), CryptorErrorReason.InvalidKey, keys.participantIdentity);
|
|
4309
|
-
}
|
|
4310
|
-
} else {
|
|
4311
|
-
throw new CryptorError("DataCryptor: Decryption failed: ".concat(error.message), CryptorErrorReason.InvalidKey, keys.participantIdentity);
|
|
4312
|
-
}
|
|
4313
|
-
}
|
|
4314
|
-
}();
|
|
4315
|
-
});
|
|
4316
|
-
}
|
|
4317
|
-
}
|
|
4318
|
-
DataCryptor.sendCount = 0;
|
|
4319
|
-
|
|
4320
|
-
var events = {exports: {}};
|
|
4321
|
-
|
|
4322
|
-
var hasRequiredEvents;
|
|
4323
|
-
function requireEvents() {
|
|
4324
|
-
if (hasRequiredEvents) return events.exports;
|
|
4325
|
-
hasRequiredEvents = 1;
|
|
4326
|
-
var R = typeof Reflect === 'object' ? Reflect : null;
|
|
4327
|
-
var ReflectApply = R && typeof R.apply === 'function' ? R.apply : function ReflectApply(target, receiver, args) {
|
|
4328
|
-
return Function.prototype.apply.call(target, receiver, args);
|
|
4329
|
-
};
|
|
4330
|
-
var ReflectOwnKeys;
|
|
4331
|
-
if (R && typeof R.ownKeys === 'function') {
|
|
4332
|
-
ReflectOwnKeys = R.ownKeys;
|
|
4333
|
-
} else if (Object.getOwnPropertySymbols) {
|
|
4334
|
-
ReflectOwnKeys = function ReflectOwnKeys(target) {
|
|
4335
|
-
return Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target));
|
|
4336
|
-
};
|
|
4337
|
-
} else {
|
|
4338
|
-
ReflectOwnKeys = function ReflectOwnKeys(target) {
|
|
4339
|
-
return Object.getOwnPropertyNames(target);
|
|
4340
|
-
};
|
|
4341
|
-
}
|
|
4342
|
-
function ProcessEmitWarning(warning) {
|
|
4343
|
-
if (console && console.warn) console.warn(warning);
|
|
4344
|
-
}
|
|
4345
|
-
var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) {
|
|
4346
|
-
return value !== value;
|
|
4347
|
-
};
|
|
4348
|
-
function EventEmitter() {
|
|
4349
|
-
EventEmitter.init.call(this);
|
|
4350
|
-
}
|
|
4351
|
-
events.exports = EventEmitter;
|
|
4352
|
-
events.exports.once = once;
|
|
4353
|
-
|
|
4354
|
-
// Backwards-compat with node 0.10.x
|
|
4355
|
-
EventEmitter.EventEmitter = EventEmitter;
|
|
4356
|
-
EventEmitter.prototype._events = undefined;
|
|
4357
|
-
EventEmitter.prototype._eventsCount = 0;
|
|
4358
|
-
EventEmitter.prototype._maxListeners = undefined;
|
|
4359
|
-
|
|
4360
|
-
// By default EventEmitters will print a warning if more than 10 listeners are
|
|
4361
|
-
// added to it. This is a useful default which helps finding memory leaks.
|
|
4362
|
-
var defaultMaxListeners = 10;
|
|
4363
|
-
function checkListener(listener) {
|
|
4364
|
-
if (typeof listener !== 'function') {
|
|
4365
|
-
throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);
|
|
4366
|
-
}
|
|
4367
|
-
}
|
|
4368
|
-
Object.defineProperty(EventEmitter, 'defaultMaxListeners', {
|
|
4369
|
-
enumerable: true,
|
|
4370
|
-
get: function () {
|
|
4371
|
-
return defaultMaxListeners;
|
|
4372
|
-
},
|
|
4373
|
-
set: function (arg) {
|
|
4374
|
-
if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {
|
|
4375
|
-
throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.');
|
|
4376
|
-
}
|
|
4377
|
-
defaultMaxListeners = arg;
|
|
4378
|
-
}
|
|
4379
|
-
});
|
|
4380
|
-
EventEmitter.init = function () {
|
|
4381
|
-
if (this._events === undefined || this._events === Object.getPrototypeOf(this)._events) {
|
|
4382
|
-
this._events = Object.create(null);
|
|
4383
|
-
this._eventsCount = 0;
|
|
4384
|
-
}
|
|
4385
|
-
this._maxListeners = this._maxListeners || undefined;
|
|
4386
|
-
};
|
|
4270
|
+
this._maxListeners = this._maxListeners || undefined;
|
|
4271
|
+
};
|
|
4387
4272
|
|
|
4388
4273
|
// Obviously not all Emitters should be limited to 10. This function allows
|
|
4389
4274
|
// that to be increased. Set to zero for unlimited.
|
|
@@ -4704,96 +4589,6 @@ function requireEvents() {
|
|
|
4704
4589
|
|
|
4705
4590
|
var eventsExports = requireEvents();
|
|
4706
4591
|
|
|
4707
|
-
var _a, _b;
|
|
4708
|
-
class TypedPromise extends (_b = Promise) {
|
|
4709
|
-
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
|
4710
|
-
constructor(executor) {
|
|
4711
|
-
super(executor);
|
|
4712
|
-
}
|
|
4713
|
-
catch(onrejected) {
|
|
4714
|
-
return super.catch(onrejected);
|
|
4715
|
-
}
|
|
4716
|
-
static reject(reason) {
|
|
4717
|
-
return super.reject(reason);
|
|
4718
|
-
}
|
|
4719
|
-
static all(values) {
|
|
4720
|
-
return super.all(values);
|
|
4721
|
-
}
|
|
4722
|
-
static race(values) {
|
|
4723
|
-
return super.race(values);
|
|
4724
|
-
}
|
|
4725
|
-
}
|
|
4726
|
-
_a = TypedPromise;
|
|
4727
|
-
TypedPromise.resolve = value => {
|
|
4728
|
-
return Reflect.get(_b, "resolve", _a).call(_a, value);
|
|
4729
|
-
};
|
|
4730
|
-
|
|
4731
|
-
// tiny, simplified version of https://github.com/lancedikson/bowser/blob/master/src/parser-browsers.js
|
|
4732
|
-
// reduced to only differentiate Chrome(ium) based browsers / Firefox / Safari
|
|
4733
|
-
const commonVersionIdentifier = /version\/(\d+(\.?_?\d+)+)/i;
|
|
4734
|
-
let browserDetails;
|
|
4735
|
-
/**
|
|
4736
|
-
* @internal
|
|
4737
|
-
*/
|
|
4738
|
-
function getBrowser(userAgent) {
|
|
4739
|
-
let force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
|
4740
|
-
if (typeof navigator === 'undefined') {
|
|
4741
|
-
return;
|
|
4742
|
-
}
|
|
4743
|
-
const ua = (navigator.userAgent).toLowerCase();
|
|
4744
|
-
if (browserDetails === undefined || force) {
|
|
4745
|
-
const browser = browsersList.find(_ref => {
|
|
4746
|
-
let test = _ref.test;
|
|
4747
|
-
return test.test(ua);
|
|
4748
|
-
});
|
|
4749
|
-
browserDetails = browser === null || browser === void 0 ? void 0 : browser.describe(ua);
|
|
4750
|
-
}
|
|
4751
|
-
return browserDetails;
|
|
4752
|
-
}
|
|
4753
|
-
const browsersList = [{
|
|
4754
|
-
test: /firefox|iceweasel|fxios/i,
|
|
4755
|
-
describe(ua) {
|
|
4756
|
-
const browser = {
|
|
4757
|
-
name: 'Firefox',
|
|
4758
|
-
version: getMatch(/(?:firefox|iceweasel|fxios)[\s/](\d+(\.?_?\d+)+)/i, ua),
|
|
4759
|
-
os: ua.toLowerCase().includes('fxios') ? 'iOS' : undefined,
|
|
4760
|
-
osVersion: getOSVersion(ua)
|
|
4761
|
-
};
|
|
4762
|
-
return browser;
|
|
4763
|
-
}
|
|
4764
|
-
}, {
|
|
4765
|
-
test: /chrom|crios|crmo/i,
|
|
4766
|
-
describe(ua) {
|
|
4767
|
-
const browser = {
|
|
4768
|
-
name: 'Chrome',
|
|
4769
|
-
version: getMatch(/(?:chrome|chromium|crios|crmo)\/(\d+(\.?_?\d+)+)/i, ua),
|
|
4770
|
-
os: ua.toLowerCase().includes('crios') ? 'iOS' : undefined,
|
|
4771
|
-
osVersion: getOSVersion(ua)
|
|
4772
|
-
};
|
|
4773
|
-
return browser;
|
|
4774
|
-
}
|
|
4775
|
-
}, /* Safari */
|
|
4776
|
-
{
|
|
4777
|
-
test: /safari|applewebkit/i,
|
|
4778
|
-
describe(ua) {
|
|
4779
|
-
const browser = {
|
|
4780
|
-
name: 'Safari',
|
|
4781
|
-
version: getMatch(commonVersionIdentifier, ua),
|
|
4782
|
-
os: ua.includes('mobile/') ? 'iOS' : 'macOS',
|
|
4783
|
-
osVersion: getOSVersion(ua)
|
|
4784
|
-
};
|
|
4785
|
-
return browser;
|
|
4786
|
-
}
|
|
4787
|
-
}];
|
|
4788
|
-
function getMatch(exp, ua) {
|
|
4789
|
-
let id = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
|
|
4790
|
-
const match = ua.match(exp);
|
|
4791
|
-
return match && match.length >= id && match[id] || '';
|
|
4792
|
-
}
|
|
4793
|
-
function getOSVersion(ua) {
|
|
4794
|
-
return ua.includes('mac os') ? getMatch(/\(.+?(\d+_\d+(:?_\d+)?)/, ua, 1).replace(/_/g, '.') : undefined;
|
|
4795
|
-
}
|
|
4796
|
-
|
|
4797
4592
|
/**
|
|
4798
4593
|
* Events are the primary way LiveKit notifies your application of changes.
|
|
4799
4594
|
*
|
|
@@ -6156,16 +5951,290 @@ function isWeb() {
|
|
|
6156
5951
|
return typeof document !== 'undefined';
|
|
6157
5952
|
}
|
|
6158
5953
|
|
|
6159
|
-
function
|
|
6160
|
-
|
|
5954
|
+
function getErrorDescription(error, errorCategory) {
|
|
5955
|
+
if (error instanceof Error) {
|
|
5956
|
+
if (error.name && error.message) {
|
|
5957
|
+
return "".concat(error.name, ": ").concat(error.message);
|
|
5958
|
+
}
|
|
5959
|
+
return error.name;
|
|
5960
|
+
}
|
|
5961
|
+
return "Encountered unknown ".concat(errorCategory, " error: ").concat(String(error));
|
|
6161
5962
|
}
|
|
6162
5963
|
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
5964
|
+
function isVideoFrame(frame) {
|
|
5965
|
+
return 'type' in frame;
|
|
5966
|
+
}
|
|
5967
|
+
function importKey(keyBytes_1) {
|
|
5968
|
+
return __awaiter(this, arguments, void 0, function (keyBytes) {
|
|
5969
|
+
let algorithm = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
|
|
5970
|
+
name: ENCRYPTION_ALGORITHM
|
|
5971
|
+
};
|
|
5972
|
+
let usage = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'encrypt';
|
|
5973
|
+
return function* () {
|
|
5974
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey
|
|
5975
|
+
return crypto.subtle.importKey('raw', keyBytes, algorithm, false, usage === 'derive' ? ['deriveBits', 'deriveKey'] : ['encrypt', 'decrypt']);
|
|
5976
|
+
}();
|
|
5977
|
+
});
|
|
5978
|
+
}
|
|
5979
|
+
function getAlgoOptions(algorithmName, salt) {
|
|
5980
|
+
const textEncoder = new TextEncoder();
|
|
5981
|
+
const encodedSalt = textEncoder.encode(salt);
|
|
5982
|
+
switch (algorithmName) {
|
|
5983
|
+
case 'HKDF':
|
|
5984
|
+
return {
|
|
5985
|
+
name: 'HKDF',
|
|
5986
|
+
salt: encodedSalt,
|
|
5987
|
+
hash: 'SHA-256',
|
|
5988
|
+
info: new ArrayBuffer(128)
|
|
5989
|
+
};
|
|
5990
|
+
case 'PBKDF2':
|
|
5991
|
+
{
|
|
5992
|
+
return {
|
|
5993
|
+
name: 'PBKDF2',
|
|
5994
|
+
salt: encodedSalt,
|
|
5995
|
+
hash: 'SHA-256',
|
|
5996
|
+
iterations: 100000
|
|
5997
|
+
};
|
|
5998
|
+
}
|
|
5999
|
+
default:
|
|
6000
|
+
throw new Error("algorithm ".concat(algorithmName, " is currently unsupported"));
|
|
6001
|
+
}
|
|
6002
|
+
}
|
|
6003
|
+
/**
|
|
6004
|
+
* Derives a set of keys from the master key.
|
|
6005
|
+
* See https://tools.ietf.org/html/draft-omara-sframe-00#section-4.3.1
|
|
6006
|
+
*/
|
|
6007
|
+
function deriveKeys(material, options) {
|
|
6008
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6009
|
+
const algorithmOptions = getAlgoOptions(material.algorithm.name, options.ratchetSalt);
|
|
6010
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey#HKDF
|
|
6011
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/HkdfParams
|
|
6012
|
+
const encryptionKey = yield crypto.subtle.deriveKey(algorithmOptions, material, {
|
|
6013
|
+
name: ENCRYPTION_ALGORITHM,
|
|
6014
|
+
length: options.keySize
|
|
6015
|
+
}, false, ['encrypt', 'decrypt']);
|
|
6016
|
+
return {
|
|
6017
|
+
material,
|
|
6018
|
+
encryptionKey
|
|
6019
|
+
};
|
|
6020
|
+
});
|
|
6021
|
+
}
|
|
6022
|
+
/**
|
|
6023
|
+
* Ratchets a key. See
|
|
6024
|
+
* https://tools.ietf.org/html/draft-omara-sframe-00#section-4.3.5.1
|
|
6025
|
+
*/
|
|
6026
|
+
function ratchet(material, salt) {
|
|
6027
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6028
|
+
const algorithmOptions = getAlgoOptions(material.algorithm.name, salt);
|
|
6029
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveBits
|
|
6030
|
+
return crypto.subtle.deriveBits(algorithmOptions, material, 256);
|
|
6031
|
+
});
|
|
6032
|
+
}
|
|
6033
|
+
function needsRbspUnescaping(frameData) {
|
|
6034
|
+
for (var i = 0; i < frameData.length - 3; i++) {
|
|
6035
|
+
if (frameData[i] == 0 && frameData[i + 1] == 0 && frameData[i + 2] == 3) return true;
|
|
6036
|
+
}
|
|
6037
|
+
return false;
|
|
6038
|
+
}
|
|
6039
|
+
function parseRbsp(stream) {
|
|
6040
|
+
const dataOut = [];
|
|
6041
|
+
var length = stream.length;
|
|
6042
|
+
for (var i = 0; i < stream.length;) {
|
|
6043
|
+
// Be careful about over/underflow here. byte_length_ - 3 can underflow, and
|
|
6044
|
+
// i + 3 can overflow, but byte_length_ - i can't, because i < byte_length_
|
|
6045
|
+
// above, and that expression will produce the number of bytes left in
|
|
6046
|
+
// the stream including the byte at i.
|
|
6047
|
+
if (length - i >= 3 && !stream[i] && !stream[i + 1] && stream[i + 2] == 3) {
|
|
6048
|
+
// Two rbsp bytes.
|
|
6049
|
+
dataOut.push(stream[i++]);
|
|
6050
|
+
dataOut.push(stream[i++]);
|
|
6051
|
+
// Skip the emulation byte.
|
|
6052
|
+
i++;
|
|
6053
|
+
} else {
|
|
6054
|
+
// Single rbsp byte.
|
|
6055
|
+
dataOut.push(stream[i++]);
|
|
6056
|
+
}
|
|
6057
|
+
}
|
|
6058
|
+
return new Uint8Array(dataOut);
|
|
6059
|
+
}
|
|
6060
|
+
const kZerosInStartSequence = 2;
|
|
6061
|
+
const kEmulationByte = 3;
|
|
6062
|
+
function writeRbsp(data_in) {
|
|
6063
|
+
const dataOut = [];
|
|
6064
|
+
var numConsecutiveZeros = 0;
|
|
6065
|
+
for (var i = 0; i < data_in.length; ++i) {
|
|
6066
|
+
var byte = data_in[i];
|
|
6067
|
+
if (byte <= kEmulationByte && numConsecutiveZeros >= kZerosInStartSequence) {
|
|
6068
|
+
// Need to escape.
|
|
6069
|
+
dataOut.push(kEmulationByte);
|
|
6070
|
+
numConsecutiveZeros = 0;
|
|
6071
|
+
}
|
|
6072
|
+
dataOut.push(byte);
|
|
6073
|
+
if (byte == 0) {
|
|
6074
|
+
++numConsecutiveZeros;
|
|
6075
|
+
} else {
|
|
6076
|
+
numConsecutiveZeros = 0;
|
|
6077
|
+
}
|
|
6078
|
+
}
|
|
6079
|
+
return new Uint8Array(dataOut);
|
|
6080
|
+
}
|
|
6081
|
+
|
|
6082
|
+
class DataCryptor {
|
|
6083
|
+
static makeIV(timestamp) {
|
|
6084
|
+
const iv = new ArrayBuffer(12);
|
|
6085
|
+
const ivView = new DataView(iv);
|
|
6086
|
+
const randomBytes = crypto.getRandomValues(new Uint32Array(1));
|
|
6087
|
+
ivView.setUint32(0, randomBytes[0]);
|
|
6088
|
+
ivView.setUint32(4, timestamp);
|
|
6089
|
+
ivView.setUint32(8, timestamp - DataCryptor.sendCount % 0xffff);
|
|
6090
|
+
DataCryptor.sendCount++;
|
|
6091
|
+
return iv;
|
|
6092
|
+
}
|
|
6093
|
+
static encrypt(data, keys) {
|
|
6094
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6095
|
+
const iv = DataCryptor.makeIV(performance.now());
|
|
6096
|
+
const keySet = yield keys.getKeySet();
|
|
6097
|
+
if (!keySet) {
|
|
6098
|
+
throw new Error('No key set found');
|
|
6099
|
+
}
|
|
6100
|
+
const cipherText = yield crypto.subtle.encrypt({
|
|
6101
|
+
name: ENCRYPTION_ALGORITHM,
|
|
6102
|
+
iv
|
|
6103
|
+
}, keySet.encryptionKey, new Uint8Array(data));
|
|
6104
|
+
return {
|
|
6105
|
+
payload: new Uint8Array(cipherText),
|
|
6106
|
+
iv: new Uint8Array(iv),
|
|
6107
|
+
keyIndex: keys.getCurrentKeyIndex()
|
|
6108
|
+
};
|
|
6109
|
+
});
|
|
6110
|
+
}
|
|
6111
|
+
static decrypt(data_1, iv_1, keys_1) {
|
|
6112
|
+
return __awaiter(this, arguments, void 0, function (data, iv, keys) {
|
|
6113
|
+
let keyIndex = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
|
|
6114
|
+
let initialMaterial = arguments.length > 4 ? arguments[4] : undefined;
|
|
6115
|
+
let ratchetOpts = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {
|
|
6116
|
+
ratchetCount: 0
|
|
6117
|
+
};
|
|
6118
|
+
return function* () {
|
|
6119
|
+
const keySet = yield keys.getKeySet(keyIndex);
|
|
6120
|
+
if (!keySet) {
|
|
6121
|
+
throw new Error('No key set found');
|
|
6122
|
+
}
|
|
6123
|
+
try {
|
|
6124
|
+
const plainText = yield crypto.subtle.decrypt({
|
|
6125
|
+
name: ENCRYPTION_ALGORITHM,
|
|
6126
|
+
iv
|
|
6127
|
+
}, keySet.encryptionKey, new Uint8Array(data));
|
|
6128
|
+
return {
|
|
6129
|
+
payload: new Uint8Array(plainText)
|
|
6130
|
+
};
|
|
6131
|
+
} catch (error) {
|
|
6132
|
+
if (keys.keyProviderOptions.ratchetWindowSize > 0) {
|
|
6133
|
+
if (ratchetOpts.ratchetCount < keys.keyProviderOptions.ratchetWindowSize) {
|
|
6134
|
+
workerLogger.debug("DataCryptor: ratcheting key attempt ".concat(ratchetOpts.ratchetCount, " of ").concat(keys.keyProviderOptions.ratchetWindowSize, ", for data packet"));
|
|
6135
|
+
let ratchetedKeySet;
|
|
6136
|
+
let ratchetResult;
|
|
6137
|
+
if ((initialMaterial !== null && initialMaterial !== void 0 ? initialMaterial : keySet) === keys.getKeySet(keyIndex)) {
|
|
6138
|
+
// only ratchet if the currently set key is still the same as the one used to decrypt this frame
|
|
6139
|
+
// if not, it might be that a different frame has already ratcheted and we try with that one first
|
|
6140
|
+
ratchetResult = yield keys.ratchetKey(keyIndex, false);
|
|
6141
|
+
ratchetedKeySet = yield deriveKeys(ratchetResult.cryptoKey, keys.keyProviderOptions);
|
|
6142
|
+
}
|
|
6143
|
+
const decryptedData = yield DataCryptor.decrypt(data, iv, keys, keyIndex, initialMaterial, {
|
|
6144
|
+
ratchetCount: ratchetOpts.ratchetCount + 1,
|
|
6145
|
+
encryptionKey: ratchetedKeySet === null || ratchetedKeySet === void 0 ? void 0 : ratchetedKeySet.encryptionKey
|
|
6146
|
+
});
|
|
6147
|
+
if (decryptedData && ratchetedKeySet) {
|
|
6148
|
+
// before updating the keys, make sure that the keySet used for this frame is still the same as the currently set key
|
|
6149
|
+
// if it's not, a new key might have been set already, which we don't want to override
|
|
6150
|
+
if ((initialMaterial !== null && initialMaterial !== void 0 ? initialMaterial : keySet) === keys.getKeySet(keyIndex)) {
|
|
6151
|
+
keys.setKeySet(ratchetedKeySet, keyIndex, ratchetResult);
|
|
6152
|
+
// decryption was successful, set the new key index to reflect the ratcheted key set
|
|
6153
|
+
keys.setCurrentKeyIndex(keyIndex);
|
|
6154
|
+
}
|
|
6155
|
+
}
|
|
6156
|
+
return decryptedData;
|
|
6157
|
+
} else {
|
|
6158
|
+
/**
|
|
6159
|
+
* Because we only set a new key once decryption has been successful,
|
|
6160
|
+
* we can be sure that we don't need to reset the key to the initial material at this point
|
|
6161
|
+
* as the key has not been updated on the keyHandler instance
|
|
6162
|
+
*/
|
|
6163
|
+
workerLogger.warn('DataCryptor: maximum ratchet attempts exceeded');
|
|
6164
|
+
throw new CryptorError("DataCryptor: valid key missing for participant ".concat(keys.participantIdentity), CryptorErrorReason.InvalidKey, keys.participantIdentity);
|
|
6165
|
+
}
|
|
6166
|
+
} else {
|
|
6167
|
+
throw new CryptorError("DataCryptor: Decryption failed: ".concat(getErrorDescription(error, 'decryption')), CryptorErrorReason.InvalidKey, keys.participantIdentity);
|
|
6168
|
+
}
|
|
6169
|
+
}
|
|
6170
|
+
}();
|
|
6171
|
+
});
|
|
6172
|
+
}
|
|
6173
|
+
}
|
|
6174
|
+
DataCryptor.sendCount = 0;
|
|
6175
|
+
|
|
6176
|
+
/**
|
|
6177
|
+
* Per-key rate limiter for repeated errors. Prevents log/emit floods and the
|
|
6178
|
+
* unbounded map growth that a per-event log would cause when a broken key
|
|
6179
|
+
* keeps producing failures.
|
|
6180
|
+
*/
|
|
6181
|
+
class ErrorRateLimiter {
|
|
6182
|
+
constructor() {
|
|
6183
|
+
let throttleMs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1000;
|
|
6184
|
+
let windowMs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 60000;
|
|
6185
|
+
let maxPerWindow = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 5;
|
|
6186
|
+
this.throttleMs = throttleMs;
|
|
6187
|
+
this.windowMs = windowMs;
|
|
6188
|
+
this.maxPerWindow = maxPerWindow;
|
|
6189
|
+
this.lastAt = new Map();
|
|
6190
|
+
this.counts = new Map();
|
|
6191
|
+
}
|
|
6192
|
+
reset() {
|
|
6193
|
+
this.lastAt.clear();
|
|
6194
|
+
this.counts.clear();
|
|
6195
|
+
}
|
|
6196
|
+
countFor(key) {
|
|
6197
|
+
var _a;
|
|
6198
|
+
return (_a = this.counts.get(key)) !== null && _a !== void 0 ? _a : 0;
|
|
6199
|
+
}
|
|
6200
|
+
/**
|
|
6201
|
+
* Returns true if the caller should emit for this key. Invokes `onSuppress`
|
|
6202
|
+
* exactly once per window when the per-window limit is first crossed.
|
|
6203
|
+
*/
|
|
6204
|
+
shouldEmit(key, onSuppress) {
|
|
6205
|
+
var _a, _b;
|
|
6206
|
+
const now = Date.now();
|
|
6207
|
+
const last = (_a = this.lastAt.get(key)) !== null && _a !== void 0 ? _a : 0;
|
|
6208
|
+
const count = (_b = this.counts.get(key)) !== null && _b !== void 0 ? _b : 0;
|
|
6209
|
+
if (now - last > this.windowMs) {
|
|
6210
|
+
this.counts.set(key, 0);
|
|
6211
|
+
this.lastAt.set(key, now);
|
|
6212
|
+
return true;
|
|
6213
|
+
}
|
|
6214
|
+
if (now - last < this.throttleMs) return false;
|
|
6215
|
+
if (count >= this.maxPerWindow) {
|
|
6216
|
+
if (count === this.maxPerWindow) {
|
|
6217
|
+
onSuppress === null || onSuppress === void 0 ? void 0 : onSuppress();
|
|
6218
|
+
this.counts.set(key, count + 1);
|
|
6219
|
+
}
|
|
6220
|
+
return false;
|
|
6221
|
+
}
|
|
6222
|
+
this.lastAt.set(key, now);
|
|
6223
|
+
this.counts.set(key, count + 1);
|
|
6224
|
+
return true;
|
|
6225
|
+
}
|
|
6226
|
+
}
|
|
6227
|
+
|
|
6228
|
+
function hasFrameMetadataPublishOptions(options) {
|
|
6229
|
+
return !!((options === null || options === void 0 ? void 0 : options.timestamp) || (options === null || options === void 0 ? void 0 : options.frameId));
|
|
6230
|
+
}
|
|
6231
|
+
|
|
6232
|
+
const PACKET_TRAILER_MAGIC = Uint8Array.from(['L'.charCodeAt(0), 'K'.charCodeAt(0), 'T'.charCodeAt(0), 'S'.charCodeAt(0)]);
|
|
6233
|
+
const PACKET_TRAILER_TIMESTAMP_TAG = 0x01;
|
|
6234
|
+
const PACKET_TRAILER_FRAME_ID_TAG = 0x02;
|
|
6235
|
+
const PACKET_TRAILER_USER_DATA_TAG = 0x03;
|
|
6236
|
+
const PACKET_TRAILER_ENVELOPE_SIZE = 5;
|
|
6237
|
+
const TIMESTAMP_TLV_SIZE = 10;
|
|
6169
6238
|
const FRAME_ID_TLV_SIZE = 6;
|
|
6170
6239
|
function appendPacketTrailer(data, userTimestamp, frameId) {
|
|
6171
6240
|
const hasTimestamp = userTimestamp !== BigInt(0);
|
|
@@ -6686,14 +6755,9 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
|
6686
6755
|
*/
|
|
6687
6756
|
this.hasFrameMetadata = false;
|
|
6688
6757
|
this.frameMetadataFrameId = 0;
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
this.lastErrorTimestamp = new Map();
|
|
6693
|
-
this.errorCounts = new Map();
|
|
6694
|
-
this.ERROR_THROTTLE_MS = 1000; // Emit error at most once per second
|
|
6695
|
-
this.MAX_ERRORS_PER_MINUTE = 5; // Maximum errors to emit per minute per key
|
|
6696
|
-
this.ERROR_WINDOW_MS = 60000; // 1 minute window
|
|
6758
|
+
this.errorLimiter = new ErrorRateLimiter();
|
|
6759
|
+
/** grace period for a teardown or a resubscribe to land before we report a stalled track */
|
|
6760
|
+
this.UNDECRYPTED_TRACK_GRACE_MS = 2000;
|
|
6697
6761
|
/**
|
|
6698
6762
|
* Tracks (participant, trackId, payloadType) tuples for which we've already logged a NALU
|
|
6699
6763
|
* fallback, so a persistent bad state doesn't flood the console (Firefox doesn't filter debug).
|
|
@@ -6738,12 +6802,15 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
|
6738
6802
|
}
|
|
6739
6803
|
unsetParticipant() {
|
|
6740
6804
|
workerLogger.debug('unsetting participant', this.logContext);
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6805
|
+
// NOTE: deliberately does not clear `currentTransform`. Nothing here cancels
|
|
6806
|
+
// the pipe, so the transform really is still running and clearing it would
|
|
6807
|
+
// desync our bookkeeping from reality -- which previously let a later
|
|
6808
|
+
// resubscribe skip setup while frames kept flowing through a cryptor with no
|
|
6809
|
+
// participant assigned.
|
|
6810
|
+
clearTimeout(this.undecryptedTrackTimeout);
|
|
6811
|
+
this.undecryptedTrackTimeout = undefined;
|
|
6744
6812
|
this.participantIdentity = undefined;
|
|
6745
|
-
this.
|
|
6746
|
-
this.errorCounts = new Map();
|
|
6813
|
+
this.errorLimiter.reset();
|
|
6747
6814
|
}
|
|
6748
6815
|
isEnabled() {
|
|
6749
6816
|
if (this.participantIdentity) {
|
|
@@ -6758,6 +6825,72 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
|
6758
6825
|
getTrackId() {
|
|
6759
6826
|
return this.trackId;
|
|
6760
6827
|
}
|
|
6828
|
+
/**
|
|
6829
|
+
* Re-point this cryptor at a new trackId, keeping its (already transferred)
|
|
6830
|
+
* encoded streams. Used when a transceiver is reused for a new track.
|
|
6831
|
+
*/
|
|
6832
|
+
setTrackId(trackId) {
|
|
6833
|
+
if (this.trackId === trackId) {
|
|
6834
|
+
return;
|
|
6835
|
+
}
|
|
6836
|
+
workerLogger.debug('re-pointing cryptor at new trackId', Object.assign(Object.assign({}, this.logContext), {
|
|
6837
|
+
newTrackId: trackId
|
|
6838
|
+
}));
|
|
6839
|
+
this.trackId = trackId;
|
|
6840
|
+
}
|
|
6841
|
+
hasActiveTransform() {
|
|
6842
|
+
return !!this.currentTransform;
|
|
6843
|
+
}
|
|
6844
|
+
/**
|
|
6845
|
+
* A track that is subscribed and known to be encrypted, but has no transform to
|
|
6846
|
+
* decrypt it, will never produce a decodable frame again. That state used to be
|
|
6847
|
+
* completely silent (a black tile and endless PLIs), so report it.
|
|
6848
|
+
*
|
|
6849
|
+
* Deferred, because on teardown the encoded streams routinely close before the
|
|
6850
|
+
* 'removeTransform' message arrives -- checking immediately would cry wolf on
|
|
6851
|
+
* every unsubscribe.
|
|
6852
|
+
*
|
|
6853
|
+
* Only meaningful while decoding: a sender's pipe closing on unpublish is
|
|
6854
|
+
* routine and has no 'removeTransform' equivalent to quiet it down.
|
|
6855
|
+
*/
|
|
6856
|
+
scheduleUndecryptedTrackWatchdog(operation) {
|
|
6857
|
+
clearTimeout(this.undecryptedTrackTimeout);
|
|
6858
|
+
this.undecryptedTrackTimeout = undefined;
|
|
6859
|
+
if (operation !== 'decode') {
|
|
6860
|
+
return;
|
|
6861
|
+
}
|
|
6862
|
+
this.undecryptedTrackTimeout = setTimeout(() => {
|
|
6863
|
+
if (this.currentTransform || !this.isEnabled()) {
|
|
6864
|
+
return;
|
|
6865
|
+
}
|
|
6866
|
+
workerLogger.warn('encrypted track has no active decrypt transform', this.logContext);
|
|
6867
|
+
this.emitThrottledError(new CryptorError("no active decrypt transform for encrypted track ".concat(this.trackId), CryptorErrorReason.InternalError, this.participantIdentity));
|
|
6868
|
+
}, this.UNDECRYPTED_TRACK_GRACE_MS);
|
|
6869
|
+
}
|
|
6870
|
+
/**
|
|
6871
|
+
* Re-establish the transform if it is gone while we still own the encoded
|
|
6872
|
+
* streams, e.g. after a pipe died on its own (the swallowed
|
|
6873
|
+
* 'Destination stream closed') or when a reused transceiver never got a new
|
|
6874
|
+
* pipeline. Without this the frames pile up in a readable nobody reads and the
|
|
6875
|
+
* decoder starves, which shows up as a permanently black tile.
|
|
6876
|
+
*/
|
|
6877
|
+
ensureTransform() {
|
|
6878
|
+
if (this.currentTransform) {
|
|
6879
|
+
return true;
|
|
6880
|
+
}
|
|
6881
|
+
if (!this.retainedStreams || this.trackId === undefined) {
|
|
6882
|
+
workerLogger.warn('no streams retained, cannot re-establish transform', this.logContext);
|
|
6883
|
+
return false;
|
|
6884
|
+
}
|
|
6885
|
+
const _this$retainedStreams = this.retainedStreams,
|
|
6886
|
+
readable = _this$retainedStreams.readable,
|
|
6887
|
+
writable = _this$retainedStreams.writable,
|
|
6888
|
+
operation = _this$retainedStreams.operation;
|
|
6889
|
+
workerLogger.info('re-establishing transform', Object.assign(Object.assign({}, this.logContext), {
|
|
6890
|
+
operation
|
|
6891
|
+
}));
|
|
6892
|
+
return this.setupTransform(operation, readable, writable, this.trackId);
|
|
6893
|
+
}
|
|
6761
6894
|
/**
|
|
6762
6895
|
* Update the video codec used by the mediaStreamTrack
|
|
6763
6896
|
* @param codec
|
|
@@ -6784,7 +6917,8 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
|
6784
6917
|
this.frameMetadataOpts = frameMetadata;
|
|
6785
6918
|
this.frameMetadataFrameId = 0;
|
|
6786
6919
|
}
|
|
6787
|
-
setupTransform(operation, readable, writable, trackId,
|
|
6920
|
+
setupTransform(operation, readable, writable, trackId, codec, frameMetadata) {
|
|
6921
|
+
var _a;
|
|
6788
6922
|
if (codec) {
|
|
6789
6923
|
workerLogger.info('setting codec on cryptor to', {
|
|
6790
6924
|
codec
|
|
@@ -6798,18 +6932,17 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
|
6798
6932
|
operation,
|
|
6799
6933
|
passedTrackId: trackId,
|
|
6800
6934
|
codec,
|
|
6801
|
-
isReuse,
|
|
6802
6935
|
hasCurrentTransform: !!this.currentTransform
|
|
6803
6936
|
}, this.logContext));
|
|
6804
|
-
// Always update trackId, even on reuse
|
|
6805
6937
|
this.trackId = trackId;
|
|
6806
|
-
//
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
}
|
|
6938
|
+
// Retain the streams so we can rebuild the pipe later even if this cryptor
|
|
6939
|
+
// gets detached from its participant in between (see `ensureTransform`).
|
|
6940
|
+
this.retainedStreams = {
|
|
6941
|
+
readable,
|
|
6942
|
+
writable,
|
|
6943
|
+
operation
|
|
6944
|
+
};
|
|
6945
|
+
clearTimeout(this.undecryptedTrackTimeout);
|
|
6813
6946
|
const symbol = Symbol('transform');
|
|
6814
6947
|
const transformFn = operation === 'encode' ? this.encodeFunction : this.decodeFunction;
|
|
6815
6948
|
const transformStream = new TransformStream({
|
|
@@ -6823,26 +6956,43 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
|
6823
6956
|
trackId,
|
|
6824
6957
|
symbol
|
|
6825
6958
|
};
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
}
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
|
|
6959
|
+
// pipeThrough/pipeTo throw synchronously on an already locked stream, which
|
|
6960
|
+
// would bypass the .catch below and leave us with no transform at all.
|
|
6961
|
+
try {
|
|
6962
|
+
readable.pipeThrough(transformStream).pipeTo(writable).catch(e => {
|
|
6963
|
+
if (e instanceof TypeError && e.message === 'Destination stream closed') {
|
|
6964
|
+
// this can happen when subscriptions happen in quick successions, but doesn't influence functionality
|
|
6965
|
+
workerLogger.debug('destination stream closed');
|
|
6966
|
+
} else {
|
|
6967
|
+
workerLogger.warn('transform error', Object.assign({
|
|
6968
|
+
error: e
|
|
6969
|
+
}, this.logContext));
|
|
6970
|
+
this.emit(CryptorEvent.Error, e instanceof CryptorError ? e : new CryptorError(e.message, undefined, this.participantIdentity));
|
|
6971
|
+
}
|
|
6972
|
+
}).finally(() => {
|
|
6973
|
+
var _a;
|
|
6974
|
+
// Only clear currentTransform if it's still the same one we started
|
|
6975
|
+
if (((_a = this.currentTransform) === null || _a === void 0 ? void 0 : _a.symbol) === symbol) {
|
|
6976
|
+
workerLogger.debug('transform completed', Object.assign(Object.assign({}, this.logContext), {
|
|
6977
|
+
trackId
|
|
6978
|
+
}));
|
|
6979
|
+
this.currentTransform = undefined;
|
|
6980
|
+
}
|
|
6981
|
+
// A pipe ending while the track is still assigned to a participant that
|
|
6982
|
+
// publishes encrypted media means we have silently stopped decrypting.
|
|
6983
|
+
this.scheduleUndecryptedTrackWatchdog(operation);
|
|
6984
|
+
});
|
|
6985
|
+
} catch (e) {
|
|
6839
6986
|
if (((_a = this.currentTransform) === null || _a === void 0 ? void 0 : _a.symbol) === symbol) {
|
|
6840
|
-
workerLogger.debug('transform completed', Object.assign(Object.assign({}, this.logContext), {
|
|
6841
|
-
trackId
|
|
6842
|
-
}));
|
|
6843
6987
|
this.currentTransform = undefined;
|
|
6844
6988
|
}
|
|
6845
|
-
|
|
6989
|
+
workerLogger.error('failed to set up transform', Object.assign({
|
|
6990
|
+
error: e
|
|
6991
|
+
}, this.logContext));
|
|
6992
|
+
this.emit(CryptorEvent.Error, e instanceof CryptorError ? e : new CryptorError(e.message, CryptorErrorReason.InternalError, this.participantIdentity));
|
|
6993
|
+
return false;
|
|
6994
|
+
}
|
|
6995
|
+
return true;
|
|
6846
6996
|
}
|
|
6847
6997
|
setSifTrailer(trailer) {
|
|
6848
6998
|
workerLogger.debug('setting SIF trailer', Object.assign(Object.assign({}, this.logContext), {
|
|
@@ -6850,58 +7000,21 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
|
6850
7000
|
}));
|
|
6851
7001
|
this.sifTrailer = trailer;
|
|
6852
7002
|
}
|
|
6853
|
-
/**
|
|
6854
|
-
* Checks if we should emit an error based on throttling rules to prevent memory leaks
|
|
6855
|
-
* @param errorKey - unique key identifying the error context
|
|
6856
|
-
* @returns true if the error should be emitted, false otherwise
|
|
6857
|
-
*/
|
|
6858
|
-
shouldEmitError(errorKey) {
|
|
6859
|
-
var _a, _b;
|
|
6860
|
-
const now = Date.now();
|
|
6861
|
-
const lastErrorTime = (_a = this.lastErrorTimestamp.get(errorKey)) !== null && _a !== void 0 ? _a : 0;
|
|
6862
|
-
const errorCount = (_b = this.errorCounts.get(errorKey)) !== null && _b !== void 0 ? _b : 0;
|
|
6863
|
-
// Reset count if we're in a new time window
|
|
6864
|
-
if (now - lastErrorTime > this.ERROR_WINDOW_MS) {
|
|
6865
|
-
this.errorCounts.set(errorKey, 0);
|
|
6866
|
-
this.lastErrorTimestamp.set(errorKey, now);
|
|
6867
|
-
return true;
|
|
6868
|
-
}
|
|
6869
|
-
// Check if we've exceeded the throttle time
|
|
6870
|
-
if (now - lastErrorTime < this.ERROR_THROTTLE_MS) {
|
|
6871
|
-
return false;
|
|
6872
|
-
}
|
|
6873
|
-
// Check if we've exceeded the max errors per window
|
|
6874
|
-
if (errorCount >= this.MAX_ERRORS_PER_MINUTE) {
|
|
6875
|
-
// Only log a warning once when hitting the limit
|
|
6876
|
-
if (errorCount === this.MAX_ERRORS_PER_MINUTE) {
|
|
6877
|
-
workerLogger.warn("Suppressing further decryption errors for ".concat(this.participantIdentity), Object.assign(Object.assign({}, this.logContext), {
|
|
6878
|
-
errorKey
|
|
6879
|
-
}));
|
|
6880
|
-
this.errorCounts.set(errorKey, errorCount + 1);
|
|
6881
|
-
}
|
|
6882
|
-
return false;
|
|
6883
|
-
}
|
|
6884
|
-
// Update tracking
|
|
6885
|
-
this.lastErrorTimestamp.set(errorKey, now);
|
|
6886
|
-
this.errorCounts.set(errorKey, errorCount + 1);
|
|
6887
|
-
return true;
|
|
6888
|
-
}
|
|
6889
|
-
/**
|
|
6890
|
-
* Emits a throttled error to prevent memory leaks from repeated decryption failures
|
|
6891
|
-
* @param error - the CryptorError to emit
|
|
6892
|
-
*/
|
|
6893
7003
|
emitThrottledError(error) {
|
|
6894
|
-
var _a;
|
|
6895
7004
|
const errorKey = "".concat(this.participantIdentity, "-").concat(error.reason, "-decrypt");
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
7005
|
+
const emit = this.errorLimiter.shouldEmit(errorKey, () => {
|
|
7006
|
+
workerLogger.warn("Suppressing further decryption errors for ".concat(this.participantIdentity), Object.assign(Object.assign({}, this.logContext), {
|
|
7007
|
+
errorKey
|
|
7008
|
+
}));
|
|
7009
|
+
});
|
|
7010
|
+
if (!emit) return;
|
|
7011
|
+
const count = this.errorLimiter.countFor(errorKey);
|
|
7012
|
+
if (count > 1) {
|
|
7013
|
+
workerLogger.debug("Decryption error (".concat(count, " occurrences in window)"), Object.assign(Object.assign({}, this.logContext), {
|
|
7014
|
+
reason: CryptorErrorReason[error.reason]
|
|
7015
|
+
}));
|
|
6904
7016
|
}
|
|
7017
|
+
this.emit(CryptorEvent.Error, error);
|
|
6905
7018
|
}
|
|
6906
7019
|
/**
|
|
6907
7020
|
* Function that will be injected in a stream and will encrypt the given encoded frames.
|
|
@@ -6980,7 +7093,9 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
|
6980
7093
|
return controller.enqueue(encodedFrame);
|
|
6981
7094
|
} catch (e) {
|
|
6982
7095
|
// TODO: surface this to the app.
|
|
6983
|
-
workerLogger.error(
|
|
7096
|
+
workerLogger.error("error while encrypting", Object.assign(Object.assign({}, this.logContext), {
|
|
7097
|
+
error: e
|
|
7098
|
+
}));
|
|
6984
7099
|
}
|
|
6985
7100
|
} else {
|
|
6986
7101
|
workerLogger.debug('failed to encrypt, emitting error', this.logContext);
|
|
@@ -7023,9 +7138,29 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
|
7023
7138
|
// best-effort: never break the media pipeline if trailer parsing fails
|
|
7024
7139
|
}
|
|
7025
7140
|
}
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7141
|
+
// skip decryption for empty dtx frames
|
|
7142
|
+
if (encodedFrame.data.byteLength === 0) {
|
|
7143
|
+
return controller.enqueue(encodedFrame);
|
|
7144
|
+
}
|
|
7145
|
+
const encryptionEnabled = this.isEnabled();
|
|
7146
|
+
if (encryptionEnabled === undefined) {
|
|
7147
|
+
// Either way we drop: forwarding would hand ciphertext straight to the
|
|
7148
|
+
// decoder, which renders as a black frame and logs nothing.
|
|
7149
|
+
if (this.participantIdentity === undefined) {
|
|
7150
|
+
// The pipe outlived its subscription. Expected on unsubscribe/disconnect:
|
|
7151
|
+
// we deliberately leave the pipe running so a reused transceiver can be
|
|
7152
|
+
// re-pointed at a new track, so in-flight frames still arrive here for a
|
|
7153
|
+
// moment. Not worth reporting -- the watchdog covers a track that stays
|
|
7154
|
+
// subscribed without a transform.
|
|
7155
|
+
workerLogger.debug('dropping frame for unassigned cryptor', this.logContext);
|
|
7156
|
+
return;
|
|
7157
|
+
}
|
|
7158
|
+
// A live subscription whose encryption state we were never told about.
|
|
7159
|
+
this.emitThrottledError(new CryptorError("encryption state unknown for track ".concat(this.trackId, ", dropping frame"), CryptorErrorReason.InternalError, this.participantIdentity));
|
|
7160
|
+
return;
|
|
7161
|
+
}
|
|
7162
|
+
if (!encryptionEnabled) {
|
|
7163
|
+
// participant is known to publish unencrypted media
|
|
7029
7164
|
return controller.enqueue(encodedFrame);
|
|
7030
7165
|
}
|
|
7031
7166
|
if (isFrameServerInjected(encodedFrame.data, this.sifTrailer)) {
|
|
@@ -7159,7 +7294,7 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
|
7159
7294
|
throw new CryptorError("valid key missing for participant ".concat(_this.participantIdentity), CryptorErrorReason.InvalidKey, _this.participantIdentity);
|
|
7160
7295
|
}
|
|
7161
7296
|
} else {
|
|
7162
|
-
throw new CryptorError("Decryption failed: ".concat(error
|
|
7297
|
+
throw new CryptorError("Decryption failed: ".concat(getErrorDescription(error, 'decryption')), CryptorErrorReason.InvalidKey, _this.participantIdentity);
|
|
7163
7298
|
}
|
|
7164
7299
|
}
|
|
7165
7300
|
}();
|
|
@@ -7497,7 +7632,22 @@ let useSharedKey = false;
|
|
|
7497
7632
|
let sifTrailer;
|
|
7498
7633
|
let keyProviderOptions = KEY_PROVIDER_DEFAULTS;
|
|
7499
7634
|
let rtpMap = new Map();
|
|
7635
|
+
const dataDecryptErrorLimiter = new ErrorRateLimiter();
|
|
7500
7636
|
workerLogger.setDefaultLevel('info');
|
|
7637
|
+
// Forward worker log calls to the main thread so they reach any
|
|
7638
|
+
// setLogExtension consumer installed there. The main-thread workerLogger
|
|
7639
|
+
// re-emits them, which invokes both the console and the extension.
|
|
7640
|
+
workerLogger.methodFactory = methodName => (msg, context) => {
|
|
7641
|
+
postMessage({
|
|
7642
|
+
kind: 'log',
|
|
7643
|
+
data: {
|
|
7644
|
+
level: methodName,
|
|
7645
|
+
msg,
|
|
7646
|
+
context
|
|
7647
|
+
}
|
|
7648
|
+
});
|
|
7649
|
+
};
|
|
7650
|
+
workerLogger.setLevel(workerLogger.getLevel());
|
|
7501
7651
|
onmessage = ev => {
|
|
7502
7652
|
messageQueue.run(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
7503
7653
|
const _ev$data = ev.data,
|
|
@@ -7506,7 +7656,7 @@ onmessage = ev => {
|
|
|
7506
7656
|
switch (kind) {
|
|
7507
7657
|
case 'init':
|
|
7508
7658
|
workerLogger.setLevel(data.loglevel);
|
|
7509
|
-
workerLogger.info('worker initialized');
|
|
7659
|
+
workerLogger.info('e2ee worker initialized');
|
|
7510
7660
|
keyProviderOptions = data.keyProviderOptions;
|
|
7511
7661
|
useSharedKey = !!data.keyProviderOptions.sharedKey;
|
|
7512
7662
|
// acknowledge init successful
|
|
@@ -7518,6 +7668,9 @@ onmessage = ev => {
|
|
|
7518
7668
|
};
|
|
7519
7669
|
postMessage(ackMsg);
|
|
7520
7670
|
break;
|
|
7671
|
+
case 'setLogLevel':
|
|
7672
|
+
workerLogger.setLevel(data.level);
|
|
7673
|
+
break;
|
|
7521
7674
|
case 'enable':
|
|
7522
7675
|
setEncryptionEnabled(data.enabled, data.participantIdentity);
|
|
7523
7676
|
workerLogger.info("updated e2ee enabled status for ".concat(data.participantIdentity, " to ").concat(data.enabled));
|
|
@@ -7527,23 +7680,18 @@ onmessage = ev => {
|
|
|
7527
7680
|
case 'decode':
|
|
7528
7681
|
let cryptor = getTrackCryptor(data.participantIdentity, data.trackId);
|
|
7529
7682
|
cryptor.setHasFrameMetadata(data.hasPacketTrailer);
|
|
7530
|
-
cryptor.setupTransform(kind, data.readableStream, data.writableStream, data.trackId, data.
|
|
7683
|
+
cryptor.setupTransform(kind, data.readableStream, data.writableStream, data.trackId, data.codec);
|
|
7531
7684
|
break;
|
|
7532
7685
|
case 'encode':
|
|
7533
7686
|
let pubCryptor = getTrackCryptor(data.participantIdentity, data.trackId);
|
|
7534
7687
|
pubCryptor.setHasFrameMetadata(data.hasPacketTrailer);
|
|
7535
|
-
pubCryptor.setupTransform(kind, data.readableStream, data.writableStream, data.trackId, data.
|
|
7688
|
+
pubCryptor.setupTransform(kind, data.readableStream, data.writableStream, data.trackId, data.codec, data.packetTrailer);
|
|
7536
7689
|
break;
|
|
7537
7690
|
case 'encryptDataRequest':
|
|
7538
7691
|
const _yield$DataCryptor$en = yield DataCryptor.encrypt(data.payload, getParticipantKeyHandler(data.participantIdentity)),
|
|
7539
7692
|
encryptedPayload = _yield$DataCryptor$en.payload,
|
|
7540
7693
|
iv = _yield$DataCryptor$en.iv,
|
|
7541
7694
|
keyIndex = _yield$DataCryptor$en.keyIndex;
|
|
7542
|
-
console.log('encrypted payload', {
|
|
7543
|
-
original: data.payload,
|
|
7544
|
-
encrypted: encryptedPayload,
|
|
7545
|
-
iv
|
|
7546
|
-
});
|
|
7547
7695
|
postMessage({
|
|
7548
7696
|
kind: 'encryptDataResponse',
|
|
7549
7697
|
data: {
|
|
@@ -7566,12 +7714,22 @@ onmessage = ev => {
|
|
|
7566
7714
|
}
|
|
7567
7715
|
});
|
|
7568
7716
|
} catch (error) {
|
|
7569
|
-
// Send error back to main thread with uuid so it can reject the corresponding promise
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7717
|
+
// Send error back to main thread with uuid so it can reject the corresponding promise.
|
|
7718
|
+
// The error response must always be posted so the awaiting future resolves; only the
|
|
7719
|
+
// log is throttled to avoid flooding when a broken key keeps producing failures.
|
|
7720
|
+
const errorKey = "".concat(data.participantIdentity, "-datadecrypt");
|
|
7721
|
+
const shouldLog = dataDecryptErrorLimiter.shouldEmit(errorKey, () => {
|
|
7722
|
+
workerLogger.warn("Suppressing further data decryption errors for ".concat(data.participantIdentity), {
|
|
7723
|
+
errorKey
|
|
7724
|
+
});
|
|
7574
7725
|
});
|
|
7726
|
+
if (shouldLog) {
|
|
7727
|
+
workerLogger.error('DataCryptor decryption failed', {
|
|
7728
|
+
error,
|
|
7729
|
+
participantIdentity: data.participantIdentity,
|
|
7730
|
+
uuid: data.uuid
|
|
7731
|
+
});
|
|
7732
|
+
}
|
|
7575
7733
|
postMessage({
|
|
7576
7734
|
kind: 'error',
|
|
7577
7735
|
data: {
|
|
@@ -7595,15 +7753,28 @@ onmessage = ev => {
|
|
|
7595
7753
|
unsetCryptorParticipant(data.trackId, data.participantIdentity);
|
|
7596
7754
|
break;
|
|
7597
7755
|
case 'updateCodec':
|
|
7598
|
-
const trackCryptor = getTrackCryptor(data.participantIdentity, data.trackId);
|
|
7599
|
-
|
|
7756
|
+
const trackCryptor = getTrackCryptor(data.participantIdentity, data.trackId, data.previousTrackId);
|
|
7757
|
+
if (data.codec) {
|
|
7758
|
+
trackCryptor.setVideoCodec(data.codec);
|
|
7759
|
+
}
|
|
7600
7760
|
trackCryptor.setHasFrameMetadata(data.hasPacketTrailer);
|
|
7601
7761
|
workerLogger.info('updated codec', {
|
|
7602
7762
|
participantIdentity: data.participantIdentity,
|
|
7603
7763
|
trackId: data.trackId,
|
|
7764
|
+
previousTrackId: data.previousTrackId,
|
|
7604
7765
|
codec: data.codec,
|
|
7605
7766
|
hasPacketTrailer: data.hasPacketTrailer
|
|
7606
7767
|
});
|
|
7768
|
+
// Sent on transceiver reuse, where the main thread cannot hand us a new
|
|
7769
|
+
// pipeline. If the previous one died in the meantime we have to rebuild it
|
|
7770
|
+
// here or no frame ever gets decrypted again.
|
|
7771
|
+
if (data.previousTrackId !== undefined && !trackCryptor.ensureTransform()) {
|
|
7772
|
+
workerLogger.error('could not re-establish transform for reused receiver', {
|
|
7773
|
+
participantIdentity: data.participantIdentity,
|
|
7774
|
+
trackId: data.trackId,
|
|
7775
|
+
previousTrackId: data.previousTrackId
|
|
7776
|
+
});
|
|
7777
|
+
}
|
|
7607
7778
|
break;
|
|
7608
7779
|
case 'setRTPMap':
|
|
7609
7780
|
// this is only used for the local participant
|
|
@@ -7638,8 +7809,24 @@ function handleRatchetRequest(data) {
|
|
|
7638
7809
|
}
|
|
7639
7810
|
});
|
|
7640
7811
|
}
|
|
7641
|
-
function getTrackCryptor(participantIdentity, trackId) {
|
|
7812
|
+
function getTrackCryptor(participantIdentity, trackId, previousTrackId) {
|
|
7642
7813
|
let cryptors = participantCryptors.filter(c => c.getTrackId() === trackId);
|
|
7814
|
+
if (cryptors.length === 0 && previousTrackId !== undefined && previousTrackId !== trackId) {
|
|
7815
|
+
// A reused transceiver carries a new trackId, but the cryptor that owns this
|
|
7816
|
+
// receiver's encoded streams is still keyed on the old one. Re-point it instead
|
|
7817
|
+
// of creating a fresh cryptor that has no pipeline (and leaving the old one
|
|
7818
|
+
// piping frames with no participant assigned).
|
|
7819
|
+
const previous = participantCryptors.filter(c => c.getTrackId() === previousTrackId);
|
|
7820
|
+
if (previous.length > 0) {
|
|
7821
|
+
workerLogger.info('reusing cryptor from previous trackId', {
|
|
7822
|
+
participantIdentity,
|
|
7823
|
+
trackId,
|
|
7824
|
+
previousTrackId
|
|
7825
|
+
});
|
|
7826
|
+
previous[0].setTrackId(trackId);
|
|
7827
|
+
cryptors = previous;
|
|
7828
|
+
}
|
|
7829
|
+
}
|
|
7643
7830
|
if (cryptors.length > 1) {
|
|
7644
7831
|
const debugInfo = cryptors.map(c => {
|
|
7645
7832
|
return {
|
|
@@ -7775,7 +7962,7 @@ if (self.RTCTransformEvent) {
|
|
|
7775
7962
|
trackId,
|
|
7776
7963
|
codec
|
|
7777
7964
|
});
|
|
7778
|
-
cryptor.setupTransform(kind, transformer.readable, transformer.writable, trackId,
|
|
7965
|
+
cryptor.setupTransform(kind, transformer.readable, transformer.writable, trackId, codec, kind === 'encode' ? options.packetTrailer : undefined);
|
|
7779
7966
|
}));
|
|
7780
7967
|
};
|
|
7781
7968
|
}
|