Почему contextBridge не импортируется в мое приложение Electron?

#node.js #reactjs #typescript #webpack #electron

#node.js #reactjs #машинопись #webpack #electron

Вопрос:

Я пишу приложение Electron с React и Typescript, используя Webpack, Babel и ESLint, но у меня возникли проблемы с настройкой:

   mainWindow = new BrowserWindow({
    title: "Biomech",
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      enableRemoteModule: false,
      sandbox: true,
      preload: path.join(__dirname, "./preload.js"),
      nativeWindowOpen: true,
    },
  });
 

что я и хочу в качестве меры безопасности.

Причина в том, что, если я установлю webPreferences, как указано выше, мне нужно использовать contextBridge и сценарий предварительной загрузки, который связывает функции, использующие IPC, с window . И проблема в том, что contextBridge он неправильно импортируется в мой файл preload.ts:

 import { contextBridge, ipcRenderer } from "electron";
import { readFileSync } from 'fs';
import { History } from 'history';
import { LIST_DRIVE_FILES_CHANNEL, LOAD_PREV_TEST_RESULTS_CHANNEL, OAUTH2_ACCESS_TOKEN_REQUEST_CHANNEL } from "../src/constants/ipcChannels";
import { VISUALIZE_RESULTS_PATH } from "../src/constants/urls";


// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
    "api", {
        exchangeCodeForAccessToken: (code: string) => {
            ipcRenderer.invoke(OAUTH2_ACCESS_TOKEN_REQUEST_CHANNEL, code);
        },
        listDriveFiles: (accessToken: string) => {
            ipcRenderer.invoke(LIST_DRIVE_FILES_CHANNEL, accessToken);
        },
        openDirectoryDialog: (history: History) => {
            ipcRenderer.invoke(LOAD_PREV_TEST_RESULTS_CHANNEL).then((dialog: any) => {
                if (dialog.canceled) {  // canceled with one l is correct
                    return;
                } else {
                    const selectedDirectory = dialog.filePaths[0];
                    console.log(readFileSync(selectedDirectory   "/README.md", 'utf-8'));
                    history.push(VISUALIZE_RESULTS_PATH);
                }
            })
        }
    }
);
 

