IPCモジュールを利用すると「Main Process」と「Renderer Process」間で通信できるようになります。ここでは、IPCモジュールの基本的な利用方法を確認します。また、nodeIntegrationなどのセキュリティ周りの設定についても取り上げます。
IPCによるプロセス間通信
ipcMain, ipcRenderer
Main Process
と Renderer Process
間での通信はIPCモジュールを利用します。
Main Process
では、ipcMain を利用します。
Renderer Process
では、ipcRenderer を利用します。
ここでは、画面上のボタンをクリックしたら、以下のような通信を行う処理を実装して動作確認します。
コード
以下ファイル構成で実装します。
├── index.html // Renderer Process(UI)
├── main.js // Main Process
└── renderer.js // Renderer Process
main.js
wakuwaku:xxx
という Channel
でメッセージを受信できるようにしています。
メッセージを受信したら、3秒待機後、wakuwaku:yyy
という Channel
にメッセージを送信しています。
( 後述しますが、 nodeIntegration: true,
の箇所は後で修正します。 )
const { app, BrowserWindow, ipcMain } = require('electron')
let window
const sleep = (second) => new Promise(resolve => setTimeout(resolve, second * 1000))
app.on('ready', () => {
window = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
}
})
window.loadFile('index.html')
window.on('closed', () => {
window = null
})
})
ipcMain.on('wakuwaku:xxx', async (event, arg) => {
console.log('receive message: wakuwaku:xxx')
console.log(arg)
await sleep(3)
event.sender.send('wakuwaku:yyy', { message: 'pong' })
})
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>わくわくBank</title>
</head>
<body>
<h1>Hello World!</h1>
<input
id='xxx'
type="button"
value="click" />
<script type="text/javascript" src="renderer.js"></script>
</body>
</html>
renderer.js
wakuwaku:xxx
という Channel
にメッセージを送信しています。wakuwaku:yyy
という Channel
でメッセージを受信できるようにしています。
const { ipcRenderer } = require('electron')
const btn = document.getElementById('xxx')
btn.addEventListener('click', () => {
ipcRenderer.send('wakuwaku:xxx', { message: 'ping' })
console.log('message sending complete: wakuwaku:xxx')
})
ipcRenderer.on('wakuwaku:yyy', (event, arg) => {
console.log('receive message: wakuwaku:yyy')
console.log(arg)
})
動作確認
ボタンをクリックするとMain Processにメッセージが送信されます。
今回、sendメソッド
を利用したため、非同期処理処理になります。そのため、ボタンクリック後にmessage sending complete: wakuwaku:xxx
のメッセージが即表示されています。
もし、sendSyncメソッド
を利用した場合、同期処理になるので、Main Processの処理(3秒待機)が完了してからmessage sending complete: wakuwaku:xxx
のメッセージが表示されます。
推奨設定に変更
BrowserWindowの設定にて、nodeIntegration: true
にするのはセキュリティ的に非推奨です。推奨されている設定に変更していきます。
nodeIntegrationプロパティ
XSS攻撃を防ぐためにnodeIntegrationの設定は無効にしておく必要があります( nodeIntegration: false
)。
window = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false,
}
})
しかし、無効にすると以下のように、require
すら利用できなくなります。
preloadプロパティ
( contextBridgeモジュールを活用 )
renderer.js
にて require
を利用せずに済むようにするため、preload
という機能を利用します。
main.js
preload.js
というファイルを作成して、preloadプロパティ
の設定に指定します。
window = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false,
preload: path.join(app.getAppPath(), 'preload.js'),
}
})
preload.js
preloadプロパティ
に設定したファイルです。Node.jsの機能を利用できます。
contextBridgeモジュール を利用して renderer.js
から処理を呼び出せるようにします。
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld(
'electron',
{
sendWakuwakuXxx: (arg) => ipcRenderer.send('wakuwaku:xxx', arg),
onWakuwakuYyy: (arg) => {
ipcRenderer.on('wakuwaku:yyy', (event, arg) => {
console.log('receive message: wakuwaku:yyy')
console.log(arg)
})
}
}
)
上記のように実装することで、window.electron.sendWakuwakuXxx
window.electron.onWakuwakuYyy
という形式で renderer.js
から処理を呼び出せます。
renderer.js
const { ipcRenderer } = require('electron')
を削除して、preload.js
で設定した処理を呼び出すように変更しています。
const btn = document.getElementById('xxx')
btn.addEventListener('click', () => {
window.electron.sendWakuwakuXxx({ message: 'ping' })
console.log('message sending complete: wakuwaku:xxx')
})
window.electron.onWakuwakuYyy()
これで、renderer.js
で require
を呼び出さずに済むようになりました。
ここで、一度動作確認したところ、以下のように Error: contextBridge API can only be used when contextIsolation is enabled
というエラーが表示されました。
contextIsolationプロパティ
contextIsolationを有効にする必要があるようなので以下のように設定します。
window = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false,
preload: path.join(app.getAppPath(), 'preload.js'),
contextIsolation: true,
}
})
設定後、再度動作確認したら無事動作しました。
おまけ|IPCによるプロセス間通信
( invoke, handleを利用 )
先述の例では、以下のように send
と on
メソッドを利用して実装しました。
以下のように、invoke
と handle
メソッドを利用したほうが処理量は少なくてすみます。
main.js
(省略)
ipcMain.handle('wakuwaku:xxx', async (event, arg) => {
console.log('receive message: wakuwaku:xxx')
console.log(arg)
await sleep(3)
return { message: 'pong' }
})
preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld(
'electron',
{
sendWakuwakuXxx: async (arg) => {
const result = await ipcRenderer.invoke('wakuwaku:xxx', arg)
console.log(result)
},
}
)
renderer.js
const btn = document.getElementById('xxx')
btn.addEventListener('click', () => {
window.electron.sendWakuwakuXxx({ message: 'ping' })
})
参考
- IPCモジュール
- BrowserWindowモジュール
- nodeIntegrationについて
- contextIsolationについて
- contextBridgeモジュール