livekit-client 1.13.2 → 1.13.4
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/README.md +2 -2
- 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 +87 -16
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +137 -31
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/e2ee/utils.d.ts +3 -0
- package/dist/src/e2ee/utils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +6 -2
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +5 -3
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +7 -0
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteTrack.d.ts +7 -0
- package/dist/src/room/track/RemoteTrack.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +1 -1
- package/dist/ts4.2/src/e2ee/utils.d.ts +3 -0
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +6 -2
- package/dist/ts4.2/src/room/participant/Participant.d.ts +5 -3
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +7 -0
- package/dist/ts4.2/src/room/track/RemoteTrack.d.ts +7 -0
- package/dist/ts4.2/src/room/utils.d.ts +1 -1
- package/package.json +15 -15
- package/src/e2ee/utils.ts +52 -0
- package/src/e2ee/worker/FrameCryptor.ts +49 -27
- package/src/room/RTCEngine.ts +3 -2
- package/src/room/Room.ts +3 -3
- package/src/room/participant/LocalParticipant.ts +9 -7
- package/src/room/participant/Participant.ts +7 -5
- package/src/room/track/LocalTrack.ts +14 -0
- package/src/room/track/RemoteTrack.ts +14 -0
- package/src/room/track/options.ts +1 -1
- package/src/room/utils.ts +2 -2
@@ -882,6 +882,54 @@ function ratchet(material, salt) {
|
|
882
882
|
return crypto.subtle.deriveBits(algorithmOptions, material, 256);
|
883
883
|
});
|
884
884
|
}
|
885
|
+
function needsRbspUnescaping(frameData) {
|
886
|
+
for (var i = 0; i < frameData.length - 3; i++) {
|
887
|
+
if (frameData[i] == 0 && frameData[i + 1] == 0 && frameData[i + 2] == 3) return true;
|
888
|
+
}
|
889
|
+
return false;
|
890
|
+
}
|
891
|
+
function parseRbsp(stream) {
|
892
|
+
const dataOut = [];
|
893
|
+
var length = stream.length;
|
894
|
+
for (var i = 0; i < stream.length;) {
|
895
|
+
// Be careful about over/underflow here. byte_length_ - 3 can underflow, and
|
896
|
+
// i + 3 can overflow, but byte_length_ - i can't, because i < byte_length_
|
897
|
+
// above, and that expression will produce the number of bytes left in
|
898
|
+
// the stream including the byte at i.
|
899
|
+
if (length - i >= 3 && !stream[i] && !stream[i + 1] && stream[i + 2] == 3) {
|
900
|
+
// Two rbsp bytes.
|
901
|
+
dataOut.push(stream[i++]);
|
902
|
+
dataOut.push(stream[i++]);
|
903
|
+
// Skip the emulation byte.
|
904
|
+
i++;
|
905
|
+
} else {
|
906
|
+
// Single rbsp byte.
|
907
|
+
dataOut.push(stream[i++]);
|
908
|
+
}
|
909
|
+
}
|
910
|
+
return new Uint8Array(dataOut);
|
911
|
+
}
|
912
|
+
const kZerosInStartSequence = 2;
|
913
|
+
const kEmulationByte = 3;
|
914
|
+
function writeRbsp(data_in) {
|
915
|
+
const dataOut = [];
|
916
|
+
var numConsecutiveZeros = 0;
|
917
|
+
for (var i = 0; i < data_in.length; ++i) {
|
918
|
+
var byte = data_in[i];
|
919
|
+
if (byte <= kEmulationByte && numConsecutiveZeros >= kZerosInStartSequence) {
|
920
|
+
// Need to escape.
|
921
|
+
dataOut.push(kEmulationByte);
|
922
|
+
numConsecutiveZeros = 0;
|
923
|
+
}
|
924
|
+
dataOut.push(byte);
|
925
|
+
if (byte == 0) {
|
926
|
+
++numConsecutiveZeros;
|
927
|
+
} else {
|
928
|
+
numConsecutiveZeros = 0;
|
929
|
+
}
|
930
|
+
}
|
931
|
+
return new Uint8Array(dataOut);
|
932
|
+
}
|
885
933
|
|
886
934
|
class SifGuard {
|
887
935
|
constructor() {
|
@@ -1045,8 +1093,9 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1045
1093
|
const keyIndex = this.keys.getCurrentKeyIndex();
|
1046
1094
|
if (encryptionKey) {
|
1047
1095
|
const iv = this.makeIV((_a = encodedFrame.getMetadata().synchronizationSource) !== null && _a !== void 0 ? _a : -1, encodedFrame.timestamp);
|
1096
|
+
let frameInfo = this.getUnencryptedBytes(encodedFrame);
|
1048
1097
|
// Thіs is not encrypted and contains the VP8 payload descriptor or the Opus TOC byte.
|
1049
|
-
const frameHeader = new Uint8Array(encodedFrame.data, 0,
|
1098
|
+
const frameHeader = new Uint8Array(encodedFrame.data, 0, frameInfo.unencryptedBytes);
|
1050
1099
|
// Frame trailer contains the R|IV_LENGTH and key index
|
1051
1100
|
const frameTrailer = new Uint8Array(2);
|
1052
1101
|
frameTrailer[0] = IV_LENGTH;
|
@@ -1063,14 +1112,18 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1063
1112
|
name: ENCRYPTION_ALGORITHM,
|
1064
1113
|
iv,
|
1065
1114
|
additionalData: new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength)
|
1066
|
-
}, encryptionKey, new Uint8Array(encodedFrame.data,
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1115
|
+
}, encryptionKey, new Uint8Array(encodedFrame.data, frameInfo.unencryptedBytes));
|
1116
|
+
let newDataWithoutHeader = new Uint8Array(cipherText.byteLength + iv.byteLength + frameTrailer.byteLength);
|
1117
|
+
newDataWithoutHeader.set(new Uint8Array(cipherText)); // add ciphertext.
|
1118
|
+
newDataWithoutHeader.set(new Uint8Array(iv), cipherText.byteLength); // append IV.
|
1119
|
+
newDataWithoutHeader.set(frameTrailer, cipherText.byteLength + iv.byteLength); // append frame trailer.
|
1120
|
+
if (frameInfo.isH264) {
|
1121
|
+
newDataWithoutHeader = writeRbsp(newDataWithoutHeader);
|
1122
|
+
}
|
1123
|
+
var newData = new Uint8Array(frameHeader.byteLength + newDataWithoutHeader.byteLength);
|
1124
|
+
newData.set(frameHeader);
|
1125
|
+
newData.set(newDataWithoutHeader, frameHeader.byteLength);
|
1126
|
+
encodedFrame.data = newData.buffer;
|
1074
1127
|
return controller.enqueue(encodedFrame);
|
1075
1128
|
} catch (e) {
|
1076
1129
|
// TODO: surface this to the app.
|
@@ -1098,6 +1151,7 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1098
1151
|
if (isFrameServerInjected(encodedFrame.data, this.sifTrailer)) {
|
1099
1152
|
this.sifGuard.recordSif();
|
1100
1153
|
if (this.sifGuard.isSifAllowed()) {
|
1154
|
+
encodedFrame.data = encodedFrame.data.slice(0, encodedFrame.data.byteLength - this.sifTrailer.byteLength);
|
1101
1155
|
return controller.enqueue(encodedFrame);
|
1102
1156
|
} else {
|
1103
1157
|
workerLogger.warn('SIF limit reached, dropping frame');
|
@@ -1149,6 +1203,7 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1149
1203
|
if (!ratchetOpts.encryptionKey && !keySet) {
|
1150
1204
|
throw new TypeError("no encryption key found for decryption of ".concat(this.participantIdentity));
|
1151
1205
|
}
|
1206
|
+
let frameInfo = this.getUnencryptedBytes(encodedFrame);
|
1152
1207
|
// Construct frame trailer. Similar to the frame header described in
|
1153
1208
|
// https://tools.ietf.org/html/draft-omara-sframe-00#section-4.2
|
1154
1209
|
// but we put it at the end.
|
@@ -1157,7 +1212,15 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1157
1212
|
// payload |IV...(length = IV_LENGTH)|R|IV_LENGTH|KID |
|
1158
1213
|
// ---------+-------------------------+-+---------+----
|
1159
1214
|
try {
|
1160
|
-
const frameHeader = new Uint8Array(encodedFrame.data, 0,
|
1215
|
+
const frameHeader = new Uint8Array(encodedFrame.data, 0, frameInfo.unencryptedBytes);
|
1216
|
+
var encryptedData = new Uint8Array(encodedFrame.data, frameHeader.length, encodedFrame.data.byteLength - frameHeader.length);
|
1217
|
+
if (frameInfo.isH264 && needsRbspUnescaping(encryptedData)) {
|
1218
|
+
encryptedData = parseRbsp(encryptedData);
|
1219
|
+
const newUint8 = new Uint8Array(frameHeader.byteLength + encryptedData.byteLength);
|
1220
|
+
newUint8.set(frameHeader);
|
1221
|
+
newUint8.set(encryptedData, frameHeader.byteLength);
|
1222
|
+
encodedFrame.data = newUint8.buffer;
|
1223
|
+
}
|
1161
1224
|
const frameTrailer = new Uint8Array(encodedFrame.data, encodedFrame.data.byteLength - 2, 2);
|
1162
1225
|
const ivLength = frameTrailer[0];
|
1163
1226
|
const iv = new Uint8Array(encodedFrame.data, encodedFrame.data.byteLength - ivLength - frameTrailer.byteLength, ivLength);
|
@@ -1252,26 +1315,32 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1252
1315
|
}
|
1253
1316
|
getUnencryptedBytes(frame) {
|
1254
1317
|
var _a;
|
1318
|
+
var frameInfo = {
|
1319
|
+
unencryptedBytes: 0,
|
1320
|
+
isH264: false
|
1321
|
+
};
|
1255
1322
|
if (isVideoFrame(frame)) {
|
1256
1323
|
let detectedCodec = (_a = this.getVideoCodec(frame)) !== null && _a !== void 0 ? _a : this.videoCodec;
|
1257
1324
|
if (detectedCodec === 'av1' || detectedCodec === 'vp9') {
|
1258
1325
|
throw new Error("".concat(detectedCodec, " is not yet supported for end to end encryption"));
|
1259
1326
|
}
|
1260
1327
|
if (detectedCodec === 'vp8') {
|
1261
|
-
|
1328
|
+
frameInfo.unencryptedBytes = UNENCRYPTED_BYTES[frame.type];
|
1329
|
+
return frameInfo;
|
1262
1330
|
}
|
1263
1331
|
const data = new Uint8Array(frame.data);
|
1264
1332
|
try {
|
1265
1333
|
const naluIndices = findNALUIndices(data);
|
1266
1334
|
// if the detected codec is undefined we test whether it _looks_ like a h264 frame as a best guess
|
1267
|
-
|
1268
|
-
if (isH264) {
|
1335
|
+
frameInfo.isH264 = detectedCodec === 'h264' || naluIndices.some(naluIndex => [NALUType.SLICE_IDR, NALUType.SLICE_NON_IDR].includes(parseNALUType(data[naluIndex])));
|
1336
|
+
if (frameInfo.isH264) {
|
1269
1337
|
for (const index of naluIndices) {
|
1270
1338
|
let type = parseNALUType(data[index]);
|
1271
1339
|
switch (type) {
|
1272
1340
|
case NALUType.SLICE_IDR:
|
1273
1341
|
case NALUType.SLICE_NON_IDR:
|
1274
|
-
|
1342
|
+
frameInfo.unencryptedBytes = index + 2;
|
1343
|
+
return frameInfo;
|
1275
1344
|
default:
|
1276
1345
|
break;
|
1277
1346
|
}
|
@@ -1281,9 +1350,11 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1281
1350
|
} catch (e) {
|
1282
1351
|
// no op, we just continue and fallback to vp8
|
1283
1352
|
}
|
1284
|
-
|
1353
|
+
frameInfo.unencryptedBytes = UNENCRYPTED_BYTES[frame.type];
|
1354
|
+
return frameInfo;
|
1285
1355
|
} else {
|
1286
|
-
|
1356
|
+
frameInfo.unencryptedBytes = UNENCRYPTED_BYTES.audio;
|
1357
|
+
return frameInfo;
|
1287
1358
|
}
|
1288
1359
|
}
|
1289
1360
|
/**
|