На мой взгляд, он используется правильно, как примеры, которые я видел здесь, в SO, или в электронных документах. Но когда я запускаю свой основной сценарий процесса: npm run dev:electron который указан в моем package.json:

 {
  "name": "electron-react-ts-app",
  "version": "1.0.0",
  "description": "Electron   React   Typescript",
  "main": "./dist/main.js",
  "preload": "./dist/preload.js",
  "scripts": {
    "dev": "concurrently --success first "npm run dev:electron" "npm run dev:react" -k",
    "dev:electron": "NODE_ENV=development webpack --config webpack.electron.config.js --mode development amp;amp; electron .",
    "dev:react": "NODE_ENV=development webpack serve --config webpack.react.config.js --mode development",
    "build:electron": "NODE_ENV=production webpack --config webpack.electron.config.js --mode production",
    "build:react": "NODE_ENV=production webpack --config webpack.react.config.js --mode production",
    "build": "npm run build:electron amp;amp; npm run build:react",
    "pack": "electron-builder --dir",
    "dist": "electron-builder",
    "lint": "eslint .",
    "format": "prettier --write "**/*. (js|jsx|json|yml|yaml|css|md|vue)""
  },
  "keywords": [],
  "license": "MIT",
  "build": {
    "files": [
      "dist/",
      "node_modules/",
      "package.json"
    ],
    "productName": "Example",
    "appId": "com.example.app",
    "directories": {
      "output": "dist"
    }
  },
  "devDependencies": {
    "@babel/preset-env": "^7.9.5",
    "@babel/preset-react": "^7.9.4",
    "@babel/preset-typescript": "^7.9.0",
    "@types/electron-devtools-installer": "^2.2.0",
    "@types/react-router-dom": "^5.1.7",
    "@types/regenerator-runtime": "^0.13.0",
    "dpdm": "^3.6.0",
    "electron": "^11.2.1",
    "electron-builder": "^22.7.0",
    "electron-devtools-installer": "^3.1.1",
    "eslint": "^7.18.0",
    "file-loader": "^6.2.0",
    "html-webpack-plugin": "^4.5.1",
    "husky": "^4.3.8",
    "lint-staged": "^10.5.3",
    "prettier": "^2.2.1",
    "react-router-dom": "^5.2.0",
    "webpack": "^5.11.1",
    "webpack-cli": "^4.3.1",
    "webpack-dev-server": "^3.11.1"
  },
  "dependencies": {
    "@babel/core": "^7.12.10",
    "@popperjs/core": "^2.6.0",
    "@types/node": "^14.14.22",
    "@types/react": "^17.0.0",
    "@types/react-dom": "^17.0.0",
    "axios": "^0.21.1",
    "babel-loader": "^8.2.2",
    "bootstrap": "^4.5.3",
    "chokidar": "^3.5.1",
    "core-js": "^3.8.3",
    "css-loader": "^5.0.1",
    "electron-fetch": "^1.7.3",
    "fsevents": "^2.3.1",
    "ini": "^2.0.0",
    "jquery": "^3.5.1",
    "react": "^17.0.1",
    "react-bootstrap": "^1.4.0",
    "react-dom": "^17.0.1",
    "react-google-login": "^5.2.2",
    "style-loader": "^2.0.0"
  },
  "husky": {
    "hooks": {
      "pre-commit": "npm run lint amp;amp; npm run format"
    }
  },
  "lint-staged": {
    "*. (js|jsx)": "eslint --fix",
    "*. (json|css|md)": "prettier --write"
  }
}
 

Я получаю следующую ошибку: не удается прочитать свойство ‘exposeInMainWorld’ неопределенного

 webpack 5.19.0 compiled successfully in 1793 ms
App threw an error during load
TypeError: Cannot read property 'exposeInMainWorld' of undefined
    at Object../electron/preload.ts (/Users/lucas_sg/Documents/ITBA/PF/pf-biomech/dist/preload.js:24:53)
    at __webpack_require__ (/Users/lucas_sg/Documents/ITBA/PF/pf-biomech/dist/preload.js:128:41)
    at /Users/lucas_sg/Documents/ITBA/PF/pf-biomech/dist/preload.js:252:11
    at Object.<anonymous> (/Users/lucas_sg/Documents/ITBA/PF/pf-biomech/dist/preload.js:254:12)
    at Module._compile (internal/modules/cjs/loader.js:1152:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1173:10)
    at Module.load (internal/modules/cjs/loader.js:992:32)
    at Module._load (internal/modules/cjs/loader.js:885:14)
    at Function.f._load (electron/js2c/asar_bundle.js:5:12738)
    at Module.require (internal/modules/cjs/loader.js:1032:19)
 

Я проверил это репозиторий, касающийся безопасности Electron, и не похоже, что он делает что-то слишком по-другому, по крайней мере, на первый взгляд, но я явно что-то напутал.

Вот моя конфигурация webpack (webpack.electron.config.js ) на случай, если это будет полезно:

 const path = require("path");

module.exports = [
  {
    resolve: {
      extensions: [".tsx", ".ts", ".js"],
    },
    devtool: "source-map",
    entry: {
        main: {
            import: "./electron/main.ts",
            dependOn: "preload"
        },
        preload: "./electron/preload.ts"
    },
    output: {
      path: path.resolve(__dirname, "dist"),
      filename: "[name].js",
    },
    target: "electron-main",
    module: {
      rules: [
        {
          test: /.(js|ts|tsx)$/,
          exclude: /node_modules/,
          use: {
            loader: "babel-loader",
          },
        },
        {
          test: /.css$/,
          use: ["style-loader", "css-loader"],
        },
      ],
    },
    node: {
      __dirname: false,
    },
  },
];
 

Ответ №1:

Я думаю, что проблема заключается в вашем файле webpack.config:

 entry: {
    main: {
        import: "./electron/main.ts",
        dependOn: "preload"
    },
    preload: "./electron/preload.ts"
},
 

Возможно, webpack пытается разрешить всю зависимость «preload.ts» во время компиляции, а также пытается объединить ее с файлом main.ts. И в таком случае он никогда не получит доступ к contextBridge. preload.js файл должен запускаться только в отдельном «контексте» — «Изолированном мире», как они упоминаются в документах.

Что бы я попробовал, так это:

  1. удалите строки dependOn: и preload: из вашей конфигурации webpack.
  2. преобразовать файл preload.ts в JS (т.е. в формат CommonJS) — попробуйте сделать это с помощью TS CLI на данный момент

Ваш preload.js файл должен выглядеть примерно так:

 const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld(
  'electron',
  {
    doThing: () => ipcRenderer.send('do-a-thing')
  }
)
 
  1. копировать preload.js к __dirname (в моем случае это был /dist)
  2. запустите electron еще раз

Это был единственный способ получить доступ к объекту «contextBridge».