laptop issue
This commit is contained in:
parent
445ac30f9e
commit
4007ca0543
|
@ -235,6 +235,7 @@ export class SettingsComponent implements OnInit {
|
|||
this.noAutoPromptBiometrics = false;
|
||||
}
|
||||
this.stateService.setBiometricLocked(false);
|
||||
console.debug('toggling key');
|
||||
await this.cryptoService.toggleKey();
|
||||
}
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ import { PasswordGeneratorComponent } from './vault/password-generator.component
|
|||
import { ModalRef } from 'jslib-angular/components/modal/modal.ref';
|
||||
|
||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||
import { MenuUpdateRequest } from 'src/main/menu.updater';
|
||||
|
||||
const BroadcasterSubscriptionId = 'AppComponent';
|
||||
const IdleTimeout = 60000 * 10; // 10 minutes
|
||||
|
@ -147,7 +148,7 @@ export class AppComponent implements OnInit {
|
|||
this.router.navigate(['login']);
|
||||
break;
|
||||
case 'logout':
|
||||
this.logOut(!!message.expired, message.userId);
|
||||
await this.logOut(!!message.expired, message.userId);
|
||||
break;
|
||||
case 'lockVault':
|
||||
await this.vaultTimeoutService.lock(true, message.userId);
|
||||
|
@ -342,13 +343,13 @@ export class AppComponent implements OnInit {
|
|||
}
|
||||
|
||||
private async updateAppMenu() {
|
||||
let data: any;
|
||||
let updateRequest: MenuUpdateRequest;
|
||||
const stateAccounts = this.stateService.accounts?.getValue();
|
||||
if (stateAccounts == null || Object.keys(stateAccounts).length < 1) {
|
||||
data = {
|
||||
updateRequest = {
|
||||
accounts: null,
|
||||
activeUserId: null,
|
||||
hideChangeMasterPass: true,
|
||||
hideChangeMasterPassword: true,
|
||||
};
|
||||
} else {
|
||||
const accounts: { [userId: string]: any } = {};
|
||||
|
@ -365,15 +366,14 @@ export class AppComponent implements OnInit {
|
|||
};
|
||||
}
|
||||
}
|
||||
data = {
|
||||
updateRequest = {
|
||||
accounts: accounts,
|
||||
activeUserId: await this.stateService.getUserId(),
|
||||
enableChangeMasterPass: !await this.keyConnectorService.getUsesKeyConnector(),
|
||||
hideChangeMasterPass: await this.keyConnectorService.getUsesKeyConnector(),
|
||||
hideChangeMasterPassword: await this.keyConnectorService.getUsesKeyConnector(),
|
||||
};
|
||||
}
|
||||
|
||||
this.messagingService.send('updateAppMenu', data);
|
||||
this.messagingService.send('updateAppMenu', { updateRequest: updateRequest });
|
||||
}
|
||||
|
||||
private async logOut(expired: boolean, userId?: string) {
|
||||
|
@ -392,7 +392,11 @@ export class AppComponent implements OnInit {
|
|||
this.keyConnectorService.clear(),
|
||||
]);
|
||||
|
||||
await this.stateService.setBiometricLocked(true);
|
||||
await this.stateService.clean({ userId: userId });
|
||||
|
||||
await this.stateService.setBiometricLocked(true, { userId: userId });
|
||||
|
||||
await this.updateAppMenu();
|
||||
|
||||
if (userId === await this.stateService.getUserId()) {
|
||||
this.searchService.clearIndex();
|
||||
|
@ -404,8 +408,6 @@ export class AppComponent implements OnInit {
|
|||
this.router.navigate(['login']);
|
||||
});
|
||||
}
|
||||
|
||||
await this.stateService.clean({ userId: userId });
|
||||
}
|
||||
|
||||
private async recordActivity() {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<a class="account-switcher" (click)="toggle()" cdkOverlayOrigin #trigger="cdkOverlayOrigin">
|
||||
<a class="account-switcher" (click)="toggle()" cdkOverlayOrigin #trigger="cdkOverlayOrigin" [hidden]="!showSwitcher">
|
||||
<app-avatar [data]="activeAccountEmail" size="25" [circle]="true" [fontSize]="14" [dynamic]="true" *ngIf="activeAccountEmail != null"></app-avatar>
|
||||
<span>{{activeAccountEmail}}</span>
|
||||
<i class="fa" aria-hidden="true" [ngClass]="{'fa-chevron-down': !isOpen, 'fa-chevron-up': isOpen}"></i>
|
||||
|
|
|
@ -11,6 +11,7 @@ import { Router } from '@angular/router';
|
|||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
||||
import { AuthenticationStatus } from 'jslib-common/enums/authenticationStatus';
|
||||
|
||||
import { Account } from 'jslib-common/models/domain/account';
|
||||
|
||||
|
@ -34,12 +35,28 @@ export class AccountSwitcherComponent implements OnInit {
|
|||
accounts: { [userId: string]: Account };
|
||||
activeAccountEmail: string;
|
||||
|
||||
get showSwitcher() {
|
||||
return this.accounts != null && Object.keys(this.accounts).length > 0;
|
||||
}
|
||||
|
||||
constructor(private stateService: StateService, private vaultTimeoutService: VaultTimeoutService,
|
||||
private messagingService: MessagingService, private router: Router) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.stateService.accounts.subscribe(async accounts => {
|
||||
this.accounts = accounts;
|
||||
|
||||
for (const userId in this.accounts) {
|
||||
if (userId === await this.stateService.getUserId()) {
|
||||
this.accounts[userId].profile.authenticationStatus = AuthenticationStatus.Active;
|
||||
continue;
|
||||
}
|
||||
|
||||
this.accounts[userId].profile.authenticationStatus = await this.vaultTimeoutService.isLocked(userId) ?
|
||||
AuthenticationStatus.Locked :
|
||||
AuthenticationStatus.Unlocked;
|
||||
}
|
||||
|
||||
this.activeAccountEmail = await this.stateService.getEmail();
|
||||
});
|
||||
}
|
||||
|
|
15
src/main.ts
15
src/main.ts
|
@ -75,18 +75,17 @@ export class Main {
|
|||
storageDefaults['global.vaultTimeout'] = -1;
|
||||
storageDefaults['global.vaultTimeoutAction'] = 'lock';
|
||||
this.storageService = new ElectronStorageService(app.getPath('userData'), storageDefaults);
|
||||
|
||||
// TODO: this state service will have access to on disk storage, but not in memory storage.
|
||||
// If we could get this to work using the stateService singleton that the rest of the app uses we could save
|
||||
// ourselves from some hacks, like having to manually update the app menu vs. the menu subscribing to events.
|
||||
this.stateService = new StateService(this.storageService, null, this.logService);
|
||||
|
||||
this.windowMain = new WindowMain(this.stateService, this.logService, true, undefined, undefined,
|
||||
arg => this.processDeepLink(arg), win => this.trayMain.setupWindowListeners(win));
|
||||
this.messagingMain = new MessagingMain(this, this.stateService);
|
||||
this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain, 'desktop', () => {
|
||||
this.menuMain.updateMenuItem.enabled = false;
|
||||
}, () => {
|
||||
this.menuMain.updateMenuItem.enabled = true;
|
||||
}, () => {
|
||||
this.menuMain.updateMenuItem.label = this.i18nService.t('restartToUpdate');
|
||||
}, 'bitwarden');
|
||||
this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain, 'desktop',
|
||||
null, null, null, 'bitwarden');
|
||||
this.menuMain = new MenuMain(this);
|
||||
this.powerMonitorMain = new PowerMonitorMain(this);
|
||||
this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.stateService);
|
||||
|
@ -98,7 +97,7 @@ export class Main {
|
|||
|
||||
if (process.platform === 'win32') {
|
||||
const BiometricWindowsMain = require('jslib-electron/biometric.windows.main').default;
|
||||
this.biometricMain = new BiometricWindowsMain(this.i18nService, this.windowMain, this.storageService);
|
||||
this.biometricMain = new BiometricWindowsMain(this.i18nService, this.windowMain, this.stateService, this.logService);
|
||||
} else if (process.platform === 'darwin') {
|
||||
const BiometricDarwinMain = require('jslib-electron/biometric.darwin.main').default;
|
||||
this.biometricMain = new BiometricDarwinMain(this.i18nService, this.stateService);
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
import { IMenubarMenu } from "./menubar";
|
||||
|
||||
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";
|
||||
|
||||
export class AboutMenu implements IMenubarMenu {
|
||||
private readonly _i18nService: I18nService;
|
||||
private readonly _updater: UpdaterMain;
|
||||
private readonly _window: BrowserWindow;
|
||||
private readonly _version: string;
|
||||
|
||||
readonly id: string = 'about';
|
||||
|
||||
get visible(): boolean {
|
||||
return !isMac();
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
return this.localize('about');
|
||||
}
|
||||
|
||||
get items(): Array<MenuItemConstructorOptions> {
|
||||
return [
|
||||
this.separator,
|
||||
this.checkForUpdates,
|
||||
this.aboutBitwarden,
|
||||
];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
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";
|
||||
|
||||
import {
|
||||
BrowserWindow,
|
||||
dialog,
|
||||
shell,
|
||||
MenuItemConstructorOptions,
|
||||
} from 'electron';
|
||||
|
||||
export class AccountMenu implements IMenubarMenu {
|
||||
private readonly _i18nService: I18nService;
|
||||
private readonly _messagingService: MessagingService;
|
||||
private readonly _webVaultUrl: string;
|
||||
private readonly _window: BrowserWindow;
|
||||
private readonly _isAuthenticated: boolean;
|
||||
|
||||
readonly id: string = 'accountMenu';
|
||||
|
||||
get label(): string {
|
||||
return this.localize('account');
|
||||
}
|
||||
|
||||
get items(): Array<MenuItemConstructorOptions> {
|
||||
return [
|
||||
this.premiumMembership,
|
||||
this.changeMasterPassword,
|
||||
this.twoStepLogin,
|
||||
this.fingerprintPhrase,
|
||||
];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
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 {
|
||||
dialog,
|
||||
BrowserWindow,
|
||||
MenuItem,
|
||||
MenuItemConstructorOptions,
|
||||
} from 'electron';
|
||||
|
||||
import { MenuAccount } from "./menu.updater";
|
||||
|
||||
// AKA: "FirstMenu" or "MacMenu" - the first menu that shows on all macOs apps
|
||||
export class BitwardenMenu implements IMenubarMenu {
|
||||
private readonly _i18nService: I18nService;
|
||||
private readonly _updater: UpdaterMain;
|
||||
private readonly _messagingService: MessagingService;
|
||||
private readonly _accounts: { [userId: string]: MenuAccount }
|
||||
private readonly _window: BrowserWindow;
|
||||
|
||||
readonly id: string = "bitwarden";
|
||||
readonly label: string = "Bitwarden";
|
||||
get items(): Array<MenuItemConstructorOptions> {
|
||||
console.debug('getting bitwarden menu items', {
|
||||
accounts: this._accounts,
|
||||
lock: this.lock.submenu,
|
||||
logout: this.logOut.submenu,
|
||||
});
|
||||
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,
|
||||
]
|
||||
}
|
||||
|
||||
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(): Array<MenuItemConstructorOptions> {
|
||||
const value: Array<MenuItemConstructorOptions> = [];
|
||||
for(let 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(): Array<MenuItemConstructorOptions> {
|
||||
const value: Array<MenuItemConstructorOptions> = [];
|
||||
for(let 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);
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
private readonly _i18nService: I18nService;
|
||||
private readonly _messagingService: MessagingService;
|
||||
private readonly _isAuthenticated: boolean;
|
||||
|
||||
readonly id: string = 'editMenu';
|
||||
|
||||
get label(): string {
|
||||
return this.localize('edit');
|
||||
}
|
||||
|
||||
get items(): Array<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,
|
||||
];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
private readonly _i18nService: I18nService;
|
||||
private readonly _messagingService: MessagingService;
|
||||
private readonly _isAuthenticated: boolean;
|
||||
|
||||
readonly id: string = 'fileMenu';
|
||||
|
||||
get label(): string {
|
||||
return this.localize('file');
|
||||
}
|
||||
|
||||
get items(): Array<MenuItemConstructorOptions> {
|
||||
return [
|
||||
this.addNewLogin,
|
||||
this.addNewItem,
|
||||
this.addNewFolder,
|
||||
this.separator,
|
||||
this.syncVault,
|
||||
this.exportVault,
|
||||
this.quitBitwarden,
|
||||
];
|
||||
}
|
||||
|
||||
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(): Array<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);
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
private readonly _i18nService: I18nService;
|
||||
private readonly _webVaultUrl: string;
|
||||
|
||||
readonly id: string = 'help';
|
||||
|
||||
get label(): string {
|
||||
return this.localize('help');
|
||||
}
|
||||
|
||||
get items(): Array<MenuItemConstructorOptions> {
|
||||
return [
|
||||
this.emailUs,
|
||||
this.visitOurWebsite,
|
||||
this.fileBugReport,
|
||||
this.legal,
|
||||
this.separator,
|
||||
this.followUs,
|
||||
this.separator,
|
||||
this.goToWebVault,
|
||||
this.separator,
|
||||
this.getMobileApp,
|
||||
this.getBrowserExtension,
|
||||
];
|
||||
}
|
||||
|
||||
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(): Array<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(): Array<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(): Array<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(): Array<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);
|
||||
}
|
||||
}
|
|
@ -1,569 +1,54 @@
|
|||
import {
|
||||
app,
|
||||
clipboard,
|
||||
dialog,
|
||||
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';
|
||||
|
||||
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.twoStepLogin, this.fingerprintPhrase,
|
||||
this.changeMasterPass, this.premiumMembership, this.passwordGenerator, this.passwordHistory,
|
||||
this.searchVault, this.copyUsername, this.copyPassword];
|
||||
this.updateApplicationMenuState(true);
|
||||
await this.setMenu();
|
||||
}
|
||||
|
||||
updateApplicationMenuState(hideChangeMasterPass: boolean, accounts?: { [userId: string]: { isAuthenticated: boolean, isLocked: boolean, userId: string, email: string }}, activeUserId?: string) {
|
||||
this.updateAuthBasedMenuState(accounts, activeUserId);
|
||||
if (hideChangeMasterPass) {
|
||||
this.changeMasterPass.visible = !(hideChangeMasterPass ?? false);
|
||||
}
|
||||
if (this.menu != null) {
|
||||
Menu.setApplicationMenu(this.menu);
|
||||
}
|
||||
async updateApplicationMenuState(updateRequest: MenuUpdateRequest) {
|
||||
await this.setMenu(updateRequest);
|
||||
}
|
||||
|
||||
private updateAuthBasedMenuState(accounts?: {[userId: string]: { isAuthenticated: boolean, isLocked: boolean, userId: string, email: string}}, activeUserId?: string) {
|
||||
accounts == null ?
|
||||
this.lockAuthBasedMenuItems() :
|
||||
this.tryUnlockAuthBasedMenuItems(accounts, activeUserId);
|
||||
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 lockAuthBasedMenuItems() {
|
||||
this.logOut.enabled = false;
|
||||
this.lockNow.enabled = false;
|
||||
this.unlockedRequiredMenuItems.forEach((mi: MenuItem) => {
|
||||
if (mi != null) {
|
||||
mi.enabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private tryUnlockAuthBasedMenuItems(accounts: { [userId: string]: { isAuthenticated: boolean, isLocked: boolean, userId: string, email: string} }, activeUserId: string) {
|
||||
this.tryUnlockActiveAccountAuthBasedMenuItems(accounts[activeUserId]);
|
||||
|
||||
this.lockNow.enabled = true;
|
||||
this.logOut.enabled = true;
|
||||
for (const userId in accounts) {
|
||||
if (userId != null) {
|
||||
if (this.lockNow.submenu.getMenuItemById(`lockNow_${accounts[userId].userId}`) == null) {
|
||||
const options: MenuItemConstructorOptions = {
|
||||
label: accounts[userId].email,
|
||||
id: `lockNow_${accounts[userId].userId}`,
|
||||
click: () => this.main.messagingService.send('lockVault', { userId: accounts[userId].userId }),
|
||||
};
|
||||
this.lockNow.submenu.insert(0, new MenuItem(options));
|
||||
}
|
||||
if (this.logOut.submenu.getMenuItemById(`logOut_${accounts[userId].userId}`) == null) {
|
||||
const options: MenuItemConstructorOptions = {
|
||||
label: accounts[userId].email,
|
||||
id: `logOut_${accounts[userId].userId}`,
|
||||
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', { userId: accounts[userId].userId });
|
||||
}
|
||||
},
|
||||
};
|
||||
this.logOut.submenu.insert(0, new MenuItem(options));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private tryUnlockActiveAccountAuthBasedMenuItems(activeAccount: { isAuthenticated: boolean, isLocked: boolean, userId: string, email: string}) {
|
||||
this.logOut.enabled = activeAccount.isAuthenticated;
|
||||
this.unlockedRequiredMenuItems.forEach((mi: MenuItem) => {
|
||||
if (mi != null) {
|
||||
mi.enabled = activeAccount.isAuthenticated && !activeAccount.isLocked;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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'),
|
||||
},
|
||||
];
|
||||
|
||||
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('lockVault'),
|
||||
id: 'lockNow',
|
||||
submenu: [
|
||||
// List of vaults
|
||||
],
|
||||
},
|
||||
{
|
||||
label: this.main.i18nService.t('lockAllVaults'),
|
||||
click: () => this.main.messagingService.send('lockAllVaults'),
|
||||
id: 'lockNow',
|
||||
accelerator: 'CmdOrCtrl+L',
|
||||
},
|
||||
{
|
||||
label: this.main.i18nService.t('logOut'),
|
||||
id: 'logOut',
|
||||
submenu: [
|
||||
// List of vaults
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
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 openWebVault() {
|
||||
let webUrl = 'https://vault.bitwarden.com';
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 {
|
||||
private readonly _i18nService: I18nService;
|
||||
private readonly _messagingService: MessagingService;
|
||||
private readonly _isAuthenticated: boolean;
|
||||
|
||||
readonly id: 'viewMenu';
|
||||
|
||||
get label(): string {
|
||||
return this.localize('view');
|
||||
}
|
||||
|
||||
get items(): Array<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,
|
||||
];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
private readonly _i18nService: I18nService;
|
||||
private readonly _messagingService: MessagingService;
|
||||
private readonly _window: WindowMain;
|
||||
|
||||
readonly id: string;
|
||||
|
||||
get label(): string {
|
||||
return this.localize('window');
|
||||
}
|
||||
|
||||
get items(): Array<MenuItemConstructorOptions> {
|
||||
return [
|
||||
this.minimize,
|
||||
this.hideToMenu,
|
||||
this.alwaysOnTop,
|
||||
this.zoom,
|
||||
this.separator,
|
||||
this.bringAllToFront,
|
||||
this.close,
|
||||
];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
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 { ViewMenu } from './menu.view';
|
||||
import { WindowMenu } from './menu.window';
|
||||
|
||||
import {
|
||||
Menu,
|
||||
MenuItemConstructorOptions,
|
||||
} from 'electron';
|
||||
import { MenuUpdateRequest } from './menu.updater';
|
||||
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: Array<MenuItemConstructorOptions>;
|
||||
}
|
||||
|
||||
export class Menubar {
|
||||
private readonly items: Array<IMenubarMenu>;
|
||||
|
||||
get menu(): Menu {
|
||||
const template: Array<MenuItemConstructorOptions> = [];
|
||||
this.items.forEach((item: IMenubarMenu) => {
|
||||
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,
|
||||
),
|
||||
new EditMenu(
|
||||
i18nService,
|
||||
messagingService,
|
||||
updateRequest?.accounts[updateRequest?.activeUserId].isLocked,
|
||||
),
|
||||
new ViewMenu(
|
||||
i18nService,
|
||||
messagingService,
|
||||
updateRequest?.accounts[updateRequest?.activeUserId].isLocked,
|
||||
),
|
||||
new AccountMenu(
|
||||
i18nService,
|
||||
messagingService,
|
||||
webVaultUrl,
|
||||
windowMain.win,
|
||||
updateRequest?.accounts[updateRequest?.activeUserId].isLocked,
|
||||
),
|
||||
new WindowMenu(
|
||||
i18nService,
|
||||
messagingService,
|
||||
windowMain,
|
||||
),
|
||||
new AboutMenu(
|
||||
i18nService,
|
||||
appVersion,
|
||||
windowMain.win,
|
||||
updaterMain,
|
||||
),
|
||||
new HelpMenu(
|
||||
i18nService,
|
||||
webVaultUrl,
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import * as path from 'path';
|
|||
import { Main } from '../main';
|
||||
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { MenuUpdateRequest } from './menu.updater';
|
||||
|
||||
const SyncInterval = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
|
@ -30,8 +31,8 @@ export class MessagingMain {
|
|||
this.scheduleNextSync();
|
||||
break;
|
||||
case 'updateAppMenu':
|
||||
this.main.menuMain.updateApplicationMenuState(message.hideChangeMasterPass, message.accounts, message.activeUserId);
|
||||
this.updateTrayMenu(message.accounts ? message.accounts[message.activeUserId] : null);
|
||||
this.main.menuMain.updateApplicationMenuState(message.updateRequest);
|
||||
this.updateTrayMenu(message.updateRequest);
|
||||
break;
|
||||
case 'minimizeOnCopy':
|
||||
this.stateService.getMinimizeOnCopyToClipboard().then(
|
||||
|
@ -90,11 +91,12 @@ export class MessagingMain {
|
|||
}, SyncInterval);
|
||||
}
|
||||
|
||||
private updateTrayMenu(activeAccount: { 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');
|
||||
const activeAccount = updateRequest.accounts[updateRequest.activeUserId];
|
||||
if (lockNowTrayMenuItem != null && activeAccount != null) {
|
||||
lockNowTrayMenuItem.enabled = activeAccount.isAuthenticated && !activeAccount.isLocked;
|
||||
}
|
||||
|
|
|
@ -152,10 +152,6 @@
|
|||
font-style: italic;
|
||||
grid-area: status;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #ececec;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue