【问题标题】:Angular 11 Electron IPC communication 'send' of undefinedAngular 11 Electron IPC 通信“发送”未定义
【发布时间】:2021-06-12 15:46:57
【问题描述】:

我做了第一次尝试,想使用角度渲染器进程中的电子 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:无法读取未定义的属性“发送”

我做错了什么?

【问题讨论】:

    标签: javascript angular electron ipc ipcrenderer


    【解决方案1】:

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

    原因

    看看这一行:

    (<any>window).require
    

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

    解决方案

    有两种解决方案:

    1. 设置nodeIntegration: truecontextIsolation: false 该应用程序将按预期运行此 hack 存在安全问题。 正如this answer中所引用的:

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

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

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

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

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

    创建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]
    }
    

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

     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; 和其他现有代码,因为它不是必需的。

    【讨论】:

      猜你喜欢
      • 2018-05-13
      • 1970-01-01
      • 2021-09-13
      • 2017-09-15
      • 2021-05-15
      • 2022-01-21
      • 2021-10-13
      • 2012-03-12
      • 2022-01-11
      相关资源
      最近更新 更多