IPCによるプロセス間通信(ipcMain, ipcRenderer, 設定)

IPCモジュールを利用すると「Main Process」と「Renderer Process」間で通信できるようになります。ここでは、IPCモジュールの基本的な利用方法を確認します。また、nodeIntegrationなどのセキュリティ周りの設定についても取り上げます。

IPCによるプロセス間通信

ipcMain, ipcRenderer

Main ProcessRenderer Process 間での通信はIPCモジュールを利用します。

Main Process では、ipcMain を利用します。

Renderer Process では、ipcRenderer を利用します。

ここでは、画面上のボタンをクリックしたら、以下のような通信を行う処理を実装して動作確認します。

773-electron-ipc_21.png

コード

以下ファイル構成で実装します。

├── 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)
})

動作確認

773-electron-ipc_00.gif

ボタンをクリックすると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 すら利用できなくなります。
773-electron-ipc_11.png

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.jsrequire を呼び出さずに済むようになりました。

ここで、一度動作確認したところ、以下のように Error: contextBridge API can only be used when contextIsolation is enabled というエラーが表示されました。

773-electron-ipc_12.png

contextIsolationプロパティ

contextIsolationを有効にする必要があるようなので以下のように設定します。

window = new BrowserWindow({
  width: 800,
  height: 600,
  webPreferences: {
    nodeIntegration: false,
    preload: path.join(app.getAppPath(), 'preload.js'),
    contextIsolation: true,
  }
})

設定後、再度動作確認したら無事動作しました。

おまけ|IPCによるプロセス間通信
( invoke, handleを利用 )

先述の例では、以下のように sendon メソッドを利用して実装しました。

773-electron-ipc_21.png

以下のように、invokehandle メソッドを利用したほうが処理量は少なくてすみます。

773-electron-ipc_22.png

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' })
})

参考