mirror of
https://github.com/bitwarden/browser
synced 2026-01-04 09:33:27 +00:00
Move desktop into apps/desktop
This commit is contained in:
36
apps/desktop/src/main/biometric/biometric.darwin.main.ts
Normal file
36
apps/desktop/src/main/biometric/biometric.darwin.main.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { ipcMain, systemPreferences } from "electron";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
import { BiometricMain } from "../biometric/biometric.main";
|
||||
|
||||
export default class BiometricDarwinMain implements BiometricMain {
|
||||
isError = false;
|
||||
|
||||
constructor(private i18nservice: I18nService, private stateService: StateService) {}
|
||||
|
||||
async init() {
|
||||
await this.stateService.setEnableBiometric(await this.supportsBiometric());
|
||||
await this.stateService.setBiometricText("unlockWithTouchId");
|
||||
await this.stateService.setNoAutoPromptBiometricsText("noAutoPromptTouchId");
|
||||
|
||||
// eslint-disable-next-line
|
||||
ipcMain.on("biometric", async (event: any, message: any) => {
|
||||
event.returnValue = await this.authenticateBiometric();
|
||||
});
|
||||
}
|
||||
|
||||
supportsBiometric(): Promise<boolean> {
|
||||
return Promise.resolve(systemPreferences.canPromptTouchID());
|
||||
}
|
||||
|
||||
async authenticateBiometric(): Promise<boolean> {
|
||||
try {
|
||||
await systemPreferences.promptTouchID(this.i18nservice.t("touchIdConsentMessage"));
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
6
apps/desktop/src/main/biometric/biometric.main.ts
Normal file
6
apps/desktop/src/main/biometric/biometric.main.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export abstract class BiometricMain {
|
||||
isError: boolean;
|
||||
init: () => Promise<void>;
|
||||
supportsBiometric: () => Promise<boolean>;
|
||||
authenticateBiometric: () => Promise<boolean>;
|
||||
}
|
||||
146
apps/desktop/src/main/biometric/biometric.windows.main.ts
Normal file
146
apps/desktop/src/main/biometric/biometric.windows.main.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { ipcMain } from "electron";
|
||||
import forceFocus from "forcefocus";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { WindowMain } from "jslib-electron/window.main";
|
||||
|
||||
import { BiometricMain } from "src/main/biometric/biometric.main";
|
||||
|
||||
export default class BiometricWindowsMain implements BiometricMain {
|
||||
isError = false;
|
||||
|
||||
private windowsSecurityCredentialsUiModule: any;
|
||||
|
||||
constructor(
|
||||
private i18nservice: I18nService,
|
||||
private windowMain: WindowMain,
|
||||
private stateService: StateService,
|
||||
private logService: LogService
|
||||
) {}
|
||||
|
||||
async init() {
|
||||
this.windowsSecurityCredentialsUiModule = this.getWindowsSecurityCredentialsUiModule();
|
||||
let supportsBiometric = false;
|
||||
try {
|
||||
supportsBiometric = await this.supportsBiometric();
|
||||
} catch {
|
||||
// store error state so we can let the user know on the settings page
|
||||
this.isError = true;
|
||||
}
|
||||
await this.stateService.setEnableBiometric(supportsBiometric);
|
||||
await this.stateService.setBiometricText("unlockWithWindowsHello");
|
||||
await this.stateService.setNoAutoPromptBiometricsText("noAutoPromptWindowsHello");
|
||||
|
||||
ipcMain.on("biometric", async (event: any, message: any) => {
|
||||
event.returnValue = await this.authenticateBiometric();
|
||||
});
|
||||
}
|
||||
|
||||
async supportsBiometric(): Promise<boolean> {
|
||||
const availability = await this.checkAvailabilityAsync();
|
||||
|
||||
return this.getAllowedAvailabilities().includes(availability);
|
||||
}
|
||||
|
||||
async authenticateBiometric(): Promise<boolean> {
|
||||
const module = this.getWindowsSecurityCredentialsUiModule();
|
||||
if (module == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const verification = await this.requestVerificationAsync(
|
||||
this.i18nservice.t("windowsHelloConsentMessage")
|
||||
);
|
||||
|
||||
return verification === module.UserConsentVerificationResult.verified;
|
||||
}
|
||||
|
||||
getWindowsSecurityCredentialsUiModule(): any {
|
||||
try {
|
||||
if (this.windowsSecurityCredentialsUiModule == null && this.getWindowsMajorVersion() >= 10) {
|
||||
this.windowsSecurityCredentialsUiModule = require("@nodert-win10-rs4/windows.security.credentials.ui");
|
||||
}
|
||||
return this.windowsSecurityCredentialsUiModule;
|
||||
} catch {
|
||||
this.isError = true;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async checkAvailabilityAsync(): Promise<any> {
|
||||
const module = this.getWindowsSecurityCredentialsUiModule();
|
||||
if (module != null) {
|
||||
// eslint-disable-next-line
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
module.UserConsentVerifier.checkAvailabilityAsync((error: Error, result: any) => {
|
||||
if (error) {
|
||||
return resolve(null);
|
||||
}
|
||||
return resolve(result);
|
||||
});
|
||||
} catch {
|
||||
this.isError = true;
|
||||
return resolve(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
async requestVerificationAsync(message: string): Promise<any> {
|
||||
const module = this.getWindowsSecurityCredentialsUiModule();
|
||||
if (module != null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
module.UserConsentVerifier.requestVerificationAsync(
|
||||
message,
|
||||
(error: Error, result: any) => {
|
||||
if (error) {
|
||||
return resolve(null);
|
||||
}
|
||||
return resolve(result);
|
||||
}
|
||||
);
|
||||
|
||||
forceFocus.focusWindow(this.windowMain.win);
|
||||
} catch (error) {
|
||||
this.isError = true;
|
||||
return reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
getAllowedAvailabilities(): any[] {
|
||||
try {
|
||||
const module = this.getWindowsSecurityCredentialsUiModule();
|
||||
if (module != null) {
|
||||
return [
|
||||
module.UserConsentVerifierAvailability.available,
|
||||
module.UserConsentVerifierAvailability.deviceBusy,
|
||||
];
|
||||
}
|
||||
} catch {
|
||||
/*Ignore error*/
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
getWindowsMajorVersion(): number {
|
||||
if (process.platform !== "win32") {
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
// eslint-disable-next-line
|
||||
const version = require("os").release();
|
||||
return Number.parseInt(version.split(".")[0], 10);
|
||||
} catch {
|
||||
this.logService.error("Unable to resolve windows major version number");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
51
apps/desktop/src/main/desktopCredentialStorageListener.ts
Normal file
51
apps/desktop/src/main/desktopCredentialStorageListener.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { ipcMain } from "electron";
|
||||
import { deletePassword, getPassword, setPassword } from "keytar";
|
||||
|
||||
import { BiometricMain } from "./biometric/biometric.main";
|
||||
|
||||
const AuthRequiredSuffix = "_biometric";
|
||||
const AuthenticatedActions = ["getPassword"];
|
||||
|
||||
export class DesktopCredentialStorageListener {
|
||||
constructor(private serviceName: string, private biometricService: BiometricMain) {}
|
||||
|
||||
init() {
|
||||
ipcMain.on("keytar", async (event: any, message: any) => {
|
||||
try {
|
||||
let serviceName = this.serviceName;
|
||||
message.keySuffix = "_" + (message.keySuffix ?? "");
|
||||
if (message.keySuffix !== "_") {
|
||||
serviceName += message.keySuffix;
|
||||
}
|
||||
|
||||
const authenticationRequired =
|
||||
AuthenticatedActions.includes(message.action) && AuthRequiredSuffix === message.keySuffix;
|
||||
const authenticated = !authenticationRequired || (await this.authenticateBiometric());
|
||||
|
||||
let val: string | boolean = null;
|
||||
if (authenticated && message.action && message.key) {
|
||||
if (message.action === "getPassword") {
|
||||
val = await getPassword(serviceName, message.key);
|
||||
} else if (message.action === "hasPassword") {
|
||||
const result = await getPassword(serviceName, message.key);
|
||||
val = result != null;
|
||||
} else if (message.action === "setPassword" && message.value) {
|
||||
await setPassword(serviceName, message.key, message.value);
|
||||
} else if (message.action === "deletePassword") {
|
||||
await deletePassword(serviceName, message.key);
|
||||
}
|
||||
}
|
||||
event.returnValue = val;
|
||||
} catch {
|
||||
event.returnValue = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async authenticateBiometric(): Promise<boolean> {
|
||||
if (this.biometricService) {
|
||||
return await this.biometricService.authenticateBiometric();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
87
apps/desktop/src/main/menu/menu.about.ts
Normal file
87
apps/desktop/src/main/menu/menu.about.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { BrowserWindow, clipboard, dialog, MenuItemConstructorOptions } from "electron";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { UpdaterMain } from "jslib-electron/updater.main";
|
||||
import { isMacAppStore, isSnapStore, isWindowsStore } from "jslib-electron/utils";
|
||||
|
||||
import { IMenubarMenu } from "./menubar";
|
||||
|
||||
export class AboutMenu implements IMenubarMenu {
|
||||
readonly id: string = "about";
|
||||
|
||||
get label(): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
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() && !isMacAppStore(),
|
||||
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);
|
||||
}
|
||||
}
|
||||
115
apps/desktop/src/main/menu/menu.account.ts
Normal file
115
apps/desktop/src/main/menu/menu.account.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
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 _isLocked: boolean;
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
messagingService: MessagingService,
|
||||
webVaultUrl: string,
|
||||
window: BrowserWindow,
|
||||
isLocked: boolean
|
||||
) {
|
||||
this._i18nService = i18nService;
|
||||
this._messagingService = messagingService;
|
||||
this._webVaultUrl = webVaultUrl;
|
||||
this._window = window;
|
||||
this._isLocked = isLocked;
|
||||
}
|
||||
|
||||
private get premiumMembership(): MenuItemConstructorOptions {
|
||||
return {
|
||||
label: this.localize("premiumMembership"),
|
||||
click: () => this.sendMessage("openPremium"),
|
||||
id: "premiumMembership",
|
||||
visible: !isWindowsStore() && !isMacAppStore(),
|
||||
enabled: !this._isLocked,
|
||||
};
|
||||
}
|
||||
|
||||
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._isLocked,
|
||||
};
|
||||
}
|
||||
|
||||
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._isLocked,
|
||||
};
|
||||
}
|
||||
|
||||
private get fingerprintPhrase(): MenuItemConstructorOptions {
|
||||
return {
|
||||
label: this.localize("fingerprintPhrase"),
|
||||
id: "fingerprintPhrase",
|
||||
click: () => this.sendMessage("showFingerprintPhrase"),
|
||||
enabled: !this._isLocked,
|
||||
};
|
||||
}
|
||||
|
||||
private localize(s: string) {
|
||||
return this._i18nService.t(s);
|
||||
}
|
||||
|
||||
private sendMessage(message: string, args?: any) {
|
||||
this._messagingService.send(message, args);
|
||||
}
|
||||
}
|
||||
113
apps/desktop/src/main/menu/menu.bitwarden.ts
Normal file
113
apps/desktop/src/main/menu/menu.bitwarden.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { BrowserWindow, 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 { isMac } from "jslib-electron/utils";
|
||||
|
||||
import { FirstMenu } from "./menu.first";
|
||||
import { MenuAccount } from "./menu.updater";
|
||||
import { IMenubarMenu } from "./menubar";
|
||||
|
||||
// AKA: "FirstMenu" or "MacMenu" - the first menu that shows on all macOs apps
|
||||
export class BitwardenMenu extends FirstMenu implements IMenubarMenu {
|
||||
readonly id: string = "bitwarden";
|
||||
readonly label: string = "Bitwarden";
|
||||
|
||||
get items(): MenuItemConstructorOptions[] {
|
||||
const items = [this.aboutBitwarden, this.checkForUpdates];
|
||||
if (this.aboutBitwarden.visible === true || this.checkForUpdates.visible === true) {
|
||||
items.push(this.separator);
|
||||
}
|
||||
items.push(this.settings);
|
||||
items.push(this.lock);
|
||||
items.push(this.lockAll);
|
||||
items.push(this.logOut);
|
||||
items.push(this.separator);
|
||||
items.push(this.services);
|
||||
|
||||
if (
|
||||
this.hideBitwarden.visible === true ||
|
||||
this.hideOthers.visible === true ||
|
||||
this.showAll.visible === true
|
||||
) {
|
||||
items.push(this.separator);
|
||||
}
|
||||
|
||||
items.push(this.hideBitwarden);
|
||||
items.push(this.hideOthers);
|
||||
items.push(this.showAll);
|
||||
|
||||
if (this.quitBitwarden.visible === true) {
|
||||
items.push(this.separator);
|
||||
}
|
||||
items.push(this.quitBitwarden);
|
||||
return items;
|
||||
}
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
messagingService: MessagingService,
|
||||
updater: UpdaterMain,
|
||||
window: BrowserWindow,
|
||||
accounts: { [userId: string]: MenuAccount },
|
||||
isLocked: boolean
|
||||
) {
|
||||
super(i18nService, messagingService, updater, window, accounts, isLocked);
|
||||
}
|
||||
|
||||
private get aboutBitwarden(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "aboutBitwarden",
|
||||
label: this.localize("aboutBitwarden"),
|
||||
role: "about",
|
||||
visible: isMac(),
|
||||
};
|
||||
}
|
||||
|
||||
private get services(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "services",
|
||||
label: this.localize("services"),
|
||||
role: "services",
|
||||
submenu: [],
|
||||
visible: isMac(),
|
||||
};
|
||||
}
|
||||
|
||||
private get hideBitwarden(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "hideBitwarden",
|
||||
label: this.localize("hideBitwarden"),
|
||||
role: "hide",
|
||||
visible: isMac(),
|
||||
};
|
||||
}
|
||||
|
||||
private get hideOthers(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "hideOthers",
|
||||
label: this.localize("hideOthers"),
|
||||
role: "hideOthers",
|
||||
visible: isMac(),
|
||||
};
|
||||
}
|
||||
|
||||
private get showAll(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "showAll",
|
||||
label: this.localize("showAll"),
|
||||
role: "unhide",
|
||||
visible: isMac(),
|
||||
};
|
||||
}
|
||||
|
||||
private get quitBitwarden(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "quitBitwarden",
|
||||
label: this.localize("quitBitwarden"),
|
||||
role: "quit",
|
||||
visible: isMac(),
|
||||
};
|
||||
}
|
||||
}
|
||||
131
apps/desktop/src/main/menu/menu.edit.ts
Normal file
131
apps/desktop/src/main/menu/menu.edit.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { MenuItemConstructorOptions } from "electron";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
|
||||
import { IMenubarMenu } from "./menubar";
|
||||
|
||||
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 _isLocked: boolean;
|
||||
|
||||
constructor(i18nService: I18nService, messagingService: MessagingService, isLocked: boolean) {
|
||||
this._i18nService = i18nService;
|
||||
this._messagingService = messagingService;
|
||||
this._isLocked = isLocked;
|
||||
}
|
||||
|
||||
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._isLocked,
|
||||
};
|
||||
}
|
||||
|
||||
private get copyPassword(): MenuItemConstructorOptions {
|
||||
return {
|
||||
label: this.localize("copyPassword"),
|
||||
id: "copyPassword",
|
||||
click: () => this.sendMessage("copyPassword"),
|
||||
accelerator: "CmdOrCtrl+P",
|
||||
enabled: !this._isLocked,
|
||||
};
|
||||
}
|
||||
|
||||
private get copyVerificationCodeTotp(): MenuItemConstructorOptions {
|
||||
return {
|
||||
label: this.localize("copyVerificationCodeTotp"),
|
||||
id: "copyTotp",
|
||||
click: () => this.sendMessage("copyTotp"),
|
||||
accelerator: "CmdOrCtrl+T",
|
||||
enabled: !this._isLocked,
|
||||
};
|
||||
}
|
||||
|
||||
private localize(s: string) {
|
||||
return this._i18nService.t(s);
|
||||
}
|
||||
|
||||
private sendMessage(message: string) {
|
||||
this._messagingService.send(message);
|
||||
}
|
||||
}
|
||||
141
apps/desktop/src/main/menu/menu.file.ts
Normal file
141
apps/desktop/src/main/menu/menu.file.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { BrowserWindow, 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 { isMac, isMacAppStore } from "jslib-electron/utils";
|
||||
|
||||
import { FirstMenu } from "./menu.first";
|
||||
import { MenuAccount } from "./menu.updater";
|
||||
import { IMenubarMenu } from "./menubar";
|
||||
|
||||
export class FileMenu extends FirstMenu implements IMenubarMenu {
|
||||
readonly id: string = "fileMenu";
|
||||
|
||||
get label(): string {
|
||||
return this.localize("file");
|
||||
}
|
||||
|
||||
get items(): MenuItemConstructorOptions[] {
|
||||
let items = [
|
||||
this.addNewLogin,
|
||||
this.addNewItem,
|
||||
this.addNewFolder,
|
||||
this.separator,
|
||||
this.syncVault,
|
||||
this.exportVault,
|
||||
];
|
||||
|
||||
if (!isMac()) {
|
||||
items = [
|
||||
...items,
|
||||
...[
|
||||
this.separator,
|
||||
this.settings,
|
||||
this.lock,
|
||||
this.lockAll,
|
||||
this.logOut,
|
||||
this.separator,
|
||||
this.quitBitwarden,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
messagingService: MessagingService,
|
||||
updater: UpdaterMain,
|
||||
window: BrowserWindow,
|
||||
accounts: { [userId: string]: MenuAccount },
|
||||
isLocked: boolean
|
||||
) {
|
||||
super(i18nService, messagingService, updater, window, accounts, isLocked);
|
||||
}
|
||||
|
||||
private get addNewLogin(): MenuItemConstructorOptions {
|
||||
return {
|
||||
label: this.localize("addNewLogin"),
|
||||
click: () => this.sendMessage("newLogin"),
|
||||
accelerator: "CmdOrCtrl+N",
|
||||
id: "addNewLogin",
|
||||
enabled: !this._isLocked,
|
||||
};
|
||||
}
|
||||
|
||||
private get addNewItem(): MenuItemConstructorOptions {
|
||||
return {
|
||||
label: this.localize("addNewItem"),
|
||||
id: "addNewItem",
|
||||
submenu: this.addNewItemSubmenu,
|
||||
enabled: !this._isLocked,
|
||||
};
|
||||
}
|
||||
|
||||
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"),
|
||||
enabled: !this._isLocked,
|
||||
};
|
||||
}
|
||||
|
||||
private get syncVault(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "syncVault",
|
||||
label: this.localize("syncVault"),
|
||||
click: () => this.sendMessage("syncVault"),
|
||||
enabled: !this._isLocked,
|
||||
};
|
||||
}
|
||||
|
||||
private get exportVault(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "exportVault",
|
||||
label: this.localize("exportVault"),
|
||||
click: () => this.sendMessage("exportVault"),
|
||||
enabled: !this._isLocked,
|
||||
};
|
||||
}
|
||||
|
||||
private get quitBitwarden(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "quitBitwarden",
|
||||
label: this.localize("quitBitwarden"),
|
||||
visible: !isMacAppStore(),
|
||||
role: "quit",
|
||||
};
|
||||
}
|
||||
}
|
||||
152
apps/desktop/src/main/menu/menu.first.ts
Normal file
152
apps/desktop/src/main/menu/menu.first.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
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 { MenuAccount } from "./menu.updater";
|
||||
|
||||
export class FirstMenu {
|
||||
protected readonly _i18nService: I18nService;
|
||||
protected readonly _updater: UpdaterMain;
|
||||
protected readonly _messagingService: MessagingService;
|
||||
protected readonly _accounts: { [userId: string]: MenuAccount };
|
||||
protected readonly _window: BrowserWindow;
|
||||
protected readonly _isLocked: boolean;
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
messagingService: MessagingService,
|
||||
updater: UpdaterMain,
|
||||
window: BrowserWindow,
|
||||
accounts: { [userId: string]: MenuAccount },
|
||||
isLocked: boolean
|
||||
) {
|
||||
this._i18nService = i18nService;
|
||||
this._updater = updater;
|
||||
this._messagingService = messagingService;
|
||||
this._window = window;
|
||||
this._accounts = accounts;
|
||||
this._isLocked = isLocked;
|
||||
}
|
||||
|
||||
protected get hasAccounts(): boolean {
|
||||
return this._accounts != null && Object.keys(this._accounts).length > 0;
|
||||
}
|
||||
|
||||
protected get checkForUpdates(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "checkForUpdates",
|
||||
label: this.localize("checkForUpdates"),
|
||||
click: (menuItem) => this.checkForUpdate(menuItem),
|
||||
visible: !isMacAppStore() && !isWindowsStore() && !isSnapStore(),
|
||||
};
|
||||
}
|
||||
|
||||
protected get separator(): MenuItemConstructorOptions {
|
||||
return {
|
||||
type: "separator",
|
||||
};
|
||||
}
|
||||
|
||||
protected get settings(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "settings",
|
||||
label: this.localize(process.platform === "darwin" ? "preferences" : "settings"),
|
||||
click: () => this.sendMessage("openSettings"),
|
||||
accelerator: "CmdOrCtrl+,",
|
||||
enabled: !this._isLocked,
|
||||
};
|
||||
}
|
||||
|
||||
protected get lock(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "lock",
|
||||
label: this.localize("lockVault"),
|
||||
submenu: this.lockSubmenu,
|
||||
enabled: this.hasAccounts,
|
||||
};
|
||||
}
|
||||
|
||||
protected 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;
|
||||
}
|
||||
|
||||
protected get lockAll(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "lockAllNow",
|
||||
label: this.localize("lockAllVaults"),
|
||||
click: () => this.sendMessage("lockAllVaults"),
|
||||
accelerator: "CmdOrCtrl+L",
|
||||
enabled: this.hasAccounts,
|
||||
};
|
||||
}
|
||||
|
||||
protected get logOut(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "logOut",
|
||||
label: this.localize("logOut"),
|
||||
submenu: this.logOutSubmenu,
|
||||
enabled: this.hasAccounts,
|
||||
};
|
||||
}
|
||||
|
||||
protected 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;
|
||||
}
|
||||
|
||||
protected localize(s: string) {
|
||||
return this._i18nService.t(s);
|
||||
}
|
||||
|
||||
protected async checkForUpdate(menuItem: MenuItem) {
|
||||
menuItem.enabled = false;
|
||||
this._updater.checkForUpdate(true);
|
||||
menuItem.enabled = true;
|
||||
}
|
||||
|
||||
protected sendMessage(message: string, args?: any) {
|
||||
this._messagingService.send(message, args);
|
||||
}
|
||||
}
|
||||
234
apps/desktop/src/main/menu/menu.help.ts
Normal file
234
apps/desktop/src/main/menu/menu.help.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
import { shell, MenuItemConstructorOptions } from "electron";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { isMacAppStore, isWindowsStore } from "jslib-electron/utils";
|
||||
|
||||
import { AboutMenu } from "./menu.about";
|
||||
import { IMenubarMenu } from "./menubar";
|
||||
|
||||
export class HelpMenu implements IMenubarMenu {
|
||||
readonly id: string = "help";
|
||||
|
||||
get label(): string {
|
||||
return this.localize("help");
|
||||
}
|
||||
|
||||
get items(): MenuItemConstructorOptions[] {
|
||||
const items = [
|
||||
this.getHelp,
|
||||
this.contactUs,
|
||||
this.fileBugReport,
|
||||
this.legal,
|
||||
this.separator,
|
||||
this.followUs,
|
||||
this.separator,
|
||||
this.goToWebVault,
|
||||
this.separator,
|
||||
this.getMobileApp,
|
||||
this.getBrowserExtension,
|
||||
];
|
||||
|
||||
if (this._aboutMenu != null) {
|
||||
items.push(...this._aboutMenu.items);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
private readonly _i18nService: I18nService;
|
||||
private readonly _webVaultUrl: string;
|
||||
private readonly _aboutMenu: AboutMenu;
|
||||
|
||||
constructor(i18nService: I18nService, webVaultUrl: string, aboutMenu: AboutMenu) {
|
||||
this._i18nService = i18nService;
|
||||
this._webVaultUrl = webVaultUrl;
|
||||
this._aboutMenu = aboutMenu;
|
||||
}
|
||||
|
||||
private get contactUs(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "contactUs",
|
||||
label: this.localize("contactUs"),
|
||||
click: () => shell.openExternal("https://bitwarden.com/contact"),
|
||||
};
|
||||
}
|
||||
|
||||
private get getHelp(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "getHelp",
|
||||
label: this.localize("getHelp"),
|
||||
click: () => shell.openExternal("https://bitwarden.com/help"),
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
52
apps/desktop/src/main/menu/menu.main.ts
Normal file
52
apps/desktop/src/main/menu/menu.main.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { app, Menu } from "electron";
|
||||
|
||||
import { BaseMenu } from "jslib-electron/baseMenu";
|
||||
|
||||
import { Main } from "../../main";
|
||||
|
||||
import { MenuUpdateRequest } from "./menu.updater";
|
||||
import { Menubar } from "./menubar";
|
||||
|
||||
const cloudWebVaultUrl = "https://vault.bitwarden.com";
|
||||
|
||||
export class MenuMain extends BaseMenu {
|
||||
constructor(private main: Main) {
|
||||
super(main.i18nService, main.windowMain);
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.initContextMenu();
|
||||
await this.setMenu();
|
||||
}
|
||||
|
||||
async updateApplicationMenuState(updateRequest: MenuUpdateRequest) {
|
||||
await this.setMenu(updateRequest);
|
||||
}
|
||||
|
||||
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 getWebVaultUrl() {
|
||||
let webVaultUrl = cloudWebVaultUrl;
|
||||
const urlsObj: any = await this.main.stateService.getEnvironmentUrls();
|
||||
if (urlsObj != null) {
|
||||
if (urlsObj.base != null) {
|
||||
webVaultUrl = urlsObj.base;
|
||||
} else if (urlsObj.webVault != null) {
|
||||
webVaultUrl = urlsObj.webVault;
|
||||
}
|
||||
}
|
||||
return webVaultUrl;
|
||||
}
|
||||
}
|
||||
12
apps/desktop/src/main/menu/menu.updater.ts
Normal file
12
apps/desktop/src/main/menu/menu.updater.ts
Normal 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;
|
||||
}
|
||||
135
apps/desktop/src/main/menu/menu.view.ts
Normal file
135
apps/desktop/src/main/menu/menu.view.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { MenuItemConstructorOptions } from "electron";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
|
||||
import { IMenubarMenu } from "./menubar";
|
||||
|
||||
export class ViewMenu implements IMenubarMenu {
|
||||
readonly id: "viewMenu";
|
||||
|
||||
get label(): string {
|
||||
return this.localize("view");
|
||||
}
|
||||
|
||||
get items(): MenuItemConstructorOptions[] {
|
||||
return [
|
||||
this.searchVault,
|
||||
this.separator,
|
||||
this.generator,
|
||||
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 _isLocked: boolean;
|
||||
|
||||
constructor(i18nService: I18nService, messagingService: MessagingService, isLocked: boolean) {
|
||||
this._i18nService = i18nService;
|
||||
this._messagingService = messagingService;
|
||||
this._isLocked = isLocked;
|
||||
}
|
||||
|
||||
private get searchVault(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "searchVault",
|
||||
label: this.localize("searchVault"),
|
||||
click: () => this.sendMessage("focusSearch"),
|
||||
accelerator: "CmdOrCtrl+F",
|
||||
enabled: !this._isLocked,
|
||||
};
|
||||
}
|
||||
|
||||
private get separator(): MenuItemConstructorOptions {
|
||||
return { type: "separator" };
|
||||
}
|
||||
|
||||
private get generator(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "generator",
|
||||
label: this.localize("generator"),
|
||||
click: () => this.sendMessage("openGenerator"),
|
||||
accelerator: "CmdOrCtrl+G",
|
||||
enabled: !this._isLocked,
|
||||
};
|
||||
}
|
||||
|
||||
private get passwordHistory(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "passwordHistory",
|
||||
label: this.localize("passwordHistory"),
|
||||
click: () => this.sendMessage("openPasswordHistory"),
|
||||
enabled: !this._isLocked,
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
105
apps/desktop/src/main/menu/menu.window.ts
Normal file
105
apps/desktop/src/main/menu/menu.window.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { MenuItemConstructorOptions } from "electron";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { isMac } from "jslib-electron/utils";
|
||||
import { WindowMain } from "jslib-electron/window.main";
|
||||
|
||||
import { IMenubarMenu } from "./menubar";
|
||||
|
||||
export class WindowMenu implements IMenubarMenu {
|
||||
readonly id: string;
|
||||
|
||||
get label(): string {
|
||||
return this.localize("window");
|
||||
}
|
||||
|
||||
get items(): MenuItemConstructorOptions[] {
|
||||
const items = [this.minimize, this.hideToMenu, this.alwaysOnTop];
|
||||
|
||||
if (isMac()) {
|
||||
items.push(this.zoom, this.separator, this.bringAllToFront);
|
||||
}
|
||||
|
||||
items.push(this.separator, this.close);
|
||||
return items;
|
||||
}
|
||||
|
||||
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",
|
||||
};
|
||||
}
|
||||
|
||||
private get hideToMenu(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "hideToMenu",
|
||||
label: this.localize(isMac() ? "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",
|
||||
};
|
||||
}
|
||||
|
||||
private get separator(): MenuItemConstructorOptions {
|
||||
return { type: "separator" };
|
||||
}
|
||||
|
||||
private get bringAllToFront(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "bringAllToFront",
|
||||
label: this.localize("bringAllToFront"),
|
||||
role: "front",
|
||||
};
|
||||
}
|
||||
|
||||
private get close(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: "close",
|
||||
label: this.localize("close"),
|
||||
role: "close",
|
||||
};
|
||||
}
|
||||
|
||||
private localize(s: string) {
|
||||
return this._i18nService.t(s);
|
||||
}
|
||||
|
||||
private sendMessage(message: string, args?: any) {
|
||||
this._messagingService.send(message, args);
|
||||
}
|
||||
}
|
||||
100
apps/desktop/src/main/menu/menubar.ts
Normal file
100
apps/desktop/src/main/menu/menubar.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Menu, 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 { isMac } from "jslib-electron/utils";
|
||||
import { WindowMain } from "jslib-electron/window.main";
|
||||
|
||||
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";
|
||||
|
||||
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
|
||||
) {
|
||||
let isLocked = true;
|
||||
if (
|
||||
updateRequest != null &&
|
||||
updateRequest.accounts != null &&
|
||||
updateRequest.activeUserId != null
|
||||
) {
|
||||
isLocked = updateRequest.accounts[updateRequest.activeUserId]?.isLocked ?? true;
|
||||
}
|
||||
|
||||
this.items = [
|
||||
new FileMenu(
|
||||
i18nService,
|
||||
messagingService,
|
||||
updaterMain,
|
||||
windowMain.win,
|
||||
updateRequest?.accounts,
|
||||
isLocked
|
||||
),
|
||||
new EditMenu(i18nService, messagingService, isLocked),
|
||||
new ViewMenu(i18nService, messagingService, isLocked),
|
||||
new AccountMenu(i18nService, messagingService, webVaultUrl, windowMain.win, isLocked),
|
||||
new WindowMenu(i18nService, messagingService, windowMain),
|
||||
new HelpMenu(
|
||||
i18nService,
|
||||
webVaultUrl,
|
||||
new AboutMenu(i18nService, appVersion, windowMain.win, updaterMain)
|
||||
),
|
||||
];
|
||||
|
||||
if (isMac()) {
|
||||
this.items = [
|
||||
...[
|
||||
new BitwardenMenu(
|
||||
i18nService,
|
||||
messagingService,
|
||||
updaterMain,
|
||||
windowMain.win,
|
||||
updateRequest?.accounts,
|
||||
isLocked
|
||||
),
|
||||
],
|
||||
...this.items,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
159
apps/desktop/src/main/messaging.main.ts
Normal file
159
apps/desktop/src/main/messaging.main.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
import { app, ipcMain } from "electron";
|
||||
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
import { Main } from "../main";
|
||||
|
||||
import { MenuUpdateRequest } from "./menu/menu.updater";
|
||||
|
||||
const SyncInterval = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
export class MessagingMain {
|
||||
private syncTimeout: NodeJS.Timer;
|
||||
|
||||
constructor(private main: Main, private stateService: StateService) {}
|
||||
|
||||
init() {
|
||||
this.scheduleNextSync();
|
||||
if (process.platform === "linux") {
|
||||
this.stateService.setOpenAtLogin(fs.existsSync(this.linuxStartupFile()));
|
||||
} else {
|
||||
const loginSettings = app.getLoginItemSettings();
|
||||
this.stateService.setOpenAtLogin(loginSettings.openAtLogin);
|
||||
}
|
||||
ipcMain.on("messagingService", async (event: any, message: any) => this.onMessage(message));
|
||||
}
|
||||
|
||||
onMessage(message: any) {
|
||||
switch (message.command) {
|
||||
case "scheduleNextSync":
|
||||
this.scheduleNextSync();
|
||||
break;
|
||||
case "updateAppMenu":
|
||||
this.main.menuMain.updateApplicationMenuState(message.updateRequest);
|
||||
this.updateTrayMenu(message.updateRequest);
|
||||
break;
|
||||
case "minimizeOnCopy":
|
||||
this.stateService.getMinimizeOnCopyToClipboard().then((shouldMinimize) => {
|
||||
if (shouldMinimize && this.main.windowMain.win !== null) {
|
||||
this.main.windowMain.win.minimize();
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "showTray":
|
||||
this.main.trayMain.showTray();
|
||||
break;
|
||||
case "removeTray":
|
||||
this.main.trayMain.removeTray();
|
||||
break;
|
||||
case "hideToTray":
|
||||
this.main.trayMain.hideToTray();
|
||||
break;
|
||||
case "addOpenAtLogin":
|
||||
this.addOpenAtLogin();
|
||||
break;
|
||||
case "removeOpenAtLogin":
|
||||
this.removeOpenAtLogin();
|
||||
break;
|
||||
case "setFocus":
|
||||
this.setFocus();
|
||||
break;
|
||||
case "getWindowIsFocused":
|
||||
this.windowIsFocused();
|
||||
break;
|
||||
case "enableBrowserIntegration":
|
||||
this.main.nativeMessagingMain.generateManifests();
|
||||
this.main.nativeMessagingMain.listen();
|
||||
break;
|
||||
case "disableBrowserIntegration":
|
||||
this.main.nativeMessagingMain.removeManifests();
|
||||
this.main.nativeMessagingMain.stop();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private scheduleNextSync() {
|
||||
if (this.syncTimeout) {
|
||||
global.clearTimeout(this.syncTimeout);
|
||||
}
|
||||
|
||||
this.syncTimeout = global.setTimeout(() => {
|
||||
if (this.main.windowMain.win == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.main.windowMain.win.webContents.send("messagingService", {
|
||||
command: "checkSyncVault",
|
||||
});
|
||||
}, SyncInterval);
|
||||
}
|
||||
|
||||
private updateTrayMenu(updateRequest: MenuUpdateRequest) {
|
||||
if (
|
||||
this.main.trayMain == null ||
|
||||
this.main.trayMain.contextMenu == null ||
|
||||
updateRequest?.activeUserId == null
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const lockVaultTrayMenuItem = this.main.trayMain.contextMenu.getMenuItemById("lockVault");
|
||||
const activeAccount = updateRequest.accounts[updateRequest.activeUserId];
|
||||
if (lockVaultTrayMenuItem != null && activeAccount != null) {
|
||||
lockVaultTrayMenuItem.enabled = activeAccount.isAuthenticated && !activeAccount.isLocked;
|
||||
}
|
||||
this.main.trayMain.updateContextMenu();
|
||||
}
|
||||
|
||||
private addOpenAtLogin() {
|
||||
if (process.platform === "linux") {
|
||||
const data = `[Desktop Entry]
|
||||
Type=Application
|
||||
Version=${app.getVersion()}
|
||||
Name=Bitwarden
|
||||
Comment=Bitwarden startup script
|
||||
Exec=${app.getPath("exe")}
|
||||
StartupNotify=false
|
||||
Terminal=false`;
|
||||
|
||||
const dir = path.dirname(this.linuxStartupFile());
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir);
|
||||
}
|
||||
fs.writeFileSync(this.linuxStartupFile(), data);
|
||||
} else {
|
||||
app.setLoginItemSettings({ openAtLogin: true });
|
||||
}
|
||||
}
|
||||
|
||||
private removeOpenAtLogin() {
|
||||
if (process.platform === "linux") {
|
||||
if (fs.existsSync(this.linuxStartupFile())) {
|
||||
fs.unlinkSync(this.linuxStartupFile());
|
||||
}
|
||||
} else {
|
||||
app.setLoginItemSettings({ openAtLogin: false });
|
||||
}
|
||||
}
|
||||
|
||||
private linuxStartupFile(): string {
|
||||
return path.join(app.getPath("home"), ".config", "autostart", "bitwarden.desktop");
|
||||
}
|
||||
|
||||
private setFocus() {
|
||||
this.main.trayMain.restoreFromTray();
|
||||
this.main.windowMain.win.focusOnWebView();
|
||||
}
|
||||
|
||||
private windowIsFocused() {
|
||||
const windowIsFocused = this.main.windowMain.win.isFocused();
|
||||
this.main.windowMain.win.webContents.send("messagingService", {
|
||||
command: "windowIsFocused",
|
||||
windowIsFocused: windowIsFocused,
|
||||
});
|
||||
}
|
||||
}
|
||||
319
apps/desktop/src/main/nativeMessaging.main.ts
Normal file
319
apps/desktop/src/main/nativeMessaging.main.ts
Normal file
@@ -0,0 +1,319 @@
|
||||
import { existsSync, promises as fs } from "fs";
|
||||
import { Socket } from "net";
|
||||
import { homedir, userInfo } from "os";
|
||||
import * as path from "path";
|
||||
import * as util from "util";
|
||||
|
||||
import { ipcMain } from "electron";
|
||||
import * as ipc from "node-ipc";
|
||||
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { WindowMain } from "jslib-electron/window.main";
|
||||
|
||||
export class NativeMessagingMain {
|
||||
private connected: Socket[] = [];
|
||||
private socket: any;
|
||||
|
||||
constructor(
|
||||
private logService: LogService,
|
||||
private windowMain: WindowMain,
|
||||
private userPath: string,
|
||||
private exePath: string
|
||||
) {}
|
||||
|
||||
async listen() {
|
||||
ipc.config.id = "bitwarden";
|
||||
ipc.config.retry = 1500;
|
||||
if (process.platform === "darwin") {
|
||||
if (!existsSync(`${homedir()}/tmp`)) {
|
||||
await fs.mkdir(`${homedir()}/tmp`);
|
||||
}
|
||||
ipc.config.socketRoot = `${homedir()}/tmp/`;
|
||||
}
|
||||
|
||||
ipc.serve(() => {
|
||||
ipc.server.on("message", (data: any, socket: any) => {
|
||||
this.socket = socket;
|
||||
this.windowMain.win.webContents.send("nativeMessaging", data);
|
||||
});
|
||||
|
||||
ipcMain.on("nativeMessagingReply", (event, msg) => {
|
||||
if (this.socket != null && msg != null) {
|
||||
this.send(msg, this.socket);
|
||||
}
|
||||
});
|
||||
|
||||
ipc.server.on("connect", (socket: Socket) => {
|
||||
this.connected.push(socket);
|
||||
});
|
||||
|
||||
ipc.server.on("socket.disconnected", (socket, destroyedSocketID) => {
|
||||
const index = this.connected.indexOf(socket);
|
||||
if (index > -1) {
|
||||
this.connected.splice(index, 1);
|
||||
}
|
||||
|
||||
this.socket = null;
|
||||
ipc.log("client " + destroyedSocketID + " has disconnected!");
|
||||
});
|
||||
});
|
||||
|
||||
ipc.server.start();
|
||||
}
|
||||
|
||||
stop() {
|
||||
ipc.server.stop();
|
||||
// Kill all existing connections
|
||||
this.connected.forEach((socket) => {
|
||||
if (!socket.destroyed) {
|
||||
socket.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
send(message: object, socket: any) {
|
||||
ipc.server.emit(socket, "message", message);
|
||||
}
|
||||
|
||||
generateManifests() {
|
||||
const baseJson = {
|
||||
name: "com.8bit.bitwarden",
|
||||
description: "Bitwarden desktop <-> browser bridge",
|
||||
path: this.binaryPath(),
|
||||
type: "stdio",
|
||||
};
|
||||
|
||||
const firefoxJson = {
|
||||
...baseJson,
|
||||
...{ allowed_extensions: ["{446900e4-71c2-419f-a6a7-df9c091e268b}"] },
|
||||
};
|
||||
const chromeJson = {
|
||||
...baseJson,
|
||||
...{
|
||||
allowed_origins: [
|
||||
"chrome-extension://nngceckbapebfimnlniiiahkandclblb/",
|
||||
"chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh/",
|
||||
"chrome-extension://ccnckbpmaceehanjmeomladnmlffdjgn/",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
switch (process.platform) {
|
||||
case "win32": {
|
||||
const destination = path.join(this.userPath, "browsers");
|
||||
this.writeManifest(path.join(destination, "firefox.json"), firefoxJson);
|
||||
this.writeManifest(path.join(destination, "chrome.json"), chromeJson);
|
||||
|
||||
this.createWindowsRegistry(
|
||||
"HKLM\\SOFTWARE\\Mozilla\\Firefox",
|
||||
"HKCU\\SOFTWARE\\Mozilla\\NativeMessagingHosts\\com.8bit.bitwarden",
|
||||
path.join(destination, "firefox.json")
|
||||
);
|
||||
this.createWindowsRegistry(
|
||||
"HKCU\\SOFTWARE\\Google\\Chrome",
|
||||
"HKCU\\SOFTWARE\\Google\\Chrome\\NativeMessagingHosts\\com.8bit.bitwarden",
|
||||
path.join(destination, "chrome.json")
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "darwin": {
|
||||
const nmhs = this.getDarwinNMHS();
|
||||
for (const [key, value] of Object.entries(nmhs)) {
|
||||
if (existsSync(value)) {
|
||||
const p = path.join(value, "NativeMessagingHosts", "com.8bit.bitwarden.json");
|
||||
|
||||
let manifest: any = chromeJson;
|
||||
if (key === "Firefox") {
|
||||
manifest = firefoxJson;
|
||||
}
|
||||
|
||||
this.writeManifest(p, manifest).catch((e) =>
|
||||
this.logService.error(`Error writing manifest for ${key}. ${e}`)
|
||||
);
|
||||
} else {
|
||||
this.logService.warning(`${key} not found skipping.`);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "linux":
|
||||
if (existsSync(`${this.homedir()}/.mozilla/`)) {
|
||||
this.writeManifest(
|
||||
`${this.homedir()}/.mozilla/native-messaging-hosts/com.8bit.bitwarden.json`,
|
||||
firefoxJson
|
||||
);
|
||||
}
|
||||
|
||||
if (existsSync(`${this.homedir()}/.config/google-chrome/`)) {
|
||||
this.writeManifest(
|
||||
`${this.homedir()}/.config/google-chrome/NativeMessagingHosts/com.8bit.bitwarden.json`,
|
||||
chromeJson
|
||||
);
|
||||
}
|
||||
|
||||
if (existsSync(`${this.homedir()}/.config/microsoft-edge/`)) {
|
||||
this.writeManifest(
|
||||
`${this.homedir()}/.config/microsoft-edge/NativeMessagingHosts/com.8bit.bitwarden.json`,
|
||||
chromeJson
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
removeManifests() {
|
||||
switch (process.platform) {
|
||||
case "win32":
|
||||
fs.unlink(path.join(this.userPath, "browsers", "firefox.json"));
|
||||
fs.unlink(path.join(this.userPath, "browsers", "chrome.json"));
|
||||
this.deleteWindowsRegistry(
|
||||
"HKCU\\SOFTWARE\\Mozilla\\NativeMessagingHosts\\com.8bit.bitwarden"
|
||||
);
|
||||
this.deleteWindowsRegistry(
|
||||
"HKCU\\SOFTWARE\\Google\\Chrome\\NativeMessagingHosts\\com.8bit.bitwarden"
|
||||
);
|
||||
break;
|
||||
case "darwin": {
|
||||
const nmhs = this.getDarwinNMHS();
|
||||
for (const [, value] of Object.entries(nmhs)) {
|
||||
const p = path.join(value, "NativeMessagingHosts", "com.8bit.bitwarden.json");
|
||||
if (existsSync(p)) {
|
||||
fs.unlink(p);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "linux":
|
||||
if (
|
||||
existsSync(`${this.homedir()}/.mozilla/native-messaging-hosts/com.8bit.bitwarden.json`)
|
||||
) {
|
||||
fs.unlink(`${this.homedir()}/.mozilla/native-messaging-hosts/com.8bit.bitwarden.json`);
|
||||
}
|
||||
|
||||
if (
|
||||
existsSync(
|
||||
`${this.homedir()}/.config/google-chrome/NativeMessagingHosts/com.8bit.bitwarden.json`
|
||||
)
|
||||
) {
|
||||
fs.unlink(
|
||||
`${this.homedir()}/.config/google-chrome/NativeMessagingHosts/com.8bit.bitwarden.json`
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
existsSync(
|
||||
`${this.homedir()}/.config/microsoft-edge/NativeMessagingHosts/com.8bit.bitwarden.json`
|
||||
)
|
||||
) {
|
||||
fs.unlink(
|
||||
`${this.homedir()}/.config/microsoft-edge/NativeMessagingHosts/com.8bit.bitwarden.json`
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private getDarwinNMHS() {
|
||||
/* eslint-disable no-useless-escape */
|
||||
return {
|
||||
Firefox: `${this.homedir()}/Library/Application\ Support/Mozilla/`,
|
||||
Chrome: `${this.homedir()}/Library/Application\ Support/Google/Chrome/`,
|
||||
"Chrome Beta": `${this.homedir()}/Library/Application\ Support/Google/Chrome\ Beta/`,
|
||||
"Chrome Dev": `${this.homedir()}/Library/Application\ Support/Google/Chrome\ Dev/`,
|
||||
"Chrome Canary": `${this.homedir()}/Library/Application\ Support/Google/Chrome\ Canary/`,
|
||||
Chromium: `${this.homedir()}/Library/Application\ Support/Chromium/`,
|
||||
"Microsoft Edge": `${this.homedir()}/Library/Application\ Support/Microsoft\ Edge/`,
|
||||
"Microsoft Edge Beta": `${this.homedir()}/Library/Application\ Support/Microsoft\ Edge\ Beta/`,
|
||||
"Microsoft Edge Dev": `${this.homedir()}/Library/Application\ Support/Microsoft\ Edge\ Dev/`,
|
||||
"Microsoft Edge Canary": `${this.homedir()}/Library/Application\ Support/Microsoft\ Edge\ Canary/`,
|
||||
Vivaldi: `${this.homedir()}/Library/Application\ Support/Vivaldi/`,
|
||||
};
|
||||
/* eslint-enable no-useless-escape */
|
||||
}
|
||||
|
||||
private async writeManifest(destination: string, manifest: object) {
|
||||
if (!existsSync(path.dirname(destination))) {
|
||||
await fs.mkdir(path.dirname(destination));
|
||||
}
|
||||
fs.writeFile(destination, JSON.stringify(manifest, null, 2)).catch(this.logService.error);
|
||||
}
|
||||
|
||||
private binaryPath() {
|
||||
if (process.platform === "win32") {
|
||||
return path.join(path.dirname(this.exePath), "resources", "native-messaging.bat");
|
||||
}
|
||||
|
||||
return this.exePath;
|
||||
}
|
||||
|
||||
private getRegeditInstance() {
|
||||
// eslint-disable-next-line
|
||||
const regedit = require("regedit");
|
||||
regedit.setExternalVBSLocation(path.join(path.dirname(this.exePath), "resources/regedit/vbs"));
|
||||
|
||||
return regedit;
|
||||
}
|
||||
|
||||
private async createWindowsRegistry(check: string, location: string, jsonFile: string) {
|
||||
const regedit = this.getRegeditInstance();
|
||||
|
||||
const list = util.promisify(regedit.list);
|
||||
const createKey = util.promisify(regedit.createKey);
|
||||
const putValue = util.promisify(regedit.putValue);
|
||||
|
||||
this.logService.debug(`Adding registry: ${location}`);
|
||||
|
||||
// Check installed
|
||||
try {
|
||||
await list(check);
|
||||
} catch {
|
||||
this.logService.warning(`Not finding registry ${check} skipping.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await createKey(location);
|
||||
|
||||
// Insert path to manifest
|
||||
const obj: any = {};
|
||||
obj[location] = {
|
||||
default: {
|
||||
value: jsonFile,
|
||||
type: "REG_DEFAULT",
|
||||
},
|
||||
};
|
||||
|
||||
return putValue(obj);
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private async deleteWindowsRegistry(key: string) {
|
||||
const regedit = this.getRegeditInstance();
|
||||
|
||||
const list = util.promisify(regedit.list);
|
||||
const deleteKey = util.promisify(regedit.deleteKey);
|
||||
|
||||
this.logService.debug(`Removing registry: ${key}`);
|
||||
|
||||
try {
|
||||
await list(key);
|
||||
await deleteKey(key);
|
||||
} catch {
|
||||
this.logService.error(`Unable to delete registry key: ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
private homedir() {
|
||||
if (process.platform === "darwin") {
|
||||
return userInfo().homedir;
|
||||
} else {
|
||||
return homedir();
|
||||
}
|
||||
}
|
||||
}
|
||||
47
apps/desktop/src/main/powerMonitor.main.ts
Normal file
47
apps/desktop/src/main/powerMonitor.main.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { powerMonitor } from "electron";
|
||||
|
||||
import { isSnapStore } from "jslib-electron/utils";
|
||||
|
||||
import { Main } from "../main";
|
||||
|
||||
// tslint:disable-next-line
|
||||
const IdleLockSeconds = 5 * 60; // 5 minutes
|
||||
const IdleCheckInterval = 30 * 1000; // 30 seconds
|
||||
|
||||
export class PowerMonitorMain {
|
||||
private idle = false;
|
||||
|
||||
constructor(private main: Main) {}
|
||||
|
||||
init() {
|
||||
// ref: https://github.com/electron/electron/issues/13767
|
||||
if (!isSnapStore()) {
|
||||
// System sleep
|
||||
powerMonitor.on("suspend", () => {
|
||||
this.main.messagingService.send("systemSuspended");
|
||||
});
|
||||
}
|
||||
|
||||
if (process.platform !== "linux") {
|
||||
// System locked
|
||||
powerMonitor.on("lock-screen", () => {
|
||||
this.main.messagingService.send("systemLocked");
|
||||
});
|
||||
}
|
||||
|
||||
// System idle
|
||||
global.setInterval(() => {
|
||||
const idleSeconds: number = powerMonitor.getSystemIdleTime();
|
||||
const idle = idleSeconds >= IdleLockSeconds;
|
||||
if (idle) {
|
||||
if (this.idle) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.main.messagingService.send("systemIdle");
|
||||
}
|
||||
|
||||
this.idle = idle;
|
||||
}, IdleCheckInterval);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user