ElectronでCORSで守られているレスポンスも見れるようにする


CORSというウェブアプリのセキュリティのための仕組みがあり、例えばドメインが違うところに通信を行おうとすると、レスポンスが見られないようになったりします。
これは、ブラウザーがそのへんをきっちり制御していて、セキュリティを守るようになっているからです。

で、今回、私はElectronのアプリでCORSで守られたAPIを叩きたいと思ったのですが、Access-Control-Allow-Originにドメインが設定されていて、Electronからもレスポンスにアクセスできなかったのです。
Electronで実行されるとき、カレントドメインにはどうやらnullが設定されているようです。

本来の解決方法

調べた限り、こうするんじゃないかなー、という雰囲気を感じ取ったのですが、うまく動きませんでした。

webRequest.onHeadersReceivedでヘッダを書き換える

参考:https://imfly.gitbooks.io/electron-docs-gitbook/jp/api/session.html

要は、Access-Control-Allow-Origin: *が帰ってきたことにしてブラウザを騙してしまえばよいのです。
と思ったので、レスポンスヘッダを書き換える方法を試してみましたが、うまくいかず。
ハンドラが呼ばれていることも、cancel: trueが有効なことも確認できましたが、ヘッダーは書き換えられず。

const { session } = require("electron");
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
  callback({
    cancel: false,
    responseHeaders: {
      ...details.responseHeaders,
      "Access-Control-Allow-Origin": ["*"],
    },
  });
});

今回やった解決方法

CORSというのはブラウザー上で動くウェブアプリでのみ有効な決まりごとです。
ブラウザー外で通信する場合にはこの制約は働きません。
そこで、実際の通信はElectronのNode.jsプロセス(メインプロセスと呼ばれます)で行い、その結果をWebアプリプロセス(レンダラプロセスと呼ばれます)に渡す、というだいぶめんどうな方法で解決することにしました。

main.js
const { ipcMain } = require("electron");
const request = require("request");

ipcMain.on("fetch", async (event, url, params) => {
  const body = await new Promise(ok => {
    request(url, params, (_, _, body) => ok(body));
  });
  event.sender.send("fetch", JSON.parse(body));
});
renderer.js
const { ipcRenderer } = require("electron");
async function fetch(url, params) {
  return new Promise(ok => {
    ipcRenderer.once("fetch", (_, body) => ok(body));
    ipcRenderer.send("fetch", url, params);
  });
}

ただ、この雑実装だと、通信が同時に走るだけで混線する可能性があるので、もうすこしいい感じに実装する必要があるでしょう。とりあえず、自分はこれで十分でした。

なんで、onHeadersReceivedが使えなかったのか、とか、一般的にElectronアプリでCORSのURL叩くときどうしてるんだろうとか、疑問はいろいろ残りました。

以上です。よろしくお願いいたします。