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
akalogerakisunicorn committed Nov 28, 2024
1 parent ef41173 commit 3c602d3
Show file tree
Hide file tree
Showing 20 changed files with 421 additions and 202 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
7 changes: 5 additions & 2 deletions src/composables/modals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,11 @@ export function useModals() {
return openModal(MODAL_CONFIRM, options);
}

function openErrorModal(entry: Record<string, any>) {
return openModal(MODAL_ERROR_LOG, { entry }).catch(handleUnknownError);
function openErrorModal(options: {
title?: string;
msg?: string;
}) {
return openModal(MODAL_ERROR_LOG, options).catch(handleUnknownError);
}

function openScanQrModal(options: {
Expand Down
3 changes: 3 additions & 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 Expand Up @@ -567,3 +568,5 @@ export const ACCOUNT_SELECT_TYPE_FILTER = {
recent: 'recent',
} as const;
export type AccountSelectTypeFilter = ObjectValues<typeof ACCOUNT_SELECT_TYPE_FILTER>;

export const MAX_LOG_ENTRIES = 1000;
41 changes: 33 additions & 8 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 { IS_PRODUCTION, MAX_LOG_ENTRIES, STORAGE_KEYS } from '@/constants';
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,13 @@ export default class Logger {
}

static write({ modal = !IS_PRODUCTION, ...error }: ILoggerInput) {
if (!Logger.background && modal && error.message) {
const { openErrorModal } = useModals();
openErrorModal({
title: error.title || t('modals.error-log.title'),
msg: error.message,
});
}
if (!Logger.saveErrorLog.value) {
return;
}
Expand All @@ -98,11 +110,7 @@ export default class Logger {
platform: process.env.PLATFORM!,
time: Date.now(),
};
WalletStorage.set(STORAGE_KEYS.errorLog, [...errorLog, logEntry]);
if (!Logger.background && modal && error.message) {
const { openErrorModal } = useModals();
openErrorModal(logEntry);
}
WalletStorage.set(STORAGE_KEYS.errorLog, [...errorLog, logEntry].slice(-MAX_LOG_ENTRIES));
}

static get(): ILoggerEntry[] {
Expand All @@ -115,4 +123,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, []);
}
}
}
1 change: 1 addition & 0 deletions src/popup/components/AddressBook/AddressBookList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ export default defineComponent({
.address-book-item {
background-color: var(--bg-color);
border: var(--border-width) solid var(--bg-color);
padding: 8px 2px 8px 8px;
}
.search-field {
Expand Down
2 changes: 1 addition & 1 deletion src/popup/components/InputField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,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
135 changes: 135 additions & 0 deletions src/popup/components/Modals/ConfirmDisableErrorLog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<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" v-text="$t('pages.errors-log-settings.disableDialog.title')" />
<div class="subtitle text-center" v-text="$t('pages.errors-log-settings.disableDialog.subtitle')" />
<div class="msg" v-text="$t('pages.errors-log-settings.disableDialog.msg')" />
<div class="question" v-text="$t('pages.errors-log-settings.disableDialog.question')" />
</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 {
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: {
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';
.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>
Loading

0 comments on commit 3c602d3

Please sign in to comment.