802 lines
24 KiB
TypeScript
802 lines
24 KiB
TypeScript
import { Box, Button, Typography } from '@mui/material'
|
|
import JSZip from 'jszip'
|
|
import { MuiFileInput } from 'mui-file-input'
|
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
import { toast } from 'react-toastify'
|
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
|
import { NostrController } from '../../controllers'
|
|
import {
|
|
DocSignatureEvent,
|
|
Meta,
|
|
SignedEvent,
|
|
OpenTimestamp,
|
|
OpenTimestampUpgradeVerifyResponse
|
|
} from '../../types'
|
|
import {
|
|
decryptArrayBuffer,
|
|
getHash,
|
|
hexToNpub,
|
|
unixNow,
|
|
parseJson,
|
|
readContentOfZipEntry,
|
|
signEventForMetaFile,
|
|
getCurrentUserFiles,
|
|
npubToHex,
|
|
generateEncryptionKey,
|
|
encryptArrayBuffer,
|
|
generateKeysFile,
|
|
ARRAY_BUFFER,
|
|
DEFLATE,
|
|
uploadMetaToFileStorage,
|
|
decrypt
|
|
} from '../../utils'
|
|
import styles from './style.module.scss'
|
|
import { useLocation, useParams } from 'react-router-dom'
|
|
import axios from 'axios'
|
|
import { FONT_SIZE, FONT_TYPE, inPx } from '../../utils/pdf.ts'
|
|
import { useAppSelector, useNDK } from '../../hooks'
|
|
import { saveAs } from 'file-saver'
|
|
import { Container } from '../../components/Container'
|
|
import { useSigitMeta } from '../../hooks/useSigitMeta.tsx'
|
|
import { StickySideColumns } from '../../layouts/StickySideColumns.tsx'
|
|
import { UsersDetails } from '../../components/UsersDetails.tsx'
|
|
import FileList from '../../components/FileList'
|
|
import { CurrentUserFile } from '../../types/file.ts'
|
|
import { Mark } from '../../types/mark.ts'
|
|
import React from 'react'
|
|
import {
|
|
convertToSigitFile,
|
|
getZipWithFiles,
|
|
SigitFile
|
|
} from '../../utils/file.ts'
|
|
import { FileDivider } from '../../components/FileDivider.tsx'
|
|
import { ExtensionFileBox } from '../../components/ExtensionFileBox.tsx'
|
|
import { useScale } from '../../hooks/useScale.tsx'
|
|
import {
|
|
faCircleInfo,
|
|
faFile,
|
|
faFileDownload
|
|
} from '@fortawesome/free-solid-svg-icons'
|
|
import { upgradeAndVerifyTimestamp } from '../../utils/opentimestamps.ts'
|
|
import _ from 'lodash'
|
|
import { MarkRender } from '../../components/MarkTypeStrategy/MarkRender.tsx'
|
|
import { SignerService } from '../../services/index.ts'
|
|
|
|
interface PdfViewProps {
|
|
files: CurrentUserFile[]
|
|
currentFile: CurrentUserFile | null
|
|
parsedSignatureEvents: {
|
|
[signer: `npub1${string}`]: DocSignatureEvent
|
|
}
|
|
}
|
|
|
|
const SlimPdfView = ({
|
|
files,
|
|
currentFile,
|
|
parsedSignatureEvents
|
|
}: PdfViewProps) => {
|
|
const pdfRefs = useRef<(HTMLDivElement | null)[]>([])
|
|
const { from } = useScale()
|
|
useEffect(() => {
|
|
if (currentFile !== null && !!pdfRefs.current[currentFile.id]) {
|
|
pdfRefs.current[currentFile.id]?.scrollIntoView({
|
|
behavior: 'smooth'
|
|
})
|
|
}
|
|
}, [currentFile])
|
|
return (
|
|
<div className="files-wrapper">
|
|
{files.length > 0 ? (
|
|
files.map((currentUserFile, i) => {
|
|
const { hash, file, id } = currentUserFile
|
|
const signatureEvents = Object.keys(parsedSignatureEvents)
|
|
if (!hash) return
|
|
return (
|
|
<React.Fragment key={file.name}>
|
|
<div
|
|
id={file.name}
|
|
ref={(el) => (pdfRefs.current[id] = el)}
|
|
className="file-wrapper"
|
|
>
|
|
{file.isPdf &&
|
|
file.pages?.map((page, i) => {
|
|
const marks: Mark[] = []
|
|
|
|
signatureEvents.forEach((e) => {
|
|
const m = parsedSignatureEvents[
|
|
e as `npub1${string}`
|
|
].parsedContent?.marks.filter(
|
|
(m) =>
|
|
(m.pdfFileHash
|
|
? m.pdfFileHash == hash
|
|
: m.fileHash == hash) && m.location.page == i
|
|
)
|
|
if (m) {
|
|
marks.push(...m)
|
|
}
|
|
})
|
|
return (
|
|
<div className="image-wrapper" key={i}>
|
|
<img
|
|
draggable="false"
|
|
src={page.image}
|
|
alt={`page ${i} of ${file.name}`}
|
|
/>
|
|
{marks.map((m) => {
|
|
return (
|
|
<div
|
|
className={`file-mark ${styles.mark}`}
|
|
key={m.id}
|
|
style={{
|
|
left: inPx(from(page.width, m.location.left)),
|
|
top: inPx(from(page.width, m.location.top)),
|
|
width: inPx(from(page.width, m.location.width)),
|
|
height: inPx(
|
|
from(page.width, m.location.height)
|
|
),
|
|
fontFamily: FONT_TYPE,
|
|
fontSize: inPx(from(page.width, FONT_SIZE))
|
|
}}
|
|
>
|
|
<MarkRender
|
|
markType={m.type}
|
|
value={m.value}
|
|
mark={m}
|
|
/>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
)
|
|
})}
|
|
{file.isImage && (
|
|
<img
|
|
className="file-image"
|
|
src={file.objectUrl}
|
|
alt={file.name}
|
|
/>
|
|
)}
|
|
{!(file.isPdf || file.isImage) && (
|
|
<ExtensionFileBox extension={file.extension} />
|
|
)}
|
|
</div>
|
|
{i < files.length - 1 && <FileDivider />}
|
|
</React.Fragment>
|
|
)
|
|
})
|
|
) : (
|
|
<LoadingSpinner variant="small" />
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export const VerifyPage = () => {
|
|
const location = useLocation()
|
|
const params = useParams()
|
|
const { updateUsersAppData, sendNotification } = useNDK()
|
|
|
|
const usersAppData = useAppSelector((state) => state.userAppData)
|
|
const usersPubkey = useAppSelector((state) => state.auth.usersPubkey)
|
|
|
|
const nostrController = NostrController.getInstance()
|
|
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
|
|
|
/**
|
|
* uploadedZip will be received from home page when a user uploads a sigit zip wrapper that contains meta.json
|
|
* meta will be received in navigation from create & home page in online mode
|
|
*/
|
|
let metaInNavState = location?.state?.meta || undefined
|
|
const uploadedZip = location?.state?.uploadedZip || undefined
|
|
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
|
|
|
/**
|
|
* If `userAppData` is present it means user is logged in and we can extract list of `sigits` from the store.
|
|
* If ID is present in the URL we search in the `sigits` list
|
|
* Otherwise sigit is set from the `location.state.meta`
|
|
*/
|
|
if (usersAppData) {
|
|
const sigitCreateId = params.id
|
|
|
|
if (sigitCreateId) {
|
|
const sigit = usersAppData.sigits[sigitCreateId]
|
|
|
|
if (sigit) {
|
|
metaInNavState = sigit
|
|
}
|
|
}
|
|
}
|
|
|
|
const [meta, setMeta] = useState<Meta>(metaInNavState)
|
|
|
|
const {
|
|
submittedBy,
|
|
zipUrl,
|
|
encryptionKey,
|
|
signers,
|
|
viewers,
|
|
fileHashes,
|
|
parsedSignatureEvents,
|
|
timestamps
|
|
} = useSigitMeta(meta)
|
|
|
|
const [files, setFiles] = useState<{ [filename: string]: SigitFile }>({})
|
|
|
|
const [currentFile, setCurrentFile] = useState<CurrentUserFile | null>(null)
|
|
const [currentFileHashes, setCurrentFileHashes] = useState<{
|
|
[key: string]: string | null
|
|
}>({})
|
|
|
|
const signTimestampEvent = async (signerContent: {
|
|
timestamps: OpenTimestamp[]
|
|
}): Promise<SignedEvent | null> => {
|
|
return await signEventForMetaFile(
|
|
JSON.stringify(signerContent),
|
|
nostrController,
|
|
setIsLoading
|
|
)
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (Object.entries(files).length > 0) {
|
|
const tmp = getCurrentUserFiles(files, currentFileHashes, fileHashes)
|
|
setCurrentFile(tmp[0])
|
|
}
|
|
}, [currentFileHashes, fileHashes, files])
|
|
|
|
useEffect(() => {
|
|
if (
|
|
timestamps &&
|
|
timestamps.length > 0 &&
|
|
usersPubkey &&
|
|
submittedBy &&
|
|
parsedSignatureEvents
|
|
) {
|
|
if (timestamps.every((t) => !!t.verification)) {
|
|
return
|
|
}
|
|
const upgradeT = async (timestamps: OpenTimestamp[]) => {
|
|
try {
|
|
setLoadingSpinnerDesc('Upgrading your timestamps.')
|
|
|
|
const findCreatorTimestamp = (timestamps: OpenTimestamp[]) => {
|
|
if (usersPubkey === submittedBy) {
|
|
return timestamps[0]
|
|
}
|
|
}
|
|
|
|
const findSignerTimestamp = (timestamps: OpenTimestamp[]) => {
|
|
const parsedEvent = parsedSignatureEvents[hexToNpub(usersPubkey)]
|
|
if (parsedEvent?.id) {
|
|
return timestamps.find((t) => t.nostrId === parsedEvent.id)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if timestamp verification has been achieved for the first time.
|
|
* Note that the upgrade flag is separate from verification. It is possible for a timestamp
|
|
* to not be upgraded, but to be verified for the first time.
|
|
* @param upgradedTimestamp
|
|
* @param timestamps
|
|
*/
|
|
const isNewlyVerified = (
|
|
upgradedTimestamp: OpenTimestampUpgradeVerifyResponse,
|
|
timestamps: OpenTimestamp[]
|
|
) => {
|
|
if (!upgradedTimestamp.verified) return false
|
|
const oldT = timestamps.find(
|
|
(t) => t.nostrId === upgradedTimestamp.timestamp.nostrId
|
|
)
|
|
if (!oldT) return false
|
|
if (!oldT.verification && upgradedTimestamp.verified) return true
|
|
}
|
|
|
|
const userTimestamps: OpenTimestamp[] = []
|
|
|
|
const creatorTimestamp = findCreatorTimestamp(timestamps)
|
|
if (creatorTimestamp) {
|
|
userTimestamps.push(creatorTimestamp)
|
|
}
|
|
|
|
const signerTimestamp = findSignerTimestamp(timestamps)
|
|
if (signerTimestamp) {
|
|
userTimestamps.push(signerTimestamp)
|
|
}
|
|
|
|
if (userTimestamps.every((t) => !!t.verification)) {
|
|
return
|
|
}
|
|
|
|
const upgradedUserTimestamps = await Promise.all(
|
|
userTimestamps.map(upgradeAndVerifyTimestamp)
|
|
)
|
|
|
|
const upgradedTimestamps = upgradedUserTimestamps
|
|
.filter((t) => t.upgraded || isNewlyVerified(t, userTimestamps))
|
|
.map((t) => {
|
|
const timestamp: OpenTimestamp = { ...t.timestamp }
|
|
if (t.verified) {
|
|
timestamp.verification = t.verification
|
|
}
|
|
return timestamp
|
|
})
|
|
|
|
if (upgradedTimestamps.length === 0) {
|
|
return
|
|
}
|
|
|
|
setLoadingSpinnerDesc('Signing a timestamp upgrade event.')
|
|
|
|
const signedEvent = await signTimestampEvent({
|
|
timestamps: upgradedTimestamps
|
|
})
|
|
if (!signedEvent) return
|
|
|
|
const finalTimestamps = timestamps.map((t) => {
|
|
const upgraded = upgradedTimestamps.find(
|
|
(tu) => tu.nostrId === t.nostrId
|
|
)
|
|
if (upgraded) {
|
|
return {
|
|
...upgraded,
|
|
signature: JSON.stringify(signedEvent, null, 2)
|
|
}
|
|
}
|
|
return t
|
|
})
|
|
|
|
const updatedMeta = _.cloneDeep(meta)
|
|
updatedMeta.timestamps = [...finalTimestamps]
|
|
updatedMeta.modifiedAt = unixNow()
|
|
|
|
const updatedEvent = await updateUsersAppData([updatedMeta])
|
|
if (!updatedEvent) return
|
|
|
|
const metaUrl = await uploadMetaToFileStorage(
|
|
updatedMeta,
|
|
encryptionKey
|
|
)
|
|
|
|
const userSet = new Set<`npub1${string}`>()
|
|
signers.forEach((signer) => {
|
|
if (signer !== usersPubkey) {
|
|
userSet.add(signer)
|
|
}
|
|
})
|
|
|
|
viewers.forEach((viewer) => {
|
|
userSet.add(viewer)
|
|
})
|
|
|
|
const users = Array.from(userSet)
|
|
const promises = users.map((user) =>
|
|
sendNotification(npubToHex(user)!, {
|
|
metaUrl,
|
|
keys: meta.keys!
|
|
})
|
|
)
|
|
|
|
await Promise.all(promises)
|
|
|
|
toast.success('Timestamp updates have been sent successfully.')
|
|
|
|
setMeta(meta)
|
|
} catch (err) {
|
|
console.error(err)
|
|
toast.error(
|
|
'There was an error upgrading or verifying your timestamps!'
|
|
)
|
|
}
|
|
}
|
|
upgradeT(timestamps)
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [timestamps, submittedBy, parsedSignatureEvents])
|
|
|
|
useEffect(() => {
|
|
if (metaInNavState && encryptionKey) {
|
|
const processSigit = async () => {
|
|
setIsLoading(true)
|
|
|
|
setLoadingSpinnerDesc('Fetching file from file server')
|
|
try {
|
|
const res = await axios.get(zipUrl, {
|
|
responseType: 'arraybuffer'
|
|
})
|
|
|
|
const fileName = zipUrl.split('/').pop()
|
|
const file = new File([res.data], fileName!)
|
|
|
|
const encryptedArrayBuffer = await file.arrayBuffer()
|
|
const arrayBuffer = await decryptArrayBuffer(
|
|
encryptedArrayBuffer,
|
|
encryptionKey
|
|
).catch((err) => {
|
|
console.log('err in decryption:>> ', err)
|
|
toast.error(err.message || 'An error occurred in decrypting file.')
|
|
return null
|
|
})
|
|
|
|
if (arrayBuffer) {
|
|
const zip = await JSZip.loadAsync(arrayBuffer).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
|
|
|
|
const files: { [fileName: string]: SigitFile } = {}
|
|
const fileHashes: { [key: string]: string | null } = {}
|
|
const fileNames = Object.values(zip.files).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) {
|
|
files[fileName] = await convertToSigitFile(
|
|
arrayBuffer,
|
|
fileName!
|
|
)
|
|
const hash = await getHash(arrayBuffer)
|
|
|
|
if (hash) {
|
|
fileHashes[fileName.replace(/^files\//, '')] = hash
|
|
}
|
|
} else {
|
|
fileHashes[fileName.replace(/^files\//, '')] = null
|
|
}
|
|
}
|
|
|
|
setCurrentFileHashes(fileHashes)
|
|
setFiles(files)
|
|
setIsLoading(false)
|
|
}
|
|
} catch (err) {
|
|
const message = `error occurred in getting file from ${zipUrl}`
|
|
console.error(message, err)
|
|
if (err instanceof Error) toast.error(err.message)
|
|
else toast.error(message)
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}
|
|
|
|
processSigit()
|
|
}
|
|
}, [encryptionKey, metaInNavState, zipUrl])
|
|
|
|
const handleVerify = useCallback(async (selectedFile: File) => {
|
|
setIsLoading(true)
|
|
setLoadingSpinnerDesc('Loading zip file')
|
|
|
|
let 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 setIsLoading(false)
|
|
}
|
|
|
|
if ('keys.json' in zip.files) {
|
|
// Decrypt
|
|
setLoadingSpinnerDesc('Decrypting zip file content')
|
|
const arrayBuffer = await decrypt(selectedFile).catch((err) => {
|
|
console.error(`error occurred in decryption`, err)
|
|
toast.error(err.message || `error occurred in decryption`)
|
|
})
|
|
|
|
if (arrayBuffer) {
|
|
// Replace the zip and continue processing
|
|
zip = await JSZip.loadAsync(arrayBuffer)
|
|
}
|
|
}
|
|
|
|
setLoadingSpinnerDesc('Opening zip file content')
|
|
|
|
const files: { [filename: string]: SigitFile } = {}
|
|
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 zipFilePath of fileNames) {
|
|
const arrayBuffer = await readContentOfZipEntry(
|
|
zip,
|
|
zipFilePath,
|
|
'arraybuffer'
|
|
)
|
|
|
|
const fileName = zipFilePath.replace(/^files\//, '')
|
|
if (arrayBuffer) {
|
|
files[fileName] = await convertToSigitFile(arrayBuffer, fileName)
|
|
const hash = await getHash(arrayBuffer)
|
|
|
|
if (hash) {
|
|
fileHashes[fileName] = hash
|
|
}
|
|
} else {
|
|
fileHashes[fileName] = null
|
|
}
|
|
}
|
|
|
|
setFiles(files)
|
|
setCurrentFileHashes(fileHashes)
|
|
|
|
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
|
|
}
|
|
)
|
|
|
|
if (!parsedMetaJson) {
|
|
setIsLoading(false)
|
|
return
|
|
}
|
|
|
|
setMeta(parsedMetaJson)
|
|
|
|
setIsLoading(false)
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (uploadedZip) {
|
|
handleVerify(uploadedZip)
|
|
}
|
|
}, [handleVerify, uploadedZip])
|
|
|
|
// Handle errors during zip file generation
|
|
const handleZipError = (err: unknown) => {
|
|
console.log('Error in zip:>> ', err)
|
|
setIsLoading(false)
|
|
if (err instanceof Error) {
|
|
toast.error(err.message || 'Error occurred in generating zip file')
|
|
}
|
|
return null
|
|
}
|
|
|
|
// create final zip file
|
|
const createFinalZipFile = async (
|
|
encryptedArrayBuffer: ArrayBuffer,
|
|
encryptionKey: string
|
|
): Promise<File | null> => {
|
|
// Get the current timestamp in seconds
|
|
const blob = new Blob([encryptedArrayBuffer])
|
|
// Create a File object with the Blob data
|
|
const file = new File([blob], `compressed.sigit`, {
|
|
type: 'application/sigit'
|
|
})
|
|
|
|
const userSet = new Set<string>()
|
|
if (submittedBy) {
|
|
userSet.add(submittedBy)
|
|
}
|
|
signers.forEach((signer) => {
|
|
userSet.add(npubToHex(signer)!)
|
|
})
|
|
viewers.forEach((viewer) => {
|
|
userSet.add(npubToHex(viewer)!)
|
|
})
|
|
|
|
const keysFileContent = await generateKeysFile(
|
|
Array.from(userSet),
|
|
encryptionKey
|
|
)
|
|
if (!keysFileContent) return null
|
|
|
|
const zip = new JSZip()
|
|
zip.file(`compressed.sigit`, file)
|
|
zip.file('keys.json', keysFileContent)
|
|
|
|
const arraybuffer = await zip
|
|
.generateAsync({
|
|
type: 'arraybuffer',
|
|
compression: 'DEFLATE',
|
|
compressionOptions: { level: 6 }
|
|
})
|
|
.catch(handleZipError)
|
|
|
|
if (!arraybuffer) return null
|
|
|
|
return new File([new Blob([arraybuffer])], `${unixNow()}.sigit.zip`, {
|
|
type: 'application/zip'
|
|
})
|
|
}
|
|
|
|
const handleExport = async () => {
|
|
const arrayBuffer = await prepareZipExport()
|
|
if (!arrayBuffer) {
|
|
setIsLoading(false)
|
|
return
|
|
}
|
|
|
|
const blob = new Blob([arrayBuffer])
|
|
saveAs(blob, `exported-${unixNow()}.sigit.zip`)
|
|
|
|
setIsLoading(false)
|
|
}
|
|
|
|
const handleEncryptedExport = async () => {
|
|
const arrayBuffer = await prepareZipExport()
|
|
if (!arrayBuffer) {
|
|
setIsLoading(false)
|
|
return
|
|
}
|
|
|
|
const key = await generateEncryptionKey()
|
|
|
|
setLoadingSpinnerDesc('Encrypting zip file')
|
|
const encryptedArrayBuffer = await encryptArrayBuffer(arrayBuffer, key)
|
|
|
|
const finalZipFile = await createFinalZipFile(encryptedArrayBuffer, key)
|
|
|
|
if (!finalZipFile) {
|
|
setIsLoading(false)
|
|
return
|
|
}
|
|
|
|
saveAs(finalZipFile, `exported-${unixNow()}.sigit.zip`)
|
|
|
|
setIsLoading(false)
|
|
}
|
|
|
|
const prepareZipExport = async (): Promise<ArrayBuffer | null> => {
|
|
if (Object.entries(files).length === 0 || !meta || !usersPubkey)
|
|
return Promise.resolve(null)
|
|
|
|
const usersNpub = hexToNpub(usersPubkey)
|
|
if (
|
|
!signers.includes(usersNpub) &&
|
|
!viewers.includes(usersNpub) &&
|
|
submittedBy !== usersNpub
|
|
) {
|
|
return Promise.resolve(null)
|
|
}
|
|
|
|
setIsLoading(true)
|
|
setLoadingSpinnerDesc('Signing nostr event')
|
|
|
|
if (!meta) return Promise.resolve(null)
|
|
|
|
const signerService = new SignerService(meta)
|
|
const prevSig = signerService.getLastSignerSig()
|
|
|
|
if (!prevSig) return Promise.resolve(null)
|
|
|
|
const signedEvent = await signEventForMetaFile(
|
|
JSON.stringify({ prevSig }),
|
|
nostrController,
|
|
setIsLoading
|
|
)
|
|
|
|
if (!signedEvent) return Promise.resolve(null)
|
|
|
|
const exportSignature = JSON.stringify(signedEvent, null, 2)
|
|
const updatedMeta = { ...meta, exportSignature }
|
|
const stringifiedMeta = JSON.stringify(updatedMeta, null, 2)
|
|
|
|
const zip = await getZipWithFiles(updatedMeta, files)
|
|
zip.file('meta.json', stringifiedMeta)
|
|
|
|
const arrayBuffer = await zip
|
|
.generateAsync({
|
|
type: ARRAY_BUFFER,
|
|
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 Promise.resolve(null)
|
|
|
|
return Promise.resolve(arrayBuffer)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
|
<Container className={styles.container}>
|
|
{!meta && (
|
|
<>
|
|
<Typography component="label" variant="h6">
|
|
Select exported zip file
|
|
</Typography>
|
|
|
|
<MuiFileInput
|
|
placeholder="Select file"
|
|
value={selectedFile}
|
|
onChange={(value) => setSelectedFile(value)}
|
|
InputProps={{
|
|
inputProps: {
|
|
accept: '.sigit.zip'
|
|
}
|
|
}}
|
|
/>
|
|
|
|
{selectedFile && (
|
|
<Box sx={{ mt: 2, display: 'flex', justifyContent: 'center' }}>
|
|
<Button
|
|
onClick={() => handleVerify(selectedFile)}
|
|
variant="contained"
|
|
>
|
|
Verify
|
|
</Button>
|
|
</Box>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{meta && (
|
|
<StickySideColumns
|
|
left={
|
|
currentFile !== null && (
|
|
<FileList
|
|
files={getCurrentUserFiles(
|
|
files,
|
|
currentFileHashes,
|
|
fileHashes
|
|
)}
|
|
currentFile={currentFile}
|
|
setCurrentFile={setCurrentFile}
|
|
handleExport={handleExport}
|
|
handleEncryptedExport={handleEncryptedExport}
|
|
/>
|
|
)
|
|
}
|
|
right={<UsersDetails meta={meta} />}
|
|
leftIcon={faFileDownload}
|
|
centerIcon={faFile}
|
|
rightIcon={faCircleInfo}
|
|
>
|
|
<SlimPdfView
|
|
currentFile={currentFile}
|
|
files={getCurrentUserFiles(files, currentFileHashes, fileHashes)}
|
|
parsedSignatureEvents={parsedSignatureEvents}
|
|
/>
|
|
</StickySideColumns>
|
|
)}
|
|
</Container>
|
|
</>
|
|
)
|
|
}
|