はじめに
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');letmainWindow=null;constCreateWindow=()=>{mainWindow=newBrowserWindow({width:800,height:600,webPreferences:{nodeIntegration:false,contextIsolation:true,preload:__dirname+'/preload.js'}});mainWindow.webContents.openDevTools();mainWindow.on('closed',function(){mainWindow=null;});/*menu creation*/consttemplate=[{label:'File',submenu:[{label:'Open',click:(menuItem,browserWindow,event)=>{// メニュークリック時に実行される関数////ここでファイルを開いたりする(今回は暫定)//constopenedPath="./hogehoge.txt";constreadData="ファイルの中身だよ";//ここまででファイルは読み込んだものとする////この関数でIPC送信(main to renderer)// browserWindow.webContents.send("openfile",//送信チャンネル名(自分で区別できるように)//{//送信したいデータ一覧//filePath:openedPath,dataText:readData});}}]}];constmenu=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><metacharset="UTF-8"><title>Test</title></head><body><buttonid="button1">test1</button><buttonid="button2">test2</button><divid="previewF">受信ファイル名</div><divid="previewD">受信データ</div></body><script type = "text/javascript">//適当なプログラムconstbutton1=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ライト勢なのでご勘弁くだされ。