diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index c445936..24538fb 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -421,38 +421,39 @@ export const CreatePage = () => { return finalZipFile } - // Handle file upload and further actions based on online/offline status - const handleFileUpload = async (file: File, arrayBuffer: ArrayBuffer) => { - if (await isOnline()) { - const fileUrl = await uploadFile(file) + const handleOnlineFlow = async ( + encryptedArrayBuffer: ArrayBuffer, + encryptionKey: string + ) => { + const unixNow = Math.floor(Date.now() / 1000) + const blob = new Blob([encryptedArrayBuffer]) + // Create a File object with the Blob data + const file = new File([blob], `compressed-${unixNow}.sigit`, { + type: 'application/sigit' + }) - if (!fileUrl) return + const fileUrl = await uploadFile(file) + if (!fileUrl) return - await sendDMs(fileUrl) - setIsLoading(false) - - navigate(appPrivateRoutes.sign, { state: { arrayBuffer } }) - } else { - handleOffline(file, arrayBuffer) - } + await sendDMs(fileUrl, encryptionKey) } // Handle errors during file upload const handleUploadError = (err: any) => { console.log('Error in upload:>> ', err) setIsLoading(false) - toast.error(err.message || 'Error occurred in uploading zip file') + toast.error(err.message || 'Error occurred in uploading file') return null } - // Upload the file to the storage and send DMs to signers/viewers + // Upload the file to the storage const uploadFile = async (file: File): Promise => { setIsLoading(true) - setLoadingSpinnerDesc('Uploading zip file to file storage.') + setLoadingSpinnerDesc('Uploading sigit to file storage.') const fileUrl = await uploadToFileStorage(file, nostrController) .then((url) => { - toast.success('zip file uploaded to file storage') + toast.success('Sigit uploaded to file storage') return url }) .catch(handleUploadError) @@ -461,7 +462,7 @@ export const CreatePage = () => { } // Send DMs to signers and viewers with the file URL - const sendDMs = async (fileUrl: string) => { + const sendDMs = async (fileUrl: string, encryptionKey: string) => { setLoadingSpinnerDesc('Sending DM to signers/viewers') const signers = users.filter((user) => user.role === UserRole.signer) @@ -470,6 +471,7 @@ export const CreatePage = () => { if (signers.length > 0) { await sendDM( fileUrl, + encryptionKey, signers[0].pubkey, nostrController, true, @@ -477,15 +479,31 @@ export const CreatePage = () => { ) } else { for (const viewer of viewers) { - await sendDM(fileUrl, viewer.pubkey, nostrController, false, setAuthUrl) + await sendDM( + fileUrl, + encryptionKey, + viewer.pubkey, + nostrController, + false, + setAuthUrl + ) } } } // Manage offline scenarios for signing or viewing the file - const handleOffline = (file: File, arrayBuffer: ArrayBuffer) => { - saveAs(file, 'request.sigit.zip') - navigate(appPrivateRoutes.sign, { state: { arrayBuffer } }) + const handleOfflineFlow = async ( + encryptedArrayBuffer: ArrayBuffer, + encryptionKey: string + ) => { + const finalZipFile = await createFinalZipFile( + encryptedArrayBuffer, + encryptionKey + ) + + if (!finalZipFile) return + + saveAs(finalZipFile, 'request.sigit.zip') } const handleCreate = async () => { @@ -507,25 +525,24 @@ export const CreatePage = () => { setLoadingSpinnerDesc('Generating zip file') - const arraybuffer = await generateZipFile(zip) - if (!arraybuffer) return + const arrayBuffer = await generateZipFile(zip) + if (!arrayBuffer) return const encryptionKey = await generateEncryptionKey() setLoadingSpinnerDesc('Encrypting zip file') const encryptedArrayBuffer = await encryptZipFile( - arraybuffer, + arrayBuffer, encryptionKey ) - const finalZipFile = await createFinalZipFile( - encryptedArrayBuffer, - encryptionKey - ) + if (await isOnline()) { + await handleOnlineFlow(encryptedArrayBuffer, encryptionKey) + } else { + await handleOfflineFlow(encryptedArrayBuffer, encryptionKey) + } - if (!finalZipFile) return - - return await handleFileUpload(finalZipFile, arraybuffer) + navigate(appPrivateRoutes.sign, { state: { arrayBuffer } }) } if (authUrl) { diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index ff7a632..2a8ca9a 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -42,7 +42,7 @@ export const SignPage = () => { const location = useLocation() const { arrayBuffer: decryptedArrayBuffer } = location.state || {} - const [searchParams] = useSearchParams() + const [searchParams, setSearchParams] = useSearchParams() const [displayInput, setDisplayInput] = useState(false) @@ -160,8 +160,9 @@ export const SignPage = () => { useEffect(() => { const fileUrl = searchParams.get('file') + const key = searchParams.get('key') - if (fileUrl) { + if (fileUrl && key) { setIsLoading(true) setLoadingSpinnerDesc('Fetching file from file server') @@ -169,13 +170,22 @@ export const SignPage = () => { .get(fileUrl, { responseType: 'arraybuffer' }) - .then((res) => { + .then(async (res) => { const fileName = fileUrl.split('/').pop() const file = new File([res.data], fileName!) - decrypt(file).then((arrayBuffer) => { - if (arrayBuffer) handleDecryptedArrayBuffer(arrayBuffer) + const encryptedArrayBuffer = await file.arrayBuffer() + + const arrayBuffer = await decryptArrayBuffer( + encryptedArrayBuffer, + key + ).catch((err) => { + console.log('err in decryption:>> ', err) + toast.error(err.message || 'An error occurred in decrypting file.') + return null }) + + if (arrayBuffer) handleDecryptedArrayBuffer(arrayBuffer) }) .catch((err) => { console.error(`error occurred in getting file from ${fileUrl}`, err) @@ -268,8 +278,6 @@ export const SignPage = () => { setAuthUrl(undefined) // Clear authentication URL }) - console.log('encryptionKey :>> ', encryptionKey) - // Return if encryption failed if (!encryptionKey) continue @@ -438,15 +446,11 @@ export const SignPage = () => { setLoadingSpinnerDesc('Encrypting zip file') const encryptedArrayBuffer = await encryptArrayBuffer(arrayBuffer, key) - const finalZipFile = await createFinalZipFile(encryptedArrayBuffer, key) - - if (!finalZipFile) return - if (await isOnline()) { - await handleOnlineFlow(finalZipFile) + await handleOnlineFlow(encryptedArrayBuffer, key) + } else { + handleDecryptedArrayBuffer(arrayBuffer).finally(() => setIsLoading(false)) } - - handleDecryptedArrayBuffer(arrayBuffer).finally(() => setIsLoading(false)) } // Read the content of the hashes.json file @@ -600,35 +604,56 @@ export const SignPage = () => { } // Handle the online flow: upload file and send DMs - const handleOnlineFlow = async (file: File) => { - const fileUrl = await uploadZipFile(file) + const handleOnlineFlow = async ( + encryptedArrayBuffer: ArrayBuffer, + encryptionKey: string + ) => { + const unixNow = Math.floor(Date.now() / 1000) + const blob = new Blob([encryptedArrayBuffer]) + // Create a File object with the Blob data + const file = new File([blob], `compressed-${unixNow}.sigit`, { + type: 'application/sigit' + }) + + const fileUrl = await uploadFile(file) if (!fileUrl) return const isLastSigner = checkIsLastSigner(signers) if (isLastSigner) { - await sendDMToAllUsers(fileUrl) + await sendDMToAllUsers(fileUrl, encryptionKey) } else { - await sendDMToNextSigner(fileUrl) + await sendDMToNextSigner(fileUrl, encryptionKey) } + // update search params with updated file url and encryption key + setSearchParams({ + file: fileUrl, + key: encryptionKey + }) + setIsLoading(false) } - // Upload the zip file to file storage - const uploadZipFile = async (file: File): Promise => { - setLoadingSpinnerDesc('Uploading zip file to file storage.') + // Handle errors during file upload + const handleUploadError = (err: any) => { + console.log('Error in upload:>> ', err) + setIsLoading(false) + toast.error(err.message || 'Error occurred in uploading file') + return null + } + + // Upload the file to file storage + const uploadFile = async (file: File): Promise => { + setIsLoading(true) + setLoadingSpinnerDesc('Uploading sigit file to file storage.') + const fileUrl = await uploadToFileStorage(file, nostrController) .then((url) => { - toast.success('Zip file uploaded to file storage') + toast.success('Sigit uploaded to file storage') return url }) - .catch((err) => { - console.log('Error uploading file:', err) - setIsLoading(false) - toast.error(err.message || 'Error uploading file') - return null - }) + .catch(handleUploadError) return fileUrl } @@ -642,7 +667,7 @@ export const SignPage = () => { } // Send DM to all users (signers and viewers) - const sendDMToAllUsers = async (fileUrl: string) => { + const sendDMToAllUsers = async (fileUrl: string, encryptionKey: string) => { const userSet = new Set<`npub1${string}`>() if (submittedBy) { @@ -662,6 +687,7 @@ export const SignPage = () => { for (const user of users) { await sendDM( fileUrl, + encryptionKey, npubToHex(user)!, nostrController, false, @@ -671,12 +697,13 @@ export const SignPage = () => { } // Send DM to the next signer - const sendDMToNextSigner = async (fileUrl: string) => { + const sendDMToNextSigner = async (fileUrl: string, encryptionKey: string) => { const usersNpub = hexToNpub(usersPubkey!) const signerIndex = signers.indexOf(usersNpub) const nextSigner = signers[signerIndex + 1] await sendDM( fileUrl, + encryptionKey, npubToHex(nextSigner)!, nostrController, true, @@ -776,8 +803,8 @@ export const SignPage = () => { const finalZipFile = await createFinalZipFile(encryptedArrayBuffer, key) if (!finalZipFile) return - - saveAs(finalZipFile, 'exported.sigit.zip') + const unixNow = Math.floor(Date.now() / 1000) + saveAs(finalZipFile, `exported-${unixNow}.sigit.zip`) } /** @@ -915,14 +942,14 @@ export const SignPage = () => { )} - {isSignerOrCreator && - signedStatus === SignedStatus.User_Is_Not_Next_Signer && ( - - - - )} + {/* todo: In offline mode export sigit is not visible after last signer has signed*/} + {isSignerOrCreator && ( + + + + )} )} diff --git a/src/utils/misc.ts b/src/utils/misc.ts index a0e1d30..fc53dff 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -56,6 +56,7 @@ export const uploadToFileStorage = async ( /** * Sends a Direct Message (DM) to a recipient, encrypting the content and handling authentication. * @param fileUrl The URL of the encrypted zip file to be included in the DM. + * @param encryptionKey The encryption key used to decrypt the zip file to be included in the DM. * @param pubkey The public key of the recipient. * @param nostrController The NostrController instance for handling authentication and encryption. * @param isSigner Boolean indicating whether the recipient is a signer or viewer. @@ -63,6 +64,7 @@ export const uploadToFileStorage = async ( */ export const sendDM = async ( fileUrl: string, + encryptionKey: string, pubkey: string, nostrController: NostrController, isSigner: boolean, @@ -75,7 +77,9 @@ export const sendDM = async ( const decryptionUrl = `${window.location.origin}/#${ appPrivateRoutes.sign - }?file=${encodeURIComponent(fileUrl)}` + }?file=${encodeURIComponent(fileUrl)}&key=${encodeURIComponent( + encryptionKey + )}` const content = `${initialLine}\n\n${decryptionUrl}`