ElectronのIPC通信にGraphQLを使う
動機
ElectronのIPC通信は特にメインプロセスとレンダラープロセスでデータのやりとりをする場合に使われます。昨今のセキュリティ向上の流れでレンダラープロセスからメインプロセスで使うモジュールを直接呼び出すことは避けられるようになり、IPC通信を介して実現することが多くなっています。
しかしながらIPC通信の種類が増えるとその管理が非常に大変になります。特に引数やキーワードのチェックがなく、正しいデータを送受信しているのか確認するのが大変です。
そこで今回はIPC通信にGraphQLを使ってみようと思います。これによりGraphQLの機能で上記の課題を解決できます。
方法
メインプロセス
まず GraphQL Tools の makeExectableSchema
を使って GraphQLSchema
のインスタンスを作ります。
const typeDefs = /* GraphQL */`
type Car {
name: String!
engine: Engine!
wheels: [Wheel!]!
}
type Engine {
name: String!
hp: Float!
}
type Wheel {
name: String!
size: Float!
}
type Query {
car(carName: String!): Car!
}
type Mutation {
buy(carName: String!): Car!
}
`
const resolvers = {
Query: {
car: (_parent, { carName }, { carRepository }) => {
return carRepository.findByName(carName)
},
},
Mutation: {
buy: async(_parent, { carName }, { carRepository }) => {
await carRepository.buy(carName)
return carRepository.findByName(carName)
}
},
}
import { makeExecutableSchema } from '@graphql-tools/schema'
const schema = makeExectableSchema({ typeDefs, resolvers })
次に ipcMain.handle
にGraphQLのハンドラを追加します( carRepository
をコンテキスト経由で渡していますが、これは本題とは関係ないです)。
IPC通信のチャンネル名は graphql
としました。
import { ipcMain } from 'electron'
import { graphql } from 'graphql'
const context = {
carRepository: new CarRepository()
}
ipcMain.handle('graphql', (_e: any, query: string, args: any) => graphql(schema, query, null, context, args))
これでメインプロセスの準備が完了しました。
レンダラープロセス
まずオペレーションを書きます。
const getCarQuery = /* graphql */`
query car($carName: String!) {
car(carName: $carName) {
name
engine {
name
hp
}
wheels {
name
size
}
}
}
`
const buyCarQuery = /* graphql */`
mutation buyCar($carName: String!) {
car(carName: $carName) {
name
}
}
`
次に ipcRenderer.invoke
を使ってメインプロセスのGraphQLチャンネルを呼び出すコードを書きます。
import { ExecutionResult } from 'graphql'
import { ipcRenderer } from 'electron'
async function invokeGraphQL(query: string, args: any) {
const result: ExecutionResult = await ipcRenderer.invoke('graphql', query, args)
if (result.errors) throw new Error('error')
return result.data
}
function getCar(args: { carName: string }) {
return invokeGraphQL(getCarQuery, args)
}
function buyCar(args: { carName: string }) {
return invokeGraphQL(buyCarQuery, args)
}
これで完成です!やったね!!
セキュリティを考慮すると contextBridge を使うことが大変望ましいですが、説明を簡略化するためにレンダラープロセスで直接 ipcRenderer
を使うコードにしています。
GraphQL Code Generatorによるコードの自動生成
上記の例ではTypeScriptの型情報を自分自身で書いていましたが、 GraphQL Code Generator を使うことでコードを自動生成してくれます。超便利!
詳しくはこちらの記事を読んでください。
まとめ
この記事ではGraphQLをIPC通信のインターフェイスとして使う方法を紹介しました。これにより実装が面倒なIPC通信の使い勝手を向上させることができます。ぜひ活用してみてください!
Discussion