import { CreateSignatureEventContent, Meta } from '../types' import { fromUnixTimestamp, 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?: `npub1${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 => { try { const createSignatureEvent = await parseJson(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 => { try { const createSignatureEventContent = await parseJson(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 = fromUnixTimestamp(createSignatureEvent.created_at) 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 as `npub1${string}` 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) } } }