Centralize log file and refactor desktop logging
- Migrate to `electron-log` v5.X.X, centralizing log files to adhere to best-practices. - Add critical event logging in the log file. - Replace `ElectronLog` type with `LogFunctions` for better abstraction. - Unify log handling in `desktop-runtime-error` by removing `renderer.log` due to `electron-log` v5 changes. - Update and extend logger interfaces, removing 'I' prefix and adding common log levels to abstract `electron-log` completely. - Move logger interfaces to the application layer as it's cross-cutting concern, meanwhile keeping the implementations in the infrastructure layer. - Introduce `useLogger` hook for easier logging in Vue components. - Simplify `WindowVariables` by removing nullable properties. - Improve documentation to clearly differentiate between desktop and web versions, outlining specific features of each.
This commit is contained in:
@@ -124,9 +124,9 @@
|
||||
- 🌍️ **Online**: [https://privacy.sexy](https://privacy.sexy).
|
||||
- 🖥️ **Offline**: Download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.8/privacy.sexy-Setup-0.12.8.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.8/privacy.sexy-0.12.8.dmg), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.8/privacy.sexy-0.12.8.AppImage). For more options, see [here](#additional-install-options).
|
||||
|
||||
Online version does not require to run any software on your computer. Offline version has more functions such as running the scripts directly.
|
||||
For a detailed comparison of features between the desktop and web versions of privacy.sexy, see [Desktop vs. Web Features](./docs/desktop-vs-web-features.md).
|
||||
|
||||
💡 You should apply your configuration from time to time (more than once). It would strengthen your privacy and security control because privacy.sexy and its scripts get better and stronger in every new version.
|
||||
💡 Regularly applying your configuration with privacy.sexy is recommended, especially after each new release and major operating system updates. Each version updates scripts to enhance stability, privacy, and security.
|
||||
|
||||
[](https://privacy.sexy)
|
||||
|
||||
|
||||
36
docs/desktop-vs-web-features.md
Normal file
36
docs/desktop-vs-web-features.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Desktop vs. Web Features
|
||||
|
||||
This table outlines the differences between the desktop and web versions of `privacy.sexy`.
|
||||
|
||||
| Feature | Desktop | Web |
|
||||
| ------- |---------|-----|
|
||||
| [Usage without installation](#usage-without-installation) | 🔴 Not available | 🟢 Available |
|
||||
| [Offline usage](#offline-usage) | 🟢 Available | 🟡 Partially available |
|
||||
| [Auto-updates](#auto-updates) | 🟢 Available | 🟢 Available |
|
||||
| [Logging](#logging) | 🟢 Available | 🔴 Not available |
|
||||
| [Script execution](#script-execution) | 🟢 Available | 🔴 Not available |
|
||||
|
||||
## Feature Descriptions
|
||||
|
||||
### Usage without installation
|
||||
|
||||
The web version can be used directly in a browser without any installation, whereas the desktop version requires downloading and installing the software.
|
||||
|
||||
> **Note for Linux:** For Linux users, privacy.sexy is available as an AppImage, which is a portable format that does not require traditional installation. This means Linux users can use the desktop version without installation, similar to the web version.
|
||||
|
||||
### Offline usage
|
||||
|
||||
Once loaded, the web version can be used offline. The desktop version inherently supports offline usage.
|
||||
|
||||
### Auto-updates
|
||||
|
||||
Both versions automatically update to ensure you have the latest features and security enhancements.
|
||||
|
||||
### Logging
|
||||
|
||||
The desktop version supports logging of activities to aid in troubleshooting. This feature is not available in the web version.
|
||||
|
||||
### Script execution
|
||||
|
||||
Direct execution of scripts is possible in the desktop version, offering a more integrated experience.
|
||||
This functionality is not present in the web version due to browser limitations.
|
||||
19
package-lock.json
generated
19
package-lock.json
generated
@@ -6,14 +6,14 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "privacy.sexy",
|
||||
"version": "0.12.7",
|
||||
"version": "0.12.8",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@floating-ui/vue": "^1.0.2",
|
||||
"@juggle/resize-observer": "^3.4.0",
|
||||
"ace-builds": "^1.30.0",
|
||||
"cross-fetch": "^4.0.0",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-log": "^5.0.1",
|
||||
"electron-progressbar": "^2.1.0",
|
||||
"electron-updater": "^6.1.4",
|
||||
"file-saver": "^2.0.5",
|
||||
@@ -6699,9 +6699,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-log": {
|
||||
"version": "4.4.8",
|
||||
"resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.4.8.tgz",
|
||||
"integrity": "sha512-QQ4GvrXO+HkgqqEOYbi+DHL7hj5JM+nHi/j+qrN9zeeXVKy8ZABgbu4CnG+BBqDZ2+tbeq9tUC4DZfIWFU5AZA=="
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.0.1.tgz",
|
||||
"integrity": "sha512-x4wnwHg00h/onWQgjmvcdLV7Mrd9TZjxNs8LmXVpqvANDf4FsSs5wLlzOykWLcaFzR3+5hdVEQ8ctmrUxgHlPA==",
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-progressbar": {
|
||||
"version": "2.1.0",
|
||||
@@ -24483,9 +24486,9 @@
|
||||
}
|
||||
},
|
||||
"electron-log": {
|
||||
"version": "4.4.8",
|
||||
"resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.4.8.tgz",
|
||||
"integrity": "sha512-QQ4GvrXO+HkgqqEOYbi+DHL7hj5JM+nHi/j+qrN9zeeXVKy8ZABgbu4CnG+BBqDZ2+tbeq9tUC4DZfIWFU5AZA=="
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.0.1.tgz",
|
||||
"integrity": "sha512-x4wnwHg00h/onWQgjmvcdLV7Mrd9TZjxNs8LmXVpqvANDf4FsSs5wLlzOykWLcaFzR3+5hdVEQ8ctmrUxgHlPA=="
|
||||
},
|
||||
"electron-progressbar": {
|
||||
"version": "2.1.0",
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"@juggle/resize-observer": "^3.4.0",
|
||||
"ace-builds": "^1.30.0",
|
||||
"cross-fetch": "^4.0.0",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-log": "^5.0.1",
|
||||
"electron-progressbar": "^2.1.0",
|
||||
"electron-updater": "^6.1.4",
|
||||
"file-saver": "^2.0.5",
|
||||
|
||||
6
src/application/Common/Log/Logger.ts
Normal file
6
src/application/Common/Log/Logger.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface Logger {
|
||||
info(...params: unknown[]): void;
|
||||
warn(...params: unknown[]): void;
|
||||
error(...params: unknown[]): void;
|
||||
debug(...params: unknown[]): void;
|
||||
}
|
||||
5
src/application/Common/Log/LoggerFactory.ts
Normal file
5
src/application/Common/Log/LoggerFactory.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Logger } from '@/application/Common/Log/Logger';
|
||||
|
||||
export interface LoggerFactory {
|
||||
readonly logger: Logger;
|
||||
}
|
||||
@@ -1,17 +1,32 @@
|
||||
import { ILogger } from './ILogger';
|
||||
import { Logger } from '@/application/Common/Log/Logger';
|
||||
|
||||
export class ConsoleLogger implements ILogger {
|
||||
constructor(private readonly consoleProxy: Partial<Console> = console) {
|
||||
export class ConsoleLogger implements Logger {
|
||||
constructor(private readonly consoleProxy: ConsoleLogFunctions = globalThis.console) {
|
||||
if (!consoleProxy) { // do not trust strictNullChecks for global objects
|
||||
throw new Error('missing console');
|
||||
}
|
||||
}
|
||||
|
||||
public info(...params: unknown[]): void {
|
||||
const logFunction = this.consoleProxy?.info;
|
||||
if (!logFunction) {
|
||||
throw new Error('missing "info" function');
|
||||
}
|
||||
logFunction.call(this.consoleProxy, ...params);
|
||||
this.consoleProxy.info(...params);
|
||||
}
|
||||
|
||||
public warn(...params: unknown[]): void {
|
||||
this.consoleProxy.warn(...params);
|
||||
}
|
||||
|
||||
public error(...params: unknown[]): void {
|
||||
this.consoleProxy.error(...params);
|
||||
}
|
||||
|
||||
public debug(...params: unknown[]): void {
|
||||
this.consoleProxy.debug(...params);
|
||||
}
|
||||
}
|
||||
|
||||
interface ConsoleLogFunctions extends Partial<Console> {
|
||||
readonly info: Console['info'];
|
||||
readonly warn: Console['warn'];
|
||||
readonly error: Console['error'];
|
||||
readonly debug: Console['debug'];
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { ElectronLog } from 'electron-log';
|
||||
import { ILogger } from './ILogger';
|
||||
import log from 'electron-log/main';
|
||||
import { Logger } from '@/application/Common/Log/Logger';
|
||||
import type { LogFunctions } from 'electron-log';
|
||||
|
||||
// Using plain-function rather than class so it can be used in Electron's context-bridging.
|
||||
export function createElectronLogger(logger: Partial<ElectronLog>): ILogger {
|
||||
if (!logger) {
|
||||
throw new Error('missing logger');
|
||||
}
|
||||
export function createElectronLogger(logger: LogFunctions = log): Logger {
|
||||
return {
|
||||
info: (...params) => {
|
||||
if (!logger.info) {
|
||||
throw new Error('missing "info" function');
|
||||
}
|
||||
logger.info(...params);
|
||||
},
|
||||
info: (...params) => logger.info(...params),
|
||||
debug: (...params) => logger.debug(...params),
|
||||
warn: (...params) => logger.warn(...params),
|
||||
error: (...params) => logger.error(...params),
|
||||
};
|
||||
}
|
||||
|
||||
export const ElectronLogger = createElectronLogger();
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export interface ILogger {
|
||||
info (...params: unknown[]): void;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { ILogger } from './ILogger';
|
||||
|
||||
export interface ILoggerFactory {
|
||||
readonly logger: ILogger;
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
import { ILogger } from './ILogger';
|
||||
import { Logger } from '@/application/Common/Log/Logger';
|
||||
|
||||
export class NoopLogger implements ILogger {
|
||||
export class NoopLogger implements Logger {
|
||||
public info(): void { /* NOOP */ }
|
||||
|
||||
public warn(): void { /* NOOP */ }
|
||||
|
||||
public error(): void { /* NOOP */ }
|
||||
|
||||
public debug(): void { /* NOOP */ }
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Logger } from '@/application/Common/Log/Logger';
|
||||
import { WindowVariables } from '../WindowVariables/WindowVariables';
|
||||
import { ILogger } from './ILogger';
|
||||
|
||||
export class WindowInjectedLogger implements ILogger {
|
||||
private readonly logger: ILogger;
|
||||
export class WindowInjectedLogger implements Logger {
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(windowVariables: WindowVariables | undefined | null = window) {
|
||||
if (!windowVariables) { // do not trust strict null checks for global objects
|
||||
@@ -17,4 +17,16 @@ export class WindowInjectedLogger implements ILogger {
|
||||
public info(...params: unknown[]): void {
|
||||
this.logger.info(...params);
|
||||
}
|
||||
|
||||
public warn(...params: unknown[]): void {
|
||||
this.logger.warn(...params);
|
||||
}
|
||||
|
||||
public debug(...params: unknown[]): void {
|
||||
this.logger.debug(...params);
|
||||
}
|
||||
|
||||
public error(...params: unknown[]): void {
|
||||
this.logger.error(...params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { ISystemOperations } from '@/infrastructure/SystemOperations/ISystemOperations';
|
||||
import { ILogger } from '@/infrastructure/Log/ILogger';
|
||||
import { Logger } from '@/application/Common/Log/Logger';
|
||||
|
||||
/* Primary entry point for platform-specific injections */
|
||||
export interface WindowVariables {
|
||||
readonly isDesktop?: boolean;
|
||||
readonly isDesktop: boolean;
|
||||
readonly system?: ISystemOperations;
|
||||
readonly os?: OperatingSystem;
|
||||
readonly log?: ILogger;
|
||||
readonly log: Logger;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { RuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironment';
|
||||
import { IRuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/IRuntimeEnvironment';
|
||||
import { ConsoleLogger } from '@/infrastructure/Log/ConsoleLogger';
|
||||
import { ILogger } from '@/infrastructure/Log/ILogger';
|
||||
import { ILoggerFactory } from '@/infrastructure/Log/ILoggerFactory';
|
||||
import { Logger } from '@/application/Common/Log/Logger';
|
||||
import { LoggerFactory } from '@/application/Common/Log/LoggerFactory';
|
||||
import { NoopLogger } from '@/infrastructure/Log/NoopLogger';
|
||||
import { WindowInjectedLogger } from '@/infrastructure/Log/WindowInjectedLogger';
|
||||
|
||||
export class ClientLoggerFactory implements ILoggerFactory {
|
||||
public static readonly Current: ILoggerFactory = new ClientLoggerFactory();
|
||||
export class ClientLoggerFactory implements LoggerFactory {
|
||||
public static readonly Current: LoggerFactory = new ClientLoggerFactory();
|
||||
|
||||
public readonly logger: ILogger;
|
||||
public readonly logger: Logger;
|
||||
|
||||
protected constructor(environment: IRuntimeEnvironment = RuntimeEnvironment.CurrentEnvironment) {
|
||||
if (environment.isDesktop) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from '@/presentation/injectionSymbols';
|
||||
import { PropertyKeys } from '@/TypeHelpers';
|
||||
import { useUserSelectionState } from '@/presentation/components/Shared/Hooks/UseUserSelectionState';
|
||||
import { useLogger } from '@/presentation/components/Shared/Hooks/UseLogger';
|
||||
|
||||
export function provideDependencies(
|
||||
context: IApplicationContext,
|
||||
@@ -57,6 +58,10 @@ export function provideDependencies(
|
||||
return useUserSelectionState(state, events);
|
||||
},
|
||||
),
|
||||
useLogger: (di) => di.provide(
|
||||
InjectionKeys.useLogger,
|
||||
useLogger,
|
||||
),
|
||||
};
|
||||
registerAll(Object.values(resolvers), api);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { ILogger } from '@/infrastructure/Log/ILogger';
|
||||
import { Logger } from '@/application/Common/Log/Logger';
|
||||
import { Bootstrapper } from '../Bootstrapper';
|
||||
import { ClientLoggerFactory } from '../ClientLoggerFactory';
|
||||
|
||||
export class AppInitializationLogger implements Bootstrapper {
|
||||
constructor(
|
||||
private readonly logger: ILogger = ClientLoggerFactory.Current.logger,
|
||||
private readonly logger: Logger = ClientLoggerFactory.Current.logger,
|
||||
) { }
|
||||
|
||||
public async bootstrap(): Promise<void> {
|
||||
|
||||
@@ -17,16 +17,18 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { injectKey } from '@/presentation/injectionSymbols';
|
||||
import { dumpNames } from './DumpNames';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const { log } = injectKey((keys) => keys.useLogger);
|
||||
const devActions: readonly DevAction[] = [
|
||||
{
|
||||
name: 'Log script/category names',
|
||||
handler: async () => {
|
||||
const names = await dumpNames();
|
||||
console.log(names);
|
||||
log.info(names);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
8
src/presentation/components/Shared/Hooks/UseLogger.ts
Normal file
8
src/presentation/components/Shared/Hooks/UseLogger.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { LoggerFactory } from '@/application/Common/Log/LoggerFactory';
|
||||
import { ClientLoggerFactory } from '@/presentation/bootstrapping/ClientLoggerFactory';
|
||||
|
||||
export function useLogger(factory: LoggerFactory = ClientLoggerFactory.Current) {
|
||||
return {
|
||||
log: factory.logger,
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { app, dialog } from 'electron';
|
||||
import { autoUpdater, UpdateInfo } from 'electron-updater';
|
||||
import { ProgressInfo } from 'electron-builder';
|
||||
import log from 'electron-log';
|
||||
import { ElectronLogger } from '@/infrastructure/Log/ElectronLogger';
|
||||
import { UpdateProgressBar } from './UpdateProgressBar';
|
||||
|
||||
export async function handleAutoUpdate() {
|
||||
@@ -23,11 +23,11 @@ function startHandlingUpdateProgress() {
|
||||
On macOS, download-progress event is not called.
|
||||
So the indeterminate progress will continue until download is finished.
|
||||
*/
|
||||
log.debug('@download-progress@\n', progress);
|
||||
ElectronLogger.debug('@download-progress@\n', progress);
|
||||
progressBar.showProgress(progress);
|
||||
});
|
||||
autoUpdater.on('update-downloaded', async (info: UpdateInfo) => {
|
||||
log.info('@update-downloaded@\n', info);
|
||||
ElectronLogger.info('@update-downloaded@\n', info);
|
||||
progressBar.close();
|
||||
await handleUpdateDownloaded();
|
||||
});
|
||||
|
||||
@@ -2,12 +2,12 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { app, dialog, shell } from 'electron';
|
||||
import { UpdateInfo } from 'electron-updater';
|
||||
import log from 'electron-log';
|
||||
import fetch from 'cross-fetch';
|
||||
import { ProjectInformation } from '@/domain/ProjectInformation';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { Version } from '@/domain/Version';
|
||||
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
|
||||
import { ElectronLogger } from '@/infrastructure/Log/ElectronLogger';
|
||||
import { UpdateProgressBar } from './UpdateProgressBar';
|
||||
|
||||
export function requiresManualUpdate(): boolean {
|
||||
@@ -64,16 +64,16 @@ async function askForVisitingWebsiteForManualUpdate(): Promise<ManualDownloadDia
|
||||
}
|
||||
|
||||
async function download(info: UpdateInfo, project: ProjectInformation) {
|
||||
log.info('Downloading update manually');
|
||||
ElectronLogger.info('Downloading update manually');
|
||||
const progressBar = new UpdateProgressBar();
|
||||
progressBar.showIndeterminateState();
|
||||
try {
|
||||
const filePath = `${path.dirname(app.getPath('temp'))}/privacy.sexy/${info.version}-installer.dmg`;
|
||||
const parentFolder = path.dirname(filePath);
|
||||
if (fs.existsSync(filePath)) {
|
||||
log.info('Update is already downloaded');
|
||||
ElectronLogger.info('Update is already downloaded');
|
||||
await fs.promises.unlink(filePath);
|
||||
log.info(`Deleted ${filePath}`);
|
||||
ElectronLogger.info(`Deleted ${filePath}`);
|
||||
} else {
|
||||
await fs.promises.mkdir(parentFolder, { recursive: true });
|
||||
}
|
||||
@@ -99,20 +99,20 @@ async function downloadFileWithProgress(
|
||||
progressHandler: ProgressCallback,
|
||||
) {
|
||||
// We don't download through autoUpdater as it cannot download DMG but requires distributing ZIP
|
||||
log.info(`Fetching ${url}`);
|
||||
ElectronLogger.info(`Fetching ${url}`);
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw Error(`Unable to download, server returned ${response.status} ${response.statusText}`);
|
||||
}
|
||||
const contentLengthString = response.headers.get('content-length');
|
||||
if (contentLengthString === null || contentLengthString === undefined) {
|
||||
log.error('Content-Length header is missing');
|
||||
ElectronLogger.error('Content-Length header is missing');
|
||||
}
|
||||
const contentLength = +(contentLengthString ?? 0);
|
||||
const writer = fs.createWriteStream(filePath);
|
||||
log.info(`Writing to ${filePath}, content length: ${contentLength}`);
|
||||
ElectronLogger.info(`Writing to ${filePath}, content length: ${contentLength}`);
|
||||
if (Number.isNaN(contentLength) || contentLength <= 0) {
|
||||
log.error('Unknown content-length', Array.from(response.headers.entries()));
|
||||
ElectronLogger.error('Unknown content-length', Array.from(response.headers.entries()));
|
||||
progressHandler = () => { /* do nothing */ };
|
||||
}
|
||||
const reader = getReader(response);
|
||||
@@ -137,9 +137,9 @@ async function streamWithProgress(
|
||||
receivedLength += chunk.length;
|
||||
const percentage = Math.floor((receivedLength / totalLength) * 100);
|
||||
progressHandler(percentage);
|
||||
log.debug(`Received ${receivedLength} of ${totalLength}`);
|
||||
ElectronLogger.debug(`Received ${receivedLength} of ${totalLength}`);
|
||||
}
|
||||
log.info('Downloaded successfully');
|
||||
ElectronLogger.info('Downloaded successfully');
|
||||
}
|
||||
|
||||
function getReader(response: Response): NodeJS.ReadableStream | undefined {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import ProgressBar from 'electron-progressbar';
|
||||
import { ProgressInfo } from 'electron-builder';
|
||||
import { app, BrowserWindow } from 'electron';
|
||||
import log from 'electron-log';
|
||||
import { ElectronLogger } from '@/infrastructure/Log/ElectronLogger';
|
||||
|
||||
export class UpdateProgressBar {
|
||||
private progressBar: ProgressBar | undefined;
|
||||
@@ -81,7 +81,7 @@ const progressBarFactory = {
|
||||
progressBar.detail = 'Download completed.';
|
||||
})
|
||||
.on('aborted', (value: number) => {
|
||||
log.info(`progress aborted... ${value}`);
|
||||
ElectronLogger.info(`Progress aborted... ${value}`);
|
||||
})
|
||||
.on('progress', (value: number) => {
|
||||
progressBar.detail = `${value}% ...`;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { autoUpdater, UpdateInfo } from 'electron-updater';
|
||||
import log from 'electron-log';
|
||||
import { ElectronLogger } from '@/infrastructure/Log/ElectronLogger';
|
||||
import { handleManualUpdate, requiresManualUpdate } from './ManualUpdater';
|
||||
import { handleAutoUpdate } from './AutoUpdater';
|
||||
|
||||
@@ -8,21 +8,21 @@ interface IUpdater {
|
||||
}
|
||||
|
||||
export function setupAutoUpdater(): IUpdater {
|
||||
autoUpdater.logger = log;
|
||||
autoUpdater.logger = ElectronLogger;
|
||||
|
||||
// Disable autodownloads because "checking" and "downloading" are handled separately based on the
|
||||
// current platform and user's choice.
|
||||
autoUpdater.autoDownload = false;
|
||||
|
||||
autoUpdater.on('error', (error: Error) => {
|
||||
log.error('@error@\n', error);
|
||||
ElectronLogger.error('@error@\n', error);
|
||||
});
|
||||
|
||||
let isAlreadyHandled = false;
|
||||
autoUpdater.on('update-available', async (info: UpdateInfo) => {
|
||||
log.info('@update-available@\n', info);
|
||||
ElectronLogger.info('@update-available@\n', info);
|
||||
if (isAlreadyHandled) {
|
||||
log.info('Available updates is already handled');
|
||||
ElectronLogger.info('Available updates is already handled');
|
||||
return;
|
||||
}
|
||||
isAlreadyHandled = true;
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
import {
|
||||
app, protocol, BrowserWindow, shell, screen,
|
||||
} from 'electron';
|
||||
import log from 'electron-log/main';
|
||||
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer';
|
||||
import log from 'electron-log';
|
||||
import { validateRuntimeSanity } from '@/infrastructure/RuntimeSanity/SanityChecks';
|
||||
import { ElectronLogger } from '@/infrastructure/Log/ElectronLogger';
|
||||
import { setupAutoUpdater } from './Update/Updater';
|
||||
import {
|
||||
APP_ICON_PATH, PRELOADER_SCRIPT_PATH, RENDERER_HTML_PATH, RENDERER_URL,
|
||||
@@ -23,6 +24,7 @@ protocol.registerSchemesAsPrivileged([
|
||||
]);
|
||||
|
||||
setupLogger();
|
||||
|
||||
validateRuntimeSanity({
|
||||
// Metadata is used by manual updates.
|
||||
validateEnvironmentVariables: true,
|
||||
@@ -89,7 +91,7 @@ app.on('ready', async () => {
|
||||
try {
|
||||
await installExtension(VUEJS_DEVTOOLS);
|
||||
} catch (e) {
|
||||
log.error('Vue Devtools failed to install:', e.toString());
|
||||
ElectronLogger.error('Vue Devtools failed to install:', e.toString());
|
||||
}
|
||||
}
|
||||
createWindow();
|
||||
@@ -123,7 +125,7 @@ function loadApplication(window: BrowserWindow) {
|
||||
updater.checkForUpdates();
|
||||
}
|
||||
// Do not remove [WINDOW_INIT]; it's a marker used in tests.
|
||||
log.info('[WINDOW_INIT] Main window initialized and content loading.');
|
||||
ElectronLogger.info('[WINDOW_INIT] Main window initialized and content loading.');
|
||||
}
|
||||
|
||||
function configureExternalsUrlsOpenBrowser(window: BrowserWindow) {
|
||||
@@ -150,5 +152,7 @@ function getWindowSize(idealWidth: number, idealHeight: number) {
|
||||
}
|
||||
|
||||
function setupLogger(): void {
|
||||
// log.initialize(); ← We inject logger to renderer through preloader, this is not needed.
|
||||
log.transports.file.level = 'silly';
|
||||
log.eventLogger.startLogging();
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import log from 'electron-log';
|
||||
import { createNodeSystemOperations } from '@/infrastructure/SystemOperations/NodeSystemOperations';
|
||||
import { createElectronLogger } from '@/infrastructure/Log/ElectronLogger';
|
||||
import { ILogger } from '@/infrastructure/Log/ILogger';
|
||||
import { Logger } from '@/application/Common/Log/Logger';
|
||||
import { WindowVariables } from '@/infrastructure/WindowVariables/WindowVariables';
|
||||
import { convertPlatformToOs } from './NodeOsMapper';
|
||||
|
||||
export function provideWindowVariables(
|
||||
createSystem = createNodeSystemOperations,
|
||||
createLogger: () => ILogger = () => createElectronLogger(log),
|
||||
createLogger: () => Logger = () => createElectronLogger(),
|
||||
convertToOs = convertPlatformToOs,
|
||||
): WindowVariables {
|
||||
return {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// This file is used to securely expose Electron APIs to the application.
|
||||
|
||||
import { contextBridge } from 'electron';
|
||||
import log from 'electron-log';
|
||||
import { validateRuntimeSanity } from '@/infrastructure/RuntimeSanity/SanityChecks';
|
||||
import { ElectronLogger } from '@/infrastructure/Log/ElectronLogger';
|
||||
import { provideWindowVariables } from './WindowVariablesProvider';
|
||||
|
||||
validateRuntimeSanity({
|
||||
@@ -20,4 +20,4 @@ Object.entries(windowVariables).forEach(([key, value]) => {
|
||||
});
|
||||
|
||||
// Do not remove [PRELOAD_INIT]; it's a marker used in tests.
|
||||
log.info('[PRELOAD_INIT] Preload script successfully initialized and executed.');
|
||||
ElectronLogger.info('[PRELOAD_INIT] Preload script successfully initialized and executed.');
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { useClipboard } from '@/presentation/components/Shared/Hooks/Clipbo
|
||||
import type { useCurrentCode } from '@/presentation/components/Shared/Hooks/UseCurrentCode';
|
||||
import type { useAutoUnsubscribedEvents } from '@/presentation/components/Shared/Hooks/UseAutoUnsubscribedEvents';
|
||||
import type { useUserSelectionState } from '@/presentation/components/Shared/Hooks/UseUserSelectionState';
|
||||
import type { useLogger } from '@/presentation/components/Shared/Hooks/UseLogger';
|
||||
|
||||
export const InjectionKeys = {
|
||||
useCollectionState: defineTransientKey<ReturnType<typeof useCollectionState>>('useCollectionState'),
|
||||
@@ -15,6 +16,7 @@ export const InjectionKeys = {
|
||||
useClipboard: defineTransientKey<ReturnType<typeof useClipboard>>('useClipboard'),
|
||||
useCurrentCode: defineTransientKey<ReturnType<typeof useCurrentCode>>('useCurrentCode'),
|
||||
useUserSelectionState: defineTransientKey<ReturnType<typeof useUserSelectionState>>('useUserSelectionState'),
|
||||
useLogger: defineTransientKey<ReturnType<typeof useLogger>>('useLogger'),
|
||||
};
|
||||
|
||||
export interface InjectionKeyWithLifetime<T> {
|
||||
|
||||
@@ -5,33 +5,28 @@ import { exists } from '../utils/io';
|
||||
import { SupportedPlatform, CURRENT_PLATFORM } from '../utils/platform';
|
||||
import { getAppName } from '../utils/npm';
|
||||
|
||||
const LOG_FILE_NAMES = ['main', 'renderer'];
|
||||
|
||||
export async function clearAppLogFiles(
|
||||
projectDir: string,
|
||||
): Promise<void> {
|
||||
if (!projectDir) { throw new Error('missing project directory'); }
|
||||
await Promise.all(LOG_FILE_NAMES.map(async (logFileName) => {
|
||||
const logPath = await determineLogPath(projectDir, logFileName);
|
||||
if (!logPath || !await exists(logPath)) {
|
||||
log(`Skipping clearing logs, log file does not exist: ${logPath}.`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await unlink(logPath);
|
||||
log(`Successfully cleared the log file at: ${logPath}.`);
|
||||
} catch (error) {
|
||||
die(`Failed to clear the log file at: ${logPath}. Reason: ${error}`);
|
||||
}
|
||||
}));
|
||||
const logPath = await determineLogPath(projectDir);
|
||||
if (!logPath || !await exists(logPath)) {
|
||||
log(`Skipping clearing logs, log file does not exist: ${logPath}.`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await unlink(logPath);
|
||||
log(`Successfully cleared the log file at: ${logPath}.`);
|
||||
} catch (error) {
|
||||
die(`Failed to clear the log file at: ${logPath}. Reason: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function readAppLogFile(
|
||||
projectDir: string,
|
||||
logFileName: string,
|
||||
): Promise<AppLogFileResult> {
|
||||
if (!projectDir) { throw new Error('missing project directory'); }
|
||||
const logPath = await determineLogPath(projectDir, logFileName);
|
||||
const logPath = await determineLogPath(projectDir);
|
||||
if (!logPath || !await exists(logPath)) {
|
||||
log(`No log file at: ${logPath}`, LogLevel.Warn);
|
||||
return {
|
||||
@@ -52,10 +47,9 @@ interface AppLogFileResult {
|
||||
|
||||
async function determineLogPath(
|
||||
projectDir: string,
|
||||
logFileName: string,
|
||||
): Promise<string> {
|
||||
if (!projectDir) { throw new Error('missing project directory'); }
|
||||
if (!LOG_FILE_NAMES.includes(logFileName)) { throw new Error(`unknown log file name: ${logFileName}`); }
|
||||
const logFileName = 'main.log';
|
||||
const appName = await getAppName(projectDir);
|
||||
if (!appName) {
|
||||
return die('App name not found.');
|
||||
@@ -67,19 +61,19 @@ async function determineLogPath(
|
||||
if (!process.env.HOME) {
|
||||
throw new Error('HOME environment variable is not defined');
|
||||
}
|
||||
return join(process.env.HOME, 'Library', 'Logs', appName, `${logFileName}.log`);
|
||||
return join(process.env.HOME, 'Library', 'Logs', appName, logFileName);
|
||||
},
|
||||
[SupportedPlatform.Linux]: () => {
|
||||
if (!process.env.HOME) {
|
||||
throw new Error('HOME environment variable is not defined');
|
||||
}
|
||||
return join(process.env.HOME, '.config', appName, 'logs', `${logFileName}.log`);
|
||||
return join(process.env.HOME, '.config', appName, 'logs', logFileName);
|
||||
},
|
||||
[SupportedPlatform.Windows]: () => {
|
||||
if (!process.env.USERPROFILE) {
|
||||
throw new Error('USERPROFILE environment variable is not defined');
|
||||
}
|
||||
return join(process.env.USERPROFILE, 'AppData', 'Roaming', appName, 'logs', `${logFileName}.log`);
|
||||
return join(process.env.USERPROFILE, 'AppData', 'Roaming', appName, 'logs', logFileName);
|
||||
},
|
||||
};
|
||||
const logFilePath = logFilePaths[CURRENT_PLATFORM]?.();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { splitTextIntoLines, indentText, filterEmpty } from '../utils/text';
|
||||
import { splitTextIntoLines, indentText } from '../utils/text';
|
||||
import { log, die } from '../utils/log';
|
||||
import { readAppLogFile } from './app-logs';
|
||||
import { STDERR_IGNORE_PATTERNS } from './error-ignore-patterns';
|
||||
@@ -11,8 +11,6 @@ const EXPECTED_LOG_MARKERS = [
|
||||
'[APP_INIT]',
|
||||
];
|
||||
|
||||
type ProcessType = 'main' | 'renderer';
|
||||
|
||||
export async function checkForErrors(
|
||||
stderr: string,
|
||||
windowTitles: readonly string[],
|
||||
@@ -31,13 +29,11 @@ async function gatherErrors(
|
||||
projectDir: string,
|
||||
): Promise<ExecutionError[]> {
|
||||
if (!projectDir) { throw new Error('missing project directory'); }
|
||||
const { logFileContent: mainLogs, logFilePath: mainLogFile } = await readAppLogFile(projectDir, 'main');
|
||||
const { logFileContent: rendererLogs, logFilePath: rendererLogFile } = await readAppLogFile(projectDir, 'renderer');
|
||||
const allLogs = filterEmpty([mainLogs, rendererLogs, stderr]).join('\n');
|
||||
const { logFileContent: mainLogs, logFilePath: mainLogFile } = await readAppLogFile(projectDir);
|
||||
const allLogs = [mainLogs, stderr].filter(Boolean).join('\n');
|
||||
return [
|
||||
verifyStdErr(stderr),
|
||||
verifyApplicationLogsExist('main', mainLogs, mainLogFile),
|
||||
verifyApplicationLogsExist('renderer', rendererLogs, rendererLogFile),
|
||||
verifyApplicationLogsExist(mainLogs, mainLogFile),
|
||||
...EXPECTED_LOG_MARKERS.map(
|
||||
(marker) => verifyLogMarkerExistsInLogs(allLogs, marker),
|
||||
),
|
||||
@@ -72,13 +68,12 @@ function formatError(error: ExecutionError): string {
|
||||
}
|
||||
|
||||
function verifyApplicationLogsExist(
|
||||
processType: ProcessType,
|
||||
logContent: string | undefined,
|
||||
logFilePath: string,
|
||||
): ExecutionError | undefined {
|
||||
if (!logContent?.length) {
|
||||
return describeError(
|
||||
`Missing application (${processType}) logs`,
|
||||
'Missing application logs',
|
||||
'Application logs are empty not were not found.'
|
||||
+ `\nLog path: ${logFilePath}`,
|
||||
);
|
||||
|
||||
@@ -32,21 +32,6 @@ describe('ConsoleLogger', () => {
|
||||
expect(consoleMock.callHistory[0].args).to.deep.equal(expectedParams);
|
||||
});
|
||||
});
|
||||
describe('throws if log function is missing', () => {
|
||||
itEachLoggingMethod((functionName, testParameters) => {
|
||||
// arrange
|
||||
const expectedError = `missing "${functionName}" function`;
|
||||
const consoleMock = {} as Partial<Console>;
|
||||
consoleMock[functionName] = undefined;
|
||||
const logger = new ConsoleLogger(consoleMock);
|
||||
|
||||
// act
|
||||
const act = () => logger[functionName](...testParameters);
|
||||
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class MockConsole
|
||||
@@ -58,4 +43,25 @@ class MockConsole
|
||||
args,
|
||||
});
|
||||
}
|
||||
|
||||
public warn(...args: unknown[]) {
|
||||
this.registerMethodCall({
|
||||
methodName: 'warn',
|
||||
args,
|
||||
});
|
||||
}
|
||||
|
||||
public debug(...args: unknown[]) {
|
||||
this.registerMethodCall({
|
||||
methodName: 'debug',
|
||||
args,
|
||||
});
|
||||
}
|
||||
|
||||
public error(...args: unknown[]) {
|
||||
this.registerMethodCall({
|
||||
methodName: 'error',
|
||||
args,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,15 @@
|
||||
import { describe, expect } from 'vitest';
|
||||
import { ElectronLog } from 'electron-log';
|
||||
import { StubWithObservableMethodCalls } from '@tests/unit/shared/Stubs/StubWithObservableMethodCalls';
|
||||
import { createElectronLogger } from '@/infrastructure/Log/ElectronLogger';
|
||||
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { itEachLoggingMethod } from './LoggerTestRunner';
|
||||
import type { LogFunctions } from 'electron-log';
|
||||
|
||||
describe('ElectronLogger', () => {
|
||||
describe('throws if logger is missing', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing logger';
|
||||
const electronLog = absentValue as never;
|
||||
// act
|
||||
const act = () => createElectronLogger(electronLog);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}, { excludeUndefined: true });
|
||||
});
|
||||
describe('throws if log function is missing', () => {
|
||||
itEachLoggingMethod((functionName, testParameters) => {
|
||||
// arrange
|
||||
const expectedError = `missing "${functionName}" function`;
|
||||
const electronLogMock = {} as Partial<ElectronLog>;
|
||||
electronLogMock[functionName] = undefined;
|
||||
const logger = createElectronLogger(electronLogMock);
|
||||
|
||||
// act
|
||||
const act = () => logger[functionName](...testParameters);
|
||||
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('methods log the provided params', () => {
|
||||
itEachLoggingMethod((functionName, testParameters) => {
|
||||
// arrange
|
||||
const expectedParams = testParameters;
|
||||
const electronLogMock = new MockElectronLog();
|
||||
const electronLogMock = new ElectronLogStub();
|
||||
const logger = createElectronLogger(electronLogMock);
|
||||
|
||||
// act
|
||||
@@ -50,9 +23,51 @@ describe('ElectronLogger', () => {
|
||||
});
|
||||
});
|
||||
|
||||
class MockElectronLog
|
||||
extends StubWithObservableMethodCalls<ElectronLog>
|
||||
implements Partial<ElectronLog> {
|
||||
class ElectronLogStub
|
||||
extends StubWithObservableMethodCalls<LogFunctions>
|
||||
implements LogFunctions {
|
||||
public error(...args: unknown[]) {
|
||||
this.registerMethodCall({
|
||||
methodName: 'error',
|
||||
args,
|
||||
});
|
||||
}
|
||||
|
||||
public warn(...args: unknown[]) {
|
||||
this.registerMethodCall({
|
||||
methodName: 'warn',
|
||||
args,
|
||||
});
|
||||
}
|
||||
|
||||
public verbose(...args: unknown[]): void {
|
||||
this.registerMethodCall({
|
||||
methodName: 'verbose',
|
||||
args,
|
||||
});
|
||||
}
|
||||
|
||||
public debug(...args: unknown[]) {
|
||||
this.registerMethodCall({
|
||||
methodName: 'debug',
|
||||
args,
|
||||
});
|
||||
}
|
||||
|
||||
public silly(...args: unknown[]) {
|
||||
this.registerMethodCall({
|
||||
methodName: 'silly',
|
||||
args,
|
||||
});
|
||||
}
|
||||
|
||||
public log(...args: unknown[]) {
|
||||
this.registerMethodCall({
|
||||
methodName: 'log',
|
||||
args,
|
||||
});
|
||||
}
|
||||
|
||||
public info(...args: unknown[]) {
|
||||
this.registerMethodCall({
|
||||
methodName: 'info',
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
import { it } from 'vitest';
|
||||
import { FunctionKeys } from '@/TypeHelpers';
|
||||
import { ILogger } from '@/infrastructure/Log/ILogger';
|
||||
|
||||
type TestParameters = [string, number, { some: string }];
|
||||
import { Logger } from '@/application/Common/Log/Logger';
|
||||
|
||||
export function itEachLoggingMethod(
|
||||
handler: (
|
||||
functionName: keyof ILogger,
|
||||
testParameters: TestParameters,
|
||||
functionName: keyof Logger,
|
||||
testParameters: readonly unknown[]
|
||||
) => void,
|
||||
) {
|
||||
const testParameters: TestParameters = ['test', 123, { some: 'object' }];
|
||||
const loggerMethods: Array<FunctionKeys<ILogger>> = [
|
||||
'info',
|
||||
];
|
||||
loggerMethods
|
||||
.forEach((functionKey) => {
|
||||
const testScenarios: {
|
||||
readonly [FunctionName in keyof Logger]: Parameters<Logger[FunctionName]>;
|
||||
} = {
|
||||
info: ['single-string'],
|
||||
warn: ['with number', 123],
|
||||
debug: ['with simple object', { some: 'object' }],
|
||||
error: ['with error object', new Error('error')],
|
||||
};
|
||||
|
||||
Object.entries(testScenarios)
|
||||
.forEach(([functionKey, testParameters]) => {
|
||||
it(functionKey, () => {
|
||||
handler(functionKey, testParameters);
|
||||
handler(functionKey as keyof Logger, testParameters);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect } from 'vitest';
|
||||
import { NoopLogger } from '@/infrastructure/Log/NoopLogger';
|
||||
import { ILogger } from '@/infrastructure/Log/ILogger';
|
||||
import { Logger } from '@/application/Common/Log/Logger';
|
||||
import { itEachLoggingMethod } from './LoggerTestRunner';
|
||||
|
||||
describe('NoopLogger', () => {
|
||||
@@ -8,7 +8,7 @@ describe('NoopLogger', () => {
|
||||
itEachLoggingMethod((functionName, testParameters) => {
|
||||
// arrange
|
||||
const randomParams = testParameters;
|
||||
const logger: ILogger = new NoopLogger();
|
||||
const logger: Logger = new NoopLogger();
|
||||
|
||||
// act
|
||||
const act = () => logger[functionName](...randomParams);
|
||||
|
||||
@@ -177,10 +177,11 @@ function expectObjectOnDesktop<T>(key: keyof WindowVariables) {
|
||||
describe('does not object type when not on desktop', () => {
|
||||
itEachInvalidObjectValue((invalidObjectValue) => {
|
||||
// arrange
|
||||
const isOnDesktop = false;
|
||||
const invalidObject = invalidObjectValue as T;
|
||||
const input: WindowVariables = {
|
||||
...new WindowVariablesStub(),
|
||||
isDesktop: undefined,
|
||||
isDesktop: isOnDesktop,
|
||||
[key]: invalidObject,
|
||||
};
|
||||
// act
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
} from 'vitest';
|
||||
import { IRuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/IRuntimeEnvironment';
|
||||
import { ClientLoggerFactory } from '@/presentation/bootstrapping/ClientLoggerFactory';
|
||||
import { ILogger } from '@/infrastructure/Log/ILogger';
|
||||
import { Logger } from '@/application/Common/Log/Logger';
|
||||
import { WindowInjectedLogger } from '@/infrastructure/Log/WindowInjectedLogger';
|
||||
import { ConsoleLogger } from '@/infrastructure/Log/ConsoleLogger';
|
||||
import { NoopLogger } from '@/infrastructure/Log/NoopLogger';
|
||||
@@ -29,7 +29,7 @@ describe('ClientLoggerFactory', () => {
|
||||
});
|
||||
const testCases: Array<{
|
||||
readonly description: string,
|
||||
readonly expectedType: Constructible<ILogger>,
|
||||
readonly expectedType: Constructible<Logger>,
|
||||
readonly environment: IRuntimeEnvironment,
|
||||
}> = [
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@ describe('DependencyProvider', () => {
|
||||
useClipboard: createTransientTests(),
|
||||
useCurrentCode: createTransientTests(),
|
||||
useUserSelectionState: createTransientTests(),
|
||||
useLogger: createTransientTests(),
|
||||
};
|
||||
Object.entries(testCases).forEach(([key, runTests]) => {
|
||||
const registeredKey = InjectionKeys[key].key;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { useLogger } from '@/presentation/components/Shared/Hooks/UseLogger';
|
||||
import { LoggerStub } from '@tests/unit/shared/Stubs/LoggerStub';
|
||||
import { LoggerFactoryStub } from '@tests/unit/shared/Stubs/LoggerFactoryStub';
|
||||
|
||||
describe('UseLogger', () => {
|
||||
it('returns expected logger from factory', () => {
|
||||
// arrange
|
||||
const expectedLogger = new LoggerStub();
|
||||
const factory = new LoggerFactoryStub()
|
||||
.withLogger(expectedLogger);
|
||||
// act
|
||||
const { log: actualLogger } = useLogger(factory);
|
||||
// assert
|
||||
expect(actualLogger).to.equal(expectedLogger);
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,7 @@ import { provideWindowVariables } from '@/presentation/electron/preload/WindowVa
|
||||
import { SystemOperationsStub } from '@tests/unit/shared/Stubs/SystemOperationsStub';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { ISystemOperations } from '@/infrastructure/SystemOperations/ISystemOperations';
|
||||
import { ILogger } from '@/infrastructure/Log/ILogger';
|
||||
import { Logger } from '@/application/Common/Log/Logger';
|
||||
import { LoggerStub } from '@tests/unit/shared/Stubs/LoggerStub';
|
||||
|
||||
describe('WindowVariablesProvider', () => {
|
||||
@@ -55,7 +55,7 @@ class TestContext {
|
||||
|
||||
private os: OperatingSystem = OperatingSystem.Android;
|
||||
|
||||
private log: ILogger = new LoggerStub();
|
||||
private log: Logger = new LoggerStub();
|
||||
|
||||
public withSystem(system: ISystemOperations): this {
|
||||
this.system = system;
|
||||
@@ -67,7 +67,7 @@ class TestContext {
|
||||
return this;
|
||||
}
|
||||
|
||||
public withLogger(log: ILogger): this {
|
||||
public withLogger(log: Logger): this {
|
||||
this.log = log;
|
||||
return this;
|
||||
}
|
||||
|
||||
12
tests/unit/shared/Stubs/LoggerFactoryStub.ts
Normal file
12
tests/unit/shared/Stubs/LoggerFactoryStub.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Logger } from '@/application/Common/Log/Logger';
|
||||
import { LoggerFactory } from '@/application/Common/Log/LoggerFactory';
|
||||
import { LoggerStub } from './LoggerStub';
|
||||
|
||||
export class LoggerFactoryStub implements LoggerFactory {
|
||||
public logger: Logger = new LoggerStub();
|
||||
|
||||
public withLogger(logger: Logger): this {
|
||||
this.logger = logger;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,32 @@
|
||||
import { ILogger } from '@/infrastructure/Log/ILogger';
|
||||
import { Logger } from '@/application/Common/Log/Logger';
|
||||
import { StubWithObservableMethodCalls } from './StubWithObservableMethodCalls';
|
||||
|
||||
export class LoggerStub extends StubWithObservableMethodCalls<ILogger> implements ILogger {
|
||||
export class LoggerStub extends StubWithObservableMethodCalls<Logger> implements Logger {
|
||||
public warn(...params: unknown[]): void {
|
||||
this.registerMethodCall({
|
||||
methodName: 'warn',
|
||||
args: params,
|
||||
});
|
||||
}
|
||||
|
||||
public error(...params: unknown[]): void {
|
||||
this.registerMethodCall({
|
||||
methodName: 'error',
|
||||
args: params,
|
||||
});
|
||||
}
|
||||
|
||||
public debug(...params: unknown[]): void {
|
||||
this.registerMethodCall({
|
||||
methodName: 'debug',
|
||||
args: params,
|
||||
});
|
||||
}
|
||||
|
||||
public info(...params: unknown[]): void {
|
||||
this.registerMethodCall({
|
||||
methodName: 'info',
|
||||
args: params,
|
||||
});
|
||||
console.log(...params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { ILogger } from '@/infrastructure/Log/ILogger';
|
||||
import { Logger } from '@/application/Common/Log/Logger';
|
||||
import { ISystemOperations } from '@/infrastructure/SystemOperations/ISystemOperations';
|
||||
import { WindowVariables } from '@/infrastructure/WindowVariables/WindowVariables';
|
||||
import { SystemOperationsStub } from './SystemOperationsStub';
|
||||
@@ -8,18 +8,18 @@ import { LoggerStub } from './LoggerStub';
|
||||
export class WindowVariablesStub implements WindowVariables {
|
||||
public system?: ISystemOperations = new SystemOperationsStub();
|
||||
|
||||
public isDesktop? = false;
|
||||
public isDesktop = false;
|
||||
|
||||
public os?: OperatingSystem = OperatingSystem.BlackBerryOS;
|
||||
|
||||
public log?: ILogger = new LoggerStub();
|
||||
public log: Logger = new LoggerStub();
|
||||
|
||||
public withLog(log?: ILogger): this {
|
||||
public withLog(log: Logger): this {
|
||||
this.log = log;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withIsDesktop(value?: boolean): this {
|
||||
public withIsDesktop(value: boolean): this {
|
||||
this.isDesktop = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user