implements png to pdf conversion and ability to download full sigits after signing
This commit is contained in:
parent
9b82c32be8
commit
b835099e36
@ -32,6 +32,7 @@
|
||||
"lodash": "4.17.21",
|
||||
"mui-file-input": "4.0.4",
|
||||
"nostr-tools": "2.7.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pdfjs-dist": "^4.4.168",
|
||||
"react": "^18.2.0",
|
||||
"react-dnd": "16.0.1",
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Box, Button, FormControl, InputLabel, TextField, Typography } from '@mui/material'
|
||||
import { Box, Button, Typography } from '@mui/material'
|
||||
import axios from 'axios'
|
||||
import saveAs from 'file-saver'
|
||||
import JSZip from 'jszip'
|
||||
import _, { set } from 'lodash'
|
||||
import _ from 'lodash'
|
||||
import { MuiFileInput } from 'mui-file-input'
|
||||
import { Event, verifyEvent } from 'nostr-tools'
|
||||
import { useEffect, useState } from 'react'
|
||||
@ -13,7 +13,7 @@ import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||
import { NostrController } from '../../controllers'
|
||||
import { appPublicRoutes } from '../../routes'
|
||||
import { State } from '../../store/rootReducer'
|
||||
import { CreateSignatureEventContent, Meta, SignedEvent, UserRole } from '../../types'
|
||||
import { CreateSignatureEventContent, Meta, SignedEvent } from '../../types'
|
||||
import {
|
||||
decryptArrayBuffer,
|
||||
encryptArrayBuffer,
|
||||
@ -34,10 +34,11 @@ import {
|
||||
import { DisplayMeta } from './internal/displayMeta'
|
||||
import styles from './style.module.scss'
|
||||
import { PdfFile } from '../../types/drawing.ts'
|
||||
import { toFile, toPdfFile } from '../../utils/pdf.ts'
|
||||
import { convertToPdfFile } from '../../utils/pdf.ts'
|
||||
import PdfView from '../../components/PDFView'
|
||||
import { CurrentUserMark, Mark, MarkConfig, MarkConfigDetails, User } from '../../types/mark.ts'
|
||||
import { CurrentUserMark, Mark, MarkConfig } from '../../types/mark.ts'
|
||||
import MarkFormField from './MarkFormField.tsx'
|
||||
import { getLastSignersSig } from '../../utils/sign.ts'
|
||||
enum SignedStatus {
|
||||
Fully_Signed,
|
||||
User_Is_Next_Signer,
|
||||
@ -98,7 +99,6 @@ export const SignPage = () => {
|
||||
const [currentUserMarks, setCurrentUserMarks] = useState<CurrentUserMark[]>([]);
|
||||
const [currentMarkValue, setCurrentMarkValue] = useState<string>('');
|
||||
const [isMarksCompleted, setIsMarksCompleted] = useState(false);
|
||||
const [isLastUserMark, setIsLastUserMark] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (signers.length > 0) {
|
||||
@ -330,11 +330,6 @@ export const SignPage = () => {
|
||||
setCurrentFileHashes(fileHashes)
|
||||
}
|
||||
|
||||
const convertToPdfFile = async (arrayBuffer: ArrayBuffer, fileName: string) => {
|
||||
const file = toFile(arrayBuffer, fileName);
|
||||
return toPdfFile(file);
|
||||
}
|
||||
|
||||
const parseKeysJson = async (zip: JSZip) => {
|
||||
const keysFileContent = await readContentOfZipEntry(
|
||||
zip,
|
||||
@ -491,8 +486,6 @@ export const SignPage = () => {
|
||||
}
|
||||
)
|
||||
|
||||
console.log('parsed meta: ', parsedMetaJson)
|
||||
|
||||
setMeta(parsedMetaJson)
|
||||
}
|
||||
|
||||
@ -616,7 +609,7 @@ export const SignPage = () => {
|
||||
encryptionKey: string
|
||||
): Promise<File | null> => {
|
||||
// Get the current timestamp in seconds
|
||||
const unixNow = Math.floor(Date.now() / 1000)
|
||||
const unixNow = now()
|
||||
const blob = new Blob([encryptedArrayBuffer])
|
||||
// Create a File object with the Blob data
|
||||
const file = new File([blob], `compressed.sigit`, {
|
||||
@ -763,7 +756,9 @@ export const SignPage = () => {
|
||||
setIsLoading(true)
|
||||
setLoadingSpinnerDesc('Signing nostr event')
|
||||
|
||||
const prevSig = getLastSignersSig()
|
||||
if (!meta) return;
|
||||
|
||||
const prevSig = getLastSignersSig(meta, signers)
|
||||
if (!prevSig) return
|
||||
|
||||
const signedEvent = await signEventForMetaFile(
|
||||
@ -813,7 +808,7 @@ export const SignPage = () => {
|
||||
if (!arrayBuffer) return
|
||||
|
||||
const blob = new Blob([arrayBuffer])
|
||||
const unixNow = Math.floor(Date.now() / 1000)
|
||||
const unixNow = now()
|
||||
saveAs(blob, `exported-${unixNow}.sigit.zip`)
|
||||
|
||||
setIsLoading(false)
|
||||
@ -878,7 +873,7 @@ export const SignPage = () => {
|
||||
const finalZipFile = await createFinalZipFile(encryptedArrayBuffer, key)
|
||||
|
||||
if (!finalZipFile) return
|
||||
const unixNow = Math.floor(Date.now() / 1000)
|
||||
const unixNow = now()
|
||||
saveAs(finalZipFile, `exported-${unixNow}.sigit.zip`)
|
||||
}
|
||||
|
||||
@ -915,37 +910,6 @@ export const SignPage = () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns the signature of last signer
|
||||
* It will be used in the content of export signature's signedEvent
|
||||
*/
|
||||
const getLastSignersSig = () => {
|
||||
if (!meta) return null
|
||||
|
||||
// if there're no signers then use creator's signature
|
||||
if (signers.length === 0) {
|
||||
try {
|
||||
const createSignatureEvent: Event = JSON.parse(meta.createSignature)
|
||||
return createSignatureEvent.sig
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// get last signer
|
||||
const lastSigner = signers[signers.length - 1]
|
||||
|
||||
// get the signature of last signer
|
||||
try {
|
||||
const lastSignatureEvent: Event = JSON.parse(
|
||||
meta.docSignatures[lastSigner]
|
||||
)
|
||||
return lastSignatureEvent.sig
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
if (authUrl) {
|
||||
return (
|
||||
<iframe
|
||||
|
@ -15,7 +15,7 @@ import { useEffect, useState } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||
import { UserComponent } from '../../components/username'
|
||||
import { MetadataController } from '../../controllers'
|
||||
import { MetadataController, NostrController } from '../../controllers'
|
||||
import {
|
||||
CreateSignatureEventContent,
|
||||
Meta,
|
||||
@ -23,19 +23,30 @@ import {
|
||||
SignedEventContent
|
||||
} from '../../types'
|
||||
import {
|
||||
decryptArrayBuffer,
|
||||
decryptArrayBuffer, extractMarksFromSignedMeta,
|
||||
extractZipUrlAndEncryptionKey,
|
||||
getHash,
|
||||
hexToNpub,
|
||||
hexToNpub, now,
|
||||
npubToHex,
|
||||
parseJson,
|
||||
readContentOfZipEntry,
|
||||
shorten
|
||||
shorten, signEventForMetaFile
|
||||
} from '../../utils'
|
||||
import styles from './style.module.scss'
|
||||
import { Cancel, CheckCircle } from '@mui/icons-material'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import axios from 'axios'
|
||||
import { PdfFile } from '../../types/drawing.ts'
|
||||
import {
|
||||
addMarks,
|
||||
convertToPdfBlob,
|
||||
convertToPdfFile,
|
||||
groupMarksByPage,
|
||||
} from '../../utils/pdf.ts'
|
||||
import { State } from '../../store/rootReducer.ts'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { getLastSignersSig } from '../../utils/sign.ts'
|
||||
import { saveAs } from 'file-saver'
|
||||
|
||||
export const VerifyPage = () => {
|
||||
const theme = useTheme()
|
||||
@ -66,10 +77,13 @@ export const VerifyPage = () => {
|
||||
const [currentFileHashes, setCurrentFileHashes] = useState<{
|
||||
[key: string]: string | null
|
||||
}>({})
|
||||
const [files, setFiles] = useState<{ [filename: string]: PdfFile}>({})
|
||||
|
||||
const [metadata, setMetadata] = useState<{ [key: string]: ProfileMetadata }>(
|
||||
{}
|
||||
)
|
||||
const usersPubkey = useSelector((state: State) => state.auth.usersPubkey)
|
||||
const nostrController = NostrController.getInstance()
|
||||
|
||||
useEffect(() => {
|
||||
if (uploadedZip) {
|
||||
@ -124,6 +138,7 @@ export const VerifyPage = () => {
|
||||
|
||||
if (!zip) return
|
||||
|
||||
const files: { [filename: string]: PdfFile } = {}
|
||||
const fileHashes: { [key: string]: string | null } = {}
|
||||
const fileNames = Object.values(zip.files).map(
|
||||
(entry) => entry.name
|
||||
@ -139,6 +154,7 @@ export const VerifyPage = () => {
|
||||
)
|
||||
|
||||
if (arrayBuffer) {
|
||||
files[fileName] = await convertToPdfFile(arrayBuffer, fileName!)
|
||||
const hash = await getHash(arrayBuffer)
|
||||
|
||||
if (hash) {
|
||||
@ -150,6 +166,8 @@ export const VerifyPage = () => {
|
||||
}
|
||||
|
||||
setCurrentFileHashes(fileHashes)
|
||||
setFiles(files)
|
||||
|
||||
|
||||
setSigners(createSignatureContent.signers)
|
||||
setViewers(createSignatureContent.viewers)
|
||||
@ -158,6 +176,8 @@ export const VerifyPage = () => {
|
||||
|
||||
setMeta(metaInNavState)
|
||||
setIsLoading(false)
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
@ -359,6 +379,77 @@ export const VerifyPage = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleExport = async () => {
|
||||
// const proverbialUrls = await addMarksV2(Object.values(files)[0].file, meta!)
|
||||
// await convertToFinalPdf(proverbialUrls)
|
||||
|
||||
|
||||
if (Object.entries(files).length === 0 ||!meta ||!usersPubkey) return;
|
||||
|
||||
const usersNpub = hexToNpub(usersPubkey)
|
||||
if (
|
||||
!signers.includes(usersNpub) &&
|
||||
!viewers.includes(usersNpub) &&
|
||||
submittedBy !== usersNpub
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
setLoadingSpinnerDesc('Signing nostr event')
|
||||
|
||||
if (!meta) return;
|
||||
|
||||
const prevSig = getLastSignersSig(meta, signers)
|
||||
if (!prevSig) return;
|
||||
|
||||
const signedEvent = await signEventForMetaFile(
|
||||
JSON.stringify({ prevSig }),
|
||||
nostrController,
|
||||
setIsLoading
|
||||
)
|
||||
|
||||
if (!signedEvent) return;
|
||||
|
||||
const exportSignature = JSON.stringify(signedEvent, null, 2)
|
||||
const updatedMeta = {...meta, exportSignature }
|
||||
const stringifiedMeta = JSON.stringify(updatedMeta, null, 2)
|
||||
|
||||
const zip = new JSZip()
|
||||
zip.file('meta.json', stringifiedMeta)
|
||||
|
||||
const marks = extractMarksFromSignedMeta(updatedMeta)
|
||||
const marksByPage = groupMarksByPage(marks)
|
||||
|
||||
for (const [fileName, pdf] of Object.entries(files)) {
|
||||
const pages = await addMarks(pdf.file, marksByPage)
|
||||
const blob = await convertToPdfBlob(pages)
|
||||
zip.file(`/files/${fileName}`, blob)
|
||||
}
|
||||
|
||||
const arrayBuffer = await zip
|
||||
.generateAsync({
|
||||
type: 'arraybuffer',
|
||||
compression: 'DEFLATE',
|
||||
compressionOptions: {
|
||||
level: 6
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('err in zip:>> ', err)
|
||||
setIsLoading(false)
|
||||
toast.error(err.message || 'Error occurred in generating zip file')
|
||||
return null
|
||||
})
|
||||
|
||||
if (!arrayBuffer) return
|
||||
|
||||
const blob = new Blob([arrayBuffer])
|
||||
saveAs(blob, `exported-${now()}.sigit.zip`)
|
||||
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
const displayUser = (pubkey: string, verifySignature = false) => {
|
||||
const profile = metadata[pubkey]
|
||||
|
||||
@ -521,6 +612,11 @@ export const VerifyPage = () => {
|
||||
Exported By
|
||||
</Typography>
|
||||
{displayExportedBy()}
|
||||
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
|
||||
<Button onClick={handleExport} variant="contained">
|
||||
Export Sigit
|
||||
</Button>
|
||||
</Box>
|
||||
</ListItem>
|
||||
|
||||
{signers.length > 0 && (
|
||||
|
@ -30,7 +30,7 @@ export interface CreateSignatureEventContent {
|
||||
|
||||
export interface SignedEventContent {
|
||||
prevSig: string
|
||||
markConfig: Mark[]
|
||||
marks: Mark[]
|
||||
}
|
||||
|
||||
export interface Sigit {
|
||||
|
@ -12,10 +12,11 @@ import { toast } from 'react-toastify'
|
||||
import { NostrController } from '../controllers'
|
||||
import { AuthState } from '../store/auth/types'
|
||||
import store from '../store/store'
|
||||
import { CreateSignatureEventContent, Meta } from '../types'
|
||||
import { CreateSignatureEventContent, Meta, SignedEventContent } from '../types'
|
||||
import { hexToNpub, now } from './nostr'
|
||||
import { parseJson } from './string'
|
||||
import { hexToBytes } from '@noble/hashes/utils'
|
||||
import { Mark } from '../types/mark.ts'
|
||||
|
||||
/**
|
||||
* Uploads a file to a file storage service.
|
||||
@ -264,3 +265,10 @@ export const extractZipUrlAndEncryptionKey = async (meta: Meta) => {
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export const extractMarksFromSignedMeta = (meta: Meta): Mark[] => {
|
||||
return Object.values(meta.docSignatures)
|
||||
.map((val: string) => JSON.parse(val as string))
|
||||
.map((val: Event) => JSON.parse(val.content))
|
||||
.flatMap((val: SignedEventContent) => val.marks);
|
||||
}
|
||||
|
178
src/utils/pdf.ts
178
src/utils/pdf.ts
@ -1,33 +1,64 @@
|
||||
import { PdfFile, PdfPage } from '../types/drawing.ts'
|
||||
import * as PDFJS from 'pdfjs-dist'
|
||||
import { PDFDocument } from 'pdf-lib'
|
||||
import { Mark } from '../types/mark.ts'
|
||||
|
||||
PDFJS.GlobalWorkerOptions.workerSrc = 'node_modules/pdfjs-dist/build/pdf.worker.mjs';
|
||||
|
||||
/**
|
||||
* Scale between the PDF page's natural size and rendered size
|
||||
* @constant {number}
|
||||
*/
|
||||
const SCALE: number = 3;
|
||||
|
||||
/**
|
||||
* Converts a PDF ArrayBuffer to a generic PDF File
|
||||
* @param arrayBuffer of a PDF
|
||||
* @param fileName identifier of the pdf file
|
||||
*/
|
||||
const toFile = (arrayBuffer: ArrayBuffer, fileName: string) : File => {
|
||||
const blob = new Blob([arrayBuffer], { type: "application/pdf" });
|
||||
return new File([blob], fileName, { type: "application/pdf" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a generic PDF File to Sigit's internal Pdf File type
|
||||
* @param {File} file
|
||||
* @return {PdfFile} Sigit's internal PDF File type
|
||||
*/
|
||||
const toPdfFile = async (file: File): Promise<PdfFile> => {
|
||||
const data = await readPdf(file)
|
||||
const pages = await pdfToImages(data)
|
||||
return { file, pages, expanded: false }
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms an array of generic PDF Files into an array of Sigit's
|
||||
* internal representation of Pdf Files
|
||||
* @param selectedFiles - an array of generic PDF Files
|
||||
* @return PdfFile[] - an array of Sigit's internal Pdf File type
|
||||
*/
|
||||
const toPdfFiles = async (selectedFiles: File[]): Promise<PdfFile[]> => {
|
||||
return Promise.all(
|
||||
selectedFiles
|
||||
.filter(isPdf)
|
||||
.map(toPdfFile));
|
||||
return Promise.all(selectedFiles
|
||||
.filter(isPdf)
|
||||
.map(toPdfFile));
|
||||
}
|
||||
|
||||
/**
|
||||
* A utility that transforms a drawing coordinate number into a CSS-compatible string
|
||||
* @param coordinate
|
||||
*/
|
||||
const inPx = (coordinate: number): string => `${coordinate}px`;
|
||||
|
||||
/**
|
||||
* A utility that checks if a given file is of the pdf type
|
||||
* @param file
|
||||
*/
|
||||
const isPdf = (file: File) => file.type.toLowerCase().includes('pdf');
|
||||
|
||||
/**
|
||||
* Reads the pdf file binaries
|
||||
*/
|
||||
const readPdf = (file: File) => {
|
||||
const readPdf = (file: File): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
@ -57,7 +88,7 @@ const pdfToImages = async (data: any): Promise<PdfPage[]> => {
|
||||
|
||||
for (let i = 0; i < pdf.numPages; i++) {
|
||||
const page = await pdf.getPage(i + 1);
|
||||
const viewport = page.getViewport({ scale: 3 });
|
||||
const viewport = page.getViewport({ scale: SCALE });
|
||||
const context = canvas.getContext("2d");
|
||||
canvas.height = viewport.height;
|
||||
canvas.width = viewport.width;
|
||||
@ -73,9 +104,140 @@ const pdfToImages = async (data: any): Promise<PdfPage[]> => {
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes in individual pdf file and an object with Marks grouped by Page number
|
||||
* Returns an array of encoded images where each image is a representation
|
||||
* of a PDF page with completed and signed marks from all users
|
||||
*/
|
||||
const addMarks = async (file: File, marksPerPage: {[key: string]: Mark[]}) => {
|
||||
const p = await readPdf(file);
|
||||
const pdf = await PDFJS.getDocument(p).promise;
|
||||
const canvas = document.createElement("canvas");
|
||||
|
||||
const images: string[] = [];
|
||||
|
||||
for (let i = 0; i< pdf.numPages; i++) {
|
||||
const page = await pdf.getPage(i+1)
|
||||
const viewport = page.getViewport({ scale: SCALE });
|
||||
const context = canvas.getContext("2d");
|
||||
canvas.height = viewport.height;
|
||||
canvas.width = viewport.width;
|
||||
await page.render({ canvasContext: context!, viewport: viewport }).promise;
|
||||
|
||||
marksPerPage[i].forEach(mark => draw(mark, context!))
|
||||
|
||||
images.push(canvas.toDataURL());
|
||||
}
|
||||
|
||||
return Promise.resolve(images);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to scale mark in line with the PDF-to-PNG scale
|
||||
*/
|
||||
const scaleMark = (mark: Mark): Mark => {
|
||||
const { location } = mark;
|
||||
return {
|
||||
...mark,
|
||||
location: {
|
||||
...location,
|
||||
width: location.width * SCALE,
|
||||
height: location.height * SCALE,
|
||||
left: location.left * SCALE,
|
||||
top: location.top * SCALE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to check if a Mark has value
|
||||
* @param mark
|
||||
*/
|
||||
const hasValue = (mark: Mark): boolean => !!mark.value;
|
||||
|
||||
/**
|
||||
* Draws a Mark on a Canvas representation of a PDF Page
|
||||
* @param mark to be drawn
|
||||
* @param ctx a Canvas representation of a specific PDF Page
|
||||
*/
|
||||
const draw = (mark: Mark, ctx: CanvasRenderingContext2D) => {
|
||||
const { location } = mark;
|
||||
ctx!.font = '20px Arial';
|
||||
ctx!.fillStyle = 'black';
|
||||
const textMetrics = ctx!.measureText(mark.value!);
|
||||
const textX = location.left + (location.width - textMetrics.width) / 2;
|
||||
const textY = location.top + (location.height + parseInt(ctx!.font)) / 2;
|
||||
ctx!.fillText(mark.value!, textX, textY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an array of encoded PDF pages and returns a blob that is a complete PDF file
|
||||
* @param markedPdfPages
|
||||
*/
|
||||
const convertToPdfBlob = async (markedPdfPages: string[]): Promise<Blob> => {
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
|
||||
for (const page of markedPdfPages) {
|
||||
const pngImage = await pdfDoc.embedPng(page)
|
||||
const p = pdfDoc.addPage([pngImage.width, pngImage.height])
|
||||
p.drawImage(pngImage, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: pngImage.width,
|
||||
height: pngImage.height
|
||||
})
|
||||
}
|
||||
|
||||
const pdfBytes = await pdfDoc.save()
|
||||
return new Blob([pdfBytes], { type: 'application/pdf' })
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an ArrayBuffer of a PDF file and converts to Sigit's Internal Pdf File type
|
||||
* @param arrayBuffer
|
||||
* @param fileName
|
||||
*/
|
||||
const convertToPdfFile = async (arrayBuffer: ArrayBuffer, fileName: string): Promise<PdfFile> => {
|
||||
const file = toFile(arrayBuffer, fileName);
|
||||
return toPdfFile(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param marks - an array of Marks
|
||||
* @function hasValue removes any Mark without a property
|
||||
* @function scaleMark scales remaining marks in line with SCALE
|
||||
* @function byPage groups remaining Marks by their page marks.location.page
|
||||
*/
|
||||
const groupMarksByPage = (marks: Mark[]) => {
|
||||
return marks
|
||||
.filter(hasValue)
|
||||
.map(scaleMark)
|
||||
.reduce<{[key: number]: Mark[]}>(byPage, {})
|
||||
}
|
||||
|
||||
/**
|
||||
* A reducer callback that transforms an array of marks into an object grouped by the page number
|
||||
* Can be replaced by Object.groupBy https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/groupBy
|
||||
* when it is implemented in TypeScript
|
||||
* Implementation is standard from the Array.prototype.reduce documentation
|
||||
* @param obj - accumulator in the reducer callback
|
||||
* @param mark - current value, i.e. Mark being examined
|
||||
*/
|
||||
const byPage = (obj: { [key: number]: Mark[]}, mark: Mark) => {
|
||||
const key = mark.location.page;
|
||||
const curGroup = obj[key] ?? [];
|
||||
return { ...obj, [key]: [...curGroup, mark]
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
toFile,
|
||||
toPdfFile,
|
||||
toPdfFiles,
|
||||
inPx
|
||||
inPx,
|
||||
convertToPdfFile,
|
||||
addMarks,
|
||||
convertToPdfBlob,
|
||||
groupMarksByPage,
|
||||
}
|
33
src/utils/sign.ts
Normal file
33
src/utils/sign.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { Event } from 'nostr-tools'
|
||||
import { Meta } from '../types'
|
||||
|
||||
/**
|
||||
* This function returns the signature of last signer
|
||||
* It will be used in the content of export signature's signedEvent
|
||||
*/
|
||||
const getLastSignersSig = (meta: Meta, signers: `npub1${string}`[]): string | null => {
|
||||
// if there're no signers then use creator's signature
|
||||
if (signers.length === 0) {
|
||||
try {
|
||||
const createSignatureEvent: Event = JSON.parse(meta.createSignature)
|
||||
return createSignatureEvent.sig
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// get last signer
|
||||
const lastSigner = signers[signers.length - 1]
|
||||
|
||||
// get the signature of last signer
|
||||
try {
|
||||
const lastSignatureEvent: Event = JSON.parse(
|
||||
meta.docSignatures[lastSigner]
|
||||
)
|
||||
return lastSignatureEvent.sig
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export { getLastSignersSig }
|
Loading…
Reference in New Issue
Block a user