2024-06-12 10:10:34 +00:00
|
|
|
import { Box, Button, Typography } from '@mui/material'
|
2024-05-14 09:27:05 +00:00
|
|
|
import axios from 'axios'
|
|
|
|
import saveAs from 'file-saver'
|
|
|
|
import JSZip from 'jszip'
|
|
|
|
import _ from 'lodash'
|
|
|
|
import { MuiFileInput } from 'mui-file-input'
|
2024-06-12 10:10:34 +00:00
|
|
|
import { Event, verifyEvent } from 'nostr-tools'
|
2024-05-14 09:27:05 +00:00
|
|
|
import { useEffect, useState } from 'react'
|
|
|
|
import { useSelector } from 'react-redux'
|
2024-06-12 10:10:34 +00:00
|
|
|
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
|
2024-05-14 09:27:05 +00:00
|
|
|
import { toast } from 'react-toastify'
|
|
|
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
2024-06-12 10:10:34 +00:00
|
|
|
import { NostrController } from '../../controllers'
|
2024-06-03 14:46:42 +00:00
|
|
|
import { appPublicRoutes } from '../../routes'
|
2024-05-14 09:27:05 +00:00
|
|
|
import { State } from '../../store/rootReducer'
|
2024-06-12 10:10:34 +00:00
|
|
|
import { CreateSignatureEventContent, Meta, SignedEvent } from '../../types'
|
2024-05-14 09:27:05 +00:00
|
|
|
import {
|
|
|
|
decryptArrayBuffer,
|
|
|
|
encryptArrayBuffer,
|
|
|
|
generateEncryptionKey,
|
2024-06-12 10:10:34 +00:00
|
|
|
generateKeysFile,
|
2024-05-14 09:27:05 +00:00
|
|
|
getHash,
|
|
|
|
hexToNpub,
|
2024-06-12 10:10:34 +00:00
|
|
|
isOnline,
|
2024-05-17 08:34:56 +00:00
|
|
|
npubToHex,
|
2024-06-12 10:10:34 +00:00
|
|
|
parseJson,
|
2024-05-14 09:27:05 +00:00
|
|
|
readContentOfZipEntry,
|
|
|
|
sendDM,
|
|
|
|
signEventForMetaFile,
|
2024-06-12 10:10:34 +00:00
|
|
|
uploadToFileStorage
|
2024-05-14 09:27:05 +00:00
|
|
|
} from '../../utils'
|
2024-06-12 10:10:34 +00:00
|
|
|
import { DisplayMeta } from './internal/displayMeta'
|
2024-05-14 09:27:05 +00:00
|
|
|
import styles from './style.module.scss'
|
|
|
|
enum SignedStatus {
|
|
|
|
Fully_Signed,
|
|
|
|
User_Is_Next_Signer,
|
|
|
|
User_Is_Not_Next_Signer
|
|
|
|
}
|
|
|
|
|
2024-05-15 11:11:57 +00:00
|
|
|
export const SignPage = () => {
|
2024-05-16 05:43:37 +00:00
|
|
|
const navigate = useNavigate()
|
2024-06-03 09:01:24 +00:00
|
|
|
const location = useLocation()
|
2024-06-12 10:02:26 +00:00
|
|
|
const { arrayBuffer: decryptedArrayBuffer } = location.state || {}
|
2024-06-03 09:01:24 +00:00
|
|
|
|
2024-06-12 14:44:06 +00:00
|
|
|
const [searchParams, setSearchParams] = useSearchParams()
|
2024-05-14 09:27:05 +00:00
|
|
|
|
|
|
|
const [displayInput, setDisplayInput] = useState(false)
|
|
|
|
|
|
|
|
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
|
|
|
|
|
|
|
const [zip, setZip] = useState<JSZip>()
|
|
|
|
|
|
|
|
const [isLoading, setIsLoading] = useState(true)
|
|
|
|
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
|
|
|
|
|
|
|
const [meta, setMeta] = useState<Meta | null>(null)
|
|
|
|
const [signedStatus, setSignedStatus] = useState<SignedStatus>()
|
|
|
|
|
2024-05-22 06:19:40 +00:00
|
|
|
const [submittedBy, setSubmittedBy] = useState<string>()
|
|
|
|
|
|
|
|
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 [signedBy, setSignedBy] = useState<`npub1${string}`[]>([])
|
|
|
|
|
2024-05-14 09:27:05 +00:00
|
|
|
const [nextSinger, setNextSinger] = useState<string>()
|
|
|
|
|
2024-05-28 10:10:06 +00:00
|
|
|
// This state variable indicates whether the logged-in user is a signer, a creator, or neither.
|
|
|
|
const [isSignerOrCreator, setIsSignerOrCreator] = useState(false)
|
|
|
|
|
2024-05-14 09:27:05 +00:00
|
|
|
const usersPubkey = useSelector((state: State) => state.auth.usersPubkey)
|
|
|
|
|
|
|
|
const [authUrl, setAuthUrl] = useState<string>()
|
|
|
|
const nostrController = NostrController.getInstance()
|
|
|
|
|
|
|
|
useEffect(() => {
|
2024-05-22 06:19:40 +00:00
|
|
|
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'
|
|
|
|
)
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-22 06:19:40 +00:00
|
|
|
if (arrayBuffer) {
|
|
|
|
const hash = await getHash(arrayBuffer)
|
|
|
|
|
|
|
|
if (hash) {
|
|
|
|
fileHashes[fileName.replace(/^files\//, '')] = hash
|
2024-05-14 09:27:05 +00:00
|
|
|
}
|
2024-05-22 06:19:40 +00:00
|
|
|
} else {
|
|
|
|
fileHashes[fileName.replace(/^files\//, '')] = null
|
2024-05-14 09:27:05 +00:00
|
|
|
}
|
|
|
|
}
|
2024-05-22 06:19:40 +00:00
|
|
|
|
|
|
|
setCurrentFileHashes(fileHashes)
|
|
|
|
}
|
|
|
|
|
|
|
|
generateCurrentFileHashes()
|
|
|
|
}
|
|
|
|
}, [zip])
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (signers.length > 0) {
|
|
|
|
// check if all signers have signed then its fully signed
|
|
|
|
if (signers.every((signer) => signedBy.includes(signer))) {
|
2024-05-14 09:27:05 +00:00
|
|
|
setSignedStatus(SignedStatus.Fully_Signed)
|
2024-05-22 06:19:40 +00:00
|
|
|
} else {
|
|
|
|
for (const signer of signers) {
|
|
|
|
if (!signedBy.includes(signer)) {
|
|
|
|
// signers in meta.json are in npub1 format
|
|
|
|
// so, convert it to hex before setting to nextSigner
|
|
|
|
setNextSinger(npubToHex(signer)!)
|
|
|
|
|
|
|
|
const usersNpub = hexToNpub(usersPubkey!)
|
|
|
|
|
|
|
|
if (signer === usersNpub) {
|
|
|
|
// logged in user is the next signer
|
|
|
|
setSignedStatus(SignedStatus.User_Is_Next_Signer)
|
|
|
|
} else {
|
|
|
|
setSignedStatus(SignedStatus.User_Is_Not_Next_Signer)
|
|
|
|
}
|
|
|
|
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2024-05-14 09:27:05 +00:00
|
|
|
}
|
2024-05-22 06:19:40 +00:00
|
|
|
} else {
|
|
|
|
// there's no signer just viewers. So its fully signed
|
|
|
|
setSignedStatus(SignedStatus.Fully_Signed)
|
2024-05-14 09:27:05 +00:00
|
|
|
}
|
2024-05-28 10:10:06 +00:00
|
|
|
|
|
|
|
// Determine and set the status of the user
|
|
|
|
if (submittedBy && usersPubkey && submittedBy === usersPubkey) {
|
|
|
|
// If the submission was made by the user, set the status to true
|
|
|
|
setIsSignerOrCreator(true)
|
|
|
|
} else if (usersPubkey) {
|
|
|
|
// Convert the user's public key from hex to npub format
|
|
|
|
const usersNpub = hexToNpub(usersPubkey)
|
|
|
|
if (signers.includes(usersNpub)) {
|
|
|
|
// If the user's npub is in the list of signers, set the status to true
|
|
|
|
setIsSignerOrCreator(true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, [signers, signedBy, usersPubkey, submittedBy])
|
2024-05-14 09:27:05 +00:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
const fileUrl = searchParams.get('file')
|
2024-06-12 14:44:06 +00:00
|
|
|
const key = searchParams.get('key')
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-12 14:44:06 +00:00
|
|
|
if (fileUrl && key) {
|
2024-05-14 09:27:05 +00:00
|
|
|
setIsLoading(true)
|
|
|
|
setLoadingSpinnerDesc('Fetching file from file server')
|
|
|
|
|
|
|
|
axios
|
|
|
|
.get(fileUrl, {
|
|
|
|
responseType: 'arraybuffer'
|
|
|
|
})
|
2024-06-12 14:44:06 +00:00
|
|
|
.then(async (res) => {
|
2024-05-14 09:27:05 +00:00
|
|
|
const fileName = fileUrl.split('/').pop()
|
|
|
|
const file = new File([res.data], fileName!)
|
|
|
|
|
2024-06-12 14:44:06 +00:00
|
|
|
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
|
2024-05-14 09:27:05 +00:00
|
|
|
})
|
2024-06-12 14:44:06 +00:00
|
|
|
|
|
|
|
if (arrayBuffer) handleDecryptedArrayBuffer(arrayBuffer)
|
2024-05-14 09:27:05 +00:00
|
|
|
})
|
|
|
|
.catch((err) => {
|
|
|
|
console.error(`error occurred in getting file from ${fileUrl}`, err)
|
|
|
|
toast.error(
|
|
|
|
err.message || `error occurred in getting file from ${fileUrl}`
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
setIsLoading(false)
|
|
|
|
})
|
2024-06-12 10:02:26 +00:00
|
|
|
} else if (decryptedArrayBuffer) {
|
|
|
|
handleDecryptedArrayBuffer(decryptedArrayBuffer).finally(() =>
|
|
|
|
setIsLoading(false)
|
|
|
|
)
|
2024-05-14 09:27:05 +00:00
|
|
|
} else {
|
|
|
|
setIsLoading(false)
|
|
|
|
setDisplayInput(true)
|
|
|
|
}
|
2024-06-12 10:02:26 +00:00
|
|
|
}, [searchParams, decryptedArrayBuffer])
|
|
|
|
|
|
|
|
const parseKeysJson = async (zip: JSZip) => {
|
|
|
|
const keysFileContent = await readContentOfZipEntry(
|
|
|
|
zip,
|
|
|
|
'keys.json',
|
|
|
|
'string'
|
|
|
|
)
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-12 10:02:26 +00:00
|
|
|
if (!keysFileContent) return null
|
|
|
|
|
|
|
|
const parsedJSON = await parseJson<{ sender: string; keys: string[] }>(
|
|
|
|
keysFileContent
|
|
|
|
).catch((err) => {
|
|
|
|
console.log(`Error parsing content of keys.json:`, err)
|
|
|
|
toast.error(err.message || `Error parsing content of keys.json`)
|
|
|
|
return null
|
|
|
|
})
|
|
|
|
|
|
|
|
return parsedJSON
|
|
|
|
}
|
|
|
|
|
|
|
|
const decrypt = async (file: File) => {
|
2024-05-14 09:27:05 +00:00
|
|
|
setLoadingSpinnerDesc('Decrypting file')
|
|
|
|
|
2024-06-12 10:02:26 +00:00
|
|
|
const zip = await JSZip.loadAsync(file).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
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-12 10:02:26 +00:00
|
|
|
const parsedKeysJson = await parseKeysJson(zip)
|
|
|
|
if (!parsedKeysJson) return
|
|
|
|
|
|
|
|
const encryptedArrayBuffer = await readContentOfZipEntry(
|
|
|
|
zip,
|
|
|
|
'compressed.sigit',
|
|
|
|
'arraybuffer'
|
|
|
|
)
|
|
|
|
|
|
|
|
if (!encryptedArrayBuffer) return
|
|
|
|
|
|
|
|
const { keys, sender } = parsedKeysJson
|
|
|
|
|
|
|
|
for (const key of keys) {
|
|
|
|
// Set up event listener for authentication event
|
|
|
|
nostrController.on('nsecbunker-auth', (url) => {
|
|
|
|
setAuthUrl(url)
|
2024-05-14 09:27:05 +00:00
|
|
|
})
|
2024-06-12 10:02:26 +00:00
|
|
|
|
|
|
|
// Set up timeout promise to handle encryption timeout
|
|
|
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
|
|
setTimeout(() => {
|
|
|
|
reject(new Error('Timeout occurred'))
|
|
|
|
}, 60000) // Timeout duration = 60 seconds
|
2024-05-14 09:27:05 +00:00
|
|
|
})
|
|
|
|
|
2024-06-12 10:02:26 +00:00
|
|
|
// decrypt the encryptionKey, with timeout
|
|
|
|
const encryptionKey = await Promise.race([
|
|
|
|
nostrController.nip04Decrypt(sender, key),
|
|
|
|
timeoutPromise
|
|
|
|
])
|
|
|
|
.then((res) => {
|
|
|
|
return res
|
|
|
|
})
|
|
|
|
.catch((err) => {
|
|
|
|
console.log('err :>> ', err)
|
|
|
|
return null
|
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
setAuthUrl(undefined) // Clear authentication URL
|
|
|
|
})
|
|
|
|
|
|
|
|
// Return if encryption failed
|
|
|
|
if (!encryptionKey) continue
|
|
|
|
|
|
|
|
const arrayBuffer = await decryptArrayBuffer(
|
|
|
|
encryptedArrayBuffer,
|
|
|
|
encryptionKey
|
|
|
|
)
|
|
|
|
.catch((err) => {
|
|
|
|
console.log('err in decryption:>> ', err)
|
|
|
|
return null
|
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
setIsLoading(false)
|
|
|
|
})
|
|
|
|
|
|
|
|
if (arrayBuffer) return arrayBuffer
|
|
|
|
}
|
|
|
|
|
|
|
|
return null
|
2024-05-14 09:27:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const handleDecryptedArrayBuffer = async (arrayBuffer: ArrayBuffer) => {
|
|
|
|
const decryptedZipFile = new File([arrayBuffer], 'decrypted.zip')
|
|
|
|
|
|
|
|
setLoadingSpinnerDesc('Parsing zip file')
|
|
|
|
|
|
|
|
const zip = await JSZip.loadAsync(decryptedZipFile).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)
|
2024-05-28 10:10:06 +00:00
|
|
|
setDisplayInput(false)
|
2024-05-14 09:27:05 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2024-05-22 06:19:40 +00:00
|
|
|
if (!parsedMetaJson) return
|
|
|
|
|
|
|
|
const createSignatureEvent = await parseJson<Event>(
|
|
|
|
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<CreateSignatureEventContent>(
|
|
|
|
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)
|
|
|
|
|
|
|
|
setSignedBy(Object.keys(parsedMetaJson.docSignatures) as `npub1${string}`[])
|
|
|
|
|
2024-05-14 09:27:05 +00:00
|
|
|
setMeta(parsedMetaJson)
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleDecrypt = async () => {
|
2024-06-12 10:02:26 +00:00
|
|
|
if (!selectedFile) return
|
2024-05-14 09:27:05 +00:00
|
|
|
|
|
|
|
setIsLoading(true)
|
2024-06-12 10:02:26 +00:00
|
|
|
const arrayBuffer = await decrypt(selectedFile)
|
2024-05-14 09:27:05 +00:00
|
|
|
|
|
|
|
if (!arrayBuffer) return
|
|
|
|
|
|
|
|
handleDecryptedArrayBuffer(arrayBuffer)
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleSign = async () => {
|
|
|
|
if (!zip || !meta) return
|
|
|
|
|
|
|
|
setIsLoading(true)
|
|
|
|
setLoadingSpinnerDesc('parsing hashes.json file')
|
|
|
|
|
2024-06-11 11:49:10 +00:00
|
|
|
const hashesFileContent = await readHashesFile()
|
|
|
|
if (!hashesFileContent) return
|
2024-05-14 09:27:05 +00:00
|
|
|
|
|
|
|
if (!hashesFileContent) {
|
|
|
|
setIsLoading(false)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-06-11 11:49:10 +00:00
|
|
|
const hashes = await parseHashes(hashesFileContent)
|
2024-05-14 09:27:05 +00:00
|
|
|
if (!hashes) return
|
|
|
|
|
|
|
|
setLoadingSpinnerDesc('Generating hashes for files')
|
|
|
|
|
|
|
|
setLoadingSpinnerDesc('Signing nostr event')
|
2024-05-24 06:39:28 +00:00
|
|
|
|
|
|
|
const prevSig = getPrevSignersSig(hexToNpub(usersPubkey!))
|
|
|
|
if (!prevSig) return
|
|
|
|
|
2024-06-11 11:49:10 +00:00
|
|
|
const signedEvent = await signEventForMeta(prevSig)
|
|
|
|
if (!signedEvent) return
|
|
|
|
|
|
|
|
const updatedMeta = updateMetaSignatures(meta, signedEvent)
|
|
|
|
|
|
|
|
const stringifiedMeta = JSON.stringify(updatedMeta, null, 2)
|
|
|
|
zip.file('meta.json', stringifiedMeta)
|
|
|
|
|
|
|
|
const metaHash = await getHash(stringifiedMeta)
|
|
|
|
if (!metaHash) return
|
|
|
|
|
|
|
|
const updatedHashes = updateHashes(hashes, metaHash)
|
|
|
|
zip.file('hashes.json', JSON.stringify(updatedHashes, null, 2))
|
|
|
|
|
|
|
|
const arrayBuffer = await generateZipArrayBuffer(zip)
|
|
|
|
if (!arrayBuffer) return
|
|
|
|
|
|
|
|
const key = await generateEncryptionKey()
|
|
|
|
|
|
|
|
setLoadingSpinnerDesc('Encrypting zip file')
|
|
|
|
const encryptedArrayBuffer = await encryptArrayBuffer(arrayBuffer, key)
|
|
|
|
|
|
|
|
if (await isOnline()) {
|
2024-06-12 14:44:06 +00:00
|
|
|
await handleOnlineFlow(encryptedArrayBuffer, key)
|
|
|
|
} else {
|
|
|
|
handleDecryptedArrayBuffer(arrayBuffer).finally(() => setIsLoading(false))
|
2024-06-11 11:49:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read the content of the hashes.json file
|
|
|
|
const readHashesFile = async (): Promise<string | null> => {
|
|
|
|
return await readContentOfZipEntry(zip!, 'hashes.json', 'string').catch(
|
|
|
|
(err) => {
|
|
|
|
console.log('Error reading hashes.json file:', err)
|
|
|
|
setIsLoading(false)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse the JSON content of the hashes file
|
|
|
|
const parseHashes = async (
|
|
|
|
hashesFileContent: string
|
|
|
|
): Promise<Record<string, string> | null> => {
|
|
|
|
return await parseJson<Record<string, string>>(hashesFileContent).catch(
|
|
|
|
(err) => {
|
|
|
|
console.log('Error parsing hashes.json content:', err)
|
|
|
|
toast.error(err.message || 'Error parsing hashes.json content')
|
|
|
|
setIsLoading(false)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sign the event for the meta file
|
|
|
|
const signEventForMeta = async (prevSig: string) => {
|
|
|
|
return await signEventForMetaFile(
|
|
|
|
JSON.stringify({ prevSig }),
|
2024-05-14 09:27:05 +00:00
|
|
|
nostrController,
|
|
|
|
setIsLoading
|
|
|
|
)
|
2024-06-11 11:49:10 +00:00
|
|
|
}
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-11 11:49:10 +00:00
|
|
|
// Update the meta signatures
|
|
|
|
const updateMetaSignatures = (meta: Meta, signedEvent: SignedEvent): Meta => {
|
2024-05-14 09:27:05 +00:00
|
|
|
const metaCopy = _.cloneDeep(meta)
|
2024-05-22 06:19:40 +00:00
|
|
|
metaCopy.docSignatures = {
|
|
|
|
...metaCopy.docSignatures,
|
2024-05-17 08:34:56 +00:00
|
|
|
[hexToNpub(signedEvent.pubkey)]: JSON.stringify(signedEvent, null, 2)
|
2024-05-14 09:27:05 +00:00
|
|
|
}
|
2024-06-11 11:49:10 +00:00
|
|
|
return metaCopy
|
|
|
|
}
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-11 11:49:10 +00:00
|
|
|
// Update the hashes with the new meta hash
|
|
|
|
const updateHashes = (
|
|
|
|
hashes: Record<string, string>,
|
|
|
|
metaHash: string
|
|
|
|
): Record<string, string> => {
|
|
|
|
return {
|
2024-05-14 09:27:05 +00:00
|
|
|
...hashes,
|
|
|
|
[usersPubkey!]: metaHash
|
|
|
|
}
|
2024-06-11 11:49:10 +00:00
|
|
|
}
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-11 11:49:10 +00:00
|
|
|
// Generate the zip array buffer
|
|
|
|
const generateZipArrayBuffer = async (
|
|
|
|
zip: JSZip
|
|
|
|
): Promise<ArrayBuffer | null> => {
|
|
|
|
return await zip
|
2024-05-14 09:27:05 +00:00
|
|
|
.generateAsync({
|
|
|
|
type: 'arraybuffer',
|
|
|
|
compression: 'DEFLATE',
|
|
|
|
compressionOptions: {
|
|
|
|
level: 6
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch((err) => {
|
2024-06-11 11:49:10 +00:00
|
|
|
console.log('Error generating zip file:', err)
|
2024-05-14 09:27:05 +00:00
|
|
|
setIsLoading(false)
|
2024-06-11 11:49:10 +00:00
|
|
|
toast.error(err.message || 'Error generating zip file')
|
2024-05-14 09:27:05 +00:00
|
|
|
return null
|
|
|
|
})
|
2024-06-11 11:49:10 +00:00
|
|
|
}
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-12 10:02:26 +00:00
|
|
|
// create final zip file
|
|
|
|
const createFinalZipFile = async (
|
|
|
|
encryptedArrayBuffer: ArrayBuffer,
|
|
|
|
encryptionKey: string
|
|
|
|
): Promise<File | null> => {
|
|
|
|
// Get the current timestamp in seconds
|
|
|
|
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.sigit`, {
|
|
|
|
type: 'application/sigit'
|
|
|
|
})
|
|
|
|
|
|
|
|
const isLastSigner = checkIsLastSigner(signers)
|
|
|
|
|
|
|
|
const userSet = new Set<string>()
|
|
|
|
|
|
|
|
if (isLastSigner) {
|
|
|
|
if (submittedBy) {
|
|
|
|
userSet.add(submittedBy)
|
|
|
|
}
|
|
|
|
|
|
|
|
signers.forEach((signer) => {
|
|
|
|
userSet.add(npubToHex(signer)!)
|
|
|
|
})
|
|
|
|
|
|
|
|
viewers.forEach((viewer) => {
|
|
|
|
userSet.add(npubToHex(viewer)!)
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
const usersNpub = hexToNpub(usersPubkey!)
|
|
|
|
const signerIndex = signers.indexOf(usersNpub)
|
|
|
|
const nextSigner = signers[signerIndex + 1]
|
|
|
|
userSet.add(npubToHex(nextSigner)!)
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
const finalZipFile = new File(
|
|
|
|
[new Blob([arraybuffer])],
|
|
|
|
`${unixNow}.sigit.zip`,
|
|
|
|
{
|
|
|
|
type: 'application/zip'
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
return finalZipFile
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle errors during zip file generation
|
|
|
|
const handleZipError = (err: any) => {
|
|
|
|
console.log('Error in zip:>> ', err)
|
|
|
|
setIsLoading(false)
|
|
|
|
toast.error(err.message || 'Error occurred in generating zip file')
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
2024-06-11 11:49:10 +00:00
|
|
|
// Handle the online flow: upload file and send DMs
|
2024-06-12 14:44:06 +00:00
|
|
|
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)
|
2024-06-11 11:49:10 +00:00
|
|
|
if (!fileUrl) return
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-11 11:49:10 +00:00
|
|
|
const isLastSigner = checkIsLastSigner(signers)
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-11 11:49:10 +00:00
|
|
|
if (isLastSigner) {
|
2024-06-12 14:44:06 +00:00
|
|
|
await sendDMToAllUsers(fileUrl, encryptionKey)
|
2024-06-11 11:49:10 +00:00
|
|
|
} else {
|
2024-06-12 14:44:06 +00:00
|
|
|
await sendDMToNextSigner(fileUrl, encryptionKey)
|
2024-06-11 11:49:10 +00:00
|
|
|
}
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-12 14:44:06 +00:00
|
|
|
// update search params with updated file url and encryption key
|
|
|
|
setSearchParams({
|
|
|
|
file: fileUrl,
|
|
|
|
key: encryptionKey
|
|
|
|
})
|
|
|
|
|
2024-06-11 11:49:10 +00:00
|
|
|
setIsLoading(false)
|
|
|
|
}
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-12 14:44:06 +00:00
|
|
|
// 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<string | null> => {
|
|
|
|
setIsLoading(true)
|
|
|
|
setLoadingSpinnerDesc('Uploading sigit file to file storage.')
|
|
|
|
|
2024-06-12 10:02:26 +00:00
|
|
|
const fileUrl = await uploadToFileStorage(file, nostrController)
|
2024-06-11 11:49:10 +00:00
|
|
|
.then((url) => {
|
2024-06-12 14:44:06 +00:00
|
|
|
toast.success('Sigit uploaded to file storage')
|
2024-06-11 11:49:10 +00:00
|
|
|
return url
|
|
|
|
})
|
2024-06-12 14:44:06 +00:00
|
|
|
.catch(handleUploadError)
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-11 11:49:10 +00:00
|
|
|
return fileUrl
|
|
|
|
}
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-11 11:49:10 +00:00
|
|
|
// Check if the current user is the last signer
|
|
|
|
const checkIsLastSigner = (signers: string[]): boolean => {
|
|
|
|
const usersNpub = hexToNpub(usersPubkey!)
|
|
|
|
const lastSignerIndex = signers.length - 1
|
|
|
|
const signerIndex = signers.indexOf(usersNpub)
|
|
|
|
return signerIndex === lastSignerIndex
|
|
|
|
}
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-11 11:49:10 +00:00
|
|
|
// Send DM to all users (signers and viewers)
|
2024-06-12 14:44:06 +00:00
|
|
|
const sendDMToAllUsers = async (fileUrl: string, encryptionKey: string) => {
|
2024-06-11 11:49:10 +00:00
|
|
|
const userSet = new Set<`npub1${string}`>()
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-11 11:49:10 +00:00
|
|
|
if (submittedBy) {
|
|
|
|
userSet.add(hexToNpub(submittedBy))
|
|
|
|
}
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-11 11:49:10 +00:00
|
|
|
signers.forEach((signer) => {
|
|
|
|
userSet.add(signer)
|
|
|
|
})
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-11 11:49:10 +00:00
|
|
|
viewers.forEach((viewer) => {
|
|
|
|
userSet.add(viewer)
|
|
|
|
})
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-06-11 11:49:10 +00:00
|
|
|
const users = Array.from(userSet)
|
2024-05-17 08:34:56 +00:00
|
|
|
|
2024-06-11 11:49:10 +00:00
|
|
|
for (const user of users) {
|
|
|
|
await sendDM(
|
|
|
|
fileUrl,
|
2024-06-12 14:44:06 +00:00
|
|
|
encryptionKey,
|
2024-06-11 11:49:10 +00:00
|
|
|
npubToHex(user)!,
|
|
|
|
nostrController,
|
|
|
|
false,
|
|
|
|
setAuthUrl
|
|
|
|
)
|
2024-05-28 10:10:06 +00:00
|
|
|
}
|
2024-05-14 09:27:05 +00:00
|
|
|
}
|
|
|
|
|
2024-06-11 11:49:10 +00:00
|
|
|
// Send DM to the next signer
|
2024-06-12 14:44:06 +00:00
|
|
|
const sendDMToNextSigner = async (fileUrl: string, encryptionKey: string) => {
|
2024-06-11 11:49:10 +00:00
|
|
|
const usersNpub = hexToNpub(usersPubkey!)
|
|
|
|
const signerIndex = signers.indexOf(usersNpub)
|
|
|
|
const nextSigner = signers[signerIndex + 1]
|
|
|
|
await sendDM(
|
|
|
|
fileUrl,
|
2024-06-12 14:44:06 +00:00
|
|
|
encryptionKey,
|
2024-06-11 11:49:10 +00:00
|
|
|
npubToHex(nextSigner)!,
|
|
|
|
nostrController,
|
|
|
|
true,
|
|
|
|
setAuthUrl
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-05-14 09:27:05 +00:00
|
|
|
const handleExport = async () => {
|
|
|
|
if (!meta || !zip || !usersPubkey) return
|
|
|
|
|
2024-05-17 08:34:56 +00:00
|
|
|
const usersNpub = hexToNpub(usersPubkey)
|
2024-05-14 09:27:05 +00:00
|
|
|
if (
|
2024-05-22 06:19:40 +00:00
|
|
|
!signers.includes(usersNpub) &&
|
|
|
|
!viewers.includes(usersNpub) &&
|
|
|
|
submittedBy !== usersNpub
|
2024-05-14 09:27:05 +00:00
|
|
|
)
|
|
|
|
return
|
|
|
|
|
|
|
|
setIsLoading(true)
|
|
|
|
setLoadingSpinnerDesc('Signing nostr event')
|
2024-05-24 06:39:28 +00:00
|
|
|
|
|
|
|
const prevSig = await getLastSignersSig()
|
|
|
|
if (!prevSig) return
|
|
|
|
|
2024-05-22 06:19:40 +00:00
|
|
|
const signedEvent = await signEventForMetaFile(
|
|
|
|
JSON.stringify({
|
2024-05-24 06:39:28 +00:00
|
|
|
prevSig
|
2024-05-22 06:19:40 +00:00
|
|
|
}),
|
|
|
|
nostrController,
|
|
|
|
setIsLoading
|
|
|
|
)
|
2024-05-14 09:27:05 +00:00
|
|
|
|
|
|
|
if (!signedEvent) return
|
|
|
|
|
|
|
|
const exportSignature = JSON.stringify(signedEvent, null, 2)
|
|
|
|
|
|
|
|
const stringifiedMeta = JSON.stringify(
|
|
|
|
{
|
|
|
|
...meta,
|
|
|
|
exportSignature
|
|
|
|
},
|
|
|
|
null,
|
|
|
|
2
|
|
|
|
)
|
|
|
|
zip.file('meta.json', stringifiedMeta)
|
|
|
|
|
|
|
|
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 blob = new Blob([arrayBuffer])
|
|
|
|
saveAs(blob, 'exported.zip')
|
|
|
|
|
|
|
|
setIsLoading(false)
|
2024-05-16 05:43:37 +00:00
|
|
|
|
2024-06-03 14:46:42 +00:00
|
|
|
navigate(appPublicRoutes.verify)
|
2024-05-14 09:27:05 +00:00
|
|
|
}
|
|
|
|
|
2024-05-28 10:10:06 +00:00
|
|
|
const handleExportSigit = async () => {
|
|
|
|
if (!zip) return
|
|
|
|
|
|
|
|
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 key = await generateEncryptionKey()
|
|
|
|
|
|
|
|
setLoadingSpinnerDesc('Encrypting zip file')
|
|
|
|
const encryptedArrayBuffer = await encryptArrayBuffer(arrayBuffer, key)
|
|
|
|
|
2024-06-12 11:21:45 +00:00
|
|
|
const finalZipFile = await createFinalZipFile(encryptedArrayBuffer, key)
|
|
|
|
|
|
|
|
if (!finalZipFile) return
|
2024-06-12 14:44:06 +00:00
|
|
|
const unixNow = Math.floor(Date.now() / 1000)
|
|
|
|
saveAs(finalZipFile, `exported-${unixNow}.sigit.zip`)
|
2024-05-28 10:10:06 +00:00
|
|
|
}
|
|
|
|
|
2024-05-24 06:39:28 +00:00
|
|
|
/**
|
|
|
|
* This function accepts an npub of a signer and return the signature of its previous signer.
|
|
|
|
* This prevSig will be used in the content of the provided signer's signedEvent
|
|
|
|
*/
|
|
|
|
const getPrevSignersSig = (npub: string) => {
|
|
|
|
if (!meta) return null
|
|
|
|
|
|
|
|
// if user is first signer then use creator's signature
|
|
|
|
if (signers[0] === npub) {
|
|
|
|
try {
|
|
|
|
const createSignatureEvent: Event = JSON.parse(meta.createSignature)
|
|
|
|
return createSignatureEvent.sig
|
|
|
|
} catch (error) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// find the index of signer
|
|
|
|
const currentSignerIndex = signers.findIndex((signer) => signer === npub)
|
|
|
|
// return null if could not found user in signer's list
|
|
|
|
if (currentSignerIndex === -1) return null
|
|
|
|
// find prev signer
|
|
|
|
const prevSigner = signers[currentSignerIndex - 1]
|
|
|
|
|
|
|
|
// get the signature of prev signer
|
|
|
|
try {
|
|
|
|
const prevSignersEvent: Event = JSON.parse(meta.docSignatures[prevSigner])
|
|
|
|
return prevSignersEvent.sig
|
|
|
|
} catch (error) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This function returns the signature of last signer
|
|
|
|
* It will be used in the content of export signature's signedEvent
|
|
|
|
*/
|
|
|
|
const getLastSignersSig = () => {
|
|
|
|
if (!meta) return null
|
|
|
|
|
|
|
|
// if there're no signers then use creator's signature
|
|
|
|
if (signers.length === 0) {
|
|
|
|
try {
|
|
|
|
const createSignatureEvent: Event = JSON.parse(meta.createSignature)
|
|
|
|
return createSignatureEvent.sig
|
|
|
|
} catch (error) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// get last signer
|
|
|
|
const lastSigner = signers[signers.length - 1]
|
|
|
|
|
|
|
|
// get the signature of last signer
|
|
|
|
try {
|
|
|
|
const lastSignatureEvent: Event = JSON.parse(
|
|
|
|
meta.docSignatures[lastSigner]
|
|
|
|
)
|
|
|
|
return lastSignatureEvent.sig
|
|
|
|
} catch (error) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-14 09:27:05 +00:00
|
|
|
if (authUrl) {
|
|
|
|
return (
|
|
|
|
<iframe
|
2024-05-16 06:25:30 +00:00
|
|
|
title="Nsecbunker auth"
|
2024-05-14 09:27:05 +00:00
|
|
|
src={authUrl}
|
2024-05-16 06:25:30 +00:00
|
|
|
width="100%"
|
|
|
|
height="500px"
|
2024-05-14 09:27:05 +00:00
|
|
|
/>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
|
|
|
<Box className={styles.container}>
|
|
|
|
{displayInput && (
|
|
|
|
<>
|
2024-05-16 06:25:30 +00:00
|
|
|
<Typography component="label" variant="h6">
|
2024-05-14 09:27:05 +00:00
|
|
|
Select sigit file
|
|
|
|
</Typography>
|
|
|
|
|
2024-05-15 06:19:28 +00:00
|
|
|
<Box className={styles.inputBlock}>
|
2024-05-14 09:27:05 +00:00
|
|
|
<MuiFileInput
|
2024-05-16 06:25:30 +00:00
|
|
|
placeholder="Select file"
|
2024-06-12 11:21:45 +00:00
|
|
|
inputProps={{ accept: '.sigit.zip' }}
|
2024-05-14 09:27:05 +00:00
|
|
|
value={selectedFile}
|
|
|
|
onChange={(value) => setSelectedFile(value)}
|
|
|
|
/>
|
|
|
|
</Box>
|
|
|
|
|
2024-06-12 10:02:26 +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={handleDecrypt} variant="contained">
|
2024-05-14 09:27:05 +00:00
|
|
|
Decrypt
|
|
|
|
</Button>
|
|
|
|
</Box>
|
|
|
|
)}
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
|
2024-05-24 06:39:28 +00:00
|
|
|
{submittedBy && zip && meta && (
|
2024-05-14 09:27:05 +00:00
|
|
|
<>
|
2024-05-22 06:19:40 +00:00
|
|
|
<DisplayMeta
|
2024-05-24 06:39:28 +00:00
|
|
|
meta={meta}
|
2024-05-22 06:19:40 +00:00
|
|
|
zip={zip}
|
|
|
|
submittedBy={submittedBy}
|
|
|
|
signers={signers}
|
|
|
|
viewers={viewers}
|
|
|
|
creatorFileHashes={creatorFileHashes}
|
|
|
|
currentFileHashes={currentFileHashes}
|
|
|
|
signedBy={signedBy}
|
|
|
|
nextSigner={nextSinger}
|
2024-05-24 06:39:28 +00:00
|
|
|
getPrevSignersSig={getPrevSignersSig}
|
2024-05-22 06:19:40 +00:00
|
|
|
/>
|
2024-05-28 10:10:06 +00:00
|
|
|
|
2024-05-22 06:19:40 +00:00
|
|
|
{signedStatus === SignedStatus.Fully_Signed && (
|
|
|
|
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
|
|
|
|
<Button onClick={handleExport} variant="contained">
|
|
|
|
Export
|
|
|
|
</Button>
|
|
|
|
</Box>
|
|
|
|
)}
|
2024-05-14 09:27:05 +00:00
|
|
|
|
2024-05-22 06:19:40 +00:00
|
|
|
{signedStatus === SignedStatus.User_Is_Next_Signer && (
|
|
|
|
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
|
|
|
|
<Button onClick={handleSign} variant="contained">
|
|
|
|
Sign
|
|
|
|
</Button>
|
|
|
|
</Box>
|
|
|
|
)}
|
2024-05-28 10:10:06 +00:00
|
|
|
|
2024-06-12 14:44:06 +00:00
|
|
|
{/* todo: In offline mode export sigit is not visible after last signer has signed*/}
|
|
|
|
{isSignerOrCreator && (
|
|
|
|
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
|
|
|
|
<Button onClick={handleExportSigit} variant="contained">
|
|
|
|
Export Sigit
|
|
|
|
</Button>
|
|
|
|
</Box>
|
|
|
|
)}
|
2024-05-14 09:27:05 +00:00
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</Box>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|