import { useCallback, useContext, useEffect, useState } from "react"; import * as sodium from "libsodium-wrappers-sumo"; import useWebSocket from "react-use-websocket"; import CBOR from "cbor"; import { wsUrl } from "./constants"; import { RoomContext } from "./lib/roomContext"; import { useDebouncedCallback } from "use-debounce"; import { Row, Col, Button, Form, Badge, OverlayTrigger, Tooltip, } from "react-bootstrap"; export default function Upload() { const { roomId } = useContext(RoomContext); const [selectedFile, setSelectedFile] = useState(null); const [encryptionKey, setEncryptionKey] = useState(null); const [passphrase, setPassphrase] = useState(""); const [uploadProgress, setUploadProgress] = useState(null); const setHashedPassphrase = useDebouncedCallback( (passphrase) => { if (passphrase) { const salt = sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES); console.log("salt", salt); setEncryptionKey( sodium.crypto_pwhash( sodium.crypto_box_SEEDBYTES, passphrase, salt, sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE, sodium.crypto_pwhash_ALG_ARGON2ID13 ) ); } }, // delay in ms 1000 ); 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 } = 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; if (passphrase) { setHashedPassphrase(passphrase); } else { const key = sodium.crypto_secretstream_xchacha20poly1305_keygen(); setEncryptionKey(key); } })(); }, [passphrase]); 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 ( <>

How does this work?

RECEIVER MUST BE USING CHROME{" "} [?]

  1. Uploader: Copy the key and send it to whoever you're sending files to
  2. Receiver: Paste the key into the "Paste key here" box
  3. Receiver: Press the "select save file" button to choose where to save the file to
  4. Receiver: Tell the uploader to press "upload"
  5. Uploader: Press "upload"

Only one person can upload at a time.

Send file

{/*

Upload passphrase: { setEncryptionKey(null); setPassphrase(e.target.value); }} />

*/}

Upload Key:{" "}

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

Receive file {readyToDownload ? ( ready ) : ( you are not ready to download, make sure you enter the key and select a place to save } > not ready )}

  1. setDecryptionKey(e.target.value)} placeholder="Paste key here..." />
); } 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); }