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

View File

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