diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 95a1cc5f..4ab1ab09 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -6,6 +6,7 @@ import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; import { Component, + NgZone, OnInit, } from '@angular/core'; import { Router } from '@angular/router'; @@ -21,6 +22,7 @@ import { CryptoService } from 'jslib/abstractions/crypto.service'; import { FolderService } from 'jslib/abstractions/folder.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service'; +import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { SettingsService } from 'jslib/abstractions/settings.service'; import { SyncService } from 'jslib/abstractions/sync.service'; import { TokenService } from 'jslib/abstractions/token.service'; @@ -47,45 +49,60 @@ export class AppComponent implements OnInit { private settingsService: SettingsService, private syncService: SyncService, private passwordGenerationService: PasswordGenerationService, private cipherService: CipherService, private authService: AuthService, private router: Router, private analytics: Angulartics2, - private toasterService: ToasterService, private i18nService: I18nService) { } + private toasterService: ToasterService, private i18nService: I18nService, + private platformUtilsService: PlatformUtilsService, private ngZone: NgZone) { } ngOnInit() { - this.broadcasterService.subscribe(async (message: any) => { - switch (message.command) { - case 'loggedIn': - break; - case 'logout': - const userId = await this.userService.getUserId(); - - await Promise.all([ - this.syncService.setLastSync(new Date(0)), - this.tokenService.clearToken(), - this.cryptoService.clearKeys(), - this.userService.clear(), - this.settingsService.clear(userId), - this.cipherService.clear(userId), - this.folderService.clear(userId), - this.passwordGenerationService.clear(), - ]); - - this.doneLoggingOut(message.expired); - break; - case 'doneLoggingOut': - this.doneLoggingOut(message.expired); - break; - case 'locked': - break; - case 'unlocked': - break; - case 'syncStarted': - break; - case 'syncCompleted': - break; - default: - } + this.broadcasterService.subscribe((message: any) => { + this.ngZone.run(async () => { + switch (message.command) { + case 'loggedIn': + break; + case 'logout': + this.logOut(message.expired); + break; + case 'doneLoggingOut': + this.doneLoggingOut(message.expired); + break; + case 'locked': + break; + case 'unlocked': + break; + case 'syncStarted': + break; + case 'syncCompleted': + break; + case 'confirmLogout': + const logoutConfirmed = await this.platformUtilsService.showDialog( + this.i18nService.t('logOutConfirmation'), this.i18nService.t('logOut'), + this.i18nService.t('logOut'), this.i18nService.t('cancel')); + if (logoutConfirmed) { + this.logOut(false); + } + break; + default: + } + }); }); } + private async logOut(expired: boolean) { + const userId = await this.userService.getUserId(); + + await Promise.all([ + this.syncService.setLastSync(new Date(0)), + this.tokenService.clearToken(), + this.cryptoService.clearKeys(), + this.userService.clear(), + this.settingsService.clear(userId), + this.cipherService.clear(userId), + this.folderService.clear(userId), + this.passwordGenerationService.clear(), + ]); + + this.doneLoggingOut(expired); + } + private doneLoggingOut(expired: boolean) { this.authService.logOut(() => { this.analytics.eventTrack.next({ action: 'Logged Out' }); diff --git a/src/app/vault/add-edit.component.ts b/src/app/vault/add-edit.component.ts index a69dcc55..810fd195 100644 --- a/src/app/vault/add-edit.component.ts +++ b/src/app/vault/add-edit.component.ts @@ -35,6 +35,7 @@ import { SecureNoteView } from 'jslib/models/view/secureNoteView'; export class AddEditComponent implements OnChanges { @Input() folderId: string; @Input() cipherId: string; + @Input() type: CipherType; @Output() onSavedCipher = new EventEmitter(); @Output() onDeletedCipher = new EventEmitter(); @Output() onCancelled = new EventEmitter(); @@ -119,7 +120,7 @@ export class AddEditComponent implements OnChanges { this.title = this.i18nService.t('addItem'); this.cipher = new CipherView(); this.cipher.folderId = this.folderId; - this.cipher.type = CipherType.Login; + this.cipher.type = this.type == null ? CipherType.Login : this.type; this.cipher.login = new LoginView(); this.cipher.card = new CardView(); this.cipher.identity = new IdentityView(); diff --git a/src/app/vault/vault.component.html b/src/app/vault/vault.component.html index f5fc4d53..660337b2 100644 --- a/src/app/vault/vault.component.html +++ b/src/app/vault/vault.component.html @@ -21,6 +21,7 @@ { + this.ngZone.run(async () => { + let detectChanges = true; + + switch (message.command) { + case 'newLogin': + this.addCipher(CipherType.Login); + break; + case 'newCard': + this.addCipher(CipherType.Card); + break; + case 'newIdentity': + this.addCipher(CipherType.Identity); + break; + case 'newSecureNote': + this.addCipher(CipherType.SecureNote); + break; + case 'newFolder': + await this.addFolder(); + break; + default: + detectChanges = false; + break; + } + + if (detectChanges) { + this.changeDetectorRef.detectChanges(); + } + }); + }); + this.route.queryParams.subscribe(async (params) => { if (params.cipherId) { const cipherView = new CipherView(); @@ -108,11 +146,12 @@ export class VaultComponent implements OnInit { this.go(); } - addCipher() { + addCipher(type: CipherType = null) { if (this.action === 'add') { return; } + this.addType = type; this.action = 'add'; this.cipherId = null; this.go(); diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index 69517af7..b0b6f38f 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -600,5 +600,11 @@ }, "loginExpired": { "message": "Your login session has expired." + }, + "logOutConfirmation": { + "message": "Are you sure you want to log out?" + }, + "logOut": { + "message": "Log Out" } } diff --git a/src/main.ts b/src/main.ts index 3c013106..def0d90e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -17,10 +17,9 @@ if (watch) { const i18nService = new I18nService('en', './locales/'); i18nService.init().then(() => { }); -let win: BrowserWindow; +const windowMain = new WindowMain(dev); const messagingMain = new MessagingMain(); -const menuMain = new MenuMain(); -const windowMain = new WindowMain(win, dev); +const menuMain = new MenuMain(windowMain); messagingMain.init(); menuMain.init(); diff --git a/src/main/menu.main.ts b/src/main/menu.main.ts index 42c13937..617dfd7d 100644 --- a/src/main/menu.main.ts +++ b/src/main/menu.main.ts @@ -1,10 +1,69 @@ -import { app, Menu, MenuItemConstructorOptions } from 'electron'; +import { + app, + BrowserWindow, + Menu, + MenuItemConstructorOptions, + ipcMain, +} from 'electron'; + +import { WindowMain } from './window.main'; export class MenuMain { - constructor() { } + constructor(private windowMain: WindowMain) { } init() { + const self = this; + const template: MenuItemConstructorOptions[] = [ + { + label: 'bitwarden' + }, + { + label: 'File', + submenu: [ + { + label: 'New Item', + submenu: [ + { + label: 'New Login', + click() { + self.send('newLogin'); + } + }, + { + label: 'New Card', + click() { + self.send('newCard'); + } + }, + { + label: 'New Identity', + click() { + self.send('newIdentity'); + } + }, + { + label: 'New Secure Note', + click() { + self.send('newSecureNote'); + } + } + ] + }, + { + label: 'New Login', + click() { + self.send('newLogin'); + } + }, + { + label: 'New Folder', + click() { + self.send('newFolder'); + } + } + ] + }, { label: 'Edit', submenu: [ @@ -33,6 +92,17 @@ export class MenuMain { { role: 'togglefullscreen' } ] }, + { + label: 'Account', + submenu: [ + { + label: 'Log Out', + click() { + self.send('confirmLogout'); + } + }, + ] + }, { role: 'window', submenu: [ @@ -52,32 +122,64 @@ export class MenuMain { ]; if (process.platform === 'darwin') { - template.unshift({ - label: app.getName(), - submenu: [ - { role: 'about' }, - { type: 'separator' }, - { role: 'services', submenu: [] }, - { type: 'separator' }, - { role: 'hide' }, - { role: 'hideothers' }, - { role: 'unhide' }, - { type: 'separator' }, - { role: 'quit' } - ] - }) + template[0].submenu = [ + { + label: 'Settings', + click() { + self.send('openSettings'); + } + }, + { + label: 'Lock', + click() { + self.send('lockApp'); + } + }, + { type: 'separator' }, + { role: 'about' }, + { type: 'separator' }, + { role: 'services', submenu: [] }, + { type: 'separator' }, + { role: 'hide' }, + { role: 'hideothers' }, + { role: 'unhide' }, + { type: 'separator' }, + { role: 'quit' } + ]; // Window menu - template[3].submenu = [ + template[4].submenu = [ { role: 'close' }, { role: 'minimize' }, { role: 'zoom' }, { type: 'separator' }, { role: 'front' } ] - }; + } else { + template[0].submenu = [ + { + label: 'Settings', + click() { + self.send('openSettings'); + } + }, + { + label: 'Lock', + click() { + self.send('lockApp'); + } + }, + ]; + } const menu = Menu.buildFromTemplate(template); Menu.setApplicationMenu(menu); } + + send(command: string, message?: any) { + this.windowMain.win.webContents.send('messagingService', { + command: command, + message: message, + }); + } } diff --git a/src/main/messaging.main.ts b/src/main/messaging.main.ts index a5e868b1..03a81a6f 100644 --- a/src/main/messaging.main.ts +++ b/src/main/messaging.main.ts @@ -12,7 +12,6 @@ export class MessagingMain { case 'logout': break; case 'syncCompleted': - console.log('sync completed!!'); break; default: break; diff --git a/src/main/window.main.ts b/src/main/window.main.ts index e12b5f08..281d1d5a 100644 --- a/src/main/window.main.ts +++ b/src/main/window.main.ts @@ -3,7 +3,9 @@ import * as path from 'path'; import * as url from 'url'; export class WindowMain { - constructor(private win: BrowserWindow, private dev: boolean) { } + win: BrowserWindow; + + constructor(private dev: boolean) { } init() { try { diff --git a/src/services/desktopMessaging.service.ts b/src/services/desktopMessaging.service.ts index ccb242ee..30cfb91e 100644 --- a/src/services/desktopMessaging.service.ts +++ b/src/services/desktopMessaging.service.ts @@ -5,7 +5,13 @@ import { MessagingService } from 'jslib/abstractions'; import { BroadcasterService } from '../app/services/broadcaster.service'; export class DesktopMessagingService implements MessagingService { - constructor(private broadcasterService: BroadcasterService) { } + constructor(private broadcasterService: BroadcasterService) { + ipcRenderer.on('messagingService', async (event: any, message: any) => { + if (message.command) { + this.send(message.command, message); + } + }); + } send(subscriber: string, arg: any = {}) { const message = Object.assign({}, { command: subscriber }, arg);