release #111
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user