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.
Files changed (39) hide show
  1. package/README.md +2 -2
  2. package/dist/livekit-client.e2ee.worker.js +1 -1
  3. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  4. package/dist/livekit-client.e2ee.worker.mjs +87 -16
  5. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  6. package/dist/livekit-client.esm.mjs +137 -31
  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/e2ee/utils.d.ts +3 -0
  11. package/dist/src/e2ee/utils.d.ts.map +1 -1
  12. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  13. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  14. package/dist/src/room/participant/LocalParticipant.d.ts +6 -2
  15. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  16. package/dist/src/room/participant/Participant.d.ts +5 -3
  17. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  18. package/dist/src/room/track/LocalTrack.d.ts +7 -0
  19. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  20. package/dist/src/room/track/RemoteTrack.d.ts +7 -0
  21. package/dist/src/room/track/RemoteTrack.d.ts.map +1 -1
  22. package/dist/src/room/utils.d.ts +1 -1
  23. package/dist/ts4.2/src/e2ee/utils.d.ts +3 -0
  24. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +6 -2
  25. package/dist/ts4.2/src/room/participant/Participant.d.ts +5 -3
  26. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +7 -0
  27. package/dist/ts4.2/src/room/track/RemoteTrack.d.ts +7 -0
  28. package/dist/ts4.2/src/room/utils.d.ts +1 -1
  29. package/package.json +15 -15
  30. package/src/e2ee/utils.ts +52 -0
  31. package/src/e2ee/worker/FrameCryptor.ts +49 -27
  32. package/src/room/RTCEngine.ts +3 -2
  33. package/src/room/Room.ts +3 -3
  34. package/src/room/participant/LocalParticipant.ts +9 -7
  35. package/src/room/participant/Participant.ts +7 -5
  36. package/src/room/track/LocalTrack.ts +14 -0
  37. package/src/room/track/RemoteTrack.ts +14 -0
  38. package/src/room/track/options.ts +1 -1
  39. 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, this.getUnencryptedBytes(encodedFrame));
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, this.getUnencryptedBytes(encodedFrame)));
1067
- const newData = new ArrayBuffer(frameHeader.byteLength + cipherText.byteLength + iv.byteLength + frameTrailer.byteLength);
1068
- const newUint8 = new Uint8Array(newData);
1069
- newUint8.set(frameHeader); // copy first bytes.
1070
- newUint8.set(new Uint8Array(cipherText), frameHeader.byteLength); // add ciphertext.
1071
- newUint8.set(new Uint8Array(iv), frameHeader.byteLength + cipherText.byteLength); // append IV.
1072
- newUint8.set(frameTrailer, frameHeader.byteLength + cipherText.byteLength + iv.byteLength); // append frame trailer.
1073
- encodedFrame.data = newData;
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, this.getUnencryptedBytes(encodedFrame));
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
- return UNENCRYPTED_BYTES[frame.type];
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
- const isH264 = detectedCodec === 'h264' || naluIndices.some(naluIndex => [NALUType.SLICE_IDR, NALUType.SLICE_NON_IDR].includes(parseNALUType(data[naluIndex])));
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
- return index + 2;
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
- return UNENCRYPTED_BYTES[frame.type];
1353
+ frameInfo.unencryptedBytes = UNENCRYPTED_BYTES[frame.type];
1354
+ return frameInfo;
1285
1355
  } else {
1286
- return UNENCRYPTED_BYTES.audio;
1356
+ frameInfo.unencryptedBytes = UNENCRYPTED_BYTES.audio;
1357
+ return frameInfo;
1287
1358
  }
1288
1359
  }
1289
1360
  /**