In offline mode create a wrapper zip file #110

Merged
s merged 8 commits from issue-109 into staging 2024-06-13 10:01:31 +00:00
Showing only changes of commit c530abd298 - Show all commits

View File

@ -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.'
)
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
}
const fileHashes: { [key: string]: string } = {}
// generating file hashes
for (const file of selectedFiles) {
const arraybuffer = await file.arrayBuffer().catch((err) => {
// Handle errors during file arrayBuffer conversion
const handleFileError = (file: File) => (err: any) => {
console.log(
`err while getting arrayBuffer of file ${file.name} :>> `,
`Error while getting arrayBuffer of file ${file.name} :>> `,
err
)
toast.error(
err.message || `err while getting arrayBuffer of file ${file.name}`
err.message || `Error while getting arrayBuffer of file ${file.name}`
)
return null
})
}
if (!arraybuffer) return
// Generate hash for each selected file
const generateFileHashes = async (): Promise<{
[key: string]: string
} | null> => {
const fileHashes: { [key: string]: string } = {}
for (const file of selectedFiles) {
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,68 +330,103 @@ 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
compressionOptions: { level: 6 }
})
.catch(handleZipError)
return arraybuffer
}
})
.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 encryptionKey = await generateEncryptionKey()
// Encrypt the zip file with the generated encryption key
const encryptZipFile = async (
arraybuffer: ArrayBuffer,
encryptionKey: string
): Promise<ArrayBuffer> => {
setLoadingSpinnerDesc('Encrypting zip file')
const encryptedArrayBuffer = await encryptArrayBuffer(
arraybuffer,
encryptionKey
).finally(() => setIsLoading(false))
const blob = new Blob([encryptedArrayBuffer])
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((err) => {
console.log('err in upload:>> ', err)
setIsLoading(false)
toast.error(err.message || 'Error occurred in uploading zip file')
return null
})
.catch(handleUploadError)
if (!fileUrl) return
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')
// send DM to first signer if exists
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,
@ -376,9 +437,7 @@ export const CreatePage = () => {
setAuthUrl
)
} else {
// send DM to all viewers if no signer
for (const viewer of viewers) {
// todo: execute in parallel
await sendDM(
fileUrl,
encryptionKey,
@ -389,27 +448,58 @@ export const CreatePage = () => {
)
}
}
setIsLoading(false)
}
// Manage offline scenarios for signing or viewing the file
const handleOffline = (blob: Blob, encryptionKey: string) => {
const signers = users.filter((user) => user.role === UserRole.signer)
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
// 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 encryptZipFile(
arraybuffer,
encryptionKey
)
const blob = new Blob([encryptedArrayBuffer])
return await handleFileUpload(blob, encryptionKey)
}
if (authUrl) {