From 00ebd0e5c8ed5103c7e9c1c842fea99fa1a8aaf2 Mon Sep 17 00:00:00 2001 From: Xymorot Date: Sat, 8 Feb 2020 23:26:57 +0100 Subject: [PATCH] refactor: re-do source structure with InversifyJS (dependency injection) and adjust meta processes --- .eslintignore | 3 - .eslintrc.json | 45 +++++-- .gitignore | 6 - .mocharc.yml | 2 +- .nycrc.yml | 1 + .prettierignore | 3 - CONTRIBUTING.md | 6 +- buildfile.js | 6 +- forge.config.js | 7 +- mocks/electron.ts | 2 +- package-lock.json | 5 + package.json | 10 +- {tests => src}/main.spec.ts | 9 +- src/main.ts | 59 ++------- src/main/core/app-path.ts | 6 + src/main/core/container.ts | 19 +++ .../main/core}/database.spec.ts | 2 +- src/main/{services => core}/database.ts | 2 +- .../services => src/main/core}/dev.spec.ts | 2 +- src/main/{services => core}/dev.ts | 0 src/{types => main/core}/error.ts | 0 src/main/modules/app-window/app-window.ts | 59 +++++++++ src/main/modules/app-window/i-app-window.ts | 7 + .../modules/app-window/main-app-window.ts | 13 ++ .../api.ts => modules/ipc/ipc-server.ts} | 22 ++-- src/main/modules/nhentai/i-nhentai-api.ts | 4 + src/main/modules/nhentai/nhentai-api.ts | 124 ++++++++++++++++++ .../modules/nhentai/nhentai-ipc-server.ts | 18 +++ src/main/modules/session/i-session.ts | 3 + src/main/modules/session/session.ts | 21 +++ .../main/modules/store}/store.mock.ts | 4 +- .../main/modules/store}/store.spec.ts | 7 +- src/main/{services => modules/store}/store.ts | 5 +- src/main/modules/web-crawler/i-web-crawler.ts | 7 + .../modules/web-crawler}/web-crawler.spec.ts | 10 +- src/main/modules/web-crawler/web-crawler.ts | 65 +++++++++ src/main/services/error.ts | 12 -- src/main/services/nhentai-crawler.ts | 110 ---------------- src/main/services/session.ts | 16 --- src/main/services/web-crawler.ts | 56 -------- src/renderer/.eslintrc.json | 6 + .../renderer/services/utils.spec.ts | 2 +- {tests => src}/services/uuid.spec.ts | 2 +- templates/index.js | 4 +- tests/.eslintrc.json | 11 -- tests/setup/after.ts | 4 - tests/setup/before.ts | 17 --- tsconfig.json | 8 +- .../constructor.ts => types/constructor.d.ts | 0 .../deep-partial.d.ts | 0 src/types/http.ts => types/http.d.ts | 2 +- src/types/ipc.ts => types/ipc.d.ts | 2 +- tests/types/mock.ts => types/mock.d.ts | 0 53 files changed, 463 insertions(+), 353 deletions(-) rename {tests => src}/main.spec.ts (99%) create mode 100644 src/main/core/app-path.ts create mode 100644 src/main/core/container.ts rename {tests/main/services => src/main/core}/database.spec.ts (84%) rename src/main/{services => core}/database.ts (97%) rename {tests/main/services => src/main/core}/dev.spec.ts (90%) rename src/main/{services => core}/dev.ts (100%) rename src/{types => main/core}/error.ts (100%) create mode 100644 src/main/modules/app-window/app-window.ts create mode 100644 src/main/modules/app-window/i-app-window.ts create mode 100644 src/main/modules/app-window/main-app-window.ts rename src/main/{controllers/api.ts => modules/ipc/ipc-server.ts} (56%) create mode 100644 src/main/modules/nhentai/i-nhentai-api.ts create mode 100644 src/main/modules/nhentai/nhentai-api.ts create mode 100644 src/main/modules/nhentai/nhentai-ipc-server.ts create mode 100644 src/main/modules/session/i-session.ts create mode 100644 src/main/modules/session/session.ts rename {tests/main/services => src/main/modules/store}/store.mock.ts (81%) rename {tests/main/services => src/main/modules/store}/store.spec.ts (92%) rename src/main/{services => modules/store}/store.ts (92%) create mode 100644 src/main/modules/web-crawler/i-web-crawler.ts rename {tests/main/services => src/main/modules/web-crawler}/web-crawler.spec.ts (84%) create mode 100644 src/main/modules/web-crawler/web-crawler.ts delete mode 100644 src/main/services/error.ts delete mode 100644 src/main/services/nhentai-crawler.ts delete mode 100644 src/main/services/session.ts delete mode 100644 src/main/services/web-crawler.ts create mode 100644 src/renderer/.eslintrc.json rename {tests => src}/renderer/services/utils.spec.ts (98%) rename {tests => src}/services/uuid.spec.ts (90%) delete mode 100644 tests/.eslintrc.json delete mode 100644 tests/setup/after.ts delete mode 100644 tests/setup/before.ts rename src/types/constructor.ts => types/constructor.d.ts (100%) rename src/types/deep-partial.ts => types/deep-partial.d.ts (100%) rename src/types/http.ts => types/http.d.ts (98%) rename src/types/ipc.ts => types/ipc.d.ts (94%) rename tests/types/mock.ts => types/mock.d.ts (100%) diff --git a/.eslintignore b/.eslintignore index b569757..1bf353e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -9,10 +9,7 @@ node_modules .nyc_output /src/**/*.js -/tests/**/*.js /mocks/**/*.js /frontend -/store-backup /test-paths -/store /out diff --git a/.eslintrc.json b/.eslintrc.json index e094c0f..5ccbf1a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,10 +1,13 @@ { "root": true, + "plugins": ["@typescript-eslint", "import"], "extends": ["eslint:recommended", "prettier"], - "plugins": ["import"], "parserOptions": { "ecmaVersion": 2019 }, + "settings": { + "import/core-modules": ["electron"] + }, "env": { "browser": true, "node": true @@ -26,7 +29,7 @@ "error", { "devDependencies": [ - "tests/**/*", + "src/**/*.spec.*", "mocks/**/*", "src/renderer/**/*", "templates/**/*", @@ -35,7 +38,16 @@ ] } ], - "import/no-default-export": "error" + "import/no-default-export": "error", + "import/first": "error", + "import/order": [ + "error", + { + "alphabetize": { + "order": "asc" + } + } + ] }, "overrides": [ { @@ -51,7 +63,10 @@ "project": "./tsconfig.json" }, "settings": { - "import/core-modules": ["electron"] + "import/extensions": [".ts", "d.ts", ".js", ".json"], + "import/parsers": { + "@typescript-eslint/parser": [".ts", "d.ts"] + } }, "rules": { "no-console": "error", @@ -93,7 +108,21 @@ "@typescript-eslint/unbound-method": "off", "@typescript-eslint/ban-ts-ignore": "off", "@typescript-eslint/no-use-before-define": "off", - "@typescript-eslint/no-misused-promises": ["error", { "checksVoidReturn": false }] + "@typescript-eslint/no-misused-promises": ["error", { "checksVoidReturn": false }], + "@typescript-eslint/member-ordering": "error" + } + }, + { + "files": ["**/*.{spec,mock}.*"], + "rules": { + "no-unused-expressions": "off", + + "import/order": "off", + + "@typescript-eslint/no-magic-numbers": "off", + "@typescript-eslint/typedef": "off", + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/explicit-function-return-type": "off" } }, { @@ -101,12 +130,6 @@ "rules": { "import/no-default-export": "off" } - }, - { - "files": ["src/renderer/**/*.*"], - "parserOptions": { - "sourceType": "module" - } } ] } diff --git a/.gitignore b/.gitignore index 4bae92d..e4a9930 100644 --- a/.gitignore +++ b/.gitignore @@ -7,18 +7,12 @@ node_modules # generated code /src/**/*.js /src/**/*.js.map -/tests/**/*.js -/tests/**/*.js.map /mocks/**/*.js /mocks/**/*.js.map /frontend # created by testing -/store-backup /test-paths -# managed by application -/store - # built app /out diff --git a/.mocharc.yml b/.mocharc.yml index ef5dd2b..9928664 100644 --- a/.mocharc.yml +++ b/.mocharc.yml @@ -1,2 +1,2 @@ # https://github.com/mochajs/mocha/blob/master/example/config -spec: 'tests/**/*spec.js' +spec: 'src/**/*.spec.js' diff --git a/.nycrc.yml b/.nycrc.yml index 75b8c5c..abc9eba 100644 --- a/.nycrc.yml +++ b/.nycrc.yml @@ -11,6 +11,7 @@ report-dir: './.nyc_output/coverage' include: - 'src/**' exclude: + - 'src/**/*.spec.*' - 'src/main/entities/**' watermarks: statements: [80, 95] diff --git a/.prettierignore b/.prettierignore index 4e864f7..abecd45 100644 --- a/.prettierignore +++ b/.prettierignore @@ -15,10 +15,7 @@ node_modules .nyc_output /src/**/*.js -/tests/**/*.js /mocks/**/*.js /frontend -/store-backup /test-paths -/store /out diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b657577..0d92596 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -113,7 +113,7 @@ The application uses [SQLite3](https://www.npmjs.com/package/sqlite3) as a datab #### Database Migrations -Migrations are stored in [src/main/migrations](src/main/migrations) and handled by typeorm. Migrations are run on app start inside [database.ts](src/main/services/database.ts). +Migrations are stored in [src/main/migrations](src/main/migrations) and handled by typeorm. Migrations are run on app start inside [database.ts](src/main/core/database.ts). To auto-generate a migration: `node_modules/.bin/typeorm migration:generate -n -c ` @@ -140,6 +140,8 @@ The testing framework of choice is [Mocha](https://mochajs.org/). Call `npm run - HTTP server mocking is done by [nock](https://github.com/nock/nock) - property based testing is made possible by [fast-check](https://github.com/dubzzz/fast-check) +For the creation of test files look at existing ones, they are named `*.spec.ts`. + #### Mocks There are 2 ways in which mocks are defined/used: @@ -147,7 +149,7 @@ There are 2 ways in which mocks are defined/used: 0. for external modules, in [mocks](mocks) - uses the [rewiremock](https://github.com/theKashey/rewiremock) package - use this only when there is some magic happening like for electron which normally runs in its own node process -1. for own modules, just beside their test file in [tests](tests) +1. for own modules, just beside their file - name the file `*.mock.ts` and use existing mock files for orientation on how to build them - use sparingly and only when not having a mock makes it more complex e.g. for modules which interact with the file system diff --git a/buildfile.js b/buildfile.js index 961177d..e13672f 100644 --- a/buildfile.js +++ b/buildfile.js @@ -1,10 +1,10 @@ const fs = require('fs'); const path = require('path'); -const minimist = require('minimist'); -const webpackConfig = require('./webpack.config'); -const templating = require('./templates'); const { watch } = require('chokidar'); const { debounce } = require('lodash'); +const minimist = require('minimist'); +const templating = require('./templates'); +const webpackConfig = require('./webpack.config'); /** @type {Object} */ const argv = minimist(process.argv); diff --git a/forge.config.js b/forge.config.js index 6a7220f..a70cebb 100644 --- a/forge.config.js +++ b/forge.config.js @@ -7,11 +7,9 @@ const ignoreList = [ /^\/\.nyc_output($|\/)/, /^\/declarations($|\/)/, /^\/mocks($|\/)/, - /^\/store($|\/)/, - /^\/store-backup($|\/)/, /^\/templates($|\/)/, /^\/test-paths($|\/)/, - /^\/tests($|\/)/, + /^\/types($|\/)/, /^\/workspace($|\/)/, /^\/\.editorconfig/, @@ -29,6 +27,9 @@ const ignoreList = [ /^\/webpack\.config\.js/, /^\/node_modules\/\.cache($|\/)/, + // test and mock files: + /^\/src\/.*\.(spec|mock)\.(ts|js(\.map)?)/, + // original typescript source and generated source map files: /^\/src\/.*\.(ts|js\.map)/, /^\/src\/.*\.eslintrc\.json/, ]; diff --git a/mocks/electron.ts b/mocks/electron.ts index 3963874..b4e0f14 100644 --- a/mocks/electron.ts +++ b/mocks/electron.ts @@ -1,6 +1,6 @@ -import WebContents = Electron.WebContents; import path from 'path'; import { rewiremock } from './rewiremock'; +import WebContents = Electron.WebContents; const electronMock: DeepPartial = { app: { diff --git a/package-lock.json b/package-lock.json index 5315b90..94c184b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5446,6 +5446,11 @@ "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", "dev": true }, + "inversify": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/inversify/-/inversify-5.0.1.tgz", + "integrity": "sha512-Ieh06s48WnEYGcqHepdsJUIJUXpwH5o5vodAX+DK2JA/gjy4EbEcQZxw+uFfzysmKjiLXGYwNG3qDZsKVMcINQ==" + }, "invert-kv": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", diff --git a/package.json b/package.json index 6fcca1d..3d2dc51 100644 --- a/package.json +++ b/package.json @@ -25,14 +25,8 @@ "watch:ts": "tsc -w --pretty --preserveWatchOutput", "build": "concurrently -c green,yellow,cyan -n webpack,index,typescript \"npm run build:webpack\" \"npm run build:index\" \"npm run build:ts\"", "watch": "concurrently -c green,yellow,cyan -n webpack,index,typescript \"npm run watch:webpack\" \"npm run watch:index\" \"npm run watch:ts\"", - "test:before": "node tests/setup/before.js", - "test:after": "node tests/setup/after.js", - "pretest:fast": "npm run test:before", "test:fast": "mocha --grep @slow --invert", - "posttest:fast": "npm run test:after", - "pretest": "npm run test:before", "test": "mocha", - "posttest": "npm run test:after", "coverage:fast": "nyc npm run test:fast", "coverage": "nyc npm run test", "prelint": "eslint --print-config forge.config.js | eslint-config-prettier-check", @@ -40,7 +34,7 @@ "lint:fix": "eslint ./**/*.* --fix", "prettier": "prettier -c **/*.*", "prettier:fix": "prettier --write **/*.*", - "fix": "npm run lint:check && npm run lint:fix && npm run prettier:fix", + "fix": "npm run lint:fix && npm run prettier:fix", "forge:make": "electron-forge --platform win32 --arch x64 make", "forge": "npm audit && npm run build && npm run forge:make", "precommit": "npm run build && npm run prettier && npm run lint && npm run coverage:fast", @@ -48,9 +42,11 @@ }, "dependencies": { "fs-extra": "^8.1.0", + "inversify": "^5.0.1", "jsdom": "^15.2.1", "minimist": "^1.2.0", "node-fetch": "^2.6.0", + "reflect-metadata": "^0.1.13", "sqlite3": "^4.1.1", "typeorm": "^0.2.21", "uuid": "^3.3.3" diff --git a/tests/main.spec.ts b/src/main.spec.ts similarity index 99% rename from tests/main.spec.ts rename to src/main.spec.ts index c642087..78b0d9e 100644 --- a/tests/main.spec.ts +++ b/src/main.spec.ts @@ -1,12 +1,13 @@ -import rewiremock from 'rewiremock'; -rewiremock.disable(); - -import { expect } from 'chai'; import * as electron from 'electron'; +import { expect } from 'chai'; +import rewiremock from 'rewiremock'; + import 'mocha'; import { Application } from 'spectron'; import packageJson from '../package.json'; +rewiremock.disable(); + describe('Application @slow', function() { this.timeout(20000); diff --git a/src/main.ts b/src/main.ts index 51e8dab..4883f38 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,60 +1,22 @@ -import { app, BrowserWindow } from 'electron'; -import BrowserWindowConstructorOptions = Electron.BrowserWindowConstructorOptions; -import os from 'os'; -import path from 'path'; -import packageJson from '../package.json'; -import './main/controllers/api'; -import { isDev } from './main/services/dev'; -import * as session from './main/services/session'; - -export let mainWindow: Electron.BrowserWindow; - -export const appPath = path.resolve(app.getPath('userData'), `${packageJson.version}${isDev() ? '-dev' : ''}`); +import { app } from 'electron'; +import { container } from './main/core/container'; +import { isDev } from './main/core/dev'; +import { IAppWindow } from './main/modules/app-window/i-app-window'; +import { ISession } from './main/modules/session/i-session'; async function createWindow(): Promise { + const session: ISession = container.get(Symbol.for('session')); session.setHeaders(); - // universal options - let options: BrowserWindowConstructorOptions = { - width: 1600, - height: 900, - webPreferences: { - nodeIntegration: true, - }, - }; - - // platform specifics - switch (os.platform()) { - case 'win32': - options = { - ...options, - ...{ - icon: 'resources/icon.ico', - }, - }; - break; - default: - break; - } - - // Create the browser window. - mainWindow = new BrowserWindow(options); + const appWindowMain: IAppWindow = container.get(Symbol.for('app-window-main')); // and load the index.html of the app. - await mainWindow.loadFile('frontend/index.html'); + await appWindowMain.open(); // Open the DevTools. if (isDev()) { - mainWindow.webContents.openDevTools(); + appWindowMain.window.webContents.openDevTools(); } - - // Emitted when the window is closed. - mainWindow.on('closed', () => { - // Dereference the window object, usually you would store windows - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - mainWindow = null; - }); } // This method will be called when Electron has finished @@ -74,7 +36,8 @@ 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. - if (mainWindow === null) { + const appWindowMain: IAppWindow = container.get(Symbol.for('app-window-main')); + if (appWindowMain.isClosed()) { await createWindow(); } }); diff --git a/src/main/core/app-path.ts b/src/main/core/app-path.ts new file mode 100644 index 0000000..ed319bb --- /dev/null +++ b/src/main/core/app-path.ts @@ -0,0 +1,6 @@ +import { app } from 'electron'; +import path from 'path'; +import packageJson from '../../../package.json'; +import { isDev } from './dev'; + +export const appPath = path.resolve(app.getPath('userData'), `${packageJson.version}${isDev() ? '-dev' : ''}`); diff --git a/src/main/core/container.ts b/src/main/core/container.ts new file mode 100644 index 0000000..894c4f5 --- /dev/null +++ b/src/main/core/container.ts @@ -0,0 +1,19 @@ +import 'reflect-metadata'; +import { Container } from 'inversify'; +import { MainAppWindow } from '../modules/app-window/main-app-window'; +import { NhentaiApi } from '../modules/nhentai/nhentai-api'; +import { NhentaiIpcServer } from '../modules/nhentai/nhentai-ipc-server'; +import { Session } from '../modules/session/session'; +import { WebCrawler } from '../modules/web-crawler/web-crawler'; + +export const container = new Container({ defaultScope: 'Singleton' }); + +container.bind(Symbol.for('web-crawler')).to(WebCrawler); + +container.bind(Symbol.for('nhentai-api')).to(NhentaiApi); +container.bind(Symbol.for('nhentai-ipc-server')).to(NhentaiIpcServer); +container.get(Symbol.for('nhentai-ipc-server')); + +container.bind(Symbol.for('app-window-main')).to(MainAppWindow); + +container.bind(Symbol.for('session')).to(Session); diff --git a/tests/main/services/database.spec.ts b/src/main/core/database.spec.ts similarity index 84% rename from tests/main/services/database.spec.ts rename to src/main/core/database.spec.ts index fe0cdf8..e0efe51 100644 --- a/tests/main/services/database.spec.ts +++ b/src/main/core/database.spec.ts @@ -3,7 +3,7 @@ import '../../../mocks/electron'; import { expect } from 'chai'; import 'mocha'; -import { Databases, getConnection } from '../../../src/main/services/database'; +import { Databases, getConnection } from './database'; describe('Database Service', () => { before(() => { diff --git a/src/main/services/database.ts b/src/main/core/database.ts similarity index 97% rename from src/main/services/database.ts rename to src/main/core/database.ts index e907b40..7b0ce90 100644 --- a/src/main/services/database.ts +++ b/src/main/core/database.ts @@ -1,7 +1,7 @@ import path from 'path'; import { Connection, createConnection as ormCreateConnection } from 'typeorm'; import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'; -import { appPath } from '../../main'; +import { appPath } from './app-path'; export enum Databases { LIBRARY = 'library', diff --git a/tests/main/services/dev.spec.ts b/src/main/core/dev.spec.ts similarity index 90% rename from tests/main/services/dev.spec.ts rename to src/main/core/dev.spec.ts index 649a076..094da15 100644 --- a/tests/main/services/dev.spec.ts +++ b/src/main/core/dev.spec.ts @@ -3,7 +3,7 @@ import '../../../mocks/electron'; import { expect } from 'chai'; import 'mocha'; -import { isDev } from '../../../src/main/services/dev'; +import { isDev } from './dev'; describe('Development Mode Service', () => { before(() => { diff --git a/src/main/services/dev.ts b/src/main/core/dev.ts similarity index 100% rename from src/main/services/dev.ts rename to src/main/core/dev.ts diff --git a/src/types/error.ts b/src/main/core/error.ts similarity index 100% rename from src/types/error.ts rename to src/main/core/error.ts diff --git a/src/main/modules/app-window/app-window.ts b/src/main/modules/app-window/app-window.ts new file mode 100644 index 0000000..550793c --- /dev/null +++ b/src/main/modules/app-window/app-window.ts @@ -0,0 +1,59 @@ +import { BrowserWindow } from 'electron'; +import os from 'os'; +import { injectable } from 'inversify'; +import { IAppWindow } from './i-app-window'; +import BrowserWindowConstructorOptions = Electron.BrowserWindowConstructorOptions; + +let defaultOptions = { + width: 1600, + height: 900, + webPreferences: { + nodeIntegration: false, + }, +}; + +switch (os.platform()) { + case 'win32': + defaultOptions = { + ...defaultOptions, + ...{ + icon: 'resources/icon.ico', + }, + }; + break; + default: + break; +} + +@injectable() +export abstract class AppWindow implements IAppWindow { + protected _window: BrowserWindow | null; + + protected constructor(options: BrowserWindowConstructorOptions = {}) { + this.initialize(options); + } + + public get window(): BrowserWindow { + return this._window; + } + + public open(): Promise { + if (this.isClosed()) { + this.initialize(); + } + + return this._window.loadFile('frontend/index.html'); + } + + public isClosed(): boolean { + return !this._window; + } + + private initialize(options: BrowserWindowConstructorOptions = {}): void { + this._window = new BrowserWindow({ ...defaultOptions, ...options }); + + this._window.on('closed', () => { + this._window = null; + }); + } +} diff --git a/src/main/modules/app-window/i-app-window.ts b/src/main/modules/app-window/i-app-window.ts new file mode 100644 index 0000000..e90a575 --- /dev/null +++ b/src/main/modules/app-window/i-app-window.ts @@ -0,0 +1,7 @@ +import BrowserWindow = Electron.BrowserWindow; + +export interface IAppWindow { + window: BrowserWindow; + open(): Promise; + isClosed(): boolean; +} diff --git a/src/main/modules/app-window/main-app-window.ts b/src/main/modules/app-window/main-app-window.ts new file mode 100644 index 0000000..b24259e --- /dev/null +++ b/src/main/modules/app-window/main-app-window.ts @@ -0,0 +1,13 @@ +import { injectable } from 'inversify'; +import { AppWindow } from './app-window'; + +@injectable() +export class MainAppWindow extends AppWindow { + public constructor() { + super({ + webPreferences: { + nodeIntegration: true, + }, + }); + } +} diff --git a/src/main/controllers/api.ts b/src/main/modules/ipc/ipc-server.ts similarity index 56% rename from src/main/controllers/api.ts rename to src/main/modules/ipc/ipc-server.ts index 75177d5..5e9555a 100644 --- a/src/main/controllers/api.ts +++ b/src/main/modules/ipc/ipc-server.ts @@ -1,10 +1,11 @@ import { ipcMain } from 'electron'; +import { injectable } from 'inversify'; import IpcMainEvent = Electron.IpcMainEvent; -import { mainWindow } from '../../main'; -import { isLoggedIn, login } from '../services/nhentai-crawler'; +import BrowserWindow = Electron.BrowserWindow; -export const ipcServer: IIpcServer = { - answer: (channel: IpcChannels, handler: (data?: any) => Promise): void => { +@injectable() +export abstract class IpcServer { + protected answer(channel: IpcChannels, handler: (data?: any) => Promise): void { ipcMain.on(channel, (event: IpcMainEvent, payload: IIpcPayload) => { handler(payload.data) .then((result: any) => { @@ -24,12 +25,9 @@ export const ipcServer: IIpcServer = { event.reply(channel, response); }); }); - }, - send: (channel: IpcChannels, data: any): void => { - mainWindow.webContents.send(channel, data); - }, -}; + } -ipcServer.answer(IpcChannels.LOGIN, (credentials: ICredentials) => login(credentials.name, credentials.password)); - -ipcServer.answer(IpcChannels.LOGGED_IN, isLoggedIn); + protected send(window: BrowserWindow, channel: IpcChannels, data: any): void { + window.webContents.send(channel, data); + } +} diff --git a/src/main/modules/nhentai/i-nhentai-api.ts b/src/main/modules/nhentai/i-nhentai-api.ts new file mode 100644 index 0000000..06af055 --- /dev/null +++ b/src/main/modules/nhentai/i-nhentai-api.ts @@ -0,0 +1,4 @@ +export interface INhentaiApi { + isLoggedIn(): Promise; + login(name: string, password: string): Promise; +} diff --git a/src/main/modules/nhentai/nhentai-api.ts b/src/main/modules/nhentai/nhentai-api.ts new file mode 100644 index 0000000..119f84e --- /dev/null +++ b/src/main/modules/nhentai/nhentai-api.ts @@ -0,0 +1,124 @@ +import { inject, injectable } from 'inversify'; +import { JSDOM } from 'jsdom'; +import { RequestInit, Response } from 'node-fetch'; +import { Errors, RenaiError } from '../../core/error'; +import { IWebCrawler } from '../web-crawler/i-web-crawler'; +import { INhentaiApi } from './i-nhentai-api'; + +const domain = 'nhentai.net'; +const url = `https://${domain}/`; + +const paths = { + books: 'g/', + login: 'login/', + favorites: 'favorites/', +}; + +const usernameInput = 'username_or_email'; +const passwordInput = 'password'; + +interface ILoginMeta { + [key: string]: string; +} + +interface ILoginAuth { + [usernameInput]: string; + [passwordInput]: string; +} + +interface ILoginParams extends ILoginMeta, ILoginAuth {} + +@injectable() +export class NhentaiApi implements INhentaiApi { + private webCrawler: IWebCrawler; + + public constructor(@inject(Symbol.for('web-crawler')) webCrawler: IWebCrawler) { + this.webCrawler = webCrawler; + } + + public isLoggedIn(): Promise { + return this.webCrawler + .fetch(`${url}${paths.favorites}`, { redirect: 'manual' }) + .then((res: Response) => res.status === HttpCode.OK); + } + + public login(name: string, password: string): Promise { + return this.getLoginMeta() + .then((meta: ILoginMeta) => { + const loginParams: ILoginParams = { + ...meta, + ...{ + [usernameInput]: name, + [passwordInput]: password, + }, + }; + + return this.postNHentai(paths.login, { + body: encodeURI( + Object.keys(loginParams) + .map((key: keyof ILoginParams) => `${key}=${loginParams[key]}`) + .join('&') + ), + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + redirect: 'manual', + }); + }) + .then(() => {}) + .catch(() => Promise.reject(new RenaiError(Errors.ELOGINFAIL))); + } + + private getNHentai(path: string): Promise { + return this.webCrawler + .fetch(`${url}${path}`) + .then((res: Response) => res.text()) + .then((text: string) => { + const { document } = new JSDOM(text).window; + return document; + }); + } + + private postNHentai(path: string, requestInit: RequestInit = {}): Promise { + const postUrl = `${url}${path}`; + return this.webCrawler.fetch(postUrl, { + ...requestInit, + ...{ + headers: { + ...requestInit.headers, + ...{ + Host: domain, + Referer: postUrl, + }, + }, + }, + method: 'post', + }); + } + + private getLoginMeta(): Promise { + return this.getNHentai(paths.login).then((document: Document) => { + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < document.forms.length; i++) { + const form: HTMLFormElement = document.forms[i]; + const valueStore: ILoginMeta = {}; + let isLoginForm = false; + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let j = 0; j < form.elements.length; j++) { + const input = form.elements[j]; + const name = input.getAttribute('name'); + + if (name === usernameInput || name === passwordInput) { + isLoginForm = true; + } else if (name) { + valueStore[name] = input.getAttribute('value'); + } + } + if (isLoginForm) { + return valueStore; + } + } + return Promise.reject(new RenaiError(Errors.ENOLOGIN)); + }); + } +} diff --git a/src/main/modules/nhentai/nhentai-ipc-server.ts b/src/main/modules/nhentai/nhentai-ipc-server.ts new file mode 100644 index 0000000..8351b86 --- /dev/null +++ b/src/main/modules/nhentai/nhentai-ipc-server.ts @@ -0,0 +1,18 @@ +import { inject, injectable } from 'inversify'; +import { IpcServer } from '../ipc/ipc-server'; +import { INhentaiApi } from './i-nhentai-api'; + +@injectable() +export class NhentaiIpcServer extends IpcServer { + private nhentaiApi: INhentaiApi; + + public constructor(@inject(Symbol.for('nhentai-api')) nhentaiApi: INhentaiApi) { + super(); + this.nhentaiApi = nhentaiApi; + + this.answer(IpcChannels.LOGIN, (credentials: ICredentials) => + this.nhentaiApi.login(credentials.name, credentials.password) + ); + this.answer(IpcChannels.LOGGED_IN, () => this.nhentaiApi.isLoggedIn()); + } +} diff --git a/src/main/modules/session/i-session.ts b/src/main/modules/session/i-session.ts new file mode 100644 index 0000000..32d84d0 --- /dev/null +++ b/src/main/modules/session/i-session.ts @@ -0,0 +1,3 @@ +export interface ISession { + setHeaders(): void; +} diff --git a/src/main/modules/session/session.ts b/src/main/modules/session/session.ts new file mode 100644 index 0000000..504910a --- /dev/null +++ b/src/main/modules/session/session.ts @@ -0,0 +1,21 @@ +import { session } from 'electron'; +import { injectable } from 'inversify'; +import { ISession } from './i-session'; +import OnHeadersReceivedDetails = Electron.OnHeadersReceivedDetails; + +@injectable() +export class Session implements ISession { + public setHeaders(): void { + // these headers only work on web requests, file:// protocol is handled via meta tags in the html + session.defaultSession.webRequest.onHeadersReceived( + (details: OnHeadersReceivedDetails, callback: (response: {}) => void) => { + callback({ + responseHeaders: { + ...details.responseHeaders, + 'Content-Security-Policy': ["default-src 'none'"], + }, + }); + } + ); + } +} diff --git a/tests/main/services/store.mock.ts b/src/main/modules/store/store.mock.ts similarity index 81% rename from tests/main/services/store.mock.ts rename to src/main/modules/store/store.mock.ts index be48a37..92e230c 100644 --- a/tests/main/services/store.mock.ts +++ b/src/main/modules/store/store.mock.ts @@ -1,5 +1,5 @@ -const store = require('../../../src/main/services/store'); -import { load, save } from '../../../src/main/services/store'; +import { load, save } from './store'; +const store = require('./store'); interface IStoreMock extends IMock { original: { diff --git a/tests/main/services/store.spec.ts b/src/main/modules/store/store.spec.ts similarity index 92% rename from tests/main/services/store.spec.ts rename to src/main/modules/store/store.spec.ts index 83bd041..8b62730 100644 --- a/tests/main/services/store.spec.ts +++ b/src/main/modules/store/store.spec.ts @@ -1,13 +1,14 @@ import rewiremock from 'rewiremock'; -import '../../../mocks/electron'; +import '../../../../mocks/electron'; import { expect } from 'chai'; import fs from 'fs-extra'; import 'mocha'; import path from 'path'; -import { load, save, StoreKeys } from '../../../src/main/services/store'; +import { appPath } from '../../core/app-path'; +import { load, save, StoreKeys } from './store'; -const storeDirectory = path.resolve('store'); +const storeDirectory = path.resolve(appPath, 'store'); describe('Store Service', function() { this.timeout(10000); diff --git a/src/main/services/store.ts b/src/main/modules/store/store.ts similarity index 92% rename from src/main/services/store.ts rename to src/main/modules/store/store.ts index 3611e63..15cb005 100644 --- a/src/main/services/store.ts +++ b/src/main/modules/store/store.ts @@ -1,5 +1,6 @@ -import fs from 'fs-extra'; import path from 'path'; +import fs from 'fs-extra'; +import { appPath } from '../../core/app-path'; export const enum StoreKeys { 'COOKIES' = 'cookies', @@ -17,7 +18,7 @@ let store: Store = {}; let synced = false; const options: IStoreOptions = { - path: path.resolve('store', 'store.json'), + path: path.resolve(appPath, 'store', 'store.json'), }; const folder = path.dirname(options.path); diff --git a/src/main/modules/web-crawler/i-web-crawler.ts b/src/main/modules/web-crawler/i-web-crawler.ts new file mode 100644 index 0000000..9e64c80 --- /dev/null +++ b/src/main/modules/web-crawler/i-web-crawler.ts @@ -0,0 +1,7 @@ +import { CookieJar } from 'jsdom'; +import { RequestInit, Response } from 'node-fetch'; + +export interface IWebCrawler { + cookieJar: CookieJar; + fetch(url: string, requestInit?: RequestInit): Promise; +} diff --git a/tests/main/services/web-crawler.spec.ts b/src/main/modules/web-crawler/web-crawler.spec.ts similarity index 84% rename from tests/main/services/web-crawler.spec.ts rename to src/main/modules/web-crawler/web-crawler.spec.ts index f9b8439..a3ddad4 100644 --- a/tests/main/services/web-crawler.spec.ts +++ b/src/main/modules/web-crawler/web-crawler.spec.ts @@ -1,5 +1,5 @@ import rewiremock from 'rewiremock'; -import '../../../mocks/electron'; +import '../../../../mocks/electron'; import { expect } from 'chai'; import { CookieJar } from 'jsdom'; @@ -7,8 +7,8 @@ import 'mocha'; import nock from 'nock'; import { Response } from 'node-fetch'; import sinon from 'sinon'; -import { fetch } from '../../../src/main/services/web-crawler'; -import { storeMock } from './store.mock'; +import { WebCrawler } from './web-crawler'; +import { storeMock } from '../store/store.mock'; describe('Web Crawler', function() { this.timeout(2000); @@ -52,7 +52,9 @@ describe('Web Crawler', function() { ) .persist(); - const res: Response = await fetch(testUrl); + const webCrawler = new WebCrawler(); + + const res: Response = await webCrawler.fetch(testUrl); expect(callback.callCount).to.equal(1, 'multiple requests (or none) are sent when only one should be'); const json = await res.json(); expect(json).to.deep.equal([{ id: 12, comment: 'Hey there' }], 'response body is incorrect'); diff --git a/src/main/modules/web-crawler/web-crawler.ts b/src/main/modules/web-crawler/web-crawler.ts new file mode 100644 index 0000000..7cd1032 --- /dev/null +++ b/src/main/modules/web-crawler/web-crawler.ts @@ -0,0 +1,65 @@ +import { injectable } from 'inversify'; +import { CookieJar } from 'jsdom'; +import nodeFetch, { RequestInit, Response } from 'node-fetch'; +import { Errors, RenaiError } from '../../core/error'; +import { load, save, StoreKeys } from '../store/store'; +import { IWebCrawler } from './i-web-crawler'; + +@injectable() +export class WebCrawler implements IWebCrawler { + public cookieJar: CookieJar; + + private initialized: boolean; + + public constructor() { + this.initialized = false; + this.cookieJar = new CookieJar(); + } + + public fetch(url: string, requestInit: RequestInit = {}): Promise { + return this.init().then(() => { + const cookiedInit = { + ...requestInit, + ...{ + headers: { + ...requestInit.headers, + ...{ + Cookie: this.cookieJar.getCookieStringSync(url), + }, + }, + }, + }; + return nodeFetch(url, cookiedInit).then((res: Response) => { + this.setCookies(res.headers.raw()['set-cookie'], url).catch((reason: any) => { + throw new RenaiError(Errors.ECOOKIESAVEFAIL, reason); + }); + return res; + }); + }); + } + + private init(): Promise { + if (!this.initialized) { + return load(StoreKeys.COOKIES).then((cookies: any) => { + if (cookies !== undefined) { + this.cookieJar = CookieJar.deserializeSync(cookies); + } + this.initialized = true; + }); + } else { + return Promise.resolve(); + } + } + + private setCookies(header: string[], url: string): Promise { + if (header) { + header.forEach((cookie: string) => { + this.cookieJar.setCookieSync(cookie, url); + }); + return save(StoreKeys.COOKIES, this.cookieJar.serializeSync()).catch((reason: any) => { + throw new RenaiError(Errors.ECOOKIESAVEFAIL, reason); + }); + } + return Promise.resolve(); + } +} diff --git a/src/main/services/error.ts b/src/main/services/error.ts deleted file mode 100644 index afc88ff..0000000 --- a/src/main/services/error.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ipcServer } from '../controllers/api'; - -export function throwError(error: any, isFatal: boolean = false): void { - let errorInstance = error; - if (!(errorInstance instanceof Error)) { - errorInstance = new Error(error); - } - if (isFatal) { - throw errorInstance; - } - ipcServer.send(IpcChannels.ERROR, errorInstance); -} diff --git a/src/main/services/nhentai-crawler.ts b/src/main/services/nhentai-crawler.ts deleted file mode 100644 index b48cdc5..0000000 --- a/src/main/services/nhentai-crawler.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { JSDOM } from 'jsdom'; -import { RequestInit, Response } from 'node-fetch'; -import { Errors, RenaiError } from '../../types/error'; -import { fetch } from './web-crawler'; - -const domain = 'nhentai.net'; -const url = `https://${domain}/`; - -const paths = { - books: 'g/', - login: 'login/', - favorites: 'favorites/', -}; - -const usernameInput = 'username_or_email'; -const passwordInput = 'password'; - -interface ILoginMeta { - [key: string]: string; -} - -interface ILoginAuth { - [usernameInput]: string; - [passwordInput]: string; -} - -interface ILoginParams extends ILoginMeta, ILoginAuth {} - -function getNHentai(path: string): Promise { - return fetch(`${url}${path}`) - .then((res: Response) => res.text()) - .then((text: string) => { - const { document } = new JSDOM(text).window; - return document; - }); -} - -function postNHentai(path: string, requestInit: RequestInit = {}): Promise { - const postUrl = `${url}${path}`; - return fetch(postUrl, { - ...requestInit, - ...{ - headers: { - ...requestInit.headers, - ...{ - Host: domain, - Referer: postUrl, - }, - }, - }, - method: 'post', - }); -} - -function getLoginMeta(): Promise { - return getNHentai(paths.login).then((document: Document) => { - // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let i = 0; i < document.forms.length; i++) { - const form: HTMLFormElement = document.forms[i]; - const valueStore: ILoginMeta = {}; - let isLoginForm = false; - // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let j = 0; j < form.elements.length; j++) { - const input = form.elements[j]; - const name = input.getAttribute('name'); - - if (name === usernameInput || name === passwordInput) { - isLoginForm = true; - } else if (name) { - valueStore[name] = input.getAttribute('value'); - } - } - if (isLoginForm) { - return valueStore; - } - } - return Promise.reject(new RenaiError(Errors.ENOLOGIN)); - }); -} - -export function isLoggedIn(): Promise { - return fetch(`${url}${paths.favorites}`, { redirect: 'manual' }).then((res: Response) => res.status === HttpCode.OK); -} - -export function login(name: string, password: string): Promise { - return getLoginMeta() - .then((meta: ILoginMeta) => { - const loginParams: ILoginParams = { - ...meta, - ...{ - [usernameInput]: name, - [passwordInput]: password, - }, - }; - - return postNHentai(paths.login, { - body: encodeURI( - Object.keys(loginParams) - .map((key: keyof ILoginParams) => `${key}=${loginParams[key]}`) - .join('&') - ), - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - redirect: 'manual', - }); - }) - .then(() => {}) - .catch(() => Promise.reject(new RenaiError(Errors.ELOGINFAIL))); -} diff --git a/src/main/services/session.ts b/src/main/services/session.ts deleted file mode 100644 index d10a1d8..0000000 --- a/src/main/services/session.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { session } from 'electron'; -import OnHeadersReceivedDetails = Electron.OnHeadersReceivedDetails; - -export function setHeaders(): void { - // these headers only work on web requests, file:// protocol is handled via meta tags in the html - session.defaultSession.webRequest.onHeadersReceived( - (details: OnHeadersReceivedDetails, callback: (response: {}) => void) => { - callback({ - responseHeaders: { - ...details.responseHeaders, - 'Content-Security-Policy': ["default-src 'none'"], - }, - }); - } - ); -} diff --git a/src/main/services/web-crawler.ts b/src/main/services/web-crawler.ts deleted file mode 100644 index 40c3d9d..0000000 --- a/src/main/services/web-crawler.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { CookieJar } from 'jsdom'; -import nodeFetch, { RequestInit, Response } from 'node-fetch'; -import { Errors, RenaiError } from '../../types/error'; -import { throwError } from './error'; -import { load, save, StoreKeys } from './store'; - -export let cookieJar: CookieJar = new CookieJar(); - -let initialized = false; - -function init(): Promise { - if (!initialized) { - return load(StoreKeys.COOKIES).then((cookies: any) => { - if (cookies !== undefined) { - cookieJar = CookieJar.deserializeSync(cookies); - } - initialized = true; - }); - } else { - return Promise.resolve(); - } -} - -export function fetch(url: string, requestInit: RequestInit = {}): Promise { - return init().then(() => { - const cookiedInit = { - ...requestInit, - ...{ - headers: { - ...requestInit.headers, - ...{ - Cookie: cookieJar.getCookieStringSync(url), - }, - }, - }, - }; - return nodeFetch(url, cookiedInit).then((res: Response) => { - setCookies(res.headers.raw()['set-cookie'], url).catch((reason: any) => { - throwError(new RenaiError(Errors.ECOOKIESAVEFAIL, reason)); - }); - return res; - }); - }); -} - -function setCookies(header: string[], url: string): Promise { - if (header) { - header.forEach((cookie: string) => { - cookieJar.setCookieSync(cookie, url); - }); - return save(StoreKeys.COOKIES, cookieJar.serializeSync()).catch((reason: any) => { - throwError(new RenaiError(Errors.ECOOKIESAVEFAIL, reason)); - }); - } - return Promise.resolve(); -} diff --git a/src/renderer/.eslintrc.json b/src/renderer/.eslintrc.json new file mode 100644 index 0000000..69e69de --- /dev/null +++ b/src/renderer/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "extends": ["../../.eslintrc.json"], + "parserOptions": { + "sourceType": "module" + } +} diff --git a/tests/renderer/services/utils.spec.ts b/src/renderer/services/utils.spec.ts similarity index 98% rename from tests/renderer/services/utils.spec.ts rename to src/renderer/services/utils.spec.ts index 6bda833..7c45b4b 100644 --- a/tests/renderer/services/utils.spec.ts +++ b/src/renderer/services/utils.spec.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import fc from 'fast-check'; import 'mocha'; -import { c, s, t } from '../../../src/renderer/services/utils'; +import { c, s, t } from './utils'; describe('Frontend Utils', function() { this.timeout(1000); diff --git a/tests/services/uuid.spec.ts b/src/services/uuid.spec.ts similarity index 90% rename from tests/services/uuid.spec.ts rename to src/services/uuid.spec.ts index 1501bba..ea7ee4d 100644 --- a/tests/services/uuid.spec.ts +++ b/src/services/uuid.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import 'mocha'; -import { uuid } from '../../src/services/uuid'; +import { uuid } from './uuid'; describe('UUID Service', function() { this.timeout(1000); diff --git a/templates/index.js b/templates/index.js index e757859..507f665 100644 --- a/templates/index.js +++ b/templates/index.js @@ -1,6 +1,6 @@ -const handlebars = require('handlebars'); -const path = require('path'); const fs = require('fs'); +const path = require('path'); +const handlebars = require('handlebars'); const packageJson = require('../package'); function compile(isDevMode = false) { diff --git a/tests/.eslintrc.json b/tests/.eslintrc.json deleted file mode 100644 index b78179c..0000000 --- a/tests/.eslintrc.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": ["../.eslintrc.json"], - "rules": { - "no-unused-expressions": "off", - - "@typescript-eslint/no-magic-numbers": "off", - "@typescript-eslint/typedef": "off", - "@typescript-eslint/no-var-requires": "off", - "@typescript-eslint/explicit-function-return-type": "off" - } -} diff --git a/tests/setup/after.ts b/tests/setup/after.ts deleted file mode 100644 index eec62d7..0000000 --- a/tests/setup/after.ts +++ /dev/null @@ -1,4 +0,0 @@ -import 'mocha'; -import { moveDir, storeBackupDirectory, storeDirectory } from './before'; - -moveDir(storeBackupDirectory, storeDirectory); diff --git a/tests/setup/before.ts b/tests/setup/before.ts deleted file mode 100644 index d702f21..0000000 --- a/tests/setup/before.ts +++ /dev/null @@ -1,17 +0,0 @@ -import fs from 'fs-extra'; -import 'mocha'; -import path from 'path'; - -export const storeDirectory = path.resolve('store'); -export const storeBackupDirectory = path.resolve('store-backup'); - -export function moveDir(fromDir: string, toDir: string) { - if (fs.existsSync(fromDir)) { - if (fs.existsSync(toDir)) { - fs.removeSync(toDir); - } - fs.moveSync(fromDir, toDir); - } -} - -moveDir(storeDirectory, storeBackupDirectory); diff --git a/tsconfig.json b/tsconfig.json index 4e4fbd7..7c330fe 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,8 @@ { "compilerOptions": { + "target": "es2019", + "lib": ["es2019", "dom"], + "types": ["reflect-metadata"], "module": "commonjs", "moduleResolution": "node", "esModuleInterop": true, @@ -9,8 +12,7 @@ "sourceMap": true, "preserveConstEnums": false, "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "lib": ["es2018", "dom"] + "emitDecoratorMetadata": true }, - "include": ["declarations/**/*.ts", "src/**/*.ts", "tests/**/*.ts", "mocks/**/*.ts"] + "include": ["declarations/**/*.ts", "types/**/*.ts", "src/**/*.ts", "mocks/**/*.ts"] } diff --git a/src/types/constructor.ts b/types/constructor.d.ts similarity index 100% rename from src/types/constructor.ts rename to types/constructor.d.ts diff --git a/src/types/deep-partial.ts b/types/deep-partial.d.ts similarity index 100% rename from src/types/deep-partial.ts rename to types/deep-partial.d.ts diff --git a/src/types/http.ts b/types/http.d.ts similarity index 98% rename from src/types/http.ts rename to types/http.d.ts index af9206f..d2719e0 100644 --- a/src/types/http.ts +++ b/types/http.d.ts @@ -1,4 +1,4 @@ -const enum HttpCode { +declare const enum HttpCode { // 100 'CONTINUE' = 100, 'SWITCHING_PROTOCOLS' = 101, diff --git a/src/types/ipc.ts b/types/ipc.d.ts similarity index 94% rename from src/types/ipc.ts rename to types/ipc.d.ts index f2cd498..67cd3b8 100644 --- a/src/types/ipc.ts +++ b/types/ipc.d.ts @@ -1,4 +1,4 @@ -const enum IpcChannels { +declare const enum IpcChannels { ERROR = 'ERROR', LOGIN = 'LOGIN', LOGGED_IN = 'LOGGED_IN', diff --git a/tests/types/mock.ts b/types/mock.d.ts similarity index 100% rename from tests/types/mock.ts rename to types/mock.d.ts