meta: change naming convention of interfaces and use importsNotUsedAsValues: 'error' in tsconfig for clearer imports
This commit is contained in:
parent
a807c6f2be
commit
09dc14726d
|
@ -150,7 +150,7 @@
|
|||
{
|
||||
"selector": "interface",
|
||||
"format": ["PascalCase"],
|
||||
"prefix": ["I"]
|
||||
"suffix": ["Interface"]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/explicit-member-accessibility": "error",
|
||||
|
|
11
src/main.ts
11
src/main.ts
|
@ -4,21 +4,20 @@ import './main/core/install';
|
|||
|
||||
import { app } from 'electron';
|
||||
import { isDev } from './main/core/env';
|
||||
import { IAppWindow } from './main/modules/app-window/i-app-window';
|
||||
import { ILogger } from './main/modules/logger/i-logger';
|
||||
import type { AppWindowInterface } from './main/modules/app-window/app-window-interface';
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
const logger: ILogger = container.get('logger');
|
||||
const logger: LoggerInterface = container.get('logger');
|
||||
void logger.fatal(`Unhandled Rejection, see ${logger.getExceptionsLogFile()}`);
|
||||
throw reason;
|
||||
});
|
||||
|
||||
process.on('uncaughtException', (error) => {
|
||||
const logger: ILogger = container.get('logger');
|
||||
const logger: LoggerInterface = container.get('logger');
|
||||
void logger.exception(error);
|
||||
if (isDev()) {
|
||||
// eslint-disable-next-line no-console -- only for development purposes
|
||||
|
@ -27,7 +26,7 @@ process.on('uncaughtException', (error) => {
|
|||
});
|
||||
|
||||
async function createWindow(): Promise<void> {
|
||||
const appWindowMain: IAppWindow = container.get('app-window-main');
|
||||
const appWindowMain: AppWindowInterface = container.get('app-window-main');
|
||||
await appWindowMain.open();
|
||||
|
||||
appWindowMain.window?.on('closed', () => {
|
||||
|
@ -48,7 +47,7 @@ app.on('window-all-closed', () => {
|
|||
app.on('activate', async () => {
|
||||
// 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.
|
||||
const appWindowMain: IAppWindow = container.get('app-window-main');
|
||||
const appWindowMain: AppWindowInterface = container.get('app-window-main');
|
||||
if (appWindowMain.isClosed()) {
|
||||
await createWindow();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import path from 'path';
|
||||
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';
|
||||
|
||||
export enum Database {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
|||
import { Author } from './author';
|
||||
|
||||
@Entity()
|
||||
export class AuthorName implements IIdentifiableEntity, INameEntity {
|
||||
export class AuthorName implements IdentifiableEntityInterface, NameEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
|||
import { AuthorRole } from './author-role';
|
||||
|
||||
@Entity()
|
||||
export class AuthorRoleName implements IIdentifiableEntity, INameEntity {
|
||||
export class AuthorRoleName implements IdentifiableEntityInterface, NameEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import { WorkAuthor } from './work-author';
|
|||
* Examples: story writing, drawing, animating, publishing, ...
|
||||
*/
|
||||
@Entity()
|
||||
export class AuthorRole implements IIdentifiableEntity, IMultiNamedEntity, IDescribableEntity {
|
||||
export class AuthorRole implements IdentifiableEntityInterface, MultiNamedEntityInterface, DescribableEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
@Entity()
|
||||
export class Author implements IIdentifiableEntity, IMultiNamedEntity {
|
||||
export class Author implements IdentifiableEntityInterface, MultiNamedEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import { WorkCharacter } from './work-character';
|
|||
*/
|
||||
@Entity()
|
||||
@PercentCheck('weight')
|
||||
export class CharacterTag implements IIdentifiableEntity, IWeightedEntity {
|
||||
export class CharacterTag implements IdentifiableEntityInterface, WeightedEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
|||
import { Collection } from './collection';
|
||||
|
||||
@Entity()
|
||||
export class CollectionName implements IIdentifiableEntity, INameEntity {
|
||||
export class CollectionName implements IdentifiableEntityInterface, NameEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import { Work } from './work';
|
|||
* The main use case is chronological ordering.
|
||||
*/
|
||||
@Entity()
|
||||
export class CollectionPart implements IIdentifiableEntity, IOrderableEntity {
|
||||
export class CollectionPart implements IdentifiableEntityInterface, OrderableEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import { CollectionPart } from './collection-part';
|
|||
* If authors of works see them as belonging together, they are a collection
|
||||
*/
|
||||
@Entity()
|
||||
export class Collection implements IIdentifiableEntity, IMultiNamedEntity {
|
||||
export class Collection implements IdentifiableEntityInterface, MultiNamedEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -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).
|
||||
*/
|
||||
@Entity()
|
||||
export class Copy implements IIdentifiableEntity {
|
||||
export class Copy implements IdentifiableEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Entities extending this one have a user-maintained description.
|
||||
*/
|
||||
declare interface IDescribableEntity {
|
||||
interface DescribableEntityInterface {
|
||||
/**
|
||||
* a text describing this entity
|
||||
*/
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Entities implementing this interface build a hierarchy.
|
||||
*/
|
||||
declare interface IHierachicalEntity<T> {
|
||||
interface HierarchicalEntityInterface<T> {
|
||||
/**
|
||||
* parent entities
|
||||
*/
|
|
@ -2,7 +2,7 @@
|
|||
* Every database entity should implement this one.
|
||||
* It does nothing more but guarantee there is an id column.
|
||||
*/
|
||||
declare interface IIdentifiableEntity {
|
||||
interface IdentifiableEntityInterface {
|
||||
/**
|
||||
* the entity id
|
||||
*/
|
|
@ -8,7 +8,7 @@ import { WorkCharacter } from './work-character';
|
|||
*/
|
||||
@Entity()
|
||||
@PercentCheck('weight')
|
||||
export class InteractionTag implements IIdentifiableEntity, IWeightedEntity {
|
||||
export class InteractionTag implements IdentifiableEntityInterface, WeightedEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Entities extending this interface can have multiple names.
|
||||
*/
|
||||
declare interface IMultiNamedEntity {
|
||||
interface MultiNamedEntityInterface {
|
||||
/**
|
||||
* the name which is displayed in the user interface
|
||||
*/
|
||||
|
@ -10,5 +10,5 @@ declare interface IMultiNamedEntity {
|
|||
/**
|
||||
* other names for the entity
|
||||
*/
|
||||
names: Promise<INameEntity[]>;
|
||||
names: Promise<NameEntityInterface[]>;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* This entity describes a single name of an entity with multiple names.
|
||||
*/
|
||||
declare interface INameEntity {
|
||||
interface NameEntityInterface {
|
||||
/**
|
||||
* the name
|
||||
*/
|
||||
|
@ -10,5 +10,5 @@ declare interface INameEntity {
|
|||
/**
|
||||
* the entity to which the names belong
|
||||
*/
|
||||
entity: Promise<IMultiNamedEntity>;
|
||||
entity: Promise<MultiNamedEntityInterface>;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Entities implementing this interface can be ordered.
|
||||
*/
|
||||
declare interface IOrderableEntity {
|
||||
interface OrderableEntityInterface {
|
||||
/**
|
||||
* a lower number means a higher ordering
|
||||
*/
|
|
@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
|||
import { Site } from './site';
|
||||
|
||||
@Entity()
|
||||
export class SiteName implements IIdentifiableEntity, INameEntity {
|
||||
export class SiteName implements IdentifiableEntityInterface, NameEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Source } from './source';
|
|||
* This non-user-maintained entity describes an online provider of works which can be scraped.
|
||||
*/
|
||||
@Entity()
|
||||
export class Site implements IIdentifiableEntity, IMultiNamedEntity {
|
||||
export class Site implements IdentifiableEntityInterface, MultiNamedEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Site } from './site';
|
|||
* This entity describes an external source of a copy, in most cases that is a website.
|
||||
*/
|
||||
@Entity()
|
||||
export class Source implements IIdentifiableEntity {
|
||||
export class Source implements IdentifiableEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
|||
import { Tag } from './tag';
|
||||
|
||||
@Entity()
|
||||
export class TagName implements IIdentifiableEntity, INameEntity {
|
||||
export class TagName implements IdentifiableEntityInterface, NameEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -11,7 +11,12 @@ import { WorkTag } from './work-tag';
|
|||
* They can also be in a hierarchy
|
||||
*/
|
||||
@Entity()
|
||||
export class Tag implements IIdentifiableEntity, IMultiNamedEntity, IDescribableEntity, IHierachicalEntity<Tag> {
|
||||
export class Tag
|
||||
implements
|
||||
IdentifiableEntityInterface,
|
||||
MultiNamedEntityInterface,
|
||||
DescribableEntityInterface,
|
||||
HierarchicalEntityInterface<Tag> {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
|||
import { TransformationType } from './transformation-type';
|
||||
|
||||
@Entity()
|
||||
export class TransformationTypeName implements IIdentifiableEntity, INameEntity {
|
||||
export class TransformationTypeName implements IdentifiableEntityInterface, NameEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@ import { TransformationTypeName } from './transformation-type-name';
|
|||
* Possible type: translation, decensor, collection, ...
|
||||
*/
|
||||
@Entity()
|
||||
export class TransformationType implements IIdentifiableEntity, IMultiNamedEntity, IDescribableEntity {
|
||||
export class TransformationType
|
||||
implements IdentifiableEntityInterface, MultiNamedEntityInterface, DescribableEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Work } from './work';
|
|||
* This entity describes how one work is transformed to another.
|
||||
*/
|
||||
@Entity()
|
||||
export class Transformation implements IIdentifiableEntity, IOrderableEntity {
|
||||
export class Transformation implements IdentifiableEntityInterface, OrderableEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* 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 can also be not not defined, null in the database
|
|
@ -7,7 +7,7 @@ import { Work } from './work';
|
|||
* This entity connects authors with their work and their role therein.
|
||||
*/
|
||||
@Entity()
|
||||
export class WorkAuthor implements IIdentifiableEntity, IOrderableEntity {
|
||||
export class WorkAuthor implements IdentifiableEntityInterface, OrderableEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
|||
import { WorkCharacter } from './work-character';
|
||||
|
||||
@Entity()
|
||||
export class WorkCharacterName implements IIdentifiableEntity, INameEntity {
|
||||
export class WorkCharacterName implements IdentifiableEntityInterface, NameEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import { WorldCharacter } from './world-character';
|
|||
* The character can be original or based on one or more existing characters.
|
||||
*/
|
||||
@Entity()
|
||||
export class WorkCharacter implements IIdentifiableEntity, IMultiNamedEntity {
|
||||
export class WorkCharacter implements IdentifiableEntityInterface, MultiNamedEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
|||
import { Work } from './work';
|
||||
|
||||
@Entity()
|
||||
export class WorkName implements IIdentifiableEntity, INameEntity {
|
||||
export class WorkName implements IdentifiableEntityInterface, NameEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Work } from './work';
|
|||
*/
|
||||
@Entity()
|
||||
@PercentCheck('weight')
|
||||
export class WorkTag implements IIdentifiableEntity, IWeightedEntity {
|
||||
export class WorkTag implements IdentifiableEntityInterface, WeightedEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import { World } from './world';
|
|||
*/
|
||||
@Entity()
|
||||
@PercentCheck('rating')
|
||||
export class Work implements IIdentifiableEntity, IMultiNamedEntity {
|
||||
export class Work implements IdentifiableEntityInterface, MultiNamedEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
|||
import { WorldCharacter } from './world-character';
|
||||
|
||||
@Entity()
|
||||
export class WorldCharacterName implements IIdentifiableEntity, INameEntity {
|
||||
export class WorldCharacterName implements IdentifiableEntityInterface, NameEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@ import { WorldCharacterName } from './world-character-name';
|
|||
* This entity describes a canon character in a fictional world.
|
||||
*/
|
||||
@Entity()
|
||||
export class WorldCharacter implements IIdentifiableEntity, IMultiNamedEntity, IHierachicalEntity<WorldCharacter> {
|
||||
export class WorldCharacter
|
||||
implements IdentifiableEntityInterface, MultiNamedEntityInterface, HierarchicalEntityInterface<WorldCharacter> {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
|||
import { World } from './world';
|
||||
|
||||
@Entity()
|
||||
export class WorldName implements IIdentifiableEntity, INameEntity {
|
||||
export class WorldName implements IdentifiableEntityInterface, NameEntityInterface {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@ import { WorldName } from './world-name';
|
|||
* This entity describes a fictional world.
|
||||
*/
|
||||
@Entity()
|
||||
export class World implements IIdentifiableEntity, IMultiNamedEntity, IHierachicalEntity<World> {
|
||||
export class World
|
||||
implements IdentifiableEntityInterface, MultiNamedEntityInterface, HierarchicalEntityInterface<World> {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id!: number;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
import type { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class initialMigration1597705000730 implements MigrationInterface {
|
||||
name = 'initialMigration1597705000730';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
import type { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class initialMigration1587511036078 implements MigrationInterface {
|
||||
name = 'initialMigration1587511036078';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import BrowserWindow = Electron.BrowserWindow;
|
||||
import { BrowserWindow } from 'electron';
|
||||
|
||||
export interface IAppWindow {
|
||||
interface AppWindowInterface {
|
||||
window: BrowserWindow | null;
|
||||
open(): Promise<void>;
|
||||
close(force?: boolean): void;
|
|
@ -2,8 +2,8 @@ import { app, BrowserWindow, Event, LoadFileOptions, LoadURLOptions, NewWindowWe
|
|||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { isDev } from '../../core/env';
|
||||
import { ISessionHelper } from '../session/i-session-helper';
|
||||
import { IAppWindow } from './i-app-window';
|
||||
import type { SessionHelperInterface } from '../session/session-helper-interface';
|
||||
import type { AppWindowInterface } from './app-window-interface';
|
||||
import BrowserWindowConstructorOptions = Electron.BrowserWindowConstructorOptions;
|
||||
|
||||
let defaultOptions: BrowserWindowConstructorOptions = {
|
||||
|
@ -30,12 +30,12 @@ switch (os.platform()) {
|
|||
break;
|
||||
}
|
||||
|
||||
export abstract class AppWindow implements IAppWindow {
|
||||
export abstract class AppWindow implements AppWindowInterface {
|
||||
protected static default = {};
|
||||
|
||||
protected _window: BrowserWindow | null = null;
|
||||
|
||||
protected readonly sessionHelper: ISessionHelper;
|
||||
protected readonly sessionHelper: SessionHelperInterface;
|
||||
|
||||
protected options: BrowserWindowConstructorOptions;
|
||||
|
||||
|
@ -43,7 +43,11 @@ export abstract class AppWindow implements IAppWindow {
|
|||
|
||||
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.options = { ...defaultOptions, ...options };
|
||||
this.uri = uri;
|
||||
|
@ -85,7 +89,7 @@ export abstract class AppWindow implements IAppWindow {
|
|||
return !this._window;
|
||||
}
|
||||
|
||||
protected getCsp(): IContentSecurityPolicy {
|
||||
protected getCsp(): Session.ContentSecurityPolicy {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { BrowserWindow, BrowserWindowConstructorOptions, LoadFileOptions } from 'electron';
|
||||
import { ISessionHelper } from '../session/i-session-helper';
|
||||
import type { BrowserWindow, BrowserWindowConstructorOptions, LoadFileOptions } from 'electron';
|
||||
import type { SessionHelperInterface } from '../session/session-helper-interface';
|
||||
import { AppWindow } from './app-window';
|
||||
|
||||
export abstract class FileAppWindow extends AppWindow {
|
||||
protected loadOptions: LoadFileOptions;
|
||||
|
||||
protected constructor(
|
||||
sessionHelper: ISessionHelper,
|
||||
sessionHelper: SessionHelperInterface,
|
||||
uri: string,
|
||||
options: BrowserWindowConstructorOptions = {},
|
||||
loadOptions: LoadFileOptions = {}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
import { IUrlAppWindow } from './i-url-app-window';
|
||||
|
||||
export interface ISiteAppWindow extends IUrlAppWindow {
|
||||
/**
|
||||
* @see IMutex.acquire
|
||||
*/
|
||||
acquireLock(): Promise<Mutex.ReleaseFunction>;
|
||||
}
|
|
@ -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>;
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
import { injectable } from 'inversify';
|
||||
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';
|
||||
|
||||
@injectable()
|
||||
export class MainAppWindow extends FileAppWindow {
|
||||
public constructor(@inject('session-helper') sessionHelper: ISessionHelper) {
|
||||
public constructor(@inject('session-helper') sessionHelper: SessionHelperInterface) {
|
||||
super(sessionHelper, 'frontend/index.html', {
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import type { UrlAppWindowInterface } from './url-app-window-interface';
|
||||
|
||||
interface SiteAppWindowInterface extends UrlAppWindowInterface {
|
||||
/**
|
||||
* @see IMutex.acquire
|
||||
*/
|
||||
acquireLock(): Promise<Mutex.ReleaseFunction>;
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
import { BrowserWindowConstructorOptions, LoadURLOptions } from 'electron';
|
||||
import type { BrowserWindowConstructorOptions, LoadURLOptions } from 'electron';
|
||||
import { SimpleMutex } from '../mutex/simple-mutex';
|
||||
import { ISessionHelper } from '../session/i-session-helper';
|
||||
import { ISiteAppWindow } from './i-site-app-window';
|
||||
import type { SessionHelperInterface } from '../session/session-helper-interface';
|
||||
import type { SiteAppWindowInterface } from './site-app-window-interface';
|
||||
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.
|
||||
* It offers a lock so that multiple calls do executed simultaneously on the same chromium window.
|
||||
*/
|
||||
export abstract class SiteAppWindow extends UrlAppWindow implements ISiteAppWindow {
|
||||
private windowLock: IMutex;
|
||||
export abstract class SiteAppWindow extends UrlAppWindow implements SiteAppWindowInterface {
|
||||
private windowLock: MutexInterface;
|
||||
|
||||
protected constructor(
|
||||
sessionHelper: ISessionHelper,
|
||||
sessionHelper: SessionHelperInterface,
|
||||
uri: string,
|
||||
options: BrowserWindowConstructorOptions = {},
|
||||
loadOptions: LoadURLOptions = {}
|
||||
|
|
|
@ -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>;
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
import { BrowserWindow, BrowserWindowConstructorOptions, LoadURLOptions } from 'electron';
|
||||
import type { BrowserWindow, BrowserWindowConstructorOptions, LoadURLOptions } from 'electron';
|
||||
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 { IUrlAppWindow } from './i-url-app-window';
|
||||
import type { UrlAppWindowInterface } from './url-app-window-interface';
|
||||
import { WindowClosedError } from './window-closed-error';
|
||||
import Timeout = NodeJS.Timeout;
|
||||
|
||||
export abstract class UrlAppWindow extends AppWindow implements IUrlAppWindow {
|
||||
export abstract class UrlAppWindow extends AppWindow implements UrlAppWindowInterface {
|
||||
protected loadOptions: LoadURLOptions;
|
||||
|
||||
/**
|
||||
|
@ -32,7 +32,7 @@ export abstract class UrlAppWindow extends AppWindow implements IUrlAppWindow {
|
|||
private loadWait: Promise<void> = Promise.resolve();
|
||||
|
||||
protected constructor(
|
||||
sessionHelper: ISessionHelper,
|
||||
sessionHelper: SessionHelperInterface,
|
||||
uri: string,
|
||||
options: BrowserWindowConstructorOptions = {},
|
||||
loadOptions: LoadURLOptions = {}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { dialog, OpenDialogOptions } from 'electron';
|
||||
|
||||
interface IDialog {
|
||||
interface DialogInterface {
|
||||
selectFolder(options: OpenDialogOptions): ReturnType<typeof dialog.showOpenDialog>;
|
||||
}
|
|
@ -1,14 +1,13 @@
|
|||
import { dialog, OpenDialogOptions } from 'electron';
|
||||
import { injectable } from 'inversify';
|
||||
import { inject } from '../../core/inject';
|
||||
import { II18nTranslator } from '../i18n/i-i18n-translator';
|
||||
import { IDialog } from './i-dialog';
|
||||
import type { DialogInterface } from './dialog-interface';
|
||||
|
||||
@injectable()
|
||||
export class Dialog implements IDialog {
|
||||
private readonly translator: II18nTranslator;
|
||||
export class Dialog implements DialogInterface {
|
||||
private readonly translator: I18nTranslatorInterface;
|
||||
|
||||
public constructor(@inject('i18n-translator') translator: II18nTranslator) {
|
||||
public constructor(@inject('i18n-translator') translator: I18nTranslatorInterface) {
|
||||
this.translator = translator;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
export interface II18nTranslator {
|
||||
t(text: string): string;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
interface I18nTranslatorInterface {
|
||||
t(text: string): string;
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
import { injectable } from 'inversify';
|
||||
import { II18nTranslator } from './i-i18n-translator';
|
||||
|
||||
@injectable()
|
||||
export class I18nTranslator implements II18nTranslator {
|
||||
export class I18nTranslator implements I18nTranslatorInterface {
|
||||
public t(text: string): string {
|
||||
return text;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { registerHandler } from '../ipc-server';
|
||||
|
||||
export function answer(channel: IpcChannel): IpcControllerMethodDecorator {
|
||||
return function (target: IIpcController, propertyKey): void {
|
||||
return function (target: IpcController, propertyKey): void {
|
||||
registerHandler(channel, target, propertyKey);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { ipcMain } from 'electron';
|
||||
import IpcMainEvent = Electron.IpcMainEvent;
|
||||
|
||||
export function registerHandler(channel: IpcChannel, controller: IIpcController, handler: string): void {
|
||||
ipcMain.on(channel, (event: IpcMainEvent, payload: IIpcPayload) => {
|
||||
export function registerHandler(channel: IpcChannel, controller: IpcController, handler: string): void {
|
||||
ipcMain.on(channel, (event: IpcMainEvent, payload: IpcPayload) => {
|
||||
((controller.get() as unknown) as { [x: string]: IpcHandler })
|
||||
[handler](payload.data)
|
||||
.then((result: unknown) => {
|
||||
const response: IIpcResponse = {
|
||||
const response: IpcResponse = {
|
||||
id: payload.id,
|
||||
success: true,
|
||||
data: result,
|
||||
|
@ -14,7 +14,7 @@ export function registerHandler(channel: IpcChannel, controller: IIpcController,
|
|||
event.reply(channel, response);
|
||||
})
|
||||
.catch((reason: Error) => {
|
||||
const response: IIpcResponse = {
|
||||
const response: IpcResponse = {
|
||||
id: payload.id,
|
||||
success: false,
|
||||
error: reason.message,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* 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
|
||||
* @see LogLevel
|
||||
|
@ -47,7 +47,7 @@ export interface ILogger {
|
|||
|
||||
/**
|
||||
* logs the error, preferably with stack trace
|
||||
* @see ILogger.getExceptionsLogFile
|
||||
* @see LoggerInterface.getExceptionsLogFile
|
||||
*/
|
||||
exception(error: Error): Promise<void>;
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
import { injectable } from 'inversify';
|
||||
import { ILogger } from './i-logger';
|
||||
|
||||
/**
|
||||
* Mock of a logger, does not log anywhere
|
||||
*/
|
||||
@injectable()
|
||||
export class LoggerMock implements ILogger {
|
||||
export class LoggerMock implements LoggerInterface {
|
||||
public getLogFile(): string {
|
||||
return '';
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import fc from 'fast-check';
|
|||
import fs from 'fs-extra';
|
||||
import { container } from '../../core/container';
|
||||
import { setDev } from '../../core/env.spec';
|
||||
import { ILogger } from './i-logger';
|
||||
import { LogLevel } from './log-level';
|
||||
|
||||
chai.use(chaiFs);
|
||||
|
@ -25,7 +24,7 @@ describe('Logger Service', () => {
|
|||
});
|
||||
}
|
||||
|
||||
function getDefaultLogReadLineInterface(logger: ILogger) {
|
||||
function getDefaultLogReadLineInterface(logger: LoggerInterface) {
|
||||
return createInterface(fs.createReadStream(logger.getLogFile()));
|
||||
}
|
||||
|
||||
|
@ -42,20 +41,20 @@ describe('Logger Service', () => {
|
|||
const logLevelNumberArbitrary = fc.constantFrom(...logLevelsNumber);
|
||||
|
||||
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.getExceptionsLogFile()).path('exception log file is not created');
|
||||
});
|
||||
|
||||
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'));
|
||||
});
|
||||
|
||||
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 minNumberOfLines = maxLogSize;
|
||||
|
@ -76,7 +75,7 @@ describe('Logger Service', () => {
|
|||
}).timeout(15000);
|
||||
|
||||
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 minNumberOfLines = maxLogSize;
|
||||
|
@ -97,7 +96,7 @@ describe('Logger Service', () => {
|
|||
}).timeout(15000);
|
||||
|
||||
it('logs different levels directly', () => {
|
||||
const logger: ILogger = container.get('logger');
|
||||
const logger: LoggerInterface = container.get('logger');
|
||||
|
||||
return fc.assert(
|
||||
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', () => {
|
||||
const logger: ILogger = container.get('logger');
|
||||
const logger: LoggerInterface = container.get('logger');
|
||||
|
||||
return fc.assert(
|
||||
fc.asyncProperty(logLevelNumberArbitrary, fc.string(), async (logLevelNumber, message) => {
|
||||
|
@ -132,7 +131,7 @@ describe('Logger Service', () => {
|
|||
});
|
||||
|
||||
it('logs debug only in dev mode', async () => {
|
||||
const logger: ILogger = container.get('logger');
|
||||
const logger: LoggerInterface = container.get('logger');
|
||||
|
||||
setDev();
|
||||
await logger.debug('this is a development message');
|
||||
|
|
|
@ -4,7 +4,6 @@ import * as fs from 'fs-extra';
|
|||
import { injectable } from 'inversify';
|
||||
import { appPath } from '../../core/app-path';
|
||||
import { isDev } from '../../core/env';
|
||||
import { ILogger } from './i-logger';
|
||||
import { LogLevel } from './log-level';
|
||||
|
||||
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.
|
||||
*/
|
||||
@injectable()
|
||||
export class Logger implements ILogger {
|
||||
export class Logger implements LoggerInterface {
|
||||
public constructor() {
|
||||
fs.createFileSync(logFile);
|
||||
fs.createFileSync(exceptionLogFile);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* 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.
|
||||
* This release function needs to be called or the lock will never release and execution of subsequent consumers will not take place.
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* 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.
|
||||
* Calling them signals the corresponding consumer that the lock is now free.
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import { ISiteAppWindow } from '../app-window/i-site-app-window';
|
||||
|
||||
export interface INhentaiAppWindow extends ISiteAppWindow {
|
||||
getFavorites(): Promise<NodeJS.ReadableStream>;
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
export interface INhentaiApi {
|
||||
interface NhentaiApiInterface {
|
||||
getFavorites(): Promise<NodeJS.ReadableStream>;
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
import { injectable } from 'inversify';
|
||||
import { inject } from '../../core/inject';
|
||||
import { INhentaiApi } from './i-nhentai-api';
|
||||
import { INhentaiAppWindow } from './i-nhentai-app-window';
|
||||
import type { NhentaiAppWindowInterface } from './nhentai-app-window-interface';
|
||||
|
||||
@injectable()
|
||||
export class NhentaiApi implements INhentaiApi {
|
||||
private readonly appWindow: INhentaiAppWindow;
|
||||
export class NhentaiApi implements NhentaiApiInterface {
|
||||
private readonly appWindow: NhentaiAppWindowInterface;
|
||||
|
||||
public constructor(@inject('nhentai-app-window') appWindow: INhentaiAppWindow) {
|
||||
public constructor(@inject('nhentai-app-window') appWindow: NhentaiAppWindowInterface) {
|
||||
this.appWindow = appWindow;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import type { SiteAppWindowInterface } from '../app-window/site-app-window-interface';
|
||||
|
||||
interface NhentaiAppWindowInterface extends SiteAppWindowInterface {
|
||||
getFavorites(): Promise<NodeJS.ReadableStream>;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { WebContents } from 'electron';
|
||||
import type { WebContents } from 'electron';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { Readable } from 'stream';
|
||||
|
@ -8,9 +8,8 @@ import { injectable } from 'inversify';
|
|||
import { inject } from '../../core/inject';
|
||||
import { SiteAppWindow } from '../app-window/site-app-window';
|
||||
import { WindowClosedError } from '../app-window/window-closed-error';
|
||||
import { ISessionHelper } from '../session/i-session-helper';
|
||||
import { INhentaiAppWindow } from './i-nhentai-app-window';
|
||||
import { IFavorite } from './nhentai';
|
||||
import type { SessionHelperInterface } from '../session/session-helper-interface';
|
||||
import type { NhentaiAppWindowInterface } from './nhentai-app-window-interface';
|
||||
import {
|
||||
url as nhentaiUrl,
|
||||
hostname as nhentaiHostname,
|
||||
|
@ -25,8 +24,8 @@ import {
|
|||
const waitInterval = 2000;
|
||||
|
||||
@injectable()
|
||||
export class NhentaiAppWindow extends SiteAppWindow implements INhentaiAppWindow {
|
||||
public constructor(@inject('session-helper') sessionHelper: ISessionHelper) {
|
||||
export class NhentaiAppWindow extends SiteAppWindow implements NhentaiAppWindowInterface {
|
||||
public constructor(@inject('session-helper') sessionHelper: SessionHelperInterface) {
|
||||
super(sessionHelper, nhentaiUrl);
|
||||
}
|
||||
|
||||
|
@ -52,7 +51,7 @@ export class NhentaiAppWindow extends SiteAppWindow implements INhentaiAppWindow
|
|||
}
|
||||
|
||||
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++) {
|
||||
const bookUrl = bookUrls[i];
|
||||
yield await thisArg.getBookTorrent(bookUrl);
|
||||
|
@ -76,7 +75,7 @@ export class NhentaiAppWindow extends SiteAppWindow implements INhentaiAppWindow
|
|||
}
|
||||
}
|
||||
|
||||
protected getCsp(): IContentSecurityPolicy {
|
||||
protected getCsp(): Session.ContentSecurityPolicy {
|
||||
return {
|
||||
'default-src': ['nhentai.net'],
|
||||
'script-src': ['nhentai.net', "'unsafe-eval'"],
|
||||
|
@ -163,7 +162,7 @@ export class NhentaiAppWindow extends SiteAppWindow implements INhentaiAppWindow
|
|||
return;
|
||||
}
|
||||
|
||||
private async getBookTorrent(bookUrl: string): Promise<IFavorite> {
|
||||
private async getBookTorrent(bookUrl: string): Promise<Nhentai.Favorite> {
|
||||
if (!this._window) {
|
||||
throw new WindowClosedError();
|
||||
}
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
import path from 'path';
|
||||
import { createWriteStream } from 'fs-extra';
|
||||
import { container } from '../../core/container';
|
||||
import { IDialog } from '../dialog/i-dialog';
|
||||
import { II18nTranslator } from '../i18n/i-i18n-translator';
|
||||
import type { DialogInterface } from '../dialog/dialog-interface';
|
||||
import { answer } from '../ipc/annotations/answer';
|
||||
import { INhentaiApi } from './i-nhentai-api';
|
||||
import { IFavorite } from './nhentai';
|
||||
|
||||
export class NhentaiIpcController implements IIpcController {
|
||||
private readonly nhentaiApi: INhentaiApi;
|
||||
export class NhentaiIpcController implements IpcController {
|
||||
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.translator = translator;
|
||||
this.dialog = dialog;
|
||||
|
@ -33,7 +30,7 @@ export class NhentaiIpcController implements IIpcController {
|
|||
const favoritesStream = await this.nhentaiApi.getFavorites();
|
||||
|
||||
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));
|
||||
favorite.torrentFile.pipe(writable);
|
||||
});
|
||||
|
@ -43,9 +40,9 @@ export class NhentaiIpcController implements IIpcController {
|
|||
}
|
||||
|
||||
public get(): NhentaiIpcController {
|
||||
const nhentaiApi: INhentaiApi = container.get('nhentai-api');
|
||||
const translator: II18nTranslator = container.get('i18n-translator');
|
||||
const dialog: IDialog = container.get('dialog');
|
||||
const nhentaiApi: NhentaiApiInterface = container.get('nhentai-api');
|
||||
const translator: I18nTranslatorInterface = container.get('i18n-translator');
|
||||
const dialog: DialogInterface = container.get('dialog');
|
||||
return new NhentaiIpcController(nhentaiApi, translator, dialog);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export interface IFavorite {
|
||||
name: string;
|
||||
torrentFile: NodeJS.ReadableStream;
|
||||
declare namespace Nhentai {
|
||||
type Favorite = {
|
||||
name: string;
|
||||
torrentFile: NodeJS.ReadableStream;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import { BrowserWindow } from 'electron';
|
||||
|
||||
export interface ISessionHelper {
|
||||
setCsp(window: BrowserWindow, csp: IContentSecurityPolicy): void;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import { BrowserWindow } from 'electron';
|
||||
|
||||
interface SessionHelperInterface {
|
||||
setCsp(window: BrowserWindow, csp: Session.ContentSecurityPolicy): void;
|
||||
}
|
|
@ -1,23 +1,26 @@
|
|||
import { injectable } from 'inversify';
|
||||
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'"],
|
||||
'style-src': ["'unsafe-inline'"],
|
||||
'object-src': ["'none'"],
|
||||
};
|
||||
|
||||
@injectable()
|
||||
export class SessionHelper implements ISessionHelper {
|
||||
private static stringifyCspHeader(csp: IContentSecurityPolicy): string {
|
||||
export class SessionHelper implements SessionHelperInterface {
|
||||
private static stringifyCspHeader(csp: Session.ContentSecurityPolicy): string {
|
||||
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('; ');
|
||||
}
|
||||
|
||||
public setCsp(window: Electron.BrowserWindow, csp: IContentSecurityPolicy): void {
|
||||
const mergedCsp: IContentSecurityPolicy = { ...defaultCsp, ...csp };
|
||||
public setCsp(window: Electron.BrowserWindow, csp: Session.ContentSecurityPolicy): void {
|
||||
const mergedCsp: Session.ContentSecurityPolicy = { ...defaultCsp, ...csp };
|
||||
|
||||
if (isDev()) {
|
||||
mergedCsp['default-src'] = ['devtools:'].concat(mergedCsp['default-src'] ?? []);
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* @see https://www.w3.org/TR/CSP/
|
||||
* @see https://content-security-policy.com/
|
||||
*/
|
||||
interface IContentSecurityPolicy {
|
||||
'child-src'?: CspValue[];
|
||||
'connect-src'?: CspValue[];
|
||||
'default-src'?: CspValue[];
|
||||
'font-src'?: CspValue[];
|
||||
'frame-src'?: CspValue[];
|
||||
'img-src'?: CspValue[];
|
||||
'media-src'?: CspValue[];
|
||||
'object-src'?: ["'none'"];
|
||||
'script-src'?: CspValue[];
|
||||
'script-src-elem'?: CspValue[];
|
||||
'script-src-attr'?: CspValue[];
|
||||
'style-src'?: CspValue[];
|
||||
'worker-src'?: CspValue[];
|
||||
/**
|
||||
* This interface represents a content security policy.
|
||||
*
|
||||
* @see https://www.w3.org/TR/CSP/
|
||||
* @see https://content-security-policy.com/
|
||||
*/
|
||||
type ContentSecurityPolicy = {
|
||||
'child-src'?: CspValue[];
|
||||
'connect-src'?: CspValue[];
|
||||
'default-src'?: CspValue[];
|
||||
'font-src'?: CspValue[];
|
||||
'frame-src'?: CspValue[];
|
||||
'img-src'?: CspValue[];
|
||||
'media-src'?: CspValue[];
|
||||
'object-src'?: ["'none'"];
|
||||
'script-src'?: CspValue[];
|
||||
'script-src-elem'?: CspValue[];
|
||||
'script-src-attr'?: CspValue[];
|
||||
'style-src'?: CspValue[];
|
||||
'worker-src'?: CspValue[];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export interface IStore {
|
||||
interface StoreInterface {
|
||||
load: (key: StoreKey) => Promise<unknown>;
|
||||
save: (key: StoreKey, data: unknown) => Promise<void>;
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
import { injectable } from 'inversify';
|
||||
import { IStore } from './i-store';
|
||||
|
||||
/**
|
||||
* This mock store saves the data in memory.
|
||||
*/
|
||||
@injectable()
|
||||
export class StoreMock implements IStore {
|
||||
export class StoreMock implements StoreInterface {
|
||||
private store: { [x in StoreKey]?: unknown } = {};
|
||||
|
||||
public load(key: StoreKey): Promise<unknown> {
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import { expect } from 'chai';
|
||||
import 'mocha';
|
||||
import { container } from '../../core/container';
|
||||
import { IStore } from './i-store';
|
||||
|
||||
describe('Store Service', function () {
|
||||
this.timeout(10000);
|
||||
|
||||
it('loads saved data', () => {
|
||||
const store: IStore = container.get('store');
|
||||
const store: StoreInterface = container.get('store');
|
||||
const testData = {
|
||||
something: 'gaga',
|
||||
somethingElse: 0,
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { injectable } from 'inversify';
|
||||
import { Database, getConnection } from '../../core/database';
|
||||
import { StoreValue } from '../../entities/store/store-value';
|
||||
import { IStore } from './i-store';
|
||||
|
||||
const CACHE_ID = 'store';
|
||||
|
||||
@injectable()
|
||||
export class Store implements IStore {
|
||||
export class Store implements StoreInterface {
|
||||
public async load(key: StoreKey): Promise<unknown> {
|
||||
const c = await getConnection(Database.STORE);
|
||||
const repository = c.getRepository(StoreValue);
|
||||
|
|
|
@ -2,16 +2,16 @@ import { ipcRenderer } from 'electron';
|
|||
import { uuid } from '../../services/uuid';
|
||||
import IpcRendererEvent = Electron.IpcRendererEvent;
|
||||
|
||||
const ipcClient: IIpcClient = {
|
||||
const ipcClient: IpcClient = {
|
||||
ask: (channel: IpcChannel, data?: unknown): Promise<unknown> => {
|
||||
const id = uuid();
|
||||
const payload: IIpcPayload = {
|
||||
const payload: IpcPayload = {
|
||||
id,
|
||||
data,
|
||||
};
|
||||
|
||||
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.success) {
|
||||
resolve(response.data);
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
"sourceMap": true,
|
||||
"preserveConstEnums": false,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
"emitDecoratorMetadata": true,
|
||||
"importsNotUsedAsValues": "error"
|
||||
},
|
||||
"include": ["declarations/**/*.ts", "types/**/*.ts", "src/main.ts", "src/main/**/*.ts", "src/services/**/*.ts"]
|
||||
}
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"noImplicitAny": true,
|
||||
"removeComments": true,
|
||||
"sourceMap": true,
|
||||
"preserveConstEnums": false
|
||||
"preserveConstEnums": false,
|
||||
"importsNotUsedAsValues": "error"
|
||||
},
|
||||
"include": [
|
||||
"declarations/**/*.ts",
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* @see MethodDecorator
|
||||
*/
|
||||
type IpcControllerMethodDecorator = <T>(
|
||||
target: IIpcController,
|
||||
target: IpcController,
|
||||
propertyKey: string,
|
||||
descriptor: TypedPropertyDescriptor<T>
|
||||
) => void;
|
||||
|
|
|
@ -2,26 +2,25 @@ declare const enum IpcChannel {
|
|||
NHENTAI_SAVE_FAVORITES = 'NHENTAI_SAVE_FAVORITES',
|
||||
}
|
||||
|
||||
interface IIpcPayload {
|
||||
type IpcPayload = {
|
||||
id: string;
|
||||
data: unknown;
|
||||
}
|
||||
};
|
||||
|
||||
interface IIpcResponse {
|
||||
type IpcResponse = {
|
||||
id: string;
|
||||
success: boolean;
|
||||
data?: unknown;
|
||||
// just the error message
|
||||
error?: string;
|
||||
}
|
||||
};
|
||||
|
||||
interface IIpcClient {
|
||||
type IpcClient = {
|
||||
ask: (channel: IpcChannel, 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
|
||||
interface IIpcController {
|
||||
get(): IIpcController;
|
||||
}
|
||||
type IpcController = {
|
||||
get(): IpcController;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue