In offline mode create a wrapper zip file #110
@ -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) {
|
||||||
|
@ -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>
|
||||||
|
@ -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}`
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user