1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00

[SM-329] Merge libs/electron into desktop (#3989)

This commit is contained in:
Oscar Hinton
2022-12-02 12:45:09 +01:00
committed by GitHub
parent 8e4e770ca3
commit 0a73290714
47 changed files with 146 additions and 385 deletions

View File

@@ -7,7 +7,8 @@ import { EnvironmentService } from "@bitwarden/common/abstractions/environment.s
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { Utils } from "@bitwarden/common/misc/utils";
import { getCookie } from "@bitwarden/electron/utils";
import { getCookie } from "../../utils";
const BroadcasterSubscriptionId = "AccessibilityCookieComponent";

View File

@@ -14,9 +14,9 @@ import { DeviceType } from "@bitwarden/common/enums/deviceType";
import { StorageLocation } from "@bitwarden/common/enums/storageLocation";
import { ThemeType } from "@bitwarden/common/enums/themeType";
import { Utils } from "@bitwarden/common/misc/utils";
import { isWindowsStore } from "@bitwarden/electron/utils";
import { flagEnabled } from "../../flags";
import { isWindowsStore } from "../../utils";
import { SetPinComponent } from "../components/set-pin.component";
@Component({

View File

@@ -1,7 +1,7 @@
import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { isDev } from "@bitwarden/electron/utils";
import { isDev } from "../utils";
// tslint:disable-next-line
require("../scss/styles.scss");

View File

@@ -10,8 +10,8 @@ import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.serv
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { SendService } from "@bitwarden/common/abstractions/send.service";
import { SendView } from "@bitwarden/common/models/view/send.view";
import { invokeMenu, RendererMenuItem } from "@bitwarden/electron/utils";
import { invokeMenu, RendererMenuItem } from "../../utils";
import { SearchBarService } from "../layout/search/search-bar.service";
import { AddEditComponent } from "./add-edit.component";

View File

@@ -39,14 +39,14 @@ import { GlobalState } from "@bitwarden/common/models/domain/global-state";
import { LoginService } from "@bitwarden/common/services/login.service";
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
import { SystemService } from "@bitwarden/common/services/system.service";
import { ElectronCryptoService } from "@bitwarden/electron/services/electronCrypto.service";
import { ElectronLogService } from "@bitwarden/electron/services/electronLog.service";
import { ElectronPlatformUtilsService } from "@bitwarden/electron/services/electronPlatformUtils.service";
import { ElectronRendererMessagingService } from "@bitwarden/electron/services/electronRendererMessaging.service";
import { ElectronRendererSecureStorageService } from "@bitwarden/electron/services/electronRendererSecureStorage.service";
import { ElectronRendererStorageService } from "@bitwarden/electron/services/electronRendererStorage.service";
import { Account } from "../../models/account";
import { ElectronCryptoService } from "../../services/electron-crypto.service";
import { ElectronLogService } from "../../services/electron-log.service";
import { ElectronPlatformUtilsService } from "../../services/electron-platform-utils.service";
import { ElectronRendererMessagingService } from "../../services/electron-renderer-messaging.service";
import { ElectronRendererSecureStorageService } from "../../services/electron-renderer-secure-storage.service";
import { ElectronRendererStorageService } from "../../services/electron-renderer-storage.service";
import { EncryptedMessageHandlerService } from "../../services/encrypted-message-handler.service";
import { I18nService } from "../../services/i18n.service";
import { NativeMessageHandlerService } from "../../services/native-message-handler.service";

View File

@@ -27,8 +27,8 @@ import { CipherType } from "@bitwarden/common/enums/cipherType";
import { EventType } from "@bitwarden/common/enums/eventType";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/models/view/folder.view";
import { invokeMenu, RendererMenuItem } from "@bitwarden/electron/utils";
import { invokeMenu, RendererMenuItem } from "../../utils";
import { SearchBarService } from "../layout/search/search-bar.service";
import { AddEditComponent } from "./add-edit.component";

View File

@@ -6,12 +6,6 @@ import { StateFactory } from "@bitwarden/common/factories/stateFactory";
import { GlobalState } from "@bitwarden/common/models/domain/global-state";
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
import { StateService } from "@bitwarden/common/services/state.service";
import { ElectronLogService } from "@bitwarden/electron/services/electronLog.service";
import { ElectronMainMessagingService } from "@bitwarden/electron/services/electronMainMessaging.service";
import { ElectronStorageService } from "@bitwarden/electron/services/electronStorage.service";
import { TrayMain } from "@bitwarden/electron/tray.main";
import { UpdaterMain } from "@bitwarden/electron/updater.main";
import { WindowMain } from "@bitwarden/electron/window.main";
import { BiometricMain } from "./main/biometric/biometric.main";
import { DesktopCredentialStorageListener } from "./main/desktop-credential-storage-listener";
@@ -19,7 +13,13 @@ import { MenuMain } from "./main/menu/menu.main";
import { MessagingMain } from "./main/messaging.main";
import { NativeMessagingMain } from "./main/native-messaging.main";
import { PowerMonitorMain } from "./main/power-monitor.main";
import { TrayMain } from "./main/tray.main";
import { UpdaterMain } from "./main/updater.main";
import { WindowMain } from "./main/window.main";
import { Account } from "./models/account";
import { ElectronLogService } from "./services/electron-log.service";
import { ElectronMainMessagingService } from "./services/electron-main-messaging.service";
import { ElectronStorageService } from "./services/electron-storage.service";
import { I18nService } from "./services/i18n.service";
export class Main {
@@ -105,15 +105,7 @@ export class Main {
(win) => this.trayMain.setupWindowListeners(win)
);
this.messagingMain = new MessagingMain(this, this.stateService);
this.updaterMain = new UpdaterMain(
this.i18nService,
this.windowMain,
"clients",
null,
null,
null,
"bitwarden"
);
this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain, "bitwarden");
this.menuMain = new MenuMain(this);
this.powerMonitorMain = new PowerMonitorMain(this);
this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.stateService);

View File

@@ -4,7 +4,8 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { biometrics } from "@bitwarden/desktop-native";
import { WindowMain } from "@bitwarden/electron/window.main";
import { WindowMain } from "../window.main";
import { BiometricMain } from "./biometric.main";

View File

@@ -1,8 +1,9 @@
import { BrowserWindow, clipboard, dialog, MenuItemConstructorOptions } from "electron";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { UpdaterMain } from "@bitwarden/electron/updater.main";
import { isMacAppStore, isSnapStore, isWindowsStore } from "@bitwarden/electron/utils";
import { isMacAppStore, isSnapStore, isWindowsStore } from "../../utils";
import { UpdaterMain } from "../updater.main";
import { IMenubarMenu } from "./menubar";

View File

@@ -2,7 +2,8 @@ import { BrowserWindow, dialog, MenuItemConstructorOptions, shell } from "electr
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { isMacAppStore, isWindowsStore } from "@bitwarden/electron/utils";
import { isMacAppStore, isWindowsStore } from "../../utils";
import { IMenubarMenu } from "./menubar";

View File

@@ -2,8 +2,9 @@ import { BrowserWindow, MenuItemConstructorOptions } from "electron";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { UpdaterMain } from "@bitwarden/electron/updater.main";
import { isMac } from "@bitwarden/electron/utils";
import { isMac } from "../../utils";
import { UpdaterMain } from "../updater.main";
import { FirstMenu } from "./menu.first";
import { MenuAccount } from "./menu.updater";

View File

@@ -2,8 +2,9 @@ import { BrowserWindow, MenuItemConstructorOptions } from "electron";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { UpdaterMain } from "@bitwarden/electron/updater.main";
import { isMac, isMacAppStore } from "@bitwarden/electron/utils";
import { isMac, isMacAppStore } from "../../utils";
import { UpdaterMain } from "../updater.main";
import { FirstMenu } from "./menu.first";
import { MenuAccount } from "./menu.updater";

View File

@@ -2,8 +2,9 @@ import { BrowserWindow, dialog, MenuItem, MenuItemConstructorOptions } from "ele
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { UpdaterMain } from "@bitwarden/electron/updater.main";
import { isMacAppStore, isSnapStore, isWindowsStore } from "@bitwarden/electron/utils";
import { isMacAppStore, isSnapStore, isWindowsStore } from "../../utils";
import { UpdaterMain } from "../updater.main";
import { MenuAccount } from "./menu.updater";

View File

@@ -1,7 +1,8 @@
import { shell, MenuItemConstructorOptions } from "electron";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { isMacAppStore, isWindowsStore } from "@bitwarden/electron/utils";
import { isMacAppStore, isWindowsStore } from "../../utils";
import { AboutMenu } from "./menu.about";
import { IMenubarMenu } from "./menubar";

View File

@@ -1,17 +1,22 @@
import { app, Menu } from "electron";
import { BaseMenu } from "@bitwarden/electron/baseMenu";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { Main } from "../../main";
import { WindowMain } from "../window.main";
import { MenuUpdateRequest } from "./menu.updater";
import { Menubar } from "./menubar";
const cloudWebVaultUrl = "https://vault.bitwarden.com";
export class MenuMain extends BaseMenu {
export class MenuMain {
private i18nService: I18nService;
private windowMain: WindowMain;
constructor(private main: Main) {
super(main.i18nService, main.windowMain);
this.i18nService = main.i18nService;
this.windowMain = main.windowMain;
}
async init() {
@@ -49,4 +54,84 @@ export class MenuMain extends BaseMenu {
}
return webVaultUrl;
}
private initContextMenu() {
if (this.windowMain.win == null) {
return;
}
const selectionMenu = Menu.buildFromTemplate([
{
label: this.i18nService.t("copy"),
role: "copy",
},
{ type: "separator" },
{
label: this.i18nService.t("selectAll"),
role: "selectAll",
},
]);
const inputMenu = Menu.buildFromTemplate([
{
label: this.i18nService.t("undo"),
role: "undo",
},
{
label: this.i18nService.t("redo"),
role: "redo",
},
{ type: "separator" },
{
label: this.i18nService.t("cut"),
role: "cut",
enabled: false,
},
{
label: this.i18nService.t("copy"),
role: "copy",
enabled: false,
},
{
label: this.i18nService.t("paste"),
role: "paste",
},
{ type: "separator" },
{
label: this.i18nService.t("selectAll"),
role: "selectAll",
},
]);
const inputSelectionMenu = Menu.buildFromTemplate([
{
label: this.i18nService.t("cut"),
role: "cut",
},
{
label: this.i18nService.t("copy"),
role: "copy",
},
{
label: this.i18nService.t("paste"),
role: "paste",
},
{ type: "separator" },
{
label: this.i18nService.t("selectAll"),
role: "selectAll",
},
]);
this.windowMain.win.webContents.on("context-menu", (e, props) => {
const selected = props.selectionText && props.selectionText.trim() !== "";
if (props.isEditable && selected) {
inputSelectionMenu.popup({ window: this.windowMain.win });
} else if (props.isEditable) {
inputMenu.popup({ window: this.windowMain.win });
} else if (selected) {
selectionMenu.popup({ window: this.windowMain.win });
}
});
}
}

View File

@@ -2,8 +2,9 @@ import { MenuItemConstructorOptions } from "electron";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { isMac } from "@bitwarden/electron/utils";
import { WindowMain } from "@bitwarden/electron/window.main";
import { isMac } from "../../utils";
import { WindowMain } from "../window.main";
import { IMenubarMenu } from "./menubar";

View File

@@ -2,9 +2,10 @@ import { Menu, MenuItemConstructorOptions } from "electron";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { UpdaterMain } from "@bitwarden/electron/updater.main";
import { isMac } from "@bitwarden/electron/utils";
import { WindowMain } from "@bitwarden/electron/window.main";
import { isMac } from "../../utils";
import { UpdaterMain } from "../updater.main";
import { WindowMain } from "../window.main";
import { AboutMenu } from "./menu.about";
import { AccountMenu } from "./menu.account";

View File

@@ -8,7 +8,8 @@ import { ipcMain } from "electron";
import * as ipc from "node-ipc";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { WindowMain } from "@bitwarden/electron/window.main";
import { WindowMain } from "./window.main";
export class NativeMessagingMain {
private connected: Socket[] = [];

View File

@@ -1,8 +1,7 @@
import { powerMonitor } from "electron";
import { isSnapStore } from "@bitwarden/electron/utils";
import { Main } from "../main";
import { isSnapStore } from "../utils";
// tslint:disable-next-line
const IdleLockSeconds = 5 * 60; // 5 minutes

View File

@@ -0,0 +1,186 @@
import * as path from "path";
import { app, BrowserWindow, Menu, MenuItemConstructorOptions, nativeImage, Tray } from "electron";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { WindowMain } from "./window.main";
export class TrayMain {
contextMenu: Menu;
private appName: string;
private tray: Tray;
private icon: string | Electron.NativeImage;
private pressedIcon: Electron.NativeImage;
constructor(
private windowMain: WindowMain,
private i18nService: I18nService,
private stateService: StateService
) {
if (process.platform === "win32") {
this.icon = path.join(__dirname, "/images/icon.ico");
} else if (process.platform === "darwin") {
const nImage = nativeImage.createFromPath(path.join(__dirname, "/images/icon-template.png"));
nImage.setTemplateImage(true);
this.icon = nImage;
this.pressedIcon = nativeImage.createFromPath(
path.join(__dirname, "/images/icon-highlight.png")
);
} else {
this.icon = path.join(__dirname, "/images/icon.png");
}
}
async init(appName: string, additionalMenuItems: MenuItemConstructorOptions[] = null) {
this.appName = appName;
const menuItemOptions: MenuItemConstructorOptions[] = [
{
label: this.i18nService.t("showHide"),
click: () => this.toggleWindow(),
},
{ type: "separator" },
{
label: this.i18nService.t("exit"),
click: () => this.closeWindow(),
},
];
if (additionalMenuItems != null) {
menuItemOptions.splice(1, 0, ...additionalMenuItems);
}
this.contextMenu = Menu.buildFromTemplate(menuItemOptions);
if (await this.stateService.getEnableTray()) {
this.showTray();
}
}
setupWindowListeners(win: BrowserWindow) {
win.on("minimize", async (e: Event) => {
if (await this.stateService.getEnableMinimizeToTray()) {
e.preventDefault();
this.hideToTray();
}
});
win.on("close", async (e: Event) => {
if (await this.stateService.getEnableCloseToTray()) {
if (!this.windowMain.isQuitting) {
e.preventDefault();
this.hideToTray();
}
}
});
win.on("show", async () => {
const enableTray = await this.stateService.getEnableTray();
if (!enableTray) {
setTimeout(() => this.removeTray(false), 100);
}
});
}
removeTray(showWindow = true) {
// Due to https://github.com/electron/electron/issues/17622
// we cannot destroy the tray icon on linux.
if (this.tray != null && process.platform !== "linux") {
this.tray.destroy();
this.tray = null;
}
if (showWindow && this.windowMain.win != null && !this.windowMain.win.isVisible()) {
this.windowMain.win.show();
}
}
async hideToTray() {
this.showTray();
if (this.windowMain.win != null) {
this.windowMain.win.hide();
}
if (this.isDarwin() && !(await this.stateService.getAlwaysShowDock())) {
this.hideDock();
}
}
restoreFromTray() {
if (this.windowMain.win == null || !this.windowMain.win.isVisible()) {
this.toggleWindow();
}
}
showTray() {
if (this.tray != null) {
return;
}
this.tray = new Tray(this.icon);
this.tray.setToolTip(this.appName);
this.tray.on("click", () => this.toggleWindow());
this.tray.on("right-click", () => this.tray.popUpContextMenu(this.contextMenu));
if (this.pressedIcon != null) {
this.tray.setPressedImage(this.pressedIcon);
}
if (this.contextMenu != null && !this.isDarwin()) {
this.tray.setContextMenu(this.contextMenu);
}
}
updateContextMenu() {
if (this.contextMenu != null && this.isLinux()) {
this.tray.setContextMenu(this.contextMenu);
}
}
private hideDock() {
app.dock.hide();
}
private showDock() {
app.dock.show();
}
private isDarwin() {
return process.platform === "darwin";
}
private isLinux() {
return process.platform === "linux";
}
private async toggleWindow() {
if (this.windowMain.win == null) {
if (this.isDarwin()) {
// On MacOS, closing the window via the red button destroys the BrowserWindow instance.
this.windowMain.createWindow().then(() => {
this.windowMain.win.show();
this.showDock();
});
}
return;
}
if (this.windowMain.win.isVisible()) {
this.windowMain.win.hide();
if (this.isDarwin() && !(await this.stateService.getAlwaysShowDock())) {
this.hideDock();
}
} else {
this.windowMain.win.show();
if (this.isDarwin()) {
this.showDock();
}
}
}
private closeWindow() {
this.windowMain.isQuitting = true;
if (this.windowMain.win != null) {
this.windowMain.win.close();
}
}
}

View File

@@ -0,0 +1,143 @@
import { dialog, shell } from "electron";
import log from "electron-log";
import { autoUpdater } from "electron-updater";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { isAppImage, isDev, isMacAppStore, isWindowsPortable, isWindowsStore } from "../utils";
import { WindowMain } from "./window.main";
const UpdaterCheckInitialDelay = 5 * 1000; // 5 seconds
const UpdaterCheckInterval = 12 * 60 * 60 * 1000; // 12 hours
export class UpdaterMain {
private doingUpdateCheck = false;
private doingUpdateCheckWithFeedback = false;
private canUpdate = false;
constructor(
private i18nService: I18nService,
private windowMain: WindowMain,
private projectName: string
) {
autoUpdater.logger = log;
const linuxCanUpdate = process.platform === "linux" && isAppImage();
const windowsCanUpdate =
process.platform === "win32" && !isWindowsStore() && !isWindowsPortable();
const macCanUpdate = process.platform === "darwin" && !isMacAppStore();
this.canUpdate =
process.env.ELECTRON_NO_UPDATER !== "1" &&
(linuxCanUpdate || windowsCanUpdate || macCanUpdate);
}
async init() {
global.setTimeout(async () => await this.checkForUpdate(), UpdaterCheckInitialDelay);
global.setInterval(async () => await this.checkForUpdate(), UpdaterCheckInterval);
autoUpdater.on("checking-for-update", () => {
this.doingUpdateCheck = true;
});
autoUpdater.on("update-available", async () => {
if (this.doingUpdateCheckWithFeedback) {
if (this.windowMain.win == null) {
this.reset();
return;
}
const result = await dialog.showMessageBox(this.windowMain.win, {
type: "info",
title:
this.i18nService.t(this.projectName) + " - " + this.i18nService.t("updateAvailable"),
message: this.i18nService.t("updateAvailable"),
detail: this.i18nService.t("updateAvailableDesc"),
buttons: [this.i18nService.t("yes"), this.i18nService.t("no")],
cancelId: 1,
defaultId: 0,
noLink: true,
});
if (result.response === 0) {
autoUpdater.downloadUpdate();
} else {
this.reset();
}
}
});
autoUpdater.on("update-not-available", () => {
if (this.doingUpdateCheckWithFeedback && this.windowMain.win != null) {
dialog.showMessageBox(this.windowMain.win, {
message: this.i18nService.t("noUpdatesAvailable"),
buttons: [this.i18nService.t("ok")],
defaultId: 0,
noLink: true,
});
}
this.reset();
});
autoUpdater.on("update-downloaded", async (info) => {
if (this.windowMain.win == null) {
return;
}
const result = await dialog.showMessageBox(this.windowMain.win, {
type: "info",
title: this.i18nService.t(this.projectName) + " - " + this.i18nService.t("restartToUpdate"),
message: this.i18nService.t("restartToUpdate"),
detail: this.i18nService.t("restartToUpdateDesc", info.version),
buttons: [this.i18nService.t("restart"), this.i18nService.t("later")],
cancelId: 1,
defaultId: 0,
noLink: true,
});
if (result.response === 0) {
// Quit and install have a different window logic, setting `isQuitting` just to be safe.
this.windowMain.isQuitting = true;
autoUpdater.quitAndInstall(true, true);
}
});
autoUpdater.on("error", (error) => {
if (this.doingUpdateCheckWithFeedback) {
dialog.showErrorBox(
this.i18nService.t("updateError"),
error == null ? this.i18nService.t("unknown") : (error.stack || error).toString()
);
}
this.reset();
});
}
async checkForUpdate(withFeedback = false) {
if (this.doingUpdateCheck || isDev()) {
return;
}
if (!this.canUpdate) {
if (withFeedback) {
shell.openExternal("https://github.com/bitwarden/clients/releases");
}
return;
}
this.doingUpdateCheckWithFeedback = withFeedback;
if (withFeedback) {
autoUpdater.autoDownload = false;
}
await autoUpdater.checkForUpdates();
}
private reset() {
autoUpdater.autoDownload = true;
this.doingUpdateCheck = false;
}
}

View File

@@ -0,0 +1,293 @@
import * as path from "path";
import * as url from "url";
import { app, BrowserWindow, screen } from "electron";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { cleanUserAgent, isDev, isMacAppStore, isSnapStore } from "../utils";
const mainWindowSizeKey = "mainWindowSize";
const WindowEventHandlingDelay = 100;
export class WindowMain {
win: BrowserWindow;
isQuitting = false;
private windowStateChangeTimer: NodeJS.Timer;
private windowStates: { [key: string]: any } = {};
private enableAlwaysOnTop = false;
constructor(
private stateService: StateService,
private logService: LogService,
private hideTitleBar = false,
private defaultWidth = 950,
private defaultHeight = 600,
private argvCallback: (argv: string[]) => void = null,
private createWindowCallback: (win: BrowserWindow) => void
) {}
init(): Promise<any> {
return new Promise<void>((resolve, reject) => {
try {
if (!isMacAppStore() && !isSnapStore()) {
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
return;
} else {
// eslint-disable-next-line
app.on("second-instance", (event, argv, workingDirectory) => {
// Someone tried to run a second instance, we should focus our window.
if (this.win != null) {
if (this.win.isMinimized() || !this.win.isVisible()) {
this.win.show();
}
this.win.focus();
}
if (process.platform === "win32" || process.platform === "linux") {
if (this.argvCallback != null) {
this.argvCallback(argv);
}
}
});
}
}
// This method will be called when Electron is shutting
// down the application.
app.on("before-quit", () => {
this.isQuitting = true;
});
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on("ready", async () => {
await this.createWindow();
resolve();
if (this.argvCallback != null) {
this.argvCallback(process.argv);
}
});
// Quit when all windows are closed.
app.on("window-all-closed", () => {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== "darwin" || this.isQuitting || isMacAppStore()) {
app.quit();
}
});
app.on("activate", async () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (this.win === null) {
await this.createWindow();
} else {
// Show the window when clicking on Dock icon
this.win.show();
}
});
} catch (e) {
// Catch Error
// throw e;
reject(e);
}
});
}
async createWindow(): Promise<void> {
this.windowStates[mainWindowSizeKey] = await this.getWindowState(
this.defaultWidth,
this.defaultHeight
);
this.enableAlwaysOnTop = await this.stateService.getEnableAlwaysOnTop();
// Create the browser window.
this.win = new BrowserWindow({
width: this.windowStates[mainWindowSizeKey].width,
height: this.windowStates[mainWindowSizeKey].height,
minWidth: 680,
minHeight: 500,
x: this.windowStates[mainWindowSizeKey].x,
y: this.windowStates[mainWindowSizeKey].y,
title: app.name,
icon: process.platform === "linux" ? path.join(__dirname, "/images/icon.png") : undefined,
titleBarStyle: this.hideTitleBar && process.platform === "darwin" ? "hiddenInset" : undefined,
show: false,
backgroundColor: "#fff",
alwaysOnTop: this.enableAlwaysOnTop,
webPreferences: {
spellcheck: false,
nodeIntegration: true,
backgroundThrottling: false,
contextIsolation: false,
},
});
if (this.windowStates[mainWindowSizeKey].isMaximized) {
this.win.maximize();
}
// Show it later since it might need to be maximized.
this.win.show();
// and load the index.html of the app.
this.win.loadURL(
url.format({
protocol: "file:",
pathname: path.join(__dirname, "/index.html"),
slashes: true,
}),
{
userAgent: cleanUserAgent(this.win.webContents.userAgent),
}
);
// Open the DevTools.
if (isDev()) {
this.win.webContents.openDevTools();
}
// Emitted when the window is closed.
this.win.on("closed", async () => {
await this.updateWindowState(mainWindowSizeKey, this.win);
// Dereference the window object, usually you would store window
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
this.win = null;
});
this.win.on("close", async () => {
await this.updateWindowState(mainWindowSizeKey, this.win);
});
this.win.on("maximize", async () => {
await this.updateWindowState(mainWindowSizeKey, this.win);
});
this.win.on("unmaximize", async () => {
await this.updateWindowState(mainWindowSizeKey, this.win);
});
this.win.on("resize", () => {
this.windowStateChangeHandler(mainWindowSizeKey, this.win);
});
this.win.on("move", () => {
this.windowStateChangeHandler(mainWindowSizeKey, this.win);
});
this.win.on("focus", () => {
this.win.webContents.send("messagingService", {
command: "windowIsFocused",
windowIsFocused: true,
});
});
if (this.createWindowCallback) {
this.createWindowCallback(this.win);
}
}
async toggleAlwaysOnTop() {
this.enableAlwaysOnTop = !this.win.isAlwaysOnTop();
this.win.setAlwaysOnTop(this.enableAlwaysOnTop);
await this.stateService.setEnableAlwaysOnTop(this.enableAlwaysOnTop);
}
private windowStateChangeHandler(configKey: string, win: BrowserWindow) {
global.clearTimeout(this.windowStateChangeTimer);
this.windowStateChangeTimer = global.setTimeout(async () => {
await this.updateWindowState(configKey, win);
}, WindowEventHandlingDelay);
}
private async updateWindowState(configKey: string, win: BrowserWindow) {
if (win == null) {
return;
}
try {
const bounds = win.getBounds();
if (this.windowStates[configKey] == null) {
this.windowStates[configKey] = await this.stateService.getWindow();
if (this.windowStates[configKey] == null) {
this.windowStates[configKey] = {};
}
}
this.windowStates[configKey].isMaximized = win.isMaximized();
this.windowStates[configKey].displayBounds = screen.getDisplayMatching(bounds).bounds;
if (!win.isMaximized() && !win.isMinimized() && !win.isFullScreen()) {
this.windowStates[configKey].x = bounds.x;
this.windowStates[configKey].y = bounds.y;
this.windowStates[configKey].width = bounds.width;
this.windowStates[configKey].height = bounds.height;
}
await this.stateService.setWindow(this.windowStates[configKey]);
} catch (e) {
this.logService.error(e);
}
}
private async getWindowState(defaultWidth: number, defaultHeight: number) {
const state = await this.stateService.getWindow();
const isValid = state != null && (this.stateHasBounds(state) || state.isMaximized);
let displayBounds: Electron.Rectangle = null;
if (!isValid) {
state.width = defaultWidth;
state.height = defaultHeight;
displayBounds = screen.getPrimaryDisplay().bounds;
} else if (this.stateHasBounds(state) && state.displayBounds) {
// Check if the display where the window was last open is still available
displayBounds = screen.getDisplayMatching(state.displayBounds).bounds;
if (
displayBounds.width !== state.displayBounds.width ||
displayBounds.height !== state.displayBounds.height ||
displayBounds.x !== state.displayBounds.x ||
displayBounds.y !== state.displayBounds.y
) {
state.x = undefined;
state.y = undefined;
displayBounds = screen.getPrimaryDisplay().bounds;
}
}
if (displayBounds != null) {
if (state.width > displayBounds.width && state.height > displayBounds.height) {
state.isMaximized = true;
}
if (state.width > displayBounds.width) {
state.width = displayBounds.width - 10;
}
if (state.height > displayBounds.height) {
state.height = displayBounds.height - 10;
}
}
return state;
}
private stateHasBounds(state: any): boolean {
return (
state != null &&
Number.isInteger(state.x) &&
Number.isInteger(state.y) &&
Number.isInteger(state.width) &&
state.width > 0 &&
Number.isInteger(state.height) &&
state.height > 0
);
}
}

View File

@@ -0,0 +1,73 @@
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { KeySuffixOptions } from "@bitwarden/common/enums/keySuffixOptions";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
import { CryptoService } from "@bitwarden/common/services/crypto.service";
export class ElectronCryptoService extends CryptoService {
constructor(
cryptoFunctionService: CryptoFunctionService,
encryptService: EncryptService,
platformUtilService: PlatformUtilsService,
logService: LogService,
stateService: StateService
) {
super(cryptoFunctionService, encryptService, platformUtilService, logService, stateService);
}
async hasKeyStored(keySuffix: KeySuffixOptions): Promise<boolean> {
await this.upgradeSecurelyStoredKey();
return super.hasKeyStored(keySuffix);
}
protected async storeKey(key: SymmetricCryptoKey, userId?: string) {
if (await this.shouldStoreKey(KeySuffixOptions.Auto, userId)) {
await this.stateService.setCryptoMasterKeyAuto(key.keyB64, { userId: userId });
} else {
this.clearStoredKey(KeySuffixOptions.Auto);
}
if (await this.shouldStoreKey(KeySuffixOptions.Biometric, userId)) {
await this.stateService.setCryptoMasterKeyBiometric(key.keyB64, { userId: userId });
} else {
this.clearStoredKey(KeySuffixOptions.Biometric);
}
}
protected async retrieveKeyFromStorage(keySuffix: KeySuffixOptions, userId?: string) {
await this.upgradeSecurelyStoredKey();
return super.retrieveKeyFromStorage(keySuffix, userId);
}
/**
* @deprecated 4 Jun 2021 This is temporary upgrade method to move from a single shared stored key to
* multiple, unique stored keys for each use, e.g. never logout vs. biometric authentication.
*/
private async upgradeSecurelyStoredKey() {
// attempt key upgrade, but if we fail just delete it. Keys will be stored property upon unlock anyway.
const key = await this.stateService.getCryptoMasterKeyB64();
if (key == null) {
return;
}
try {
if (await this.shouldStoreKey(KeySuffixOptions.Auto)) {
await this.stateService.setCryptoMasterKeyAuto(key);
}
if (await this.shouldStoreKey(KeySuffixOptions.Biometric)) {
await this.stateService.setCryptoMasterKeyBiometric(key);
}
} catch (e) {
this.logService.error(
`Encountered error while upgrading obsolete Bitwarden secure storage item:`
);
this.logService.error(e);
}
await this.stateService.setCryptoMasterKeyB64(null);
}
}

View File

@@ -0,0 +1,9 @@
import { ElectronLogService } from "./electron-log.service";
describe("ElectronLogService", () => {
it("sets dev based on electron method", () => {
process.env.ELECTRON_IS_DEV = "1";
const logService = new ElectronLogService();
expect(logService).toEqual(expect.objectContaining({ isDev: true }) as any);
});
});

View File

@@ -0,0 +1,45 @@
import * as path from "path";
import log from "electron-log";
import { LogLevelType } from "@bitwarden/common/enums/logLevelType";
import { ConsoleLogService as BaseLogService } from "@bitwarden/common/services/consoleLog.service";
import { isDev } from "../utils";
export class ElectronLogService extends BaseLogService {
constructor(protected filter: (level: LogLevelType) => boolean = null, logDir: string = null) {
super(isDev(), filter);
if (log.transports == null) {
return;
}
log.transports.file.level = "info";
if (logDir != null) {
log.transports.file.file = path.join(logDir, "app.log");
}
}
write(level: LogLevelType, message: string) {
if (this.filter != null && this.filter(level)) {
return;
}
switch (level) {
case LogLevelType.Debug:
log.debug(message);
break;
case LogLevelType.Info:
log.info(message);
break;
case LogLevelType.Warning:
log.warn(message);
break;
case LogLevelType.Error:
log.error(message);
break;
default:
break;
}
}
}

View File

@@ -0,0 +1,69 @@
import { app, dialog, ipcMain, Menu, MenuItem, nativeTheme, session } from "electron";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { ThemeType } from "@bitwarden/common/enums/themeType";
import { WindowMain } from "../main/window.main";
import { RendererMenuItem } from "../utils";
export class ElectronMainMessagingService implements MessagingService {
constructor(private windowMain: WindowMain, private onMessage: (message: any) => void) {
ipcMain.handle("appVersion", () => {
return app.getVersion();
});
ipcMain.handle("systemTheme", () => {
return nativeTheme.shouldUseDarkColors ? ThemeType.Dark : ThemeType.Light;
});
ipcMain.handle("showMessageBox", (event, options) => {
return dialog.showMessageBox(this.windowMain.win, options);
});
ipcMain.handle("openContextMenu", (event, options: { menu: RendererMenuItem[] }) => {
return new Promise((resolve) => {
const menu = new Menu();
options.menu.forEach((m, index) => {
menu.append(
new MenuItem({
label: m.label,
type: m.type,
click: () => {
resolve(index);
},
})
);
});
menu.popup({
window: windowMain.win,
callback: () => {
resolve(-1);
},
});
});
});
ipcMain.handle("windowVisible", () => {
return windowMain.win?.isVisible();
});
ipcMain.handle("getCookie", async (event, options) => {
return await session.defaultSession.cookies.get(options);
});
nativeTheme.on("updated", () => {
windowMain.win?.webContents.send(
"systemThemeUpdated",
nativeTheme.shouldUseDarkColors ? ThemeType.Dark : ThemeType.Light
);
});
}
send(subscriber: string, arg: any = {}) {
const message = Object.assign({}, { command: subscriber }, arg);
this.onMessage(message);
if (this.windowMain.win != null) {
this.windowMain.win.webContents.send("messagingService", message);
}
}
}

View File

@@ -0,0 +1,183 @@
import { clipboard, ipcRenderer, shell } from "electron";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { ClientType } from "@bitwarden/common/enums/clientType";
import { DeviceType } from "@bitwarden/common/enums/deviceType";
import { isDev, isMacAppStore } from "../utils";
export class ElectronPlatformUtilsService implements PlatformUtilsService {
private deviceCache: DeviceType = null;
constructor(
protected i18nService: I18nService,
private messagingService: MessagingService,
private clientType: ClientType.Desktop | ClientType.DirectoryConnector,
private stateService: StateService
) {}
getDevice(): DeviceType {
if (!this.deviceCache) {
switch (process.platform) {
case "win32":
this.deviceCache = DeviceType.WindowsDesktop;
break;
case "darwin":
this.deviceCache = DeviceType.MacOsDesktop;
break;
case "linux":
default:
this.deviceCache = DeviceType.LinuxDesktop;
break;
}
}
return this.deviceCache;
}
getDeviceString(): string {
const device = DeviceType[this.getDevice()].toLowerCase();
return device.replace("desktop", "");
}
getClientType() {
return this.clientType;
}
isFirefox(): boolean {
return false;
}
isChrome(): boolean {
return true;
}
isEdge(): boolean {
return false;
}
isOpera(): boolean {
return false;
}
isVivaldi(): boolean {
return false;
}
isSafari(): boolean {
return false;
}
isMacAppStore(): boolean {
return isMacAppStore();
}
isViewOpen(): Promise<boolean> {
return Promise.resolve(false);
}
launchUri(uri: string, options?: any): void {
shell.openExternal(uri);
}
getApplicationVersion(): Promise<string> {
return ipcRenderer.invoke("appVersion");
}
// Temporarily restricted to only Windows until https://github.com/electron/electron/pull/28349
// has been merged and an updated electron build is available.
supportsWebAuthn(win: Window): boolean {
return process.platform === "win32";
}
supportsDuo(): boolean {
return true;
}
showToast(
type: "error" | "success" | "warning" | "info",
title: string,
text: string | string[],
options?: any
): void {
this.messagingService.send("showToast", {
text: text,
title: title,
type: type,
options: options,
});
}
async showDialog(
text: string,
title?: string,
confirmText?: string,
cancelText?: string,
type?: string
): Promise<boolean> {
const buttons = [confirmText == null ? this.i18nService.t("ok") : confirmText];
if (cancelText != null) {
buttons.push(cancelText);
}
const result = await ipcRenderer.invoke("showMessageBox", {
type: type,
title: title,
message: title,
detail: text,
buttons: buttons,
cancelId: buttons.length === 2 ? 1 : null,
defaultId: 0,
noLink: true,
});
return Promise.resolve(result.response === 0);
}
isDev(): boolean {
return isDev();
}
isSelfHost(): boolean {
return false;
}
copyToClipboard(text: string, options?: any): void {
const type = options ? options.type : null;
const clearing = options ? !!options.clearing : false;
const clearMs: number = options && options.clearMs ? options.clearMs : null;
clipboard.writeText(text, type);
if (!clearing) {
this.messagingService.send("copiedToClipboard", {
clipboardValue: text,
clearMs: clearMs,
type: type,
clearing: clearing,
});
}
}
readFromClipboard(options?: any): Promise<string> {
const type = options ? options.type : null;
return Promise.resolve(clipboard.readText(type));
}
async supportsBiometric(): Promise<boolean> {
return await this.stateService.getEnableBiometric();
}
async authenticateBiometric(): Promise<boolean> {
const val = await ipcRenderer.invoke("biometric", {
action: "authenticate",
});
return val;
}
supportsSecureStorage(): boolean {
return true;
}
}

View File

@@ -0,0 +1,26 @@
import { ipcRenderer } from "electron";
import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
export class ElectronRendererMessagingService implements MessagingService {
constructor(private broadcasterService: BroadcasterService) {
ipcRenderer.on("messagingService", async (event: any, message: any) => {
if (message.command) {
this.sendMessage(message.command, message, false);
}
});
}
send(subscriber: string, arg: any = {}) {
this.sendMessage(subscriber, arg, true);
}
private sendMessage(subscriber: string, arg: any = {}, toMain: boolean) {
const message = Object.assign({}, { command: subscriber }, arg);
this.broadcasterService.send(message);
if (toMain) {
ipcRenderer.send("messagingService", message);
}
}
}

View File

@@ -0,0 +1,41 @@
import { ipcRenderer } from "electron";
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
import { StorageOptions } from "@bitwarden/common/models/domain/storage-options";
export class ElectronRendererSecureStorageService implements AbstractStorageService {
async get<T>(key: string, options?: StorageOptions): Promise<T> {
const val = await ipcRenderer.invoke("keytar", {
action: "getPassword",
key: key,
keySuffix: options?.keySuffix ?? "",
});
return val != null ? (JSON.parse(val) as T) : null;
}
async has(key: string, options?: StorageOptions): Promise<boolean> {
const val = await ipcRenderer.invoke("keytar", {
action: "hasPassword",
key: key,
keySuffix: options?.keySuffix ?? "",
});
return !!val;
}
async save(key: string, obj: any, options?: StorageOptions): Promise<any> {
await ipcRenderer.invoke("keytar", {
action: "setPassword",
key: key,
keySuffix: options?.keySuffix ?? "",
value: JSON.stringify(obj),
});
}
async remove(key: string, options?: StorageOptions): Promise<any> {
await ipcRenderer.invoke("keytar", {
action: "deletePassword",
key: key,
keySuffix: options?.keySuffix ?? "",
});
}
}

View File

@@ -0,0 +1,34 @@
import { ipcRenderer } from "electron";
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
export class ElectronRendererStorageService implements AbstractStorageService {
get<T>(key: string): Promise<T> {
return ipcRenderer.invoke("storageService", {
action: "get",
key: key,
});
}
has(key: string): Promise<boolean> {
return ipcRenderer.invoke("storageService", {
action: "has",
key: key,
});
}
save(key: string, obj: any): Promise<any> {
return ipcRenderer.invoke("storageService", {
action: "save",
key: key,
obj: obj,
});
}
remove(key: string): Promise<any> {
return ipcRenderer.invoke("storageService", {
action: "remove",
key: key,
});
}
}

View File

@@ -0,0 +1,85 @@
import * as fs from "fs";
import { ipcMain } from "electron";
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
import { NodeUtils } from "@bitwarden/common/misc/nodeUtils";
// See: https://github.com/sindresorhus/electron-store/blob/main/index.d.ts
interface ElectronStoreOptions {
defaults: unknown;
name: string;
}
type ElectronStoreConstructor = new (options: ElectronStoreOptions) => ElectronStore;
// eslint-disable-next-line
const Store: ElectronStoreConstructor = require("electron-store");
interface ElectronStore {
get: (key: string) => unknown;
set: (key: string, obj: unknown) => void;
delete: (key: string) => void;
}
interface BaseOptions<T extends string> {
action: T;
key: string;
}
interface SaveOptions extends BaseOptions<"save"> {
obj: unknown;
}
type Options = BaseOptions<"get"> | BaseOptions<"has"> | SaveOptions | BaseOptions<"remove">;
export class ElectronStorageService implements AbstractStorageService {
private store: ElectronStore;
constructor(dir: string, defaults = {}) {
if (!fs.existsSync(dir)) {
NodeUtils.mkdirpSync(dir, "700");
}
const storeConfig: ElectronStoreOptions = {
defaults: defaults,
name: "data",
};
this.store = new Store(storeConfig);
ipcMain.handle("storageService", (event, options: Options) => {
switch (options.action) {
case "get":
return this.get(options.key);
case "has":
return this.has(options.key);
case "save":
return this.save(options.key, options.obj);
case "remove":
return this.remove(options.key);
}
});
}
get<T>(key: string): Promise<T> {
const val = this.store.get(key) as T;
return Promise.resolve(val != null ? val : null);
}
has(key: string): Promise<boolean> {
const val = this.store.get(key);
return Promise.resolve(val != null);
}
save(key: string, obj: unknown): Promise<void> {
if (obj instanceof Set) {
obj = Array.from(obj);
}
this.store.set(key, obj);
return Promise.resolve();
}
remove(key: string): Promise<void> {
this.store.delete(key);
return Promise.resolve();
}
}

View File

@@ -0,0 +1,27 @@
import { cleanUserAgent } from "./utils";
const expectedUserAgent = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${process.versions.chrome} Safari/537.36`;
describe("cleanUserAgent", () => {
it("cleans mac agent", () => {
const initialMacAgent = `Mozilla/5.0 (Macintosh; Intel Mac OS X 11_6_0) AppleWebKit/537.36 (KHTML, like Gecko) Bitwarden/${process.version} Chrome/${process.versions.chrome} Electron/${process.versions.electron} Safari/537.36`;
expect(cleanUserAgent(initialMacAgent)).toEqual(expectedUserAgent);
});
it("cleans windows agent", () => {
const initialWindowsAgent = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Bitwarden/${process.version} Chrome/${process.versions.chrome} Electron/${process.versions.electron} Safari/537.36`;
expect(cleanUserAgent(initialWindowsAgent)).toEqual(expectedUserAgent);
});
it("cleans linux agent", () => {
const initialWindowsAgent = `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Bitwarden/${process.version} Chrome/${process.versions.chrome} Electron/${process.versions.electron} Safari/537.36`;
expect(cleanUserAgent(initialWindowsAgent)).toEqual(expectedUserAgent);
});
it("does not change version numbers", () => {
const expected = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36`;
const initialAgent = `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Bitwarden/1.28.3 Chrome/87.0.4280.141 Electron/11.4.5 Safari/537.36`;
expect(cleanUserAgent(initialAgent)).toEqual(expected);
});
});

80
apps/desktop/src/utils.ts Normal file
View File

@@ -0,0 +1,80 @@
import { ipcRenderer } from "electron";
export type RendererMenuItem = {
label?: string;
type?: "normal" | "separator" | "submenu" | "checkbox" | "radio";
click?: () => any;
};
export function invokeMenu(menu: RendererMenuItem[]) {
const menuWithoutClick = menu.map((m) => {
return { label: m.label, type: m.type };
});
ipcRenderer.invoke("openContextMenu", { menu: menuWithoutClick }).then((i: number) => {
if (i !== -1) {
menu[i].click();
}
});
}
export function isDev() {
// ref: https://github.com/sindresorhus/electron-is-dev
if ("ELECTRON_IS_DEV" in process.env) {
return parseInt(process.env.ELECTRON_IS_DEV, 10) === 1;
}
return process.defaultApp || /node_modules[\\/]electron[\\/]/.test(process.execPath);
}
export function isAppImage() {
return process.platform === "linux" && "APPIMAGE" in process.env;
}
export function isMac() {
return process.platform === "darwin";
}
export function isMacAppStore() {
return isMac() && process.mas === true;
}
export function isWindowsStore() {
const isWindows = process.platform === "win32";
let windowsStore = process.windowsStore;
if (
isWindows &&
!windowsStore &&
process.resourcesPath.indexOf("8bitSolutionsLLC.bitwardendesktop_") > -1
) {
windowsStore = true;
}
return isWindows && windowsStore === true;
}
export function isSnapStore() {
return process.platform === "linux" && process.env.SNAP_USER_DATA != null;
}
export function isWindowsPortable() {
return process.platform === "win32" && process.env.PORTABLE_EXECUTABLE_DIR != null;
}
/**
* Sanitize user agent so external resources used by the app can't built data on our users.
*/
export function cleanUserAgent(userAgent: string): string {
const userAgentItem = (startString: string, endString: string) => {
const startIndex = userAgent.indexOf(startString);
return userAgent.substring(startIndex, userAgent.indexOf(endString, startIndex) + 1);
};
const systemInformation = "(Windows NT 10.0; Win64; x64)";
// Set system information, remove bitwarden, and electron information
return userAgent
.replace(userAgentItem("(", ")"), systemInformation)
.replace(userAgentItem("Bitwarden", " "), "")
.replace(userAgentItem("Electron", " "), "");
}
export async function getCookie(url: string, name: string): Promise<Electron.Cookie[]> {
return await ipcRenderer.invoke("getCookie", { url: url, name: name });
}

View File

@@ -11,8 +11,7 @@
"baseUrl": ".",
"paths": {
"@bitwarden/common/*": ["../../libs/common/src/*"],
"@bitwarden/angular/*": ["../../libs/angular/src/*"],
"@bitwarden/electron/*": ["../../libs/electron/src/*"]
"@bitwarden/angular/*": ["../../libs/angular/src/*"]
}
},
"angularCompilerOptions": {