sigit.io/src/utils/file.ts

185 lines
5.1 KiB
TypeScript

import { MARK_TYPE_CONFIG } from '../components/MarkTypeStrategy/MarkStrategy.tsx'
import { NostrController } from '../controllers/NostrController.ts'
import store from '../store/store.ts'
import { Meta } from '../types'
import { PdfPage } from '../types/drawing.ts'
import { MOST_COMMON_MEDIA_TYPES } from './const.ts'
import { extractMarksFromSignedMeta } from './mark.ts'
import { hexToNpub } from './nostr.ts'
import {
addMarks,
groupMarksByFileNamePage,
isPdf,
pdfToImages
} from './pdf.ts'
import JSZip from 'jszip'
export const getZipWithFiles = async (
meta: Meta,
files: { [filename: string]: SigitFile }
): Promise<JSZip> => {
const zip = new JSZip()
const marks = extractMarksFromSignedMeta(meta)
const marksByFileNamePage = groupMarksByFileNamePage(marks)
for (const [fileName, file] of Object.entries(files)) {
// Handle PDF Files, add marks
if (file.isPdf && fileName in marksByFileNamePage) {
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
) {
// Fetch and decrypt the original file
const link = m.value.split('/')
const decrypted = await fetchAndDecrypt(m.value, encryptionKey)
// Save decrypted
zip.file(
`signatures/${link[link.length - 1]}.json`,
new Blob([decrypted])
)
marks[i].value = decrypted
}
} catch (error) {
console.error(`Error during mark fetchAndDecrypt phase`, error)
}
}
}
}
const blob = await addMarks(file, marksToAdd)
zip.file(`marked/${fileName}`, blob)
}
// Save original files
zip.file(`files/${fileName}`, file)
}
return zip
}
/**
* 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
isImage: boolean
pages?: PdfPage[]
objectUrl?: string
constructor(file: File) {
super([file], file.name, { type: file.type })
this.isPdf = isPdf(this)
this.isImage = isImage(this)
this.extension = extractFileExtension(this.name)
}
async process() {
if (this.isPdf) this.pages = await pdfToImages(await this.arrayBuffer())
if (this.isImage) this.objectUrl = URL.createObjectURL(this)
}
}
export const getSigitFile = async (file: File) => {
const sigitFile = new SigitFile(file)
// Process sigit file
// - generate pages for PDF files
// - generate ObjectRL for image files
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)
}
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())
}