import { Box, Button, List, ListItem, ListSubheader, Typography, useTheme } from '@mui/material' import JSZip from 'jszip' import { MuiFileInput } from 'mui-file-input' import { Event, verifyEvent } from 'nostr-tools' import { useEffect, useState } from 'react' import { toast } from 'react-toastify' import { LoadingSpinner } from '../../components/LoadingSpinner' import { UserComponent } from '../../components/username' import { MetadataController } from '../../controllers' import { CreateSignatureEventContent, Meta, ProfileMetadata } from '../../types' import { getHash, hexToNpub, npubToHex, parseJson, readContentOfZipEntry, shorten } from '../../utils' import styles from './style.module.scss' export const VerifyPage = () => { const theme = useTheme() const textColor = theme.palette.getContrastText( theme.palette.background.paper ) const [isLoading, setIsLoading] = useState(false) const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('') const [selectedFile, setSelectedFile] = useState(null) const [zip, setZip] = useState() const [meta, setMeta] = useState(null) const [submittedBy, setSubmittedBy] = useState() const [signers, setSigners] = useState<`npub1${string}`[]>([]) const [viewers, setViewers] = useState<`npub1${string}`[]>([]) const [creatorFileHashes, setCreatorFileHashes] = useState<{ [key: string]: string }>({}) const [currentFileHashes, setCurrentFileHashes] = useState<{ [key: string]: string | null }>({}) const [metadata, setMetadata] = useState<{ [key: string]: ProfileMetadata }>( {} ) useEffect(() => { if (zip) { const generateCurrentFileHashes = async () => { const fileHashes: { [key: string]: string | null } = {} const fileNames = Object.values(zip.files) .filter((entry) => entry.name.startsWith('files/') && !entry.dir) .map((entry) => entry.name) // 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 arrayBuffer = await readContentOfZipEntry( zip, fileName, 'arraybuffer' ) if (arrayBuffer) { const hash = await getHash(arrayBuffer) if (hash) { fileHashes[fileName.replace(/^files\//, '')] = hash } } else { fileHashes[fileName.replace(/^files\//, '')] = null } } setCurrentFileHashes(fileHashes) } generateCurrentFileHashes() } }, [zip]) useEffect(() => { if (submittedBy) { const metadataController = new MetadataController() const users = [submittedBy, ...signers, ...viewers] users.forEach((user) => { const pubkey = npubToHex(user)! if (!(pubkey in metadata)) { metadataController .findMetadata(pubkey) .then((metadataEvent) => { const metadataContent = metadataController.extractProfileMetadataContent(metadataEvent) if (metadataContent) setMetadata((prev) => ({ ...prev, [pubkey]: metadataContent })) }) .catch((err) => { console.error( `error occurred in finding metadata for: ${user}`, err ) }) } }) } }, [submittedBy, signers, viewers]) const handleVerify = async () => { if (!selectedFile) return setIsLoading(true) 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 setZip(zip) setLoadingSpinnerDesc('Parsing meta.json') const metaFileContent = await readContentOfZipEntry( zip, 'meta.json', 'string' ) if (!metaFileContent) { setIsLoading(false) return } const parsedMetaJson = 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 } ) if (!parsedMetaJson) return const createSignatureEvent = await parseJson( parsedMetaJson.createSignature ).catch((err) => { console.log('err in parsing the createSignature event:>> ', err) toast.error( err.message || 'error occurred in parsing the create signature event' ) setIsLoading(false) return null }) if (!createSignatureEvent) return const isValidCreateSignature = verifyEvent(createSignatureEvent) if (!isValidCreateSignature) { toast.error('Create signature is invalid') setIsLoading(false) return } const createSignatureContent = await parseJson( createSignatureEvent.content ).catch((err) => { console.log( `err in parsing the createSignature event's content :>> `, err ) toast.error( err.message || `error occurred in parsing the create signature event's content` ) setIsLoading(false) return null }) if (!createSignatureContent) return setSigners(createSignatureContent.signers) setViewers(createSignatureContent.viewers) setCreatorFileHashes(createSignatureContent.fileHashes) setSubmittedBy(createSignatureEvent.pubkey) setMeta(parsedMetaJson) setIsLoading(false) } const displayUser = (pubkey: string, verifySignature = false) => { const profile = metadata[pubkey] let isValidSignature = false if (verifySignature) { const npub = hexToNpub(pubkey) const signedEventString = meta ? meta.docSignatures[npub] : null if (signedEventString) { try { const signedEvent = JSON.parse(signedEventString) isValidSignature = verifyEvent(signedEvent) } catch (error) { console.error( `An error occurred in parsing and verifying the signature event for ${pubkey}`, error ) } } } return ( <> {verifySignature && ( {isValidSignature ? 'Valid' : 'Invalid'} Signature )} ) } const displayExportedBy = () => { if (!meta || !meta.exportSignature) return null const exportSignatureString = meta.exportSignature try { const exportSignatureEvent = JSON.parse(exportSignatureString) as Event if (verifyEvent(exportSignatureEvent)) { return displayUser(exportSignatureEvent.pubkey) } else { toast.error(`Invalid export signature!`) return ( Invalid export signature ) } } catch (error) { console.error(`An error occurred wile parsing exportSignature`, error) return null } } return ( <> {isLoading && } {!meta && ( <> Select exported zip file setSelectedFile(value)} InputProps={{ inputProps: { accept: '.zip' } }} /> {selectedFile && ( )} )} {meta && ( <> Meta Info } > {submittedBy && ( Submitted By {displayUser(submittedBy)} )} Exported By {displayExportedBy()} {signers.length > 0 && ( Signers
    {signers.map((signer) => (
  • {displayUser(npubToHex(signer)!, true)}
  • ))}
)} {viewers.length > 0 && ( Viewers
    {viewers.map((viewer) => (
  • {displayUser(npubToHex(viewer)!)}
  • ))}
)} Files {Object.entries(currentFileHashes).map( ([filename, hash], index) => { const isValidHash = creatorFileHashes[filename] === hash return ( {filename} {isValidHash ? 'Valid' : 'Invalid'} hash ) } )}
)}
) }