diff --git a/package-lock.json b/package-lock.json
index 71ab84b..a03e759 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -48,7 +48,6 @@
"react-toastify": "10.0.4",
"redux": "5.0.1",
"signature_pad": "^5.0.4",
- "svgo": "^3.3.2",
"tseep": "1.2.1"
},
"devDependencies": {
@@ -2215,6 +2214,7 @@
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
+ "dev": true,
"license": "ISC",
"engines": {
"node": ">=10.13.0"
@@ -2936,6 +2936,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+ "dev": true,
"license": "ISC"
},
"node_modules/brace-expansion": {
@@ -3635,6 +3636,7 @@
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
+ "dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0",
@@ -3651,6 +3653,7 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
"integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"mdn-data": "2.0.30",
@@ -3664,6 +3667,7 @@
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
+ "dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">= 6"
@@ -3676,6 +3680,7 @@
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz",
"integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"css-tree": "~2.2.0"
@@ -3689,6 +3694,7 @@
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz",
"integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"mdn-data": "2.0.28",
@@ -3703,6 +3709,7 @@
"version": "2.0.28",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz",
"integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",
+ "dev": true,
"license": "CC0-1.0"
},
"node_modules/csstype": {
@@ -3950,6 +3957,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
@@ -3977,6 +3985,7 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -3989,6 +3998,7 @@
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+ "dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"domelementtype": "^2.3.0"
@@ -4004,6 +4014,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
+ "dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"dom-serializer": "^2.0.0",
@@ -4053,6 +4064,7 @@
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
@@ -5980,6 +5992,7 @@
"version": "2.0.30",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
+ "dev": true,
"license": "CC0-1.0"
},
"node_modules/merge-stream": {
@@ -6561,6 +6574,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0"
@@ -6920,6 +6934,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
+ "dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
@@ -7978,6 +7993,7 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@@ -8204,6 +8220,7 @@
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz",
"integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@trysound/sax": "0.2.0",
@@ -8229,6 +8246,7 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">= 10"
diff --git a/package.json b/package.json
index 02e1946..ab49da0 100644
--- a/package.json
+++ b/package.json
@@ -58,7 +58,6 @@
"react-toastify": "10.0.4",
"redux": "5.0.1",
"signature_pad": "^5.0.4",
- "svgo": "^3.3.2",
"tseep": "1.2.1"
},
"devDependencies": {
diff --git a/src/components/PDFView/PdfPageItem.tsx b/src/components/PDFView/PdfPageItem.tsx
index 5d4be7d..9f56550 100644
--- a/src/components/PDFView/PdfPageItem.tsx
+++ b/src/components/PDFView/PdfPageItem.tsx
@@ -6,6 +6,7 @@ import { useEffect, useRef } from 'react'
import pdfViewStyles from './style.module.scss'
import { FONT_SIZE, FONT_TYPE, inPx } from '../../utils/pdf.ts'
import { useScale } from '../../hooks/useScale.tsx'
+import { MARK_TYPE_CONFIG } from '../getMarkComponents.tsx'
interface PdfPageProps {
fileName: string
pageIndex: number
@@ -60,6 +61,7 @@ const PdfPageItem = ({
/>
))}
{otherUserMarks.map((m, i) => {
+ const { render: MarkRenderComponent } = MARK_TYPE_CONFIG[m.type] || {}
return (
- {m.value}
+ {typeof MarkRenderComponent !== 'undefined' && (
+
+ )}
)
})}
diff --git a/src/components/getMarkComponents.tsx b/src/components/getMarkComponents.tsx
index 507f388..e81bdd4 100644
--- a/src/components/getMarkComponents.tsx
+++ b/src/components/getMarkComponents.tsx
@@ -1,8 +1,17 @@
+import { toast } from 'react-toastify'
import { MarkType } from '../types/drawing'
import { MarkConfigs } from '../types/mark'
+import {
+ decryptArrayBuffer,
+ encryptArrayBuffer,
+ getHash,
+ isOnline,
+ uploadToFileStorage
+} from '../utils'
import { MarkInputSignature } from './MarkInputs/Signature'
import { MarkInputText } from './MarkInputs/Text'
import { MarkRenderSignature } from './MarkRender/Signature'
+import axios from 'axios'
export const MARK_TYPE_CONFIG: MarkConfigs = {
[MarkType.TEXT]: {
@@ -11,6 +20,73 @@ export const MARK_TYPE_CONFIG: MarkConfigs = {
},
[MarkType.SIGNATURE]: {
input: MarkInputSignature,
- render: MarkRenderSignature
+ render: MarkRenderSignature,
+ encryptAndUpload: async (value, encryptionKey) => {
+ // Value is the stringified signature object
+ // Encode it as text to the arrayBuffer
+ const encoder = new TextEncoder()
+ const uint8Array = encoder.encode(value)
+ const hash = await getHash(uint8Array)
+
+ if (!hash) {
+ throw new Error("Can't get file hash.")
+ }
+
+ if (!encryptionKey) {
+ throw new Error('Signature requires an encryption key')
+ }
+
+ // Encrypt the file contents with the same encryption key from the create signature
+ const encryptedArrayBuffer = await encryptArrayBuffer(
+ uint8Array,
+ encryptionKey
+ )
+
+ // Create the encrypted json file from array buffer and hash
+ const file = new File([encryptedArrayBuffer], `${hash}.json`)
+
+ if (await isOnline()) {
+ try {
+ const url = await uploadToFileStorage(file)
+ toast.success('files.zip uploaded to file storage')
+ return url
+ } catch (error) {
+ if (error instanceof Error) {
+ toast.error(error.message || 'Error occurred in uploading file')
+ }
+ }
+ } else {
+ // Handle offline?
+ }
+
+ return value
+ },
+ fetchAndDecrypt: async (value, encryptionKey) => {
+ if (!encryptionKey) {
+ throw new Error('Signature requires an encryption key')
+ }
+
+ const encryptedArrayBuffer = await axios.get(value, {
+ responseType: 'arraybuffer'
+ })
+
+ const arrayBuffer = await decryptArrayBuffer(
+ encryptedArrayBuffer.data,
+ encryptionKey
+ ).catch((err) => {
+ console.log('err in decryption:>> ', err)
+ return null
+ })
+
+ if (arrayBuffer) {
+ // decode json
+ const decoder = new TextDecoder()
+ const value = decoder.decode(arrayBuffer)
+ return value
+ }
+
+ // Handle offline?
+ return value
+ }
}
}
diff --git a/src/hooks/useSigitMeta.tsx b/src/hooks/useSigitMeta.tsx
index 8f79dc6..d37ae1e 100644
--- a/src/hooks/useSigitMeta.tsx
+++ b/src/hooks/useSigitMeta.tsx
@@ -21,6 +21,7 @@ import { Event } from 'nostr-tools'
import store from '../store/store'
import { NostrController } from '../controllers'
import { MetaParseError } from '../types/errors/MetaParseError'
+import { MARK_TYPE_CONFIG } from '../components/getMarkComponents'
/**
* Flattened interface that combines properties `Meta`, `CreateSignatureEventContent`,
@@ -142,6 +143,7 @@ export const useSigitMeta = (meta: Meta): FlatMeta => {
setMarkConfig(markConfig)
setZipUrl(zipUrl)
+ let encryptionKey: string | null = null
if (meta.keys) {
const { sender, keys } = meta.keys
// Retrieve the user's public key from the state
@@ -162,6 +164,7 @@ export const useSigitMeta = (meta: Meta): FlatMeta => {
return null
})
+ encryptionKey = decrypted
setEncryptionKey(decrypted)
}
}
@@ -206,13 +209,40 @@ export const useSigitMeta = (meta: Meta): FlatMeta => {
}
}
- parsedSignatureEventsMap.forEach((event, npub) => {
+ for (const [npub, event] of parsedSignatureEventsMap) {
const isValidSignature = verifyEvent(event)
if (isValidSignature) {
// get the signature of prev signer from the content of current signers signedEvent
const prevSignersSig = getPrevSignerSig(npub)
+
try {
const obj: SignedEventContent = JSON.parse(event.content)
+
+ // Signature object can include values that need to be fetched and decrypted
+ for (let i = 0; i < obj.marks.length; i++) {
+ const m = obj.marks[i]
+
+ try {
+ const { fetchAndDecrypt } = MARK_TYPE_CONFIG[m.type] || {}
+ if (
+ typeof fetchAndDecrypt === 'function' &&
+ m.value &&
+ encryptionKey
+ ) {
+ const decrypted = await fetchAndDecrypt(
+ m.value,
+ encryptionKey
+ )
+ obj.marks[i].value = decrypted
+ }
+ } catch (error) {
+ console.error(
+ `Error during mark fetchAndDecrypt phase`,
+ error
+ )
+ }
+ }
+
parsedSignatureEventsMap.set(npub, {
...event,
parsedContent: obj
@@ -228,7 +258,7 @@ export const useSigitMeta = (meta: Meta): FlatMeta => {
signerStatusMap.set(npub as `npub1${string}`, SignStatus.Invalid)
}
}
- })
+ }
signers
.filter((s) => !parsedSignatureEventsMap.has(s))
diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx
index 0a022eb..847ce2a 100644
--- a/src/pages/sign/index.tsx
+++ b/src/pages/sign/index.tsx
@@ -33,7 +33,8 @@ import {
signEventForMetaFile,
updateUsersAppData,
findOtherUserMarks,
- timeout
+ timeout,
+ processMarks
} from '../../utils'
import { Container } from '../../components/Container'
import { DisplayMeta } from './internal/displayMeta'
@@ -54,6 +55,7 @@ import {
} from '../../utils/file.ts'
import { ARRAY_BUFFER, DEFLATE } from '../../utils/const.ts'
import { generateTimestamp } from '../../utils/opentimestamps.ts'
+import { MARK_TYPE_CONFIG } from '../../components/getMarkComponents.tsx'
enum SignedStatus {
Fully_Signed,
@@ -237,6 +239,43 @@ export const SignPage = () => {
const signedMarks = extractMarksFromSignedMeta(meta)
const currentUserMarks = getCurrentUserMarks(metaMarks, signedMarks)
const otherUserMarks = findOtherUserMarks(signedMarks, usersPubkey!)
+
+ if (meta.keys) {
+ for (let i = 0; i < otherUserMarks.length; i++) {
+ const m = otherUserMarks[i]
+ const { sender, keys } = meta.keys
+ const usersNpub = hexToNpub(usersPubkey)
+ if (usersNpub in keys) {
+ const encryptionKey = await nostrController
+ .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
+ ) {
+ const decrypted = await fetchAndDecrypt(
+ m.value,
+ encryptionKey
+ )
+ otherUserMarks[i].value = decrypted
+ }
+ } catch (error) {
+ console.error(`Error during mark fetchAndDecrypt phase`, error)
+ }
+ }
+ }
+ }
+
setOtherUserMarks(otherUserMarks)
setCurrentUserMarks(currentUserMarks)
setIsMarksCompleted(isCurrentUserMarksComplete(currentUserMarks))
@@ -248,6 +287,7 @@ export const SignPage = () => {
if (meta) {
handleUpdatedMeta(meta)
}
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [meta, usersPubkey])
const handleDownload = async () => {
@@ -552,8 +592,8 @@ export const SignPage = () => {
setIsLoading(true)
setLoadingSpinnerDesc('Signing nostr event')
-
- const prevSig = getPrevSignersSig(hexToNpub(usersPubkey!))
+ const usersNpub = hexToNpub(usersPubkey!)
+ const prevSig = getPrevSignersSig(usersNpub)
if (!prevSig) {
setIsLoading(false)
toast.error('Previous signature is invalid')
@@ -562,7 +602,26 @@ export const SignPage = () => {
const marks = getSignerMarksForMeta() || []
- const signedEvent = await signEventForMeta({ prevSig, marks })
+ let encryptionKey: string | undefined
+ if (meta.keys) {
+ const { sender, keys } = meta.keys
+ encryptionKey = await nostrController
+ .nip04Decrypt(sender, keys[usersNpub])
+ .catch((err) => {
+ // Log and display an error message if decryption fails
+ console.log('An error occurred in decrypting encryption key', err)
+ toast.error('An error occurred in decrypting encryption key')
+ return undefined
+ })
+ }
+
+ const processedMarks = await processMarks(marks, encryptionKey)
+
+ const signedEvent = await signEventForMeta({
+ prevSig,
+ marks: processedMarks
+ })
+
if (!signedEvent) return
const updatedMeta = updateMetaSignatures(meta, signedEvent)
diff --git a/src/pages/verify/index.tsx b/src/pages/verify/index.tsx
index 6ce4c83..7eafc78 100644
--- a/src/pages/verify/index.tsx
+++ b/src/pages/verify/index.tsx
@@ -367,82 +367,77 @@ export const VerifyPage = () => {
setIsLoading(true)
setLoadingSpinnerDesc('Fetching file from file server')
- axios
- .get(zipUrl, {
+ try {
+ const res = await axios.get(zipUrl, {
responseType: 'arraybuffer'
})
- .then(async (res) => {
- const fileName = zipUrl.split('/').pop()
- const file = new File([res.data], fileName!)
- const encryptedArrayBuffer = await file.arrayBuffer()
- const arrayBuffer = await decryptArrayBuffer(
- encryptedArrayBuffer,
- encryptionKey
- ).catch((err) => {
- console.log('err in decryption:>> ', err)
+ const fileName = zipUrl.split('/').pop()
+ const file = new File([res.data], fileName!)
+
+ const encryptedArrayBuffer = await file.arrayBuffer()
+ const arrayBuffer = await decryptArrayBuffer(
+ encryptedArrayBuffer,
+ encryptionKey
+ ).catch((err) => {
+ console.log('err in decryption:>> ', err)
+ toast.error(err.message || 'An error occurred in decrypting file.')
+ return null
+ })
+
+ if (arrayBuffer) {
+ const zip = await JSZip.loadAsync(arrayBuffer).catch((err) => {
+ console.log('err in loading zip file :>> ', err)
toast.error(
- err.message || 'An error occurred in decrypting file.'
+ err.message || 'An error occurred in loading zip file.'
)
return null
})
- if (arrayBuffer) {
- const zip = await JSZip.loadAsync(arrayBuffer).catch((err) => {
- console.log('err in loading zip file :>> ', err)
- toast.error(
- err.message || 'An error occurred in loading zip file.'
- )
- return null
- })
+ if (!zip) return
- if (!zip) return
+ const files: { [fileName: string]: SigitFile } = {}
+ const fileHashes: { [key: string]: string | null } = {}
+ const fileNames = Object.values(zip.files).map(
+ (entry) => entry.name
+ )
- const files: { [fileName: string]: SigitFile } = {}
- const fileHashes: { [key: string]: string | null } = {}
- const fileNames = Object.values(zip.files).map(
- (entry) => entry.name
+ // generate hashes for all entries in files folder of zipArchive
+ // these hashes can be used to verify the originality of files
+ for (const fileName of fileNames) {
+ const arrayBuffer = await readContentOfZipEntry(
+ zip,
+ fileName,
+ 'arraybuffer'
)
- // generate hashes for all entries in files folder of zipArchive
- // these hashes can be used to verify the originality of files
- for (const fileName of fileNames) {
- const arrayBuffer = await readContentOfZipEntry(
- zip,
- fileName,
- 'arraybuffer'
+ if (arrayBuffer) {
+ files[fileName] = await convertToSigitFile(
+ arrayBuffer,
+ fileName!
)
+ const hash = await getHash(arrayBuffer)
- if (arrayBuffer) {
- files[fileName] = await convertToSigitFile(
- arrayBuffer,
- fileName!
- )
- const hash = await getHash(arrayBuffer)
-
- if (hash) {
- fileHashes[fileName.replace(/^files\//, '')] = hash
- }
- } else {
- fileHashes[fileName.replace(/^files\//, '')] = null
+ if (hash) {
+ fileHashes[fileName.replace(/^files\//, '')] = hash
}
+ } else {
+ fileHashes[fileName.replace(/^files\//, '')] = null
}
-
- setCurrentFileHashes(fileHashes)
- setFiles(files)
-
- setIsLoading(false)
}
- })
- .catch((err) => {
- console.error(`error occurred in getting file from ${zipUrl}`, err)
- toast.error(
- err.message || `error occurred in getting file from ${zipUrl}`
- )
- })
- .finally(() => {
+
+ setCurrentFileHashes(fileHashes)
+ setFiles(files)
setIsLoading(false)
- })
+ }
+ } catch (err) {
+ const message = `error occurred in getting file from ${zipUrl}`
+ console.error(message, err)
+ if (err instanceof Error) toast.error(err.message)
+ else toast.error(message)
+ } finally {
+ setIsLoading(false)
+ }
}
processSigit()
diff --git a/src/types/mark.ts b/src/types/mark.ts
index ec1c162..0a54d3e 100644
--- a/src/types/mark.ts
+++ b/src/types/mark.ts
@@ -43,7 +43,9 @@ export interface MarkRenderProps {
export interface MarkConfig {
input: React.FC
- render?: React.FC
+ render: React.FC
+ encryptAndUpload?: (value: string, key?: string) => Promise
+ fetchAndDecrypt?: (value: string, key?: string) => Promise
}
export type MarkConfigs = {
diff --git a/src/utils/file.ts b/src/utils/file.ts
index c08d5e7..38dd0f1 100644
--- a/src/utils/file.ts
+++ b/src/utils/file.ts
@@ -1,7 +1,11 @@
+import { MARK_TYPE_CONFIG } from '../components/getMarkComponents.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,
@@ -21,7 +25,45 @@ export const getZipWithFiles = async (
for (const [fileName, file] of Object.entries(files)) {
// Handle PDF Files, add marks
if (file.isPdf && fileName in marksByFileNamePage) {
- const blob = await addMarks(file, marksByFileNamePage[fileName])
+ 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
+ ) {
+ // TODO
+ // extract draw with proper values
+ // save both pdf with marking and original hash files for signature
+ // ...
+ const decrypted = await fetchAndDecrypt(m.value, encryptionKey)
+ 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)
}
diff --git a/src/utils/mark.ts b/src/utils/mark.ts
index a5eb974..061f3f0 100644
--- a/src/utils/mark.ts
+++ b/src/utils/mark.ts
@@ -24,6 +24,7 @@ import {
faStamp,
faTableCellsLarge
} from '@fortawesome/free-solid-svg-icons'
+import { MARK_TYPE_CONFIG } from '../components/getMarkComponents.tsx'
/**
* Takes in an array of Marks already filtered by User.
@@ -279,6 +280,26 @@ export const getOptimizedPathsWithStrokeWidth = (svgString: string) => {
return tuples
}
+export const processMarks = async (marks: Mark[], encryptionKey?: string) => {
+ const _marks = [...marks]
+ for (let i = 0; i < _marks.length; i++) {
+ const mark = _marks[i]
+ const hasProcess =
+ mark.type in MARK_TYPE_CONFIG &&
+ typeof MARK_TYPE_CONFIG[mark.type]?.encryptAndUpload === 'function'
+
+ if (hasProcess) {
+ const value = mark.value!
+ const processFn = MARK_TYPE_CONFIG[mark.type]?.encryptAndUpload
+ if (processFn) {
+ mark.value = await processFn(value, encryptionKey)
+ }
+ }
+ }
+
+ return _marks
+}
+
export {
getCurrentUserMarks,
filterMarksByPubkey,
diff --git a/src/utils/pdf.ts b/src/utils/pdf.ts
index 41e1d02..88cdc67 100644
--- a/src/utils/pdf.ts
+++ b/src/utils/pdf.ts
@@ -1,5 +1,5 @@
import { MarkType, PdfPage } from '../types/drawing.ts'
-import { LineCapStyle, PDFDocument, PDFFont, PDFPage, rgb } from 'pdf-lib'
+import { PDFDocument, PDFFont, PDFPage, rgb } from 'pdf-lib'
import { Mark } from '../types/mark.ts'
import * as PDFJS from 'pdfjs-dist'
import PDFJSWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?worker'
@@ -11,6 +11,9 @@ if (!PDFJS.GlobalWorkerOptions.workerPort) {
import fontkit from '@pdf-lib/fontkit'
import defaultFont from '../assets/fonts/roboto-regular.ttf'
+import { BasicPoint } from 'signature_pad/dist/types/point'
+import SignaturePad from 'signature_pad'
+import { SIGNATURE_PAD_OPTIONS, SIGNATURE_PAD_SIZE } from './const.ts'
/**
* Defined font size used when generating a PDF. Currently it is difficult to fully
@@ -136,7 +139,7 @@ export const addMarks = async (
const mark = marksPerPage[i][j]
switch (mark.type) {
case MarkType.SIGNATURE:
- drawSignatureText(mark, pages[i])
+ await embedSignaturePng(mark, pages[i], pdf)
break
default:
@@ -255,23 +258,41 @@ async function embedFont(pdf: PDFDocument) {
return embeddedFont
}
-const drawSignatureText = (mark: Mark, page: PDFPage) => {
+const embedSignaturePng = async (
+ mark: Mark,
+ page: PDFPage,
+ pdf: PDFDocument
+) => {
const { location } = mark
const { height } = page.getSize()
- // Convert the mark location origin (top, left) to PDF origin (bottom, left)
- const x = location.left
- const y = height - location.top
-
if (hasValue(mark)) {
- const segments: string[][] = JSON.parse(mark.value!)
- segments.forEach(([d, w]) => {
- page.drawSvgPath(d, {
- x,
- y,
- borderWidth: parseFloat(w),
- borderLineCap: LineCapStyle.Round
- })
+ const data = JSON.parse(mark.value!).map((p: BasicPoint[]) => ({
+ points: p
+ }))
+ const canvas = document.createElement('canvas')
+ canvas.width = SIGNATURE_PAD_SIZE.width
+ canvas.height = SIGNATURE_PAD_SIZE.height
+ const pad = new SignaturePad(canvas, SIGNATURE_PAD_OPTIONS)
+ pad.fromData(data)
+ const signatureImage = await pdf.embedPng(pad.toDataURL())
+
+ const scaled = signatureImage.scaleToFit(location.width, location.height)
+
+ // Convert the mark location origin (top, left) to PDF origin (bottom, left)
+ // and center the image
+ const x = location.left + (location.width - scaled.width) / 2
+ const y =
+ height -
+ location.top -
+ location.height +
+ (location.height - scaled.height) / 2
+
+ page.drawImage(signatureImage, {
+ x,
+ y,
+ width: scaled.width,
+ height: scaled.height
})
}
}