2024-11-15 18:04:40 +01:00
|
|
|
import axios from 'axios'
|
2024-11-15 17:51:11 +01:00
|
|
|
import { MARK_TYPE_CONFIG } from '../components/getMarkComponents.tsx'
|
|
|
|
import { NostrController } from '../controllers/NostrController.ts'
|
|
|
|
import store from '../store/store.ts'
|
2024-08-13 12:48:52 +03:00
|
|
|
import { Meta } from '../types'
|
2024-08-22 18:20:54 +02:00
|
|
|
import { PdfPage } from '../types/drawing.ts'
|
|
|
|
import { MOST_COMMON_MEDIA_TYPES } from './const.ts'
|
2024-08-13 12:48:52 +03:00
|
|
|
import { extractMarksFromSignedMeta } from './mark.ts'
|
2024-11-15 17:51:11 +01:00
|
|
|
import { hexToNpub } from './nostr.ts'
|
2024-08-22 18:20:54 +02:00
|
|
|
import {
|
|
|
|
addMarks,
|
|
|
|
groupMarksByFileNamePage,
|
|
|
|
isPdf,
|
|
|
|
pdfToImages
|
|
|
|
} from './pdf.ts'
|
2024-08-13 12:48:52 +03:00
|
|
|
import JSZip from 'jszip'
|
|
|
|
|
2024-08-22 18:20:54 +02:00
|
|
|
export const getZipWithFiles = async (
|
2024-08-13 12:48:52 +03:00
|
|
|
meta: Meta,
|
2024-08-22 18:20:54 +02:00
|
|
|
files: { [filename: string]: SigitFile }
|
2024-08-13 12:48:52 +03:00
|
|
|
): Promise<JSZip> => {
|
|
|
|
const zip = new JSZip()
|
|
|
|
const marks = extractMarksFromSignedMeta(meta)
|
2024-08-16 12:01:41 +02:00
|
|
|
const marksByFileNamePage = groupMarksByFileNamePage(marks)
|
2024-08-13 12:48:52 +03:00
|
|
|
|
2024-08-21 17:07:29 +02:00
|
|
|
for (const [fileName, file] of Object.entries(files)) {
|
2024-10-09 11:51:26 +02:00
|
|
|
// Handle PDF Files, add marks
|
2024-10-11 16:16:59 +02:00
|
|
|
if (file.isPdf && fileName in marksByFileNamePage) {
|
2024-11-15 17:51:11 +01:00
|
|
|
const marksToAdd = marksByFileNamePage[fileName]
|
|
|
|
if (meta.keys) {
|
|
|
|
for (let i = 0; i < marks.length; i++) {
|
|
|
|
const m = marks[i]
|
|
|
|
const { sender, keys } = meta.keys
|
|
|
|
const usersPubkey = store.getState().auth.usersPubkey!
|
|
|
|
const usersNpub = hexToNpub(usersPubkey)
|
|
|
|
if (usersNpub in keys) {
|
|
|
|
const encryptionKey = await NostrController.getInstance()
|
|
|
|
.nip04Decrypt(sender, keys[usersNpub])
|
|
|
|
.catch((err) => {
|
|
|
|
console.log(
|
|
|
|
'An error occurred in decrypting encryption key',
|
|
|
|
err
|
|
|
|
)
|
|
|
|
return null
|
|
|
|
})
|
|
|
|
|
|
|
|
try {
|
|
|
|
const { fetchAndDecrypt } = MARK_TYPE_CONFIG[m.type] || {}
|
|
|
|
if (
|
|
|
|
typeof fetchAndDecrypt === 'function' &&
|
|
|
|
m.value &&
|
|
|
|
encryptionKey
|
|
|
|
) {
|
2024-11-15 18:04:40 +01:00
|
|
|
// Save the original file
|
|
|
|
const encryptedArrayBuffer = await axios.get(m.value, {
|
|
|
|
responseType: 'arraybuffer'
|
|
|
|
})
|
|
|
|
const link = m.value.split('/')
|
|
|
|
zip.file(
|
|
|
|
`blossom/encrypted/${link[link.length - 1]}`,
|
|
|
|
new Blob([encryptedArrayBuffer.data])
|
|
|
|
)
|
2024-11-15 17:51:11 +01:00
|
|
|
const decrypted = await fetchAndDecrypt(m.value, encryptionKey)
|
2024-11-15 18:04:40 +01:00
|
|
|
|
|
|
|
// Save decrypted
|
|
|
|
zip.file(
|
|
|
|
`blossom/decrypted/${link[link.length - 1]}.json`,
|
|
|
|
new Blob([decrypted])
|
|
|
|
)
|
2024-11-15 17:51:11 +01:00
|
|
|
marks[i].value = decrypted
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error(`Error during mark fetchAndDecrypt phase`, error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const blob = await addMarks(file, marksToAdd)
|
2024-10-09 11:51:26 +02:00
|
|
|
zip.file(`marked/${fileName}`, blob)
|
2024-08-21 17:07:29 +02:00
|
|
|
}
|
2024-10-09 11:51:26 +02:00
|
|
|
|
|
|
|
// Save original files
|
|
|
|
zip.file(`files/${fileName}`, file)
|
2024-08-13 12:48:52 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return zip
|
|
|
|
}
|
|
|
|
|
2024-08-22 18:20:54 +02:00
|
|
|
/**
|
|
|
|
* Converts a PDF ArrayBuffer to a generic PDF File
|
|
|
|
* @param arrayBuffer of a PDF
|
|
|
|
* @param fileName identifier of the pdf file
|
|
|
|
* @param type optional file type (defaults to pdf)
|
|
|
|
*/
|
|
|
|
export const toFile = (
|
|
|
|
arrayBuffer: ArrayBuffer,
|
|
|
|
fileName: string,
|
|
|
|
type: string = 'application/pdf'
|
|
|
|
): File => {
|
|
|
|
const blob = new Blob([arrayBuffer], { type })
|
|
|
|
return new File([blob], fileName, { type })
|
|
|
|
}
|
|
|
|
|
|
|
|
export class SigitFile extends File {
|
|
|
|
extension: string
|
|
|
|
isPdf: boolean
|
2024-08-22 18:48:03 +02:00
|
|
|
isImage: boolean
|
|
|
|
|
|
|
|
pages?: PdfPage[]
|
|
|
|
objectUrl?: string
|
2024-08-22 18:20:54 +02:00
|
|
|
|
|
|
|
constructor(file: File) {
|
|
|
|
super([file], file.name, { type: file.type })
|
|
|
|
this.isPdf = isPdf(this)
|
2024-08-22 18:48:03 +02:00
|
|
|
this.isImage = isImage(this)
|
2024-08-22 18:20:54 +02:00
|
|
|
this.extension = extractFileExtension(this.name)
|
|
|
|
}
|
|
|
|
|
|
|
|
async process() {
|
|
|
|
if (this.isPdf) this.pages = await pdfToImages(await this.arrayBuffer())
|
2024-08-22 18:48:03 +02:00
|
|
|
if (this.isImage) this.objectUrl = URL.createObjectURL(this)
|
2024-08-22 18:20:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export const getSigitFile = async (file: File) => {
|
|
|
|
const sigitFile = new SigitFile(file)
|
|
|
|
// Process sigit file
|
|
|
|
// - generate pages for PDF files
|
2024-08-23 21:30:32 +02:00
|
|
|
// - generate ObjectRL for image files
|
2024-08-22 18:20:54 +02:00
|
|
|
await sigitFile.process()
|
|
|
|
return sigitFile
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Takes an ArrayBuffer and converts to Sigit's Internal File type
|
|
|
|
* @param arrayBuffer
|
|
|
|
* @param fileName
|
|
|
|
*/
|
|
|
|
export const convertToSigitFile = async (
|
|
|
|
arrayBuffer: ArrayBuffer,
|
|
|
|
fileName: string
|
|
|
|
): Promise<SigitFile> => {
|
|
|
|
const type = getMediaType(extractFileExtension(fileName))
|
|
|
|
const file = toFile(arrayBuffer, fileName, type)
|
|
|
|
const sigitFile = await getSigitFile(file)
|
|
|
|
return sigitFile
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param fileNames - List of filenames to check
|
|
|
|
* @returns List of extensions and if all are same
|
|
|
|
*/
|
|
|
|
export const extractFileExtensions = (fileNames: string[]) => {
|
|
|
|
const extensions = fileNames.reduce((result: string[], file: string) => {
|
|
|
|
const extension = file.split('.').pop()
|
|
|
|
if (extension) {
|
|
|
|
result.push(extension)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
const isSame = extensions.every((ext) => ext === extensions[0])
|
|
|
|
|
|
|
|
return { extensions, isSame }
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param fileName - Filename to check
|
|
|
|
* @returns Extension string
|
|
|
|
*/
|
|
|
|
export const extractFileExtension = (fileName: string) => {
|
|
|
|
const parts = fileName.split('.')
|
|
|
|
return parts[parts.length - 1]
|
|
|
|
}
|
|
|
|
|
|
|
|
export const getMediaType = (extension: string) => {
|
|
|
|
return MOST_COMMON_MEDIA_TYPES.get(extension)
|
|
|
|
}
|
2024-08-22 18:48:03 +02:00
|
|
|
|
|
|
|
export const isImage = (file: File) => {
|
|
|
|
const validImageMediaTypes = [
|
|
|
|
'image/png',
|
|
|
|
'image/jpeg',
|
|
|
|
'image/jpg',
|
|
|
|
'image/gif',
|
|
|
|
'image/svg+xml',
|
|
|
|
'image/bmp',
|
|
|
|
'image/x-icon'
|
|
|
|
]
|
|
|
|
|
|
|
|
return validImageMediaTypes.includes(file.type.toLowerCase())
|
|
|
|
}
|