From 826ad0a2b21d52ce4a1729f3105ad2c1994c0a31 Mon Sep 17 00:00:00 2001 From: Svetoslav Borislavov Date: Mon, 16 Dec 2024 09:00:27 +0200 Subject: [PATCH 01/11] feat: add safeAwait util in the main process Signed-off-by: Svetoslav Borislavov --- front-end/src/main/utils/safeAwait.ts | 52 +++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 front-end/src/main/utils/safeAwait.ts diff --git a/front-end/src/main/utils/safeAwait.ts b/front-end/src/main/utils/safeAwait.ts new file mode 100644 index 000000000..4aea50df8 --- /dev/null +++ b/front-end/src/main/utils/safeAwait.ts @@ -0,0 +1,52 @@ +interface ISafeAwaitResultData { + data: T; + error?: never; +} +interface ISafeAwaitResultError { + data?: never; + error: unknown; +} +export type ISafeAwaitResult = ISafeAwaitResultData | ISafeAwaitResultError; +const nativeExceptions = [ + EvalError, + RangeError, + ReferenceError, + SyntaxError, + TypeError, + URIError, +].filter(except => typeof except === 'function'); +const throwNative = (error?: Error) => { + for (const Exception of nativeExceptions) { + if (error instanceof Exception) { + throw error; + } + } +}; +export const safeAwait = async (promise: Promise): Promise> => { + try { + const data = await promise; + if (data instanceof Error) { + throwNative(data); + return { error: data }; + } + return { data } as ISafeAwaitResultData; + } catch (error: unknown) { + throwNative(error as Error); + return { error }; + } +}; +export const safeAwaitAll = async ( + promises: { [K in keyof T]: Promise }, + finallyFunc?: () => Promise | void, +): Promise<{ [K in keyof T]: ISafeAwaitResult }> => { + try { + const results = await Promise.all(promises.map(p => safeAwait(p))); + return results as { [K in keyof T]: ISafeAwaitResult }; + } catch (error: unknown) { + return promises.map(() => ({ error }) as ISafeAwaitResultError) as { + [K in keyof T]: ISafeAwaitResult; + }; + } finally { + await finallyFunc?.(); + } +}; From 84876abcea9e7c51228741e70948e4ced5f2bc3e Mon Sep 17 00:00:00 2001 From: Svetoslav Borislavov Date: Mon, 16 Dec 2024 09:00:27 +0200 Subject: [PATCH 02/11] feat: update try catch statements in the main process Signed-off-by: Svetoslav Borislavov --- front-end/src/main/index.ts | 11 ++--- .../src/main/modules/ipcHandlers/utils.ts | 7 +-- .../main/services/localUser/dataMigration.ts | 47 ++++++++++--------- .../src/main/services/localUser/files.ts | 9 ++-- .../src/main/services/localUser/keyPairs.ts | 4 +- .../services/localUser/transactionDrafts.ts | 4 +- .../services/localUser/transactionGroups.ts | 8 ++-- .../main/services/localUser/transactions.ts | 26 +++++----- 8 files changed, 54 insertions(+), 62 deletions(-) diff --git a/front-end/src/main/index.ts b/front-end/src/main/index.ts index f88b1ee30..8f106544d 100644 --- a/front-end/src/main/index.ts +++ b/front-end/src/main/index.ts @@ -9,9 +9,10 @@ import createMenu from '@main/modules/menu'; import handleDeepLink, { PROTOCOL_NAME } from '@main/modules/deepLink'; import registerIpcListeners from '@main/modules/ipcHandlers'; -import { restoreOrCreateWindow } from '@main/windows/mainWindow'; +import { safeAwait } from '@main/utils/safeAwait'; +import { deleteAllTempFolders } from '@main/services/localUser'; -import { deleteAllTempFolders } from './services/localUser'; +import { restoreOrCreateWindow } from '@main/windows/mainWindow'; let mainWindow: BrowserWindow | null; @@ -66,11 +67,7 @@ function attachAppEvents() { e.preventDefault(); deleteRetires++; - try { - await deleteAllTempFolders(); - } catch { - /* Empty */ - } + await safeAwait(deleteAllTempFolders()); app.quit(); } diff --git a/front-end/src/main/modules/ipcHandlers/utils.ts b/front-end/src/main/modules/ipcHandlers/utils.ts index 965721256..7179130a9 100644 --- a/front-end/src/main/modules/ipcHandlers/utils.ts +++ b/front-end/src/main/modules/ipcHandlers/utils.ts @@ -53,12 +53,7 @@ export default () => { }); if (!filePath.trim() || canceled) return; - try { - await fs.writeFile(filePath, Uint8Array.from(content)); - } catch (error: any) { - dialog.showErrorBox('Failed to save file', error?.message || 'Unknown error'); - console.log(error); - } + await fs.writeFile(filePath, Uint8Array.from(content)); } catch (error: any) { dialog.showErrorBox('Failed to save file', error?.message || 'Unknown error'); } diff --git a/front-end/src/main/services/localUser/dataMigration.ts b/front-end/src/main/services/localUser/dataMigration.ts index 055078a69..1afd4f18a 100644 --- a/front-end/src/main/services/localUser/dataMigration.ts +++ b/front-end/src/main/services/localUser/dataMigration.ts @@ -17,6 +17,7 @@ import { } from '@main/shared/constants'; import { parseNetwork } from '@main/utils/parsers'; +import { safeAwait } from '@main/utils/safeAwait'; import { addAccount } from './accounts'; import { addClaim } from './claim'; @@ -237,14 +238,15 @@ export async function migrateUserData(userId: string): Promise { const prisma = getPrismaClient(); @@ -140,12 +141,8 @@ export const showContentInTemp = async (content: Buffer, fileId: string) => { }; export const deleteTempFolder = async (folder: string) => { - try { - const directoryPath = path.join(app.getPath('temp'), folder); - await deleteDirectory(directoryPath); - } catch { - /* Empty */ - } + const directoryPath = path.join(app.getPath('temp'), folder); + await safeAwait(deleteDirectory(directoryPath)); }; export const deleteAllTempFolders = async () => { diff --git a/front-end/src/main/services/localUser/keyPairs.ts b/front-end/src/main/services/localUser/keyPairs.ts index 9ddabcfa6..f715855cd 100644 --- a/front-end/src/main/services/localUser/keyPairs.ts +++ b/front-end/src/main/services/localUser/keyPairs.ts @@ -78,9 +78,9 @@ export const storeKeyPair = async ( await prisma.keyPair.create({ data: keyPair, }); - } catch (error: any) { + } catch (error: unknown) { console.log(error); - throw new Error(error.message || 'Failed to store key pair'); + throw new Error(error instanceof Error ? error.message : 'Failed to store key pair'); } }; diff --git a/front-end/src/main/services/localUser/transactionDrafts.ts b/front-end/src/main/services/localUser/transactionDrafts.ts index 1eb6828ba..e5f8bf10f 100644 --- a/front-end/src/main/services/localUser/transactionDrafts.ts +++ b/front-end/src/main/services/localUser/transactionDrafts.ts @@ -90,7 +90,7 @@ export const getDraftsCount = async (userId: string) => { }); return count; - } catch (error: any) { - throw new Error(error.message || 'Failed to get drafts count'); + } catch (error: unknown) { + throw new Error(error instanceof Error ? error.message : 'Failed to get drafts count'); } }; diff --git a/front-end/src/main/services/localUser/transactionGroups.ts b/front-end/src/main/services/localUser/transactionGroups.ts index c81179fa3..77a8b9f36 100644 --- a/front-end/src/main/services/localUser/transactionGroups.ts +++ b/front-end/src/main/services/localUser/transactionGroups.ts @@ -6,8 +6,8 @@ export const getGroups = async (findArgs: Prisma.TransactionGroupFindManyArgs) = try { return await prisma.transactionGroup.findMany(findArgs); - } catch (error: any) { - throw new Error(error.message || 'Failed to fetch transaction groups'); + } catch (error: unknown) { + throw new Error(error instanceof Error ? error.message : 'Failed to fetch transaction groups'); } }; @@ -109,8 +109,8 @@ export const getGroupsCount = async (userId: string) => { }); return count; - } catch (error: any) { - throw new Error(error.message || 'Failed to get drafts count'); + } catch (error: unknown) { + throw new Error(error instanceof Error ? error.message : 'Failed to get drafts count'); } }; diff --git a/front-end/src/main/services/localUser/transactions.ts b/front-end/src/main/services/localUser/transactions.ts index 96bd5c2a7..d2602ba0a 100644 --- a/front-end/src/main/services/localUser/transactions.ts +++ b/front-end/src/main/services/localUser/transactions.ts @@ -193,10 +193,10 @@ export const executeQuery = async ( } else { return response; } - } catch (error: any) { + } catch (error: unknown) { console.log(error); client._operator = null; - throw new Error(error.message); + throw new Error(error instanceof Error ? error.message : 'Failed to execute query'); } }; @@ -216,9 +216,9 @@ export const storeTransaction = async (transaction: Prisma.TransactionUncheckedC return await prisma.transaction.create({ data: transaction, }); - } catch (error: any) { + } catch (error: unknown) { console.log(error); - throw new Error(error.message || 'Failed to store transaction'); + throw new Error(error instanceof Error ? error.message : 'Failed to store transaction'); } }; @@ -234,8 +234,8 @@ export const getTransactions = async (findArgs: Prisma.TransactionFindManyArgs) }); return transactions; - } catch (error: any) { - throw new Error(error.message || 'Failed to fetch transactions'); + } catch (error: unknown) { + throw new Error(error instanceof Error ? error.message : 'Failed to fetch transactions'); } }; @@ -251,8 +251,8 @@ export const getTransactionsCount = async (userId: string) => { }); return count; - } catch (error: any) { - throw new Error(error.message || 'Failed to get transactions count'); + } catch (error: unknown) { + throw new Error(error instanceof Error ? error.message : 'Failed to get transactions count'); } }; @@ -272,8 +272,10 @@ export const getTransaction = async (id: string) => { transaction.body = Uint8Array.from(Buffer.from(transaction.body, 'hex')).toString(); return transaction; - } catch (error: any) { - throw new Error(error.message || `Failed to fetch transaction with id: ${id}`); + } catch (error: unknown) { + throw new Error( + error instanceof Error ? error.message : `Failed to fetch transaction with id: ${id}`, + ); } }; @@ -285,8 +287,8 @@ export const encodeSpecialFile = async (content: Uint8Array, fileId: string) => } else { throw new Error('File is not one of special files'); } - } catch (error: any) { + } catch (error: unknown) { console.log(error); - throw new Error(error.message || 'Failed to encode special file'); + throw new Error(error instanceof Error ? error.message : 'Failed to encode special file'); } }; From 7e124a3f0b89922756d267801418a32ee06bc085 Mon Sep 17 00:00:00 2001 From: Svetoslav Borislavov Date: Mon, 16 Dec 2024 09:00:27 +0200 Subject: [PATCH 03/11] refactor: error in catch statement type Signed-off-by: Svetoslav Borislavov --- .../components/Contacts/ContactDetails.vue | 2 +- .../components/ForgotPasswordModal.vue | 4 +-- .../ImportExternalPrivateKeyModal.vue | 4 +-- .../Organization/AddOrganizationModal.vue | 4 +-- .../BaseTransaction/BaseTransaction.vue | 11 ++++---- .../Create/FileContents/FileContents.vue | 6 ++-- .../Transaction/TransactionGroupProcessor.vue | 8 +++--- .../src/renderer/composables/useLoader.ts | 4 ++- .../AccountSetup/components/KeyPairs.vue | 8 +++--- .../AccountSetup/components/NewPassword.vue | 4 +-- .../LinkExistingAccount.vue | 2 +- .../CreateTransactionGroup.vue | 4 +-- .../LinkExistingFile/LinkExistingFile.vue | 2 +- .../OrganizationLogin/OrganizationLogin.vue | 4 +-- .../renderer/pages/RestoreKey/RestoreKey.vue | 8 +++--- .../RestoreMissingKeys/RestoreMissingKeys.vue | 4 +-- .../pages/Settings/components/KeysTab.vue | 6 ++-- .../pages/Settings/components/ProfileTab.vue | 4 +-- .../Settings/components/WorkGroupsTab.vue | 4 +-- .../TransactionDetails/TransactionDetails.vue | 2 +- .../renderer/services/organization/health.ts | 2 +- .../services/transactionGroupsService.ts | 28 +++++++++---------- front-end/src/renderer/utils/axios.ts | 2 +- front-end/src/renderer/utils/ipc.ts | 4 +-- .../utils/transactionSignatureModels/index.ts | 4 +-- .../src/renderer/utils/userStoreHelpers.ts | 4 +-- 26 files changed, 70 insertions(+), 69 deletions(-) diff --git a/front-end/src/renderer/components/Contacts/ContactDetails.vue b/front-end/src/renderer/components/Contacts/ContactDetails.vue index d2bd7f2ce..28d862a1f 100644 --- a/front-end/src/renderer/components/Contacts/ContactDetails.vue +++ b/front-end/src/renderer/components/Contacts/ContactDetails.vue @@ -101,7 +101,7 @@ const handleResend = async () => { await signUp(user.selectedOrganization.serverUrl, email); } toast.success('Email sent successfully'); - } catch (error: unknown) { + } catch (error) { toast.error(getErrorMessage(error, 'Error while sending email. Please try again.')); } }; diff --git a/front-end/src/renderer/components/ForgotPasswordModal.vue b/front-end/src/renderer/components/ForgotPasswordModal.vue index ab6d6e9bb..66cfa522a 100644 --- a/front-end/src/renderer/components/ForgotPasswordModal.vue +++ b/front-end/src/renderer/components/ForgotPasswordModal.vue @@ -78,7 +78,7 @@ async function handleEmailEnter() { shouldEnterToken.value = true; setTimeout(() => otpInputRef.value?.focus(), 100); - } catch (error: unknown) { + } catch (error) { toast.error(getErrorMessage(error, 'Failed to request password reset')); } } @@ -97,7 +97,7 @@ async function handleTokenEnter() { shouldEnterToken.value = false; shouldSetNewPassword.value = true; - } catch (error: unknown) { + } catch (error) { toast.error(getErrorMessage(error, 'Failed to verify OTP')); } } diff --git a/front-end/src/renderer/components/ImportExternalPrivateKeyModal.vue b/front-end/src/renderer/components/ImportExternalPrivateKeyModal.vue index e31568bb3..c65118efc 100644 --- a/front-end/src/renderer/components/ImportExternalPrivateKeyModal.vue +++ b/front-end/src/renderer/components/ImportExternalPrivateKeyModal.vue @@ -82,8 +82,8 @@ const handleImportExternalKey = async () => { emit('update:show', false); toast.success(`${props.keyType} private key imported successfully`); - } catch (err: unknown) { - toast.error(getErrorMessage(err, `Failed to import ${props.keyType} private key`)); + } catch (error) { + toast.error(getErrorMessage(error, `Failed to import ${props.keyType} private key`)); } }; diff --git a/front-end/src/renderer/components/Organization/AddOrganizationModal.vue b/front-end/src/renderer/components/Organization/AddOrganizationModal.vue index 9655c2862..fde4f4c15 100644 --- a/front-end/src/renderer/components/Organization/AddOrganizationModal.vue +++ b/front-end/src/renderer/components/Organization/AddOrganizationModal.vue @@ -56,8 +56,8 @@ const handleAdd = async (e: Event) => { toast.success('Organization Added'); emit('added', organization); emit('update:show', false); - } catch (err: unknown) { - toast.error(getErrorMessage(err, 'Failed to add organization')); + } catch (error) { + toast.error(getErrorMessage(error, 'Failed to add organization')); } }; diff --git a/front-end/src/renderer/components/Transaction/Create/BaseTransaction/BaseTransaction.vue b/front-end/src/renderer/components/Transaction/Create/BaseTransaction/BaseTransaction.vue index 17dc98bb3..ed503de50 100644 --- a/front-end/src/renderer/components/Transaction/Create/BaseTransaction/BaseTransaction.vue +++ b/front-end/src/renderer/components/Transaction/Create/BaseTransaction/BaseTransaction.vue @@ -17,6 +17,7 @@ import { useToast } from 'vue-toast-notification'; import useAccountId from '@renderer/composables/useAccountId'; import { + getErrorMessage, getTransactionType, isAccountId, redirectToDetails, @@ -34,7 +35,7 @@ import TransactionIdControls from '@renderer/components/Transaction/TransactionI import TransactionProcessor from '@renderer/components/Transaction/TransactionProcessor'; import BaseDraftLoad from '@renderer/components/Transaction/Create/BaseTransaction/BaseDraftLoad.vue'; import BaseGroupHandler from '@renderer/components/Transaction/Create/BaseTransaction/BaseGroupHandler.vue'; -import BaseApproversObserverData from './BaseApproversObserverData.vue'; +import BaseApproversObserverData from '@renderer/components/Transaction/Create/BaseTransaction/BaseApproversObserverData.vue'; /* Props */ const props = defineProps<{ @@ -168,11 +169,9 @@ function handleInputValidation(e: Event) { try { validate100CharInput(target.value, 'Transaction Memo'); memoError.value = false; - } catch (err) { - if (err instanceof Error) { - memoError.value = true; - toast.error(err.message); - } + } catch (error) { + toast.error(getErrorMessage(error, 'Invalid Transaction Memo')); + memoError.value = true; } } diff --git a/front-end/src/renderer/components/Transaction/Create/FileContents/FileContents.vue b/front-end/src/renderer/components/Transaction/Create/FileContents/FileContents.vue index c216b4a1d..65d617515 100644 --- a/front-end/src/renderer/components/Transaction/Create/FileContents/FileContents.vue +++ b/front-end/src/renderer/components/Transaction/Create/FileContents/FileContents.vue @@ -84,8 +84,8 @@ const readFile = async () => { ).join(','); await updateLocalFileInfo(contentBytes, privateKey, keyPair.type); - } catch (err: unknown) { - toast.error(getErrorMessage(err, 'Failed to execute query')); + } catch (error) { + toast.error(getErrorMessage(error, 'Failed to execute query')); } finally { network.client._operator = null; isLoading.value = false; @@ -150,7 +150,7 @@ const updateLocalFileInfo = async (content: string, privateKey: string, privateK toast.success(`File ${fileId.value} linked`); } - } catch (error: any) { + } catch (error) { toast.error(getErrorMessage(error, 'Failed to add/update file info')); } }; diff --git a/front-end/src/renderer/components/Transaction/TransactionGroupProcessor.vue b/front-end/src/renderer/components/Transaction/TransactionGroupProcessor.vue index ebbd0c657..0da959c01 100644 --- a/front-end/src/renderer/components/Transaction/TransactionGroupProcessor.vue +++ b/front-end/src/renderer/components/Transaction/TransactionGroupProcessor.vue @@ -148,8 +148,8 @@ async function signAfterConfirm() { await executeTransaction(signedTransactionBytes, groupItem); } - } catch (err: unknown) { - toast.error(getErrorMessage(err, 'Transaction signing failed')); + } catch (error) { + toast.error(getErrorMessage(error, 'Transaction signing failed')); } finally { isSigning.value = false; } @@ -224,8 +224,8 @@ async function executeTransaction(transactionBytes: Uint8Array, groupItem?: Grou if (unmounted.value) { toast.success('Transaction executed'); } - } catch (err: any) { - const data = JSON.parse(err.message); + } catch (error) { + const data = JSON.parse(getErrorMessage(error, 'Transaction execution failed')); status = data.status; toast.error(data.message); diff --git a/front-end/src/renderer/composables/useLoader.ts b/front-end/src/renderer/composables/useLoader.ts index 39ee310de..6a7094d57 100644 --- a/front-end/src/renderer/composables/useLoader.ts +++ b/front-end/src/renderer/composables/useLoader.ts @@ -6,6 +6,8 @@ import { useToast } from 'vue-toast-notification'; import { GLOBAL_MODAL_LOADER_KEY } from '@renderer/providers'; +import { getErrorMessage } from '@renderer/utils'; + export default function useLoader() { /* Composables */ const toast = useToast(); @@ -27,7 +29,7 @@ export default function useLoader() { globalModalLoaderRef?.value?.open(); return await Promise.race([fn(), timeoutPromise]); } catch (error) { - toast.error(error instanceof Error ? error.message : defaultErrorMessage); + toast.error(getErrorMessage(error, defaultErrorMessage)); } finally { globalModalLoaderRef?.value?.close(); } diff --git a/front-end/src/renderer/pages/AccountSetup/components/KeyPairs.vue b/front-end/src/renderer/pages/AccountSetup/components/KeyPairs.vue index c945e0cca..6165e0627 100644 --- a/front-end/src/renderer/pages/AccountSetup/components/KeyPairs.vue +++ b/front-end/src/renderer/pages/AccountSetup/components/KeyPairs.vue @@ -98,8 +98,8 @@ const addKeyToRestored = async (index: number, mnemonicHash: string, veirificati keys.value.push(key); } - } catch (e) { - toast.error(getErrorMessage(e, `Restoring key at index: ${index} failed`)); + } catch (error) { + toast.error(getErrorMessage(error, `Restoring key at index: ${index} failed`)); } }; @@ -211,8 +211,8 @@ const handleSave = async () => { user.secretHashes.push(key.mnemonicHash); } storedCount++; - } catch (e) { - toast.error(getErrorMessage(e, `Failed to store key pair: ${key.publicKey}`)); + } catch (error) { + toast.error(getErrorMessage(error, `Failed to store key pair: ${key.publicKey}`)); } } diff --git a/front-end/src/renderer/pages/AccountSetup/components/NewPassword.vue b/front-end/src/renderer/pages/AccountSetup/components/NewPassword.vue index ba14d1fff..ae9668138 100644 --- a/front-end/src/renderer/pages/AccountSetup/components/NewPassword.vue +++ b/front-end/src/renderer/pages/AccountSetup/components/NewPassword.vue @@ -90,8 +90,8 @@ const handleChangePassword = async () => { props.handleContinue(); toast.success('Password changed successfully'); - } catch (err: unknown) { - toast.error(getErrorMessage(err, 'Failed to change password')); + } catch (error) { + toast.error(getErrorMessage(error, 'Failed to change password')); } finally { isLoading.value = false; } diff --git a/front-end/src/renderer/pages/Accounts/LinkExistingAccount/LinkExistingAccount.vue b/front-end/src/renderer/pages/Accounts/LinkExistingAccount/LinkExistingAccount.vue index 10b50e0a0..ef387ccaf 100644 --- a/front-end/src/renderer/pages/Accounts/LinkExistingAccount/LinkExistingAccount.vue +++ b/front-end/src/renderer/pages/Accounts/LinkExistingAccount/LinkExistingAccount.vue @@ -49,7 +49,7 @@ const handleLinkAccount = async (e: Event) => { router.push({ name: 'accounts' }); toast.success('Account linked successfully!'); - } catch (error: unknown) { + } catch (error) { toast.error(getErrorMessage(error, 'Account link failed')); } } diff --git a/front-end/src/renderer/pages/CreateTransactionGroup/CreateTransactionGroup.vue b/front-end/src/renderer/pages/CreateTransactionGroup/CreateTransactionGroup.vue index edfffe25b..16cc9b705 100644 --- a/front-end/src/renderer/pages/CreateTransactionGroup/CreateTransactionGroup.vue +++ b/front-end/src/renderer/pages/CreateTransactionGroup/CreateTransactionGroup.vue @@ -170,8 +170,8 @@ async function handleSignSubmit() { const requiredKey = new KeyList(ownerKeys); await transactionGroupProcessor.value?.process(requiredKey); - } catch (err: unknown) { - toast.error(getErrorMessage(err, 'Failed to create transaction')); + } catch (error) { + toast.error(getErrorMessage(error, 'Failed to create transaction')); } } diff --git a/front-end/src/renderer/pages/Files/LinkExistingFile/LinkExistingFile.vue b/front-end/src/renderer/pages/Files/LinkExistingFile/LinkExistingFile.vue index e5c4a4bc9..2768d3c34 100644 --- a/front-end/src/renderer/pages/Files/LinkExistingFile/LinkExistingFile.vue +++ b/front-end/src/renderer/pages/Files/LinkExistingFile/LinkExistingFile.vue @@ -62,7 +62,7 @@ const handleLinkFile = async (e: Event) => { router.push({ name: 'files' }); toast.success('File linked successfully!'); - } catch (error: unknown) { + } catch (error) { toast.error(getErrorMessage(error, 'File link failed')); } } diff --git a/front-end/src/renderer/pages/OrganizationLogin/OrganizationLogin.vue b/front-end/src/renderer/pages/OrganizationLogin/OrganizationLogin.vue index de38edf8d..4cc3e9b76 100644 --- a/front-end/src/renderer/pages/OrganizationLogin/OrganizationLogin.vue +++ b/front-end/src/renderer/pages/OrganizationLogin/OrganizationLogin.vue @@ -79,10 +79,10 @@ const handleLogin = async () => { redirectIfRequiredKeysToMigrate, 'Failed to redirect to recovery phrase migration', ); - } catch (error: any) { + } catch (error) { + toast.error(getErrorMessage(error, 'Failed to sign in')); inputEmailInvalid.value = true; inputPasswordInvalid.value = true; - toast.error(getErrorMessage(error, 'Failed to sign in')); } finally { loading.value = false; } diff --git a/front-end/src/renderer/pages/RestoreKey/RestoreKey.vue b/front-end/src/renderer/pages/RestoreKey/RestoreKey.vue index c4ef62db1..55d3c457c 100644 --- a/front-end/src/renderer/pages/RestoreKey/RestoreKey.vue +++ b/front-end/src/renderer/pages/RestoreKey/RestoreKey.vue @@ -96,8 +96,8 @@ const handleRestoreKey = async () => { } step.value++; - } catch (e) { - toast.error(getErrorMessage(e, 'Failed to restore private key')); + } catch (error) { + toast.error(getErrorMessage(error, 'Failed to restore private key')); } finally { loadingText.value = null; } @@ -156,8 +156,8 @@ const handleSaveKey = async () => { toast.success('Key Pair saved'); router.push({ name: 'settingsKeys' }); - } catch (e) { - toast.error(getErrorMessage(e, 'Failed to store private key')); + } catch (error) { + toast.error(getErrorMessage(error, 'Failed to store private key')); } finally { loadingText.value = null; } diff --git a/front-end/src/renderer/pages/RestoreMissingKeys/RestoreMissingKeys.vue b/front-end/src/renderer/pages/RestoreMissingKeys/RestoreMissingKeys.vue index 8889969ef..445e3228e 100644 --- a/front-end/src/renderer/pages/RestoreMissingKeys/RestoreMissingKeys.vue +++ b/front-end/src/renderer/pages/RestoreMissingKeys/RestoreMissingKeys.vue @@ -100,8 +100,8 @@ const storeKeys = async ( false, ); restoredKeys++; - } catch (err) { - toast.error(getErrorMessage(err, 'Failed to store key pair')); + } catch (error) { + toast.error(getErrorMessage(error, 'Failed to store key pair')); } } diff --git a/front-end/src/renderer/pages/Settings/components/KeysTab.vue b/front-end/src/renderer/pages/Settings/components/KeysTab.vue index c10e204fd..4c292ee1a 100644 --- a/front-end/src/renderer/pages/Settings/components/KeysTab.vue +++ b/front-end/src/renderer/pages/Settings/components/KeysTab.vue @@ -232,7 +232,7 @@ const handleDelete = async () => { const organizationKeyToDelete = getUserKeyToDelete(keyPairId); await deleteKeyPair(keyPairId); await deleteOrganization(organizationKeyToDelete?.id || null); - } catch (error: unknown) { + } catch (error) { toast.error(getErrorMessage(error, 'Unable to delete one or more key pair(s)')); } } @@ -253,8 +253,8 @@ const handleDelete = async () => { if (user.shouldSetupAccount) { router.push({ name: 'accountSetup' }); } - } catch (err: unknown) { - toast.error(getErrorMessage(err, 'Failed to delete key pair')); + } catch (error) { + toast.error(getErrorMessage(error, 'Failed to delete key pair')); } finally { selectedKeyPairIdsToDelete.value = []; selectedMissingKeyPairIdsToDelete.value = []; diff --git a/front-end/src/renderer/pages/Settings/components/ProfileTab.vue b/front-end/src/renderer/pages/Settings/components/ProfileTab.vue index 3e38dd1b9..213cf082c 100644 --- a/front-end/src/renderer/pages/Settings/components/ProfileTab.vue +++ b/front-end/src/renderer/pages/Settings/components/ProfileTab.vue @@ -81,8 +81,8 @@ const handleChangePassword = async () => { isSuccessModalShown.value = true; await user.refetchAccounts(); - } catch (err: unknown) { - toast.error(getErrorMessage(err, 'Failed to change password')); + } catch (error) { + toast.error(getErrorMessage(error, 'Failed to change password')); } finally { isChangingPassword.value = false; } diff --git a/front-end/src/renderer/pages/Settings/components/WorkGroupsTab.vue b/front-end/src/renderer/pages/Settings/components/WorkGroupsTab.vue index ae16cdd10..c15454e3e 100644 --- a/front-end/src/renderer/pages/Settings/components/WorkGroupsTab.vue +++ b/front-end/src/renderer/pages/Settings/components/WorkGroupsTab.vue @@ -34,8 +34,8 @@ const handleAddOrganization = async (e: Event) => { }); toast.success('Organization added successfully'); - } catch (err: any) { - toast.error(getErrorMessage(err, 'Failed to add organization')); + } catch (error) { + toast.error(getErrorMessage(error, 'Failed to add organization')); } } }; diff --git a/front-end/src/renderer/pages/TransactionDetails/TransactionDetails.vue b/front-end/src/renderer/pages/TransactionDetails/TransactionDetails.vue index 2d6397836..ee3dd56f9 100644 --- a/front-end/src/renderer/pages/TransactionDetails/TransactionDetails.vue +++ b/front-end/src/renderer/pages/TransactionDetails/TransactionDetails.vue @@ -280,7 +280,7 @@ const handleSign = async () => { ); toast.success('Transaction signed successfully'); } - } catch (error: unknown) { + } catch (error) { toast.error(getErrorMessage(error, 'Failed to sign transaction')); } finally { isSigning.value = false; diff --git a/front-end/src/renderer/services/organization/health.ts b/front-end/src/renderer/services/organization/health.ts index 4dfed745e..1b0945633 100644 --- a/front-end/src/renderer/services/organization/health.ts +++ b/front-end/src/renderer/services/organization/health.ts @@ -6,7 +6,7 @@ export const healthCheck = async (serverUrl: string): Promise => { try { const { data } = await axios.get(`${serverUrl}${healtController}`); return data || false; - } catch (error: any) { + } catch (error) { console.log(error); return false; } diff --git a/front-end/src/renderer/services/transactionGroupsService.ts b/front-end/src/renderer/services/transactionGroupsService.ts index 846f0f289..d44b62f92 100644 --- a/front-end/src/renderer/services/transactionGroupsService.ts +++ b/front-end/src/renderer/services/transactionGroupsService.ts @@ -13,7 +13,7 @@ import { deleteDraft } from './transactionDraftsService'; export const getGroups = async (findArgs: Prisma.TransactionGroupFindManyArgs) => { try { return await window.electronAPI.local.transactionGroups.getGroups(findArgs); - } catch (error: any) { + } catch (error) { throw Error(getMessageFromIPCError(error, 'Failed to fetch transaction groups')); } }; @@ -21,7 +21,7 @@ export const getGroups = async (findArgs: Prisma.TransactionGroupFindManyArgs) = export const getGroup = async (id: string) => { try { return await window.electronAPI.local.transactionGroups.getGroup(id); - } catch (error: any) { + } catch (error) { throw Error(getMessageFromIPCError(error, `Failed to fetch transaction group with id: ${id}`)); } }; @@ -29,7 +29,7 @@ export const getGroup = async (id: string) => { export async function getGroupItems(id: string) { try { return await window.electronAPI.local.transactionGroups.getGroupItems(id); - } catch (error: any) { + } catch (error) { throw Error( getMessageFromIPCError(error, `Failed to fetch transaction group items with id: ${id}`), ); @@ -39,7 +39,7 @@ export async function getGroupItems(id: string) { export async function getGroupItem(id: string, seq: string) { try { return await window.electronAPI.local.transactionGroups.getGroupItem(id, seq); - } catch (error: any) { + } catch (error) { throw Error( getMessageFromIPCError(error, `Failed to fetch transaction group items with id: ${id}`), ); @@ -74,7 +74,7 @@ export const addGroupWithDrafts = async ( await window.electronAPI.local.transactionGroups.addGroupItem(groupItem); } return group.id; - } catch (error: any) { + } catch (error) { throw Error(getMessageFromIPCError(error, 'Failed to add transaction draft')); } }; @@ -91,7 +91,7 @@ export async function addGroupItem( }; try { await window.electronAPI.local.transactionGroups.addGroupItem(groupItemStruct); - } catch (error: any) { + } catch (error) { throw Error(getMessageFromIPCError(error, 'Failed to add groupItem')); } } @@ -105,7 +105,7 @@ export async function addGroup(description: string, atomic: boolean) { }; try { return await window.electronAPI.local.transactionGroups.addGroup(transactionGroup); - } catch (error: any) { + } catch (error) { throw Error(getMessageFromIPCError(error, 'Failed to add transaction draft')); } } @@ -169,7 +169,7 @@ export async function updateGroup( await window.electronAPI.local.transactionGroups.addGroupItem(groupItem); } } - } catch (error: any) { + } catch (error) { throw Error(getMessageFromIPCError(error, `Failed to fetch transaction group with id: ${id}`)); } } @@ -177,7 +177,7 @@ export async function updateGroup( export const deleteGroup = async (id: string) => { try { return await window.electronAPI.local.transactionGroups.deleteGroup(id); - } catch (error: any) { + } catch (error) { throw Error(getMessageFromIPCError(error, `Failed to delete transaction group with id: ${id}`)); } }; @@ -187,7 +187,7 @@ export const deleteGroup = async (id: string) => { // return await window.electronAPI.local.transactionDrafts.draftExists( // transactionBytes.toString(), // ); -// } catch (error: any) { +// } catch (error) { // throw Error(getMessageFromIPCError(error, `Failed to determine if transaction draft exist`)); // } // }; @@ -196,15 +196,15 @@ export const deleteGroup = async (id: string) => { export const getGroupsCount = async (userId: string) => { try { return await window.electronAPI.local.transactionGroups.getGroupsCount(userId); - } catch (err: any) { - throw Error(getMessageFromIPCError(err, 'Failed to get transactions count')); + } catch (error) { + throw Error(getMessageFromIPCError(error, 'Failed to get transactions count')); } }; export async function editGroupItem(groupItem: GroupItem) { try { return await window.electronAPI.local.transactionGroups.editGroupItem(groupItem); - } catch (err: any) { - throw Error(getMessageFromIPCError(err, 'Failed to update group item')); + } catch (error) { + throw Error(getMessageFromIPCError(error, 'Failed to update group item')); } } diff --git a/front-end/src/renderer/utils/axios.ts b/front-end/src/renderer/utils/axios.ts index a0cb09b53..c37a5d9e9 100644 --- a/front-end/src/renderer/utils/axios.ts +++ b/front-end/src/renderer/utils/axios.ts @@ -18,7 +18,7 @@ export const commonRequestHandler = async ( ) => { try { return await callback(); - } catch (error: any) { + } catch (error) { let message = defaultMessage; if (error instanceof AxiosError) { diff --git a/front-end/src/renderer/utils/ipc.ts b/front-end/src/renderer/utils/ipc.ts index 2b3469e0e..37972b2f9 100644 --- a/front-end/src/renderer/utils/ipc.ts +++ b/front-end/src/renderer/utils/ipc.ts @@ -6,7 +6,7 @@ export const getMessageFromIPCError = (err: any, msg: string) => { export const commonIPCHandler = async (callback: () => Promise, defaultMessage: string) => { try { return await callback(); - } catch (err: any) { - throw Error(getMessageFromIPCError(err, defaultMessage)); + } catch (error) { + throw Error(getMessageFromIPCError(error, defaultMessage)); } }; diff --git a/front-end/src/renderer/utils/transactionSignatureModels/index.ts b/front-end/src/renderer/utils/transactionSignatureModels/index.ts index 0b041d3b7..2ec614db8 100644 --- a/front-end/src/renderer/utils/transactionSignatureModels/index.ts +++ b/front-end/src/renderer/utils/transactionSignatureModels/index.ts @@ -34,8 +34,8 @@ export const getSignatureEntities = (transaction: Transaction) => { }; return result; - } catch (err) { - console.log(err); + } catch (error) { + console.log(error); return { accounts: [], receiverAccounts: [], diff --git a/front-end/src/renderer/utils/userStoreHelpers.ts b/front-end/src/renderer/utils/userStoreHelpers.ts index 76438ab10..d8cb07f00 100644 --- a/front-end/src/renderer/utils/userStoreHelpers.ts +++ b/front-end/src/renderer/utils/userStoreHelpers.ts @@ -708,9 +708,9 @@ export const restoreOrganizationKeys = async ( keys.push(key); } } - } catch (e) { + } catch (error) { failedRestoreMessages.push( - getErrorMessage(e, `Failed to restore key at index ${organizationKey.index}`), + getErrorMessage(error, `Failed to restore key at index ${organizationKey.index}`), ); } } From 7da3a79a7dd12bbce01e7ad97c19e6304d68a4db Mon Sep 17 00:00:00 2001 From: Svetoslav Borislavov Date: Mon, 16 Dec 2024 09:00:27 +0200 Subject: [PATCH 04/11] refactor: use safeAwait in SelectEncryptedKeysModal Signed-off-by: Svetoslav Borislavov --- .../components/SelectEncryptedKeysModal.vue | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/front-end/src/renderer/components/KeyPair/ImportEncrypted/components/SelectEncryptedKeysModal.vue b/front-end/src/renderer/components/KeyPair/ImportEncrypted/components/SelectEncryptedKeysModal.vue index a559f186a..442861e80 100644 --- a/front-end/src/renderer/components/KeyPair/ImportEncrypted/components/SelectEncryptedKeysModal.vue +++ b/front-end/src/renderer/components/KeyPair/ImportEncrypted/components/SelectEncryptedKeysModal.vue @@ -4,6 +4,8 @@ import { computed, ref, watch } from 'vue'; import { showOpenDialog } from '@renderer/services/electronUtilsService'; import { searchEncryptedKeys, abortFileSearch } from '@renderer/services/encryptedKeys'; +import { safeAwait } from '@renderer/utils'; + import AppButton from '@renderer/components/ui/AppButton.vue'; import AppModal from '@renderer/components/ui/AppModal.vue'; import AppCheckBox from '@renderer/components/ui/AppCheckBox.vue'; @@ -68,21 +70,21 @@ const handleSelect = async () => { foundKeyPaths.value = null; - try { - searching.value = true; + searching.value = true; - const encryptedKeyPaths = await searchEncryptedKeys(result.filePaths); + const { data } = await safeAwait(searchEncryptedKeys(result.filePaths)); + if (data) { if (searching.value) { - foundKeyPaths.value = encryptedKeyPaths; - selectedKeyPaths.value = encryptedKeyPaths; // Auto-select all items + foundKeyPaths.value = data; + selectedKeyPaths.value = data; // Auto-select all items } else { foundKeyPaths.value = null; selectedKeyPaths.value = null; } - } finally { - searching.value = false; } + + searching.value = false; }; const handleCheckboxChecked = (path: string, checked: boolean) => { From b00510a699b33aad63e336ec079b704ac3e27738 Mon Sep 17 00:00:00 2001 From: Svetoslav Borislavov Date: Mon, 16 Dec 2024 09:00:27 +0200 Subject: [PATCH 05/11] refactor: use safeAwait in NodeFormData Signed-off-by: Svetoslav Borislavov --- .../Create/NodeCreate/NodeFormData.vue | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/front-end/src/renderer/components/Transaction/Create/NodeCreate/NodeFormData.vue b/front-end/src/renderer/components/Transaction/Create/NodeCreate/NodeFormData.vue index e292256d9..0a5d0fd46 100644 --- a/front-end/src/renderer/components/Transaction/Create/NodeCreate/NodeFormData.vue +++ b/front-end/src/renderer/components/Transaction/Create/NodeCreate/NodeFormData.vue @@ -3,14 +3,23 @@ import type { NodeData } from '@renderer/utils/sdk'; import { ref, useTemplateRef, watch } from 'vue'; -import { formatAccountId, hexToUint8Array, uint8ToHex, validate100CharInput } from '@renderer/utils'; +import { useToast } from 'vue-toast-notification'; + import { sha384, x509BytesFromPem } from '@renderer/services/electronUtilsService'; +import { + formatAccountId, + getErrorMessage, + hexToUint8Array, + safeAwait, + uint8ToHex, + validate100CharInput, +} from '@renderer/utils'; + import AppInput from '@renderer/components/ui/AppInput.vue'; import AppButton from '@renderer/components/ui/AppButton.vue'; import AppTextArea from '@renderer/components/ui/AppTextArea.vue'; import KeyField from '@renderer/components/KeyField.vue'; -import { useToast } from 'vue-toast-notification'; /* Props */ const props = defineProps<{ @@ -88,18 +97,19 @@ async function handleUpdateGossipCert(str: string) { let gossipCaCertificate = Uint8Array.from([]); publicKeyHash.value = ''; - try { - if (str.trim().length !== 0) { - const { raw, hash } = await x509BytesFromPem(str); - gossipCaCertificate = raw; - publicKeyHash.value = hash; + if (str.trim().length !== 0) { + const { data } = await safeAwait(x509BytesFromPem(str)); + + if (data) { + gossipCaCertificate = data.raw; + publicKeyHash.value = data.hash; } - } finally { - emit('update:data', { - ...props.data, - gossipCaCertificate, - }); } + + emit('update:data', { + ...props.data, + gossipCaCertificate, + }); } async function handleUpdateGrpcCert(str: string) { @@ -150,11 +160,9 @@ function handleInputValidation(e: Event) { try { validate100CharInput(target.value, 'Transaction Memo'); nodeDescriptionError.value = false; - } catch (err) { - if (err instanceof Error) { - nodeDescriptionError.value = true; - toast.error(err.message); - } + } catch (error) { + toast.error(getErrorMessage(error, 'Invalid Node Description')); + nodeDescriptionError.value = true; } } From 47ee21b497e6b7cb95a497eeadc34247d477607f Mon Sep 17 00:00:00 2001 From: Svetoslav Borislavov Date: Mon, 16 Dec 2024 09:00:27 +0200 Subject: [PATCH 06/11] refactor: use safeAwait in DeleteAccountDetails Signed-off-by: Svetoslav Borislavov --- .../Details/DeleteAccountDetails.vue | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/front-end/src/renderer/components/Transaction/Details/DeleteAccountDetails.vue b/front-end/src/renderer/components/Transaction/Details/DeleteAccountDetails.vue index e0f6ddaa2..e52313259 100644 --- a/front-end/src/renderer/components/Transaction/Details/DeleteAccountDetails.vue +++ b/front-end/src/renderer/components/Transaction/Details/DeleteAccountDetails.vue @@ -2,7 +2,6 @@ import type { ITransactionFull } from '@main/shared/interfaces'; import { onBeforeMount, onBeforeUnmount, ref, watch } from 'vue'; - import { Transaction, AccountDeleteTransaction, Hbar, HbarUnit } from '@hashgraph/sdk'; import { TransactionStatus } from '@main/shared/interfaces'; @@ -11,7 +10,7 @@ import useNetworkStore from '@renderer/stores/storeNetwork'; import { getTransactionInfo } from '@renderer/services/mirrorNodeDataService'; -import { stringifyHbar } from '@renderer/utils'; +import { safeAwait, stringifyHbar } from '@renderer/utils'; /* Props */ const props = defineProps<{ @@ -28,23 +27,23 @@ const transferredAmount = ref(new Hbar(0)); /* Functions */ async function fetchTransactionInfo(payer: string, seconds: string, nanos: string) { - try { - const { transactions } = await getTransactionInfo( + const { data } = await safeAwait( + getTransactionInfo( `${payer}-${seconds}-${nanos}`, network.mirrorNodeBaseURL, controller.value || undefined, - ); + ), + ); - if (transactions.length > 0 && props.transaction instanceof AccountDeleteTransaction) { + if (data) { + if (data.transactions.length > 0 && props.transaction instanceof AccountDeleteTransaction) { const deletedAccountId = props.transaction.accountId?.toString(); const amount = - transactions[0].transfers?.find( + data.transactions[0].transfers?.find( transfer => transfer.account?.toString() === deletedAccountId, )?.amount || 0; transferredAmount.value = Hbar.from(Math.abs(amount), HbarUnit.Tinybar); } - } catch { - /* Ignore if transaction not available in mirror node */ } } From b54b3ecc5dc50ece73c1e6f93d803e55046a0cfe Mon Sep 17 00:00:00 2001 From: Svetoslav Borislavov Date: Mon, 16 Dec 2024 09:00:27 +0200 Subject: [PATCH 07/11] refactor: minor change in ContactList Signed-off-by: Svetoslav Borislavov --- .../pages/ContactList/ContactList.vue | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/front-end/src/renderer/pages/ContactList/ContactList.vue b/front-end/src/renderer/pages/ContactList/ContactList.vue index 25f14f13a..c209f427e 100644 --- a/front-end/src/renderer/pages/ContactList/ContactList.vue +++ b/front-end/src/renderer/pages/ContactList/ContactList.vue @@ -62,6 +62,15 @@ function handleSelectContact(id: number) { selectedId.value = id; } +const handleFetchContacts = async () => { + try { + fetching.value = true; + await contacts.fetch(); + } finally { + fetching.value = false; + } +}; + async function handleRemove() { if (!isUserLoggedIn(user.personal)) throw new Error('User is not logged in'); if (!contact.value) throw new Error('Contact is not selected'); @@ -75,22 +84,12 @@ async function handleRemove() { selectedId.value = null; - try { - fetching.value = true; - await contacts.fetch(); - } finally { - fetching.value = false; - } + await handleFetchContacts(); } /* Hooks */ onBeforeMount(async () => { - try { - fetching.value = true; - await contacts.fetch(); - } finally { - fetching.value = false; - } + await handleFetchContacts(); if (isUserLoggedIn(user.personal)) { linkedAccounts.value = await getAll({ From 466c48f21c7f9fd652ab72d606538219829d0f23 Mon Sep 17 00:00:00 2001 From: Svetoslav Borislavov Date: Mon, 16 Dec 2024 09:00:27 +0200 Subject: [PATCH 08/11] refactor: use safeAwait in MigrateRecoveryPhraseHash Signed-off-by: Svetoslav Borislavov --- .../MigrateRecoveryPhraseHash.vue | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/front-end/src/renderer/pages/MigrateRecoveryPhraseHash/MigrateRecoveryPhraseHash.vue b/front-end/src/renderer/pages/MigrateRecoveryPhraseHash/MigrateRecoveryPhraseHash.vue index be41114a5..fb152d9f9 100644 --- a/front-end/src/renderer/pages/MigrateRecoveryPhraseHash/MigrateRecoveryPhraseHash.vue +++ b/front-end/src/renderer/pages/MigrateRecoveryPhraseHash/MigrateRecoveryPhraseHash.vue @@ -70,14 +70,15 @@ const handleContinue = async () => { return; } - try { - loadingText.value = 'Updating recovery phrase hash...'; - await updateKeyPairsHash(keysToUpdate.value, user.recoveryPhrase.hash); + loadingText.value = 'Updating recovery phrase hash...'; + const { error } = await safeAwait( + updateKeyPairsHash(keysToUpdate.value, user.recoveryPhrase.hash), + ); + if (!error) { toast.success('Recovery phrase hash updated successfully'); await router.push({ name: 'transactions' }); - } finally { - loadingText.value = null; } + loadingText.value = null; }; const handleOpenResetModal = () => (isResetDataModalShown.value = true); From 03191ea4d71478eb4523888809443b11e8c12f80 Mon Sep 17 00:00:00 2001 From: Svetoslav Borislavov Date: Mon, 16 Dec 2024 09:00:27 +0200 Subject: [PATCH 09/11] refacot: error in catch type in UserLogin Signed-off-by: Svetoslav Borislavov --- front-end/src/renderer/pages/UserLogin/UserLogin.vue | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/front-end/src/renderer/pages/UserLogin/UserLogin.vue b/front-end/src/renderer/pages/UserLogin/UserLogin.vue index ad465fbc2..48a81190e 100644 --- a/front-end/src/renderer/pages/UserLogin/UserLogin.vue +++ b/front-end/src/renderer/pages/UserLogin/UserLogin.vue @@ -15,7 +15,7 @@ import { loginLocal, registerLocal, getUsersCount } from '@renderer/services/use import { GLOBAL_MODAL_LOADER_KEY } from '@renderer/providers'; -import { isEmail, isUserLoggedIn } from '@renderer/utils'; +import { getErrorMessage, isEmail, isUserLoggedIn } from '@renderer/utils'; import AppButton from '@renderer/components/ui/AppButton.vue'; import AppCheckBox from '@renderer/components/ui/AppCheckBox.vue'; @@ -98,14 +98,15 @@ const handleOnFormSubmit = async (event: Event) => { keepLoggedIn.value, false, ); - } catch (error: any) { + } catch (error) { inputEmailInvalid.value = false; inputPasswordInvalid.value = false; - if (error.message.includes('email')) { + const message = getErrorMessage(error, 'Invalid email or password'); + if (message.includes('email')) { inputEmailInvalid.value = true; } - if (error.message.includes('password')) { + if (message.includes('password')) { inputPasswordInvalid.value = true; } } finally { From 331fab5cdc29f313fe01ff0a7c6b86cb38dc1cdb Mon Sep 17 00:00:00 2001 From: Svetoslav Borislavov Date: Mon, 16 Dec 2024 09:00:27 +0200 Subject: [PATCH 10/11] fix: main process unit tests Signed-off-by: Svetoslav Borislavov --- .../main/services/localUser/dataMigration.ts | 3 +- .../services/localUser/dataMigration.spec.ts | 64 ++++++++++++++++- .../main/services/localUser/files.spec.ts | 8 ++- .../localUser/transactionDrafts.spec.ts | 8 ++- .../services/localUser/transactions.spec.ts | 25 +++++-- .../src/tests/main/utils/safeAwait.spec.ts | 70 +++++++++++++++++++ 6 files changed, 164 insertions(+), 14 deletions(-) create mode 100644 front-end/src/tests/main/utils/safeAwait.spec.ts diff --git a/front-end/src/main/services/localUser/dataMigration.ts b/front-end/src/main/services/localUser/dataMigration.ts index 1afd4f18a..879569de0 100644 --- a/front-end/src/main/services/localUser/dataMigration.ts +++ b/front-end/src/main/services/localUser/dataMigration.ts @@ -283,9 +283,10 @@ export async function migrateUserData(userId: string): Promise ({ })); vi.mock('@main/services/localUser/accounts'); vi.mock('@main/services/localUser/claim'); +vi.mock('@main/utils/safeAwait'); describe('Data Migration', () => { beforeEach(() => { @@ -275,6 +277,11 @@ describe('Data Migration', () => { vi.mocked(fs.promises.readFile).mockResolvedValueOnce(mockContent); mockAccounts(); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + const result = await migrateUserData(mockUserId); expect(result).toEqual({ accountsImported: 1, @@ -301,6 +308,11 @@ describe('Data Migration', () => { const mockContent = 'credentials={"mockDir/InputFiles": "svet"}'; vi.mocked(fs.promises.readFile).mockResolvedValueOnce(mockContent); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + await migrateUserData(mockUserId); expect(addClaim).toHaveBeenCalledWith(mockUserId, UPDATE_LOCATION, 'mockDir/InputFiles'); @@ -324,7 +336,17 @@ describe('Data Migration', () => { vi.mocked(fs.promises.readFile).mockResolvedValueOnce(mockContent); mockAccounts(); - vi.mocked(addClaim).mockRejectedValue(new Error('Claim error')); + + vi.mocked(safeAwait).mockResolvedValueOnce({ + data: undefined, + error: new Error('Claim error'), + }); + vi.mocked(safeAwait).mockResolvedValueOnce({ + data: undefined, + error: new Error('failed to add claim'), + }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); const result = await migrateUserData(mockUserId); expect(result).toEqual({ @@ -351,7 +373,12 @@ describe('Data Migration', () => { vi.mocked(fs.promises.readFile).mockResolvedValueOnce(mockContent); mockAccounts(); - vi.mocked(addAccount).mockRejectedValue(new Error('Account error')); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ + data: undefined, + error: new Error('Add account error'), + }); const result = await migrateUserData(mockUserId); expect(result).toEqual({ @@ -379,6 +406,11 @@ describe('Data Migration', () => { vi.mocked(fs.promises.readFile).mockResolvedValueOnce(mockContent); mockAccounts(); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + const result = await migrateUserData(mockUserId); expect(result).toEqual({ accountsImported: 1, @@ -401,6 +433,11 @@ describe('Data Migration', () => { vi.mocked(fs.promises.readFile).mockResolvedValueOnce(mockContent); mockAccounts(); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + const result = await migrateUserData(mockUserId); expect(result).toEqual({ accountsImported: 1, @@ -428,6 +465,11 @@ describe('Data Migration', () => { vi.mocked(fs.promises.readdir).mockResolvedValue([]); vi.mocked(fs.promises.readFile).mockResolvedValueOnce(mockContent); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + const result = await migrateUserData(mockUserId); expect(result).toEqual({ accountsImported: 0, @@ -449,6 +491,11 @@ describe('Data Migration', () => { vi.mocked(fs.promises.readFile).mockResolvedValue(mockContent); vi.mocked(fs.promises.readdir).mockRejectedValue(new Error('Read error')); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + const result = await migrateUserData(mockUserId); expect(result).toEqual({ accountsImported: 0, @@ -471,6 +518,11 @@ describe('Data Migration', () => { vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.promises.readdir).mockRejectedValueOnce(new Error('Read dir error')); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + const result = await migrateUserData(mockUserId); expect(result).toEqual({ accountsImported: 0, @@ -489,7 +541,13 @@ describe('Data Migration', () => { const mockUserId = 'userId'; const mockContent = 'credentials={"mockDir": "svet"}'; vi.mocked(fs.promises.readFile).mockResolvedValueOnce(mockContent); - vi.mocked(addClaim).mockImplementationOnce(vi.fn()); + + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: undefined }); + vi.mocked(safeAwait).mockResolvedValueOnce({ + data: undefined, + error: new Error('Claim error'), + }); + vi.mocked(addClaim).mockRejectedValueOnce(new Error('Claim error')); const result = await migrateUserData(mockUserId); diff --git a/front-end/src/tests/main/services/localUser/files.spec.ts b/front-end/src/tests/main/services/localUser/files.spec.ts index 8e5c67e0b..231c5b2e0 100644 --- a/front-end/src/tests/main/services/localUser/files.spec.ts +++ b/front-end/src/tests/main/services/localUser/files.spec.ts @@ -18,6 +18,7 @@ import { import { CommonNetwork } from '@main/shared/enums'; import { saveContentToPath, getNumberArrayFromString, deleteDirectory } from '@main/utils'; +import { safeAwait } from '@main/utils/safeAwait'; import { app, shell } from 'electron'; @@ -27,6 +28,7 @@ vi.mock('@main/utils', () => ({ getNumberArrayFromString: vi.fn(), deleteDirectory: vi.fn(), })); +vi.mock('@main/utils/safeAwait'); vi.mock('electron', () => ({ app: { getPath: vi.fn(() => '') }, shell: { showItemInFolder: vi.fn(), openPath: vi.fn() }, @@ -212,7 +214,8 @@ describe('Services Local User Files', () => { }); test('Should do nothing if deleteDirectory fails', async () => { - vi.mocked(deleteDirectory).mockRejectedValue('An error'); + vi.mocked(app.getPath).mockReturnValue('/tmp'); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: new Error('An error') }); const result = await deleteTempFolder('electronHederaFiles'); @@ -240,7 +243,8 @@ describe('Services Local User Files', () => { }); test('Should do nothing if deleteDirectory fails', async () => { - vi.mocked(deleteDirectory).mockRejectedValue('An error'); + vi.mocked(app.getPath).mockReturnValue('/tmp'); + vi.mocked(safeAwait).mockResolvedValueOnce({ data: undefined, error: new Error('An error') }); const result = await deleteAllTempFolders(); diff --git a/front-end/src/tests/main/services/localUser/transactionDrafts.spec.ts b/front-end/src/tests/main/services/localUser/transactionDrafts.spec.ts index 5f64a8496..21b7b7847 100644 --- a/front-end/src/tests/main/services/localUser/transactionDrafts.spec.ts +++ b/front-end/src/tests/main/services/localUser/transactionDrafts.spec.ts @@ -180,9 +180,15 @@ describe('Services Local User Public Keys Linked', () => { test('Should throw error if failed to get count', async () => { const userId = 'user1'; - prisma.transactionDraft.count.mockRejectedValue('Transaction draft Database error'); + prisma.transactionDraft.count.mockRejectedValueOnce('Transaction draft Database error'); + prisma.transactionDraft.count.mockRejectedValueOnce( + new Error('Transaction draft Database error'), + ); expect(() => getDraftsCount(userId)).rejects.toThrow('Failed to get drafts count'); + expect(() => getDraftsCount(userId)).rejects.toThrow( + new Error('Transaction draft Database error'), + ); }); }); }); diff --git a/front-end/src/tests/main/services/localUser/transactions.spec.ts b/front-end/src/tests/main/services/localUser/transactions.spec.ts index cf98abc12..5fcabec2c 100644 --- a/front-end/src/tests/main/services/localUser/transactions.spec.ts +++ b/front-end/src/tests/main/services/localUser/transactions.spec.ts @@ -566,13 +566,24 @@ describe('Services Local User Transactions', () => { }), }; + const queryMock2 = { + execute: vi.fn().mockImplementation(() => { + throw errorMessage; + }), + }; + vi.spyOn(SDK.Query, 'fromBytes').mockReturnValue(queryMock as unknown as SDK.Query); vi.spyOn(SDK.PrivateKey, 'fromStringED25519').mockReturnValue( privateKey as unknown as SDK.PrivateKey, ); - expect(() => executeQuery(queryBytes, accountId, privateKey, privateKeyType)).rejects.toThrow( - errorMessage, + await expect(executeQuery(queryBytes, accountId, privateKey, privateKeyType)).rejects.toThrow( + new Error(errorMessage), + ); + + vi.spyOn(SDK.Query, 'fromBytes').mockReturnValue(queryMock2 as unknown as SDK.Query); + await expect(executeQuery(queryBytes, accountId, privateKey, privateKeyType)).rejects.toThrow( + 'Failed to execute query', ); }); @@ -680,7 +691,7 @@ describe('Services Local User Transactions', () => { test('Should throw if storing transaction fails', async () => { vi.mocked(getNumberArrayFromString).mockImplementation(() => { - throw new Error(); + throw ''; }); expect( @@ -738,7 +749,7 @@ describe('Services Local User Transactions', () => { }, }; - prisma.transaction.findMany.mockRejectedValue(new Error()); + prisma.transaction.findMany.mockRejectedValue(''); await expect(getTransactions(findArgs)).rejects.toThrow('Failed to fetch transactions'); }); @@ -777,7 +788,7 @@ describe('Services Local User Transactions', () => { test('Should throw custom error if getting transactions count fails', async () => { const userId = '123'; - prisma.transaction.count.mockRejectedValue(new Error()); + prisma.transaction.count.mockRejectedValue(''); await expect(getTransactionsCount(userId)).rejects.toThrow( 'Failed to get transactions count', @@ -822,7 +833,7 @@ describe('Services Local User Transactions', () => { test('Should throw if fetching transaction fails', async () => { const id = '123'; - prisma.transaction.findFirst.mockRejectedValue(new Error()); + prisma.transaction.findFirst.mockRejectedValue(''); await expect(getTransaction(id)).rejects.toThrow( `Failed to fetch transaction with id: ${id}`, @@ -879,7 +890,7 @@ describe('Services Local User Transactions', () => { vi.mocked(isHederaSpecialFileId).mockReturnValue(true); vi.mocked(encodeHederaSpecialFile).mockImplementation(() => { - throw new Error(); + throw ''; }); await expect(encodeSpecialFile(content, fileId)).rejects.toThrow( diff --git a/front-end/src/tests/main/utils/safeAwait.spec.ts b/front-end/src/tests/main/utils/safeAwait.spec.ts new file mode 100644 index 000000000..d7253ecf8 --- /dev/null +++ b/front-end/src/tests/main/utils/safeAwait.spec.ts @@ -0,0 +1,70 @@ +import { safeAwait, safeAwaitAll } from '../../../main/utils/safeAwait'; + +describe('safeAwait', () => { + test('should return data when promise resolves', async () => { + const promise = Promise.resolve('test'); + const result = await safeAwait(promise); + expect(result).toEqual({ data: 'test' }); + }); + + test('should return error when promise rejects', async () => { + const promise = Promise.reject(new Error('test error')); + const result = await safeAwait(promise); + expect(result).toEqual({ error: new Error('test error') }); + }); + + test('should throw native exceptions', async () => { + const promise = Promise.reject(new TypeError('type error')); + await expect(safeAwait(promise)).rejects.toThrow(TypeError); + }); + + test('should handle native exceptions correctly', async () => { + const promise = Promise.reject(new TypeError('type error')); + await expect(safeAwait(promise)).rejects.toThrow(TypeError); + }); + + test('should return error when promise resolves with an Error instance', async () => { + const promise = Promise.resolve(new Error('resolved error')); + const result = await safeAwait(promise); + expect(result).toEqual({ error: new Error('resolved error') }); + }); +}); + +describe('safeAwaitAll', () => { + test('should return data for all resolved promises', async () => { + const promises = [Promise.resolve('test1'), Promise.resolve('test2')]; + const result = await safeAwaitAll(promises); + expect(result).toEqual([{ data: 'test1' }, { data: 'test2' }]); + }); + + test('should return error for all rejected promises', async () => { + const promises = [Promise.reject(new Error('error1')), Promise.reject(new Error('error2'))]; + const result = await safeAwaitAll(promises); + expect(result).toEqual([{ error: new Error('error1') }, { error: new Error('error2') }]); + }); + + test('should execute finallyFunc after all promises', async () => { + const promises = [Promise.resolve('test1'), Promise.resolve('test2')]; + const finallyFunc = vi.fn(); + await safeAwaitAll(promises, finallyFunc); + expect(finallyFunc).toHaveBeenCalled(); + }); + + test('should handle mixed resolved and rejected promises', async () => { + const promises = [Promise.resolve('test1'), Promise.reject(new Error('error2'))]; + const result = await safeAwaitAll(promises); + expect(result).toEqual([{ data: 'test1' }, { error: new Error('error2') }]); + }); + + test('should return error for all promises if safeAwait throws', async () => { + const promises = [Promise.reject(new TypeError('type error'))]; + const result = await safeAwaitAll(promises); + expect(result).toEqual([{ error: new TypeError('type error') }]); + }); + + test('should handle empty promises array', async () => { + const promises: Promise[] = []; + const result = await safeAwaitAll(promises); + expect(result).toEqual([]); + }); +}); From 5409e503a69c2407f46b83110cd072fe495059b9 Mon Sep 17 00:00:00 2001 From: Svetoslav Borislavov Date: Mon, 16 Dec 2024 09:00:27 +0200 Subject: [PATCH 11/11] feat: update NOTICE Signed-off-by: Svetoslav Borislavov --- NOTICE | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/NOTICE b/NOTICE index 7010b58bf..e07e929e2 100644 --- a/NOTICE +++ b/NOTICE @@ -950,10 +950,6 @@ The Initial Developer of atomic-sleep@1.0.0, is David Mark Clements (https://github.com/davidmarkclements/atomic-sleep). Copyright David Mark Clements. All Rights Reserved. -The Initial Developer of axios@1.7.7, -is Matt Zabriskie (https://github.com/axios/axios). -Copyright Matt Zabriskie. All Rights Reserved. - The Initial Developer of axios@1.7.8, is Matt Zabriskie (https://github.com/axios/axios). Copyright Matt Zabriskie. All Rights Reserved. @@ -2188,7 +2184,7 @@ The Initial Developer of murlock@4.0.0, is https://github.com/felanios (https://github.com/felanios/murlock). Copyright https://github.com/felanios. All Rights Reserved. -The Initial Developer of nanoid@3.3.7, +The Initial Developer of nanoid@3.3.8, is Andrey Sitnik (https://github.com/ai/nanoid). Copyright Andrey Sitnik. All Rights Reserved.