feat(offline): add decrypt as zip util
This commit is contained in:
parent
7b2537e355
commit
8b5abe02e2
140
src/utils/zip.ts
140
src/utils/zip.ts
@ -1,6 +1,12 @@
|
||||
import JSZip from 'jszip'
|
||||
import { toast } from 'react-toastify'
|
||||
import { InputFileFormat, OutputByType, OutputType } from '../types'
|
||||
import { InputFileFormat, Meta, OutputByType, OutputType } from '../types'
|
||||
import { NavigateOptions, To } from 'react-router-dom'
|
||||
import { appPublicRoutes } from '../routes'
|
||||
import { NostrController } from '../controllers'
|
||||
import { decryptArrayBuffer } from './crypto'
|
||||
import { hexToNpub, parseJson, SigitStatus, timeout } from '.'
|
||||
import { SignerService } from '../services'
|
||||
|
||||
/**
|
||||
* Read the content of a file within a zip archive.
|
||||
@ -9,7 +15,7 @@ import { InputFileFormat, OutputByType, OutputType } from '../types'
|
||||
* @param outputType The type of output to return (e.g., 'string', 'arraybuffer', 'uint8array', etc.).
|
||||
* @returns A Promise resolving to the content of the file, or null if an error occurs.
|
||||
*/
|
||||
const readContentOfZipEntry = async <T extends OutputType>(
|
||||
export const readContentOfZipEntry = async <T extends OutputType>(
|
||||
zip: JSZip,
|
||||
filePath: string,
|
||||
outputType: T
|
||||
@ -34,7 +40,7 @@ const readContentOfZipEntry = async <T extends OutputType>(
|
||||
})
|
||||
}
|
||||
|
||||
const loadZip = async (data: InputFileFormat): Promise<JSZip | null> => {
|
||||
export const loadZip = async (data: InputFileFormat): Promise<JSZip | null> => {
|
||||
try {
|
||||
return await JSZip.loadAsync(data)
|
||||
} catch (err) {
|
||||
@ -46,4 +52,130 @@ const loadZip = async (data: InputFileFormat): Promise<JSZip | null> => {
|
||||
}
|
||||
}
|
||||
|
||||
export { readContentOfZipEntry, loadZip }
|
||||
export const decrypt = async (file: File) => {
|
||||
const nostrController = NostrController.getInstance()
|
||||
|
||||
const zip = await loadZip(file)
|
||||
if (!zip) return
|
||||
|
||||
const keysFileContent = await readContentOfZipEntry(
|
||||
zip,
|
||||
'keys.json',
|
||||
'string'
|
||||
)
|
||||
|
||||
if (!keysFileContent) return null
|
||||
|
||||
const parsedKeysJson = await parseJson<{ sender: string; keys: string[] }>(
|
||||
keysFileContent
|
||||
).catch((err) => {
|
||||
console.log(`Error parsing content of keys.json:`, err)
|
||||
toast.error(err.message || `Error parsing content of keys.json`)
|
||||
return null
|
||||
})
|
||||
|
||||
if (!parsedKeysJson) return
|
||||
|
||||
const encryptedArrayBuffer = await readContentOfZipEntry(
|
||||
zip,
|
||||
'compressed.sigit',
|
||||
'arraybuffer'
|
||||
)
|
||||
|
||||
if (!encryptedArrayBuffer) return
|
||||
|
||||
const { keys, sender } = parsedKeysJson
|
||||
|
||||
for (const key of keys) {
|
||||
// decrypt the encryptionKey, with timeout (duration = 60 seconds)
|
||||
const encryptionKey = await Promise.race([
|
||||
nostrController.nip04Decrypt(sender, key),
|
||||
timeout(60000)
|
||||
])
|
||||
.then((res) => {
|
||||
return res
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('err :>> ', err)
|
||||
return null
|
||||
})
|
||||
|
||||
// Return if encryption failed
|
||||
if (!encryptionKey) continue
|
||||
|
||||
const arrayBuffer = await decryptArrayBuffer(
|
||||
encryptedArrayBuffer,
|
||||
encryptionKey
|
||||
).catch((err) => {
|
||||
console.log('err in decryption:>> ', err)
|
||||
return null
|
||||
})
|
||||
|
||||
if (arrayBuffer) return arrayBuffer
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
type NavigateArgs = { to: To; options?: NavigateOptions }
|
||||
export const navigateFromZip = async (file: File, pubkey: `npub1${string}`) => {
|
||||
if (!file.name.endsWith('.sigit.zip')) {
|
||||
toast.error(`Not a SiGit zip file: ${file.name}`)
|
||||
}
|
||||
|
||||
try {
|
||||
let zip = await JSZip.loadAsync(file)
|
||||
if (!zip) {
|
||||
return null
|
||||
}
|
||||
|
||||
let arrayBuffer: ArrayBuffer | undefined
|
||||
if ('keys.json' in zip.files) {
|
||||
// Decrypt
|
||||
const decryptedArrayBuffer = await decrypt(file).catch((err) => {
|
||||
console.error(`error occurred in decryption`, err)
|
||||
toast.error(err.message || `error occurred in decryption`)
|
||||
})
|
||||
|
||||
if (decryptedArrayBuffer) {
|
||||
// Replace the zip and continue processing
|
||||
zip = await JSZip.loadAsync(decryptedArrayBuffer)
|
||||
arrayBuffer = decryptedArrayBuffer
|
||||
}
|
||||
}
|
||||
|
||||
if ('meta.json' in zip.files) {
|
||||
// Check where we need to navigate
|
||||
// Find Meta and process it for signer state
|
||||
const metaContent = await readContentOfZipEntry(
|
||||
zip,
|
||||
'meta.json',
|
||||
'string'
|
||||
)
|
||||
if (metaContent) {
|
||||
const meta = JSON.parse(metaContent) as Meta
|
||||
const signerService = new SignerService(meta)
|
||||
|
||||
const to =
|
||||
signerService.getSignedStatus() === SigitStatus.Complete
|
||||
? appPublicRoutes.verify
|
||||
: signerService.getNavigate(hexToNpub(pubkey))
|
||||
|
||||
return {
|
||||
to,
|
||||
options: {
|
||||
state: { uploadedZip: arrayBuffer || file }
|
||||
}
|
||||
} as NavigateArgs
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
} catch (err) {
|
||||
console.error('err in processing sigit zip file :>> ', err)
|
||||
if (err instanceof Error) {
|
||||
toast.error(err.message || 'An error occurred in loading zip file.')
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user