Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

feat: permite exportar o registro de erros #195

Merged
merged 5 commits into from
May 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@
"vue/no-lone-template": "error",
"vue/this-in-template": ["error", "never"],

"vue/block-lang": ["error", { "script": { "lang": "ts" } }],
"vue/block-lang": ["error", { "script": { "lang": "ts" }, "style": { "lang": "scss" } }],
"vue/component-api-style": ["error", ["script-setup"]],
"vue/component-name-in-template-casing": ["error", "PascalCase"],
"vue/component-options-name-casing": ["error", "PascalCase"],
Expand Down
28 changes: 20 additions & 8 deletions electron/child-process/error.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { join } from 'node:path';
import { appendFile } from 'node:fs/promises';
import * as path from 'node:path';
import * as fs from 'node:fs/promises';
import { AresError } from '$global/error';
import { isString } from '$global/guards';
import { ErrorLogFile } from '$global/constants';
import type { ElectronErrorLogType, OmitOptionalErrorLogProps } from '$types/error';

export class ChildProcessError extends AresError {
constructor(message: string) {
Expand All @@ -10,15 +13,24 @@ export class ChildProcessError extends AresError {

public static override async catch(err: unknown) {
if (!process.env.USER_DATA_PATH) return;
process.env.ARES_VERSION ??= 'unknown version';
process.env.ELECTRON_VERSION ??= 'unknown version';

if (err instanceof Error) {
const errorLog: OmitOptionalErrorLogProps<ElectronErrorLogType> = {
name: err.name,
message: err.message,
stack: isString(err.stack) ? err.stack : err.message,
time: Date.now(),
ares: process.env.ARES_VERSION ?? 'unknown',
chrome: process.env.CHROME_VERSION ?? 'unknown',
electron: process.env.ELECTRON_VERSION ??= 'unknown',
tribal: process.env.TRIBAL_WARS_VERSION ?? 'unknown',
locale: process.env.TRIBAL_WARS_LOCALE ?? 'unknown'
};

// Gera um arquivo de log com a data e a pilha de erros.
const date = new Date().toLocaleString('pt-br');
const logPath = join(process.env.USER_DATA_PATH, 'child-process-error.log');
const content = `${date}\nAres: ${process.env.ARES_VERSION} Electron: ${process.env.ELECTRON_VERSION}\n${err.stack}\n\n`;
await appendFile(logPath, content);
const content = this.generateLogContent(errorLog);
const logPath = path.join(process.env.USER_DATA_PATH, ErrorLogFile.ChildProcess);
await fs.appendFile(logPath, content, { encoding: 'utf-8' });
};
};
};
9 changes: 9 additions & 0 deletions electron/env.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { app } from 'electron';
import { storeToRefs, watch } from 'mechanus';
import { useAresStore } from '$electron/interface';

/** Define as variáveis de ambiente. */
export function setEnv() {
process.env.ARES_MODE = 'dev';
process.env.ARES_VERSION = app.getVersion();
process.env.CHROME_VERSION = process.versions.chrome;
process.env.ELECTRON_VERSION = process.versions.electron;
process.env.USER_DATA_PATH = app.getPath('userData');

const aresStore = useAresStore();
const { locale, majorVersion } = storeToRefs(aresStore);

watch(locale, (value) => (process.env.TRIBAL_WARS_LOCALE = value ?? 'unknown'));
watch(majorVersion, (value) => (process.env.TRIBAL_WARS_VERSION = value ?? 'unknown'));
};
23 changes: 19 additions & 4 deletions electron/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import * as path from 'node:path';
import * as fs from 'node:fs/promises';
import { app } from 'electron';
import { AresError } from '$global/error';
import { isString } from '$global/guards';
import { ErrorLogFile } from '$global/constants';
import type { ElectronErrorLogType, OmitOptionalErrorLogProps } from '$types/error';

export class MainProcessError extends AresError {
constructor(message: string) {
Expand All @@ -18,10 +21,22 @@ export class MainProcessError extends AresError {
/** Gera um arquivo de log com a data e a pilha de erros. */
public static async log(err: unknown) {
if (!(err instanceof Error)) return;
const date = new Date().toLocaleString('pt-br');
const logPath = path.join(app.getPath('userData'), 'ares-error.log');
const content = `${date}\nAres: ${app.getVersion()} Electron: ${process.versions.electron}\n${err.stack}\n\n`;
await fs.appendFile(logPath, content);

const errorLog: OmitOptionalErrorLogProps<ElectronErrorLogType> = {
name: err.name,
message: err.message,
stack: isString(err.stack) ? err.stack : null,
time: Date.now(),
ares: app.getVersion(),
chrome: process.versions.chrome,
electron: process.versions.electron,
tribal: process.env.TRIBAL_WARS_VERSION ?? 'unknown',
locale: process.env.TRIBAL_WARS_LOCALE ?? 'unknown'
};

const content = this.generateLogContent(errorLog);
const logPath = path.join(app.getPath('userData'), ErrorLogFile.Uncaught);
await fs.appendFile(logPath, content, { encoding: 'utf-8' });
};
};

Expand Down
77 changes: 57 additions & 20 deletions electron/events/error.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import * as path from 'node:path';
import * as fs from 'node:fs/promises';
import { URL } from 'url';
import { Op } from 'sequelize';
import { app, ipcMain, BrowserWindow } from 'electron';
import { app, dialog, ipcMain, BrowserWindow } from 'electron';
import { MainProcessEventError } from '$electron/error';
import { sequelize } from '$electron/database';
import { getActiveModule } from '$electron/app/modules';
import { ErrorLog, ElectronErrorLog, useAresStore } from '$electron/interface';
import type { ErrorLogBase, ErrorLogType } from '$types/error';
import { getMainWindow } from '$electron/utils/helpers';
import { ErrorLogFile } from '$global/constants';
import type { AllErrorLogTypes, ErrorLogBase, ErrorLogType, OmitOptionalErrorLogProps } from '$types/error';

export function setErrorEvents() {
const mainWindow = getMainWindow();
const aresStore = useAresStore();

ipcMain.on('error:create-log', async (e, error: ErrorLogBase) => {
ipcMain.on('error:create-log', async (e, error: OmitOptionalErrorLogProps<ErrorLogBase>) => {
try {
const errorLog: Omit<ErrorLogType, 'id' | 'pending'> = {
const errorLog: OmitOptionalErrorLogProps<ErrorLogType> = {
name: error.name,
message: error.message,
stack: error.stack,
Expand Down Expand Up @@ -49,7 +54,7 @@ export function setErrorEvents() {
await ErrorLog.destroy({ where: { time: { [Op.lte]: expiration } }, transaction });
});

const errors = await ErrorLog.findAll();
const errors = await ErrorLog.findAll({ where: { pending: true } });
return errors.map((error) => error.toJSON());

} catch (err) {
Expand All @@ -58,17 +63,6 @@ export function setErrorEvents() {
};
});

ipcMain.on('error:delete-log', async (_e, id: number) => {
try {
await sequelize.transaction(async (transaction) => {
await ErrorLog.destroy({ where: { id }, transaction });
});

} catch (err) {
MainProcessEventError.catch(err);
};
});

ipcMain.handle('error:get-electron-log', async () => {
try {
await sequelize.transaction(async (transaction) => {
Expand All @@ -77,7 +71,7 @@ export function setErrorEvents() {
await ElectronErrorLog.destroy({ where: { time: { [Op.lte]: expiration } }, transaction });
});

const errors = await ElectronErrorLog.findAll();
const errors = await ElectronErrorLog.findAll({ where: { pending: true } });
return errors.map((error) => error.toJSON());

} catch (err) {
Expand All @@ -86,14 +80,57 @@ export function setErrorEvents() {
};
});

ipcMain.on('error:delete-electron-log', async (_e, id: number) => {
ipcMain.handle('error:export', async (): Promise<'canceled' | 'error' | 'sucess'> => {
try {
const normalErrors = await ErrorLog.findAll({ where: { pending: true } });
const electronErrors = await ElectronErrorLog.findAll({ where: { pending: true } });
const errors = [...normalErrors, ...electronErrors].sort((a, b) => a.time - b.time);
if (errors.length === 0) return 'canceled';

const asJson = errors.map((err) => err.toJSON()) as AllErrorLogTypes[];
let content = MainProcessEventError.generateLogContent(asJson);

const userData = app.getPath('userData');
const uncaughtLogFilePath = path.join(userData, ErrorLogFile.Uncaught);
content = await consumeLogFile(uncaughtLogFilePath, content);

const childProcessFilePath = path.join(userData, ErrorLogFile.ChildProcess);
content = await consumeLogFile(childProcessFilePath, content);

const mergedLogPath = path.join(userData, ErrorLogFile.All);
await fs.appendFile(mergedLogPath, content, { encoding: 'utf-8' });

const defaultPath = `ares-error-log-${Date.now()}.log`;
const { canceled, filePath: savePath } = await dialog.showSaveDialog(mainWindow, { defaultPath });

if (canceled) {
return 'canceled';
} else if (!savePath) {
throw new MainProcessEventError('Could not export error log.');
};

await fs.writeFile(savePath, content, { encoding: 'utf-8' });

await sequelize.transaction(async (transaction) => {
await ElectronErrorLog.destroy({ where: { id }, transaction });
await ErrorLog.update({ pending: false }, { fields: ['pending'], where: { pending: true }, transaction });
await ElectronErrorLog.update({ pending: false }, { fields: ['pending'], where: { pending: true }, transaction });
});


return 'sucess';

} catch (err) {
MainProcessEventError.catch(err);
return 'error';
};
});
};

async function consumeLogFile(filePath: string, currentContent: string) {
try {
const newContent = await fs.readFile(filePath, { encoding: 'utf-8' });
queueMicrotask(() => fs.rm(filePath).catch(MainProcessEventError.catch));
return currentContent + newContent;
} catch {
return currentContent;
};
};
13 changes: 6 additions & 7 deletions electron/interface/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,28 @@ import { sequelize } from '$electron/database';
import { getActiveModule } from '$electron/app/modules';
import { getMainWindow } from '$electron/utils/helpers';
import { MainProcessError } from '$electron/error';
import type { ElectronErrorLogBase } from '$types/error';
import type { useAresStore, useAppNotificationsStore } from '$electron/interface';
import type { ElectronErrorLogType, OmitOptionalErrorLogProps } from '$types/error';
import type { useAppNotificationsStore } from '$electron/interface';
import type { ElectronErrorLog as ElectronErrorLogTable } from '$electron/interface';

export function catchError(
aresStore: ReturnType<typeof useAresStore>,
appNotificationsStore: ReturnType<typeof useAppNotificationsStore>,
ElectronErrorLog: typeof ElectronErrorLogTable
) {
const { notifyOnError } = storeToRefs(appNotificationsStore);
return async function(err: unknown) {
if (!(err instanceof Error)) return;
try {
const errorLog: ElectronErrorLogBase = {
const errorLog: OmitOptionalErrorLogProps<ElectronErrorLogType> = {
name: err.name,
message: err.message,
stack: isString(err.stack) ? err.stack : null,
stack: isString(err.stack) ? err.stack : err.message,
time: Date.now(),
ares: app.getVersion(),
chrome: process.versions.chrome,
electron: process.versions.electron,
tribal: aresStore.majorVersion,
locale: aresStore.locale
tribal: process.env.TRIBAL_WARS_VERSION ?? 'unknown',
locale: process.env.TRIBAL_WARS_LOCALE ?? 'unknown'
};

await sequelize.transaction(async (transaction) => {
Expand Down
2 changes: 1 addition & 1 deletion electron/interface/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const aliasArgs = [
] as const;

// ERROS
MainProcessError.catch = catchError(useAresStore(), useAppNotificationsStore(), ElectronErrorLog);
MainProcessError.catch = catchError(useAppNotificationsStore(), ElectronErrorLog);

// WATCHERS
// Essas funções retornam outras funções, que, por sua vez, são usadas como callbacks.
Expand Down
1 change: 1 addition & 0 deletions electron/utils/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { GameRegion } from '$types/game';
export const getMainViewWebContents = () => {
const id = Number.parseIntStrict(process.env.MAIN_VIEW_WEB_CONTENTS_ID ?? '');
const mainViewWebContents = webContents.fromId(id);
if (!mainViewWebContents) throw new BrowserViewError('Could not get main view web contents.');
return mainViewWebContents;
};

Expand Down
6 changes: 6 additions & 0 deletions global/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ export const enum Kronos {
Month = 30 * Day
};

export const enum ErrorLogFile {
All = 'error.log',
ChildProcess = 'child-process-error.log',
Uncaught = 'uncaught-error.log'
};

// Jogo.
export const resources = ['wood', 'stone', 'iron'] as const;
export const farmUnits = ['spear', 'sword', 'axe', 'archer', 'spy', 'light', 'marcher', 'heavy', 'knight'] as const;
Expand Down
16 changes: 16 additions & 0 deletions global/error.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
import type { AllErrorLogTypes } from '$types/error';

export class AresError extends Error {
declare public static catch: (err: unknown) => Promise<void>;

constructor(message: string) {
super(message);
this.name = 'AresError';
};

public static generateLogContent(err: AllErrorLogTypes | AllErrorLogTypes[]) {
const errors = Array.isArray(err) ? err : [err];

let content = '';
for (const error of errors) {
const date = new Date(error.time).toLocaleString('pt-br');
content += `${date}\nAres: ${error.ares} Electron: ${error.electron} Chrome: ${error.chrome}\n`;
content += `Tribal Wars: ${error.tribal ?? 'unknown'} Locale: ${error.locale ?? 'unknown'}\n`;
content += `${error.stack ?? error.message}\n\n`;
};

return content;
};
};
21 changes: 19 additions & 2 deletions modules/assets/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,19 @@ html {
user-select: none;
}

.module-content {
@mixin module-tabbed-view-wrapper($margin-bottom: 0) {
position: absolute;
top: 30px;
bottom: 0;
width: 100%;
padding-left: 0.3em;
padding-right: 0.3em;
margin-top: 0.5em;
overflow: hidden;
user-select: none;

@if $margin-bottom > 0 {
margin-bottom: $margin-bottom;
}
}

.module-view {
Expand All @@ -35,4 +38,18 @@ html {
overflow-x: hidden;
overflow-y: auto;
user-select: none;
}

.config-divider {
margin-top: 1em !important;
margin-bottom: 0.5em !important;
}

.config-divider:first-of-type {
margin-top: 0.3em !important;
}

.error-log {
margin-bottom: 0.5em;
user-select: text;
}
6 changes: 1 addition & 5 deletions modules/components/ConfigPlunderGridAttack.vue
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,4 @@ const blindAttackOptions = [
</NGridItem>
</NGrid>
</div>
</template>

<style scoped>

</style>
</template>
Loading