1
0
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:
Hinton
2022-05-05 17:16:23 +02:00
parent 9852f2ec22
commit 28bc4113b9
331 changed files with 2 additions and 2 deletions

View 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;
}
}
}

View File

@@ -0,0 +1,6 @@
export abstract class BiometricMain {
isError: boolean;
init: () => Promise<void>;
supportsBiometric: () => Promise<boolean>;
authenticateBiometric: () => Promise<boolean>;
}

View 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;
}
}

View 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;
}
}

View 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);
}
}

View 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);
}
}

View 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(),
};
}
}

View 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);
}
}

View 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",
};
}
}

View 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);
}
}

View 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);
}
}

View 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;
}
}

View File

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

View 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);
}
}

View 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);
}
}

View 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,
];
}
}
}

View 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,
});
}
}

View 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();
}
}
}

View 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);
}
}