34
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ElectronでcontextBridgeによる安全なIPC通信(受信編)

Last updated at Posted at 2020-02-13

はじめに

Electronにおけるメインプロセスとレンダラープロセス間のやり取りに関して、セキュアなIPC通信にはcontextBridge1を使おう、という記事を前回書いたらそれなりに読んでもらえているみたいです。ありがとうございます。

その時の例として、レンダラープロセスからメインプロセスへの送信を扱いましたが、受信についてもリクエストがあったので紹介します。基本的にはStackOverFlow2からの引用です。

基本的にElectronにおけるメニュー操作はメインプロセスでハンドルすることになるので、それをレンダラープロセスへ送る際には、メインプロセスからチャンネル付きで信号を送信し、レンダラープロセスで受信時にチャンネルに従って処理を分ける、ということをするでしょう。これを目的としたcontextBridgeの利用法です。

前回からの改修点

まずは前回の記事の方法3までをお読みください。今回は前回の方法3からの改修点として次の様にしました。

  • レンダラーからメインプロセスへの送信時にはチャンネルを設定して複数の信号の送信に対応した。
  • メインプロセスから送信してレンダラープロセスで受信する部分では:
    • アプリのメニュークリックで送信(メニュー操作はメインプロセスの範疇)
    • レンダラーで受信したらHTMLに反映

方法4:レンダラーでの受信

メインプロセスのコードはmain.jsとします。preloadファイルはpreload.js、レンダラープロセスで読み込むhtmlファイルはindex.htmlとします。各ファイルの中身は次のようになります。

/* main.js, case 4(extend: send and recv) */
"use strict";
const {electron,BrowserWindow,app,ipcMain,Menu} = require('electron');
let mainWindow = null;
const CreateWindow = () => {
  mainWindow = new BrowserWindow({width: 800, height: 600,
    webPreferences: { 
      nodeIntegration: false,
      contextIsolation: true,
      preload: __dirname + '/preload.js'
    } });
  mainWindow.webContents.openDevTools(); 
  mainWindow.on('closed', function() {
    mainWindow = null;
  });

  /*menu creation*/
  const template = [ {
    label: 'File',
      submenu: [{
        label: 'Open',
        click: (menuItem, browserWindow, event) => { 
          // メニュークリック時に実行される関数//

          //ここでファイルを開いたりする(今回は暫定)//
          const openedPath = "./hogehoge.txt";
          const readData = "ファイルの中身だよ";
          //ここまででファイルは読み込んだものとする//

          //この関数でIPC送信(main to renderer)//    
          browserWindow.webContents.send(
            "openfile", //送信チャンネル名(自分で区別できるように)//
            {   //送信したいデータ一覧//
               filePath: openedPath,
               dataText: readData
            }
          ); 
        } 
      }]  
    }];
        
    const menu = Menu.buildFromTemplate(template)
    Menu.setApplicationMenu(menu);

    mainWindow.loadURL('file://' + __dirname + '/index.html');
}
app.on('ready', CreateWindow);

//IPC受信部//
ipcMain.on("msg_render_to_main1", (event, arg) => {
  console.log(arg); //printing "good job"
});
ipcMain.on("msg_render_to_main2", (event, arg) => {
  console.log("We are the", arg.teamName, "!");
  //printing "We are the Victories !"
});
/* preload.js, case 4 (extend)*/
const { contextBridge, ipcRenderer} = require("electron");
contextBridge.exposeInMainWorld(
  "api", {
    send: (channel, data) => {//rendererからの送信用//
        ipcRenderer.send(channel, data);            
      },
    on: (channel, func) => { //rendererでの受信用, funcはコールバック関数//
        ipcRenderer.on(channel, (event, ...args) => func(...args));
    }
  }
);
<!--index.html, case 4 (extend)-->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Test</title>
  </head>
  <body>
    <button id="button1">test1</button>
    <button id="button2">test2</button>
    <div id="previewF">受信ファイル名</div>
    <div id="previewD">受信データ</div>
  </body>
  <script type = "text/javascript">
      //適当なプログラム
    const button1 = document.getElementById("button1");  
    
    //送信用(チャンネル名指定)//
    button1.addEventListener("click", (e)=>{
      window.api.send("msg_render_to_main1", "god job");
    });
    
    //送信用(チャンネル名指定)//
    button2.addEventListener("click", (e)=>{
      window.api.send("msg_render_to_main2", {teamName: "Victories"});
    });

    //受信部(チャンネル名指定)//
    window.api.on("openfile", (arg)=>{
      document.getElementById("previewF").textContent = arg.filePath;
      document.getElementById("previewD").textContent = arg.dataText;
    });
 </script>
</html>

まず、レンダラープロセスからメインプロセスへ送信する部分ですが、ボタンを二つ配置し、チャンネル名を変えて二種類の信号が送れるようになっています。メインプロセスではipsMain.on(チャンネル名, コールバック関数) でチャンネル名を指定することで、処理を分けて行えるようになります。

次に、本目的のレンダラープロセスでの受信ですが、preload.jsでのonの部分の記述がポイントです。コールバック関数名をfuncとしておいて、 ipcRenderer.on()の中ではスプレッド構文...argsを使っています。これにより、メインプロセスから送られてきた引数のうち、eventだけを取り除いてコールバック関数へ渡しています。コールバック関数はindex.html内のwindow.api.on("openfile",(arg)=>{ ... })にて記述できるので、実質的にipcRenderer.on()を直接書いていた時代と同様に利用できます。

注意点

contextBridgeはとっても良さそうなAPIですが、Electronのドキュメント3には次のように書かれています。

"The contextBridge API has been published to Electron's master branch, but has not yet been included in an Electron release."

一応、私の環境のversion7.1.9では使えていますが、いつから使えるようになったのかはちょっと不明なので、気を付けてください。

感想

コールバック関数という表現が合っているのか不安ですが、Javascriptライト勢なのでご勘弁くだされ。

追記 2020/9/23

contextBridgeのより良い使い方として、もう少し注意すべき点があります。
上記のサンプルコードは、Electronのversion upの歴史に沿ってコードをどのように改変していくかを示すために残しておきますが、使用する前に下記の記事も是非読んでみてください。

Electronのセキュリティについて大きく誤認していたこと

Electron(v10.1.2現在)の IPC 通信入門 - よりセキュアな方法への変遷

  1. https://electronjs.org/docs/api/context-bridge

  2. StackOverFlow:With contextIsolation = true, is it possible to use ipcRenderer?

  3. https://electronjs.org/docs/api/context-bridge/history

34
26
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?