181 lines
4.7 KiB
TypeScript
181 lines
4.7 KiB
TypeScript
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_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 event parser that throws custom SigitMetaParseError with cause and context
|
|
* @param raw Raw string for parsing
|
|
* @returns parsed Event
|
|
*/
|
|
export const parseNostrEvent = async (raw: string): Promise<Event> => {
|
|
try {
|
|
const event = await parseJson<Event>(raw)
|
|
return event
|
|
} catch (error) {
|
|
throw new SigitMetaParseError(SigitMetaParseErrorType.PARSE_ERROR_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 parseNostrEvent(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 = extractFileExtensions(files)
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
export const extractFileExtensions = (fileNames: string[]) => {
|
|
const extensions = fileNames.reduce((result: string[], file: string) => {
|
|
const extension = file.split('.').pop()
|
|
if (extension) {
|
|
result.push(extension)
|
|
}
|
|
return result
|
|
}, [])
|
|
|
|
return extensions
|
|
}
|