javascript - Angular 11 Electron IPC 通信 'send' 未定义

标签 javascript angular electron ipc ipcrenderer

我做了第一次尝试,想使用 Angular 渲染器进程中的 Electron API。我按照

中的说明进行操作

Instruction creating Angular -Electron application

所以在我的 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 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: Cannot read property 'send' of undefined

我做错了什么?

最佳答案

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

原因

看一下这一行:

(<any>window).require

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

解决方案

有两种解决方案:

  1. 设置nodeIntegration: truecontextIsolation: false 该应用程序将按预期工作但是此黑客攻击存在安全问题。 正如 this answer 中引用的:

Electron apps are great because we get to use node, but this power is a double-edged sword. If we are not careful, we give someone access to node through our app, and with node a bad actor can corrupt your machine or delete your operating system files (among other things, I imagine).

  • 使用 contextBridge和 preload.js:这是从上面的答案和其他答案 this question 得出的。 .
  • 我将展示使用 Angular 的解决方法。

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

    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:引用的答案中已经提到了添加此代码的原因,因此我不会在这里重复。

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

    创建 WindowRefService并添加以下代码:

    window-ref-service.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]
    }
    

    之后将其注入(inject)到您的组件中:

     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;以及构造函数中的其他现有代码,因为它们不是必需的。

    关于javascript - Angular 11 Electron IPC 通信 'send' 未定义,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66636943/

    相关文章:

    angular - 尝试在没有节点的情况下运行 Angular 4

    jquery - 侧边栏 Accordion 状态和图标状态

    javascript - 如何注销 ipcRenderer.on 事件监听器?

    javascript - 如何在 Electron 中使用 BrowserWindow 对象执行脚本?

    javascript - 如何在 Jest 中模拟未安装的 npm 包?

    angular - ng-content 选择绑定(bind)变量

    javascript - 无法在 React 项目中导入 materialize-css 以使用 Chips

    javascript - Electron包,生成的文件夹

    javascript - 当值具有 HTML 字符时,按其值选择元素

    javascript - jQuery 验证由 JavaScript 提交的表单