diff --git a/src/hooks/useSigitMeta.tsx b/src/hooks/useSigitMeta.tsx index fea5154..088940e 100644 --- a/src/hooks/useSigitMeta.tsx +++ b/src/hooks/useSigitMeta.tsx @@ -11,7 +11,6 @@ import { hexToNpub, parseNostrEvent, parseCreateSignatureEventContent, - SigitMetaParseError, SigitStatus, SignStatus } from '../utils' @@ -21,6 +20,7 @@ import { Event } from 'nostr-tools' import store from '../store/store' import { AuthState } from '../store/auth/types' import { NostrController } from '../controllers' +import { MetaParseError } from '../types/errors/MetaParseError' /** * Flattened interface that combines properties `Meta`, `CreateSignatureEventContent`, @@ -247,7 +247,7 @@ export const useSigitMeta = (meta: Meta): FlatMeta => { ) } } catch (error) { - if (error instanceof SigitMetaParseError) { + if (error instanceof MetaParseError) { toast.error(error.message) } console.error(error) diff --git a/src/layouts/StickySideColumns.tsx b/src/layouts/StickySideColumns.tsx index 43dc430..d27ad4b 100644 --- a/src/layouts/StickySideColumns.tsx +++ b/src/layouts/StickySideColumns.tsx @@ -17,8 +17,10 @@ export const StickySideColumns = ({
{left}
-
- {children} +
+
+ {children} +
{right}
diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index 2bcd063..2c32f7d 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -514,6 +514,9 @@ export const CreatePage = () => { return ( file.pages?.flatMap((page, index) => { return page.drawnFields.map((drawnField) => { + if (!drawnField.counterpart) { + throw new Error('Missing counterpart') + } return { type: drawnField.type, location: { @@ -670,6 +673,7 @@ export const CreatePage = () => { } const generateCreateSignature = async ( + markConfig: Mark[], fileHashes: { [key: string]: string }, @@ -677,7 +681,6 @@ export const CreatePage = () => { ) => { const signers = users.filter((user) => user.role === UserRole.signer) const viewers = users.filter((user) => user.role === UserRole.viewer) - const markConfig = createMarks(fileHashes) const content: CreateSignatureEventContent = { signers: signers.map((signer) => hexToNpub(signer.pubkey)), @@ -721,131 +724,128 @@ export const CreatePage = () => { } const handleCreate = async () => { - if (!validateInputs()) return + try { + if (!validateInputs()) return - setIsLoading(true) - setLoadingSpinnerDesc('Generating file hashes') - const fileHashes = await generateFileHashes() - if (!fileHashes) { + setIsLoading(true) + setLoadingSpinnerDesc('Generating file hashes') + const fileHashes = await generateFileHashes() + if (!fileHashes) return + + setLoadingSpinnerDesc('Generating encryption key') + const encryptionKey = await generateEncryptionKey() + + if (await isOnline()) { + setLoadingSpinnerDesc('generating files.zip') + const arrayBuffer = await generateFilesZip() + if (!arrayBuffer) return + + setLoadingSpinnerDesc('Encrypting files.zip') + const encryptedArrayBuffer = await encryptZipFile( + arrayBuffer, + encryptionKey + ) + + const markConfig = createMarks(fileHashes) + + setLoadingSpinnerDesc('Uploading files.zip to file storage') + const fileUrl = await uploadFile(encryptedArrayBuffer) + if (!fileUrl) return + + setLoadingSpinnerDesc('Generating create signature') + const createSignature = await generateCreateSignature( + markConfig, + fileHashes, + fileUrl + ) + if (!createSignature) return + + setLoadingSpinnerDesc('Generating keys for decryption') + + // generate key pairs for decryption + const pubkeys = users.map((user) => user.pubkey) + // also add creator in the list + if (pubkeys.includes(usersPubkey!)) { + pubkeys.push(usersPubkey!) + } + + const keys = await generateKeys(pubkeys, encryptionKey) + if (!keys) return + + const meta: Meta = { + createSignature, + keys, + modifiedAt: unixNow(), + docSignatures: {} + } + + setLoadingSpinnerDesc('Updating user app data') + const event = await updateUsersAppData(meta) + if (!event) return + + setLoadingSpinnerDesc('Sending notifications to counterparties') + const promises = sendNotifications(meta) + + await Promise.all(promises) + .then(() => { + toast.success('Notifications sent successfully') + }) + .catch(() => { + toast.error('Failed to publish notifications') + }) + + navigate(appPrivateRoutes.sign, { state: { meta: meta } }) + } else { + const zip = new JSZip() + + selectedFiles.forEach((file) => { + zip.file(`files/${file.name}`, file) + }) + + const markConfig = createMarks(fileHashes) + + setLoadingSpinnerDesc('Generating create signature') + const createSignature = await generateCreateSignature( + markConfig, + fileHashes, + '' + ) + if (!createSignature) return + + const meta: Meta = { + createSignature, + modifiedAt: unixNow(), + docSignatures: {} + } + + // add meta to zip + try { + const stringifiedMeta = JSON.stringify(meta, null, 2) + zip.file('meta.json', stringifiedMeta) + } catch (err) { + console.error(err) + toast.error('An error occurred in converting meta json to string') + return null + } + + const arrayBuffer = await generateZipFile(zip) + if (!arrayBuffer) return + + setLoadingSpinnerDesc('Encrypting zip file') + const encryptedArrayBuffer = await encryptZipFile( + arrayBuffer, + encryptionKey + ) + + await handleOfflineFlow(encryptedArrayBuffer, encryptionKey) + } + } catch (error) { + if (error instanceof Error) { + toast.error(error.message) + } + console.error(error) + } finally { setIsLoading(false) - return - } - - setLoadingSpinnerDesc('Generating encryption key') - const encryptionKey = await generateEncryptionKey() - - if (await isOnline()) { - setLoadingSpinnerDesc('generating files.zip') - const arrayBuffer = await generateFilesZip() - if (!arrayBuffer) { - setIsLoading(false) - return - } - - setLoadingSpinnerDesc('Encrypting files.zip') - const encryptedArrayBuffer = await encryptZipFile( - arrayBuffer, - encryptionKey - ) - - setLoadingSpinnerDesc('Uploading files.zip to file storage') - const fileUrl = await uploadFile(encryptedArrayBuffer) - if (!fileUrl) { - setIsLoading(false) - return - } - - setLoadingSpinnerDesc('Generating create signature') - const createSignature = await generateCreateSignature(fileHashes, fileUrl) - if (!createSignature) { - setIsLoading(false) - return - } - - setLoadingSpinnerDesc('Generating keys for decryption') - - // generate key pairs for decryption - const pubkeys = users.map((user) => user.pubkey) - // also add creator in the list - if (pubkeys.includes(usersPubkey!)) { - pubkeys.push(usersPubkey!) - } - - const keys = await generateKeys(pubkeys, encryptionKey) - - if (!keys) { - setIsLoading(false) - return - } - const meta: Meta = { - createSignature, - keys, - modifiedAt: unixNow(), - docSignatures: {} - } - - setLoadingSpinnerDesc('Updating user app data') - const event = await updateUsersAppData(meta) - if (!event) { - setIsLoading(false) - return - } - - setLoadingSpinnerDesc('Sending notifications to counterparties') - const promises = sendNotifications(meta) - - await Promise.all(promises) - .then(() => { - toast.success('Notifications sent successfully') - }) - .catch(() => { - toast.error('Failed to publish notifications') - }) - - navigate(appPrivateRoutes.sign, { state: { meta: meta } }) - } else { - const zip = new JSZip() - - selectedFiles.forEach((file) => { - zip.file(`files/${file.name}`, file) - }) - - setLoadingSpinnerDesc('Generating create signature') - const createSignature = await generateCreateSignature(fileHashes, '') - if (!createSignature) { - setIsLoading(false) - return - } - - const meta: Meta = { - createSignature, - modifiedAt: unixNow(), - docSignatures: {} - } - - // add meta to zip - try { - const stringifiedMeta = JSON.stringify(meta, null, 2) - zip.file('meta.json', stringifiedMeta) - } catch (err) { - console.error(err) - toast.error('An error occurred in converting meta json to string') - return null - } - - const arrayBuffer = await generateZipFile(zip) - if (!arrayBuffer) { - setIsLoading(false) - return - } - - setLoadingSpinnerDesc('Encrypting zip file') - const encryptedArrayBuffer = await encryptZipFile( - arrayBuffer, - encryptionKey - ) - - await handleOfflineFlow(encryptedArrayBuffer, encryptionKey) } } @@ -893,7 +893,7 @@ export const CreatePage = () => {
    {selectedFiles.length > 0 && selectedFiles.map((file, index) => ( -
    { @@ -901,32 +901,32 @@ export const CreatePage = () => { setCurrentFile(file) }} > - <> - {file.name} - - -
    + {file.name} + + ))}
+
} right={ diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index 7ca88e6..8720954 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -542,10 +542,13 @@ export const SignPage = () => { setLoadingSpinnerDesc('Signing nostr event') const prevSig = getPrevSignersSig(hexToNpub(usersPubkey!)) - if (!prevSig) return + if (!prevSig) { + setIsLoading(false) + toast.error('Previous signature is invalid') + return + } - const marks = getSignerMarksForMeta() - if (!marks) return + const marks = getSignerMarksForMeta() || [] const signedEvent = await signEventForMeta({ prevSig, marks }) if (!signedEvent) return diff --git a/src/types/errors/MetaParseError.ts b/src/types/errors/MetaParseError.ts new file mode 100644 index 0000000..96adab5 --- /dev/null +++ b/src/types/errors/MetaParseError.ts @@ -0,0 +1,23 @@ +import { Jsonable } from '.' + +// Reuse common error messages for meta parsing +export enum MetaParseErrorType { + '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 class MetaParseError 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 + } +} diff --git a/src/types/errors/index.ts b/src/types/errors/index.ts new file mode 100644 index 0000000..6ef8990 --- /dev/null +++ b/src/types/errors/index.ts @@ -0,0 +1,29 @@ +export type Jsonable = + | string + | number + | boolean + | null + | undefined + | readonly Jsonable[] + | { readonly [key: string]: Jsonable } + | { toJSON(): Jsonable } + +/** + * Handle errors + * Wraps the errors without message property and stringify to a message so we can use it later + * @param error + * @returns + */ +export 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(`Wrapped Error: ${stringified}`) +} diff --git a/src/utils/mark.ts b/src/utils/mark.ts index effa2ba..44540c4 100644 --- a/src/utils/mark.ts +++ b/src/utils/mark.ts @@ -47,7 +47,7 @@ const filterMarksByPubkey = (marks: Mark[], pubkey: string): Mark[] => { /** * Takes Signed Doc Signatures part of Meta and extracts - * all Marks into one flar array, regardless of the user. + * all Marks into one flat array, regardless of the user. * @param meta */ const extractMarksFromSignedMeta = (meta: Meta): Mark[] => { diff --git a/src/utils/meta.ts b/src/utils/meta.ts index fd3481c..c915f66 100644 --- a/src/utils/meta.ts +++ b/src/utils/meta.ts @@ -3,6 +3,11 @@ import { fromUnixTimestamp, parseJson } from '.' import { Event, verifyEvent } from 'nostr-tools' import { toast } from 'react-toastify' import { extractFileExtensions } from './file' +import { handleError } from '../types/errors' +import { + MetaParseError, + MetaParseErrorType +} from '../types/errors/MetaParseError' export enum SignStatus { Signed = 'Signed', @@ -17,58 +22,6 @@ export enum SigitStatus { 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 @@ -89,7 +42,7 @@ export const parseNostrEvent = async (raw: string): Promise => { const event = await parseJson(raw) return event } catch (error) { - throw new SigitMetaParseError(SigitMetaParseErrorType.PARSE_ERROR_EVENT, { + throw new MetaParseError(MetaParseErrorType.PARSE_ERROR_EVENT, { cause: handleError(error), context: raw }) @@ -109,8 +62,8 @@ export const parseCreateSignatureEventContent = async ( await parseJson(raw) return createSignatureEventContent } catch (error) { - throw new SigitMetaParseError( - SigitMetaParseErrorType.PARSE_ERROR_SIGNATURE_EVENT_CONTENT, + throw new MetaParseError( + MetaParseErrorType.PARSE_ERROR_SIGNATURE_EVENT_CONTENT, { cause: handleError(error), context: raw @@ -165,7 +118,7 @@ export const extractSigitCardDisplayInfo = async (meta: Meta) => { return sigitInfo } catch (error) { - if (error instanceof SigitMetaParseError) { + if (error instanceof MetaParseError) { toast.error(error.message) console.error(error.name, error.message, error.cause, error.context) } else {