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()) { if (!title.trim()) {
toast.error('Title can not be empty') toast.error('Title can not be empty')
return return false
} }
if (users.length === 0) { if (users.length === 0) {
toast.error( 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) { if (selectedFiles.length === 0) {
toast.error('No file is selected. Select at least 1 file') toast.error('No file is selected. Select at least 1 file')
return return false
} }
setIsLoading(true) return true
setLoadingSpinnerDesc('Generating hashes for files') }
const fileHashes: { [key: string]: string } = {} // Handle errors during file arrayBuffer conversion
const handleFileError = (file: File) => (err: any) => {
// generating file hashes
for (const file of selectedFiles) {
const arraybuffer = await file.arrayBuffer().catch((err) => {
console.log( console.log(
`err while getting arrayBuffer of file ${file.name} :>> `, `Error while getting arrayBuffer of file ${file.name} :>> `,
err err
) )
toast.error( toast.error(
err.message || `err while getting arrayBuffer of file ${file.name}` err.message || `Error while getting arrayBuffer of file ${file.name}`
) )
return null 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) const hash = await getHash(arraybuffer)
if (!hash) { if (!hash) {
setIsLoading(false) return null
return
} }
fileHashes[file.name] = hash 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() const zip = new JSZip()
// zipping files
selectedFiles.forEach((file) => { selectedFiles.forEach((file) => {
zip.file(`files/${file.name}`, file) zip.file(`files/${file.name}`, file)
}) })
@ -280,6 +290,7 @@ export const CreatePage = () => {
const viewers = users.filter((user) => user.role === UserRole.viewer) const viewers = users.filter((user) => user.role === UserRole.viewer)
setLoadingSpinnerDesc('Signing nostr event') setLoadingSpinnerDesc('Signing nostr event')
const createSignature = await signEventForMetaFile( const createSignature = await signEventForMetaFile(
JSON.stringify({ JSON.stringify({
signers: signers.map((signer) => hexToNpub(signer.pubkey)), signers: signers.map((signer) => hexToNpub(signer.pubkey)),
@ -290,12 +301,27 @@ export const CreatePage = () => {
setIsLoading 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 // create content for meta file
const meta: Meta = { const meta: Meta = {
title, title,
createSignature: JSON.stringify(createSignature, null, 2), createSignature,
docSignatures: {} docSignatures: {}
} }
@ -304,68 +330,103 @@ export const CreatePage = () => {
zip.file('meta.json', stringifiedMeta) zip.file('meta.json', stringifiedMeta)
const metaHash = await getHash(stringifiedMeta) const metaHash = await getHash(stringifiedMeta)
if (!metaHash) return if (!metaHash) return null
const metaHashJson = { const metaHashJson = {
[usersPubkey!]: metaHash [usersPubkey!]: metaHash
} }
zip.file('hashes.json', JSON.stringify(metaHashJson, null, 2)) zip.file('hashes.json', JSON.stringify(metaHashJson, null, 2))
return metaHash
} catch (err) { } catch (err) {
console.error(err) console.error(err)
toast.error('An error occurred in converting meta json to string') 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') setLoadingSpinnerDesc('Generating zip file')
const arraybuffer = await zip const arraybuffer = await zip
.generateAsync({ .generateAsync({
type: 'arraybuffer', type: 'arraybuffer',
compression: 'DEFLATE', compression: 'DEFLATE',
compressionOptions: { compressionOptions: { level: 6 }
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') setLoadingSpinnerDesc('Encrypting zip file')
const encryptedArrayBuffer = await encryptArrayBuffer( return encryptArrayBuffer(arraybuffer, encryptionKey).finally(() =>
arraybuffer, setIsLoading(false)
encryptionKey )
).finally(() => setIsLoading(false)) }
const blob = new Blob([encryptedArrayBuffer])
// Handle file upload and further actions based on online/offline status
const handleFileUpload = async (blob: Blob, encryptionKey: string) => {
if (await isOnline()) { 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) setIsLoading(true)
setLoadingSpinnerDesc('Uploading zip file to file storage.') setLoadingSpinnerDesc('Uploading zip file to file storage.')
const fileUrl = await uploadToFileStorage(blob, nostrController) const fileUrl = await uploadToFileStorage(blob, nostrController)
.then((url) => { .then((url) => {
toast.success('zip file uploaded to file storage') toast.success('zip file uploaded to file storage')
return url return url
}) })
.catch((err) => { .catch(handleUploadError)
console.log('err in upload:>> ', err)
setIsLoading(false)
toast.error(err.message || 'Error occurred in uploading zip file')
return null
})
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') 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) { if (signers.length > 0) {
await sendDM( await sendDM(
fileUrl, fileUrl,
@ -376,9 +437,7 @@ export const CreatePage = () => {
setAuthUrl setAuthUrl
) )
} else { } else {
// send DM to all viewers if no signer
for (const viewer of viewers) { for (const viewer of viewers) {
// todo: execute in parallel
await sendDM( await sendDM(
fileUrl, fileUrl,
encryptionKey, 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) { 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`, { const file = new File([blob], `compressed.sigit`, {
type: 'application/sigit' type: 'application/sigit'
}) })
navigate(appPrivateRoutes.sign, { state: { file, encryptionKey } }) navigate(appPrivateRoutes.sign, { state: { file, encryptionKey } })
} else { } else {
// Save the file and show encryption key for offline viewing
saveAs(blob, 'request.sigit') saveAs(blob, 'request.sigit')
setTextToCopy(encryptionKey) setTextToCopy(encryptionKey)
setOpenCopyModel(true) 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) { if (authUrl) {