fix(eslint): apply '@stylistic/semi' rule

Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
This commit is contained in:
Maksim Sukharev
2025-05-23 12:28:57 +02:00
parent ccfda0f407
commit 1b3757bfac
6 changed files with 138 additions and 139 deletions

View File

@ -20,7 +20,6 @@ export default [
'@stylistic/implicit-arrow-linebreak': 'off', // weird formatting
'@stylistic/max-statements-per-line': 'off', // non-fixable
'@stylistic/member-delimiter-style': 'off', // removes commas from types
'@stylistic/semi': 'off', // changes e2ee files
'@typescript-eslint/no-unused-expressions': 'off', // non-fixable
'@typescript-eslint/no-unused-vars': 'off', // non-fixable
'@typescript-eslint/no-use-before-define': 'off', // non-fixable

View File

@ -19,23 +19,23 @@ export default class Deferred {
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = (...args) => {
this.clearRejectTimeout();
resolve(...args);
};
this.clearRejectTimeout()
resolve(...args)
}
this.reject = (...args) => {
this.clearRejectTimeout();
reject(...args);
};
});
this.then = this.promise.then.bind(this.promise);
this.catch = this.promise.catch.bind(this.promise);
this.clearRejectTimeout()
reject(...args)
}
})
this.then = this.promise.then.bind(this.promise)
this.catch = this.promise.catch.bind(this.promise)
}
/**
* Clears the reject timeout.
*/
clearRejectTimeout() {
clearTimeout(this._timeout);
clearTimeout(this._timeout)
}
/**
@ -43,7 +43,7 @@ export default class Deferred {
*/
setRejectTimeout(ms) {
this._timeout = setTimeout(() => {
this.reject(new Error('timeout'));
}, ms);
this.reject(new Error('timeout'))
}, ms)
}
}

View File

@ -12,7 +12,7 @@ import Worker from './JitsiEncryptionWorker.worker.js'
// Flag to set on senders / receivers to avoid setting up the encryption transform
// more than once.
const kJitsiE2EE = Symbol('kJitsiE2EE');
const kJitsiE2EE = Symbol('kJitsiE2EE')
/**
* Context encapsulating the cryptography bits required for E2EE.
@ -32,14 +32,14 @@ export default class E2EEcontext {
* @param {boolean} [options.sharedKey] - whether there is a uniques key shared amoung all participants.
*/
constructor({ sharedKey } = {}) {
this._worker = new Worker();
this._worker = new Worker()
this._worker.onerror = (e) => console.error(e);
this._worker.onerror = (e) => console.error(e)
this._worker.postMessage({
operation: 'initialize',
sharedKey,
});
})
}
/**
@ -52,7 +52,7 @@ export default class E2EEcontext {
this._worker.postMessage({
operation: 'cleanup',
participantId,
});
})
}
/**
@ -62,7 +62,7 @@ export default class E2EEcontext {
cleanupAll() {
this._worker.postMessage({
operation: 'cleanupAll',
});
})
}
/**
@ -75,26 +75,26 @@ export default class E2EEcontext {
*/
handleReceiver(receiver, kind, participantId) {
if (receiver[kJitsiE2EE]) {
return;
return
}
receiver[kJitsiE2EE] = true;
receiver[kJitsiE2EE] = true
if (window.RTCRtpScriptTransform) {
const options = {
operation: 'decode',
participantId,
};
}
receiver.transform = new RTCRtpScriptTransform(this._worker, options);
receiver.transform = new RTCRtpScriptTransform(this._worker, options)
} else {
const receiverStreams = receiver.createEncodedStreams();
const receiverStreams = receiver.createEncodedStreams()
this._worker.postMessage({
operation: 'decode',
readableStream: receiverStreams.readable,
writableStream: receiverStreams.writable,
participantId,
}, [receiverStreams.readable, receiverStreams.writable]);
}, [receiverStreams.readable, receiverStreams.writable])
}
}
@ -108,26 +108,26 @@ export default class E2EEcontext {
*/
handleSender(sender, kind, participantId) {
if (sender[kJitsiE2EE]) {
return;
return
}
sender[kJitsiE2EE] = true;
sender[kJitsiE2EE] = true
if (window.RTCRtpScriptTransform) {
const options = {
operation: 'encode',
participantId,
};
}
sender.transform = new RTCRtpScriptTransform(this._worker, options);
sender.transform = new RTCRtpScriptTransform(this._worker, options)
} else {
const senderStreams = sender.createEncodedStreams();
const senderStreams = sender.createEncodedStreams()
this._worker.postMessage({
operation: 'encode',
readableStream: senderStreams.readable,
writableStream: senderStreams.writable,
participantId,
}, [senderStreams.readable, senderStreams.writable]);
}, [senderStreams.readable, senderStreams.writable])
}
}
@ -144,6 +144,6 @@ export default class E2EEcontext {
key,
keyIndex,
participantId,
});
})
}
}

