From 3a03e3c7e84aa74b02f355e4fcbe273ed06bcd47 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 27 Aug 2024 17:47:19 +0200 Subject: [PATCH 1/7] chore: comment typo --- src/utils/mark.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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[] => { From 624afae8514420a36034ef29814920579da308f6 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 27 Aug 2024 17:58:07 +0200 Subject: [PATCH 2/7] fix(create): throw on mark with no counterpart --- src/pages/create/index.tsx | 272 ++++++++++++++++++++----------------- 1 file changed, 148 insertions(+), 124 deletions(-) diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index 2bcd063..d5f5355 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,152 @@ 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) { + 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 + ) + + const markConfig = createMarks(fileHashes) + + 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( + markConfig, + 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) + }) + + const markConfig = createMarks(fileHashes) + + setLoadingSpinnerDesc('Generating create signature') + const createSignature = await generateCreateSignature( + markConfig, + 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) + } + } 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) } } From fa1811a330d510cffa87ce497a1abe2384359a69 Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 28 Aug 2024 09:29:05 +0200 Subject: [PATCH 3/7] refactor(error): refactor SigitMetaParseError --- src/hooks/useSigitMeta.tsx | 4 +-- src/types/errors/MetaParseError.ts | 17 ++++++++++ src/types/errors/index.ts | 29 ++++++++++++++++ src/utils/meta.ts | 54 +++--------------------------- 4 files changed, 53 insertions(+), 51 deletions(-) create mode 100644 src/types/errors/MetaParseError.ts create mode 100644 src/types/errors/index.ts 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/types/errors/MetaParseError.ts b/src/types/errors/MetaParseError.ts new file mode 100644 index 0000000..85cddec --- /dev/null +++ b/src/types/errors/MetaParseError.ts @@ -0,0 +1,17 @@ +import { Jsonable } from '.' + +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/meta.ts b/src/utils/meta.ts index fd3481c..62eb740 100644 --- a/src/utils/meta.ts +++ b/src/utils/meta.ts @@ -3,6 +3,8 @@ 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 } from '../types/errors/MetaParseError' export enum SignStatus { Signed = 'Signed', @@ -17,52 +19,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', @@ -89,7 +45,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(SigitMetaParseErrorType.PARSE_ERROR_EVENT, { cause: handleError(error), context: raw }) @@ -109,7 +65,7 @@ export const parseCreateSignatureEventContent = async ( await parseJson(raw) return createSignatureEventContent } catch (error) { - throw new SigitMetaParseError( + throw new MetaParseError( SigitMetaParseErrorType.PARSE_ERROR_SIGNATURE_EVENT_CONTENT, { cause: handleError(error), @@ -165,7 +121,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 { From 1c998ab99feb780f6183bcb7bd518eb3f2d77736 Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 28 Aug 2024 09:32:23 +0200 Subject: [PATCH 4/7] refactor(error): refactor MetaParseError 2 --- src/types/errors/MetaParseError.ts | 6 ++++++ src/utils/meta.ts | 15 ++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/types/errors/MetaParseError.ts b/src/types/errors/MetaParseError.ts index 85cddec..96adab5 100644 --- a/src/types/errors/MetaParseError.ts +++ b/src/types/errors/MetaParseError.ts @@ -1,5 +1,11 @@ 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 diff --git a/src/utils/meta.ts b/src/utils/meta.ts index 62eb740..c915f66 100644 --- a/src/utils/meta.ts +++ b/src/utils/meta.ts @@ -4,7 +4,10 @@ import { Event, verifyEvent } from 'nostr-tools' import { toast } from 'react-toastify' import { extractFileExtensions } from './file' import { handleError } from '../types/errors' -import { MetaParseError } from '../types/errors/MetaParseError' +import { + MetaParseError, + MetaParseErrorType +} from '../types/errors/MetaParseError' export enum SignStatus { Signed = 'Signed', @@ -19,12 +22,6 @@ export enum SigitStatus { Complete = 'Completed' } -// 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 @@ -45,7 +42,7 @@ export const parseNostrEvent = async (raw: string): Promise => { const event = await parseJson(raw) return event } catch (error) { - throw new MetaParseError(SigitMetaParseErrorType.PARSE_ERROR_EVENT, { + throw new MetaParseError(MetaParseErrorType.PARSE_ERROR_EVENT, { cause: handleError(error), context: raw }) @@ -66,7 +63,7 @@ export const parseCreateSignatureEventContent = async ( return createSignatureEventContent } catch (error) { throw new MetaParseError( - SigitMetaParseErrorType.PARSE_ERROR_SIGNATURE_EVENT_CONTENT, + MetaParseErrorType.PARSE_ERROR_SIGNATURE_EVENT_CONTENT, { cause: handleError(error), context: raw From a8020e6db2ad88f29e6cf2bb35cca1cace56cb07 Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 28 Aug 2024 09:45:15 +0200 Subject: [PATCH 5/7] fix(column-layout): wrap content column to prevent expanding --- src/layouts/StickySideColumns.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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}
From 20d1170f7dd41832b83b34656a9f95e239f074cf Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 28 Aug 2024 09:48:15 +0200 Subject: [PATCH 6/7] fix(sign): allow signing without marks, hide loading and show toast for prevSig error --- src/pages/sign/index.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 From e2b12afc721e7e809c5220e42c3db57d9ab86918 Mon Sep 17 00:00:00 2001 From: enes Date: Thu, 29 Aug 2024 12:54:51 +0200 Subject: [PATCH 7/7] refactor(review): handle loading state, browser warnings --- src/pages/create/index.tsx | 82 ++++++++++++++------------------------ 1 file changed, 29 insertions(+), 53 deletions(-) diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index d5f5355..2c32f7d 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -730,10 +730,7 @@ export const CreatePage = () => { setIsLoading(true) setLoadingSpinnerDesc('Generating file hashes') const fileHashes = await generateFileHashes() - if (!fileHashes) { - setIsLoading(false) - return - } + if (!fileHashes) return setLoadingSpinnerDesc('Generating encryption key') const encryptionKey = await generateEncryptionKey() @@ -741,10 +738,7 @@ export const CreatePage = () => { if (await isOnline()) { setLoadingSpinnerDesc('generating files.zip') const arrayBuffer = await generateFilesZip() - if (!arrayBuffer) { - setIsLoading(false) - return - } + if (!arrayBuffer) return setLoadingSpinnerDesc('Encrypting files.zip') const encryptedArrayBuffer = await encryptZipFile( @@ -756,10 +750,7 @@ export const CreatePage = () => { setLoadingSpinnerDesc('Uploading files.zip to file storage') const fileUrl = await uploadFile(encryptedArrayBuffer) - if (!fileUrl) { - setIsLoading(false) - return - } + if (!fileUrl) return setLoadingSpinnerDesc('Generating create signature') const createSignature = await generateCreateSignature( @@ -767,10 +758,7 @@ export const CreatePage = () => { fileHashes, fileUrl ) - if (!createSignature) { - setIsLoading(false) - return - } + if (!createSignature) return setLoadingSpinnerDesc('Generating keys for decryption') @@ -782,11 +770,8 @@ export const CreatePage = () => { } const keys = await generateKeys(pubkeys, encryptionKey) + if (!keys) return - if (!keys) { - setIsLoading(false) - return - } const meta: Meta = { createSignature, keys, @@ -796,10 +781,7 @@ export const CreatePage = () => { setLoadingSpinnerDesc('Updating user app data') const event = await updateUsersAppData(meta) - if (!event) { - setIsLoading(false) - return - } + if (!event) return setLoadingSpinnerDesc('Sending notifications to counterparties') const promises = sendNotifications(meta) @@ -828,10 +810,7 @@ export const CreatePage = () => { fileHashes, '' ) - if (!createSignature) { - setIsLoading(false) - return - } + if (!createSignature) return const meta: Meta = { createSignature, @@ -850,10 +829,7 @@ export const CreatePage = () => { } const arrayBuffer = await generateZipFile(zip) - if (!arrayBuffer) { - setIsLoading(false) - return - } + if (!arrayBuffer) return setLoadingSpinnerDesc('Encrypting zip file') const encryptedArrayBuffer = await encryptZipFile( @@ -917,7 +893,7 @@ export const CreatePage = () => {
    {selectedFiles.length > 0 && selectedFiles.map((file, index) => ( -
    { @@ -925,32 +901,32 @@ export const CreatePage = () => { setCurrentFile(file) }} > - <> - {file.name} - - -
    + {file.name} + + ))}
+
} right={