0

我做了第一次尝试,想使用角度渲染器进程中的电子 API。我按照中的说明进行操作

创建 Angular -Electron 应用程序的指令

所以在我的 main.js 文件中我添加了:

const {app, BrowserWindow, ipcMain} = require('electron')

我还添加了

function openModal(){
  const { BrowserWindow } = require('electron');
  let modal = new BrowserWindow({ parent: mainWindow, modal: true, show: false })
  modal.loadURL('https://www.sitepoint.com')
  modal.once('ready-to-show', () => {
    modal.show()
  })
}

ipcMain.on('openModal', (event, arg) => {
  openModal()
})

在我的 app.component.ts 文件中添加了导入import { IpcRenderer } from 'electron';

我添加了以下构造函数

  private ipc: IpcRenderer
  constructor(){
    if ((<any>window).require) {
      try {
        this.ipc = (<any>window).require('electron').ipcRenderer;
      } catch (e) {
        throw e;
      }
    } else {
      console.warn('App not running inside Electron!');
    }
  }

由于我的 CLI 并不完全清楚 icp 将是 IpcRenderer 类型,因此我在这一行中添加了

private ipc: IpcRenderer | any;

带功能

  openModal(){
    console.log("Open a modal");
    this.ipc.send("openModal");
  }

它应该能够向“主”进程发送一些东西。但是,如果我调用该函数,我会收到错误

TypeError:无法读取未定义的属性“发送”

我做错了什么?

4

1 回答 1

0

我也遇到过同样的问题,但发现了一个很长的问题。

原因

看看这一行:

(<any>window).require

此行搜索require()全局窗口对象中的方法,这是一个 NodeJS 函数,因此如果您使用 Electron 版本 > 5,则不会在运行时注入,因为webPreferences -> nodeIntegrationBrowserWindow 类的属性默认设置为 false。

解决方案

有两种解决方案:

  1. 设置nodeIntegration: true并且contextIsolation: false 该应用程序将按预期工作此黑客存在安全问题。正如这个答案中所引用的:

Electron 应用程序很棒,因为我们可以使用节点,但这种力量是一把双刃剑。如果我们不小心,我们会通过我们的应用程序让某人访问节点,并且使用节点的不良行为者可能会损坏您的机器或删除您的操作系统文件(除其他外,我想)。

  1. 使用contextBridge和 preload.js:这是从上面的答案和这个问题的其他答案中得出的。

我将介绍一个使用 Angular 的解决方法。

在main.js中webPreferences创建的mainWindow时候,进行如下修改:

webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      enableRemoteModule: false,
      preload: path.join(__dirname, "preload.js")
    }

在相同的目录中main.js,创建一个preload.js文件并在其中添加以下代码(您可以省略receive()该用例的声明,因为它不是必需的)。

const {
    contextBridge,
    ipcRenderer
} = require("electron");

// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
    "api", {
        send: (channel, data) => {
            // whitelist channels
            let validChannels = ["openModal"];
            if (validChannels.includes(channel)) {
                ipcRenderer.send(channel, data);
            }
        },
        receive: (channel, func) => {
            let validChannels = ["fromMain"];
            if (validChannels.includes(channel)) {
                // Deliberately strip event as it includes `sender` 
                ipcRenderer.on(channel, (event, ...args) => func(...args));
            }
        }
    }
);

PS:添加此代码的原因已在参考答案中提到,因此我不会在这里重复。

现在,在运行时,电子将在全局对象中注入window.api.send()和方法。如果您尝试直接在 Angular 组件中访问它们,linter 将给出错误,因为它们未在标准窗口对象上定义。为了解决这个问题,我们将创建一个 Angular 服务并注入我们的应用程序。该服务将引用在运行时创建的浏览器的窗口对象,并添加了上述两个功能。window.api.receive()window

创建一个WindowRefService并添加以下代码:

窗口参考服务.ts

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})

export class WindowRefService {

  getWindow(): any {
    return window;
  }
  constructor() { }
  get nativeWindow(): Window{
    return this.getWindow();
  }
}

不要忘记在 AppModule 中注册服务:

...

import { WindowRefService } from './window-ref.service';
...
@NgModule{
...
providers: [WindowRefService]
}

之后将其注入您的组件中:

 import { WindowRefService } from './window-ref.service';
 ...
 
 private _window:any;
 constructor(windowRef: WindowRefService){
     this._window = windowRef.nativeWindow;
 }
 ...
 //openModal function
 openModal(){
  console.log("Open a modal");
  this._window.api.send("openModal", /* data to be sent, if any*/);
 }
 

删除private ipc: IpcRenderer | any;构造函数中的 和其他现有代码,因为它不是必需的。

于 2022-01-28T11:57:17.853 回答