From 0dd2cf35955ea8b2e49fc44e84d56601d2f17796 Mon Sep 17 00:00:00 2001 From: thecodrr Date: Mon, 20 Sep 2021 12:10:08 +0500 Subject: [PATCH] feat: add attachment upload & download --- apps/web/package.json | 1 + apps/web/public/crypto.worker.js | 8 ++- .../editor/plugins/attachmentpicker.js | 8 +-- apps/web/src/interfaces/fs.js | 56 ++++++++++++++++++- .../src/interfaces/nncrypto/nncryptoworker.js | 6 +- 5 files changed, 67 insertions(+), 12 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 13b6d069d..e6b198e05 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -13,6 +13,7 @@ "@rebass/forms": "^4.0.6", "@streetwritersco/tinymce-plugins": "file:../notesnook/packages/tinymce-plugins", "@tinymce/tinymce-react": "^3.12.6", + "axios": "^0.21.4", "clipboard": "^2.0.6", "cogo-toast": "^4.2.3", "compressorjs": "^1.0.7", diff --git a/apps/web/public/crypto.worker.js b/apps/web/public/crypto.worker.js index 123850bf0..ad2e156b0 100644 --- a/apps/web/public/crypto.worker.js +++ b/apps/web/public/crypto.worker.js @@ -188,7 +188,7 @@ const decrypt = (passwordOrKey, { iv, cipher, salt, output, inputType }) => { const { key } = _getKey({ salt, ...passwordOrKey }); const input = inputType === "uint8array" - ? input + ? cipher : inputType === "base64" ? sodium.from_base64(cipher) : sodium.decode(cipher); @@ -199,10 +199,12 @@ const decrypt = (passwordOrKey, { iv, cipher, salt, output, inputType }) => { undefined, sodium.from_base64(iv), key, - output + output !== "base64" ? output : "uint8array" ); sodium.memzero(key); - return data; + return output === "base64" + ? sodium.to_base64(data, sodium.base64_variants.ORIGINAL) + : data; }; if (self.document) { diff --git a/apps/web/src/components/editor/plugins/attachmentpicker.js b/apps/web/src/components/editor/plugins/attachmentpicker.js index 55bfaaca6..57a23ef32 100644 --- a/apps/web/src/components/editor/plugins/attachmentpicker.js +++ b/apps/web/src/components/editor/plugins/attachmentpicker.js @@ -47,7 +47,7 @@ async function pickFile() { const key = await getEncryptionKey(); const buffer = await selectedFile.arrayBuffer(); const output = await fs.writeEncrypted(null, { - data: buffer, + data: new Uint8Array(buffer), type: "buffer", key, }); @@ -59,7 +59,7 @@ async function pickFile() { }); return { - hash: selectedFile.hash, + hash: output.hash, filename: selectedFile.name, type: selectedFile.type, size: selectedFile.size, @@ -74,7 +74,7 @@ async function pickImage() { const key = await getEncryptionKey(); const output = await fs.writeEncrypted(null, { - data: buffer, + data: new Uint8Array(buffer), type: "buffer", key, }); @@ -86,7 +86,7 @@ async function pickImage() { }); return { - hash: selectedImage.hash, + hash: output.hash, filename: selectedImage.name, type: selectedImage.type, size: output.length, diff --git a/apps/web/src/interfaces/fs.js b/apps/web/src/interfaces/fs.js index 0bbf377e8..de0d4ed6e 100644 --- a/apps/web/src/interfaces/fs.js +++ b/apps/web/src/interfaces/fs.js @@ -1,6 +1,7 @@ import NNCrypto from "./nncrypto/index"; import localforage from "localforage"; import xxhash from "xxhash-wasm"; +import axios from "axios"; const crypto = new NNCrypto(); const fs = localforage.createInstance({ @@ -48,12 +49,13 @@ async function writeEncrypted(filename, { data, type, key }) { async function hashBuffer(data) { const hasher = await xxhash(); return { - hash: Buffer.from(hasher.h64Raw(data)).toString("base64"), + hash: Buffer.from(hasher.h64Raw(data)).toString("hex"), type: "xxh64", }; } async function readEncrypted(filename, key, cipherData) { + console.log("Reading encrypted file", filename); const readAsBuffer = localforage.supports(localforage.INDEXEDDB); cipherData.cipher = await fs.getItem(filename); if (!cipherData.cipher) @@ -64,5 +66,55 @@ async function readEncrypted(filename, key, cipherData) { : await crypto.decrypt(key, cipherData, cipherData.outputType); } -const FS = { writeEncrypted, readEncrypted }; +async function uploadFile(filename, requestOptions) { + console.log("Request to upload file", filename, requestOptions); + const { url } = requestOptions; + + let cipher = await fs.getItem(filename); + if (!cipher) throw new Error(`File not found. Filename: ${filename}`); + + const readAsBuffer = localforage.supports(localforage.INDEXEDDB); + if (!readAsBuffer) + cipher = Uint8Array.from(window.atob(cipher), (c) => c.charCodeAt(0)); + + const response = await axios.request({ + url: url, + method: "PUT", + headers: { + "Content-Type": "", + }, + data: new Blob([cipher.buffer]), + onUploadProgress: (ev) => { + console.log("Uploading file", filename, ev); + }, + }); + + console.log("File uploaded:", filename, response); + return isSuccessStatusCode(response.status); +} + +async function downloadFile(filename, requestOptions) { + const { url, headers } = requestOptions; + console.log("Request to download file", filename, url, headers); + if (await fs.hasItem(filename)) return true; + + const response = await axios.get(url, { + headers: headers, + responseType: "blob", + onDownloadProgress: (ev) => { + console.log("Downloading file", filename, ev); + }, + }); + console.log("File downloaded", filename, url, response); + if (!isSuccessStatusCode(response.status)) return false; + const blob = new Blob([response.data]); + await fs.setItem(filename, new Uint8Array(await blob.arrayBuffer())); + return true; +} + +const FS = { writeEncrypted, readEncrypted, uploadFile, downloadFile }; export default FS; + +function isSuccessStatusCode(statusCode) { + return statusCode >= 200 && statusCode <= 299; +} diff --git a/apps/web/src/interfaces/nncrypto/nncryptoworker.js b/apps/web/src/interfaces/nncrypto/nncryptoworker.js index ea31fbae8..b980caaaf 100644 --- a/apps/web/src/interfaces/nncrypto/nncryptoworker.js +++ b/apps/web/src/interfaces/nncrypto/nncryptoworker.js @@ -81,12 +81,12 @@ export default class NNCryptoWorker { /** * * @param {{password: string}|{key:string, salt: string}} passwordOrKey - password or derived key - * @param {string} data - the plaintext data + * @param {string|Uint8Array} data - the plaintext data * @param {string} type */ encryptBinary = (passwordOrKey, data, type = "plain") => { const payload = { type, data }; - const transferables = type === "buffer" ? [payload.data] : []; + const transferables = type === "buffer" ? [payload.data.buffer] : []; return this._communicate( "encryptBinary", { @@ -106,7 +106,7 @@ export default class NNCryptoWorker { cipherData.output = outputType; cipherData.inputType = "uint8array"; return this._communicate("decrypt", { passwordOrKey, cipher: cipherData }, [ - cipherData.cipher, + cipherData.cipher.buffer, ]); };