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
3 changed files with 120 additions and 72 deletions
Showing only changes of commit e8da0dc76f - Show all commits

View File

@ -421,38 +421,39 @@ export const CreatePage = () => {
return finalZipFile
}
// Handle file upload and further actions based on online/offline status
const handleFileUpload = async (file: File, arrayBuffer: ArrayBuffer) => {
if (await isOnline()) {
const fileUrl = await uploadFile(file)
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'
})
if (!fileUrl) return
const fileUrl = await uploadFile(file)
if (!fileUrl) return
await sendDMs(fileUrl)
setIsLoading(false)
navigate(appPrivateRoutes.sign, { state: { arrayBuffer } })
} else {
handleOffline(file, arrayBuffer)
}
await sendDMs(fileUrl, 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')
toast.error(err.message || 'Error occurred in uploading file')
return null
}
// Upload the file to the storage and send DMs to signers/viewers
// Upload the file to the storage
const uploadFile = async (file: File): Promise<string | null> => {
setIsLoading(true)
setLoadingSpinnerDesc('Uploading zip file to file storage.')
setLoadingSpinnerDesc('Uploading sigit to file storage.')
const fileUrl = await uploadToFileStorage(file, nostrController)
.then((url) => {
toast.success('zip file uploaded to file storage')
toast.success('Sigit uploaded to file storage')
return url
})
.catch(handleUploadError)
@ -461,7 +462,7 @@ export const CreatePage = () => {
}
// Send DMs to signers and viewers with the file URL
const sendDMs = async (fileUrl: string) => {
const sendDMs = async (fileUrl: string, encryptionKey: string) => {
setLoadingSpinnerDesc('Sending DM to signers/viewers')
const signers = users.filter((user) => user.role === UserRole.signer)
@ -470,6 +471,7 @@ export const CreatePage = () => {
if (signers.length > 0) {
await sendDM(
fileUrl,
encryptionKey,
signers[0].pubkey,
nostrController,
true,
@ -477,15 +479,31 @@ export const CreatePage = () => {
)
} else {
for (const viewer of viewers) {
await sendDM(fileUrl, viewer.pubkey, nostrController, false, setAuthUrl)
await sendDM(
fileUrl,
encryptionKey,
viewer.pubkey,
nostrController,
false,
setAuthUrl
)
}
}
}
// Manage offline scenarios for signing or viewing the file
const handleOffline = (file: File, arrayBuffer: ArrayBuffer) => {
saveAs(file, 'request.sigit.zip')
navigate(appPrivateRoutes.sign, { state: { arrayBuffer } })
const handleOfflineFlow = async (
encryptedArrayBuffer: ArrayBuffer,
encryptionKey: string
) => {
const finalZipFile = await createFinalZipFile(
encryptedArrayBuffer,
encryptionKey
)
if (!finalZipFile) return
saveAs(finalZipFile, 'request.sigit.zip')
}
const handleCreate = async () => {
@ -507,25 +525,24 @@ export const CreatePage = () => {
setLoadingSpinnerDesc('Generating zip file')
const arraybuffer = await generateZipFile(zip)
if (!arraybuffer) return
const arrayBuffer = await generateZipFile(zip)
if (!arrayBuffer) return
const encryptionKey = await generateEncryptionKey()
setLoadingSpinnerDesc('Encrypting zip file')
const encryptedArrayBuffer = await encryptZipFile(
arraybuffer,
arrayBuffer,
encryptionKey
)
const finalZipFile = await createFinalZipFile(
encryptedArrayBuffer,
encryptionKey
)
if (await isOnline()) {
await handleOnlineFlow(encryptedArrayBuffer, encryptionKey)
} else {
await handleOfflineFlow(encryptedArrayBuffer, encryptionKey)
}
if (!finalZipFile) return
return await handleFileUpload(finalZipFile, arraybuffer)
navigate(appPrivateRoutes.sign, { state: { arrayBuffer } })
}
if (authUrl) {

View File

@ -42,7 +42,7 @@ export const SignPage = () => {
const location = useLocation()
const { arrayBuffer: decryptedArrayBuffer } = location.state || {}
const [searchParams] = useSearchParams()
const [searchParams, setSearchParams] = useSearchParams()
const [displayInput, setDisplayInput] = useState(false)
@ -160,8 +160,9 @@ export const SignPage = () => {
useEffect(() => {
const fileUrl = searchParams.get('file')
const key = searchParams.get('key')
if (fileUrl) {
if (fileUrl && key) {
setIsLoading(true)
setLoadingSpinnerDesc('Fetching file from file server')
@ -169,13 +170,22 @@ export const SignPage = () => {
.get(fileUrl, {
responseType: 'arraybuffer'
})
.then((res) => {
.then(async (res) => {
const fileName = fileUrl.split('/').pop()
const file = new File([res.data], fileName!)
decrypt(file).then((arrayBuffer) => {
if (arrayBuffer) handleDecryptedArrayBuffer(arrayBuffer)
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
})
if (arrayBuffer) handleDecryptedArrayBuffer(arrayBuffer)
})
.catch((err) => {
console.error(`error occurred in getting file from ${fileUrl}`, err)
@ -268,8 +278,6 @@ export const SignPage = () => {
setAuthUrl(undefined) // Clear authentication URL
})
console.log('encryptionKey :>> ', encryptionKey)
// Return if encryption failed
if (!encryptionKey) continue
@ -438,15 +446,11 @@ export const SignPage = () => {
setLoadingSpinnerDesc('Encrypting zip file')
const encryptedArrayBuffer = await encryptArrayBuffer(arrayBuffer, key)
const finalZipFile = await createFinalZipFile(encryptedArrayBuffer, key)
if (!finalZipFile) return
if (await isOnline()) {
await handleOnlineFlow(finalZipFile)
await handleOnlineFlow(encryptedArrayBuffer, key)
} else {
handleDecryptedArrayBuffer(arrayBuffer).finally(() => setIsLoading(false))
}
handleDecryptedArrayBuffer(arrayBuffer).finally(() => setIsLoading(false))
}
// Read the content of the hashes.json file
@ -600,35 +604,56 @@ export const SignPage = () => {
}
// Handle the online flow: upload file and send DMs
const handleOnlineFlow = async (file: File) => {
const fileUrl = await uploadZipFile(file)
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)
if (!fileUrl) return
const isLastSigner = checkIsLastSigner(signers)
if (isLastSigner) {
await sendDMToAllUsers(fileUrl)
await sendDMToAllUsers(fileUrl, encryptionKey)
} else {
await sendDMToNextSigner(fileUrl)
await sendDMToNextSigner(fileUrl, encryptionKey)
}
// update search params with updated file url and encryption key
setSearchParams({
file: fileUrl,
key: encryptionKey
})
setIsLoading(false)
}
// Upload the zip file to file storage
const uploadZipFile = async (file: File): Promise<string | null> => {
setLoadingSpinnerDesc('Uploading zip file to file storage.')
// 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.')
const fileUrl = await uploadToFileStorage(file, nostrController)
.then((url) => {
toast.success('Zip file uploaded to file storage')
toast.success('Sigit uploaded to file storage')
return url
})
.catch((err) => {
console.log('Error uploading file:', err)
setIsLoading(false)
toast.error(err.message || 'Error uploading file')
return null
})
.catch(handleUploadError)
return fileUrl
}
@ -642,7 +667,7 @@ export const SignPage = () => {
}
// Send DM to all users (signers and viewers)
const sendDMToAllUsers = async (fileUrl: string) => {
const sendDMToAllUsers = async (fileUrl: string, encryptionKey: string) => {
const userSet = new Set<`npub1${string}`>()
if (submittedBy) {
@ -662,6 +687,7 @@ export const SignPage = () => {
for (const user of users) {
await sendDM(
fileUrl,
encryptionKey,
npubToHex(user)!,
nostrController,
false,
@ -671,12 +697,13 @@ export const SignPage = () => {
}
// Send DM to the next signer
const sendDMToNextSigner = async (fileUrl: string) => {
const sendDMToNextSigner = async (fileUrl: string, encryptionKey: string) => {
const usersNpub = hexToNpub(usersPubkey!)
const signerIndex = signers.indexOf(usersNpub)
const nextSigner = signers[signerIndex + 1]
await sendDM(
fileUrl,
encryptionKey,
npubToHex(nextSigner)!,
nostrController,
true,
@ -776,8 +803,8 @@ export const SignPage = () => {
const finalZipFile = await createFinalZipFile(encryptedArrayBuffer, key)
if (!finalZipFile) return
saveAs(finalZipFile, 'exported.sigit.zip')
const unixNow = Math.floor(Date.now() / 1000)
saveAs(finalZipFile, `exported-${unixNow}.sigit.zip`)
}
/**
@ -915,14 +942,14 @@ export const SignPage = () => {
</Box>
)}
{isSignerOrCreator &&
signedStatus === SignedStatus.User_Is_Not_Next_Signer && (
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
<Button onClick={handleExportSigit} variant="contained">
Export Sigit
</Button>
</Box>
)}
{/* 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>
)}
</>
)}
</Box>

View File

@ -56,6 +56,7 @@ export const uploadToFileStorage = async (
/**
* Sends a Direct Message (DM) to a recipient, encrypting the content and handling authentication.
* @param fileUrl The URL of the encrypted zip file to be included in the DM.
* @param encryptionKey The encryption key used to decrypt the zip file to be included in the DM.
* @param pubkey The public key of the recipient.
* @param nostrController The NostrController instance for handling authentication and encryption.
* @param isSigner Boolean indicating whether the recipient is a signer or viewer.
@ -63,6 +64,7 @@ export const uploadToFileStorage = async (
*/
export const sendDM = async (
fileUrl: string,
encryptionKey: string,
pubkey: string,
nostrController: NostrController,
isSigner: boolean,
@ -75,7 +77,9 @@ export const sendDM = async (
const decryptionUrl = `${window.location.origin}/#${
appPrivateRoutes.sign
}?file=${encodeURIComponent(fileUrl)}`
}?file=${encodeURIComponent(fileUrl)}&key=${encodeURIComponent(
encryptionKey
)}`
const content = `${initialLine}\n\n${decryptionUrl}`