2022-01-19 07:39:39 +00:00
|
|
|
import { ipcRenderer } from 'electron';
|
2022-04-20 06:38:47 +00:00
|
|
|
import { t } from 'fyo';
|
2022-08-10 05:43:58 +00:00
|
|
|
import { ConfigKeys } from 'fyo/core/types';
|
2022-04-24 06:48:44 +00:00
|
|
|
import { Doc } from 'fyo/model/doc';
|
2022-07-28 07:55:48 +00:00
|
|
|
import { BaseError } from 'fyo/utils/errors';
|
2022-04-19 05:59:36 +00:00
|
|
|
import { ErrorLog } from 'fyo/utils/types';
|
2022-05-24 17:59:05 +00:00
|
|
|
import { truncate } from 'lodash';
|
2022-03-31 07:33:58 +00:00
|
|
|
import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages';
|
2022-04-20 06:38:47 +00:00
|
|
|
import { fyo } from './initFyo';
|
2022-05-24 17:59:05 +00:00
|
|
|
import router from './router';
|
2022-05-30 11:34:25 +00:00
|
|
|
import { getErrorMessage, stringifyCircular } from './utils';
|
2022-05-24 17:59:05 +00:00
|
|
|
import { MessageDialogOptions, ToastOptions } from './utils/types';
|
2023-03-24 07:32:22 +00:00
|
|
|
import { showMessageDialog } from './utils/ui';
|
2022-01-18 13:03:16 +00:00
|
|
|
|
2022-01-24 09:04:37 +00:00
|
|
|
function shouldNotStore(error: Error) {
|
2022-07-28 07:55:48 +00:00
|
|
|
const shouldLog = (error as BaseError).shouldStore ?? true;
|
|
|
|
return !shouldLog;
|
2022-01-18 13:03:16 +00:00
|
|
|
}
|
|
|
|
|
2022-09-20 17:19:09 +00:00
|
|
|
export async function sendError(errorLogObj: ErrorLog) {
|
2022-01-28 08:03:07 +00:00
|
|
|
if (!errorLogObj.stack) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-12-23 07:13:06 +00:00
|
|
|
errorLogObj.more ??= {};
|
|
|
|
errorLogObj.more!.path ??= router.currentRoute.value.fullPath;
|
|
|
|
|
2022-01-28 08:03:07 +00:00
|
|
|
const body = {
|
|
|
|
error_name: errorLogObj.name,
|
|
|
|
message: errorLogObj.message,
|
|
|
|
stack: errorLogObj.stack,
|
2022-08-23 09:14:36 +00:00
|
|
|
platform: fyo.store.platform,
|
|
|
|
version: fyo.store.appVersion,
|
|
|
|
language: fyo.store.language,
|
|
|
|
instance_id: fyo.store.instanceId,
|
2022-09-20 17:19:09 +00:00
|
|
|
device_id: fyo.store.deviceId,
|
2022-08-23 09:14:36 +00:00
|
|
|
open_count: fyo.store.openCount,
|
|
|
|
country_code: fyo.singles.SystemSettings?.countryCode,
|
2022-12-23 07:13:06 +00:00
|
|
|
more: stringifyCircular(errorLogObj.more!),
|
2022-01-28 08:03:07 +00:00
|
|
|
};
|
2022-03-17 06:03:02 +00:00
|
|
|
|
2022-04-20 06:38:47 +00:00
|
|
|
if (fyo.store.isDevelopment) {
|
2022-09-20 17:19:09 +00:00
|
|
|
console.log('sendError', body);
|
2022-03-17 06:03:02 +00:00
|
|
|
}
|
|
|
|
|
2022-02-02 07:18:03 +00:00
|
|
|
await ipcRenderer.invoke(IPC_ACTIONS.SEND_ERROR, JSON.stringify(body));
|
2022-01-19 10:59:13 +00:00
|
|
|
}
|
|
|
|
|
2022-05-27 07:50:14 +00:00
|
|
|
function getToastProps(errorLogObj: ErrorLog) {
|
2022-04-20 06:38:47 +00:00
|
|
|
const props: ToastOptions = {
|
2022-05-20 11:12:32 +00:00
|
|
|
message: errorLogObj.name ?? t`Error`,
|
2022-01-19 11:13:50 +00:00
|
|
|
type: 'error',
|
2022-05-23 08:09:07 +00:00
|
|
|
actionText: t`Report Error`,
|
|
|
|
action: () => reportIssue(errorLogObj),
|
2022-01-19 11:13:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return props;
|
|
|
|
}
|
|
|
|
|
2022-03-16 11:42:06 +00:00
|
|
|
export function getErrorLogObject(
|
|
|
|
error: Error,
|
|
|
|
more: Record<string, unknown>
|
|
|
|
): ErrorLog {
|
2022-08-30 12:43:42 +00:00
|
|
|
const { name, stack, message, cause } = error;
|
|
|
|
if (cause) {
|
|
|
|
more.cause = cause;
|
|
|
|
}
|
|
|
|
|
2022-02-03 10:03:21 +00:00
|
|
|
const errorLogObj = { name, stack, message, more };
|
|
|
|
|
2022-04-20 06:38:47 +00:00
|
|
|
fyo.errorLog.push(errorLogObj);
|
2022-02-03 10:03:21 +00:00
|
|
|
|
|
|
|
return errorLogObj;
|
|
|
|
}
|
|
|
|
|
2022-05-23 08:09:07 +00:00
|
|
|
export async function handleError(
|
2022-07-28 07:55:48 +00:00
|
|
|
logToConsole: boolean,
|
2022-01-24 09:04:37 +00:00
|
|
|
error: Error,
|
2022-05-27 07:50:14 +00:00
|
|
|
more?: Record<string, unknown>
|
2022-01-24 09:04:37 +00:00
|
|
|
) {
|
2022-07-28 07:55:48 +00:00
|
|
|
if (logToConsole) {
|
2022-01-18 13:03:16 +00:00
|
|
|
console.error(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (shouldNotStore(error)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-16 11:42:06 +00:00
|
|
|
const errorLogObj = getErrorLogObject(error, more ?? {});
|
2022-01-18 13:03:16 +00:00
|
|
|
|
2022-09-20 17:19:09 +00:00
|
|
|
await sendError(errorLogObj);
|
2022-05-27 07:50:14 +00:00
|
|
|
const toastProps = getToastProps(errorLogObj);
|
2023-03-24 07:32:22 +00:00
|
|
|
const { showToast } = await import('src/utils/interactive');
|
2022-05-23 08:09:07 +00:00
|
|
|
await showToast(toastProps);
|
2022-01-18 13:03:16 +00:00
|
|
|
}
|
2022-01-19 06:14:12 +00:00
|
|
|
|
2022-05-24 17:59:05 +00:00
|
|
|
export async function handleErrorWithDialog(
|
2023-03-06 08:48:04 +00:00
|
|
|
error: unknown,
|
2022-05-24 17:59:05 +00:00
|
|
|
doc?: Doc,
|
2023-03-21 07:52:49 +00:00
|
|
|
reportError?: boolean,
|
|
|
|
dontThrow?: boolean
|
2022-05-24 17:59:05 +00:00
|
|
|
) {
|
2023-03-06 08:48:04 +00:00
|
|
|
if (!(error instanceof Error)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-24 09:04:37 +00:00
|
|
|
const errorMessage = getErrorMessage(error, doc);
|
2022-05-23 08:09:07 +00:00
|
|
|
await handleError(false, error, { errorMessage, doc });
|
2022-01-19 07:39:39 +00:00
|
|
|
|
2022-08-29 12:35:33 +00:00
|
|
|
const label = getErrorLabel(error);
|
2022-08-30 09:04:18 +00:00
|
|
|
const options: MessageDialogOptions = {
|
|
|
|
message: label,
|
|
|
|
detail: errorMessage,
|
|
|
|
};
|
2022-05-24 17:59:05 +00:00
|
|
|
|
|
|
|
if (reportError) {
|
|
|
|
options.detail = truncate(options.detail, { length: 128 });
|
|
|
|
options.buttons = [
|
|
|
|
{
|
|
|
|
label: t`Report`,
|
|
|
|
action() {
|
|
|
|
reportIssue(getErrorLogObject(error, { errorMessage }));
|
|
|
|
},
|
|
|
|
},
|
2022-08-29 12:35:33 +00:00
|
|
|
{ label: t`Cancel`, action() {} },
|
2022-05-24 17:59:05 +00:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
await showMessageDialog(options);
|
|
|
|
if (dontThrow) {
|
|
|
|
if (fyo.store.isDevelopment) {
|
|
|
|
console.error(error);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-19 07:39:39 +00:00
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
|
2022-01-24 09:04:37 +00:00
|
|
|
export async function showErrorDialog(title?: string, content?: string) {
|
2022-01-19 07:39:39 +00:00
|
|
|
// To be used for show stopper errors
|
2022-01-24 06:44:11 +00:00
|
|
|
title ??= t`Error`;
|
|
|
|
content ??= t`Something has gone terribly wrong. Please check the console and raise an issue.`;
|
2022-01-19 07:39:39 +00:00
|
|
|
|
|
|
|
await ipcRenderer.invoke(IPC_ACTIONS.SHOW_ERROR, { title, content });
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wrapper Functions
|
|
|
|
|
2022-01-24 09:04:37 +00:00
|
|
|
export function getErrorHandled(func: Function) {
|
|
|
|
return async function errorHandled(...args: unknown[]) {
|
2022-01-19 06:14:12 +00:00
|
|
|
try {
|
|
|
|
return await func(...args);
|
|
|
|
} catch (error) {
|
2022-05-23 08:09:07 +00:00
|
|
|
await handleError(false, error as Error, {
|
2022-01-19 06:14:12 +00:00
|
|
|
functionName: func.name,
|
|
|
|
functionArgs: args,
|
|
|
|
});
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-01-24 09:04:37 +00:00
|
|
|
export function getErrorHandledSync(func: Function) {
|
|
|
|
return function errorHandledSync(...args: unknown[]) {
|
2022-01-19 06:14:12 +00:00
|
|
|
try {
|
|
|
|
return func(...args);
|
|
|
|
} catch (error) {
|
2022-01-24 09:04:37 +00:00
|
|
|
handleError(false, error as Error, {
|
2022-01-19 06:14:12 +00:00
|
|
|
functionName: func.name,
|
|
|
|
functionArgs: args,
|
2022-05-23 08:09:07 +00:00
|
|
|
}).then(() => {
|
|
|
|
throw error;
|
2022-01-19 06:14:12 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2022-01-24 11:19:34 +00:00
|
|
|
|
2022-01-25 09:33:17 +00:00
|
|
|
function getIssueUrlQuery(errorLogObj?: ErrorLog): string {
|
2022-01-24 11:19:34 +00:00
|
|
|
const baseUrl = 'https://github.com/frappe/books/issues/new?labels=bug';
|
|
|
|
|
2023-03-02 10:20:03 +00:00
|
|
|
const body = [
|
|
|
|
'<h2>Description</h2>',
|
|
|
|
'Add some description...',
|
|
|
|
'',
|
|
|
|
'<h2>Steps to Reproduce</h2>',
|
|
|
|
'Add steps to reproduce the error...',
|
|
|
|
'',
|
|
|
|
'<h2>Info</h2>',
|
|
|
|
'',
|
|
|
|
];
|
2022-01-25 09:33:17 +00:00
|
|
|
|
|
|
|
if (errorLogObj) {
|
2023-03-02 10:20:03 +00:00
|
|
|
body.push(`**Error**: _${errorLogObj.name}: ${errorLogObj.message}_`, '');
|
2022-01-25 09:33:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (errorLogObj?.stack) {
|
2022-01-24 11:19:34 +00:00
|
|
|
body.push('**Stack**:', '```', errorLogObj.stack, '```', '');
|
|
|
|
}
|
|
|
|
|
2022-05-24 19:06:54 +00:00
|
|
|
body.push(`**Version**: \`${fyo.store.appVersion}\``);
|
2022-07-07 12:29:06 +00:00
|
|
|
body.push(`**Platform**: \`${fyo.store.platform}\``);
|
2022-05-24 19:06:54 +00:00
|
|
|
body.push(`**Path**: \`${router.currentRoute.value.fullPath}\``);
|
2022-05-24 17:59:05 +00:00
|
|
|
|
2022-08-10 05:43:58 +00:00
|
|
|
body.push(`**Language**: \`${fyo.config.get(ConfigKeys.Language)}\``);
|
|
|
|
if (fyo.singles.SystemSettings?.countryCode) {
|
|
|
|
body.push(`**Country**: \`${fyo.singles.SystemSettings.countryCode}\``);
|
|
|
|
}
|
|
|
|
|
2022-01-24 11:19:34 +00:00
|
|
|
const url = [baseUrl, `body=${body.join('\n')}`].join('&');
|
|
|
|
return encodeURI(url);
|
|
|
|
}
|
|
|
|
|
2022-01-25 09:33:17 +00:00
|
|
|
export function reportIssue(errorLogObj?: ErrorLog) {
|
2022-01-24 11:19:34 +00:00
|
|
|
const urlQuery = getIssueUrlQuery(errorLogObj);
|
|
|
|
ipcRenderer.send(IPC_MESSAGES.OPEN_EXTERNAL, urlQuery);
|
|
|
|
}
|
2022-08-29 12:35:33 +00:00
|
|
|
|
|
|
|
function getErrorLabel(error: Error) {
|
|
|
|
const name = error.name;
|
|
|
|
if (!name) {
|
|
|
|
return t`Error`;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (name === 'BaseError') {
|
|
|
|
return t`Error`;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (name === 'ValidationError') {
|
|
|
|
return t`Validation Error`;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (name === 'NotFoundError') {
|
|
|
|
return t`Not Found`;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (name === 'ForbiddenError') {
|
|
|
|
return t`Forbidden Error`;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (name === 'DuplicateEntryError') {
|
|
|
|
return t`Duplicate Entry`;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (name === 'LinkValidationError') {
|
|
|
|
return t`Link Validation Error`;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (name === 'MandatoryError') {
|
|
|
|
return t`Mandatory Error`;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (name === 'DatabaseError') {
|
|
|
|
return t`Database Error`;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (name === 'CannotCommitError') {
|
|
|
|
return t`Cannot Commit Error`;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (name === 'NotImplemented') {
|
|
|
|
return t`Error`;
|
|
|
|
}
|
|
|
|
|
2022-09-20 17:19:09 +00:00
|
|
|
if (name === 'ToDebugError') {
|
|
|
|
return t`Error`;
|
|
|
|
}
|
|
|
|
|
2022-08-29 12:35:33 +00:00
|
|
|
return t`Error`;
|
|
|
|
}
|