Skip to content

Commit

Permalink
feat: export error log and show error modals on transaction errors
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexandros Kalogerakis committed Nov 20, 2024
1 parent 2fee398 commit 6bc458f
Show file tree
Hide file tree
Showing 18 changed files with 417 additions and 142 deletions.
28 changes: 6 additions & 22 deletions src/composables/addressBook.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { computed, ref, watch } from 'vue';
import { Directory, Filesystem } from '@capacitor/filesystem';
import { Capacitor } from '@capacitor/core';

import {
AccountAddress,
Expand All @@ -10,12 +8,12 @@ import {
import { STORAGE_KEYS, MODAL_ADDRESS_BOOK_IMPORT } from '@/constants';
import { AddressBookEntryExists, AddressBookInvalidAddress, AddressBookRequiredFields } from '@/lib/errors';
import {
convertBlobToBase64,
createCustomScopedComposable,
getProtocolByAddress,
handleUnknownError,
selectFiles,
pipe,
exportFile,
} from '@/utils';
import { tg as t } from '@/popup/plugins/i18n';

Expand Down Expand Up @@ -170,29 +168,15 @@ export const useAddressBook = createCustomScopedComposable(() => {
}

async function exportAddressBook() {
const json = JSON.stringify(addressBook.value);
const blob = new Blob([json], { type: 'text/plain' });
const a = document.createElement('a');
const href = window.URL.createObjectURL(blob);
const filename = 'addressBookExport.json';

if (Capacitor.isNativePlatform()) {
const base64 = await convertBlobToBase64(blob);
const saveFile = await Filesystem.writeFile({
path: filename,
data: base64,
directory: Directory.Documents,
});
const path = saveFile.uri;
const path = await exportFile(
JSON.stringify(addressBook.value),
'addressBookExport.json',
);
if (path) {
openDefaultModal({
title: t('pages.addressBook.export.title'),
msg: t('pages.addressBook.export.message') + path,
});
} else {
a.download = filename;
a.href = href;
a.dataset.downloadurl = ['text/json', a.download, a.href].join(':');
a.click();
}
}

Expand Down
1 change: 1 addition & 0 deletions src/constants/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ export const MODAL_CONFIRM_RAW_SIGN = 'confirm-raw-sign';
export const MODAL_CONFIRM_UNSAFE_SIGN = 'confirm-unsafe-sign';
export const MODAL_CONFIRM_CONNECT = 'confirm-connect';
export const MODAL_CONFIRM_ACCOUNT_LIST = 'confirm-account-list';
export const MODAL_CONFIRM_DISABLE_ERROR_LOG = 'confirm-disable-error-log';
export const MODAL_CONSENSUS_INFO = 'consensus-info';
export const MODAL_DEFAULT = 'default';
export const MODAL_ERROR_LOG = 'error-log';
Expand Down
39 changes: 33 additions & 6 deletions src/lib/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
import { pick } from 'lodash-es';
import { detect } from 'detect-browser';
import { App, ComputedRef } from 'vue';

import { IS_PRODUCTION, STORAGE_KEYS } from '@/constants';
import { useModals, useUi } from '../composables';
import { RejectedByUserError } from './errors';
import { exportFile } from '@/utils';
import { tg as t } from '@/popup/plugins/i18n';
import { useModals, useUi } from '@/composables';

import { WalletStorage } from './WalletStorage';
import { RejectedByUserError } from './errors';

interface ILoggerOptions {
background?: boolean;
Expand All @@ -21,6 +25,7 @@ interface ILoggerEntry {
}

interface ILoggerInput {
title?: string;
modal?: boolean;
message: string;
type: 'vue-error' | 'unhandledrejection' | 'window-error' | 'api-response';
Expand Down Expand Up @@ -87,6 +92,15 @@ export default class Logger {
}

static write({ modal = !IS_PRODUCTION, ...error }: ILoggerInput) {
if (!Logger.background && modal && error.message) {
const { openDefaultModal } = useModals();
openDefaultModal({
icon: 'critical',
title: error.title || t('modals.error-log.title'),
msg: error.message,
textCenter: true,
});
}
if (!Logger.saveErrorLog.value) {
return;
}
Expand All @@ -99,10 +113,6 @@ export default class Logger {
time: Date.now(),
};
WalletStorage.set(STORAGE_KEYS.errorLog, [...errorLog, logEntry]);
if (!Logger.background && modal && error.message) {
const { openErrorModal } = useModals();
openErrorModal(logEntry);
}
}

static get(): ILoggerEntry[] {
Expand All @@ -115,4 +125,21 @@ export default class Logger {
// TODO: make call to backend here
}
}

static async exportErrorLog(clear: boolean = false) {
const path = await exportFile(
JSON.stringify(Logger.get()),
'errorLogExport.json',
);
if (path) {
const { openDefaultModal } = useModals();
openDefaultModal({
title: t('pages.addressBook.export.title'),
msg: t('pages.addressBook.export.message') + path,
});
}
if (clear) {
WalletStorage.set(STORAGE_KEYS.errorLog, []);
}
}
}
2 changes: 1 addition & 1 deletion src/popup/components/InputField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ export default defineComponent({
.input-wrapper {
position: relative;
display: block;
padding: 10px 8px 12px 10px; // Decides on the input size
padding: 10px 8px 10px 10px; // Decides on the input size
background-color: var(--color-bg);
border: none;
border-radius: $border-radius-interactive;
Expand Down
151 changes: 151 additions & 0 deletions src/popup/components/Modals/ConfirmDisableErrorLog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<template>
<Modal
class="confirm"
has-close-button
from-bottom
no-padding
@close="reject"
>
<div class="content">
<div class="icon-wrapper">
<IconBoxed>
<IconWrapper
:icon="ExportIcon"
icon-size="xl"
class="icon"
/>
</IconBoxed>
</div>

<h2 class="text-heading-4 text-center">
{{ $t('pages.errors-log-settings.disableDialog.title') }}
</h2>

<div class="subtitle text-center">
{{ $t('pages.errors-log-settings.disableDialog.subtitle') }}
</div>

<div class="msg">
{{ $t('pages.errors-log-settings.disableDialog.msg') }}
</div>

<div class="question">
{{ $t('pages.errors-log-settings.disableDialog.question') }}
</div>
</div>

<template #footer>
<div class="footer">
<BtnMain
variant="muted"
:text="$t('common.cancel')"
@click="reject"
/>
<BtnMain
variant="primary"
extra-padded
:text="$t('pages.errors-log-settings.disableDialog.btnText')"
@click="resolve"
/>
</div>
</template>
</Modal>
</template>

<script lang="ts">
import {
defineComponent,
PropType,
} from 'vue';
import type {
TxFunctionMultisig,
RejectCallback,
ResolveCallback,
} from '@/types';
import IconBoxed from '@/popup/components/IconBoxed.vue';
import IconWrapper from '@/popup/components/IconWrapper.vue';
import Modal from '@/popup/components/Modal.vue';
import BtnMain from '@/popup/components/buttons/BtnMain.vue';
import ExportIcon from '@/icons/export-address-book.svg?vue-component';
export default defineComponent({
components: {
IconWrapper,
Modal,
BtnMain,
IconBoxed,
},
props: {
signers: { type: Array as PropType<string[]>, required: true },
action: { type: String as PropType<TxFunctionMultisig>, required: true },
resolve: {
type: Function as PropType<ResolveCallback<boolean>>,
required: true,
},
reject: { type: Function as PropType<RejectCallback>, required: true },
},
setup() {
return {
ExportIcon,
};
},
});
</script>

<style lang="scss" scoped>
@use '@/styles/variables' as *;
@use '@/styles/typography';
@use '@/styles/mixins';
.confirm {
text-align: center;
.content {
padding: 8px 24px;
}
.text-heading-4 {
margin-bottom: 4px;
}
.subtitle {
@extend %face-sans-16-medium;
margin-bottom: 20px;
}
.msg {
@extend %face-sans-15-regular;
color: rgba($color-white, 0.85);
margin-bottom: 10px;
}
.question {
@extend %face-sans-15-semi-bold;
color: rgba($color-white, 0.85);
margin-bottom: 20px;
}
.icon-wrapper {
margin-bottom: 20px;
.icon {
background-color: rgba($color-primary, 0.4);
color: $color-primary;
}
}
.footer {
display: flex;
justify-content: center;
gap: 8px;
width: 100%;
padding-bottom: 24px;
padding-inline: 24px;
}
}
</style>
38 changes: 32 additions & 6 deletions src/popup/components/Modals/Default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@

<template #footer>
<slot name="footer">
<BtnMain
v-if="icon === 'critical' && saveErrorLogEnabled"
variant="muted"
:class="{ 'center-button': textCenter }"
:text="buttonMessage || $t('pages.errors-log-settings.exportErrorLog')"
:icon="ExportIcon"
@click="exportErrorLog"
/>
<BtnMain
:class="{ 'center-button': textCenter }"
:text="buttonMessage || $t('common.ok')"
Expand All @@ -48,13 +56,17 @@
</template>

<script lang="ts">
import { defineComponent, PropType } from 'vue';
import { computed, defineComponent, PropType } from 'vue';
import type { ResolveCallback, StatusIconType } from '@/types';
import Modal from '../Modal.vue';
import BtnMain from '../buttons/BtnMain.vue';
import StatusIcon from '../StatusIcon.vue';
import TemplateRenderer from '../TemplateRenderer.vue';
import IconBoxed from '../IconBoxed.vue';
import Logger from '@/lib/logger';
import Modal from '@/popup/components/Modal.vue';
import BtnMain from '@/popup/components/buttons/BtnMain.vue';
import StatusIcon from '@/popup/components/StatusIcon.vue';
import TemplateRenderer from '@/popup/components/TemplateRenderer.vue';
import IconBoxed from '@/popup/components/IconBoxed.vue';
import ExportIcon from '@/icons/export-address-book.svg?vue-component';
export default defineComponent({
components: {
Expand All @@ -74,6 +86,20 @@ export default defineComponent({
textCenter: Boolean,
fullScreen: Boolean,
},
setup({ resolve }) {
const saveErrorLogEnabled = computed(() => Logger.saveErrorLog.value);
function exportErrorLog() {
Logger.exportErrorLog();
resolve();
}
return {
saveErrorLogEnabled,
exportErrorLog,
ExportIcon,
};
},
});
</script>

Expand Down
15 changes: 8 additions & 7 deletions src/popup/components/Modals/MultisigVaultCreate.vue
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,8 @@ import { Encoded } from '@aeternity/aepp-sdk';
import type { ICreateMultisigAccount, ObjectValues } from '@/types';
import { MODAL_ADDRESS_BOOK_ACCOUNT_SELECTOR, PROTOCOLS } from '@/constants';
import { excludeFalsy, handleUnknownError } from '@/utils';
import { tg } from '@/popup/plugins/i18n';
import { excludeFalsy } from '@/utils';
import { ROUTE_MULTISIG_ACCOUNT } from '@/popup/router/routeNames';
import {
useAccounts,
Expand Down Expand Up @@ -240,6 +241,7 @@ import CircleCloseIcon from '@/icons/circle-close.svg?vue-component';
import QrScanIcon from '@/icons/qr-scan.svg?vue-component';
import AddressBookIcon from '@/icons/menu-card-fill.svg?vue-component';
import PlusCircleIcon from '@/icons/plus-circle.svg?vue-component';
import Logger from '@/lib/logger';
const STEPS = {
form: 'form',
Expand Down Expand Up @@ -426,12 +428,11 @@ export default defineComponent({
signersAddressList.value,
);
} catch (error: any) {
handleUnknownError(error);
await openDefaultModal({
title: t('multisig.multisigVaultCreationFailed'),
icon: 'critical',
msg: error?.details?.reason,
textCenter: true,
Logger.write({
title: tg('multisig.multisigVaultCreationFailed'),
message: error?.details?.reason || '',
type: 'api-response',
modal: true,
});
currentStep.value = STEPS.form;
}
Expand Down
Loading

0 comments on commit 6bc458f

Please sign in to comment.