import { Box, Button, Typography } from '@mui/material' import { MuiFileInput } from 'mui-file-input' import { useState } from 'react' import { LoadingSpinner } from '../../components/LoadingSpinner' import styles from './style.module.scss' import JSZip from 'jszip' import { toast } from 'react-toastify' import { encryptArrayBuffer, generateEncryptionKey, getHash, parseJson, readContentOfZipEntry, sendDM, signEventForMetaFile, uploadToFileStorage } from '../../utils' import { useSelector } from 'react-redux' import { State } from '../../store/rootReducer' import { NostrController } from '../../controllers' export const SignDocument = () => { const [selectedFile, setSelectedFile] = useState(null) const [isLoading, setIsLoading] = useState(false) const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('') const [isDraggingOver, setIsDraggingOver] = useState(false) const [authUrl, setAuthUrl] = useState() const usersPubkey = useSelector((state: State) => state.auth.usersPubkey) const nostrController = NostrController.getInstance() const handleDrop = (event: React.DragEvent) => { event.preventDefault() setIsDraggingOver(false) const file = event.dataTransfer.files[0] if (file.type === 'application/zip') setSelectedFile(file) } const handleDragOver = (event: React.DragEvent) => { event.preventDefault() setIsDraggingOver(true) } const handleSign = async () => { if (!selectedFile) return setIsLoading(true) setLoadingSpinnerDesc('Parsing zip file') const zip = await JSZip.loadAsync(selectedFile).catch((err) => { console.log('err in loading zip file :>> ', err) toast.error(err.message || 'An error occurred in loading zip file.') return null }) if (!zip) return setLoadingSpinnerDesc('Parsing meta.json') const metaFileContent = await readContentOfZipEntry( zip, 'meta.json', 'string' ) if (!metaFileContent) { setIsLoading(false) return } const meta = await parseJson(metaFileContent).catch((err) => { console.log('err in parsing the content of meta.json :>> ', err) toast.error( err.message || 'error occurred in parsing the content of meta.json' ) setIsLoading(false) return null }) const hashesFileContent = await readContentOfZipEntry( zip, 'hashes.json', 'string' ) if (!hashesFileContent) { setIsLoading(false) return } const hashes = await parseJson(hashesFileContent).catch((err) => { console.log('err in parsing the content of hashes.json :>> ', err) toast.error( err.message || 'error occurred in parsing the content of hashes.json' ) setIsLoading(false) return null }) if (!hashes) return setLoadingSpinnerDesc('Generating hashes for files') const fileHashes: { [key: string]: string } = {} const fileNames = Object.keys(meta.fileHashes) // create generate hashes for all entries in files folder of zipArchive // these hashes can be used to verify the originality of files for (const fileName of fileNames) { const filePath = `files/${fileName}` const arrayBuffer = await readContentOfZipEntry( zip, filePath, 'arraybuffer' ) if (!arrayBuffer) { setIsLoading(false) return } const hash = await getHash(arrayBuffer) if (!hash) { setIsLoading(false) return } fileHashes[fileName] = hash } // check if the current user is the last signer const lastSignerIndex = meta.signers.length - 1 const signerIndex = meta.signers.indexOf(usersPubkey) const isLastSigner = signerIndex === lastSignerIndex // if current user is the last signer, then send DMs to all viewers if (isLastSigner) { for (const viewer of meta.viewers) { await signAndSendToNext(zip, meta, hashes, viewer, fileHashes, false) } } else { const nextSigner = meta.signers[signerIndex + 1] await signAndSendToNext(zip, meta, hashes, nextSigner, fileHashes, true) } setIsLoading(false) } const signAndSendToNext = async ( zip: JSZip, meta: any, hashes: any, receiver: string, fileHashes: { [key: string]: string }, isNextSigner: boolean ) => { setLoadingSpinnerDesc('Signing nostr event for meta file') const signedEvent = await signEventForMetaFile( receiver, fileHashes, nostrController, setIsLoading ) if (!signedEvent) return meta.signedEvents = { ...meta.signedEvents, [signedEvent.pubkey]: JSON.stringify(signedEvent, null, 2) } const stringifiedMeta = JSON.stringify(meta, null, 2) zip.file('meta.json', stringifiedMeta) const metaHash = await getHash(stringifiedMeta) if (!metaHash) return hashes = { ...hashes, [usersPubkey!]: metaHash } zip.file('hashes.json', JSON.stringify(hashes, null, 2)) const arrayBuffer = await zip .generateAsync({ type: 'arraybuffer', compression: 'DEFLATE', compressionOptions: { level: 6 } }) .catch((err) => { console.log('err in zip:>> ', err) setIsLoading(false) toast.error(err.message || 'Error occurred in generating zip file') return null }) if (!arrayBuffer) return const encryptionKey = await generateEncryptionKey() setLoadingSpinnerDesc('Encrypting zip file') const encryptedArrayBuffer = await encryptArrayBuffer( arrayBuffer, encryptionKey ) const blob = new Blob([encryptedArrayBuffer]) setLoadingSpinnerDesc('Uploading zip file to file storage.') const fileUrl = await uploadToFileStorage(blob, nostrController) .then((url) => { toast.success('zip file uploaded to file storage') return url }) .catch((err) => { console.log('err in upload:>> ', err) toast.error(err.message || 'Error occurred in uploading zip file') return null }) if (!fileUrl) return setLoadingSpinnerDesc( `Sending DM to next ${isNextSigner ? 'signer' : 'viewer'}` ) await sendDM( fileUrl, encryptionKey, receiver, nostrController, isNextSigner, setAuthUrl ) } if (authUrl) { return (