Files
archived-vdo.ninja/insertableStreamWorker.js
2023-12-19 10:14:38 -05:00

224 lines
7.6 KiB
JavaScript

'use strict';
var cryptoKey = false; // 'aabbccddeeff00112233445566778899';
const IV_LENGTH = 16; // AES-CBC requires a 16-byte IV
var lyraCodecModule = false;
var EncryptionFailedMessage = null;
async function encodeFunction(encodedFrame, controller) {
if (cryptoKey){
try {
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
encodedFrame.data = await encryptAES(encodedFrame.data, cryptoKey, iv);
if (EncryptionFailedMessage){
console.error("👍 e2e encryption is now working! whew.");
DecryptionFailedMessage = false;
}
} catch(e){
if (!EncryptionFailedMessage){
EncryptionFailedMessage = true;
console.error("😢 e2e encryption failed"); // didn't encode. we'll just send it anyways. Insecure or user friendly? :|
}
}
}
controller.enqueue(encodedFrame);
}
var DecryptionFailedMessage = null;
async function decodeFunction(encodedFrame, controller) {
if (cryptoKey){
try {
const iv = new Uint8Array(encodedFrame.data.slice(0, IV_LENGTH));
const encryptedData = new Uint8Array(encodedFrame.data.slice(IV_LENGTH));
encodedFrame.data = await decryptAES(encryptedData, cryptoKey, iv);
if (DecryptionFailedMessage){
console.error("👍 Decryption is now working");
DecryptionFailedMessage = false;
}
} catch(e){
if (!DecryptionFailedMessage){
DecryptionFailedMessage = true;
console.error("😢 Decryption failed. Will try without E2EE."); // lets try playing it, just in case it wasn't encoded. Insecure or user friendly? :|
}
}
}
controller.enqueue(encodedFrame);
}
async function encryptAES(data, key, iv) {
const encodedKey = new TextEncoder().encode(key);
const importedKey = await crypto.subtle.importKey(
'raw',
encodedKey,
'AES-CBC',
false,
['encrypt']
);
const encryptedData = await crypto.subtle.encrypt(
{ name: 'AES-CBC', iv: iv },
importedKey,
data
);
const encryptedBytes = new Uint8Array(encryptedData);
return new Uint8Array([...iv, ...encryptedBytes]).buffer;
}
async function decryptAES(encryptedData, key, iv) {
const encodedKey = new TextEncoder().encode(key);
const importedKey = await crypto.subtle.importKey(
'raw',
encodedKey,
'AES-CBC',
false,
['decrypt']
);
return await crypto.subtle.decrypt(
{ name: 'AES-CBC', iv: iv },
importedKey,
encryptedData
);
}
function lyraEncodeFunction(encodedFrame, controller) { // Snippet is Apache 2.0 licenced. Source: https://github.com/Flash-Meeting/lyra-webrtc
const inputDataArray = new Uint8Array(encodedFrame.data);
const inputBufferPtr = lyraCodecModule._malloc(encodedFrame.data.byteLength);
const encodedBufferPtr = lyraCodecModule._malloc(1024);
lyraCodecModule.HEAPU8.set(inputDataArray, inputBufferPtr);
const length = lyraCodecModule.encode(inputBufferPtr, inputDataArray.length, 16000, encodedBufferPtr);
const newData = new ArrayBuffer(length);
if (length > 0){
const newDataArray = new Uint8Array(newData);
newDataArray.set(lyraCodecModule.HEAPU8.subarray(encodedBufferPtr, encodedBufferPtr + length));
}
lyraCodecModule._free(inputBufferPtr);
lyraCodecModule._free(encodedBufferPtr);
encodedFrame.data = newData;
controller.enqueue(encodedFrame);
}
function lyraDecodeFunction(encodedFrame, controller) { // Apache 2.0 licenced. Source: https://github.com/Flash-Meeting/lyra-webrtc
const newData = new ArrayBuffer(16000 * 0.02 * 2);
if (encodedFrame.data.byteLength > 0) {
const inputDataArray = new Uint8Array(encodedFrame.data);
const inputBufferPtr = lyraCodecModule._malloc(encodedFrame.data.byteLength);
const outputBufferPtr = lyraCodecModule._malloc(2048);
lyraCodecModule.HEAPU8.set(inputDataArray, inputBufferPtr);
const length = lyraCodecModule.decode(inputBufferPtr,
inputDataArray.length, 16000,
outputBufferPtr);
const newDataArray = new Uint8Array(newData);
newDataArray.set(lyraCodecModule.HEAPU8.subarray(outputBufferPtr, outputBufferPtr + length));
lyraCodecModule._free(inputBufferPtr);
lyraCodecModule._free(outputBufferPtr);
}
encodedFrame.data = newData;
controller.enqueue(encodedFrame);
}
function handleTransform(operation, readable, writable) {
if (operation === 'encode') {
const transformStream = new TransformStream({
transform: encodeFunction,
});
readable.pipeThrough(transformStream).pipeTo(writable);
} else if (operation === 'decode') {
const transformStream = new TransformStream({
transform: decodeFunction,
});
readable.pipeThrough(transformStream).pipeTo(writable);
} else if (operation === 'lyradecode') {
const transformStream = new TransformStream({
transform: lyraDecodeFunction,
});
readable.pipeThrough(transformStream).pipeTo(writable);
} else if (operation === 'lyraencode') {
const transformStream = new TransformStream({
transform: lyraEncodeFunction,
});
readable.pipeThrough(transformStream).pipeTo(writable);
} else if (operation === 'redencode') {
const transformStream = new TransformStream({
transform: (chunk, controller) => {
controller.enqueue(chunk);
/* const clonedChunk = new EncodedAudioChunk({
type: chunk.type,
timestamp: chunk.timestamp,
// .. i need a way to clone the binary data here
data: cloneChunkData(chunk)
});
controller.enqueue(clonedChunk); */
},
});
readable.pipeThrough(transformStream).pipeTo(writable);
} else if (operation === 'reddecode') {
const receivedChunks = new Set();
const transformStream = new TransformStream({
transform: (chunk, controller) => {
const chunkId = chunk.timestamp;
if (receivedChunks.has(chunkId)) {
// If it's a duplicate, ignore it
return;
}
receivedChunks.add(chunkId);
controller.enqueue(chunk);
},
});
readable.pipeThrough(transformStream).pipeTo(writable);
} else if (operation === 'pass') {
const transformStream = new TransformStream({
transform: (chunk, controller) => {
controller.enqueue(chunk);
},
});
readable.pipeThrough(transformStream).pipeTo(writable);
}
}
if (self.RTCTransformEvent) {
self.onrtctransform = (event) => {
const transformer = event.transformer;
handleTransform(transformer.options.operation, transformer.readable, transformer.writable);
};
}
onmessage = async (event) => {
if (event.data.cryptoKey) {
cryptoKey = event.data.cryptoKey;
if (cryptoKey){
self.postMessage('crypto key Loaded. e2ee will be used where possible');
} else {
self.postMessage('crypto key Un-loaded. ?');
}
} else if (event.data.cryptoPhrase) {
try {
const encoder = new TextEncoder();
const data = encoder.encode(event.data.cryptoPhrase);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const keyBytes = new Uint8Array(hashBuffer, 0, IV_LENGTH);
cryptoKey = Array.from(keyBytes).map(byte => byte.toString(16).padStart(2, '0')).join('');
} catch(e){
console.error(e);
cryptoKey = false;
}
if (cryptoKey){
self.postMessage('crypto key Generated from a password. e2ee will be used where possible');
} else {
self.postMessage('WARNING: crypto key could Not be generated');
}
} else if (event.data.lyraCodecModule) {
lyraCodecModule = event.data.lyraCodecModule;
}
if (event.data.operation) {
return handleTransform(event.data.operation, event.data.readable, event.data.writable);
}
};
self.postMessage('insertableStreamWorkerLoaded');