📖

ElectronのIPC通信にGraphQLを使う

2021/09/21に公開

動機

ElectronのIPC通信は特にメインプロセスとレンダラープロセスでデータのやりとりをする場合に使われます。昨今のセキュリティ向上の流れでレンダラープロセスからメインプロセスで使うモジュールを直接呼び出すことは避けられるようになり、IPC通信を介して実現することが多くなっています。

しかしながらIPC通信の種類が増えるとその管理が非常に大変になります。特に引数やキーワードのチェックがなく、正しいデータを送受信しているのか確認するのが大変です。

そこで今回はIPC通信にGraphQLを使ってみようと思います。これによりGraphQLの機能で上記の課題を解決できます。

方法

メインプロセス

まず GraphQL ToolsmakeExectableSchema を使って 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 をコンテキスト経由で渡していますが、これは本題とは関係ないです)。

https://www.electronjs.org/docs/api/ipc-main#ipcmainhandlechannel-listener

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チャンネルを呼び出すコードを書きます。

https://www.electronjs.org/docs/api/ipc-renderer#ipcrendererinvokechannel-args

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 を使うことでコードを自動生成してくれます。超便利!

詳しくはこちらの記事を読んでください。
https://techlife.cookpad.com/entry/2021/03/24/123214

まとめ

この記事ではGraphQLをIPC通信のインターフェイスとして使う方法を紹介しました。これにより実装が面倒なIPC通信の使い勝手を向上させることができます。ぜひ活用してみてください!

Discussion