import { useCallback, useEffect, useState } from "react"; import * as sodium from "libsodium-wrappers"; import useWebSocket from "react-use-websocket"; import CBOR from "cbor"; import { wsUrl } from "./constants"; export default function Upload({ roomId }) { const [selectedFile, setSelectedFile] = useState(null); const [encryptionKey, setEncryptionKey] = useState(null); const [uploadProgress, setUploadProgress] = useState(null); const [decryptionKey, setDecryptionKey] = useState(""); const [selectedSaveFile, setSelectedSaveFile] = useState(null); const [decryptState, setDecryptState] = useState(null); const [pendingDecryption, setPendingDecryption] = useState([]); let decryptionKeyInternal: Uint8Array; if (decryptionKey) { decryptionKeyInternal = hex2Bytes(decryptionKey); } const [writeStream, setWriteStream] = useState(null); const readyToDownload = !!decryptionKeyInternal && !!selectedSaveFile && !!writeStream; const { sendMessage, lastJsonMessage, readyState: newReadyState, } = useWebSocket(wsUrl, { share: true, onMessage: async (event) => { const message = CBOR.decode(await event.data.arrayBuffer()); if (message.type === "FileUploadBegin") { const state = sodium.crypto_secretstream_xchacha20poly1305_init_pull( message.header, decryptionKeyInternal ); setDecryptState(state); } if (message.type === "FileUploadChunk") { if (readyToDownload && decryptState) { for (const { data } of [...pendingDecryption, message]) { const { message } = sodium.crypto_secretstream_xchacha20poly1305_pull( decryptState, data ); const result = await writeStream.write(message); } setPendingDecryption([]); } else { setPendingDecryption([...pendingDecryption, message]); } } if (message.type === "FileUploadComplete") { await writeStream.close(); } }, }); function sendCborMessage(data) { let cbor = CBOR.encode(data); sendMessage(cbor); } useEffect(() => { (async () => { await sodium.ready; const key = sodium.crypto_secretstream_xchacha20poly1305_keygen(); setEncryptionKey(key); })(); }, []); const uploadFile = useCallback(async () => { if (!encryptionKey) return; if (!selectedFile) return; const { state, header } = sodium.crypto_secretstream_xchacha20poly1305_init_push(encryptionKey); sendCborMessage({ type: "FileUploadBegin", room_id: roomId, header, }); const stream = selectedFile.stream(); const reader = stream.getReader(); try { while (true) { const { done, value } = await reader.read(); if (done) break; // Encrypt this chunk const encryptedValue = sodium.crypto_secretstream_xchacha20poly1305_push( state, value, null, 0 ); sendCborMessage({ type: "FileUploadChunk", room_id: roomId, data: encryptedValue, }); } sendCborMessage({ type: "FileUploadComplete", room_id: roomId, }); } finally { reader.releaseLock(); } }, [encryptionKey, selectedFile]); async function selectSaveFile() { const newHandle = await window.showSaveFilePicker(); setSelectedSaveFile(newHandle); const writableStream = await newHandle.createWritable(); setWriteStream(writableStream); } return (

Send file

Upload Key: {encryptionKey && bytes2Hex(encryptionKey)}

setSelectedFile(e.target.files[0])} />

Receive file

  1. setDecryptionKey(e.target.value)} placeholder="Download Key..." />
( {readyToDownload ? "you are ready to download" : "you are not ready to download, make sure you enter the key and select a place to save"} )

); } function bytes2Hex(byteArray: Uint8Array): string { return Array.prototype.map .call(byteArray, function (byte: number) { return ("0" + (byte & 0xff).toString(16)).slice(-2); }) .join(""); } function hex2Bytes(hexString: string): Uint8Array { let result = []; for (var i = 0; i < hexString.length; i += 2) { result.push(parseInt(hexString.substr(i, 2), 16)); } return new Uint8Array(result); }