squiggle with signature-pad and smoother lines + blossom #258
20
package-lock.json
generated
20
package-lock.json
generated
@ -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"
|
||||
|
@ -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": {
|
||||
|
@ -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 (
|
||||
<div
|
||||
key={i}
|
||||
@ -73,7 +75,9 @@ const PdfPageItem = ({
|
||||
fontSize: inPx(from(page.width, FONT_SIZE))
|
||||
}}
|
||||
>
|
||||
{m.value}
|
||||
{typeof MarkRenderComponent !== 'undefined' && (
|
||||
<MarkRenderComponent value={m.value} mark={m} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -43,7 +43,9 @@ export interface MarkRenderProps {
|
||||
|
||||
export interface MarkConfig {
|
||||
input: React.FC<MarkInputProps>
|
||||
render?: React.FC<MarkRenderProps>
|
||||
render: React.FC<MarkRenderProps>
|
||||
encryptAndUpload?: (value: string, key?: string) => Promise<string>
|
||||
fetchAndDecrypt?: (value: string, key?: string) => Promise<string>
|
||||
}
|
||||
|
||||
export type MarkConfigs = {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user