diff --git a/package-lock.json b/package-lock.json index f4ed2388a7..def3d55456 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4223,8 +4223,7 @@ "jsbn": { "version": "0.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "json-schema": { "version": "0.2.3", diff --git a/src/app/main.ts b/src/app/main.ts index fab9cae260..049081c05d 100644 --- a/src/app/main.ts +++ b/src/app/main.ts @@ -1,7 +1,7 @@ import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { isDev } from '../scripts/utils'; +import { isDev } from 'jslib/electron/utils'; // tslint:disable-next-line require('../scss/styles.scss'); diff --git a/src/app/services/services.module.ts b/src/app/services/services.module.ts index 29530a0c76..1ac8d2052b 100644 --- a/src/app/services/services.module.ts +++ b/src/app/services/services.module.ts @@ -5,14 +5,14 @@ import { import { ToasterModule } from 'angular2-toaster'; -import { isDev } from '../../scripts/utils'; +import { ElectronLogService } from 'jslib/electron/services/electronLog.service'; +import { ElectronPlatformUtilsService } from 'jslib/electron/services/electronPlatformUtils.service'; +import { ElectronRendererSecureStorageService } from 'jslib/electron/services/electronRendererSecureStorage.service'; +import { ElectronStorageService } from 'jslib/electron/services/electronStorage.service'; +import { isDev } from 'jslib/electron/utils'; -import { DesktopPlatformUtilsService } from '../../services/desktopPlatformUtils.service'; import { DesktopRendererMessagingService } from '../../services/desktopRendererMessaging.service'; -import { DesktopRendererSecureStorageService } from '../../services/desktopRendererSecureStorage.service'; -import { DesktopStorageService } from '../../services/desktopStorage.service'; import { I18nService } from '../../services/i18n.service'; -import { LogService } from '../../services/log.service'; import { AuthGuardService } from 'jslib/angular/services/auth-guard.service'; import { BroadcasterService } from 'jslib/angular/services/broadcaster.service'; @@ -67,14 +67,14 @@ import { TokenService as TokenServiceAbstraction } from 'jslib/abstractions/toke import { TotpService as TotpServiceAbstraction } from 'jslib/abstractions/totp.service'; import { UserService as UserServiceAbstraction } from 'jslib/abstractions/user.service'; -const logService = new LogService(); +const logService = new ElectronLogService(); const i18nService = new I18nService(window.navigator.language, './locales'); const stateService = new StateService(); -const platformUtilsService = new DesktopPlatformUtilsService(i18nService); +const platformUtilsService = new ElectronPlatformUtilsService(i18nService, true); const broadcasterService = new BroadcasterService(); const messagingService = new DesktopRendererMessagingService(broadcasterService); -const storageService: StorageServiceAbstraction = new DesktopStorageService(); -const secureStorageService: StorageServiceAbstraction = new DesktopRendererSecureStorageService(); +const storageService: StorageServiceAbstraction = new ElectronStorageService(); +const secureStorageService: StorageServiceAbstraction = new ElectronRendererSecureStorageService(); const cryptoFunctionService: CryptoFunctionServiceAbstraction = new WebCryptoFunctionService(window, platformUtilsService); const cryptoService = new CryptoService(storageService, secureStorageService, cryptoFunctionService); diff --git a/src/main.ts b/src/main.ts index 4685d73d02..9da77e2bcc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,20 +2,21 @@ import { app, BrowserWindow } from 'electron'; import * as path from 'path'; import { DesktopMainMessagingService } from './services/desktopMainMessaging.service'; -import { DesktopStorageService } from './services/desktopStorage.service'; import { I18nService } from './services/i18n.service'; -import { LogService } from './services/log.service'; import { MenuMain } from './main/menu.main'; import { MessagingMain } from './main/messaging.main'; import { PowerMonitorMain } from './main/powerMonitor.main'; import { UpdaterMain } from './main/updater.main'; -import { WindowMain } from './main/window.main'; + +import { ElectronLogService } from 'jslib/electron/services/electronLog.service'; +import { ElectronStorageService } from 'jslib/electron/services/electronStorage.service'; +import { WindowMain } from 'jslib/electron/window.main'; export class Main { - logService: LogService; + logService: ElectronLogService; i18nService: I18nService; - storageService: DesktopStorageService; + storageService: ElectronStorageService; messagingService: DesktopMainMessagingService; windowMain: WindowMain; @@ -48,12 +49,12 @@ export class Main { require('electron-reload')(__dirname, {}); } - this.logService = new LogService(null, app.getPath('userData')); + this.logService = new ElectronLogService(null, app.getPath('userData')); this.i18nService = new I18nService('en', './locales/'); - this.storageService = new DesktopStorageService(); + this.storageService = new ElectronStorageService(); this.messagingService = new DesktopMainMessagingService(this); - this.windowMain = new WindowMain(this); + this.windowMain = new WindowMain(this.storageService); this.messagingMain = new MessagingMain(this); this.updaterMain = new UpdaterMain(this); this.menuMain = new MenuMain(this); diff --git a/src/main/menu.main.ts b/src/main/menu.main.ts index 186905bce8..cc38186aef 100644 --- a/src/main/menu.main.ts +++ b/src/main/menu.main.ts @@ -11,7 +11,8 @@ import { } from 'electron'; import { Main } from '../main'; -import { isMacAppStore, isSnapStore, isWindowsStore } from '../scripts/utils'; + +import { isMacAppStore, isSnapStore, isWindowsStore } from 'jslib/electron/utils'; import { ConstantsService } from 'jslib/services/constants.service'; diff --git a/src/main/updater.main.ts b/src/main/updater.main.ts index 7b9473a7c9..ea0dee5b1d 100644 --- a/src/main/updater.main.ts +++ b/src/main/updater.main.ts @@ -8,13 +8,14 @@ import log from 'electron-log'; import { autoUpdater } from 'electron-updater'; import { Main } from '../main'; + import { isAppImage, isDev, isMacAppStore, isWindowsPortable, isWindowsStore, -} from '../scripts/utils'; +} from 'jslib/electron/utils'; const UpdaterCheckInitalDelay = 5 * 1000; // 5 seconds const UpdaterCheckInterval = 12 * 60 * 60 * 1000; // 12 hours diff --git a/src/main/window.main.ts b/src/main/window.main.ts deleted file mode 100644 index 1a1c19a000..0000000000 --- a/src/main/window.main.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { app, BrowserWindow, screen } from 'electron'; -import * as path from 'path'; -import * as url from 'url'; - -import { Main } from '../main'; -import { isDev, isMacAppStore, isSnapStore } from '../scripts/utils'; - -const WindowEventHandlingDelay = 100; -const Keys = { - mainWindowSize: 'mainWindowSize', -}; - -export class WindowMain { - win: BrowserWindow; - - private windowStateChangeTimer: NodeJS.Timer; - private windowStates: { [key: string]: any; } = {}; - - constructor(private main: Main) { } - - init(): Promise { - return new Promise((resolve, reject) => { - try { - if (!isMacAppStore() && !isSnapStore()) { - const shouldQuit = app.makeSingleInstance((args, dir) => { - // Someone tried to run a second instance, we should focus our window. - if (this.win != null) { - if (this.win.isMinimized()) { - this.win.restore(); - } - this.win.focus(); - } - }); - - if (shouldQuit) { - app.quit(); - return; - } - } - - // This method will be called when Electron has finished - // initialization and is ready to create browser windows. - // Some APIs can only be used after this event occurs. - app.on('ready', async () => { - await this.createWindow(); - resolve(); - }); - - // Quit when all windows are closed. - app.on('window-all-closed', () => { - // On OS X it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== 'darwin' || isMacAppStore()) { - app.quit(); - } - }); - - 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 (this.win === null) { - await this.createWindow(); - } - }); - - } catch (e) { - // Catch Error - // throw e; - reject(e); - } - }); - } - - private async createWindow() { - this.windowStates[Keys.mainWindowSize] = await this.getWindowState(Keys.mainWindowSize, 950, 600); - - // Create the browser window. - this.win = new BrowserWindow({ - width: this.windowStates[Keys.mainWindowSize].width, - height: this.windowStates[Keys.mainWindowSize].height, - minWidth: 680, - minHeight: 500, - x: this.windowStates[Keys.mainWindowSize].x, - y: this.windowStates[Keys.mainWindowSize].y, - title: app.getName(), - icon: process.platform === 'linux' ? path.join(__dirname, '/images/icon.png') : undefined, - show: false, - }); - - if (this.windowStates[Keys.mainWindowSize].isMaximized) { - this.win.maximize(); - } - - // Show it later since it might need to be maximized. - this.win.show(); - - // and load the index.html of the app. - this.win.loadURL(url.format({ - protocol: 'file:', - pathname: path.join(__dirname, '/index.html'), - slashes: true, - })); - - // Open the DevTools. - if (isDev()) { - this.win.webContents.openDevTools(); - } - - // Emitted when the window is closed. - this.win.on('closed', async () => { - await this.updateWindowState(Keys.mainWindowSize, this.win); - - // Dereference the window object, usually you would store window - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - this.win = null; - }); - - this.win.on('close', async () => { - await this.updateWindowState(Keys.mainWindowSize, this.win); - }); - - this.win.on('maximize', async () => { - await this.updateWindowState(Keys.mainWindowSize, this.win); - }); - - this.win.on('unmaximize', async () => { - await this.updateWindowState(Keys.mainWindowSize, this.win); - }); - - this.win.on('resize', () => { - this.windowStateChangeHandler(Keys.mainWindowSize, this.win); - }); - - this.win.on('move', () => { - this.windowStateChangeHandler(Keys.mainWindowSize, this.win); - }); - } - - private windowStateChangeHandler(configKey: string, win: BrowserWindow) { - global.clearTimeout(this.windowStateChangeTimer); - this.windowStateChangeTimer = global.setTimeout(async () => { - await this.updateWindowState(configKey, win); - }, WindowEventHandlingDelay); - } - - private async updateWindowState(configKey: string, win: BrowserWindow) { - if (win == null) { - return; - } - - try { - const bounds = win.getBounds(); - - if (this.windowStates[configKey] == null) { - this.windowStates[configKey] = await this.main.storageService.get(configKey); - if (this.windowStates[configKey] == null) { - this.windowStates[configKey] = {}; - } - } - - this.windowStates[configKey].isMaximized = win.isMaximized(); - this.windowStates[configKey].displayBounds = screen.getDisplayMatching(bounds).bounds; - - if (!win.isMaximized() && !win.isMinimized() && !win.isFullScreen()) { - this.windowStates[configKey].x = bounds.x; - this.windowStates[configKey].y = bounds.y; - this.windowStates[configKey].width = bounds.width; - this.windowStates[configKey].height = bounds.height; - } - - await this.main.storageService.save(configKey, this.windowStates[configKey]); - } catch (e) { } - } - - private async getWindowState(configKey: string, defaultWidth: number, defaultHeight: number) { - let state = await this.main.storageService.get(configKey); - - const isValid = state != null && (this.stateHasBounds(state) || state.isMaximized); - let displayBounds: Electron.Rectangle = null; - if (!isValid) { - state = { - width: defaultWidth, - height: defaultHeight, - }; - - displayBounds = screen.getPrimaryDisplay().bounds; - } else if (this.stateHasBounds(state) && state.displayBounds) { - // Check if the display where the window was last open is still available - displayBounds = screen.getDisplayMatching(state.displayBounds).bounds; - - if (displayBounds.width !== state.displayBounds.width || - displayBounds.height !== state.displayBounds.height || - displayBounds.x !== state.displayBounds.x || - displayBounds.y !== state.displayBounds.y) { - state.x = undefined; - state.y = undefined; - displayBounds = screen.getPrimaryDisplay().bounds; - } - } - - if (displayBounds != null) { - if (state.width > displayBounds.width && state.height > displayBounds.height) { - state.isMaximized = true; - } - - if (state.width > displayBounds.width) { - state.width = displayBounds.width - 10; - } - if (state.height > displayBounds.height) { - state.height = displayBounds.height - 10; - } - } - - return state; - } - - private stateHasBounds(state: any): boolean { - return state != null && Number.isInteger(state.x) && Number.isInteger(state.y) && - Number.isInteger(state.width) && state.width > 0 && Number.isInteger(state.height) && state.height > 0; - } -} diff --git a/src/scripts/utils.ts b/src/scripts/utils.ts deleted file mode 100644 index cb19144bc4..0000000000 --- a/src/scripts/utils.ts +++ /dev/null @@ -1,27 +0,0 @@ -export function isDev() { - // ref: https://github.com/sindresorhus/electron-is-dev - if ('ELECTRON_IS_DEV' in process.env) { - return parseInt(process.env.ELECTRON_IS_DEV, 10) === 1; - } - return (process.defaultApp || /node_modules[\\/]electron[\\/]/.test(process.execPath)); -} - -export function isAppImage() { - return process.platform === 'linux' && 'APPIMAGE' in process.env; -} - -export function isMacAppStore() { - return process.platform === 'darwin' && process.mas && process.mas === true; -} - -export function isWindowsStore() { - return process.platform === 'win32' && process.windowsStore && process.windowsStore === true; -} - -export function isSnapStore() { - return process.platform === 'linux' && process.env.SNAP_USER_DATA != null; -} - -export function isWindowsPortable() { - return process.platform === 'win32' && process.env.PORTABLE_EXECUTABLE_DIR != null; -} diff --git a/src/services/desktopPlatformUtils.service.ts b/src/services/desktopPlatformUtils.service.ts deleted file mode 100644 index fe8aa3d62f..0000000000 --- a/src/services/desktopPlatformUtils.service.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { - clipboard, - remote, - shell, -} from 'electron'; - -import { - isDev, - isMacAppStore, -} from '../scripts/utils'; - -import { DeviceType } from 'jslib/enums/deviceType'; - -import { I18nService } from 'jslib/abstractions/i18n.service'; -import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; - -const AnalyticsIds = { - [DeviceType.Windows]: 'UA-81915606-17', - [DeviceType.Linux]: 'UA-81915606-19', - [DeviceType.MacOs]: 'UA-81915606-18', -}; - -export class DesktopPlatformUtilsService implements PlatformUtilsService { - identityClientId: string = 'desktop'; - - private deviceCache: DeviceType = null; - private analyticsIdCache: string = null; - - constructor(private i18nService: I18nService) { - } - - getDevice(): DeviceType { - if (!this.deviceCache) { - switch (process.platform) { - case 'win32': - this.deviceCache = DeviceType.Windows; - break; - case 'darwin': - this.deviceCache = DeviceType.MacOs; - break; - case 'linux': - this.deviceCache = DeviceType.Linux; - break; - default: - this.deviceCache = DeviceType.Linux; - break; - } - } - - return this.deviceCache; - } - - getDeviceString(): string { - return DeviceType[this.getDevice()].toLowerCase(); - } - - isFirefox(): boolean { - return false; - } - - isChrome(): boolean { - return true; - } - - isEdge(): boolean { - return false; - } - - isOpera(): boolean { - return false; - } - - isVivaldi(): boolean { - return false; - } - - isSafari(): boolean { - return false; - } - - isMacAppStore(): boolean { - return isMacAppStore(); - } - - analyticsId(): string { - if (this.analyticsIdCache) { - return this.analyticsIdCache; - } - - this.analyticsIdCache = (AnalyticsIds as any)[this.getDevice()]; - return this.analyticsIdCache; - } - - getDomain(uriString: string): string { - if (uriString == null) { - return null; - } - - uriString = uriString.trim(); - if (uriString === '') { - return null; - } - - if (uriString.indexOf('://') > -1) { - try { - const url = new URL(uriString); - return url.hostname; - } catch (e) { } - } - - return null; - } - - isViewOpen(): boolean { - return false; - } - - launchUri(uri: string, options?: any): void { - shell.openExternal(uri); - } - - saveFile(win: Window, blobData: any, blobOptions: any, fileName: string): void { - const blob = new Blob([blobData], blobOptions); - const a = win.document.createElement('a'); - a.href = win.URL.createObjectURL(blob); - a.download = fileName; - window.document.body.appendChild(a); - a.click(); - window.document.body.removeChild(a); - } - - getApplicationVersion(): string { - return remote.app.getVersion(); - } - - supportsU2f(win: Window): boolean { - // Not supported in Electron at this time. - // ref: https://github.com/electron/electron/issues/3226 - return false; - } - - showDialog(text: string, title?: string, confirmText?: string, cancelText?: string, type?: string): - Promise { - const buttons = [confirmText == null ? this.i18nService.t('ok') : confirmText]; - if (cancelText != null) { - buttons.push(cancelText); - } - - const result = remote.dialog.showMessageBox(remote.getCurrentWindow(), { - type: type, - title: title, - message: title, - detail: text, - buttons: buttons, - cancelId: buttons.length === 2 ? 1 : null, - defaultId: 0, - noLink: true, - }); - - return Promise.resolve(result === 0); - } - - isDev(): boolean { - return isDev(); - } - - copyToClipboard(text: string, options?: any): void { - const type = options ? options.type : null; - clipboard.writeText(text, type); - } -} diff --git a/src/services/desktopRendererSecureStorage.service.ts b/src/services/desktopRendererSecureStorage.service.ts deleted file mode 100644 index c2bca66713..0000000000 --- a/src/services/desktopRendererSecureStorage.service.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { ipcRenderer } from 'electron'; - -import { StorageService } from 'jslib/abstractions/storage.service'; - -export class DesktopRendererSecureStorageService implements StorageService { - async get(key: string): Promise { - const val = ipcRenderer.sendSync('keytar', { - action: 'getPassword', - key: key, - }); - return Promise.resolve(val != null ? JSON.parse(val) as T : null); - } - - async save(key: string, obj: any): Promise { - ipcRenderer.sendSync('keytar', { - action: 'setPassword', - key: key, - value: JSON.stringify(obj), - }); - return Promise.resolve(); - } - - async remove(key: string): Promise { - ipcRenderer.sendSync('keytar', { - action: 'deletePassword', - key: key, - }); - return Promise.resolve(); - } -} diff --git a/src/services/desktopStorage.service.ts b/src/services/desktopStorage.service.ts deleted file mode 100644 index f2dac40fce..0000000000 --- a/src/services/desktopStorage.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { StorageService } from 'jslib/abstractions/storage.service'; - -import { ConstantsService } from 'jslib/services/constants.service'; - -// tslint:disable-next-line -const Store = require('electron-store'); - -export class DesktopStorageService implements StorageService { - private store: any; - - constructor() { - const storeConfig: any = { - defaults: {} as any, - name: 'data', - }; - // Default lock options to "on restart". - storeConfig.defaults[ConstantsService.lockOptionKey] = -1; - this.store = new Store(storeConfig); - } - - get(key: string): Promise { - const val = this.store.get(key) as T; - return Promise.resolve(val != null ? val : null); - } - - save(key: string, obj: any): Promise { - this.store.set(key, obj); - return Promise.resolve(); - } - - remove(key: string): Promise { - this.store.delete(key); - return Promise.resolve(); - } -} diff --git a/src/services/log.service.ts b/src/services/log.service.ts deleted file mode 100644 index 31b447c69c..0000000000 --- a/src/services/log.service.ts +++ /dev/null @@ -1,64 +0,0 @@ -import log from 'electron-log'; -import * as path from 'path'; - -import { isDev } from '../scripts/utils'; - -import { LogLevelType } from 'jslib/enums/logLevelType'; - -import { LogService as LogServiceAbstraction } from 'jslib/abstractions/log.service'; - -export class LogService implements LogServiceAbstraction { - constructor(private filter: (level: LogLevelType) => boolean = null, logDir: string = null) { - if (log.transports == null) { - return; - } - - log.transports.file.level = 'info'; - if (logDir != null) { - log.transports.file.file = path.join(logDir, 'app.log'); - } - } - - debug(message: string) { - if (!isDev()) { - return; - } - - this.write(LogLevelType.Debug, message); - } - - info(message: string) { - this.write(LogLevelType.Info, message); - } - - warning(message: string) { - this.write(LogLevelType.Warning, message); - } - - error(message: string) { - this.write(LogLevelType.Error, message); - } - - write(level: LogLevelType, message: string) { - if (this.filter != null && this.filter(level)) { - return; - } - - switch (level) { - case LogLevelType.Debug: - log.debug(message); - break; - case LogLevelType.Info: - log.info(message); - break; - case LogLevelType.Warning: - log.warn(message); - break; - case LogLevelType.Error: - log.error(message); - break; - default: - break; - } - } -} diff --git a/tsconfig.json b/tsconfig.json index ebde5f337f..f074d81d87 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,9 @@ "target": "ES2016", "allowJs": true, "sourceMap": true, - "types": [], + "types": [ + "node" + ], "baseUrl": ".", "paths": { "jslib/*": [ "jslib/src/*" ], @@ -19,6 +21,8 @@ }, "exclude": [ "node_modules", + "src/node_modules", + "jslib/node_modules/electron/electron.d.ts", "jslib/node_modules", "dist", "jslib/dist",