From 216c77fa255b6de77f7fbd9c58b0f57887bb5b3f Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Tue, 13 Feb 2018 23:38:18 -0500 Subject: [PATCH] electron main class --- package.json | 2 +- src/main.ts | 77 ++++++---- src/main/menu.main.ts | 145 +++++++++---------- src/main/messaging.main.ts | 6 +- src/main/powerMonitor.main.ts | 11 +- src/main/updater.main.ts | 37 +++-- src/main/window.main.ts | 7 +- src/services/desktopMainMessaging.service.ts | 9 +- 8 files changed, 152 insertions(+), 142 deletions(-) diff --git a/package.json b/package.json index dc3db3e6d3..f05a23572b 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "build:main": "webpack --config webpack.main.js", "build:renderer": "webpack --config webpack.renderer.js", "build:renderer:watch": "webpack --config webpack.renderer.js --watch", - "electron": "npm run build:main && (electron ./build --dev --watch | npm run build:renderer:watch)", + "electron": "npm run build:main && (electron ./build --watch | npm run build:renderer:watch)", "pack": "electron-builder --dir", "dist": "electron-builder" }, diff --git a/src/main.ts b/src/main.ts index 31c5429a87..8ab5669dd0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,42 +1,59 @@ import { BrowserWindow } from 'electron'; +import { DesktopMainMessagingService } from './services/desktopMainMessaging.service'; +import { DesktopStorageService } from './services/desktopStorage.service'; +import { I18nService } from './services/i18n.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 { DesktopMainMessagingService } from './services/desktopMainMessaging.service'; -import { DesktopStorageService } from './services/desktopStorage.service'; -import { I18nService } from './services/i18n.service'; +export class Main { + i18nService: I18nService; + storageService: DesktopStorageService; + messagingService: DesktopMainMessagingService; -const args = process.argv.slice(1); -const watch = args.some((val) => val === '--watch'); -const dev = args.some((val) => val === '--dev'); + windowMain: WindowMain; + messagingMain: MessagingMain; + updaterMain: UpdaterMain; + menuMain: MenuMain; + powerMonitorMain: PowerMonitorMain; -if (watch) { - // tslint:disable-next-line - require('electron-reload')(__dirname, {}); + constructor() { + const args = process.argv.slice(1); + const watch = args.some((val) => val === '--watch'); + + if (watch) { + // tslint:disable-next-line + require('electron-reload')(__dirname, {}); + } + + this.i18nService = new I18nService('en', './locales/'); + this.storageService = new DesktopStorageService(); + this.messagingService = new DesktopMainMessagingService(this); + + this.windowMain = new WindowMain(this); + this.messagingMain = new MessagingMain(this); + this.updaterMain = new UpdaterMain(this); + this.menuMain = new MenuMain(this); + this.powerMonitorMain = new PowerMonitorMain(this); + } + + bootstrap() { + this.windowMain.init().then(async () => { + await this.i18nService.init(); + this.messagingMain.init(); + this.menuMain.init(); + this.powerMonitorMain.init(); + await this.updaterMain.init(); + }, (e: any) => { + // tslint:disable-next-line + console.error(e); + }); + } } -const windowMain = new WindowMain(dev); -const messagingMain = new MessagingMain(windowMain); - -const i18nService = new I18nService('en', './locales/'); -const storageService = new DesktopStorageService(); -const messagingService = new DesktopMainMessagingService(windowMain, messagingMain); - -const updaterMain = new UpdaterMain(windowMain, i18nService); -const menuMain = new MenuMain(windowMain, updaterMain, i18nService, messagingService); -const powerMonitorMain = new PowerMonitorMain(storageService, messagingService); - -windowMain.init().then(async () => { - messagingMain.init(); - await i18nService.init(); - menuMain.init(); - powerMonitorMain.init(); - await updaterMain.init(); -}, (e: any) => { - // tslint:disable-next-line - console.log(e); -}); +const main = new Main(); +main.bootstrap(); diff --git a/src/main/menu.main.ts b/src/main/menu.main.ts index 443043b475..0291a4119d 100644 --- a/src/main/menu.main.ts +++ b/src/main/menu.main.ts @@ -10,15 +10,10 @@ import { shell, } from 'electron'; -import { UpdaterMain } from './updater.main'; -import { WindowMain } from './window.main'; - -import { I18nService } from 'jslib/abstractions/i18n.service'; -import { MessagingService } from 'jslib/abstractions/messaging.service'; +import { Main } from '../main'; export class MenuMain { - constructor(private windowMain: WindowMain, private updaterMain: UpdaterMain, - private i18nService: I18nService, private messagingService: MessagingService) { } + constructor(private main: Main) { } init() { this.initContextMenu(); @@ -51,14 +46,14 @@ export class MenuMain { { role: 'selectall' }, ]); - this.windowMain.win.webContents.on('context-menu', (e, props) => { + this.main.windowMain.win.webContents.on('context-menu', (e, props) => { const selected = props.selectionText && props.selectionText.trim() !== ''; if (props.isEditable && selected) { - inputSelectionMenu.popup(this.windowMain.win); + inputSelectionMenu.popup(this.main.windowMain.win); } else if (props.isEditable) { - inputMenu.popup(this.windowMain.win); + inputMenu.popup(this.main.windowMain.win); } else if (selected) { - selectionMenu.popup(this.windowMain.win); + selectionMenu.popup(this.main.windowMain.win); } }); } @@ -66,51 +61,51 @@ export class MenuMain { private initApplicationMenu() { const template: MenuItemConstructorOptions[] = [ { - label: this.i18nService.t('file'), + label: this.main.i18nService.t('file'), submenu: [ { - label: this.i18nService.t('addNewLogin'), - click: () => this.messagingService.send('newLogin'), + label: this.main.i18nService.t('addNewLogin'), + click: () => this.main.messagingService.send('newLogin'), accelerator: 'CmdOrCtrl+N', }, { - label: this.i18nService.t('addNewItem'), + label: this.main.i18nService.t('addNewItem'), submenu: [ { - label: this.i18nService.t('typeLogin'), - click: () => this.messagingService.send('newLogin'), + label: this.main.i18nService.t('typeLogin'), + click: () => this.main.messagingService.send('newLogin'), accelerator: 'Alt+L', }, { - label: this.i18nService.t('typeCard'), - click: () => this.messagingService.send('newCard'), + label: this.main.i18nService.t('typeCard'), + click: () => this.main.messagingService.send('newCard'), accelerator: 'Alt+C', }, { - label: this.i18nService.t('typeIdentity'), - click: () => this.messagingService.send('newIdentity'), + label: this.main.i18nService.t('typeIdentity'), + click: () => this.main.messagingService.send('newIdentity'), accelerator: 'Alt+I', }, { - label: this.i18nService.t('typeSecureNote'), - click: () => this.messagingService.send('newSecureNote'), + label: this.main.i18nService.t('typeSecureNote'), + click: () => this.main.messagingService.send('newSecureNote'), accelerator: 'Alt+S', }, ], }, { - label: this.i18nService.t('addNewFolder'), - click: () => this.messagingService.send('newFolder'), + label: this.main.i18nService.t('addNewFolder'), + click: () => this.main.messagingService.send('newFolder'), }, { type: 'separator' }, { - label: this.i18nService.t('syncVault'), - click: () => this.messagingService.send('syncVault'), + label: this.main.i18nService.t('syncVault'), + click: () => this.main.messagingService.send('syncVault'), }, ], }, { - label: this.i18nService.t('edit'), + label: this.main.i18nService.t('edit'), submenu: [ { role: 'undo' }, { role: 'redo' }, @@ -123,16 +118,16 @@ export class MenuMain { ], }, { - label: this.i18nService.t('view'), + label: this.main.i18nService.t('view'), submenu: [ { - label: this.i18nService.t('passwordGenerator'), - click: () => this.messagingService.send('openPasswordGenerator'), + label: this.main.i18nService.t('passwordGenerator'), + click: () => this.main.messagingService.send('openPasswordGenerator'), accelerator: 'CmdOrCtrl+G', }, { - label: this.i18nService.t('searchVault'), - click: () => this.messagingService.send('focusSearch'), + label: this.main.i18nService.t('searchVault'), + click: () => this.main.messagingService.send('focusSearch'), accelerator: 'CmdOrCtrl+F', }, { type: 'separator' }, @@ -148,19 +143,19 @@ export class MenuMain { ], }, { - label: this.i18nService.t('account'), + label: this.main.i18nService.t('account'), submenu: [ { - label: this.i18nService.t('premiumMembership'), - click: () => this.messagingService.send('premiumMembership'), + label: this.main.i18nService.t('premiumMembership'), + click: () => this.main.messagingService.send('premiumMembership'), }, { - label: this.i18nService.t('changeMasterPass'), + label: this.main.i18nService.t('changeMasterPass'), click: () => { - const result = dialog.showMessageBox(this.windowMain.win, { - title: this.i18nService.t('changeMasterPass'), - message: this.i18nService.t('changeMasterPasswordConfirmation'), - buttons: [this.i18nService.t('yes'), this.i18nService.t('no')], + const result = dialog.showMessageBox(this.main.windowMain.win, { + title: this.main.i18nService.t('changeMasterPass'), + message: this.main.i18nService.t('changeMasterPasswordConfirmation'), + buttons: [this.main.i18nService.t('yes'), this.main.i18nService.t('no')], cancelId: 1, defaultId: 0, noLink: true, @@ -171,12 +166,12 @@ export class MenuMain { }, }, { - label: this.i18nService.t('changeEmail'), + label: this.main.i18nService.t('changeEmail'), click: () => { - const result = dialog.showMessageBox(this.windowMain.win, { - title: this.i18nService.t('changeEmail'), - message: this.i18nService.t('changeEmailConfirmation'), - buttons: [this.i18nService.t('yes'), this.i18nService.t('no')], + const result = dialog.showMessageBox(this.main.windowMain.win, { + title: this.main.i18nService.t('changeEmail'), + message: this.main.i18nService.t('changeEmailConfirmation'), + buttons: [this.main.i18nService.t('yes'), this.main.i18nService.t('no')], cancelId: 1, defaultId: 0, noLink: true, @@ -187,12 +182,12 @@ export class MenuMain { }, }, { - label: this.i18nService.t('twoStepLogin'), + label: this.main.i18nService.t('twoStepLogin'), click: () => { - const result = dialog.showMessageBox(this.windowMain.win, { - title: this.i18nService.t('twoStepLogin'), - message: this.i18nService.t('twoStepLoginConfirmation'), - buttons: [this.i18nService.t('yes'), this.i18nService.t('no')], + const result = dialog.showMessageBox(this.main.windowMain.win, { + title: this.main.i18nService.t('twoStepLogin'), + message: this.main.i18nService.t('twoStepLoginConfirmation'), + buttons: [this.main.i18nService.t('yes'), this.main.i18nService.t('no')], cancelId: 1, defaultId: 0, noLink: true, @@ -204,18 +199,18 @@ export class MenuMain { }, { type: 'separator' }, { - label: this.i18nService.t('logOut'), + label: this.main.i18nService.t('logOut'), click: () => { - const result = dialog.showMessageBox(this.windowMain.win, { - title: this.i18nService.t('logOut'), - message: this.i18nService.t('logOutConfirmation'), - buttons: [this.i18nService.t('logOut'), this.i18nService.t('cancel')], + const result = dialog.showMessageBox(this.main.windowMain.win, { + title: this.main.i18nService.t('logOut'), + message: this.main.i18nService.t('logOutConfirmation'), + buttons: [this.main.i18nService.t('logOut'), this.main.i18nService.t('cancel')], cancelId: 1, defaultId: 0, noLink: true, }); if (result === 0) { - this.messagingService.send('logout'); + this.main.messagingService.send('logout'); } }, }, @@ -232,23 +227,23 @@ export class MenuMain { role: 'help', submenu: [ { - label: this.i18nService.t('emailUs'), + label: this.main.i18nService.t('emailUs'), click: () => shell.openExternal('mailTo:hello@bitwarden.com'), }, { - label: this.i18nService.t('visitOurWebsite'), + label: this.main.i18nService.t('visitOurWebsite'), click: () => shell.openExternal('https://bitwarden.com/contact'), }, { - label: this.i18nService.t('fileBugReport'), + label: this.main.i18nService.t('fileBugReport'), click: () => shell.openExternal('https://github.com/bitwarden/desktop'), }, { type: 'separator' }, { - label: this.i18nService.t('followUs'), + label: this.main.i18nService.t('followUs'), submenu: [ { - label: this.i18nService.t('blog'), + label: this.main.i18nService.t('blog'), click: () => shell.openExternal('https://blog.bitwarden.com'), }, { @@ -271,11 +266,11 @@ export class MenuMain { }, { type: 'separator' }, { - label: this.i18nService.t('goToWebVault'), + label: this.main.i18nService.t('goToWebVault'), click: () => shell.openExternal('https://vault.bitwarden.com'), }, { - label: this.i18nService.t('getMobileApp'), + label: this.main.i18nService.t('getMobileApp'), submenu: [ { label: 'iOS', @@ -294,7 +289,7 @@ export class MenuMain { ], }, { - label: this.i18nService.t('getBrowserExtension'), + label: this.main.i18nService.t('getBrowserExtension'), submenu: [ { label: 'Chrome', @@ -340,19 +335,19 @@ export class MenuMain { const firstMenuOptions: MenuItemConstructorOptions[] = [ { type: 'separator' }, { - label: this.i18nService.t('settings'), - click: () => this.messagingService.send('openSettings'), + label: this.main.i18nService.t('settings'), + click: () => this.main.messagingService.send('openSettings'), }, { - label: this.i18nService.t('lockNow'), - click: () => this.messagingService.send('lockVault'), + label: this.main.i18nService.t('lockNow'), + click: () => this.main.messagingService.send('lockVault'), accelerator: 'CmdOrCtrl+L', }, ]; const updateMenuItem = { - label: this.i18nService.t('checkForUpdates'), - click: () => this.updaterMain.checkForUpdate(true), + label: this.main.i18nService.t('checkForUpdates'), + click: () => this.main.updaterMain.checkForUpdate(true), id: 'checkForUpdates', }; @@ -395,20 +390,20 @@ export class MenuMain { { type: 'separator' }, updateMenuItem, { - label: this.i18nService.t('about'), + label: this.main.i18nService.t('about'), click: () => { - const aboutInformation = this.i18nService.t('version', app.getVersion()) + + const aboutInformation = this.main.i18nService.t('version', app.getVersion()) + '\nShell ' + process.versions.electron + '\nRenderer ' + process.versions.chrome + '\nNode ' + process.versions.node + '\nArchitecture ' + process.arch; - const result = dialog.showMessageBox(this.windowMain.win, { + const result = dialog.showMessageBox(this.main.windowMain.win, { title: 'Bitwarden', message: 'Bitwarden', detail: aboutInformation, type: 'info', noLink: true, - buttons: [this.i18nService.t('ok'), this.i18nService.t('copy')], + buttons: [this.main.i18nService.t('ok'), this.main.i18nService.t('copy')], }); if (result === 1) { clipboard.writeText(aboutInformation); diff --git a/src/main/messaging.main.ts b/src/main/messaging.main.ts index fb0ab012d3..9606aca803 100644 --- a/src/main/messaging.main.ts +++ b/src/main/messaging.main.ts @@ -9,7 +9,7 @@ import { setPassword, } from 'keytar'; -import { WindowMain } from './window.main'; +import { Main } from '../main'; const KeytarService = 'Bitwarden'; const SyncInterval = 5 * 60 * 1000; // 5 minutes @@ -17,7 +17,7 @@ const SyncInterval = 5 * 60 * 1000; // 5 minutes export class MessagingMain { private syncTimeout: NodeJS.Timer; - constructor(private windowMain: WindowMain) { } + constructor(private main: Main) { } init() { this.scheduleNextSync(); @@ -65,7 +65,7 @@ export class MessagingMain { } this.syncTimeout = global.setTimeout(() => { - this.windowMain.win.webContents.send('messagingService', { + this.main.windowMain.win.webContents.send('messagingService', { command: 'checkSyncVault', }); }, SyncInterval); diff --git a/src/main/powerMonitor.main.ts b/src/main/powerMonitor.main.ts index cd91980e17..6e9d4f4bf3 100644 --- a/src/main/powerMonitor.main.ts +++ b/src/main/powerMonitor.main.ts @@ -2,8 +2,7 @@ import { powerMonitor } from 'electron'; import { ConstantsService } from 'jslib/services/constants.service'; -import { MessagingService } from 'jslib/abstractions/messaging.service'; -import { StorageService } from 'jslib/abstractions/storage.service'; +import { Main } from '../main'; // tslint:disable-next-line const desktopIdle = require('desktop-idle'); @@ -13,14 +12,14 @@ const IdleCheckInterval = 30 * 1000; // 30 seconds export class PowerMonitorMain { private idle: boolean = false; - constructor(private storageService: StorageService, private messagingService: MessagingService) { } + constructor(private main: Main) { } init() { // System sleep powerMonitor.on('suspend', async () => { const lockOption = await this.getLockOption(); if (lockOption === -3) { - this.messagingService.send('lockVault'); + this.main.messagingService.send('lockVault'); } }); @@ -35,7 +34,7 @@ export class PowerMonitorMain { const lockOption = await this.getLockOption(); if (lockOption === -4) { - this.messagingService.send('lockVault'); + this.main.messagingService.send('lockVault'); } } @@ -46,6 +45,6 @@ export class PowerMonitorMain { } private async getLockOption(): Promise { - return await this.storageService.get(ConstantsService.lockOptionKey); + return await this.main.storageService.get(ConstantsService.lockOptionKey); } } diff --git a/src/main/updater.main.ts b/src/main/updater.main.ts index e42c789452..a45809bf17 100644 --- a/src/main/updater.main.ts +++ b/src/main/updater.main.ts @@ -5,21 +5,18 @@ import { } from 'electron'; import { autoUpdater } from 'electron-updater'; +import { Main } from '../main'; import { isDev } from '../scripts/utils'; -import { WindowMain } from './window.main'; - -import { I18nService } from 'jslib/abstractions/i18n.service'; const UpdaterCheckInitalDelay = 5 * 1000; // 5 seconds const UpdaterCheckInterval = 12 * 60 * 60 * 1000; // 12 hours export class UpdaterMain { - private doingUpdateCheck = false; private doingUpdateCheckWithFeedback = false; private updateMenuItem: MenuItem; - constructor(private windowMain: WindowMain, private i18nService: I18nService) { } + constructor(private main: Main) { } async init() { global.setTimeout(async () => await this.checkForUpdate(), UpdaterCheckInitalDelay); @@ -33,12 +30,12 @@ export class UpdaterMain { autoUpdater.on('update-available', () => { if (this.doingUpdateCheckWithFeedback) { - const result = dialog.showMessageBox(this.windowMain.win, { + const result = dialog.showMessageBox(this.main.windowMain.win, { type: 'info', - title: this.i18nService.t('updateAvailable'), - message: this.i18nService.t('updateAvailable'), - detail: this.i18nService.t('updateAvailableDesc'), - buttons: [this.i18nService.t('yes'), this.i18nService.t('no')], + title: this.main.i18nService.t('updateAvailable'), + message: this.main.i18nService.t('updateAvailable'), + detail: this.main.i18nService.t('updateAvailableDesc'), + buttons: [this.main.i18nService.t('yes'), this.main.i18nService.t('no')], cancelId: 1, defaultId: 0, noLink: true, @@ -54,8 +51,8 @@ export class UpdaterMain { autoUpdater.on('update-not-available', () => { if (this.doingUpdateCheckWithFeedback) { - dialog.showMessageBox(this.windowMain.win, { - message: this.i18nService.t('noUpdatesAvailable'), + dialog.showMessageBox(this.main.windowMain.win, { + message: this.main.i18nService.t('noUpdatesAvailable'), }); } @@ -63,14 +60,14 @@ export class UpdaterMain { }); autoUpdater.on('update-downloaded', (info) => { - this.updateMenuItem.label = this.i18nService.t('restartToUpdate'); + this.updateMenuItem.label = this.main.i18nService.t('restartToUpdate'); - const result = dialog.showMessageBox(this.windowMain.win, { + const result = dialog.showMessageBox(this.main.windowMain.win, { type: 'info', - title: this.i18nService.t('restartToUpdate'), - message: this.i18nService.t('restartToUpdate'), - detail: this.i18nService.t('restartToUpdateDesc', info.version), - buttons: [this.i18nService.t('restart'), this.i18nService.t('later')], + title: this.main.i18nService.t('restartToUpdate'), + message: this.main.i18nService.t('restartToUpdate'), + detail: this.main.i18nService.t('restartToUpdateDesc', info.version), + buttons: [this.main.i18nService.t('restart'), this.main.i18nService.t('later')], cancelId: 1, defaultId: 0, noLink: true, @@ -83,8 +80,8 @@ export class UpdaterMain { autoUpdater.on('error', (error) => { if (this.doingUpdateCheckWithFeedback) { - dialog.showErrorBox(this.i18nService.t('updateError'), - error == null ? this.i18nService.t('unknown') : (error.stack || error).toString()); + dialog.showErrorBox(this.main.i18nService.t('updateError'), + error == null ? this.main.i18nService.t('unknown') : (error.stack || error).toString()); } this.reset(); diff --git a/src/main/window.main.ts b/src/main/window.main.ts index e382084fde..588cc85e46 100644 --- a/src/main/window.main.ts +++ b/src/main/window.main.ts @@ -1,11 +1,14 @@ +import { isDev } from '../scripts/utils'; import { app, BrowserWindow, screen } from 'electron'; import * as path from 'path'; import * as url from 'url'; +import { Main } from '../main'; + export class WindowMain { win: BrowserWindow; - constructor(private dev: boolean) { } + constructor(private main: Main) { } init(): Promise { return new Promise((resolve, reject) => { @@ -65,7 +68,7 @@ export class WindowMain { })); // Open the DevTools. - if (this.dev) { + if (isDev()) { this.win.webContents.openDevTools(); } diff --git a/src/services/desktopMainMessaging.service.ts b/src/services/desktopMainMessaging.service.ts index eb58e6362f..3b0b492c1d 100644 --- a/src/services/desktopMainMessaging.service.ts +++ b/src/services/desktopMainMessaging.service.ts @@ -1,14 +1,13 @@ import { MessagingService } from 'jslib/abstractions'; -import { MessagingMain } from '../main/messaging.main'; -import { WindowMain } from '../main/window.main'; +import { Main } from '../main'; export class DesktopMainMessagingService implements MessagingService { - constructor(private windowMain: WindowMain, private messagingMain: MessagingMain) { } + constructor(private main: Main) { } send(subscriber: string, arg: any = {}) { const message = Object.assign({}, { command: subscriber }, arg); - this.windowMain.win.webContents.send('messagingService', message); - this.messagingMain.onMessage(message); + this.main.windowMain.win.webContents.send('messagingService', message); + this.main.messagingMain.onMessage(message); } }