View File

@ -9,11 +9,11 @@
// Worker for E2EE/Insertable streams.
import { Context } from './JitsiEncryptionWorkerContext.js';
import { Context } from './JitsiEncryptionWorkerContext.js'
const contexts = new Map(); // Map participant id => context
const contexts = new Map() // Map participant id => context
let sharedContext;
let sharedContext
/**
* Retrieves the participant {@code Context}, creating it if necessary.
@ -23,14 +23,14 @@ let sharedContext;
*/
function getParticipantContext(participantId) {
if (sharedContext) {
return sharedContext;
return sharedContext
}
if (!contexts.has(participantId)) {
contexts.set(participantId, new Context());
contexts.set(participantId, new Context())
}
return contexts.get(participantId);
return contexts.get(participantId)
}
/**
@ -43,60 +43,60 @@ function getParticipantContext(participantId) {
*/
function handleTransform(context, operation, readableStream, writableStream) {
if (operation === 'encode' || operation === 'decode') {
const transformFn = operation === 'encode' ? context.encodeFunction : context.decodeFunction;
const transformFn = operation === 'encode' ? context.encodeFunction : context.decodeFunction
const transformStream = new TransformStream({
transform: transformFn.bind(context),
});
})
readableStream
.pipeThrough(transformStream)
.pipeTo(writableStream);
.pipeTo(writableStream)
} else {
console.error(`Invalid operation: ${operation}`);
console.error(`Invalid operation: ${operation}`)
}
}
onmessage = async (event) => {
const { operation } = event.data;
const { operation } = event.data
if (operation === 'initialize') {
const { sharedKey } = event.data;
const { sharedKey } = event.data
if (sharedKey) {
sharedContext = new Context({ sharedKey });
sharedContext = new Context({ sharedKey })
}
} else if (operation === 'encode' || operation === 'decode') {
const { readableStream, writableStream, participantId } = event.data;
const context = getParticipantContext(participantId);
const { readableStream, writableStream, participantId } = event.data
const context = getParticipantContext(participantId)
handleTransform(context, operation, readableStream, writableStream);
handleTransform(context, operation, readableStream, writableStream)
} else if (operation === 'setKey') {
const { participantId, key, keyIndex } = event.data;
const context = getParticipantContext(participantId);
const { participantId, key, keyIndex } = event.data
const context = getParticipantContext(participantId)
if (key) {
context.setKey(key, keyIndex);
context.setKey(key, keyIndex)
} else {
context.setKey(false, keyIndex);
context.setKey(false, keyIndex)
}
} else if (operation === 'cleanup') {
const { participantId } = event.data;
const { participantId } = event.data
contexts.delete(participantId);
contexts.delete(participantId)
} else if (operation === 'cleanupAll') {
contexts.clear();
contexts.clear()
} else {
console.error('e2ee worker', operation);
console.error('e2ee worker', operation)
}
};
}
// Operations using RTCRtpScriptTransform.
if (self.RTCTransformEvent) {
self.onrtctransform = (event) => {
const transformer = event.transformer;
const { operation, participantId } = transformer.options;
const context = getParticipantContext(participantId);
const transformer = event.transformer
const { operation, participantId } = transformer.options
const context = getParticipantContext(participantId)
handleTransform(context, operation, transformer.readable, transformer.writable);
};
handleTransform(context, operation, transformer.readable, transformer.writable)
}
}

View File

