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 50fa68d
Show file tree
Hide file tree
Showing 14 changed files with 336 additions and 69 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),
'errorLogExportaddressBookExport.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
2 changes: 1 addition & 1 deletion src/popup/components/buttons/BtnSubheader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default defineComponent({
display: flex;
align-items: center;
width: 100%;
padding: 20px 12px;
padding: 20px 16px;
border-radius: $border-radius-interactive;
color: $color-white;
Expand Down
13 changes: 11 additions & 2 deletions src/popup/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@
"txDetails": "Transaction details",
"tokenDetails": "Token details",
"coinDetails": "Coin details",
"saveErrorsLog": "Save error log",
"saveErrorsLog": "Error log",
"resetWallet": "Reset wallet",
"seedPhrase": "Seed phrase",
"secureLogin": "Secure login",
Expand All @@ -626,7 +626,16 @@
"addressBookAdd": "Add new address"
},
"errors-log-settings": {
"description": "This will help us to identify what causes the errors. Thank you for being a fellow Superhero!"
"description": "<p>Superhero Wallet is able of keeping an error log of the errors you encounter if you chose to do so. This will help us to identify what causes the errors and work on proper fixes.</p><p>You may export the error log in <strong>.json</strong> format and provide this information when requested by Support.</p><p>Thank you for being a fellow Superhero!</p>",
"keepErrorLog": "Keep error log",
"exportErrorLog": "Export error log",
"disableDialog": {
"title": "Disable error log",
"subtitle": "and export collected data",
"msg": "You are about to disable keeping an error log for Superhero Wallet. This will delete all previous error data saved in the storage.",
"question": "Would you like to export the error log and disable the feature?",
"btnText": "Export and disable"
}
},
"addressBook": {
"addAddress": "Add",
Expand Down
Loading

0 comments on commit 50fa68d

Please sign in to comment.