feat(design): home page new design and functionality #135
@ -1,6 +1,6 @@
|
|||||||
import { Dispatch, SetStateAction, useEffect } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Meta, ProfileMetadata } from '../../types'
|
import { Meta, ProfileMetadata } from '../../types'
|
||||||
import { SigitInfo, SignedStatus } from '../../hooks/useSigitMeta'
|
import { SigitCardDisplayInfo, SigitStatus } from '../../utils'
|
||||||
import { Event, kinds } from 'nostr-tools'
|
import { Event, kinds } from 'nostr-tools'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { MetadataController } from '../../controllers'
|
import { MetadataController } from '../../controllers'
|
||||||
@ -25,17 +25,10 @@ import { getExtensionIconLabel } from '../getExtensionIconLabel'
|
|||||||
|
|
||||||
type SigitProps = {
|
type SigitProps = {
|
||||||
meta: Meta
|
meta: Meta
|
||||||
parsedMeta: SigitInfo
|
parsedMeta: SigitCardDisplayInfo
|
||||||
profiles: { [key: string]: ProfileMetadata }
|
|
||||||
setProfiles: Dispatch<SetStateAction<{ [key: string]: ProfileMetadata }>>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DisplaySigit = ({
|
export const DisplaySigit = ({ meta, parsedMeta }: SigitProps) => {
|
||||||
meta,
|
|
||||||
parsedMeta,
|
|
||||||
profiles,
|
|
||||||
setProfiles
|
|
||||||
}: SigitProps) => {
|
|
||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
createdAt,
|
createdAt,
|
||||||
@ -45,51 +38,67 @@ export const DisplaySigit = ({
|
|||||||
fileExtensions
|
fileExtensions
|
||||||
} = parsedMeta
|
} = parsedMeta
|
||||||
|
|
||||||
|
const [profiles, setProfiles] = useState<{ [key: string]: ProfileMetadata }>(
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const hexKeys: string[] = []
|
const hexKeys = new Set<string>([
|
||||||
|
...signers.map((signer) => npubToHex(signer)!)
|
||||||
|
])
|
||||||
|
|
||||||
if (submittedBy) {
|
if (submittedBy) {
|
||||||
hexKeys.push(npubToHex(submittedBy)!)
|
hexKeys.add(npubToHex(submittedBy)!)
|
||||||
}
|
}
|
||||||
hexKeys.push(...signers.map((signer) => npubToHex(signer)!))
|
|
||||||
|
|
||||||
const metadataController = new MetadataController()
|
const metadataController = new MetadataController()
|
||||||
hexKeys.forEach((key) => {
|
|
||||||
if (!(key in profiles)) {
|
const handleMetadataEvent = (key: string) => (event: Event) => {
|
||||||
const handleMetadataEvent = (event: Event) => {
|
|
||||||
const metadataContent =
|
const metadataContent =
|
||||||
metadataController.extractProfileMetadataContent(event)
|
metadataController.extractProfileMetadataContent(event)
|
||||||
|
|
||||||
if (metadataContent)
|
if (metadataContent) {
|
||||||
setProfiles((prev) => ({
|
setProfiles((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[key]: metadataContent
|
[key]: metadataContent
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
metadataController.on(key, (kind: number, event: Event) => {
|
|
||||||
if (kind === kinds.Metadata) {
|
|
||||||
handleMetadataEvent(event)
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
const handleEventListener =
|
||||||
|
(key: string) => (kind: number, event: Event) => {
|
||||||
|
if (kind === kinds.Metadata) {
|
||||||
|
handleMetadataEvent(key)(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hexKeys.forEach((key) => {
|
||||||
|
if (!(key in profiles)) {
|
||||||
|
metadataController.on(key, handleEventListener(key))
|
||||||
|
|
||||||
metadataController
|
metadataController
|
||||||
.findMetadata(key)
|
.findMetadata(key)
|
||||||
.then((metadataEvent) => {
|
.then((metadataEvent) => {
|
||||||
if (metadataEvent) handleMetadataEvent(metadataEvent)
|
if (metadataEvent) handleMetadataEvent(key)(metadataEvent)
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error(`error occurred in finding metadata for: ${key}`, err)
|
console.error(`error occurred in finding metadata for: ${key}`, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [submittedBy, signers, profiles, setProfiles])
|
|
||||||
|
return () => {
|
||||||
|
hexKeys.forEach((key) => {
|
||||||
|
metadataController.off(key, handleEventListener(key))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [submittedBy, signers, profiles])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.itemWrapper}>
|
<div className={styles.itemWrapper}>
|
||||||
<Link
|
<Link
|
||||||
to={
|
to={
|
||||||
signedStatus === SignedStatus.Complete
|
signedStatus === SigitStatus.Complete
|
||||||
? appPublicRoutes.verify
|
? appPublicRoutes.verify
|
||||||
: appPrivateRoutes.sign
|
: appPrivateRoutes.sign
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,8 @@ export const DisplaySigner = ({
|
|||||||
const [signStatus, setSignedStatus] = useState<SignStatus>()
|
const [signStatus, setSignedStatus] = useState<SignStatus>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!meta) return
|
||||||
|
|
||||||
const updateSignStatus = async () => {
|
const updateSignStatus = async () => {
|
||||||
const npub = hexToNpub(pubkey)
|
const npub = hexToNpub(pubkey)
|
||||||
if (npub in meta.docSignatures) {
|
if (npub in meta.docSignatures) {
|
||||||
|
@ -1,117 +1,147 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { toast } from 'react-toastify'
|
|
||||||
import { CreateSignatureEventContent, Meta } from '../types'
|
import { CreateSignatureEventContent, Meta } from '../types'
|
||||||
import { parseJson } from '../utils'
|
import { Mark } from '../types/mark'
|
||||||
|
import {
|
||||||
|
parseCreateSignatureEvent,
|
||||||
|
parseCreateSignatureEventContent,
|
||||||
|
SigitMetaParseError,
|
||||||
|
SigitStatus,
|
||||||
|
SignStatus
|
||||||
|
} from '../utils'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
import { verifyEvent } from 'nostr-tools'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
|
|
||||||
type npub = `npub1${string}`
|
interface FlatMeta extends Meta, CreateSignatureEventContent, Partial<Event> {
|
||||||
|
// Validated create signature event
|
||||||
|
isValid: boolean
|
||||||
|
|
||||||
export enum SignedStatus {
|
// Calculated status fields
|
||||||
Partial = 'In-Progress',
|
signedStatus: SigitStatus
|
||||||
Complete = 'Completed'
|
signersStatus: {
|
||||||
|
[signer: `npub1${string}`]: SignStatus
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SigitInfo {
|
/**
|
||||||
createdAt?: number
|
* Custom use hook for parsing the Sigit Meta
|
||||||
title?: string
|
* @param meta Sigit Meta
|
||||||
submittedBy?: string
|
* @returns flattened Meta object with calculated signed status
|
||||||
signers: npub[]
|
*/
|
||||||
fileExtensions: string[]
|
export const useSigitMeta = (meta: Meta): FlatMeta => {
|
||||||
signedStatus: SignedStatus
|
const [isValid, setIsValid] = useState(false)
|
||||||
}
|
const [kind, setKind] = useState<number>()
|
||||||
|
const [tags, setTags] = useState<string[][]>()
|
||||||
|
const [created_at, setCreatedAt] = useState<number>()
|
||||||
|
const [pubkey, setPubkey] = useState<string>() // submittedBy, pubkey from nostr event
|
||||||
|
const [id, setId] = useState<string>()
|
||||||
|
const [sig, setSig] = useState<string>()
|
||||||
|
|
||||||
export const extractSigitInfo = async (meta: Meta) => {
|
const [signers, setSigners] = useState<`npub1${string}`[]>([])
|
||||||
if (!meta?.createSignature) return
|
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>('')
|
||||||
|
|
||||||
const sigitInfo: SigitInfo = {
|
const [signedStatus, setSignedStatus] = useState<SigitStatus>(
|
||||||
signers: [],
|
SigitStatus.Partial
|
||||||
fileExtensions: [],
|
|
||||||
signedStatus: SignedStatus.Partial
|
|
||||||
}
|
|
||||||
|
|
||||||
const createSignatureEvent = await parseJson<Event>(
|
|
||||||
meta.createSignature
|
|
||||||
).catch((err) => {
|
|
||||||
console.log('err in parsing the createSignature event:>> ', err)
|
|
||||||
toast.error(
|
|
||||||
err.message || 'error occurred in parsing the create signature event'
|
|
||||||
)
|
)
|
||||||
return
|
const [signersStatus, setSignersStatus] = useState<{
|
||||||
})
|
[signer: `npub1${string}`]: SignStatus
|
||||||
|
}>({})
|
||||||
if (!createSignatureEvent) return
|
|
||||||
|
|
||||||
// created_at in nostr events are stored in seconds
|
|
||||||
sigitInfo.createdAt = createSignatureEvent.created_at * 1000
|
|
||||||
|
|
||||||
const createSignatureContent = await parseJson<CreateSignatureEventContent>(
|
|
||||||
createSignatureEvent.content
|
|
||||||
).catch((err) => {
|
|
||||||
console.log(`err in parsing the createSignature event's content :>> `, err)
|
|
||||||
return
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!createSignatureContent) return
|
|
||||||
|
|
||||||
const files = Object.keys(createSignatureContent.fileHashes)
|
|
||||||
const extensions = files.reduce((result: string[], file: string) => {
|
|
||||||
const extension = file.split('.').pop()
|
|
||||||
if (extension) {
|
|
||||||
result.push(extension)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const signedBy = Object.keys(meta.docSignatures) as npub[]
|
|
||||||
const isCompletelySigned = createSignatureContent.signers.every((signer) =>
|
|
||||||
signedBy.includes(signer)
|
|
||||||
)
|
|
||||||
|
|
||||||
sigitInfo.title = createSignatureContent.title
|
|
||||||
sigitInfo.submittedBy = createSignatureEvent.pubkey
|
|
||||||
sigitInfo.signers = createSignatureContent.signers
|
|
||||||
sigitInfo.fileExtensions = extensions
|
|
||||||
|
|
||||||
if (isCompletelySigned) {
|
|
||||||
sigitInfo.signedStatus = SignedStatus.Complete
|
|
||||||
}
|
|
||||||
|
|
||||||
return sigitInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useSigitMeta = (meta: Meta) => {
|
|
||||||
const [title, setTitle] = useState<string>()
|
|
||||||
const [createdAt, setCreatedAt] = useState<number>()
|
|
||||||
const [submittedBy, setSubmittedBy] = useState<string>()
|
|
||||||
const [signers, setSigners] = useState<npub[]>([])
|
|
||||||
const [signedStatus, setSignedStatus] = useState<SignedStatus>(
|
|
||||||
SignedStatus.Partial
|
|
||||||
)
|
|
||||||
const [fileExtensions, setFileExtensions] = useState<string[]>([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getSigitInfo = async () => {
|
if (!meta) return
|
||||||
const sigitInfo = await extractSigitInfo(meta)
|
;(async function () {
|
||||||
|
try {
|
||||||
|
const createSignatureEvent = await parseCreateSignatureEvent(
|
||||||
|
meta.createSignature
|
||||||
|
)
|
||||||
|
|
||||||
if (!sigitInfo) return
|
const { kind, tags, created_at, pubkey, id, sig, content } =
|
||||||
|
createSignatureEvent
|
||||||
|
|
||||||
setTitle(sigitInfo.title)
|
setIsValid(verifyEvent(createSignatureEvent))
|
||||||
setCreatedAt(sigitInfo.createdAt)
|
setKind(kind)
|
||||||
setSubmittedBy(sigitInfo.submittedBy)
|
setTags(tags)
|
||||||
setSigners(sigitInfo.signers)
|
// created_at in nostr events are stored in seconds
|
||||||
setSignedStatus(sigitInfo.signedStatus)
|
setCreatedAt(created_at * 1000)
|
||||||
|
|||||||
setFileExtensions(sigitInfo.fileExtensions)
|
setPubkey(pubkey)
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Parse each signature event and set signer status
|
||||||
|
for (const npub in meta.docSignatures) {
|
||||||
|
try {
|
||||||
|
const event = await parseCreateSignatureEvent(
|
||||||
|
meta.docSignatures[npub as `npub1${string}`]
|
||||||
|
)
|
||||||
|
const isValidSignature = verifyEvent(event)
|
||||||
|
setSignersStatus((prev) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
[npub]: isValidSignature
|
||||||
|
? SignStatus.Signed
|
||||||
|
: SignStatus.Invalid
|
||||||
}
|
}
|
||||||
|
})
|
||||||
getSigitInfo()
|
} catch (error) {
|
||||||
|
setSignersStatus((prev) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
[npub]: SignStatus.Invalid
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const signedBy = Object.keys(meta.docSignatures) as `npub1${string}`[]
|
||||||
|
const isCompletelySigned = signers.every((signer) =>
|
||||||
|
signedBy.includes(signer)
|
||||||
|
)
|
||||||
|
setSignedStatus(
|
||||||
|
isCompletelySigned ? SigitStatus.Complete : SigitStatus.Partial
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof SigitMetaParseError) {
|
||||||
|
toast.error(error.message)
|
||||||
|
}
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
})()
|
||||||
}, [meta])
|
}, [meta])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title,
|
modifiedAt: meta.modifiedAt,
|
||||||
createdAt,
|
createSignature: meta.createSignature,
|
||||||
submittedBy,
|
docSignatures: meta.docSignatures,
|
||||||
|
keys: meta.keys,
|
||||||
|
isValid,
|
||||||
|
kind,
|
||||||
|
tags,
|
||||||
|
created_at,
|
||||||
|
pubkey,
|
||||||
|
id,
|
||||||
|
sig,
|
||||||
signers,
|
signers,
|
||||||
|
viewers,
|
||||||
|
fileHashes,
|
||||||
|
markConfig,
|
||||||
|
title,
|
||||||
|
zipUrl,
|
||||||
signedStatus,
|
signedStatus,
|
||||||
fileExtensions
|
signersStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom'
|
|||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { useAppSelector } from '../../hooks'
|
import { useAppSelector } from '../../hooks'
|
||||||
import { appPrivateRoutes, appPublicRoutes } from '../../routes'
|
import { appPrivateRoutes, appPublicRoutes } from '../../routes'
|
||||||
import { Meta, ProfileMetadata } from '../../types'
|
import { Meta } from '../../types'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { Select } from '../../components/Select'
|
import { Select } from '../../components/Select'
|
||||||
@ -14,10 +14,10 @@ import { useDropzone } from 'react-dropzone'
|
|||||||
import { Container } from '../../components/Container'
|
import { Container } from '../../components/Container'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
import {
|
import {
|
||||||
extractSigitInfo,
|
extractSigitCardDisplayInfo,
|
||||||
SigitInfo,
|
SigitCardDisplayInfo,
|
||||||
SignedStatus
|
SigitStatus
|
||||||
} from '../../hooks/useSigitMeta'
|
} from '../../utils'
|
||||||
|
|
||||||
// Unsupported Filter options are commented
|
// Unsupported Filter options are commented
|
||||||
const FILTERS = [
|
const FILTERS = [
|
||||||
@ -52,30 +52,29 @@ export const HomePage = () => {
|
|||||||
|
|
||||||
const [sigits, setSigits] = useState<{ [key: string]: Meta }>({})
|
const [sigits, setSigits] = useState<{ [key: string]: Meta }>({})
|
||||||
const [parsedSigits, setParsedSigits] = useState<{
|
const [parsedSigits, setParsedSigits] = useState<{
|
||||||
[key: string]: SigitInfo
|
[key: string]: SigitCardDisplayInfo
|
||||||
}>({})
|
}>({})
|
||||||
const [profiles, setProfiles] = useState<{ [key: string]: ProfileMetadata }>(
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
const usersAppData = useAppSelector((state) => state.userAppData)
|
const usersAppData = useAppSelector((state) => state.userAppData)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (usersAppData) {
|
if (usersAppData) {
|
||||||
const getSigitInfo = async () => {
|
const getSigitInfo = async () => {
|
||||||
|
const parsedSigits: { [key: string]: SigitCardDisplayInfo } = {}
|
||||||
for (const key in usersAppData.sigits) {
|
for (const key in usersAppData.sigits) {
|
||||||
if (Object.prototype.hasOwnProperty.call(usersAppData.sigits, key)) {
|
if (Object.prototype.hasOwnProperty.call(usersAppData.sigits, key)) {
|
||||||
const sigitInfo = await extractSigitInfo(usersAppData.sigits[key])
|
const sigitInfo = await extractSigitCardDisplayInfo(
|
||||||
|
usersAppData.sigits[key]
|
||||||
|
)
|
||||||
if (sigitInfo) {
|
if (sigitInfo) {
|
||||||
setParsedSigits((prev) => {
|
parsedSigits[key] = sigitInfo
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
[key]: sigitInfo
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setParsedSigits({
|
||||||
|
...parsedSigits
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setSigits(usersAppData.sigits)
|
setSigits(usersAppData.sigits)
|
||||||
getSigitInfo()
|
getSigitInfo()
|
||||||
@ -240,9 +239,9 @@ export const HomePage = () => {
|
|||||||
const isMatch = title?.toLowerCase().includes(q.toLowerCase())
|
const isMatch = title?.toLowerCase().includes(q.toLowerCase())
|
||||||
switch (filter) {
|
switch (filter) {
|
||||||
case 'Completed':
|
case 'Completed':
|
||||||
return signedStatus === SignedStatus.Complete && isMatch
|
return signedStatus === SigitStatus.Complete && isMatch
|
||||||
case 'In-progress':
|
case 'In-progress':
|
||||||
return signedStatus === SignedStatus.Partial && isMatch
|
return signedStatus === SigitStatus.Partial && isMatch
|
||||||
case 'Show all':
|
case 'Show all':
|
||||||
return isMatch
|
return isMatch
|
||||||
default:
|
default:
|
||||||
@ -259,8 +258,6 @@ export const HomePage = () => {
|
|||||||
key={`sigit-${key}`}
|
key={`sigit-${key}`}
|
||||||
parsedMeta={parsedSigits[key]}
|
parsedMeta={parsedSigits[key]}
|
||||||
meta={sigits[key]}
|
meta={sigits[key]}
|
||||||
profiles={profiles}
|
|
||||||
setProfiles={setProfiles}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,3 +7,4 @@ export * from './string'
|
|||||||
export * from './zip'
|
export * from './zip'
|
||||||
export * from './utils'
|
export * from './utils'
|
||||||
export * from './mark'
|
export * from './mark'
|
||||||
|
export * from './meta'
|
||||||
|
181
src/utils/meta.ts
Normal file
181
src/utils/meta.ts
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
import { CreateSignatureEventContent, Meta } from '../types'
|
||||||
|
import { parseJson } from '.'
|
||||||
|
import { Event } from 'nostr-tools'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
|
export enum SignStatus {
|
||||||
|
Signed = 'Signed',
|
||||||
|
Pending = 'Pending',
|
||||||
|
Invalid = 'Invalid Sign'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SigitStatus {
|
||||||
|
Partial = 'In-Progress',
|
||||||
|
Complete = 'Completed'
|
||||||
|
}
|
||||||
|
|
||||||
|
type Jsonable =
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| null
|
||||||
|
| undefined
|
||||||
|
| readonly Jsonable[]
|
||||||
|
| { readonly [key: string]: Jsonable }
|
||||||
|
| { toJSON(): Jsonable }
|
||||||
|
|
||||||
|
export class SigitMetaParseError extends Error {
|
||||||
|
public readonly context?: Jsonable
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
message: string,
|
||||||
|
options: { cause?: Error; context?: Jsonable } = {}
|
||||||
|
) {
|
||||||
|
const { cause, context } = options
|
||||||
|
|
||||||
|
super(message, { cause })
|
||||||
|
this.name = this.constructor.name
|
||||||
|
|
||||||
|
this.context = context
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle meta errors
|
||||||
|
* Wraps the errors without message property and stringify to a message so we can use it later
|
||||||
|
* @param error
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function handleError(error: unknown): Error {
|
||||||
|
if (error instanceof Error) return error
|
||||||
|
|
||||||
|
// No message error, wrap it and stringify
|
||||||
|
let stringified = 'Unable to stringify the thrown value'
|
||||||
|
try {
|
||||||
|
stringified = JSON.stringify(error)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(stringified, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Error(`[SiGit Error]: ${stringified}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reuse common error messages for meta parsing
|
||||||
|
export enum SigitMetaParseErrorType {
|
||||||
|
'PARSE_ERROR_SIGNATURE_EVENT' = 'error occurred in parsing the create signature event',
|
||||||
|
'PARSE_ERROR_SIGNATURE_EVENT_CONTENT' = "err in parsing the createSignature event's content"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SigitCardDisplayInfo {
|
||||||
|
createdAt?: number
|
||||||
|
title?: string
|
||||||
|
submittedBy?: string
|
||||||
|
signers: `npub1${string}`[]
|
||||||
|
fileExtensions: string[]
|
||||||
|
signedStatus: SigitStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for createSignatureEvent parse that throws custom SigitMetaParseError with cause and context
|
||||||
|
* @param raw Raw string for parsing
|
||||||
|
* @returns parsed Event
|
||||||
|
*/
|
||||||
|
export const parseCreateSignatureEvent = async (
|
||||||
|
raw: string
|
||||||
|
): Promise<Event> => {
|
||||||
|
try {
|
||||||
|
const createSignatureEvent = await parseJson<Event>(raw)
|
||||||
|
return createSignatureEvent
|
||||||
|
} catch (error) {
|
||||||
|
throw new SigitMetaParseError(
|
||||||
|
SigitMetaParseErrorType.PARSE_ERROR_SIGNATURE_EVENT,
|
||||||
|
{
|
||||||
|
cause: handleError(error),
|
||||||
|
context: raw
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for event content parser that throws custom SigitMetaParseError with cause and context
|
||||||
|
* @param raw Raw string for parsing
|
||||||
|
* @returns parsed CreateSignatureEventContent
|
||||||
|
*/
|
||||||
|
export const parseCreateSignatureEventContent = async (
|
||||||
|
raw: string
|
||||||
|
): Promise<CreateSignatureEventContent> => {
|
||||||
|
try {
|
||||||
|
const createSignatureEventContent =
|
||||||
|
await parseJson<CreateSignatureEventContent>(raw)
|
||||||
|
return createSignatureEventContent
|
||||||
|
} catch (error) {
|
||||||
|
throw new SigitMetaParseError(
|
||||||
|
SigitMetaParseErrorType.PARSE_ERROR_SIGNATURE_EVENT_CONTENT,
|
||||||
|
{
|
||||||
|
cause: handleError(error),
|
||||||
|
context: raw
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts only necessary metadata for the card display
|
||||||
|
* @param meta Sigit metadata
|
||||||
|
* @returns SigitCardDisplayInfo
|
||||||
|
*/
|
||||||
|
export const extractSigitCardDisplayInfo = async (meta: Meta) => {
|
||||||
|
if (!meta?.createSignature) return
|
||||||
|
|
||||||
|
const sigitInfo: SigitCardDisplayInfo = {
|
||||||
|
signers: [],
|
||||||
|
fileExtensions: [],
|
||||||
|
signedStatus: SigitStatus.Partial
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const createSignatureEvent = await parseCreateSignatureEvent(
|
||||||
|
meta.createSignature
|
||||||
|
)
|
||||||
|
|
||||||
|
// created_at in nostr events are stored in seconds
|
||||||
|
sigitInfo.createdAt = createSignatureEvent.created_at * 1000
|
||||||
|
|
||||||
|
const createSignatureContent = await parseCreateSignatureEventContent(
|
||||||
|
createSignatureEvent.content
|
||||||
|
)
|
||||||
|
|
||||||
|
const files = Object.keys(createSignatureContent.fileHashes)
|
||||||
|
const extensions = files.reduce((result: string[], file: string) => {
|
||||||
|
const extension = file.split('.').pop()
|
||||||
|
if (extension) {
|
||||||
|
result.push(extension)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const signedBy = Object.keys(meta.docSignatures) as `npub1${string}`[]
|
||||||
|
const isCompletelySigned = createSignatureContent.signers.every((signer) =>
|
||||||
|
signedBy.includes(signer)
|
||||||
|
)
|
||||||
|
|
||||||
|
sigitInfo.title = createSignatureContent.title
|
||||||
|
sigitInfo.submittedBy = createSignatureEvent.pubkey
|
||||||
|
sigitInfo.signers = createSignatureContent.signers
|
||||||
|
sigitInfo.fileExtensions = extensions
|
||||||
|
|
||||||
|
if (isCompletelySigned) {
|
||||||
|
sigitInfo.signedStatus = SigitStatus.Complete
|
||||||
|
}
|
||||||
|
|
||||||
|
return sigitInfo
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof SigitMetaParseError) {
|
||||||
|
toast.error(error.message)
|
||||||
|
console.error(error.name, error.message, error.cause, error.context)
|
||||||
|
} else {
|
||||||
|
console.error('Unexpected error', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user
created_at * 1000
should be made into a utility function and used in other similar places (there is at least one that I could find).Similarly, there is a reverse action,
created_at / 1000
, which should also be a utility.