@ -7,12 +7,12 @@
/* global BigInt */
import { deriveKeys, importKey, ratchet } from './crypto-utils';
import { deriveKeys, importKey, ratchet } from './crypto-utils'
// We use a ringbuffer of keys so we can change them and still decode packets that were
// encrypted with an old key. We use a size of 16 which corresponds to the four bits
// in the frame trailer.
const KEYRING_SIZE = 16;
const KEYRING_SIZE = 16
// We copy the first bytes of the VP8 payload unencrypted.
// For keyframes this is 10 bytes, for non-keyframes (delta) 3. See
@ -28,14 +28,14 @@ const UNENCRYPTED_BYTES = {
key: 10,
delta: 3,
undefined: 1, // frame.type is not set on audio
};
const ENCRYPTION_ALGORITHM = 'AES-GCM';
}
const ENCRYPTION_ALGORITHM = 'AES-GCM'
/* We use a 96 bit IV for AES GCM. This is signalled in plain together with the
packet. See https://developer.mozilla.org/en-US/docs/Web/API/AesGcmParams */
const IV_LENGTH = 12;
const IV_LENGTH = 12
const RATCHET_WINDOW_SIZE = 8;
const RATCHET_WINDOW_SIZE = 8
/**
* Per-participant context holding the cryptographic keys and
@ -47,14 +47,14 @@ export class Context {
*/
constructor({ sharedKey = false } = {}) {
// An array (ring) of keys that we use for sending and receiving.
this._cryptoKeyRing = new Array(KEYRING_SIZE);
this._cryptoKeyRing = new Array(KEYRING_SIZE)
// A pointer to the currently used key.
this._currentKeyIndex = -1;
this._currentKeyIndex = -1
this._sendCounts = new Map();
this._sendCounts = new Map()
this._sharedKey = sharedKey;
this._sharedKey = sharedKey
}
/**
@ -64,19 +64,19 @@ export class Context {
* @param {Number} keyIndex
*/
async setKey(key, keyIndex = -1) {
let newKey = false;
let newKey = false
if (key) {
if (this._sharedKey) {
newKey = key;
newKey = key
} else {
const material = await importKey(key);
const material = await importKey(key)
newKey = await deriveKeys(material);
newKey = await deriveKeys(material)
}
}
this._setKeys(newKey, keyIndex);
this._setKeys(newKey, keyIndex)
}
/**
@ -88,12 +88,12 @@ export class Context {
*/
_setKeys(keys, keyIndex = -1) {
if (keyIndex >= 0) {
this._currentKeyIndex = keyIndex % this._cryptoKeyRing.length;
this._currentKeyIndex = keyIndex % this._cryptoKeyRing.length
}
this._cryptoKeyRing[this._currentKeyIndex] = keys;
this._cryptoKeyRing[this._currentKeyIndex] = keys
this._sendCount = BigInt(0);
this._sendCount = BigInt(0)
}
/**
@ -119,19 +119,19 @@ export class Context {
* 9) Enqueue the encrypted frame for sending.
*/
encodeFunction(encodedFrame, controller) {
const keyIndex = this._currentKeyIndex;
const keyIndex = this._currentKeyIndex
if (this._cryptoKeyRing[keyIndex]) {
const iv = this._makeIV(encodedFrame.getMetadata().synchronizationSource, encodedFrame.timestamp);
const iv = this._makeIV(encodedFrame.getMetadata().synchronizationSource, encodedFrame.timestamp)
// Thіs is not encrypted and contains the VP8 payload descriptor or the Opus TOC byte.
const frameHeader = new Uint8Array(encodedFrame.data, 0, UNENCRYPTED_BYTES[encodedFrame.type]);
const frameHeader = new Uint8Array(encodedFrame.data, 0, UNENCRYPTED_BYTES[encodedFrame.type])
// Frame trailer contains the R|IV_LENGTH and key index
const frameTrailer = new Uint8Array(2);
const frameTrailer = new Uint8Array(2)
frameTrailer[0] = IV_LENGTH;
frameTrailer[1] = keyIndex;
frameTrailer[0] = IV_LENGTH
frameTrailer[1] = keyIndex
// Construct frame trailer. Similar to the frame header described in
// https://tools.ietf.org/html/draft-omara-sframe-00#section-4.2
@ -149,34 +149,34 @@ export class Context {
UNENCRYPTED_BYTES[encodedFrame.type]))
.then((cipherText) => {
const newData = new ArrayBuffer(frameHeader.byteLength + cipherText.byteLength
+ iv.byteLength + frameTrailer.byteLength);
const newUint8 = new Uint8Array(newData);
+ iv.byteLength + frameTrailer.byteLength)
const newUint8 = new Uint8Array(newData)
newUint8.set(frameHeader); // copy first bytes.
newUint8.set(frameHeader) // copy first bytes.
newUint8.set(
new Uint8Array(cipherText), frameHeader.byteLength); // add ciphertext.
new Uint8Array(cipherText), frameHeader.byteLength) // add ciphertext.
newUint8.set(
new Uint8Array(iv), frameHeader.byteLength + cipherText.byteLength); // append IV.
new Uint8Array(iv), frameHeader.byteLength + cipherText.byteLength) // append IV.
newUint8.set(
frameTrailer,
frameHeader.byteLength + cipherText.byteLength + iv.byteLength); // append frame trailer.
frameHeader.byteLength + cipherText.byteLength + iv.byteLength) // append frame trailer.
encodedFrame.data = newData;
encodedFrame.data = newData
return controller.enqueue(encodedFrame);
return controller.enqueue(encodedFrame)
}, (e) => {
// TODO: surface this to the app.
console.error(e);
console.error(e)
// We are not enqueuing the frame here on purpose.
});
})
}
/* NOTE WELL:
* This will send unencrypted data (only protected by DTLS transport encryption) when no key is configured.
* This is ok for demo purposes but should not be done once this becomes more relied upon.
*/
controller.enqueue(encodedFrame);
controller.enqueue(encodedFrame)
}
/**
@ -186,16 +186,16 @@ export class Context {
* @param {TransformStreamDefaultController} controller - TransportStreamController.
*/
async decodeFunction(encodedFrame, controller) {
const data = new Uint8Array(encodedFrame.data);
const keyIndex = data[encodedFrame.data.byteLength - 1];
const data = new Uint8Array(encodedFrame.data)
const keyIndex = data[encodedFrame.data.byteLength - 1]
if (this._cryptoKeyRing[keyIndex]) {
const decodedFrame = await this._decryptFrame(
encodedFrame,
keyIndex);
keyIndex)
if (decodedFrame) {
controller.enqueue(decodedFrame);
controller.enqueue(decodedFrame)
}
}
}
@ -215,8 +215,8 @@ export class Context {
keyIndex,
initialKey = undefined,
ratchetCount = 0) {
const { encryptionKey } = this._cryptoKeyRing[keyIndex];
let { material } = this._cryptoKeyRing[keyIndex];
const { encryptionKey } = this._cryptoKeyRing[keyIndex]
let { material } = this._cryptoKeyRing[keyIndex]
// Construct frame trailer. Similar to the frame header described in
// https://tools.ietf.org/html/draft-omara-sframe-00#section-4.2
@ -227,18 +227,18 @@ export class Context {
// ---------+-------------------------+-+---------+----
try {
const frameHeader = new Uint8Array(encodedFrame.data, 0, UNENCRYPTED_BYTES[encodedFrame.type]);
const frameTrailer = new Uint8Array(encodedFrame.data, encodedFrame.data.byteLength - 2, 2);
const frameHeader = new Uint8Array(encodedFrame.data, 0, UNENCRYPTED_BYTES[encodedFrame.type])
const frameTrailer = new Uint8Array(encodedFrame.data, encodedFrame.data.byteLength - 2, 2)
const ivLength = frameTrailer[0];
const ivLength = frameTrailer[0]
const iv = new Uint8Array(
encodedFrame.data,
encodedFrame.data.byteLength - ivLength - frameTrailer.byteLength,
ivLength);
ivLength)
const cipherTextStart = frameHeader.byteLength;
const cipherTextStart = frameHeader.byteLength
const cipherTextLength = encodedFrame.data.byteLength
- (frameHeader.byteLength + ivLength + frameTrailer.byteLength);
- (frameHeader.byteLength + ivLength + frameTrailer.byteLength)
const plainText = await crypto.subtle.decrypt({
name: 'AES-GCM',
@ -246,36 +246,36 @@ export class Context {
additionalData: new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength),
},
encryptionKey,
new Uint8Array(encodedFrame.data, cipherTextStart, cipherTextLength));
new Uint8Array(encodedFrame.data, cipherTextStart, cipherTextLength))
const newData = new ArrayBuffer(frameHeader.byteLength + plainText.byteLength);
const newUint8 = new Uint8Array(newData);
const newData = new ArrayBuffer(frameHeader.byteLength + plainText.byteLength)
const newUint8 = new Uint8Array(newData)
newUint8.set(new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength));
newUint8.set(new Uint8Array(plainText), frameHeader.byteLength);
newUint8.set(new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength))
newUint8.set(new Uint8Array(plainText), frameHeader.byteLength)
encodedFrame.data = newData;
encodedFrame.data = newData
return encodedFrame;
return encodedFrame
} catch (error) {
if (this._sharedKey) {
return;
return
}
if (ratchetCount < RATCHET_WINDOW_SIZE) {
const currentKey = this._cryptoKeyRing[this._currentKeyIndex];
const currentKey = this._cryptoKeyRing[this._currentKeyIndex]
material = await importKey(await ratchet(material));
material = await importKey(await ratchet(material))
const newKey = await deriveKeys(material);
const newKey = await deriveKeys(material)
this._setKeys(newKey);
this._setKeys(newKey)
return await this._decryptFrame(
encodedFrame,
keyIndex,
initialKey || currentKey,
ratchetCount + 1);
ratchetCount + 1)
}
/**
@ -284,7 +284,7 @@ export class Context {
* yet and ratcheting, of course, did not solve the problem. So if we fail RATCHET_WINDOW_SIZE times,
* we come back to the initial key.
*/
this._setKeys(initialKey);
this._setKeys(initialKey)
// TODO: notify the application about error status.
}
@ -310,23 +310,23 @@ export class Context {
* See also https://developer.mozilla.org/en-US/docs/Web/API/AesGcmParams
*/
_makeIV(synchronizationSource, timestamp) {
const iv = new ArrayBuffer(IV_LENGTH);
const ivView = new DataView(iv);
const iv = new ArrayBuffer(IV_LENGTH)
const ivView = new DataView(iv)
// having to keep our own send count (similar to a picture id) is not ideal.
if (!this._sendCounts.has(synchronizationSource)) {
// Initialize with a random offset, similar to the RTP sequence number.
this._sendCounts.set(synchronizationSource, Math.floor(Math.random() * 0xFFFF));
this._sendCounts.set(synchronizationSource, Math.floor(Math.random() * 0xFFFF))
}
const sendCount = this._sendCounts.get(synchronizationSource);
const sendCount = this._sendCounts.get(synchronizationSource)
ivView.setUint32(0, synchronizationSource);
ivView.setUint32(4, timestamp);
ivView.setUint32(8, sendCount % 0xFFFF);
ivView.setUint32(0, synchronizationSource)
ivView.setUint32(4, timestamp)
ivView.setUint32(8, sendCount % 0xFFFF)
this._sendCounts.set(synchronizationSource, sendCount + 1);
this._sendCounts.set(synchronizationSource, sendCount + 1)
return iv;
return iv
}
}

