ipc
ipc通信とは
electronは
・メインプロセス(electronアプリがOS機能を使用する過程)
・レンダラープロセス(アプリのウィンドウが作成され、そのウィンドウがindex.htmlを読み込むと、ウィンドウ内でhtmlのUI(とそこから読み込まれるスクリプト)を描画する過程)
の2つに分かれている
レンダラープロセスはChroniumというウェブブラウザをつかって行われており、通常のウェブサイト(GoogleChrome等)と変わらないため、世界中の人々がアクセスできる
つまり、メインプロセスとレンダラープロセスを自由に行き来できると、世界中の人々がelectronアプリを通して開発者のローカルPCのOS機能を使用できてしまう
これはセキュリティ上よろしくないので、メインプロセスとレンダラープロセスの間をpreload.jsという小窓でつなぐ
preload.jsは、レンダラープロセスがメインプロセスの特定の機能にのみアクセスできるようにする小窓
具体的には、メインプロセス(main.js)でipcMainによって処理を設定し、レンダラープロセス(renderer.js)でipcRendererによってinvokeする
install
// electron-storeはアプリを閉じてもデータが失われないように、
// キーとバリュー形式でデータをローカルファイルconfig.jsonに保存するためのモジュール
// オンライン上のデータベース or ローカルストレージを使わないときに使用する
npm install electron@latest electron-store uuiddirectory structure
electronproject
|---package.json
|---main.js
|---index.html
|
|---preload.js
|---store.js
|---renderer.jsmain.js
// ES形式 ... importでJSモジュールを読み込む
// CommonJS形式 ... constとrequireで読み込む
// app ... アプリ全体を起動・終了する
// BrowserWindow ... アプリ内でウィンドウを作成する
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
const store = require('./store');
let win;
function createWindow() {
win = new BrowserWindow({
width: 1000,
height: 1200,
title: 'electron app',
// ipc通信の設定
webPreferences: {
// プレロードスクリプトが実行されてからレンダラープロセスが読み込まれる
preload: path.join(__dirname, 'preload.js'),
// レンダラープロセスがnode.jsの機能(OS機能)を利用できないようにする
nodeIntegration: false,
// メインプロセスとレンダラープロセスのcontextを分離してpreload.jsでつなぐ
contextIsolation: true
}
});
// ウィンドウにindex.htmlを読み込ませる
win.loadFile('index.html');
}
// 初期化が完了したらcreateWindow関数を実行する
app.whenReady().then(createWindow);
// すべてのウィンドウが閉じたときの処理
// MacOS(darwin platform)以外ならアプリを終了させる
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
ipcMain.handle('events:get', async () => {
return Store.getAllEvents();
});
ipcMain.handle('events:add', async (event, ev) => {
return Store.addEvent(ev);
});
ipcMain.handle('events:update', async (event, id, updates) => {
return Store.updateEvent(id, updates);
});
ipcMain.handle('events:delete', async (event, id) => {
return Store.deleteEvent(id);
});preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
getAllEvents: () => ipcRenderer.invoke('events:get');
addEvent: (ev) => ipcRenderer.invoke('events:add', ev);
updaetEvent: (id, update) => ipcRenderer.invoke('events:update', id, updates);
deleteEvent: (id) => ipcRenderer.invoke('events:delete', id)
});store.js
const Store = require('electron-store').default;
const { v4: uuidv4 } = require('uuid');
const schema = {
events: {
type: 'array',
default: []
}
};
const store = new Store({ schema });
function getAllEvents() {
return store.get('events', []);
}
function addEvent(ev) {
const events = getAllEvents();
const newEv = {
id: uuidv4(),
title: ev.title || 'Untitled',
start: ev.start,
end: ev.end || ev.start,
notes: ev.notes || ''
};
events.push(newEv);
store.set('events', events);
return newEv;
}
function updateEvent(id, updates) {
consts events = getAllEvents();
const idx = events.findIndex(e => e.id === id);
if (idx === -1) throw new Error('Not found');
events[idx] = { ...events[idx], ...updates };
store.set('events', events);
return events[idx];
}
function deleteEvent(id) {
let events = getAllEvents();
events = events.filter(e => e.id !== id);
store.set('events', events);
return true;
}
module.exports = { getAllEvents, addEvent, updateEvent, deleteEvent };index.html
<!DOCTYPE html>
<html>
...
<script src="renderer.js"></script>
</body>
</html>renderer.js