[Electron]contextBridgeでipcRenderer.invokeを使う

# Electronとは
html/css/javascriptのみで簡単にマルチプラットフォーム対応のアプリを開発できるフレームワークです。デスクトップPC向けのハイブリッドアプリみたいな立ち位置をしています。VScodeやSlackとかで使われています。
[参考] https://slack.engineering/building-hybrid-applications-with-electron/

リファレンス読んでも解決しなくて、業務改善をするためのアプリ開発で丸一日躓いてしまったため、
その戒めと備忘録のために記事にしておきます。
※普段分かっているはずのことが、フレームワーク×基本の組み合わせだとミスってしまう事例でした

# ipcRenderer.invokeとは
Node.jsとChromiumを使用しており、html/css/javascriptのweb画面 + Node.jsで動作します。
画面(レンダラープロセス)の入力値を使って、メインプロセス(Node.js)で処理したい時、プロセス間通信が必要になります。そこでipcRenderer.invokeの出番です。
ipcRenderer.send(送信) → ipcMain.on → ipcRenderer.on(結果の受信)と書かなければいけないものを
ipcRenderer.invoke(送信/結果の受信) → ipcMain.handle のみで実装できます。
 
公式ページには以下のように使い方を紹介されています。
[引用] https://www.electronjs.org/docs/api/ipc-renderer#ipcrendererinvokechannel-args

```javascript:renderer.js
// Renderer process
ipcRenderer.invoke('some-name', someArgument).then((result) => {
  // ...
})

// 以下のようにも書けます。
const result = ipcRenderer.invoke('some-name', someArgument)
```

```javascript:main.js
// Main process
ipcMain.handle('some-name', async (event, someArgument) => {
  const result = await doSomeWork(someArgument)
  return result
})
```

上記のようにPromise処理のようにプロセス通信を実装できます。
ですが、上記の通りに実装しようとするとnodeIntegration: trueにすることになります。

```javascript:main.js
// Main process
mainWindow = new BrowserWindow({
  width: 1000,
  height: 800,
  webPreferences: {
    nodeIntegration: true 
    // レンダラープロセスでElectronのAPIを使うためには
    // nodeIntegration: true にする必要がある。
  },
});
```

chromeやfirefoxでは害のないwebページでも、nodeIntegration: trueにしたアプリではXSSの危険性が出てしまいます。
※悪意のあるwebページ以外にも危険性があり、推奨されていません。やめときましょう。

# contextBridgeとは
上記のリスクを避けて、安全にレンダラープロセスにAPIを渡してあげるための仕組みがcontextBrigeです。
[引用] https://www.electronjs.org/docs/api/context-bridge#contextbridge

```javascript:main.js
//Main process
const mainWindow = new BrowserWindow({
  width: 800,
  height: 600,
  webPreferences: {
    nodeIntegration: false,
    contextIsolation: true,
    preload: path.join(__dirname, 'preload.js')
  }
})

ipcMain.on("msg_render_to_main1", (event, arg) => {
  console.log(arg); //printing "good job"
});
```

```javascript:preload.js

// Preload (Isolated World)

const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld(
  'electron',
  {
    doThing: () => ipcRenderer.send('do-a-thing')
  }
)
```

```javascript:renderer.js

// Renderer (Main World)
window.electron.doThing()

//以下のようにwindowは省略できます。
electron.doThing()
```

# contextBridgeでipcRenderer.invokeを使う
結論から言うと、contextBridgeでipcRenderer.invokeを使うときは、returnを使いましょう。
生のipcRenderer.invokeを使うときは、特段意識する必要はありませんが、contextBridgeでレンダラープロセスに渡すメソッドはasyncで定義しているので、returnを入れないとpreload.jsで定義したメソッドdoThingのresultがundefinedになってしまいます。自分はここで丸一日躓きました。

```javascript:main.js
//Main process
const mainWindow = new BrowserWindow({
  width: 800,
  height: 600,
  webPreferences: {
    nodeIntegration: false,
    contextIsolation: true,
    preload: path.join(__dirname, 'preload.js')
  }
})

ipcMain.handle('some-name', async (event, someArgument) => {
  const result = await doSomeWork(someArgument)
  return result
})
```

```javascript:preload.js
// Preload (Isolated World)
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld(
  'electron',
  {
    doThing: async () => return await ipcRenderer.invoke('do-a-thing')
    // メソッドdoThingはasync functionを渡しているので、
    // returnがないとresultがundefinedになります。
  }
)
```

```javascript:renderer.js
// Renderer (Main World)
const result = await electron.doThing()
console.log(result)
```