1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-17 16:03:20 +00:00

[Account Switching] [Feature] Add the ability to maintain state for up to 5 accounts at once (#1079)

* [refactor] Remove references to deprecated services

* [feature] Implement account switching

* [bug] Fix state handling for authentication dependent system menu items

* [bug] Enable the account switcher to fucntion properly when switching to a locked accounts

* [feature] Enable locking any account from the menu

* [bug] Ensure the avatar instance used in the account switcher updates on account change

* [style] Fix lint complaints

* [bug] Ensure the logout command callback can handle any user in state

* [style] Fix lint complaints

* rollup

* [style] Fix lint complaints

* [bug] Don't clean up state until everything else is done on logout

* [bug] Navigate to vault on a succesful account switch

* [bug] Init the state service on start

* [feature] Limit account switching to 5 account maximum

* [bug] Resolve app lock state with 5 logged out accounts

* [chore] Update account refrences to match recent jslib restructuring

* [bug] Add missing awaits

* [bug] Update app menu on logout

* [bug] Hide the switcher if there are no authed accounts

* [bug] Move authenticationStatus display information out of jslib

* [bug] Remove unused active style from scss

* [refactor] Rewrite the menu bar

* [style] Fix lint complaints

* [bug] Clean state of loggout out user after redirect

* [bug] Redirect on logout if not explicity provided a userId that isn't active

* [bug] Relocated several settings items to persistant storage

* [bug] Correct account switcher styles on all themes

* [chore] Include state migration service in services

* [bug] Swap to next account on logout

* [bug] Correct DI service

* [bug] fix loginGuard deps in services.module

* [chore] update jslib

* [bug] Remove badly merged scss

* [chore] update jslib

* [review] Code review cleanup

* [review] Code review cleanup

Co-authored-by: Hinton <oscar@oscarhinton.com>
This commit is contained in:
Addison Beck
2021-12-15 17:32:00 -05:00
committed by GitHub
parent 5865f08b37
commit 0b306ca1a7
56 changed files with 2106 additions and 837 deletions

96
src/main/menu.about.ts Normal file
View File

@@ -0,0 +1,96 @@
import {
BrowserWindow,
clipboard,
dialog,
MenuItemConstructorOptions,
} from 'electron';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { UpdaterMain } from 'jslib-electron/updater.main';
import { isMac, isSnapStore, isWindowsStore } from 'jslib-electron/utils';
import { IMenubarMenu } from './menubar';
export class AboutMenu implements IMenubarMenu {
readonly id: string = 'about';
get visible(): boolean {
return !isMac();
}
get label(): string {
return this.localize('about');
}
get items(): MenuItemConstructorOptions[] {
return [
this.separator,
this.checkForUpdates,
this.aboutBitwarden,
];
}
private readonly _i18nService: I18nService;
private readonly _updater: UpdaterMain;
private readonly _window: BrowserWindow;
private readonly _version: string;
constructor(
i18nService: I18nService,
version: string,
window: BrowserWindow,
updater: UpdaterMain,
) {
this._i18nService = i18nService;
this._updater = updater;
this._version = version;
this._window = window;
}
private get separator(): MenuItemConstructorOptions {
return { type: 'separator' };
}
private get checkForUpdates(): MenuItemConstructorOptions {
return {
id: 'checkForUpdates',
label: this.localize('checkForUpdates'),
visible: !isWindowsStore() && !isSnapStore(),
click: () => this.checkForUpdate(),
};
}
private get aboutBitwarden(): MenuItemConstructorOptions {
return {
id: 'aboutBitwarden',
label: this.localize('aboutBitwarden'),
click: async () => {
const aboutInformation = this.localize('version', this._version) +
'\nShell ' + process.versions.electron +
'\nRenderer ' + process.versions.chrome +
'\nNode ' + process.versions.node +
'\nArchitecture ' + process.arch;
const result = await dialog.showMessageBox(this._window, {
title: 'Bitwarden',
message: 'Bitwarden',
detail: aboutInformation,
type: 'info',
noLink: true,
buttons: [this.localize('ok'), this.localize('copy')],
});
if (result.response === 1) {
clipboard.writeText(aboutInformation);
}
},
};
}
private localize(s: string, p?: string) {
return this._i18nService.t(s, p);
}
private async checkForUpdate() {
this._updater.checkForUpdate(true);
}
}

121
src/main/menu.account.ts Normal file
View File

@@ -0,0 +1,121 @@
import {
BrowserWindow,
dialog,
MenuItemConstructorOptions,
shell,
} from 'electron';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { isMacAppStore, isWindowsStore } from 'jslib-electron/utils';
import { IMenubarMenu } from './menubar';
export class AccountMenu implements IMenubarMenu {
readonly id: string = 'accountMenu';
get label(): string {
return this.localize('account');
}
get items(): MenuItemConstructorOptions[] {
return [
this.premiumMembership,
this.changeMasterPassword,
this.twoStepLogin,
this.fingerprintPhrase,
];
}
private readonly _i18nService: I18nService;
private readonly _messagingService: MessagingService;
private readonly _webVaultUrl: string;
private readonly _window: BrowserWindow;
private readonly _isAuthenticated: boolean;
constructor(
i18nService: I18nService,
messagingService: MessagingService,
webVaultUrl: string,
window: BrowserWindow,
isAuthenticated: boolean,
) {
this._i18nService = i18nService;
this._messagingService = messagingService;
this._webVaultUrl = webVaultUrl;
this._window = window;
this._isAuthenticated = isAuthenticated;
}
private get premiumMembership(): MenuItemConstructorOptions {
return {
label: this.localize('premiumMembership'),
click: () => this.sendMessage('openPremium'),
id: 'premiumMembership',
visible: !isWindowsStore() && !isMacAppStore(),
enabled: this._isAuthenticated,
};
}
private get changeMasterPassword(): MenuItemConstructorOptions {
return {
label: this.localize('changeMasterPass'),
id: 'changeMasterPass',
click: async () => {
const result = await dialog.showMessageBox(this._window, {
title: this.localize('changeMasterPass'),
message: this.localize('changeMasterPass'),
detail: this.localize('changeMasterPasswordConfirmation'),
buttons: [this.localize('yes'), this.localize('no')],
cancelId: 1,
defaultId: 0,
noLink: true,
});
if (result.response === 0) {
shell.openExternal(this._webVaultUrl);
}
},
enabled: this._isAuthenticated,
};
}
private get twoStepLogin(): MenuItemConstructorOptions {
return {
label: this.localize('twoStepLogin'),
id: 'twoStepLogin',
click: async () => {
const result = await dialog.showMessageBox(this._window, {
title: this.localize('twoStepLogin'),
message: this.localize('twoStepLogin'),
detail: this.localize('twoStepLoginConfirmation'),
buttons: [this.localize('yes'), this.localize('no')],
cancelId: 1,
defaultId: 0,
noLink: true,
});
if (result.response === 0) {
shell.openExternal(this._webVaultUrl);
}
},
enabled: this._isAuthenticated,
};
}
private get fingerprintPhrase(): MenuItemConstructorOptions {
return {
label: this.localize('fingerprintPhrase'),
id: 'fingerprintPhrase',
click: () => this.sendMessage('showFingerprintPhrase'),
enabled: this._isAuthenticated,
};
}
private localize(s: string) {
return this._i18nService.t(s);
}
private sendMessage(message: string, args?: any) {
this._messagingService.send(message, args);
}
}

237
src/main/menu.bitwarden.ts Normal file
View File

@@ -0,0 +1,237 @@
import {
BrowserWindow,
dialog,
MenuItem,
MenuItemConstructorOptions,
} from 'electron';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { UpdaterMain } from 'jslib-electron/updater.main';
import { isMacAppStore, isSnapStore, isWindowsStore } from 'jslib-electron/utils';
import { IMenubarMenu } from './menubar';
import { MenuAccount } from './menu.updater';
// AKA: "FirstMenu" or "MacMenu" - the first menu that shows on all macOs apps
export class BitwardenMenu implements IMenubarMenu {
readonly id: string = 'bitwarden';
readonly label: string = 'Bitwarden';
get items(): MenuItemConstructorOptions[] {
return [
this.aboutBitwarden,
this.checkForUpdates,
this.separator,
this.settings,
this.lock,
this.lockAll,
this.logOut,
this.services,
this.separator,
this.hideBitwarden,
this.hideOthers,
this.showAll,
this.separator,
this.quitBitwarden,
];
}
private readonly _i18nService: I18nService;
private readonly _updater: UpdaterMain;
private readonly _messagingService: MessagingService;
private readonly _accounts: { [userId: string]: MenuAccount };
private readonly _window: BrowserWindow;
constructor(
i18nService: I18nService,
messagingService: MessagingService,
updater: UpdaterMain,
window: BrowserWindow,
accounts: { [userId: string]: MenuAccount },
) {
this._i18nService = i18nService;
this._updater = updater;
this._messagingService = messagingService;
this._window = window;
this._accounts = accounts;
}
private get hasAccounts(): boolean {
return this._accounts != null && Object.keys(this._accounts).length > 0;
}
private get aboutBitwarden(): MenuItemConstructorOptions {
return {
id: 'aboutBitwarden',
label: this.localize('aboutBitwarden'),
role: 'about',
visible: isMacAppStore(),
};
}
private get checkForUpdates(): MenuItemConstructorOptions {
return {
id: 'checkForUpdates',
label: this.localize('checkForUpdates'),
click: menuItem => this.checkForUpdate(menuItem),
visible: !isMacAppStore() && !isWindowsStore() && !isSnapStore(),
};
}
private get separator(): MenuItemConstructorOptions {
return {
type: 'separator',
};
}
private get settings(): MenuItemConstructorOptions {
return {
id: 'settings',
label: this.localize(process.platform === 'darwin' ?
'preferences' :
'settings'
),
click: () => this.sendMessage('openSettings'),
accelerator: 'CmdOrCtrl+,',
};
}
private get lock(): MenuItemConstructorOptions {
return {
id: 'lock',
label: this.localize('lockVault'),
submenu: this.lockSubmenu,
enabled: this.hasAccounts,
};
}
private get lockSubmenu(): MenuItemConstructorOptions[] {
const value: MenuItemConstructorOptions[] = [];
for (const userId in this._accounts) {
if (userId == null) {
continue;
}
value.push({
label: this._accounts[userId].email,
id: `lockNow_${this._accounts[userId].userId}`,
click: () => this.sendMessage('lockVault', { userId: this._accounts[userId].userId }),
enabled: !this._accounts[userId].isLocked,
visible: this._accounts[userId].isAuthenticated,
});
}
return value;
}
private get lockAll(): MenuItemConstructorOptions {
return {
id: 'lockAllNow',
label: this.localize('lockAllVaults'),
click: () => this.sendMessage('lockAllVaults'),
accelerator: 'CmdOrCtrl+L',
enabled: this.hasAccounts,
};
}
private get logOut(): MenuItemConstructorOptions {
return {
id: 'logOut',
label: this.localize('logOut'),
submenu: this.logOutSubmenu,
enabled: this.hasAccounts,
};
}
private get logOutSubmenu(): MenuItemConstructorOptions[] {
const value: MenuItemConstructorOptions[] = [];
for (const userId in this._accounts) {
if (userId == null) {
continue;
}
value.push({
label: this._accounts[userId].email,
id: `logOut_${this._accounts[userId].userId}`,
click: async () => {
const result = await dialog.showMessageBox(this._window, {
title: this.localize('logOut'),
message: this.localize('logOut'),
detail: this.localize('logOutConfirmation'),
buttons: [this.localize('logOut'), this.localize('cancel')],
cancelId: 1,
defaultId: 0,
noLink: true,
});
if (result.response === 0) {
this.sendMessage('logout', { userId: this._accounts[userId].userId });
}
},
visible: this._accounts[userId].isAuthenticated,
});
}
return value;
}
private get services(): MenuItemConstructorOptions {
return {
id: 'services',
label: this.localize('services'),
role: 'services',
submenu: [],
visible: isMacAppStore(),
};
}
private get hideBitwarden(): MenuItemConstructorOptions {
return {
id: 'hideBitwarden',
label: this.localize('hideBitwarden'),
role: 'hide',
visible: isMacAppStore(),
};
}
private get hideOthers(): MenuItemConstructorOptions {
return {
id: 'hideOthers',
label: this.localize('hideOthers'),
role: 'hideOthers',
visible: isMacAppStore(),
};
}
private get showAll(): MenuItemConstructorOptions {
return {
id: 'showAll',
label: this.localize('showAll'),
role: 'unhide',
visible: isMacAppStore(),
};
}
private get quitBitwarden(): MenuItemConstructorOptions {
return {
id: 'quitBitwarden',
label: this.localize('quitBitwarden'),
role: 'quit',
visible: isMacAppStore(),
};
}
private localize(s: string) {
return this._i18nService.t(s);
}
private async checkForUpdate(menuItem: MenuItem) {
menuItem.enabled = false;
this._updater.checkForUpdate(true);
menuItem.enabled = true;
}
private sendMessage(message: string, args?: any) {
this._messagingService.send(message, args);
}
}

134
src/main/menu.edit.ts Normal file
View File

@@ -0,0 +1,134 @@
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { IMenubarMenu } from './menubar';
import { MenuItemConstructorOptions } from 'electron';
export class EditMenu implements IMenubarMenu {
readonly id: string = 'editMenu';
get label(): string {
return this.localize('edit');
}
get items(): MenuItemConstructorOptions[] {
return [
this.undo,
this.redo,
this.separator,
this.cut,
this.copy,
this.paste,
this.separator,
this.selectAll,
this.separator,
this.copyUsername,
this.copyPassword,
this.copyVerificationCodeTotp,
];
}
private readonly _i18nService: I18nService;
private readonly _messagingService: MessagingService;
private readonly _isAuthenticated: boolean;
constructor(
i18nService: I18nService,
messagingService: MessagingService,
isAuthenticated: boolean,
) {
this._i18nService = i18nService;
this._messagingService = messagingService;
this._isAuthenticated = isAuthenticated;
}
private get undo(): MenuItemConstructorOptions {
return {
id: 'undo',
label: this.localize('undo'),
role: 'undo',
};
}
private get redo(): MenuItemConstructorOptions {
return {
id: 'redo',
label: this.localize('redo'),
role: 'redo',
};
}
private get separator(): MenuItemConstructorOptions {
return { type: 'separator' };
}
private get cut(): MenuItemConstructorOptions {
return {
id: 'cut',
label: this.localize('cut'),
role: 'cut',
};
}
private get copy(): MenuItemConstructorOptions {
return {
id: 'copy',
label: this.localize('copy'),
role: 'copy',
};
}
private get paste(): MenuItemConstructorOptions {
return {
id: 'paste',
label: this.localize('paste'),
role: 'paste',
};
}
private get selectAll(): MenuItemConstructorOptions {
return {
id: 'selectAll',
label: this.localize('selectAll'),
role: 'selectAll',
};
}
private get copyUsername(): MenuItemConstructorOptions {
return {
label: this.localize('copyUsername'),
id: 'copyUsername',
click: () => this.sendMessage('copyUsername'),
accelerator: 'CmdOrCtrl+U',
enabled: this._isAuthenticated,
};
}
private get copyPassword(): MenuItemConstructorOptions {
return {
label: this.localize('copyPassword'),
id: 'copyPassword',
click: () => this.sendMessage('copyPassword'),
accelerator: 'CmdOrCtrl+P',
enabled: this._isAuthenticated,
};
}
private get copyVerificationCodeTotp(): MenuItemConstructorOptions {
return {
label: this.localize('copyVerificationCodeTotp'),
id: 'copyTotp',
click: () => this.sendMessage('copyTotp'),
accelerator: 'CmdOrCtrl+T',
};
}
private localize(s: string) {
return this._i18nService.t(s);
}
private sendMessage(message: string) {
this._messagingService.send(message);
}
}

134
src/main/menu.file.ts Normal file
View File

@@ -0,0 +1,134 @@
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { isMacAppStore } from 'jslib-electron/utils';
import { IMenubarMenu } from './menubar';
import { MenuItemConstructorOptions } from 'electron';
export class FileMenu implements IMenubarMenu {
readonly id: string = 'fileMenu';
get label(): string {
return this.localize('file');
}
get items(): MenuItemConstructorOptions[] {
return [
this.addNewLogin,
this.addNewItem,
this.addNewFolder,
this.separator,
this.syncVault,
this.exportVault,
this.quitBitwarden,
];
}
private readonly _i18nService: I18nService;
private readonly _messagingService: MessagingService;
private readonly _isAuthenticated: boolean;
constructor(
i18nService: I18nService,
messagingService: MessagingService,
isAuthenticated: boolean,
) {
this._i18nService = i18nService;
this._messagingService = messagingService;
this._isAuthenticated = isAuthenticated;
}
private get addNewLogin(): MenuItemConstructorOptions {
return {
label: this.localize('addNewLogin'),
click: () => this.sendMessage('newLogin'),
accelerator: 'CmdOrCtrl+N',
id: 'addNewLogin',
};
}
private get addNewItem(): MenuItemConstructorOptions {
return {
label: this.localize('addNewItem'),
id: 'addNewItem',
submenu: this.addNewItemSubmenu,
enabled: this._isAuthenticated,
};
}
private get addNewItemSubmenu(): MenuItemConstructorOptions[] {
return [
{
id: 'typeLogin',
label: this.localize('typeLogin'),
click: () => this.sendMessage('newLogin'),
accelerator: 'CmdOrCtrl+Shift+L',
},
{
id: 'typeCard',
label: this.localize('typeCard'),
click: () => this.sendMessage('newCard'),
accelerator: 'CmdOrCtrl+Shift+C',
},
{
id: 'typeIdentity',
label: this.localize('typeIdentity'),
click: () => this.sendMessage('newIdentity'),
accelerator: 'CmdOrCtrl+Shift+I',
},
{
id: 'typeSecureNote',
label: this.localize('typeSecureNote'),
click: () => this.sendMessage('newSecureNote'),
accelerator: 'CmdOrCtrl+Shift+S',
},
];
}
private get addNewFolder(): MenuItemConstructorOptions {
return {
id: 'addNewFolder',
label: this.localize('addNewFolder'),
click: () => this.sendMessage('newFolder'),
};
}
private get separator(): MenuItemConstructorOptions {
return { type: 'separator' };
}
private get syncVault(): MenuItemConstructorOptions {
return {
id: 'syncVault',
label: this.localize('syncVault'),
click: () => this.sendMessage('syncVault'),
};
}
private get exportVault(): MenuItemConstructorOptions {
return {
id: 'exportVault',
label: this.localize('exportVault'),
click: () => this.sendMessage('exportVault'),
};
}
private get quitBitwarden(): MenuItemConstructorOptions {
return {
id: 'quitBitwarden',
label: this.localize('quitBitwarden'),
visible: !isMacAppStore(),
role: 'quit',
};
}
private localize(s: string) {
return this._i18nService.t(s);
}
private sendMessage(message: string) {
this._messagingService.send(message);
}
}

223
src/main/menu.help.ts Normal file
View File

@@ -0,0 +1,223 @@
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { IMenubarMenu } from './menubar';
import { shell } from 'electron';
import { isMacAppStore, isWindowsStore } from 'jslib-electron/utils';
import { MenuItemConstructorOptions } from 'electron';
export class HelpMenu implements IMenubarMenu {
readonly id: string = 'help';
get label(): string {
return this.localize('help');
}
get items(): MenuItemConstructorOptions[] {
return [
this.emailUs,
this.visitOurWebsite,
this.fileBugReport,
this.legal,
this.separator,
this.followUs,
this.separator,
this.goToWebVault,
this.separator,
this.getMobileApp,
this.getBrowserExtension,
];
}
private readonly _i18nService: I18nService;
private readonly _webVaultUrl: string;
constructor(
i18nService: I18nService,
webVaultUrl: string
) {
this._i18nService = i18nService;
this._webVaultUrl = webVaultUrl;
}
private get emailUs(): MenuItemConstructorOptions {
return {
id: 'emailUs',
label: this.localize('emailUs'),
click: () => shell.openExternal('mailTo:hello@bitwarden.com'),
};
}
private get visitOurWebsite(): MenuItemConstructorOptions {
return {
id: 'visitOurWebsite',
label: this.localize('visitOurWebsite'),
click: () => shell.openExternal('https://bitwarden.com/contact'),
};
}
private get fileBugReport(): MenuItemConstructorOptions {
return {
id: 'fileBugReport',
label: this.localize('fileBugReport'),
click: () => shell.openExternal('https://github.com/bitwarden/desktop/issues'),
};
}
private get legal(): MenuItemConstructorOptions {
return {
id: 'legal',
label: this.localize('legal'),
visible: !isMacAppStore(),
submenu: this.legalSubmenu,
};
}
private get legalSubmenu(): MenuItemConstructorOptions[] {
return [
{
id: 'termsOfService',
label: this.localize('termsOfService'),
click: () => shell.openExternal('https://bitwarden.com/terms/'),
},
{
id: 'privacyPolicy',
label: this.localize('privacyPolicy'),
click: () => shell.openExternal('https://bitwarden.com/privacy/'),
},
];
}
private get separator(): MenuItemConstructorOptions {
return { type: 'separator' };
}
private get followUs(): MenuItemConstructorOptions {
return {
id: 'followUs',
label: this.localize('followUs'),
submenu: this.followUsSubmenu,
};
}
private get followUsSubmenu(): MenuItemConstructorOptions[] {
return [
{
id: 'blog',
label: this.localize('blog'),
click: () => shell.openExternal('https://blog.bitwarden.com'),
},
{
id: 'twitter',
label: 'Twitter',
click: () => shell.openExternal('https://twitter.com/bitwarden'),
},
{
id: 'facebook',
label: 'Facebook',
click: () => shell.openExternal('https://www.facebook.com/bitwarden/'),
},
{
id: 'github',
label: 'GitHub',
click: () => shell.openExternal('https://github.com/bitwarden'),
},
];
}
private get goToWebVault(): MenuItemConstructorOptions {
return {
id: 'goToWebVault',
label: this.localize('goToWebVault'),
click: () => shell.openExternal(this._webVaultUrl),
};
}
private get getMobileApp(): MenuItemConstructorOptions {
return {
id: 'getMobileApp',
label: this.localize('getMobileApp'),
visible: !isWindowsStore(),
submenu: this.getMobileAppSubmenu,
};
}
private get getMobileAppSubmenu(): MenuItemConstructorOptions[] {
return [
{
id: 'iOS',
label: 'iOS',
click: () => {
shell.openExternal('https://itunes.apple.com/app/' +
'bitwarden-free-password-manager/id1137397744?mt=8');
},
},
{
id: 'android',
label: 'Android',
click: () => {
shell.openExternal('https://play.google.com/store/apps/' +
'details?id=com.x8bit.bitwarden');
},
},
];
}
private get getBrowserExtension(): MenuItemConstructorOptions {
return {
id: 'getBrowserExtension',
label: this.localize('getBrowserExtension'),
visible: !isWindowsStore(),
submenu: this.getBrowserExtensionSubmenu,
};
}
private get getBrowserExtensionSubmenu(): MenuItemConstructorOptions[] {
return [
{
id: 'chrome',
label: 'Chrome',
click: () => {
shell.openExternal('https://chrome.google.com/webstore/detail/' +
'bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb');
},
},
{
id: 'firefox',
label: 'Firefox',
click: () => {
shell.openExternal('https://addons.mozilla.org/firefox/addon/' +
'bitwarden-password-manager/');
},
},
{
id: 'firefox',
label: 'Opera',
click: () => {
shell.openExternal('https://addons.opera.com/extensions/details/' +
'bitwarden-free-password-manager/');
},
},
{
id: 'firefox',
label: 'Edge',
click: () => {
shell.openExternal('https://microsoftedge.microsoft.com/addons/' +
'detail/jbkfoedolllekgbhcbcoahefnbanhhlh');
},
},
{
id: 'safari',
label: 'Safari',
click: () => {
shell.openExternal('https://bitwarden.com/download/');
},
},
];
}
private localize(s: string) {
return this._i18nService.t(s);
}
}

View File

@@ -1,525 +1,54 @@
import {
app,
BrowserWindow,
clipboard,
dialog,
ipcMain,
Menu,
MenuItem,
MenuItemConstructorOptions,
shell,
} from 'electron';
import { Main } from '../main';
import { BaseMenu } from 'jslib-electron/baseMenu';
import { isMacAppStore, isSnapStore, isWindowsStore } from 'jslib-electron/utils';
import { MenuUpdateRequest } from './menu.updater';
import { Menubar } from './menubar';
import { ConstantsService } from 'jslib-common/services/constants.service';
const cloudWebVaultUrl: string = 'https://vault.bitwarden.com';
export class MenuMain extends BaseMenu {
menu: Menu;
updateMenuItem: MenuItem;
addNewLogin: MenuItem;
addNewItem: MenuItem;
addNewFolder: MenuItem;
syncVault: MenuItem;
exportVault: MenuItem;
settings: MenuItem;
lockNow: MenuItem;
logOut: MenuItem;
twoStepLogin: MenuItem;
fingerprintPhrase: MenuItem;
changeMasterPass: MenuItem;
premiumMembership: MenuItem;
passwordGenerator: MenuItem;
passwordHistory: MenuItem;
searchVault: MenuItem;
copyUsername: MenuItem;
copyPassword: MenuItem;
copyTotp: MenuItem;
unlockedRequiredMenuItems: MenuItem[] = [];
constructor(private main: Main) {
super(main.i18nService, main.windowMain);
}
init() {
this.initProperties();
async init() {
this.initContextMenu();
this.initApplicationMenu();
this.updateMenuItem = this.menu.getMenuItemById('checkForUpdates');
this.addNewLogin = this.menu.getMenuItemById('addNewLogin');
this.addNewItem = this.menu.getMenuItemById('addNewItem');
this.addNewFolder = this.menu.getMenuItemById('addNewFolder');
this.syncVault = this.menu.getMenuItemById('syncVault');
this.exportVault = this.menu.getMenuItemById('exportVault');
this.settings = this.menu.getMenuItemById('settings');
this.lockNow = this.menu.getMenuItemById('lockNow');
this.logOut = this.menu.getMenuItemById('logOut');
this.twoStepLogin = this.menu.getMenuItemById('twoStepLogin');
this.fingerprintPhrase = this.menu.getMenuItemById('fingerprintPhrase');
this.changeMasterPass = this.menu.getMenuItemById('changeMasterPass');
this.premiumMembership = this.menu.getMenuItemById('premiumMembership');
this.passwordGenerator = this.menu.getMenuItemById('passwordGenerator');
this.passwordHistory = this.menu.getMenuItemById('passwordHistory');
this.searchVault = this.menu.getMenuItemById('searchVault');
this.copyUsername = this.menu.getMenuItemById('copyUsername');
this.copyPassword = this.menu.getMenuItemById('copyPassword');
this.copyTotp = this.menu.getMenuItemById('copyTotp');
this.unlockedRequiredMenuItems = [
this.addNewLogin, this.addNewItem, this.addNewFolder,
this.syncVault, this.exportVault, this.settings, this.lockNow, this.twoStepLogin, this.fingerprintPhrase,
this.changeMasterPass, this.premiumMembership, this.passwordGenerator, this.passwordHistory,
this.searchVault, this.copyUsername, this.copyPassword];
this.updateApplicationMenuState(false, true, false);
await this.setMenu();
}
updateApplicationMenuState(isAuthenticated: boolean, isLocked: boolean, hideChangeMasterPass: boolean) {
if (isAuthenticated != null && isLocked != null) {
this.unlockedRequiredMenuItems.forEach((mi: MenuItem) => {
if (mi != null) {
mi.enabled = isAuthenticated && !isLocked;
}
});
if (this.logOut != null) {
this.logOut.enabled = isAuthenticated;
}
}
if (this.changeMasterPass != null) {
this.changeMasterPass.visible = !(hideChangeMasterPass ?? false);
}
if (this.menu != null) {
Menu.setApplicationMenu(this.menu);
}
async updateApplicationMenuState(updateRequest: MenuUpdateRequest) {
await this.setMenu(updateRequest);
}
private initApplicationMenu() {
const accountSubmenu: MenuItemConstructorOptions[] = [
{
label: this.main.i18nService.t('changeMasterPass'),
id: 'changeMasterPass',
click: async () => {
const result = await dialog.showMessageBox(this.main.windowMain.win, {
title: this.main.i18nService.t('changeMasterPass'),
message: this.main.i18nService.t('changeMasterPass'),
detail: this.main.i18nService.t('changeMasterPasswordConfirmation'),
buttons: [this.main.i18nService.t('yes'), this.main.i18nService.t('no')],
cancelId: 1,
defaultId: 0,
noLink: true,
});
if (result.response === 0) {
await this.openWebVault();
}
},
},
{
label: this.main.i18nService.t('twoStepLogin'),
id: 'twoStepLogin',
click: async () => {
const result = await dialog.showMessageBox(this.main.windowMain.win, {
title: this.main.i18nService.t('twoStepLogin'),
message: this.main.i18nService.t('twoStepLogin'),
detail: this.main.i18nService.t('twoStepLoginConfirmation'),
buttons: [this.main.i18nService.t('yes'), this.main.i18nService.t('no')],
cancelId: 1,
defaultId: 0,
noLink: true,
});
if (result.response === 0) {
await this.openWebVault();
}
},
},
{
label: this.main.i18nService.t('fingerprintPhrase'),
id: 'fingerprintPhrase',
click: () => this.main.messagingService.send('showFingerprintPhrase'),
},
{ type: 'separator' },
{
label: this.i18nService.t('logOut'),
id: 'logOut',
click: async () => {
const result = await dialog.showMessageBox(this.windowMain.win, {
title: this.i18nService.t('logOut'),
message: this.i18nService.t('logOut'),
detail: this.i18nService.t('logOutConfirmation'),
buttons: [this.i18nService.t('logOut'), this.i18nService.t('cancel')],
cancelId: 1,
defaultId: 0,
noLink: true,
});
if (result.response === 0) {
this.main.messagingService.send('logout');
}
},
},
];
this.editMenuItemOptions.submenu = (this.editMenuItemOptions.submenu as MenuItemConstructorOptions[]).concat([
{ type: 'separator' },
{
label: this.main.i18nService.t('copyUsername'),
id: 'copyUsername',
click: () => this.main.messagingService.send('copyUsername'),
accelerator: 'CmdOrCtrl+U',
},
{
label: this.main.i18nService.t('copyPassword'),
id: 'copyPassword',
click: () => this.main.messagingService.send('copyPassword'),
accelerator: 'CmdOrCtrl+P',
},
{
label: this.main.i18nService.t('copyVerificationCodeTotp'),
id: 'copyTotp',
click: () => this.main.messagingService.send('copyTotp'),
accelerator: 'CmdOrCtrl+T',
},
]);
if (!isWindowsStore() && !isMacAppStore()) {
accountSubmenu.unshift({
label: this.main.i18nService.t('premiumMembership'),
click: () => this.main.messagingService.send('openPremium'),
id: 'premiumMembership',
});
}
let helpSubmenu: MenuItemConstructorOptions[] = [
{
label: this.main.i18nService.t('emailUs'),
click: () => shell.openExternal('mailTo:hello@bitwarden.com'),
},
{
label: this.main.i18nService.t('visitOurWebsite'),
click: () => shell.openExternal('https://bitwarden.com/contact'),
},
{
label: this.main.i18nService.t('fileBugReport'),
click: () => shell.openExternal('https://github.com/bitwarden/desktop/issues'),
},
];
if (isMacAppStore()) {
helpSubmenu.push({
label: this.main.i18nService.t('legal'),
submenu: [
{
label: this.main.i18nService.t('termsOfService'),
click: () => shell.openExternal('https://bitwarden.com/terms/'),
},
{
label: this.main.i18nService.t('privacyPolicy'),
click: () => shell.openExternal('https://bitwarden.com/privacy/'),
},
],
});
}
helpSubmenu = helpSubmenu.concat([
{ type: 'separator' },
{
label: this.main.i18nService.t('followUs'),
submenu: [
{
label: this.main.i18nService.t('blog'),
click: () => shell.openExternal('https://blog.bitwarden.com'),
},
{
label: 'Twitter',
click: () => shell.openExternal('https://twitter.com/bitwarden'),
},
{
label: 'Facebook',
click: () => shell.openExternal('https://www.facebook.com/bitwarden/'),
},
{
label: 'GitHub',
click: () => shell.openExternal('https://github.com/bitwarden'),
},
],
},
{ type: 'separator' },
{
label: this.main.i18nService.t('goToWebVault'),
click: async () => await this.openWebVault(),
},
]);
if (!isWindowsStore()) {
helpSubmenu.push({
label: this.main.i18nService.t('getMobileApp'),
submenu: [
{
label: 'iOS',
click: () => {
shell.openExternal('https://itunes.apple.com/app/' +
'bitwarden-free-password-manager/id1137397744?mt=8');
},
},
{
label: 'Android',
click: () => {
shell.openExternal('https://play.google.com/store/apps/' +
'details?id=com.x8bit.bitwarden');
},
},
],
});
helpSubmenu.push({
label: this.main.i18nService.t('getBrowserExtension'),
submenu: [
{
label: 'Chrome',
click: () => {
shell.openExternal('https://chrome.google.com/webstore/detail/' +
'bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb');
},
},
{
label: 'Firefox',
click: () => {
shell.openExternal('https://addons.mozilla.org/firefox/addon/' +
'bitwarden-password-manager/');
},
},
{
label: 'Opera',
click: () => {
shell.openExternal('https://addons.opera.com/extensions/details/' +
'bitwarden-free-password-manager/');
},
},
{
label: 'Edge',
click: () => {
shell.openExternal('https://microsoftedge.microsoft.com/addons/' +
'detail/jbkfoedolllekgbhcbcoahefnbanhhlh');
},
},
{
label: 'Safari',
click: () => {
shell.openExternal('https://bitwarden.com/download/');
},
},
],
});
}
const template: MenuItemConstructorOptions[] = [
{
label: this.main.i18nService.t('file'),
submenu: [
{
label: this.main.i18nService.t('addNewLogin'),
click: () => this.main.messagingService.send('newLogin'),
accelerator: 'CmdOrCtrl+N',
id: 'addNewLogin',
},
{
label: this.main.i18nService.t('addNewItem'),
id: 'addNewItem',
submenu: [
{
label: this.main.i18nService.t('typeLogin'),
click: () => this.main.messagingService.send('newLogin'),
accelerator: 'CmdOrCtrl+Shift+L',
},
{
label: this.main.i18nService.t('typeCard'),
click: () => this.main.messagingService.send('newCard'),
accelerator: 'CmdOrCtrl+Shift+C',
},
{
label: this.main.i18nService.t('typeIdentity'),
click: () => this.main.messagingService.send('newIdentity'),
accelerator: 'CmdOrCtrl+Shift+I',
},
{
label: this.main.i18nService.t('typeSecureNote'),
click: () => this.main.messagingService.send('newSecureNote'),
accelerator: 'CmdOrCtrl+Shift+S',
},
],
},
{
label: this.main.i18nService.t('addNewFolder'),
id: 'addNewFolder',
click: () => this.main.messagingService.send('newFolder'),
},
{ type: 'separator' },
{
label: this.main.i18nService.t('syncVault'),
id: 'syncVault',
click: () => this.main.messagingService.send('syncVault'),
},
{
label: this.main.i18nService.t('exportVault'),
id: 'exportVault',
click: () => this.main.messagingService.send('exportVault'),
},
],
},
this.editMenuItemOptions,
{
label: this.main.i18nService.t('view'),
submenu: ([
{
label: this.main.i18nService.t('searchVault'),
id: 'searchVault',
click: () => this.main.messagingService.send('focusSearch'),
accelerator: 'CmdOrCtrl+F',
},
{ type: 'separator' },
{
label: this.main.i18nService.t('passwordGenerator'),
id: 'passwordGenerator',
click: () => this.main.messagingService.send('openPasswordGenerator'),
accelerator: 'CmdOrCtrl+G',
},
{
label: this.main.i18nService.t('passwordHistory'),
id: 'passwordHistory',
click: () => this.main.messagingService.send('openPasswordHistory'),
},
{ type: 'separator' },
] as MenuItemConstructorOptions[]).concat(this.viewSubMenuItemOptions),
},
{
label: this.main.i18nService.t('account'),
submenu: accountSubmenu,
},
this.windowMenuItemOptions,
{
label: this.main.i18nService.t('help'),
role: 'help',
submenu: helpSubmenu,
},
];
const firstMenuOptions: MenuItemConstructorOptions[] = [
{ type: 'separator' },
{
label: this.main.i18nService.t(process.platform === 'darwin' ? 'preferences' : 'settings'),
id: 'settings',
click: () => this.main.messagingService.send('openSettings'),
accelerator: 'CmdOrCtrl+,',
},
{
label: this.main.i18nService.t('lockNow'),
id: 'lockNow',
click: () => this.main.messagingService.send('lockVault'),
accelerator: 'CmdOrCtrl+L',
},
];
const updateMenuItem = {
label: this.main.i18nService.t('checkForUpdates'),
click: () => this.main.updaterMain.checkForUpdate(true),
id: 'checkForUpdates',
};
if (process.platform === 'darwin') {
const firstMenuPart: MenuItemConstructorOptions[] = [
{
label: this.main.i18nService.t('aboutBitwarden'),
role: 'about',
},
];
if (!isMacAppStore()) {
firstMenuPart.push(updateMenuItem);
}
template.unshift({
label: 'Bitwarden',
submenu: firstMenuPart.concat(firstMenuOptions, [
{ type: 'separator' },
], this.macAppMenuItemOptions),
});
// Window menu
template[template.length - 2].submenu = this.macWindowSubmenuOptions;
} else {
// File menu
template[0].submenu = (template[0].submenu as MenuItemConstructorOptions[]).concat(
firstMenuOptions, {
label: this.i18nService.t('quitBitwarden'),
role: 'quit',
});
// About menu
const aboutMenuAdditions: MenuItemConstructorOptions[] = [
{ type: 'separator' },
];
if (!isWindowsStore() && !isSnapStore()) {
aboutMenuAdditions.push(updateMenuItem);
}
aboutMenuAdditions.push({
label: this.i18nService.t('aboutBitwarden'),
click: async () => {
const aboutInformation = this.i18nService.t('version', app.getVersion()) +
'\nShell ' + process.versions.electron +
'\nRenderer ' + process.versions.chrome +
'\nNode ' + process.versions.node +
'\nArchitecture ' + process.arch;
const result = await dialog.showMessageBox(this.windowMain.win, {
title: 'Bitwarden',
message: 'Bitwarden',
detail: aboutInformation,
type: 'info',
noLink: true,
buttons: [this.i18nService.t('ok'), this.i18nService.t('copy')],
});
if (result.response === 1) {
clipboard.writeText(aboutInformation);
}
},
});
template[template.length - 1].submenu =
(template[template.length - 1].submenu as MenuItemConstructorOptions[]).concat(aboutMenuAdditions);
}
(template[template.length - 2].submenu as MenuItemConstructorOptions[]).splice(1, 0,
{
label: this.main.i18nService.t(process.platform === 'darwin' ? 'hideToMenuBar' : 'hideToTray'),
click: () => this.main.messagingService.send('hideToTray'),
accelerator: 'CmdOrCtrl+Shift+M',
},
{
type: 'checkbox',
label: this.main.i18nService.t('alwaysOnTop'),
checked: this.windowMain.win.isAlwaysOnTop(),
click: () => this.main.windowMain.toggleAlwaysOnTop(),
accelerator: 'CmdOrCtrl+Shift+T',
});
this.menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(this.menu);
private async setMenu(updateRequest?: MenuUpdateRequest) {
Menu.setApplicationMenu(new Menubar(
this.main.i18nService,
this.main.messagingService,
this.main.updaterMain,
this.windowMain,
await this.getWebVaultUrl(),
app.getVersion(),
updateRequest,
).menu);
}
private async openWebVault() {
let webUrl = 'https://vault.bitwarden.com';
const urlsObj: any = await this.main.storageService.get(ConstantsService.environmentUrlsKey);
private async getWebVaultUrl() {
let webVaultUrl = cloudWebVaultUrl;
const urlsObj: any = await this.main.stateService.getEnvironmentUrls();
if (urlsObj != null) {
if (urlsObj.base != null) {
webUrl = urlsObj.base;
webVaultUrl = urlsObj.base;
} else if (urlsObj.webVault != null) {
webUrl = urlsObj.webVault;
webVaultUrl = urlsObj.webVault;
}
}
shell.openExternal(webUrl);
return webVaultUrl;
}
}

12
src/main/menu.updater.ts Normal file
View File

@@ -0,0 +1,12 @@
export class MenuUpdateRequest {
hideChangeMasterPassword: boolean;
activeUserId: string;
accounts: { [userId: string]: MenuAccount };
}
export class MenuAccount {
isAuthenticated: boolean;
isLocked: boolean;
userId: string;
email: string;
}

140
src/main/menu.view.ts Normal file
View File

@@ -0,0 +1,140 @@
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { IMenubarMenu } from './menubar';
import { MenuItemConstructorOptions } from 'electron';
export class ViewMenu implements IMenubarMenu {
readonly id: 'viewMenu';
get label(): string {
return this.localize('view');
}
get items(): MenuItemConstructorOptions[] {
return [
this.searchVault,
this.separator,
this.passwordGenerator,
this.passwordHistory,
this.separator,
this.zoomIn,
this.zoomOut,
this.resetZoom,
this.separator,
this.toggleFullscreen,
this.separator,
this.reload,
this.toggleDevTools,
];
}
private readonly _i18nService: I18nService;
private readonly _messagingService: MessagingService;
private readonly _isAuthenticated: boolean;
constructor(
i18nService: I18nService,
messagingService: MessagingService,
isAuthenticated: boolean,
)
{
this._i18nService = i18nService;
this._messagingService = messagingService;
this._isAuthenticated = isAuthenticated;
}
private get searchVault(): MenuItemConstructorOptions {
return {
id: 'searchVault',
label: this.localize('searchVault'),
click: () => this.sendMessage('focusSearch'),
accelerator: 'CmdOrCtrl+F',
enabled: this._isAuthenticated,
};
}
private get separator(): MenuItemConstructorOptions {
return { type: 'separator' };
}
private get passwordGenerator(): MenuItemConstructorOptions {
return {
id: 'passwordGenerator',
label: this.localize('passwordGenerator'),
click: () => this.sendMessage('openPasswordGenerator'),
accelerator: 'CmdOrCtrl+G',
enabled: this._isAuthenticated,
};
}
private get passwordHistory(): MenuItemConstructorOptions {
return {
id: 'passwordHistory',
label: this.localize('passwordHistory'),
click: () => this.sendMessage('openPasswordHistory'),
enabled: this._isAuthenticated,
};
}
private get zoomIn(): MenuItemConstructorOptions {
return {
id: 'zoomIn',
label: this.localize('zoomIn'),
role: 'zoomIn',
accelerator: 'CmdOrCtrl+=',
};
}
private get zoomOut(): MenuItemConstructorOptions {
return {
id: 'zoomOut',
label: this.localize('zoomOut'),
role: 'zoomOut',
accelerator: 'CmdOrCtrl+-',
};
}
private get resetZoom(): MenuItemConstructorOptions {
return {
id: 'resetZoom',
label: this.localize('resetZoom'),
role: 'resetZoom',
accelerator: 'CmdOrCtrl+0',
};
}
private get toggleFullscreen(): MenuItemConstructorOptions {
return {
id: 'toggleFullScreen',
label: this.localize('toggleFullScreen'),
role: 'togglefullscreen',
};
}
private get reload(): MenuItemConstructorOptions {
return {
id: 'reload',
label: this.localize('reload'),
role: 'forceReload',
};
}
private get toggleDevTools(): MenuItemConstructorOptions {
return {
id: 'toggleDevTools',
label: this.localize('toggleDevTools'),
role: 'toggleDevTools',
accelerator: 'F12',
};
}
private localize(s: string) {
return this._i18nService.t(s);
}
private sendMessage(message: string) {
this._messagingService.send(message);
}
}

111
src/main/menu.window.ts Normal file
View File

@@ -0,0 +1,111 @@
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { isMacAppStore } from 'jslib-electron/utils';
import { WindowMain } from 'jslib-electron/window.main';
import { IMenubarMenu } from './menubar';
import { MenuItemConstructorOptions } from 'electron';
export class WindowMenu implements IMenubarMenu {
readonly id: string;
get label(): string {
return this.localize('window');
}
get items(): MenuItemConstructorOptions[] {
return [
this.minimize,
this.hideToMenu,
this.alwaysOnTop,
this.zoom,
this.separator,
this.bringAllToFront,
this.close,
];
}
private readonly _i18nService: I18nService;
private readonly _messagingService: MessagingService;
private readonly _window: WindowMain;
constructor(
i18nService: I18nService,
messagingService: MessagingService,
windowMain: WindowMain,
) {
this._i18nService = i18nService;
this._messagingService = messagingService;
this._window = windowMain;
}
private get minimize(): MenuItemConstructorOptions {
return {
id: 'minimize',
label: this.localize('minimize'),
role: 'minimize',
visible: isMacAppStore(),
};
}
private get hideToMenu(): MenuItemConstructorOptions {
return {
id: 'hideToMenu',
label: this.localize(isMacAppStore() ? 'hideToMenuBar' : 'hideToTray'),
click: () => this.sendMessage('hideToTray'),
accelerator: 'CmdOrCtrl+Shift+M',
};
}
private get alwaysOnTop(): MenuItemConstructorOptions {
return {
id: 'alwaysOnTop',
label: this.localize('alwaysOnTop'),
type: 'checkbox',
checked: this._window.win.isAlwaysOnTop(),
click: () => this._window.toggleAlwaysOnTop(),
accelerator: 'CmdOrCtrl+Shift+T',
};
}
private get zoom(): MenuItemConstructorOptions {
return {
id: 'zoom',
label: this.localize('zoom'),
role: 'zoom',
visible: isMacAppStore(),
};
}
private get separator(): MenuItemConstructorOptions {
return { type: 'separator' };
}
private get bringAllToFront(): MenuItemConstructorOptions {
return {
id: 'bringAllToFront',
label: this.localize('bringAllToFront'),
role: 'front',
visible: isMacAppStore(),
};
}
private get close(): MenuItemConstructorOptions {
return {
id: 'close',
label: this.localize('close'),
role: 'close',
visible: isMacAppStore(),
};
}
private localize(s: string) {
return this._i18nService.t(s);
}
private sendMessage(message: string, args?: any) {
this._messagingService.send(message, args);
}
}

105
src/main/menubar.ts Normal file
View File

@@ -0,0 +1,105 @@
import {
Menu,
MenuItemConstructorOptions,
} from 'electron';
import { AboutMenu } from './menu.about';
import { AccountMenu } from './menu.account';
import { BitwardenMenu } from './menu.bitwarden';
import { EditMenu } from './menu.edit';
import { FileMenu } from './menu.file';
import { HelpMenu } from './menu.help';
import { MenuUpdateRequest } from './menu.updater';
import { ViewMenu } from './menu.view';
import { WindowMenu } from './menu.window';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { UpdaterMain } from 'jslib-electron/updater.main';
import { WindowMain } from 'jslib-electron/window.main';
export interface IMenubarMenu {
id: string;
label: string;
visible?: boolean; // Assumes true if null
items: MenuItemConstructorOptions[];
}
export class Menubar {
private readonly items: IMenubarMenu[];
get menu(): Menu {
const template: MenuItemConstructorOptions[] = [];
if (this.items != null) {
this.items.forEach((item: IMenubarMenu) => {
if (item != null) {
template.push({
id: item.id,
label: item.label,
submenu: item.items,
visible: item.visible ?? true,
});
}
});
}
return Menu.buildFromTemplate(template);
}
constructor(
i18nService: I18nService,
messagingService: MessagingService,
updaterMain: UpdaterMain,
windowMain: WindowMain,
webVaultUrl: string,
appVersion: string,
updateRequest?: MenuUpdateRequest,
) {
this.items = [
new BitwardenMenu(
i18nService,
messagingService,
updaterMain,
windowMain.win,
updateRequest?.accounts
),
new FileMenu(
i18nService,
messagingService,
updateRequest?.accounts[updateRequest?.activeUserId]?.isLocked ?? true,
),
new EditMenu(
i18nService,
messagingService,
updateRequest?.accounts[updateRequest?.activeUserId]?.isLocked ?? true,
),
new ViewMenu(
i18nService,
messagingService,
updateRequest?.accounts[updateRequest?.activeUserId]?.isLocked ?? true,
),
new AccountMenu(
i18nService,
messagingService,
webVaultUrl,
windowMain.win,
updateRequest?.accounts[updateRequest?.activeUserId]?.isLocked ?? true,
),
new WindowMenu(
i18nService,
messagingService,
windowMain,
),
new AboutMenu(
i18nService,
appVersion,
windowMain.win,
updaterMain,
),
new HelpMenu(
i18nService,
webVaultUrl,
),
];
}
}

View File

@@ -4,24 +4,24 @@ import * as path from 'path';
import { Main } from '../main';
import { ElectronConstants } from 'jslib-electron/electronConstants';
import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { MenuUpdateRequest } from './menu.updater';
const SyncInterval = 5 * 60 * 1000; // 5 minutes
export class MessagingMain {
private syncTimeout: NodeJS.Timer;
constructor(private main: Main, private storageService: StorageService) { }
constructor(private main: Main, private stateService: StateService) { }
init() {
this.scheduleNextSync();
if (process.platform === 'linux') {
this.storageService.save(ElectronConstants.openAtLogin, fs.existsSync(this.linuxStartupFile()));
this.stateService.setOpenAtLogin(fs.existsSync(this.linuxStartupFile()));
} else {
const loginSettings = app.getLoginItemSettings();
this.storageService.save(ElectronConstants.openAtLogin, loginSettings.openAtLogin);
this.stateService.setOpenAtLogin(loginSettings.openAtLogin);
}
ipcMain.on('messagingService', async (event: any, message: any) => this.onMessage(message));
}
@@ -32,12 +32,11 @@ export class MessagingMain {
this.scheduleNextSync();
break;
case 'updateAppMenu':
this.main.menuMain.updateApplicationMenuState(message.isAuthenticated, message.isLocked,
message.hideChangeMasterPass);
this.updateTrayMenu(message.isAuthenticated, message.isLocked);
this.main.menuMain.updateApplicationMenuState(message.updateRequest);
this.updateTrayMenu(message.updateRequest);
break;
case 'minimizeOnCopy':
this.storageService.get<boolean>(ElectronConstants.minimizeOnCopyToClipboardKey).then(
this.stateService.getMinimizeOnCopyToClipboard().then(
shouldMinimize => {
if (shouldMinimize && this.main.windowMain.win !== null) {
this.main.windowMain.win.minimize();
@@ -93,13 +92,14 @@ export class MessagingMain {
}, SyncInterval);
}
private updateTrayMenu(isAuthenticated: boolean, isLocked: boolean) {
if (this.main.trayMain == null || this.main.trayMain.contextMenu == null) {
private updateTrayMenu(updateRequest: MenuUpdateRequest) {
if (this.main.trayMain == null || this.main.trayMain.contextMenu == null || updateRequest?.activeUserId == null) {
return;
}
const lockNowTrayMenuItem = this.main.trayMain.contextMenu.getMenuItemById('lockNow');
if (lockNowTrayMenuItem != null) {
lockNowTrayMenuItem.enabled = isAuthenticated && !isLocked;
const activeAccount = updateRequest.accounts[updateRequest.activeUserId];
if (lockNowTrayMenuItem != null && activeAccount != null) {
lockNowTrayMenuItem.enabled = activeAccount.isAuthenticated && !activeAccount.isLocked;
}
this.main.trayMain.updateContextMenu();
}

View File

@@ -1,7 +1,5 @@
import { powerMonitor } from 'electron';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { isSnapStore } from 'jslib-electron/utils';
import { Main } from '../main';
@@ -60,8 +58,8 @@ export class PowerMonitorMain {
}
private async getVaultTimeoutOptions(): Promise<[number, string]> {
const timeout = await this.main.storageService.get<number>(ConstantsService.vaultTimeoutKey);
const action = await this.main.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
const timeout = await this.main.stateService.getVaultTimeout();
const action = await this.main.stateService.getVaultTimeoutAction();
return [timeout, action];
}
}