diff --git a/web/src/lib/crypto.worker.ts b/web/src/lib/crypto.worker.ts new file mode 100644 index 0000000..ee92105 --- /dev/null +++ b/web/src/lib/crypto.worker.ts @@ -0,0 +1,23 @@ +import { importKey, encrypt, decrypt } from "./crypto"; + +self.onmessage = async (e: MessageEvent) => { + const { type, keyEncoded } = e.data; + try { + const key = await importKey(keyEncoded); + + if (type === "encrypt") { + const { fileName, fileBuffer } = e.data; + const ciphertext = await encrypt(fileName, fileBuffer, key); + self.postMessage({ ciphertext }, [ciphertext.buffer]); + } else if (type === "decrypt") { + const { ciphertext } = e.data; + const { fileName, fileData } = await decrypt( + new Uint8Array(ciphertext), + key, + ); + self.postMessage({ fileName, fileData }, [fileData.buffer]); + } + } catch (err: any) { + self.postMessage({ error: err.message }); + } +}; diff --git a/web/src/pages/Upload.tsx b/web/src/pages/Upload.tsx index 1beb52e..84531b9 100644 --- a/web/src/pages/Upload.tsx +++ b/web/src/pages/Upload.tsx @@ -7,7 +7,7 @@ import { createEffect, } from "solid-js"; -import { generateKey, encrypt } from "../lib/crypto"; +import { generateKey } from "../lib/crypto"; import { formatBytes } from "../lib/utils"; const viewClass = "flex flex-col items-center gap-2 sm:gap-3"; @@ -51,6 +51,7 @@ export default function Upload() { let fileInput!: HTMLInputElement; let activeXhr: XMLHttpRequest | null = null; + let worker: Worker | null = null; const RADIUS = 130; const CENTER = RADIUS + 10; @@ -135,6 +136,9 @@ export default function Upload() { }; onMount(async () => { + worker = new Worker(new URL("../lib/crypto.worker.ts", import.meta.url), { + type: "module", + }); document.addEventListener("dragover", handleDragOver); document.addEventListener("dragleave", handleDragLeave); document.addEventListener("drop", handleDrop); @@ -157,6 +161,7 @@ export default function Upload() { document.removeEventListener("dragover", handleDragOver); document.removeEventListener("dragleave", handleDragLeave); document.removeEventListener("drop", handleDrop); + worker?.terminate(); }); const removeFile = () => { @@ -185,9 +190,25 @@ export default function Upload() { setProgress(0); try { - const { key, encoded } = await generateKey(); + const { encoded } = await generateKey(); const buffer = await f.arrayBuffer(); - const ciphertext = await encrypt(f.name || "file", buffer, key); + const ciphertext = await new Promise>( + (resolve, reject) => { + worker!.onmessage = (e) => { + if (e.data.error) reject(new Error(e.data.error)); + else resolve(e.data.ciphertext); + }; + worker!.postMessage( + { + type: "encrypt", + fileName: f.name || "file", + fileBuffer: buffer, + keyEncoded: encoded, + }, + [buffer], + ); + }, + ); const formData = new FormData(); formData.append("file", new Blob([ciphertext])); diff --git a/web/src/pages/View.tsx b/web/src/pages/View.tsx index 6dc4570..8af7646 100644 --- a/web/src/pages/View.tsx +++ b/web/src/pages/View.tsx @@ -1,7 +1,14 @@ import { useParams } from "@solidjs/router"; -import { createSignal, Show, Switch, Match, onMount } from "solid-js"; +import { + createSignal, + Show, + Switch, + Match, + onMount, + onCleanup, +} from "solid-js"; -import { importKey, decrypt } from "../lib/crypto"; +import { importKey } from "../lib/crypto"; import { formatBytes, formatExpiry, @@ -34,7 +41,13 @@ export default function View() { const [fileName, setFileName] = createSignal(""); let decryptedBlob: Blob | null = null; - let cryptoKey: CryptoKey; + const worker = new Worker( + new URL("../lib/crypto.worker.ts", import.meta.url), + { + type: "module", + }, + ); + onCleanup(() => worker.terminate()); onMount(async () => { const id = params.id; @@ -47,7 +60,7 @@ export default function View() { } try { - cryptoKey = await importKey(keyEncoded); + await importKey(keyEncoded); } catch { setError("Invalid key."); setStage("error"); @@ -81,10 +94,23 @@ export default function View() { } const buf = await res.arrayBuffer(); - const { fileName: name, fileData } = await decrypt( - new Uint8Array(buf), - cryptoKey, - ); + const { fileName: name, fileData } = await new Promise<{ + fileName: string; + fileData: Uint8Array; + }>((resolve, reject) => { + worker.onmessage = (e) => { + if (e.data.error) reject(new Error(e.data.error)); + else resolve(e.data); + }; + worker.postMessage( + { + type: "decrypt", + ciphertext: buf, + keyEncoded: location.hash.slice(1), + }, + [buf], + ); + }); const ext = getExt(name); setFileName(name);