From 7cfa38e344138f98fbee17a9fea69e6f67c303ec Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 12 Oct 2023 11:50:17 +0200 Subject: [PATCH] [PM-3996] Scaffolding for preload script (#6065) This PR wires up a polyfill for window.ipc which allows us to progressively migrate the codebase to a format which supports context bridge. This avoids a big bang effort where every non sandboxed call has to be migrated before we can run the code. Once all calls to node modules are removed from the renderer and only exists in preload.ts. We will turn on context isolation and use the context bridge for communication instead. --- apps/desktop/package.json | 4 +- apps/desktop/scripts/start.js | 5 ++ .../src/app/accounts/settings.component.ts | 3 +- apps/desktop/src/app/app.component.ts | 3 +- apps/desktop/src/app/main.ts | 4 ++ apps/desktop/src/global.d.ts | 1 + apps/desktop/src/main/window.main.ts | 1 + apps/desktop/src/platform/preload.ts | 26 ++++++++ .../electron-platform-utils.service.ts | 27 ++------ apps/desktop/src/preload.ts | 19 ++++++ apps/desktop/webpack.main.js | 1 - apps/desktop/webpack.preload.js | 63 +++++++++++++++++++ apps/desktop/webpack.renderer.js | 2 + 13 files changed, 131 insertions(+), 28 deletions(-) create mode 100644 apps/desktop/src/platform/preload.ts create mode 100644 apps/desktop/src/preload.ts create mode 100644 apps/desktop/webpack.preload.js diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 7906190e2be..68d2cc0f87f 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -19,8 +19,10 @@ "postinstall": "electron-rebuild", "start": "cross-env ELECTRON_IS_DEV=0 ELECTRON_NO_UPDATER=1 electron ./build", "build-native": "cd desktop_native && npm run build", - "build": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\"", + "build": "concurrently -n Main,Rend,Prel -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\" \"npm run build:preload\"", "build:dev": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main:dev\" \"npm run build:renderer:dev\"", + "build:preload": "cross-env NODE_ENV=production webpack --config webpack.preload.js", + "build:preload:watch": "cross-env NODE_ENV=production webpack --config webpack.preload.js --watch", "build:main": "cross-env NODE_ENV=production webpack --config webpack.main.js", "build:main:dev": "npm run build-native && cross-env NODE_ENV=development webpack --config webpack.main.js", "build:main:watch": "npm run build-native && cross-env NODE_ENV=development webpack --config webpack.main.js --watch", diff --git a/apps/desktop/scripts/start.js b/apps/desktop/scripts/start.js index 19b11125061..388bf09405c 100644 --- a/apps/desktop/scripts/start.js +++ b/apps/desktop/scripts/start.js @@ -13,6 +13,11 @@ concurrently( command: "npm run build:main:watch", prefixColor: "yellow", }, + { + name: "Prel", + command: "npm run build:preload:watch", + prefixColor: "magenta", + }, { name: "Rend", command: "npm run build:renderer:watch", diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index 7ccf4aca770..a2a9e71a32b 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -21,7 +21,6 @@ import { DialogService } from "@bitwarden/components"; import { flagEnabled } from "../../platform/flags"; import { ElectronStateService } from "../../platform/services/electron-state.service.abstraction"; -import { isWindowsStore } from "../../utils"; import { SetPinComponent } from "../components/set-pin.component"; @Component({ selector: "app-settings", @@ -589,7 +588,7 @@ export class SettingsComponent implements OnInit { this.form.controls.enableBrowserIntegration.setValue(false); return; - } else if (isWindowsStore()) { + } else if (ipc.platform.isWindowsStore) { await this.dialogService.openSimpleDialog({ title: { key: "browserIntegrationUnsupportedTitle" }, content: { key: "browserIntegrationWindowsStoreDesc" }, diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index 34262c3a309..65ac83b59fd 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -10,7 +10,6 @@ import { } from "@angular/core"; import { DomSanitizer } from "@angular/platform-browser"; import { Router } from "@angular/router"; -import { ipcRenderer } from "electron"; import { IndividualConfig, ToastrService } from "ngx-toastr"; import { firstValueFrom, Subject, takeUntil } from "rxjs"; @@ -227,7 +226,7 @@ export class AppComponent implements OnInit, OnDestroy { this.systemService.cancelProcessReload(); break; case "reloadProcess": - ipcRenderer.send("reload-process"); + ipc.platform.reloadProcess(); break; case "syncStarted": break; diff --git a/apps/desktop/src/app/main.ts b/apps/desktop/src/app/main.ts index 7d99e48ea22..c22d4eb9e10 100644 --- a/apps/desktop/src/app/main.ts +++ b/apps/desktop/src/app/main.ts @@ -1,8 +1,12 @@ import { enableProdMode } from "@angular/core"; import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; +import { ipc } from "../preload"; import { isDev } from "../utils"; +// Temporary polyfill for preload script +(window as any).ipc = ipc; + require("../scss/styles.scss"); require("../scss/tailwind.css"); diff --git a/apps/desktop/src/global.d.ts b/apps/desktop/src/global.d.ts index 1b85bb1b6b1..4d103b2cdef 100644 --- a/apps/desktop/src/global.d.ts +++ b/apps/desktop/src/global.d.ts @@ -1 +1,2 @@ declare module "forcefocus"; +declare const ipc: typeof import("./preload").ipc; diff --git a/apps/desktop/src/main/window.main.ts b/apps/desktop/src/main/window.main.ts index 4ea28c74c5f..e326b9e9e1d 100644 --- a/apps/desktop/src/main/window.main.ts +++ b/apps/desktop/src/main/window.main.ts @@ -143,6 +143,7 @@ export class WindowMain { backgroundColor: await this.getBackgroundColor(), alwaysOnTop: this.enableAlwaysOnTop, webPreferences: { + // preload: path.join(__dirname, "preload.js"), spellcheck: false, nodeIntegration: true, backgroundThrottling: false, diff --git a/apps/desktop/src/platform/preload.ts b/apps/desktop/src/platform/preload.ts new file mode 100644 index 00000000000..b8aed8f65d1 --- /dev/null +++ b/apps/desktop/src/platform/preload.ts @@ -0,0 +1,26 @@ +import { ipcRenderer } from "electron"; + +import { DeviceType } from "@bitwarden/common/enums/device-type.enum"; + +import { isDev, isWindowsStore } from "../utils"; + +export default { + versions: { + app: (): Promise => ipcRenderer.invoke("appVersion"), + }, + deviceType: deviceType(), + isDev: isDev(), + isWindowsStore: isWindowsStore(), + reloadProcess: () => ipcRenderer.send("reload-process"), +}; + +function deviceType(): DeviceType { + switch (process.platform) { + case "win32": + return DeviceType.WindowsDesktop; + case "darwin": + return DeviceType.MacOsDesktop; + default: + return DeviceType.LinuxDesktop; + } +} diff --git a/apps/desktop/src/platform/services/electron-platform-utils.service.ts b/apps/desktop/src/platform/services/electron-platform-utils.service.ts index dbc35de9311..6c99507b12b 100644 --- a/apps/desktop/src/platform/services/electron-platform-utils.service.ts +++ b/apps/desktop/src/platform/services/electron-platform-utils.service.ts @@ -9,31 +9,14 @@ import { } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { BiometricMessage, BiometricStorageAction } from "../../types/biometric-message"; -import { isDev, isMacAppStore } from "../../utils"; +import { isMacAppStore } from "../../utils"; import { ClipboardWriteMessage } from "../types/clipboard"; export class ElectronPlatformUtilsService implements PlatformUtilsService { - private deviceCache: DeviceType = null; - constructor(protected i18nService: I18nService, private messagingService: MessagingService) {} 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; + return ipc.platform.deviceType; } getDeviceString(): string { @@ -82,7 +65,7 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService { } getApplicationVersion(): Promise { - return ipcRenderer.invoke("appVersion"); + return ipc.platform.versions.app(); } async getApplicationVersionNumber(): Promise { @@ -92,7 +75,7 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService { // 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"; + return this.getDevice() === DeviceType.WindowsDesktop; } supportsDuo(): boolean { @@ -114,7 +97,7 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService { } isDev(): boolean { - return isDev(); + return ipc.platform.isDev; } isSelfHost(): boolean { diff --git a/apps/desktop/src/preload.ts b/apps/desktop/src/preload.ts new file mode 100644 index 00000000000..a6ddfdefca5 --- /dev/null +++ b/apps/desktop/src/preload.ts @@ -0,0 +1,19 @@ +// import { contextBridge } from "electron"; +import platform from "./platform/preload"; + +/** + * Bitwarden Preload script. + * + * This file contains the "glue" between the main process and the renderer process. Please ensure + * that you have read through the following articles before modifying any preload script. + * + * https://www.electronjs.org/docs/latest/tutorial/tutorial-preload + * https://www.electronjs.org/docs/latest/api/context-bridge + */ + +// Each team owns a subspace of the `ipc` global variable in the renderer. +export const ipc = { + platform, +}; + +// contextBridge.exposeInMainWorld("ipc", ipc); diff --git a/apps/desktop/webpack.main.js b/apps/desktop/webpack.main.js index efd1de20d13..59e043fa12e 100644 --- a/apps/desktop/webpack.main.js +++ b/apps/desktop/webpack.main.js @@ -67,7 +67,6 @@ const main = { ], }, plugins: [ - new CleanWebpackPlugin(), new CopyWebpackPlugin({ patterns: [ "./src/package.json", diff --git a/apps/desktop/webpack.preload.js b/apps/desktop/webpack.preload.js new file mode 100644 index 00000000000..721d0567ca4 --- /dev/null +++ b/apps/desktop/webpack.preload.js @@ -0,0 +1,63 @@ +const path = require("path"); +const { merge } = require("webpack-merge"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const { CleanWebpackPlugin } = require("clean-webpack-plugin"); +const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); +const configurator = require("./config/config"); +const { EnvironmentPlugin } = require("webpack"); + +const NODE_ENV = process.env.NODE_ENV == null ? "development" : process.env.NODE_ENV; + +console.log("Preload process config"); +const envConfig = configurator.load(NODE_ENV); +configurator.log(envConfig); + +const common = { + module: { + rules: [ + { + test: /\.tsx?$/, + use: "ts-loader", + exclude: /node_modules\/(?!(@bitwarden)\/).*/, + }, + ], + }, + plugins: [], + resolve: { + extensions: [".tsx", ".ts", ".js"], + plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json" })], + }, +}; + +const prod = { + output: { + filename: "[name].js", + path: path.resolve(__dirname, "build"), + }, +}; + +const dev = { + output: { + filename: "[name].js", + path: path.resolve(__dirname, "build"), + devtoolModuleFilenameTemplate: "[absolute-resource-path]", + }, + devtool: "cheap-source-map", +}; + +const main = { + mode: NODE_ENV, + target: "electron-preload", + node: { + __dirname: false, + __filename: false, + }, + entry: { + preload: "./src/preload.ts", + }, + optimization: { + minimize: false, + }, +}; + +module.exports = merge(common, NODE_ENV === "development" ? dev : prod, main); diff --git a/apps/desktop/webpack.renderer.js b/apps/desktop/webpack.renderer.js index 64eef5729f6..ea1c350c389 100644 --- a/apps/desktop/webpack.renderer.js +++ b/apps/desktop/webpack.renderer.js @@ -62,6 +62,8 @@ const common = { const renderer = { mode: NODE_ENV, devtool: "source-map", + // TODO: Replace this with web. + // target: "web", target: "electron-renderer", node: { __dirname: false,