2024-05-14 09:27:05 +00:00
|
|
|
import {
|
|
|
|
Box,
|
|
|
|
Button,
|
|
|
|
List,
|
|
|
|
ListItem,
|
|
|
|
ListSubheader,
|
|
|
|
Typography,
|
|
|
|
useTheme
|
|
|
|
} from '@mui/material'
|
|
|
|
import JSZip from 'jszip'
|
|
|
|
import { MuiFileInput } from 'mui-file-input'
|
2024-05-16 11:22:05 +00:00
|
|
|
import { Event, verifyEvent } from 'nostr-tools'
|
2024-05-14 09:27:05 +00:00
|
|
|
import { useEffect, useState } from 'react'
|
|
|
|
import { toast } from 'react-toastify'
|
|
|
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
2024-05-16 11:22:05 +00:00
|
|
|
import { UserComponent } from '../../components/username'
|
2024-05-16 05:40:56 +00:00
|
|
|
import { MetadataController } from '../../controllers'
|
|
|
|
import { Meta, ProfileMetadata } from '../../types'
|
2024-05-14 09:27:05 +00:00
|
|
|
import {
|
|
|
|
hexToNpub,
|
|
|
|
parseJson,
|
|
|
|
readContentOfZipEntry,
|
2024-05-16 05:40:56 +00:00
|
|
|
shorten
|
2024-05-14 09:27:05 +00:00
|
|
|
} from '../../utils'
|
|
|
|
import styles from './style.module.scss'
|
|
|
|
|
|
|
|
export const VerifyPage = () => {
|
2024-05-16 05:40:56 +00:00
|
|
|
const theme = useTheme()
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
const textColor = theme.palette.getContrastText(
|
|
|
|
theme.palette.background.paper
|
|
|
|
)
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
const [isLoading, setIsLoading] = useState(false)
|
2024-05-14 09:27:05 +00:00
|
|
|
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
2024-05-14 09:27:05 +00:00
|
|
|
const [meta, setMeta] = useState<Meta | null>(null)
|
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
const [metadata, setMetadata] = useState<{ [key: string]: ProfileMetadata }>(
|
|
|
|
{}
|
|
|
|
)
|
2024-05-14 09:27:05 +00:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (meta) {
|
2024-05-16 05:40:56 +00:00
|
|
|
const metadataController = new MetadataController()
|
|
|
|
|
|
|
|
const users = [meta.submittedBy, ...meta.signers, ...meta.viewers]
|
|
|
|
|
|
|
|
users.forEach((user) => {
|
|
|
|
if (!(user in metadata)) {
|
|
|
|
metadataController
|
|
|
|
.findMetadata(user)
|
|
|
|
.then((metadataEvent) => {
|
|
|
|
const metadataContent =
|
|
|
|
metadataController.extractProfileMetadataContent(metadataEvent)
|
|
|
|
if (metadataContent)
|
|
|
|
setMetadata((prev) => ({
|
|
|
|
...prev,
|
|
|
|
[user]: metadataContent
|
|
|
|
}))
|
|
|
|
})
|
|
|
|
.catch((err) => {
|
|
|
|
console.error(
|
|
|
|
`error occurred in finding metadata for: ${user}`,
|
|
|
|
err
|
|
|
|
)
|
|
|
|
})
|
2024-05-14 09:27:05 +00:00
|
|
|
}
|
|
|
|
})
|
2024-05-16 05:40:56 +00:00
|
|
|
}
|
|
|
|
}, [meta])
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
const handleVerify = async () => {
|
|
|
|
if (!selectedFile) return
|
|
|
|
setIsLoading(true)
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
const zip = await JSZip.loadAsync(selectedFile).catch((err) => {
|
2024-05-14 09:27:05 +00:00
|
|
|
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 parsedMetaJson = await parseJson<Meta>(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
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
setMeta(parsedMetaJson)
|
2024-05-16 05:40:56 +00:00
|
|
|
setIsLoading(false)
|
2024-05-14 09:27:05 +00:00
|
|
|
}
|
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
const displayUser = (pubkey: string, verifySignature = false) => {
|
|
|
|
const profile = metadata[pubkey]
|
|
|
|
|
|
|
|
let isValidSignature = false
|
|
|
|
|
|
|
|
if (verifySignature) {
|
|
|
|
const signedEventString = meta ? meta.signedEvents[pubkey] : 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
|
|
|
|
)
|
|
|
|
}
|
2024-05-14 09:27:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
return (
|
2024-05-16 11:22:05 +00:00
|
|
|
<>
|
|
|
|
<UserComponent
|
|
|
|
pubkey={pubkey}
|
|
|
|
name={
|
|
|
|
profile?.display_name || profile?.name || shorten(hexToNpub(pubkey))
|
|
|
|
}
|
|
|
|
image={profile?.picture}
|
2024-05-16 05:40:56 +00:00
|
|
|
/>
|
2024-05-16 11:22:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
{verifySignature && (
|
2024-05-16 06:25:30 +00:00
|
|
|
<Typography component="label">
|
2024-05-16 05:40:56 +00:00
|
|
|
({isValidSignature ? 'Valid' : 'Not Valid'} Signature)
|
|
|
|
</Typography>
|
|
|
|
)}
|
2024-05-16 11:22:05 +00:00
|
|
|
</>
|
2024-05-14 09:27:05 +00:00
|
|
|
)
|
2024-05-16 05:40:56 +00:00
|
|
|
}
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
const displayExportedBy = () => {
|
|
|
|
if (!meta || !meta.exportSignature) return null
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
const exportSignatureString = meta.exportSignature
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
try {
|
|
|
|
const exportSignatureEvent = JSON.parse(exportSignatureString) as Event
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
if (verifyEvent(exportSignatureEvent)) {
|
|
|
|
return displayUser(exportSignatureEvent.pubkey)
|
|
|
|
} else {
|
|
|
|
toast.error(`Invalid export signature!`)
|
|
|
|
return (
|
2024-05-16 06:25:30 +00:00
|
|
|
<Typography component="label" sx={{ color: 'red' }}>
|
2024-05-16 05:40:56 +00:00
|
|
|
Invalid export signature
|
|
|
|
</Typography>
|
2024-05-14 09:27:05 +00:00
|
|
|
)
|
|
|
|
}
|
2024-05-16 05:40:56 +00:00
|
|
|
} catch (error) {
|
|
|
|
console.error(`An error occurred wile parsing exportSignature`, error)
|
2024-05-14 09:27:05 +00:00
|
|
|
return null
|
2024-05-16 05:40:56 +00:00
|
|
|
}
|
2024-05-14 09:27:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
|
|
|
<Box className={styles.container}>
|
2024-05-16 05:40:56 +00:00
|
|
|
{!meta && (
|
2024-05-14 09:27:05 +00:00
|
|
|
<>
|
2024-05-15 08:50:21 +00:00
|
|
|
<Typography component="label" variant="h6">
|
2024-05-16 05:40:56 +00:00
|
|
|
Select exported zip file
|
2024-05-14 09:27:05 +00:00
|
|
|
</Typography>
|
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
<MuiFileInput
|
2024-05-16 06:25:30 +00:00
|
|
|
placeholder="Select file"
|
2024-05-16 05:40:56 +00:00
|
|
|
value={selectedFile}
|
|
|
|
onChange={(value) => setSelectedFile(value)}
|
|
|
|
InputProps={{
|
|
|
|
inputProps: {
|
|
|
|
accept: '.zip'
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
/>
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
{selectedFile && (
|
2024-05-15 06:19:28 +00:00
|
|
|
<Box sx={{ mt: 2, display: 'flex', justifyContent: 'center' }}>
|
2024-05-16 06:25:30 +00:00
|
|
|
<Button onClick={handleVerify} variant="contained">
|
2024-05-16 05:40:56 +00:00
|
|
|
Verify
|
2024-05-14 09:27:05 +00:00
|
|
|
</Button>
|
|
|
|
</Box>
|
|
|
|
)}
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
{meta && (
|
2024-05-14 09:27:05 +00:00
|
|
|
<>
|
2024-05-16 05:40:56 +00:00
|
|
|
<List
|
|
|
|
sx={{
|
|
|
|
bgcolor: 'background.paper',
|
|
|
|
marginTop: 2
|
|
|
|
}}
|
|
|
|
subheader={
|
|
|
|
<ListSubheader className={styles.subHeader}>
|
|
|
|
Meta Info
|
|
|
|
</ListSubheader>
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<ListItem
|
|
|
|
sx={{
|
|
|
|
marginTop: 1,
|
|
|
|
gap: '15px'
|
|
|
|
}}
|
|
|
|
>
|
2024-05-16 06:25:30 +00:00
|
|
|
<Typography variant="h6" sx={{ color: textColor }}>
|
2024-05-16 05:40:56 +00:00
|
|
|
Submitted By
|
|
|
|
</Typography>
|
|
|
|
{displayUser(meta.submittedBy)}
|
|
|
|
</ListItem>
|
|
|
|
|
|
|
|
<ListItem
|
|
|
|
sx={{
|
|
|
|
marginTop: 1,
|
|
|
|
gap: '15px'
|
|
|
|
}}
|
|
|
|
>
|
2024-05-16 06:25:30 +00:00
|
|
|
<Typography variant="h6" sx={{ color: textColor }}>
|
2024-05-16 05:40:56 +00:00
|
|
|
Exported By
|
|
|
|
</Typography>
|
|
|
|
{displayExportedBy()}
|
|
|
|
</ListItem>
|
|
|
|
|
|
|
|
{meta.signers.length > 0 && (
|
|
|
|
<ListItem
|
|
|
|
sx={{
|
|
|
|
marginTop: 1,
|
|
|
|
flexDirection: 'column',
|
|
|
|
alignItems: 'flex-start'
|
|
|
|
}}
|
|
|
|
>
|
2024-05-16 06:25:30 +00:00
|
|
|
<Typography variant="h6" sx={{ color: textColor }}>
|
2024-05-16 05:40:56 +00:00
|
|
|
Signers
|
|
|
|
</Typography>
|
|
|
|
<ul className={styles.usersList}>
|
|
|
|
{meta.signers.map((signer) => (
|
2024-05-16 11:22:05 +00:00
|
|
|
<li
|
|
|
|
key={signer}
|
|
|
|
style={{
|
|
|
|
color: textColor,
|
|
|
|
display: 'flex',
|
|
|
|
alignItems: 'center',
|
|
|
|
gap: '15px'
|
|
|
|
}}
|
|
|
|
>
|
2024-05-16 05:40:56 +00:00
|
|
|
{displayUser(signer, true)}
|
|
|
|
</li>
|
|
|
|
))}
|
|
|
|
</ul>
|
|
|
|
</ListItem>
|
|
|
|
)}
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
{meta.viewers.length > 0 && (
|
|
|
|
<ListItem
|
|
|
|
sx={{
|
|
|
|
marginTop: 1,
|
|
|
|
flexDirection: 'column',
|
|
|
|
alignItems: 'flex-start'
|
|
|
|
}}
|
|
|
|
>
|
2024-05-16 06:25:30 +00:00
|
|
|
<Typography variant="h6" sx={{ color: textColor }}>
|
2024-05-16 05:40:56 +00:00
|
|
|
Viewers
|
|
|
|
</Typography>
|
|
|
|
<ul className={styles.usersList}>
|
|
|
|
{meta.viewers.map((viewer) => (
|
|
|
|
<li key={viewer} style={{ color: textColor }}>
|
|
|
|
{displayUser(viewer)}
|
|
|
|
</li>
|
|
|
|
))}
|
|
|
|
</ul>
|
|
|
|
</ListItem>
|
|
|
|
)}
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-16 05:40:56 +00:00
|
|
|
<ListItem
|
|
|
|
sx={{
|
|
|
|
marginTop: 1,
|
|
|
|
flexDirection: 'column',
|
|
|
|
alignItems: 'flex-start'
|
|
|
|
}}
|
|
|
|
>
|
2024-05-16 06:25:30 +00:00
|
|
|
<Typography variant="h6" sx={{ color: textColor }}>
|
2024-05-16 05:40:56 +00:00
|
|
|
Files
|
|
|
|
</Typography>
|
|
|
|
<ul>
|
|
|
|
{Object.keys(meta.fileHashes).map((file, index) => (
|
|
|
|
<li key={index} style={{ color: textColor }}>
|
|
|
|
{file}
|
|
|
|
</li>
|
|
|
|
))}
|
|
|
|
</ul>
|
|
|
|
</ListItem>
|
|
|
|
</List>
|
2024-05-14 09:27:05 +00:00
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</Box>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|