In offline mode create a wrapper zip file #110
@ -34,6 +34,7 @@ import {
|
|||||||
CreateSignatureEventContent,
|
CreateSignatureEventContent,
|
||||||
Meta,
|
Meta,
|
||||||
ProfileMetadata,
|
ProfileMetadata,
|
||||||
|
SignedEvent,
|
||||||
SignedEventContent,
|
SignedEventContent,
|
||||||
User,
|
User,
|
||||||
UserRole
|
UserRole
|
||||||
@ -366,26 +367,15 @@ export const SignPage = () => {
|
|||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
setLoadingSpinnerDesc('parsing hashes.json file')
|
setLoadingSpinnerDesc('parsing hashes.json file')
|
||||||
|
|
||||||
const hashesFileContent = await readContentOfZipEntry(
|
const hashesFileContent = await readHashesFile()
|
||||||
zip,
|
if (!hashesFileContent) return
|
||||||
'hashes.json',
|
|
||||||
'string'
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!hashesFileContent) {
|
if (!hashesFileContent) {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let hashes = await parseJson(hashesFileContent).catch((err) => {
|
const hashes = await parseHashes(hashesFileContent)
|
||||||
console.log('err in parsing the content of hashes.json :>> ', err)
|
|
||||||
toast.error(
|
|
||||||
err.message || 'error occurred in parsing the content of hashes.json'
|
|
||||||
)
|
|
||||||
setIsLoading(false)
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!hashes) return
|
if (!hashes) return
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Generating hashes for files')
|
setLoadingSpinnerDesc('Generating hashes for files')
|
||||||
@ -395,51 +385,21 @@ export const SignPage = () => {
|
|||||||
const prevSig = getPrevSignersSig(hexToNpub(usersPubkey!))
|
const prevSig = getPrevSignersSig(hexToNpub(usersPubkey!))
|
||||||
if (!prevSig) return
|
if (!prevSig) return
|
||||||
|
|
||||||
const signedEvent = await signEventForMetaFile(
|
const signedEvent = await signEventForMeta(prevSig)
|
||||||
JSON.stringify({
|
|
||||||
prevSig
|
|
||||||
}),
|
|
||||||
nostrController,
|
|
||||||
setIsLoading
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!signedEvent) return
|
if (!signedEvent) return
|
||||||
|
|
||||||
const metaCopy = _.cloneDeep(meta)
|
const updatedMeta = updateMetaSignatures(meta, signedEvent)
|
||||||
|
|
||||||
metaCopy.docSignatures = {
|
const stringifiedMeta = JSON.stringify(updatedMeta, null, 2)
|
||||||
...metaCopy.docSignatures,
|
|
||||||
[hexToNpub(signedEvent.pubkey)]: JSON.stringify(signedEvent, null, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
const stringifiedMeta = JSON.stringify(metaCopy, null, 2)
|
|
||||||
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
|
||||||
|
|
||||||
hashes = {
|
const updatedHashes = updateHashes(hashes, metaHash)
|
||||||
...hashes,
|
zip.file('hashes.json', JSON.stringify(updatedHashes, null, 2))
|
||||||
[usersPubkey!]: metaHash
|
|
||||||
}
|
|
||||||
|
|
||||||
zip.file('hashes.json', JSON.stringify(hashes, null, 2))
|
|
||||||
|
|
||||||
const arrayBuffer = await zip
|
|
||||||
.generateAsync({
|
|
||||||
type: 'arraybuffer',
|
|
||||||
compression: 'DEFLATE',
|
|
||||||
compressionOptions: {
|
|
||||||
level: 6
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log('err in zip:>> ', err)
|
|
||||||
setIsLoading(false)
|
|
||||||
toast.error(err.message || 'Error occurred in generating zip file')
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
|
const arrayBuffer = await generateZipArrayBuffer(zip)
|
||||||
if (!arrayBuffer) return
|
if (!arrayBuffer) return
|
||||||
|
|
||||||
const key = await generateEncryptionKey()
|
const key = await generateEncryptionKey()
|
||||||
@ -450,80 +410,180 @@ export const SignPage = () => {
|
|||||||
const blob = new Blob([encryptedArrayBuffer])
|
const blob = new Blob([encryptedArrayBuffer])
|
||||||
|
|
||||||
if (await isOnline()) {
|
if (await isOnline()) {
|
||||||
setLoadingSpinnerDesc('Uploading zip file to file storage.')
|
await handleOnlineFlow(blob, key)
|
||||||
const fileUrl = await uploadToFileStorage(blob, nostrController)
|
|
||||||
.then((url) => {
|
|
||||||
toast.success('zip file uploaded to file storage')
|
|
||||||
return url
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log('err in upload:>> ', err)
|
|
||||||
setIsLoading(false)
|
|
||||||
toast.error(err.message || 'Error occurred in uploading zip file')
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!fileUrl) return
|
|
||||||
|
|
||||||
// check if the current user is the last signer
|
|
||||||
const usersNpub = hexToNpub(usersPubkey!)
|
|
||||||
const lastSignerIndex = signers.length - 1
|
|
||||||
const signerIndex = signers.indexOf(usersNpub)
|
|
||||||
const isLastSigner = signerIndex === lastSignerIndex
|
|
||||||
|
|
||||||
// if current user is the last signer, then send DMs to all signers and viewers
|
|
||||||
if (isLastSigner) {
|
|
||||||
const userSet = new Set<`npub1${string}`>()
|
|
||||||
|
|
||||||
if (submittedBy) {
|
|
||||||
userSet.add(hexToNpub(submittedBy))
|
|
||||||
}
|
|
||||||
|
|
||||||
signers.forEach((signer) => {
|
|
||||||
userSet.add(signer)
|
|
||||||
})
|
|
||||||
|
|
||||||
viewers.forEach((viewer) => {
|
|
||||||
userSet.add(viewer)
|
|
||||||
})
|
|
||||||
|
|
||||||
const users = Array.from(userSet)
|
|
||||||
|
|
||||||
for (const user of users) {
|
|
||||||
// todo: execute in parallel
|
|
||||||
await sendDM(
|
|
||||||
fileUrl,
|
|
||||||
key,
|
|
||||||
npubToHex(user)!,
|
|
||||||
nostrController,
|
|
||||||
false,
|
|
||||||
setAuthUrl
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const nextSigner = signers[signerIndex + 1]
|
|
||||||
await sendDM(
|
|
||||||
fileUrl,
|
|
||||||
key,
|
|
||||||
npubToHex(nextSigner)!,
|
|
||||||
nostrController,
|
|
||||||
true,
|
|
||||||
setAuthUrl
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoading(false)
|
|
||||||
|
|
||||||
// update search params with updated file url and encryption key
|
|
||||||
setSearchParams({
|
|
||||||
file: fileUrl,
|
|
||||||
key: key
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
handleDecryptedArrayBuffer(arrayBuffer).finally(() => setIsLoading(false))
|
handleDecryptedArrayBuffer(arrayBuffer).finally(() => setIsLoading(false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read the content of the hashes.json file
|
||||||
|
const readHashesFile = async (): Promise<string | null> => {
|
||||||
|
return await readContentOfZipEntry(zip!, 'hashes.json', 'string').catch(
|
||||||
|
(err) => {
|
||||||
|
console.log('Error reading hashes.json file:', err)
|
||||||
|
setIsLoading(false)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the JSON content of the hashes file
|
||||||
|
const parseHashes = async (
|
||||||
|
hashesFileContent: string
|
||||||
|
): Promise<Record<string, string> | null> => {
|
||||||
|
return await parseJson<Record<string, string>>(hashesFileContent).catch(
|
||||||
|
(err) => {
|
||||||
|
console.log('Error parsing hashes.json content:', err)
|
||||||
|
toast.error(err.message || 'Error parsing hashes.json content')
|
||||||
|
setIsLoading(false)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign the event for the meta file
|
||||||
|
const signEventForMeta = async (prevSig: string) => {
|
||||||
|
return await signEventForMetaFile(
|
||||||
|
JSON.stringify({ prevSig }),
|
||||||
|
nostrController,
|
||||||
|
setIsLoading
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the meta signatures
|
||||||
|
const updateMetaSignatures = (meta: Meta, signedEvent: SignedEvent): Meta => {
|
||||||
|
const metaCopy = _.cloneDeep(meta)
|
||||||
|
metaCopy.docSignatures = {
|
||||||
|
...metaCopy.docSignatures,
|
||||||
|
[hexToNpub(signedEvent.pubkey)]: JSON.stringify(signedEvent, null, 2)
|
||||||
|
}
|
||||||
|
return metaCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the hashes with the new meta hash
|
||||||
|
const updateHashes = (
|
||||||
|
hashes: Record<string, string>,
|
||||||
|
metaHash: string
|
||||||
|
): Record<string, string> => {
|
||||||
|
return {
|
||||||
|
...hashes,
|
||||||
|
[usersPubkey!]: metaHash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the zip array buffer
|
||||||
|
const generateZipArrayBuffer = async (
|
||||||
|
zip: JSZip
|
||||||
|
): Promise<ArrayBuffer | null> => {
|
||||||
|
return await zip
|
||||||
|
.generateAsync({
|
||||||
|
type: 'arraybuffer',
|
||||||
|
compression: 'DEFLATE',
|
||||||
|
compressionOptions: {
|
||||||
|
level: 6
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('Error generating zip file:', err)
|
||||||
|
setIsLoading(false)
|
||||||
|
toast.error(err.message || 'Error generating zip file')
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the online flow: upload file and send DMs
|
||||||
|
const handleOnlineFlow = async (blob: Blob, key: string) => {
|
||||||
|
const fileUrl = await uploadZipFile(blob)
|
||||||
|
if (!fileUrl) return
|
||||||
|
|
||||||
|
const isLastSigner = checkIsLastSigner(signers)
|
||||||
|
|
||||||
|
if (isLastSigner) {
|
||||||
|
await sendDMToAllUsers(fileUrl, key)
|
||||||
|
} else {
|
||||||
|
await sendDMToNextSigner(fileUrl, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(false)
|
||||||
|
|
||||||
|
// Update search params with updated file URL and encryption key
|
||||||
|
setSearchParams({
|
||||||
|
file: fileUrl,
|
||||||
|
key: key
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload the zip file to file storage
|
||||||
|
const uploadZipFile = async (blob: Blob): Promise<string | null> => {
|
||||||
|
setLoadingSpinnerDesc('Uploading zip file to file storage.')
|
||||||
|
const fileUrl = await uploadToFileStorage(blob, nostrController)
|
||||||
|
.then((url) => {
|
||||||
|
toast.success('Zip file 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
|
||||||
|
})
|
||||||
|
|
||||||
|
return fileUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the current user is the last signer
|
||||||
|
const checkIsLastSigner = (signers: string[]): boolean => {
|
||||||
|
const usersNpub = hexToNpub(usersPubkey!)
|
||||||
|
const lastSignerIndex = signers.length - 1
|
||||||
|
const signerIndex = signers.indexOf(usersNpub)
|
||||||
|
return signerIndex === lastSignerIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send DM to all users (signers and viewers)
|
||||||
|
const sendDMToAllUsers = async (fileUrl: string, key: string) => {
|
||||||
|
const userSet = new Set<`npub1${string}`>()
|
||||||
|
|
||||||
|
if (submittedBy) {
|
||||||
|
userSet.add(hexToNpub(submittedBy))
|
||||||
|
}
|
||||||
|
|
||||||
|
signers.forEach((signer) => {
|
||||||
|
userSet.add(signer)
|
||||||
|
})
|
||||||
|
|
||||||
|
viewers.forEach((viewer) => {
|
||||||
|
userSet.add(viewer)
|
||||||
|
})
|
||||||
|
|
||||||
|
const users = Array.from(userSet)
|
||||||
|
|
||||||
|
for (const user of users) {
|
||||||
|
await sendDM(
|
||||||
|
fileUrl,
|
||||||
|
key,
|
||||||
|
npubToHex(user)!,
|
||||||
|
nostrController,
|
||||||
|
false,
|
||||||
|
setAuthUrl
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send DM to the next signer
|
||||||
|
const sendDMToNextSigner = async (fileUrl: string, key: string) => {
|
||||||
|
const usersNpub = hexToNpub(usersPubkey!)
|
||||||
|
const signerIndex = signers.indexOf(usersNpub)
|
||||||
|
const nextSigner = signers[signerIndex + 1]
|
||||||
|
await sendDM(
|
||||||
|
fileUrl,
|
||||||
|
key,
|
||||||
|
npubToHex(nextSigner)!,
|
||||||
|
nostrController,
|
||||||
|
true,
|
||||||
|
setAuthUrl
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const handleExport = async () => {
|
const handleExport = async () => {
|
||||||
if (!meta || !zip || !usersPubkey) return
|
if (!meta || !zip || !usersPubkey) return
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user