2024-08-06 10:42:21 +00:00
|
|
|
import { useEffect, useState } from 'react'
|
2024-08-15 11:47:23 +00:00
|
|
|
import {
|
|
|
|
CreateSignatureEventContent,
|
|
|
|
DocSignatureEvent,
|
|
|
|
Meta,
|
|
|
|
SignedEventContent
|
|
|
|
} from '../types'
|
2024-08-12 12:26:03 +00:00
|
|
|
import { Mark } from '../types/mark'
|
|
|
|
import {
|
2024-08-13 09:52:05 +00:00
|
|
|
fromUnixTimestamp,
|
2024-08-13 11:32:32 +00:00
|
|
|
hexToNpub,
|
2024-08-13 15:28:14 +00:00
|
|
|
parseNostrEvent,
|
2024-08-12 12:26:03 +00:00
|
|
|
parseCreateSignatureEventContent,
|
|
|
|
SigitMetaParseError,
|
|
|
|
SigitStatus,
|
|
|
|
SignStatus
|
|
|
|
} from '../utils'
|
|
|
|
import { toast } from 'react-toastify'
|
|
|
|
import { verifyEvent } from 'nostr-tools'
|
2024-08-06 10:42:21 +00:00
|
|
|
import { Event } from 'nostr-tools'
|
2024-08-13 11:32:32 +00:00
|
|
|
import store from '../store/store'
|
|
|
|
import { AuthState } from '../store/auth/types'
|
|
|
|
import { NostrController } from '../controllers'
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Flattened interface that combines properties `Meta`, `CreateSignatureEventContent`,
|
|
|
|
* and `Event` (event's fields are made optional and pubkey and created_at replaced with our versions)
|
|
|
|
*/
|
2024-08-13 15:28:14 +00:00
|
|
|
export interface FlatMeta
|
2024-08-13 11:32:32 +00:00
|
|
|
extends Meta,
|
|
|
|
CreateSignatureEventContent,
|
|
|
|
Partial<Omit<Event, 'pubkey' | 'created_at'>> {
|
|
|
|
// Remove pubkey and use submittedBy as `npub1${string}`
|
|
|
|
submittedBy?: `npub1${string}`
|
|
|
|
|
|
|
|
// Remove created_at and replace with createdAt
|
|
|
|
createdAt?: number
|
2024-08-06 10:42:21 +00:00
|
|
|
|
2024-08-12 12:26:03 +00:00
|
|
|
// Validated create signature event
|
|
|
|
isValid: boolean
|
2024-08-07 12:15:20 +00:00
|
|
|
|
2024-08-13 11:32:32 +00:00
|
|
|
// Decryption
|
|
|
|
encryptionKey: string | null
|
|
|
|
|
2024-08-13 15:28:14 +00:00
|
|
|
// Parsed Document Signatures
|
2024-08-15 11:47:23 +00:00
|
|
|
parsedSignatureEvents: {
|
|
|
|
[signer: `npub1${string}`]: DocSignatureEvent
|
|
|
|
}
|
2024-08-13 15:28:14 +00:00
|
|
|
|
|
|
|
// Calculated completion time
|
|
|
|
completedAt?: number
|
|
|
|
|
2024-08-12 12:26:03 +00:00
|
|
|
// Calculated status fields
|
|
|
|
signedStatus: SigitStatus
|
|
|
|
signersStatus: {
|
|
|
|
[signer: `npub1${string}`]: SignStatus
|
2024-08-07 12:15:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-12 12:26:03 +00:00
|
|
|
/**
|
|
|
|
* Custom use hook for parsing the Sigit Meta
|
|
|
|
* @param meta Sigit Meta
|
|
|
|
* @returns flattened Meta object with calculated signed status
|
|
|
|
*/
|
|
|
|
export const useSigitMeta = (meta: Meta): FlatMeta => {
|
|
|
|
const [isValid, setIsValid] = useState(false)
|
|
|
|
const [kind, setKind] = useState<number>()
|
|
|
|
const [tags, setTags] = useState<string[][]>()
|
2024-08-13 11:32:32 +00:00
|
|
|
const [createdAt, setCreatedAt] = useState<number>()
|
|
|
|
const [submittedBy, setSubmittedBy] = useState<`npub1${string}`>() // submittedBy, pubkey from nostr event
|
2024-08-12 12:26:03 +00:00
|
|
|
const [id, setId] = useState<string>()
|
|
|
|
const [sig, setSig] = useState<string>()
|
|
|
|
|
|
|
|
const [signers, setSigners] = useState<`npub1${string}`[]>([])
|
|
|
|
const [viewers, setViewers] = useState<`npub1${string}`[]>([])
|
|
|
|
const [fileHashes, setFileHashes] = useState<{
|
|
|
|
[user: `npub1${string}`]: string
|
|
|
|
}>({})
|
|
|
|
const [markConfig, setMarkConfig] = useState<Mark[]>([])
|
|
|
|
const [title, setTitle] = useState<string>('')
|
|
|
|
const [zipUrl, setZipUrl] = useState<string>('')
|
|
|
|
|
2024-08-13 15:28:14 +00:00
|
|
|
const [parsedSignatureEvents, setParsedSignatureEvents] = useState<{
|
2024-08-15 11:47:23 +00:00
|
|
|
[signer: `npub1${string}`]: DocSignatureEvent
|
2024-08-13 15:28:14 +00:00
|
|
|
}>({})
|
|
|
|
|
|
|
|
const [completedAt, setCompletedAt] = useState<number>()
|
|
|
|
|
2024-08-12 12:26:03 +00:00
|
|
|
const [signedStatus, setSignedStatus] = useState<SigitStatus>(
|
|
|
|
SigitStatus.Partial
|
2024-08-06 10:42:21 +00:00
|
|
|
)
|
2024-08-12 12:26:03 +00:00
|
|
|
const [signersStatus, setSignersStatus] = useState<{
|
|
|
|
[signer: `npub1${string}`]: SignStatus
|
|
|
|
}>({})
|
2024-08-06 10:42:21 +00:00
|
|
|
|
2024-08-13 11:32:32 +00:00
|
|
|
const [encryptionKey, setEncryptionKey] = useState<string | null>(null)
|
|
|
|
|
2024-08-06 10:42:21 +00:00
|
|
|
useEffect(() => {
|
2024-08-12 12:26:03 +00:00
|
|
|
if (!meta) return
|
|
|
|
;(async function () {
|
|
|
|
try {
|
2024-08-13 15:28:14 +00:00
|
|
|
const createSignatureEvent = await parseNostrEvent(meta.createSignature)
|
2024-08-12 12:26:03 +00:00
|
|
|
|
|
|
|
const { kind, tags, created_at, pubkey, id, sig, content } =
|
|
|
|
createSignatureEvent
|
|
|
|
|
|
|
|
setIsValid(verifyEvent(createSignatureEvent))
|
|
|
|
setKind(kind)
|
|
|
|
setTags(tags)
|
|
|
|
// created_at in nostr events are stored in seconds
|
2024-08-13 09:52:05 +00:00
|
|
|
setCreatedAt(fromUnixTimestamp(created_at))
|
2024-08-13 11:32:32 +00:00
|
|
|
setSubmittedBy(pubkey as `npub1${string}`)
|
2024-08-12 12:26:03 +00:00
|
|
|
setId(id)
|
|
|
|
setSig(sig)
|
|
|
|
|
|
|
|
const { title, signers, viewers, fileHashes, markConfig, zipUrl } =
|
|
|
|
await parseCreateSignatureEventContent(content)
|
|
|
|
|
|
|
|
setTitle(title)
|
|
|
|
setSigners(signers)
|
|
|
|
setViewers(viewers)
|
|
|
|
setFileHashes(fileHashes)
|
|
|
|
setMarkConfig(markConfig)
|
|
|
|
setZipUrl(zipUrl)
|
|
|
|
|
2024-08-13 11:32:32 +00:00
|
|
|
if (meta.keys) {
|
|
|
|
const { sender, keys } = meta.keys
|
|
|
|
// Retrieve the user's public key from the state
|
|
|
|
const usersPubkey = (store.getState().auth as AuthState).usersPubkey!
|
|
|
|
const usersNpub = hexToNpub(usersPubkey)
|
|
|
|
|
|
|
|
// Check if the user's public key is in the keys object
|
|
|
|
if (usersNpub in keys) {
|
|
|
|
// Instantiate the NostrController to decrypt the encryption key
|
|
|
|
const nostrController = NostrController.getInstance()
|
|
|
|
const decrypted = await nostrController
|
|
|
|
.nip04Decrypt(sender, keys[usersNpub])
|
|
|
|
.catch((err) => {
|
|
|
|
console.log(
|
|
|
|
'An error occurred in decrypting encryption key',
|
|
|
|
err
|
|
|
|
)
|
|
|
|
return null
|
|
|
|
})
|
|
|
|
|
|
|
|
setEncryptionKey(decrypted)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-14 12:27:49 +00:00
|
|
|
// Temp. map to hold events and signers
|
2024-08-15 11:47:23 +00:00
|
|
|
const parsedSignatureEventsMap = new Map<
|
|
|
|
`npub1${string}`,
|
|
|
|
DocSignatureEvent
|
|
|
|
>()
|
2024-08-14 12:27:49 +00:00
|
|
|
const signerStatusMap = new Map<`npub1${string}`, SignStatus>()
|
|
|
|
|
|
|
|
const getPrevSignerSig = (npub: `npub1${string}`) => {
|
|
|
|
if (signers[0] === npub) {
|
|
|
|
return sig
|
|
|
|
}
|
|
|
|
|
|
|
|
// find the index of signer
|
|
|
|
const currentSignerIndex = signers.findIndex(
|
|
|
|
(signer) => signer === npub
|
|
|
|
)
|
|
|
|
// return if could not found user in signer's list
|
|
|
|
if (currentSignerIndex === -1) return
|
|
|
|
// find prev signer
|
|
|
|
const prevSigner = signers[currentSignerIndex - 1]
|
|
|
|
|
|
|
|
// get the signature of prev signer
|
|
|
|
return parsedSignatureEventsMap.get(prevSigner)?.sig
|
|
|
|
}
|
|
|
|
|
2024-08-12 12:26:03 +00:00
|
|
|
for (const npub in meta.docSignatures) {
|
|
|
|
try {
|
2024-08-13 15:28:14 +00:00
|
|
|
// Parse each signature event
|
|
|
|
const event = await parseNostrEvent(
|
2024-08-12 12:26:03 +00:00
|
|
|
meta.docSignatures[npub as `npub1${string}`]
|
|
|
|
)
|
2024-08-13 15:28:14 +00:00
|
|
|
|
|
|
|
// Save events to a map, to save all at once outside loop
|
|
|
|
// We need the object to find completedAt
|
|
|
|
// Avoided using parsedSignatureEvents due to useEffect deps
|
|
|
|
parsedSignatureEventsMap.set(npub as `npub1${string}`, event)
|
2024-08-12 12:26:03 +00:00
|
|
|
} catch (error) {
|
2024-08-14 12:27:49 +00:00
|
|
|
signerStatusMap.set(npub as `npub1${string}`, SignStatus.Invalid)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
parsedSignatureEventsMap.forEach((event, npub) => {
|
|
|
|
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)
|
2024-08-15 11:47:23 +00:00
|
|
|
parsedSignatureEventsMap.set(npub, {
|
|
|
|
...event,
|
|
|
|
parsedContent: obj
|
|
|
|
})
|
2024-08-14 12:27:49 +00:00
|
|
|
if (
|
|
|
|
obj.prevSig &&
|
|
|
|
prevSignersSig &&
|
|
|
|
obj.prevSig === prevSignersSig
|
|
|
|
) {
|
|
|
|
signerStatusMap.set(npub as `npub1${string}`, SignStatus.Signed)
|
2024-08-12 12:26:03 +00:00
|
|
|
}
|
2024-08-14 12:27:49 +00:00
|
|
|
} catch (error) {
|
|
|
|
signerStatusMap.set(npub as `npub1${string}`, SignStatus.Invalid)
|
|
|
|
}
|
2024-08-12 12:26:03 +00:00
|
|
|
}
|
2024-08-14 12:27:49 +00:00
|
|
|
})
|
|
|
|
|
2024-08-14 12:34:51 +00:00
|
|
|
signers
|
|
|
|
.filter((s) => !parsedSignatureEventsMap.has(s))
|
|
|
|
.forEach((s) => signerStatusMap.set(s, SignStatus.Pending))
|
|
|
|
|
|
|
|
// Get the first signer that hasn't signed
|
2024-08-14 12:27:49 +00:00
|
|
|
const nextSigner = signers.find((s) => !parsedSignatureEventsMap.has(s))
|
|
|
|
if (nextSigner) {
|
|
|
|
signerStatusMap.set(nextSigner, SignStatus.Awaiting)
|
2024-08-12 12:26:03 +00:00
|
|
|
}
|
2024-08-13 15:28:14 +00:00
|
|
|
|
2024-08-14 12:27:49 +00:00
|
|
|
setSignersStatus(Object.fromEntries(signerStatusMap))
|
|
|
|
setParsedSignatureEvents(Object.fromEntries(parsedSignatureEventsMap))
|
2024-08-13 15:28:14 +00:00
|
|
|
|
2024-08-12 12:26:03 +00:00
|
|
|
const signedBy = Object.keys(meta.docSignatures) as `npub1${string}`[]
|
|
|
|
const isCompletelySigned = signers.every((signer) =>
|
|
|
|
signedBy.includes(signer)
|
|
|
|
)
|
|
|
|
setSignedStatus(
|
|
|
|
isCompletelySigned ? SigitStatus.Complete : SigitStatus.Partial
|
|
|
|
)
|
2024-08-13 15:28:14 +00:00
|
|
|
|
2024-08-14 12:27:49 +00:00
|
|
|
// Check if all signers signed
|
2024-08-13 15:28:14 +00:00
|
|
|
if (isCompletelySigned) {
|
|
|
|
setCompletedAt(
|
|
|
|
fromUnixTimestamp(
|
|
|
|
signedBy.reduce((p, c) => {
|
|
|
|
return Math.max(
|
|
|
|
p,
|
|
|
|
parsedSignatureEventsMap.get(c)?.created_at || 0
|
|
|
|
)
|
|
|
|
}, 0)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
2024-08-12 12:26:03 +00:00
|
|
|
} catch (error) {
|
|
|
|
if (error instanceof SigitMetaParseError) {
|
|
|
|
toast.error(error.message)
|
|
|
|
}
|
|
|
|
console.error(error)
|
|
|
|
}
|
|
|
|
})()
|
2024-08-06 10:42:21 +00:00
|
|
|
}, [meta])
|
|
|
|
|
|
|
|
return {
|
2024-08-13 11:32:32 +00:00
|
|
|
modifiedAt: meta?.modifiedAt,
|
|
|
|
createSignature: meta?.createSignature,
|
|
|
|
docSignatures: meta?.docSignatures,
|
|
|
|
keys: meta?.keys,
|
2024-08-12 12:26:03 +00:00
|
|
|
isValid,
|
|
|
|
kind,
|
|
|
|
tags,
|
2024-08-13 11:32:32 +00:00
|
|
|
createdAt,
|
|
|
|
submittedBy,
|
2024-08-12 12:26:03 +00:00
|
|
|
id,
|
|
|
|
sig,
|
2024-08-06 10:42:21 +00:00
|
|
|
signers,
|
2024-08-12 12:26:03 +00:00
|
|
|
viewers,
|
|
|
|
fileHashes,
|
|
|
|
markConfig,
|
|
|
|
title,
|
|
|
|
zipUrl,
|
2024-08-13 15:28:14 +00:00
|
|
|
parsedSignatureEvents,
|
|
|
|
completedAt,
|
2024-08-06 10:42:21 +00:00
|
|
|
signedStatus,
|
2024-08-13 11:32:32 +00:00
|
|
|
signersStatus,
|
|
|
|
encryptionKey
|
2024-08-06 10:42:21 +00:00
|
|
|
}
|
|
|
|
}
|