我做了第一次尝试,想使用 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。
解决方案
有两种解决方案:
- 设置
nodeIntegration: true
和contextIsolation: 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/