release #111
@ -221,57 +221,67 @@ export const CreatePage = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const handleCreate = async () => {
|
||||
// Validate inputs before proceeding
|
||||
const validateInputs = (): boolean => {
|
||||
if (!title.trim()) {
|
||||
toast.error('Title can not be empty')
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
if (users.length === 0) {
|
||||
toast.error(
|
||||
'No signer/viewer is provided. At least add one signer or viewer.'
|
||||
'No signer/viewer is provided. At least add one signer or viewer.'
|
||||
)
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
if (selectedFiles.length === 0) {
|
||||
toast.error('No file is selected. Select at least 1 file')
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
setLoadingSpinnerDesc('Generating hashes for files')
|
||||
return true
|
||||
}
|
||||
|
||||
// Handle errors during file arrayBuffer conversion
|
||||
const handleFileError = (file: File) => (err: any) => {
|
||||
console.log(
|
||||
`Error while getting arrayBuffer of file ${file.name} :>> `,
|
||||
err
|
||||
)
|
||||
toast.error(
|
||||
err.message || `Error while getting arrayBuffer of file ${file.name}`
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
// Generate hash for each selected file
|
||||
const generateFileHashes = async (): Promise<{
|
||||
[key: string]: string
|
||||
} | null> => {
|
||||
const fileHashes: { [key: string]: string } = {}
|
||||
|
||||
// generating file hashes
|
||||
for (const file of selectedFiles) {
|
||||
const arraybuffer = await file.arrayBuffer().catch((err) => {
|
||||
console.log(
|
||||
`err while getting arrayBuffer of file ${file.name} :>> `,
|
||||
err
|
||||
)
|
||||
toast.error(
|
||||
err.message || `err while getting arrayBuffer of file ${file.name}`
|
||||
)
|
||||
return null
|
||||
})
|
||||
|
||||
if (!arraybuffer) return
|
||||
const arraybuffer = await file.arrayBuffer().catch(handleFileError(file))
|
||||
if (!arraybuffer) return null
|
||||
|
||||
const hash = await getHash(arraybuffer)
|
||||
|
||||
if (!hash) {
|
||||
setIsLoading(false)
|
||||
return
|
||||
return null
|
||||
}
|
||||
|
||||
fileHashes[file.name] = hash
|
||||
}
|
||||
|
||||
return fileHashes
|
||||
}
|
||||
|
||||
// Create a zip file with the selected files and sign the event
|
||||
const createZipFile = async (fileHashes: {
|
||||
[key: string]: string
|
||||
}): Promise<{ zip: JSZip; createSignature: string } | null> => {
|
||||
const zip = new JSZip()
|
||||
|
||||
// zipping files
|
||||
selectedFiles.forEach((file) => {
|
||||
zip.file(`files/${file.name}`, file)
|
||||
})
|
||||
@ -280,6 +290,7 @@ export const CreatePage = () => {
|
||||
const viewers = users.filter((user) => user.role === UserRole.viewer)
|
||||
|
||||
setLoadingSpinnerDesc('Signing nostr event')
|
||||
|
||||
const createSignature = await signEventForMetaFile(
|
||||
JSON.stringify({
|
||||
signers: signers.map((signer) => hexToNpub(signer.pubkey)),
|
||||
@ -290,12 +301,27 @@ export const CreatePage = () => {
|
||||
setIsLoading
|
||||
)
|
||||
|
||||
if (!createSignature) return
|
||||
if (!createSignature) return null
|
||||
|
||||
try {
|
||||
return {
|
||||
zip,
|
||||
createSignature: JSON.stringify(createSignature, null, 2)
|
||||
}
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// Add metadata and file hashes to the zip file
|
||||
const addMetaToZip = async (
|
||||
zip: JSZip,
|
||||
createSignature: string
|
||||
): Promise<string | null> => {
|
||||
// create content for meta file
|
||||
const meta: Meta = {
|
||||
title,
|
||||
createSignature: JSON.stringify(createSignature, null, 2),
|
||||
createSignature,
|
||||
docSignatures: {}
|
||||
}
|
||||
|
||||
@ -304,112 +330,176 @@ export const CreatePage = () => {
|
||||
zip.file('meta.json', stringifiedMeta)
|
||||
|
||||
const metaHash = await getHash(stringifiedMeta)
|
||||
if (!metaHash) return
|
||||
if (!metaHash) return null
|
||||
|
||||
const metaHashJson = {
|
||||
[usersPubkey!]: metaHash
|
||||
}
|
||||
|
||||
zip.file('hashes.json', JSON.stringify(metaHashJson, null, 2))
|
||||
return metaHash
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
toast.error('An error occurred in converting meta json to string')
|
||||
return
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Generate the zip file
|
||||
const generateZipFile = async (zip: JSZip): Promise<ArrayBuffer | null> => {
|
||||
setLoadingSpinnerDesc('Generating zip file')
|
||||
|
||||
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
|
||||
compressionOptions: { level: 6 }
|
||||
})
|
||||
.catch(handleZipError)
|
||||
|
||||
return arraybuffer
|
||||
}
|
||||
|
||||
// Encrypt the zip file with the generated encryption key
|
||||
const encryptZipFile = async (
|
||||
arraybuffer: ArrayBuffer,
|
||||
encryptionKey: string
|
||||
): Promise<ArrayBuffer> => {
|
||||
setLoadingSpinnerDesc('Encrypting zip file')
|
||||
return encryptArrayBuffer(arraybuffer, encryptionKey).finally(() =>
|
||||
setIsLoading(false)
|
||||
)
|
||||
}
|
||||
|
||||
// Handle file upload and further actions based on online/offline status
|
||||
const handleFileUpload = async (blob: Blob, encryptionKey: string) => {
|
||||
if (await isOnline()) {
|
||||
const fileUrl = await uploadFile(blob)
|
||||
|
||||
if (!fileUrl) return
|
||||
|
||||
await sendDMs(fileUrl, encryptionKey)
|
||||
setIsLoading(false)
|
||||
|
||||
navigate(
|
||||
`${appPrivateRoutes.sign}?file=${encodeURIComponent(fileUrl)}&key=${encodeURIComponent(encryptionKey)}`
|
||||
)
|
||||
} else {
|
||||
handleOffline(blob, encryptionKey)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 zip file')
|
||||
return null
|
||||
}
|
||||
|
||||
// Upload the file to the storage and send DMs to signers/viewers
|
||||
const uploadFile = async (blob: Blob): Promise<string | null> => {
|
||||
setIsLoading(true)
|
||||
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(handleUploadError)
|
||||
|
||||
return fileUrl
|
||||
}
|
||||
|
||||
// Send DMs to signers and viewers with the file URL and encryption key
|
||||
const sendDMs = async (fileUrl: string, encryptionKey: string) => {
|
||||
setLoadingSpinnerDesc('Sending DM to signers/viewers')
|
||||
|
||||
const signers = users.filter((user) => user.role === UserRole.signer)
|
||||
const viewers = users.filter((user) => user.role === UserRole.viewer)
|
||||
|
||||
if (signers.length > 0) {
|
||||
await sendDM(
|
||||
fileUrl,
|
||||
encryptionKey,
|
||||
signers[0].pubkey,
|
||||
nostrController,
|
||||
true,
|
||||
setAuthUrl
|
||||
)
|
||||
} else {
|
||||
for (const viewer of viewers) {
|
||||
await sendDM(
|
||||
fileUrl,
|
||||
encryptionKey,
|
||||
viewer.pubkey,
|
||||
nostrController,
|
||||
false,
|
||||
setAuthUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Manage offline scenarios for signing or viewing the file
|
||||
const handleOffline = (blob: Blob, encryptionKey: string) => {
|
||||
const signers = users.filter((user) => user.role === UserRole.signer)
|
||||
|
||||
if (signers[0] && signers[0].pubkey === usersPubkey) {
|
||||
// Create a File object with the Blob data for offline signing
|
||||
const file = new File([blob], `compressed.sigit`, {
|
||||
type: 'application/sigit'
|
||||
})
|
||||
navigate(appPrivateRoutes.sign, { state: { file, encryptionKey } })
|
||||
} else {
|
||||
// Save the file and show encryption key for offline viewing
|
||||
saveAs(blob, 'request.sigit')
|
||||
setTextToCopy(encryptionKey)
|
||||
setOpenCopyModel(true)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCreate = async () => {
|
||||
if (!validateInputs()) return
|
||||
|
||||
setIsLoading(true)
|
||||
setLoadingSpinnerDesc('Generating hashes for files')
|
||||
|
||||
const fileHashes = await generateFileHashes()
|
||||
if (!fileHashes) return
|
||||
|
||||
const createZipResponse = await createZipFile(fileHashes)
|
||||
if (!createZipResponse) return
|
||||
|
||||
const { zip, createSignature } = createZipResponse
|
||||
|
||||
const metaHash = await addMetaToZip(zip, createSignature)
|
||||
if (!metaHash) return
|
||||
|
||||
setLoadingSpinnerDesc('Generating zip file')
|
||||
|
||||
const arraybuffer = await generateZipFile(zip)
|
||||
if (!arraybuffer) return
|
||||
|
||||
const encryptionKey = await generateEncryptionKey()
|
||||
|
||||
setLoadingSpinnerDesc('Encrypting zip file')
|
||||
const encryptedArrayBuffer = await encryptArrayBuffer(
|
||||
const encryptedArrayBuffer = await encryptZipFile(
|
||||
arraybuffer,
|
||||
encryptionKey
|
||||
).finally(() => setIsLoading(false))
|
||||
|
||||
)
|
||||
const blob = new Blob([encryptedArrayBuffer])
|
||||
|
||||
if (await isOnline()) {
|
||||
setIsLoading(true)
|
||||
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)
|
||||
setIsLoading(false)
|
||||
toast.error(err.message || 'Error occurred in uploading zip file')
|
||||
return null
|
||||
})
|
||||
|
||||
if (!fileUrl) return
|
||||
|
||||
setLoadingSpinnerDesc('Sending DM to signers/viewers')
|
||||
|
||||
// send DM to first signer if exists
|
||||
if (signers.length > 0) {
|
||||
await sendDM(
|
||||
fileUrl,
|
||||
encryptionKey,
|
||||
signers[0].pubkey,
|
||||
nostrController,
|
||||
true,
|
||||
setAuthUrl
|
||||
)
|
||||
} else {
|
||||
// send DM to all viewers if no signer
|
||||
for (const viewer of viewers) {
|
||||
// todo: execute in parallel
|
||||
await sendDM(
|
||||
fileUrl,
|
||||
encryptionKey,
|
||||
viewer.pubkey,
|
||||
nostrController,
|
||||
false,
|
||||
setAuthUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
setIsLoading(false)
|
||||
|
||||
navigate(
|
||||
`${appPrivateRoutes.sign}?file=${encodeURIComponent(
|
||||
fileUrl
|
||||
)}&key=${encodeURIComponent(encryptionKey)}`
|
||||
)
|
||||
} else {
|
||||
if (signers[0] && signers[0].pubkey === usersPubkey) {
|
||||
// Create a File object with the Blob data
|
||||
const file = new File([blob], `compressed.sigit`, {
|
||||
type: 'application/sigit'
|
||||
})
|
||||
|
||||
navigate(appPrivateRoutes.sign, { state: { file, encryptionKey } })
|
||||
} else {
|
||||
saveAs(blob, 'request.sigit')
|
||||
setTextToCopy(encryptionKey)
|
||||
setOpenCopyModel(true)
|
||||
}
|
||||
}
|
||||
return await handleFileUpload(blob, encryptionKey)
|
||||
}
|
||||
|
||||
if (authUrl) {
|
||||
|
Loading…
Reference in New Issue
Block a user