View File

@ -12,8 +12,8 @@
* See https://tools.ietf.org/html/draft-omara-sframe-00#section-4.3.1
*/
export async function deriveKeys(material) {
const info = new ArrayBuffer();
const textEncoder = new TextEncoder();
const info = new ArrayBuffer()
const textEncoder = new TextEncoder()
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey#HKDF
// https://developer.mozilla.org/en-US/docs/Web/API/HkdfParams
@ -25,12 +25,12 @@ export async function deriveKeys(material) {
}, material, {
name: 'AES-GCM',
length: 128,
}, false, ['encrypt', 'decrypt']);
}, false, ['encrypt', 'decrypt'])
return {
material,
encryptionKey,
};
}
}
/**
@ -40,7 +40,7 @@ export async function deriveKeys(material) {
* @returns {Promise<ArrayBuffer>} - ratcheted key material
*/
export async function ratchet(material) {
const textEncoder = new TextEncoder();
const textEncoder = new TextEncoder()
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveBits
return crypto.subtle.deriveBits({
@ -48,7 +48,7 @@ export async function ratchet(material) {
salt: textEncoder.encode('TalkFrameRatchetKey'),
hash: 'SHA-256',
info: new ArrayBuffer(),
}, material, 256);
}, material, 256)
}
/**
@ -60,5 +60,5 @@ export async function ratchet(material) {
*/
export async function importKey(keyBytes) {
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey
return crypto.subtle.importKey('raw', keyBytes, 'HKDF', false, ['deriveBits', 'deriveKey']);
return crypto.subtle.importKey('raw', keyBytes, 'HKDF', false, ['deriveBits', 'deriveKey'])
}