mirror of
https://github.com/SrIzan10/vdo.ninja.git
synced 2026-05-01 11:05:24 +00:00
224 lines
7.6 KiB
JavaScript
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');
|