meta: change naming convention of interfaces and use importsNotUsedAsValues: 'error' in tsconfig for clearer imports

This commit is contained in:
Xymorot 2021-01-06 02:35:46 +01:00
parent a807c6f2be
commit 09dc14726d
83 changed files with 219 additions and 213 deletions

View File

@ -150,7 +150,7 @@
{ {
"selector": "interface", "selector": "interface",
"format": ["PascalCase"], "format": ["PascalCase"],
"prefix": ["I"] "suffix": ["Interface"]
} }
], ],
"@typescript-eslint/explicit-member-accessibility": "error", "@typescript-eslint/explicit-member-accessibility": "error",

View File

@ -4,21 +4,20 @@ import './main/core/install';
import { app } from 'electron'; import { app } from 'electron';
import { isDev } from './main/core/env'; import { isDev } from './main/core/env';
import { IAppWindow } from './main/modules/app-window/i-app-window'; import type { AppWindowInterface } from './main/modules/app-window/app-window-interface';
import { ILogger } from './main/modules/logger/i-logger';
/** /**
* have a read: https://github.com/nodejs/node/issues/20392, over 100 comments as of 2020-07-26 * have a read: https://github.com/nodejs/node/issues/20392, over 100 comments as of 2020-07-26
* https://nodejs.org/api/process.html#process_event_unhandledrejection * https://nodejs.org/api/process.html#process_event_unhandledrejection
*/ */
process.on('unhandledRejection', (reason) => { process.on('unhandledRejection', (reason) => {
const logger: ILogger = container.get('logger'); const logger: LoggerInterface = container.get('logger');
void logger.fatal(`Unhandled Rejection, see ${logger.getExceptionsLogFile()}`); void logger.fatal(`Unhandled Rejection, see ${logger.getExceptionsLogFile()}`);
throw reason; throw reason;
}); });
process.on('uncaughtException', (error) => { process.on('uncaughtException', (error) => {
const logger: ILogger = container.get('logger'); const logger: LoggerInterface = container.get('logger');
void logger.exception(error); void logger.exception(error);
if (isDev()) { if (isDev()) {
// eslint-disable-next-line no-console -- only for development purposes // eslint-disable-next-line no-console -- only for development purposes
@ -27,7 +26,7 @@ process.on('uncaughtException', (error) => {
}); });
async function createWindow(): Promise<void> { async function createWindow(): Promise<void> {
const appWindowMain: IAppWindow = container.get('app-window-main'); const appWindowMain: AppWindowInterface = container.get('app-window-main');
await appWindowMain.open(); await appWindowMain.open();
appWindowMain.window?.on('closed', () => { appWindowMain.window?.on('closed', () => {
@ -48,7 +47,7 @@ app.on('window-all-closed', () => {
app.on('activate', async () => { app.on('activate', async () => {
// On OS X it"s common to re-create a window in the app when the // On OS X it"s common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open. // dock icon is clicked and there are no other windows open.
const appWindowMain: IAppWindow = container.get('app-window-main'); const appWindowMain: AppWindowInterface = container.get('app-window-main');
if (appWindowMain.isClosed()) { if (appWindowMain.isClosed()) {
await createWindow(); await createWindow();
} }

View File

@ -1,6 +1,6 @@
import path from 'path'; import path from 'path';
import { Connection, createConnection as ormCreateConnection } from 'typeorm'; import { Connection, createConnection as ormCreateConnection } from 'typeorm';
import { BetterSqlite3ConnectionOptions } from 'typeorm/driver/better-sqlite3/BetterSqlite3ConnectionOptions'; import type { BetterSqlite3ConnectionOptions } from 'typeorm/driver/better-sqlite3/BetterSqlite3ConnectionOptions';
import { appPath } from './app-path'; import { appPath } from './app-path';
export enum Database { export enum Database {

View File

@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { Author } from './author'; import { Author } from './author';
@Entity() @Entity()
export class AuthorName implements IIdentifiableEntity, INameEntity { export class AuthorName implements IdentifiableEntityInterface, NameEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { AuthorRole } from './author-role'; import { AuthorRole } from './author-role';
@Entity() @Entity()
export class AuthorRoleName implements IIdentifiableEntity, INameEntity { export class AuthorRoleName implements IdentifiableEntityInterface, NameEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -7,7 +7,7 @@ import { WorkAuthor } from './work-author';
* Examples: story writing, drawing, animating, publishing, ... * Examples: story writing, drawing, animating, publishing, ...
*/ */
@Entity() @Entity()
export class AuthorRole implements IIdentifiableEntity, IMultiNamedEntity, IDescribableEntity { export class AuthorRole implements IdentifiableEntityInterface, MultiNamedEntityInterface, DescribableEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -6,7 +6,7 @@ import { WorkAuthor } from './work-author';
* This entity represents a single real-world entity, be it a person or named group of persons. * This entity represents a single real-world entity, be it a person or named group of persons.
*/ */
@Entity() @Entity()
export class Author implements IIdentifiableEntity, IMultiNamedEntity { export class Author implements IdentifiableEntityInterface, MultiNamedEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -8,7 +8,7 @@ import { WorkCharacter } from './work-character';
*/ */
@Entity() @Entity()
@PercentCheck('weight') @PercentCheck('weight')
export class CharacterTag implements IIdentifiableEntity, IWeightedEntity { export class CharacterTag implements IdentifiableEntityInterface, WeightedEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { Collection } from './collection'; import { Collection } from './collection';
@Entity() @Entity()
export class CollectionName implements IIdentifiableEntity, INameEntity { export class CollectionName implements IdentifiableEntityInterface, NameEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -7,7 +7,7 @@ import { Work } from './work';
* The main use case is chronological ordering. * The main use case is chronological ordering.
*/ */
@Entity() @Entity()
export class CollectionPart implements IIdentifiableEntity, IOrderableEntity { export class CollectionPart implements IdentifiableEntityInterface, OrderableEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -11,7 +11,7 @@ import { CollectionPart } from './collection-part';
* If authors of works see them as belonging together, they are a collection * If authors of works see them as belonging together, they are a collection
*/ */
@Entity() @Entity()
export class Collection implements IIdentifiableEntity, IMultiNamedEntity { export class Collection implements IdentifiableEntityInterface, MultiNamedEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -9,7 +9,7 @@ import { Work } from './work';
* Multiple works can have multiple copies (think of different scans of a physical work, or lossy compression). * Multiple works can have multiple copies (think of different scans of a physical work, or lossy compression).
*/ */
@Entity() @Entity()
export class Copy implements IIdentifiableEntity { export class Copy implements IdentifiableEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -1,7 +1,7 @@
/** /**
* Entities extending this one have a user-maintained description. * Entities extending this one have a user-maintained description.
*/ */
declare interface IDescribableEntity { interface DescribableEntityInterface {
/** /**
* a text describing this entity * a text describing this entity
*/ */

View File

@ -1,7 +1,7 @@
/** /**
* Entities implementing this interface build a hierarchy. * Entities implementing this interface build a hierarchy.
*/ */
declare interface IHierachicalEntity<T> { interface HierarchicalEntityInterface<T> {
/** /**
* parent entities * parent entities
*/ */

View File

@ -2,7 +2,7 @@
* Every database entity should implement this one. * Every database entity should implement this one.
* It does nothing more but guarantee there is an id column. * It does nothing more but guarantee there is an id column.
*/ */
declare interface IIdentifiableEntity { interface IdentifiableEntityInterface {
/** /**
* the entity id * the entity id
*/ */

View File

@ -8,7 +8,7 @@ import { WorkCharacter } from './work-character';
*/ */
@Entity() @Entity()
@PercentCheck('weight') @PercentCheck('weight')
export class InteractionTag implements IIdentifiableEntity, IWeightedEntity { export class InteractionTag implements IdentifiableEntityInterface, WeightedEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -1,7 +1,7 @@
/** /**
* Entities extending this interface can have multiple names. * Entities extending this interface can have multiple names.
*/ */
declare interface IMultiNamedEntity { interface MultiNamedEntityInterface {
/** /**
* the name which is displayed in the user interface * the name which is displayed in the user interface
*/ */
@ -10,5 +10,5 @@ declare interface IMultiNamedEntity {
/** /**
* other names for the entity * other names for the entity
*/ */
names: Promise<INameEntity[]>; names: Promise<NameEntityInterface[]>;
} }

View File

@ -1,7 +1,7 @@
/** /**
* This entity describes a single name of an entity with multiple names. * This entity describes a single name of an entity with multiple names.
*/ */
declare interface INameEntity { interface NameEntityInterface {
/** /**
* the name * the name
*/ */
@ -10,5 +10,5 @@ declare interface INameEntity {
/** /**
* the entity to which the names belong * the entity to which the names belong
*/ */
entity: Promise<IMultiNamedEntity>; entity: Promise<MultiNamedEntityInterface>;
} }

View File

@ -1,7 +1,7 @@
/** /**
* Entities implementing this interface can be ordered. * Entities implementing this interface can be ordered.
*/ */
declare interface IOrderableEntity { interface OrderableEntityInterface {
/** /**
* a lower number means a higher ordering * a lower number means a higher ordering
*/ */

View File

@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { Site } from './site'; import { Site } from './site';
@Entity() @Entity()
export class SiteName implements IIdentifiableEntity, INameEntity { export class SiteName implements IdentifiableEntityInterface, NameEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -6,7 +6,7 @@ import { Source } from './source';
* This non-user-maintained entity describes an online provider of works which can be scraped. * This non-user-maintained entity describes an online provider of works which can be scraped.
*/ */
@Entity() @Entity()
export class Site implements IIdentifiableEntity, IMultiNamedEntity { export class Site implements IdentifiableEntityInterface, MultiNamedEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -6,7 +6,7 @@ import { Site } from './site';
* This entity describes an external source of a copy, in most cases that is a website. * This entity describes an external source of a copy, in most cases that is a website.
*/ */
@Entity() @Entity()
export class Source implements IIdentifiableEntity { export class Source implements IdentifiableEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { Tag } from './tag'; import { Tag } from './tag';
@Entity() @Entity()
export class TagName implements IIdentifiableEntity, INameEntity { export class TagName implements IdentifiableEntityInterface, NameEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -11,7 +11,12 @@ import { WorkTag } from './work-tag';
* They can also be in a hierarchy * They can also be in a hierarchy
*/ */
@Entity() @Entity()
export class Tag implements IIdentifiableEntity, IMultiNamedEntity, IDescribableEntity, IHierachicalEntity<Tag> { export class Tag
implements
IdentifiableEntityInterface,
MultiNamedEntityInterface,
DescribableEntityInterface,
HierarchicalEntityInterface<Tag> {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { TransformationType } from './transformation-type'; import { TransformationType } from './transformation-type';
@Entity() @Entity()
export class TransformationTypeName implements IIdentifiableEntity, INameEntity { export class TransformationTypeName implements IdentifiableEntityInterface, NameEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -7,7 +7,8 @@ import { TransformationTypeName } from './transformation-type-name';
* Possible type: translation, decensor, collection, ... * Possible type: translation, decensor, collection, ...
*/ */
@Entity() @Entity()
export class TransformationType implements IIdentifiableEntity, IMultiNamedEntity, IDescribableEntity { export class TransformationType
implements IdentifiableEntityInterface, MultiNamedEntityInterface, DescribableEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -6,7 +6,7 @@ import { Work } from './work';
* This entity describes how one work is transformed to another. * This entity describes how one work is transformed to another.
*/ */
@Entity() @Entity()
export class Transformation implements IIdentifiableEntity, IOrderableEntity { export class Transformation implements IdentifiableEntityInterface, OrderableEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -1,7 +1,7 @@
/** /**
* An entity implementing this interface has a weight property. * An entity implementing this interface has a weight property.
*/ */
declare interface IWeightedEntity { interface WeightedEntityInterface {
/** /**
* the weight, mathematically a number (0,1], practically between (0,Number.MAX_SAFE_INTEGER] * the weight, mathematically a number (0,1], practically between (0,Number.MAX_SAFE_INTEGER]
* the weight can also be not not defined, null in the database * the weight can also be not not defined, null in the database

View File

@ -7,7 +7,7 @@ import { Work } from './work';
* This entity connects authors with their work and their role therein. * This entity connects authors with their work and their role therein.
*/ */
@Entity() @Entity()
export class WorkAuthor implements IIdentifiableEntity, IOrderableEntity { export class WorkAuthor implements IdentifiableEntityInterface, OrderableEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { WorkCharacter } from './work-character'; import { WorkCharacter } from './work-character';
@Entity() @Entity()
export class WorkCharacterName implements IIdentifiableEntity, INameEntity { export class WorkCharacterName implements IdentifiableEntityInterface, NameEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -10,7 +10,7 @@ import { WorldCharacter } from './world-character';
* The character can be original or based on one or more existing characters. * The character can be original or based on one or more existing characters.
*/ */
@Entity() @Entity()
export class WorkCharacter implements IIdentifiableEntity, IMultiNamedEntity { export class WorkCharacter implements IdentifiableEntityInterface, MultiNamedEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { Work } from './work'; import { Work } from './work';
@Entity() @Entity()
export class WorkName implements IIdentifiableEntity, INameEntity { export class WorkName implements IdentifiableEntityInterface, NameEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -8,7 +8,7 @@ import { Work } from './work';
*/ */
@Entity() @Entity()
@PercentCheck('weight') @PercentCheck('weight')
export class WorkTag implements IIdentifiableEntity, IWeightedEntity { export class WorkTag implements IdentifiableEntityInterface, WeightedEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -17,7 +17,7 @@ import { World } from './world';
*/ */
@Entity() @Entity()
@PercentCheck('rating') @PercentCheck('rating')
export class Work implements IIdentifiableEntity, IMultiNamedEntity { export class Work implements IdentifiableEntityInterface, MultiNamedEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { WorldCharacter } from './world-character'; import { WorldCharacter } from './world-character';
@Entity() @Entity()
export class WorldCharacterName implements IIdentifiableEntity, INameEntity { export class WorldCharacterName implements IdentifiableEntityInterface, NameEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -7,7 +7,8 @@ import { WorldCharacterName } from './world-character-name';
* This entity describes a canon character in a fictional world. * This entity describes a canon character in a fictional world.
*/ */
@Entity() @Entity()
export class WorldCharacter implements IIdentifiableEntity, IMultiNamedEntity, IHierachicalEntity<WorldCharacter> { export class WorldCharacter
implements IdentifiableEntityInterface, MultiNamedEntityInterface, HierarchicalEntityInterface<WorldCharacter> {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { World } from './world'; import { World } from './world';
@Entity() @Entity()
export class WorldName implements IIdentifiableEntity, INameEntity { export class WorldName implements IdentifiableEntityInterface, NameEntityInterface {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -7,7 +7,8 @@ import { WorldName } from './world-name';
* This entity describes a fictional world. * This entity describes a fictional world.
*/ */
@Entity() @Entity()
export class World implements IIdentifiableEntity, IMultiNamedEntity, IHierachicalEntity<World> { export class World
implements IdentifiableEntityInterface, MultiNamedEntityInterface, HierarchicalEntityInterface<World> {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
public id!: number; public id!: number;

View File

@ -1,4 +1,4 @@
import { MigrationInterface, QueryRunner } from 'typeorm'; import type { MigrationInterface, QueryRunner } from 'typeorm';
export class initialMigration1597705000730 implements MigrationInterface { export class initialMigration1597705000730 implements MigrationInterface {
name = 'initialMigration1597705000730'; name = 'initialMigration1597705000730';

View File

@ -1,4 +1,4 @@
import { MigrationInterface, QueryRunner } from 'typeorm'; import type { MigrationInterface, QueryRunner } from 'typeorm';
export class initialMigration1587511036078 implements MigrationInterface { export class initialMigration1587511036078 implements MigrationInterface {
name = 'initialMigration1587511036078'; name = 'initialMigration1587511036078';

View File

@ -1,6 +1,6 @@
import BrowserWindow = Electron.BrowserWindow; import { BrowserWindow } from 'electron';
export interface IAppWindow { interface AppWindowInterface {
window: BrowserWindow | null; window: BrowserWindow | null;
open(): Promise<void>; open(): Promise<void>;
close(force?: boolean): void; close(force?: boolean): void;

View File

@ -2,8 +2,8 @@ import { app, BrowserWindow, Event, LoadFileOptions, LoadURLOptions, NewWindowWe
import os from 'os'; import os from 'os';
import path from 'path'; import path from 'path';
import { isDev } from '../../core/env'; import { isDev } from '../../core/env';
import { ISessionHelper } from '../session/i-session-helper'; import type { SessionHelperInterface } from '../session/session-helper-interface';
import { IAppWindow } from './i-app-window'; import type { AppWindowInterface } from './app-window-interface';
import BrowserWindowConstructorOptions = Electron.BrowserWindowConstructorOptions; import BrowserWindowConstructorOptions = Electron.BrowserWindowConstructorOptions;
let defaultOptions: BrowserWindowConstructorOptions = { let defaultOptions: BrowserWindowConstructorOptions = {
@ -30,12 +30,12 @@ switch (os.platform()) {
break; break;
} }
export abstract class AppWindow implements IAppWindow { export abstract class AppWindow implements AppWindowInterface {
protected static default = {}; protected static default = {};
protected _window: BrowserWindow | null = null; protected _window: BrowserWindow | null = null;
protected readonly sessionHelper: ISessionHelper; protected readonly sessionHelper: SessionHelperInterface;
protected options: BrowserWindowConstructorOptions; protected options: BrowserWindowConstructorOptions;
@ -43,7 +43,11 @@ export abstract class AppWindow implements IAppWindow {
protected abstract loadOptions: LoadFileOptions | LoadURLOptions; protected abstract loadOptions: LoadFileOptions | LoadURLOptions;
protected constructor(sessionHelper: ISessionHelper, uri: string, options: BrowserWindowConstructorOptions = {}) { protected constructor(
sessionHelper: SessionHelperInterface,
uri: string,
options: BrowserWindowConstructorOptions = {}
) {
this.sessionHelper = sessionHelper; this.sessionHelper = sessionHelper;
this.options = { ...defaultOptions, ...options }; this.options = { ...defaultOptions, ...options };
this.uri = uri; this.uri = uri;
@ -85,7 +89,7 @@ export abstract class AppWindow implements IAppWindow {
return !this._window; return !this._window;
} }
protected getCsp(): IContentSecurityPolicy { protected getCsp(): Session.ContentSecurityPolicy {
return {}; return {};
} }

View File

@ -1,12 +1,12 @@
import { BrowserWindow, BrowserWindowConstructorOptions, LoadFileOptions } from 'electron'; import type { BrowserWindow, BrowserWindowConstructorOptions, LoadFileOptions } from 'electron';
import { ISessionHelper } from '../session/i-session-helper'; import type { SessionHelperInterface } from '../session/session-helper-interface';
import { AppWindow } from './app-window'; import { AppWindow } from './app-window';
export abstract class FileAppWindow extends AppWindow { export abstract class FileAppWindow extends AppWindow {
protected loadOptions: LoadFileOptions; protected loadOptions: LoadFileOptions;
protected constructor( protected constructor(
sessionHelper: ISessionHelper, sessionHelper: SessionHelperInterface,
uri: string, uri: string,
options: BrowserWindowConstructorOptions = {}, options: BrowserWindowConstructorOptions = {},
loadOptions: LoadFileOptions = {} loadOptions: LoadFileOptions = {}

View File

@ -1,8 +0,0 @@
import { IUrlAppWindow } from './i-url-app-window';
export interface ISiteAppWindow extends IUrlAppWindow {
/**
* @see IMutex.acquire
*/
acquireLock(): Promise<Mutex.ReleaseFunction>;
}

View File

@ -1,8 +0,0 @@
import { LoadURLOptions } from 'electron';
import { IAppWindow } from './i-app-window';
export interface IUrlAppWindow extends IAppWindow {
downloadUrlSafe(url: string, savePath: string, options?: LoadURLOptions): Promise<void>;
loadUrlSafe(url: string, options?: LoadURLOptions): Promise<void>;
}

View File

@ -1,11 +1,11 @@
import { injectable } from 'inversify'; import { injectable } from 'inversify';
import { inject } from '../../core/inject'; import { inject } from '../../core/inject';
import { ISessionHelper } from '../session/i-session-helper'; import type { SessionHelperInterface } from '../session/session-helper-interface';
import { FileAppWindow } from './file-app-window'; import { FileAppWindow } from './file-app-window';
@injectable() @injectable()
export class MainAppWindow extends FileAppWindow { export class MainAppWindow extends FileAppWindow {
public constructor(@inject('session-helper') sessionHelper: ISessionHelper) { public constructor(@inject('session-helper') sessionHelper: SessionHelperInterface) {
super(sessionHelper, 'frontend/index.html', { super(sessionHelper, 'frontend/index.html', {
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,

View File

@ -0,0 +1,8 @@
import type { UrlAppWindowInterface } from './url-app-window-interface';
interface SiteAppWindowInterface extends UrlAppWindowInterface {
/**
* @see IMutex.acquire
*/
acquireLock(): Promise<Mutex.ReleaseFunction>;
}

View File

@ -1,18 +1,18 @@
import { BrowserWindowConstructorOptions, LoadURLOptions } from 'electron'; import type { BrowserWindowConstructorOptions, LoadURLOptions } from 'electron';
import { SimpleMutex } from '../mutex/simple-mutex'; import { SimpleMutex } from '../mutex/simple-mutex';
import { ISessionHelper } from '../session/i-session-helper'; import type { SessionHelperInterface } from '../session/session-helper-interface';
import { ISiteAppWindow } from './i-site-app-window'; import type { SiteAppWindowInterface } from './site-app-window-interface';
import { UrlAppWindow } from './url-app-window'; import { UrlAppWindow } from './url-app-window';
/** /**
* This class represents an app window of a site which need to be crawled via the built-in chromium of Electron. * This class represents an app window of a site which need to be crawled via the built-in chromium of Electron.
* It offers a lock so that multiple calls do executed simultaneously on the same chromium window. * It offers a lock so that multiple calls do executed simultaneously on the same chromium window.
*/ */
export abstract class SiteAppWindow extends UrlAppWindow implements ISiteAppWindow { export abstract class SiteAppWindow extends UrlAppWindow implements SiteAppWindowInterface {
private windowLock: IMutex; private windowLock: MutexInterface;
protected constructor( protected constructor(
sessionHelper: ISessionHelper, sessionHelper: SessionHelperInterface,
uri: string, uri: string,
options: BrowserWindowConstructorOptions = {}, options: BrowserWindowConstructorOptions = {},
loadOptions: LoadURLOptions = {} loadOptions: LoadURLOptions = {}

View File

@ -0,0 +1,8 @@
import type { LoadURLOptions } from 'electron';
import type { AppWindowInterface } from './app-window-interface';
interface UrlAppWindowInterface extends AppWindowInterface {
downloadUrlSafe(url: string, savePath: string, options?: LoadURLOptions): Promise<void>;
loadUrlSafe(url: string, options?: LoadURLOptions): Promise<void>;
}

View File

@ -1,12 +1,12 @@
import { BrowserWindow, BrowserWindowConstructorOptions, LoadURLOptions } from 'electron'; import type { BrowserWindow, BrowserWindowConstructorOptions, LoadURLOptions } from 'electron';
import { promisify } from 'util'; import { promisify } from 'util';
import { ISessionHelper } from '../session/i-session-helper'; import type { SessionHelperInterface } from '../session/session-helper-interface';
import { AppWindow } from './app-window'; import { AppWindow } from './app-window';
import { IUrlAppWindow } from './i-url-app-window'; import type { UrlAppWindowInterface } from './url-app-window-interface';
import { WindowClosedError } from './window-closed-error'; import { WindowClosedError } from './window-closed-error';
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
export abstract class UrlAppWindow extends AppWindow implements IUrlAppWindow { export abstract class UrlAppWindow extends AppWindow implements UrlAppWindowInterface {
protected loadOptions: LoadURLOptions; protected loadOptions: LoadURLOptions;
/** /**
@ -32,7 +32,7 @@ export abstract class UrlAppWindow extends AppWindow implements IUrlAppWindow {
private loadWait: Promise<void> = Promise.resolve(); private loadWait: Promise<void> = Promise.resolve();
protected constructor( protected constructor(
sessionHelper: ISessionHelper, sessionHelper: SessionHelperInterface,
uri: string, uri: string,
options: BrowserWindowConstructorOptions = {}, options: BrowserWindowConstructorOptions = {},
loadOptions: LoadURLOptions = {} loadOptions: LoadURLOptions = {}

View File

@ -1,5 +1,5 @@
import { dialog, OpenDialogOptions } from 'electron'; import { dialog, OpenDialogOptions } from 'electron';
interface IDialog { interface DialogInterface {
selectFolder(options: OpenDialogOptions): ReturnType<typeof dialog.showOpenDialog>; selectFolder(options: OpenDialogOptions): ReturnType<typeof dialog.showOpenDialog>;
} }

View File

@ -1,14 +1,13 @@
import { dialog, OpenDialogOptions } from 'electron'; import { dialog, OpenDialogOptions } from 'electron';
import { injectable } from 'inversify'; import { injectable } from 'inversify';
import { inject } from '../../core/inject'; import { inject } from '../../core/inject';
import { II18nTranslator } from '../i18n/i-i18n-translator'; import type { DialogInterface } from './dialog-interface';
import { IDialog } from './i-dialog';
@injectable() @injectable()
export class Dialog implements IDialog { export class Dialog implements DialogInterface {
private readonly translator: II18nTranslator; private readonly translator: I18nTranslatorInterface;
public constructor(@inject('i18n-translator') translator: II18nTranslator) { public constructor(@inject('i18n-translator') translator: I18nTranslatorInterface) {
this.translator = translator; this.translator = translator;
} }

View File

@ -1,3 +0,0 @@
export interface II18nTranslator {
t(text: string): string;
}

View File

@ -0,0 +1,3 @@
interface I18nTranslatorInterface {
t(text: string): string;
}

View File

@ -1,8 +1,7 @@
import { injectable } from 'inversify'; import { injectable } from 'inversify';
import { II18nTranslator } from './i-i18n-translator';
@injectable() @injectable()
export class I18nTranslator implements II18nTranslator { export class I18nTranslator implements I18nTranslatorInterface {
public t(text: string): string { public t(text: string): string {
return text; return text;
} }

View File

@ -1,7 +1,7 @@
import { registerHandler } from '../ipc-server'; import { registerHandler } from '../ipc-server';
export function answer(channel: IpcChannel): IpcControllerMethodDecorator { export function answer(channel: IpcChannel): IpcControllerMethodDecorator {
return function (target: IIpcController, propertyKey): void { return function (target: IpcController, propertyKey): void {
registerHandler(channel, target, propertyKey); registerHandler(channel, target, propertyKey);
}; };
} }

View File

@ -1,12 +1,12 @@
import { ipcMain } from 'electron'; import { ipcMain } from 'electron';
import IpcMainEvent = Electron.IpcMainEvent; import IpcMainEvent = Electron.IpcMainEvent;
export function registerHandler(channel: IpcChannel, controller: IIpcController, handler: string): void { export function registerHandler(channel: IpcChannel, controller: IpcController, handler: string): void {
ipcMain.on(channel, (event: IpcMainEvent, payload: IIpcPayload) => { ipcMain.on(channel, (event: IpcMainEvent, payload: IpcPayload) => {
((controller.get() as unknown) as { [x: string]: IpcHandler }) ((controller.get() as unknown) as { [x: string]: IpcHandler })
[handler](payload.data) [handler](payload.data)
.then((result: unknown) => { .then((result: unknown) => {
const response: IIpcResponse = { const response: IpcResponse = {
id: payload.id, id: payload.id,
success: true, success: true,
data: result, data: result,
@ -14,7 +14,7 @@ export function registerHandler(channel: IpcChannel, controller: IIpcController,
event.reply(channel, response); event.reply(channel, response);
}) })
.catch((reason: Error) => { .catch((reason: Error) => {
const response: IIpcResponse = { const response: IpcResponse = {
id: payload.id, id: payload.id,
success: false, success: false,
error: reason.message, error: reason.message,

View File

@ -1,8 +1,8 @@
/** /**
* A Logger provides methods to save a developer message somewhere it can be retrieved * A Logger provides methods to save a developer message somewhere it can be retrieved
* @see ILogger.getLogFile * @see LoggerInterface.getLogFile
*/ */
export interface ILogger { interface LoggerInterface {
/** /**
* default logging method, the logging level needs to be specified * default logging method, the logging level needs to be specified
* @see LogLevel * @see LogLevel
@ -47,7 +47,7 @@ export interface ILogger {
/** /**
* logs the error, preferably with stack trace * logs the error, preferably with stack trace
* @see ILogger.getExceptionsLogFile * @see LoggerInterface.getExceptionsLogFile
*/ */
exception(error: Error): Promise<void>; exception(error: Error): Promise<void>;

View File

@ -1,11 +1,10 @@
import { injectable } from 'inversify'; import { injectable } from 'inversify';
import { ILogger } from './i-logger';
/** /**
* Mock of a logger, does not log anywhere * Mock of a logger, does not log anywhere
*/ */
@injectable() @injectable()
export class LoggerMock implements ILogger { export class LoggerMock implements LoggerInterface {
public getLogFile(): string { public getLogFile(): string {
return ''; return '';
} }

View File

@ -6,7 +6,6 @@ import fc from 'fast-check';
import fs from 'fs-extra'; import fs from 'fs-extra';
import { container } from '../../core/container'; import { container } from '../../core/container';
import { setDev } from '../../core/env.spec'; import { setDev } from '../../core/env.spec';
import { ILogger } from './i-logger';
import { LogLevel } from './log-level'; import { LogLevel } from './log-level';
chai.use(chaiFs); chai.use(chaiFs);
@ -25,7 +24,7 @@ describe('Logger Service', () => {
}); });
} }
function getDefaultLogReadLineInterface(logger: ILogger) { function getDefaultLogReadLineInterface(logger: LoggerInterface) {
return createInterface(fs.createReadStream(logger.getLogFile())); return createInterface(fs.createReadStream(logger.getLogFile()));
} }
@ -42,20 +41,20 @@ describe('Logger Service', () => {
const logLevelNumberArbitrary = fc.constantFrom(...logLevelsNumber); const logLevelNumberArbitrary = fc.constantFrom(...logLevelsNumber);
it('creates log files', () => { it('creates log files', () => {
const logger: ILogger = container.get('logger'); const logger: LoggerInterface = container.get('logger');
expect(logger.getLogFile()).path('log file is not created'); expect(logger.getLogFile()).path('log file is not created');
expect(logger.getExceptionsLogFile()).path('exception log file is not created'); expect(logger.getExceptionsLogFile()).path('exception log file is not created');
}); });
it('logs exceptions', async () => { it('logs exceptions', async () => {
const logger: ILogger = container.get('logger'); const logger: LoggerInterface = container.get('logger');
await logger.exception(new Error('this is an error')); await logger.exception(new Error('this is an error'));
}); });
it("default log file doesn't get bigger than 50KB @slow", async () => { it("default log file doesn't get bigger than 50KB @slow", async () => {
const logger: ILogger = container.get('logger'); const logger: LoggerInterface = container.get('logger');
let prevLogFileSize = (await fs.stat(logger.getLogFile())).size; let prevLogFileSize = (await fs.stat(logger.getLogFile())).size;
let minNumberOfLines = maxLogSize; let minNumberOfLines = maxLogSize;
@ -76,7 +75,7 @@ describe('Logger Service', () => {
}).timeout(15000); }).timeout(15000);
it("exception log file doesn't get bigger than 50KB @slow", async () => { it("exception log file doesn't get bigger than 50KB @slow", async () => {
const logger: ILogger = container.get('logger'); const logger: LoggerInterface = container.get('logger');
let prevLogFileSize = (await fs.stat(logger.getExceptionsLogFile())).size; let prevLogFileSize = (await fs.stat(logger.getExceptionsLogFile())).size;
let minNumberOfLines = maxLogSize; let minNumberOfLines = maxLogSize;
@ -97,7 +96,7 @@ describe('Logger Service', () => {
}).timeout(15000); }).timeout(15000);
it('logs different levels directly', () => { it('logs different levels directly', () => {
const logger: ILogger = container.get('logger'); const logger: LoggerInterface = container.get('logger');
return fc.assert( return fc.assert(
fc.asyncProperty(logLevelArbitrary as LogLevelArbitrary, fc.string(), async (logLevel, message) => { fc.asyncProperty(logLevelArbitrary as LogLevelArbitrary, fc.string(), async (logLevel, message) => {
@ -113,7 +112,7 @@ describe('Logger Service', () => {
}); });
it('logs different levels indirectly via the generic log function', () => { it('logs different levels indirectly via the generic log function', () => {
const logger: ILogger = container.get('logger'); const logger: LoggerInterface = container.get('logger');
return fc.assert( return fc.assert(
fc.asyncProperty(logLevelNumberArbitrary, fc.string(), async (logLevelNumber, message) => { fc.asyncProperty(logLevelNumberArbitrary, fc.string(), async (logLevelNumber, message) => {
@ -132,7 +131,7 @@ describe('Logger Service', () => {
}); });
it('logs debug only in dev mode', async () => { it('logs debug only in dev mode', async () => {
const logger: ILogger = container.get('logger'); const logger: LoggerInterface = container.get('logger');
setDev(); setDev();
await logger.debug('this is a development message'); await logger.debug('this is a development message');

View File

@ -4,7 +4,6 @@ import * as fs from 'fs-extra';
import { injectable } from 'inversify'; import { injectable } from 'inversify';
import { appPath } from '../../core/app-path'; import { appPath } from '../../core/app-path';
import { isDev } from '../../core/env'; import { isDev } from '../../core/env';
import { ILogger } from './i-logger';
import { LogLevel } from './log-level'; import { LogLevel } from './log-level';
const loggingDir = path.resolve(appPath, 'logs'); const loggingDir = path.resolve(appPath, 'logs');
@ -16,7 +15,7 @@ const maxLogSize = 50000;
* A logger not using winston to log to files in the appPath. * A logger not using winston to log to files in the appPath.
*/ */
@injectable() @injectable()
export class Logger implements ILogger { export class Logger implements LoggerInterface {
public constructor() { public constructor() {
fs.createFileSync(logFile); fs.createFileSync(logFile);
fs.createFileSync(exceptionLogFile); fs.createFileSync(exceptionLogFile);

View File

@ -3,7 +3,7 @@
* *
* Acquiring this lock returns a release function function (via promise) which needs to be called to release the lock again. * Acquiring this lock returns a release function function (via promise) which needs to be called to release the lock again.
*/ */
interface IMutex { interface MutexInterface {
/** /**
* acquires the lock and returns a Promise with the release function to be called when the lock shall be released. * acquires the lock and returns a Promise with the release function to be called when the lock shall be released.
* This release function needs to be called or the lock will never release and execution of subsequent consumers will not take place. * This release function needs to be called or the lock will never release and execution of subsequent consumers will not take place.

View File

@ -1,7 +1,7 @@
/** /**
* This class implements a simple mutex using a semaphore. * This class implements a simple mutex using a semaphore.
*/ */
export class SimpleMutex implements IMutex { export class SimpleMutex implements MutexInterface {
/** /**
* This queue is an array of promise resolve functions. * This queue is an array of promise resolve functions.
* Calling them signals the corresponding consumer that the lock is now free. * Calling them signals the corresponding consumer that the lock is now free.

View File

@ -1,5 +0,0 @@
import { ISiteAppWindow } from '../app-window/i-site-app-window';
export interface INhentaiAppWindow extends ISiteAppWindow {
getFavorites(): Promise<NodeJS.ReadableStream>;
}

View File

@ -1,3 +1,3 @@
export interface INhentaiApi { interface NhentaiApiInterface {
getFavorites(): Promise<NodeJS.ReadableStream>; getFavorites(): Promise<NodeJS.ReadableStream>;
} }

View File

@ -1,13 +1,12 @@
import { injectable } from 'inversify'; import { injectable } from 'inversify';
import { inject } from '../../core/inject'; import { inject } from '../../core/inject';
import { INhentaiApi } from './i-nhentai-api'; import type { NhentaiAppWindowInterface } from './nhentai-app-window-interface';
import { INhentaiAppWindow } from './i-nhentai-app-window';
@injectable() @injectable()
export class NhentaiApi implements INhentaiApi { export class NhentaiApi implements NhentaiApiInterface {
private readonly appWindow: INhentaiAppWindow; private readonly appWindow: NhentaiAppWindowInterface;
public constructor(@inject('nhentai-app-window') appWindow: INhentaiAppWindow) { public constructor(@inject('nhentai-app-window') appWindow: NhentaiAppWindowInterface) {
this.appWindow = appWindow; this.appWindow = appWindow;
} }

View File

@ -0,0 +1,5 @@
import type { SiteAppWindowInterface } from '../app-window/site-app-window-interface';
interface NhentaiAppWindowInterface extends SiteAppWindowInterface {
getFavorites(): Promise<NodeJS.ReadableStream>;
}

View File

@ -1,4 +1,4 @@
import { WebContents } from 'electron'; import type { WebContents } from 'electron';
import os from 'os'; import os from 'os';
import path from 'path'; import path from 'path';
import { Readable } from 'stream'; import { Readable } from 'stream';
@ -8,9 +8,8 @@ import { injectable } from 'inversify';
import { inject } from '../../core/inject'; import { inject } from '../../core/inject';
import { SiteAppWindow } from '../app-window/site-app-window'; import { SiteAppWindow } from '../app-window/site-app-window';
import { WindowClosedError } from '../app-window/window-closed-error'; import { WindowClosedError } from '../app-window/window-closed-error';
import { ISessionHelper } from '../session/i-session-helper'; import type { SessionHelperInterface } from '../session/session-helper-interface';
import { INhentaiAppWindow } from './i-nhentai-app-window'; import type { NhentaiAppWindowInterface } from './nhentai-app-window-interface';
import { IFavorite } from './nhentai';
import { import {
url as nhentaiUrl, url as nhentaiUrl,
hostname as nhentaiHostname, hostname as nhentaiHostname,
@ -25,8 +24,8 @@ import {
const waitInterval = 2000; const waitInterval = 2000;
@injectable() @injectable()
export class NhentaiAppWindow extends SiteAppWindow implements INhentaiAppWindow { export class NhentaiAppWindow extends SiteAppWindow implements NhentaiAppWindowInterface {
public constructor(@inject('session-helper') sessionHelper: ISessionHelper) { public constructor(@inject('session-helper') sessionHelper: SessionHelperInterface) {
super(sessionHelper, nhentaiUrl); super(sessionHelper, nhentaiUrl);
} }
@ -52,7 +51,7 @@ export class NhentaiAppWindow extends SiteAppWindow implements INhentaiAppWindow
} }
const readable = Readable.from( const readable = Readable.from(
(async function* (thisArg): AsyncGenerator<IFavorite, undefined> { (async function* (thisArg): AsyncGenerator<Nhentai.Favorite, undefined> {
for (let i = 0; i < bookUrls.length; i++) { for (let i = 0; i < bookUrls.length; i++) {
const bookUrl = bookUrls[i]; const bookUrl = bookUrls[i];
yield await thisArg.getBookTorrent(bookUrl); yield await thisArg.getBookTorrent(bookUrl);
@ -76,7 +75,7 @@ export class NhentaiAppWindow extends SiteAppWindow implements INhentaiAppWindow
} }
} }
protected getCsp(): IContentSecurityPolicy { protected getCsp(): Session.ContentSecurityPolicy {
return { return {
'default-src': ['nhentai.net'], 'default-src': ['nhentai.net'],
'script-src': ['nhentai.net', "'unsafe-eval'"], 'script-src': ['nhentai.net', "'unsafe-eval'"],
@ -163,7 +162,7 @@ export class NhentaiAppWindow extends SiteAppWindow implements INhentaiAppWindow
return; return;
} }
private async getBookTorrent(bookUrl: string): Promise<IFavorite> { private async getBookTorrent(bookUrl: string): Promise<Nhentai.Favorite> {
if (!this._window) { if (!this._window) {
throw new WindowClosedError(); throw new WindowClosedError();
} }

View File

@ -1,20 +1,17 @@
import path from 'path'; import path from 'path';
import { createWriteStream } from 'fs-extra'; import { createWriteStream } from 'fs-extra';
import { container } from '../../core/container'; import { container } from '../../core/container';
import { IDialog } from '../dialog/i-dialog'; import type { DialogInterface } from '../dialog/dialog-interface';
import { II18nTranslator } from '../i18n/i-i18n-translator';
import { answer } from '../ipc/annotations/answer'; import { answer } from '../ipc/annotations/answer';
import { INhentaiApi } from './i-nhentai-api';
import { IFavorite } from './nhentai';
export class NhentaiIpcController implements IIpcController { export class NhentaiIpcController implements IpcController {
private readonly nhentaiApi: INhentaiApi; private readonly nhentaiApi: NhentaiApiInterface;
private readonly translator: II18nTranslator; private readonly translator: I18nTranslatorInterface;
private readonly dialog: IDialog; private readonly dialog: DialogInterface;
private constructor(nhentaiApi: INhentaiApi, translator: II18nTranslator, dialog: IDialog) { private constructor(nhentaiApi: NhentaiApiInterface, translator: I18nTranslatorInterface, dialog: DialogInterface) {
this.nhentaiApi = nhentaiApi; this.nhentaiApi = nhentaiApi;
this.translator = translator; this.translator = translator;
this.dialog = dialog; this.dialog = dialog;
@ -33,7 +30,7 @@ export class NhentaiIpcController implements IIpcController {
const favoritesStream = await this.nhentaiApi.getFavorites(); const favoritesStream = await this.nhentaiApi.getFavorites();
return new Promise((resolve) => { return new Promise((resolve) => {
favoritesStream.on('data', (favorite: IFavorite) => { favoritesStream.on('data', (favorite: Nhentai.Favorite) => {
const writable = createWriteStream(path.resolve(result.filePaths[0], favorite.name)); const writable = createWriteStream(path.resolve(result.filePaths[0], favorite.name));
favorite.torrentFile.pipe(writable); favorite.torrentFile.pipe(writable);
}); });
@ -43,9 +40,9 @@ export class NhentaiIpcController implements IIpcController {
} }
public get(): NhentaiIpcController { public get(): NhentaiIpcController {
const nhentaiApi: INhentaiApi = container.get('nhentai-api'); const nhentaiApi: NhentaiApiInterface = container.get('nhentai-api');
const translator: II18nTranslator = container.get('i18n-translator'); const translator: I18nTranslatorInterface = container.get('i18n-translator');
const dialog: IDialog = container.get('dialog'); const dialog: DialogInterface = container.get('dialog');
return new NhentaiIpcController(nhentaiApi, translator, dialog); return new NhentaiIpcController(nhentaiApi, translator, dialog);
} }
} }

View File

@ -1,4 +1,6 @@
export interface IFavorite { declare namespace Nhentai {
name: string; type Favorite = {
torrentFile: NodeJS.ReadableStream; name: string;
torrentFile: NodeJS.ReadableStream;
};
} }

View File

@ -1,5 +0,0 @@
import { BrowserWindow } from 'electron';
export interface ISessionHelper {
setCsp(window: BrowserWindow, csp: IContentSecurityPolicy): void;
}

View File

@ -0,0 +1,5 @@
import { BrowserWindow } from 'electron';
interface SessionHelperInterface {
setCsp(window: BrowserWindow, csp: Session.ContentSecurityPolicy): void;
}

View File

@ -1,23 +1,26 @@
import { injectable } from 'inversify'; import { injectable } from 'inversify';
import { isDev } from '../../core/env'; import { isDev } from '../../core/env';
import { ISessionHelper } from './i-session-helper'; import type { SessionHelperInterface } from './session-helper-interface';
const defaultCsp: IContentSecurityPolicy = { const defaultCsp: Session.ContentSecurityPolicy = {
'default-src': ["'self'"], 'default-src': ["'self'"],
'style-src': ["'unsafe-inline'"], 'style-src': ["'unsafe-inline'"],
'object-src': ["'none'"], 'object-src': ["'none'"],
}; };
@injectable() @injectable()
export class SessionHelper implements ISessionHelper { export class SessionHelper implements SessionHelperInterface {
private static stringifyCspHeader(csp: IContentSecurityPolicy): string { private static stringifyCspHeader(csp: Session.ContentSecurityPolicy): string {
return Object.entries(csp) return Object.entries(csp)
.map((directive: [string, CspValue[]]) => `${directive[0]} ${directive[1]?.join(' ')}`) .map(
(directive: [string, Session.CspValue[] | undefined]) =>
`${directive[0]} ${directive[1] ? directive[1]?.join(' ') : ''}`
)
.join('; '); .join('; ');
} }
public setCsp(window: Electron.BrowserWindow, csp: IContentSecurityPolicy): void { public setCsp(window: Electron.BrowserWindow, csp: Session.ContentSecurityPolicy): void {
const mergedCsp: IContentSecurityPolicy = { ...defaultCsp, ...csp }; const mergedCsp: Session.ContentSecurityPolicy = { ...defaultCsp, ...csp };
if (isDev()) { if (isDev()) {
mergedCsp['default-src'] = ['devtools:'].concat(mergedCsp['default-src'] ?? []); mergedCsp['default-src'] = ['devtools:'].concat(mergedCsp['default-src'] ?? []);

View File

@ -1,23 +1,25 @@
type CspValue = '*' | "'none'" | "'self'" | "'unsafe-inline'" | "'unsafe-eval'" | string; declare namespace Session {
type CspValue = '*' | "'none'" | "'self'" | "'unsafe-inline'" | "'unsafe-eval'" | string;
/** /**
* This interface represents a content security policy. * This interface represents a content security policy.
* *
* @see https://www.w3.org/TR/CSP/ * @see https://www.w3.org/TR/CSP/
* @see https://content-security-policy.com/ * @see https://content-security-policy.com/
*/ */
interface IContentSecurityPolicy { type ContentSecurityPolicy = {
'child-src'?: CspValue[]; 'child-src'?: CspValue[];
'connect-src'?: CspValue[]; 'connect-src'?: CspValue[];
'default-src'?: CspValue[]; 'default-src'?: CspValue[];
'font-src'?: CspValue[]; 'font-src'?: CspValue[];
'frame-src'?: CspValue[]; 'frame-src'?: CspValue[];
'img-src'?: CspValue[]; 'img-src'?: CspValue[];
'media-src'?: CspValue[]; 'media-src'?: CspValue[];
'object-src'?: ["'none'"]; 'object-src'?: ["'none'"];
'script-src'?: CspValue[]; 'script-src'?: CspValue[];
'script-src-elem'?: CspValue[]; 'script-src-elem'?: CspValue[];
'script-src-attr'?: CspValue[]; 'script-src-attr'?: CspValue[];
'style-src'?: CspValue[]; 'style-src'?: CspValue[];
'worker-src'?: CspValue[]; 'worker-src'?: CspValue[];
};
} }

View File

@ -1,4 +1,4 @@
export interface IStore { interface StoreInterface {
load: (key: StoreKey) => Promise<unknown>; load: (key: StoreKey) => Promise<unknown>;
save: (key: StoreKey, data: unknown) => Promise<void>; save: (key: StoreKey, data: unknown) => Promise<void>;
} }

View File

@ -1,11 +1,10 @@
import { injectable } from 'inversify'; import { injectable } from 'inversify';
import { IStore } from './i-store';
/** /**
* This mock store saves the data in memory. * This mock store saves the data in memory.
*/ */
@injectable() @injectable()
export class StoreMock implements IStore { export class StoreMock implements StoreInterface {
private store: { [x in StoreKey]?: unknown } = {}; private store: { [x in StoreKey]?: unknown } = {};
public load(key: StoreKey): Promise<unknown> { public load(key: StoreKey): Promise<unknown> {

View File

@ -1,13 +1,12 @@
import { expect } from 'chai'; import { expect } from 'chai';
import 'mocha'; import 'mocha';
import { container } from '../../core/container'; import { container } from '../../core/container';
import { IStore } from './i-store';
describe('Store Service', function () { describe('Store Service', function () {
this.timeout(10000); this.timeout(10000);
it('loads saved data', () => { it('loads saved data', () => {
const store: IStore = container.get('store'); const store: StoreInterface = container.get('store');
const testData = { const testData = {
something: 'gaga', something: 'gaga',
somethingElse: 0, somethingElse: 0,

View File

@ -1,12 +1,11 @@
import { injectable } from 'inversify'; import { injectable } from 'inversify';
import { Database, getConnection } from '../../core/database'; import { Database, getConnection } from '../../core/database';
import { StoreValue } from '../../entities/store/store-value'; import { StoreValue } from '../../entities/store/store-value';
import { IStore } from './i-store';
const CACHE_ID = 'store'; const CACHE_ID = 'store';
@injectable() @injectable()
export class Store implements IStore { export class Store implements StoreInterface {
public async load(key: StoreKey): Promise<unknown> { public async load(key: StoreKey): Promise<unknown> {
const c = await getConnection(Database.STORE); const c = await getConnection(Database.STORE);
const repository = c.getRepository(StoreValue); const repository = c.getRepository(StoreValue);

View File

@ -2,16 +2,16 @@ import { ipcRenderer } from 'electron';
import { uuid } from '../../services/uuid'; import { uuid } from '../../services/uuid';
import IpcRendererEvent = Electron.IpcRendererEvent; import IpcRendererEvent = Electron.IpcRendererEvent;
const ipcClient: IIpcClient = { const ipcClient: IpcClient = {
ask: (channel: IpcChannel, data?: unknown): Promise<unknown> => { ask: (channel: IpcChannel, data?: unknown): Promise<unknown> => {
const id = uuid(); const id = uuid();
const payload: IIpcPayload = { const payload: IpcPayload = {
id, id,
data, data,
}; };
return new Promise((resolve: (value?: unknown) => void, reject: (reason?: Error) => void): void => { return new Promise((resolve: (value?: unknown) => void, reject: (reason?: Error) => void): void => {
const listener = (event: IpcRendererEvent, response: IIpcResponse): void => { const listener = (event: IpcRendererEvent, response: IpcResponse): void => {
if (response.id === id) { if (response.id === id) {
if (response.success) { if (response.success) {
resolve(response.data); resolve(response.data);

View File

@ -12,7 +12,8 @@
"sourceMap": true, "sourceMap": true,
"preserveConstEnums": false, "preserveConstEnums": false,
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true "emitDecoratorMetadata": true,
"importsNotUsedAsValues": "error"
}, },
"include": ["declarations/**/*.ts", "types/**/*.ts", "src/main.ts", "src/main/**/*.ts", "src/services/**/*.ts"] "include": ["declarations/**/*.ts", "types/**/*.ts", "src/main.ts", "src/main/**/*.ts", "src/services/**/*.ts"]
} }

View File

@ -6,7 +6,8 @@
"noImplicitAny": true, "noImplicitAny": true,
"removeComments": true, "removeComments": true,
"sourceMap": true, "sourceMap": true,
"preserveConstEnums": false "preserveConstEnums": false,
"importsNotUsedAsValues": "error"
}, },
"include": [ "include": [
"declarations/**/*.ts", "declarations/**/*.ts",

View File

@ -2,7 +2,7 @@
* @see MethodDecorator * @see MethodDecorator
*/ */
type IpcControllerMethodDecorator = <T>( type IpcControllerMethodDecorator = <T>(
target: IIpcController, target: IpcController,
propertyKey: string, propertyKey: string,
descriptor: TypedPropertyDescriptor<T> descriptor: TypedPropertyDescriptor<T>
) => void; ) => void;

19
types/ipc.d.ts vendored
View File

@ -2,26 +2,25 @@ declare const enum IpcChannel {
NHENTAI_SAVE_FAVORITES = 'NHENTAI_SAVE_FAVORITES', NHENTAI_SAVE_FAVORITES = 'NHENTAI_SAVE_FAVORITES',
} }
interface IIpcPayload { type IpcPayload = {
id: string; id: string;
data: unknown; data: unknown;
} };
interface IIpcResponse { type IpcResponse = {
id: string; id: string;
success: boolean; success: boolean;
data?: unknown; data?: unknown;
// just the error message // just the error message
error?: string; error?: string;
} };
interface IIpcClient { type IpcClient = {
ask: (channel: IpcChannel, data?: unknown) => Promise<unknown>; ask: (channel: IpcChannel, data?: unknown) => Promise<unknown>;
} };
type IpcHandler = (data?: unknown) => Promise<unknown>; type IpcHandler = (data?: unknown) => Promise<unknown>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- https://github.com/typescript-eslint/typescript-eslint/issues/2714 type IpcController = {
interface IIpcController { get(): IpcController;
get(): IIpcController; };
}