ElectronでProxy認証 - ユーザー入力ウィンドウ編

# はじめに
Electronアプリで社内NWから外部へfetchした時、プロキシ通過でちょっと詰まったのでメモ

# 結論
loginイベント + IPC(プロセス間通信)で解決しました。

具体的な流れとしては、以下のようになります。
1. ログイン認証要求
2. メインプロセスでloginイベントをキャッチ
3. 入力ウィンドウを表示
4. 入力されたユーザー情報をIPCでメインプロセスに送信
5. メインプロセスで受信したユーザー情報をサーバーに転送

# 解説
## loginイベント

```javascript:メインプロセス
const app = electron.app;

app.on("login", (event, webContents, request, authInfo, callback)=>{
    if(authInfo.isProxy){
        event.preventDefault();
        // ほげほげ
        callback(username, password);
    }
});
```
基本的に他のイベント処理と同じです。
登録しておくと、ログイン要求があった際にキャッチし、認証等の処理が行えるようになります。

``event.preventDefault();``が、認証をスキップせず保留してくれるメソッドです。
プロキシの場合は``authInfo.isProxy``にて判別可能で、ユーザー情報は``callback(username, password)``で送信できます。


## IPC(プロセス間通信)
```javascript:メインプロセス
const ipcMain = electron.ipcMain;

ipcMain.on("channel-name", (event, arg)=>{
    // ほげほげ
});
```

```javascript:レンダラープロセス
const ipcRenderer = electron.ipcRenderer;

ipcRenderer.send("channel-name", arg);
```
IPCとは、プロセス間でデータ伝達を行う為の仕組みです。

ElectronでのIPCは、メインプロセス/レンダラープロセス間で相互にデータ伝達する際に使います。
チャネルと言う概念があり、送信時に指定したチャネル名と、受信待機時に指定したチャネル名が紐付く事で、通信が可能になります。

メインプロセス/レンダラープロセスで、使用するメンバ名が若干違いますが、機能的には大体同じです。

``ipc.on()``で受信待機、``ipc.send()``で送信、``arg``が受け渡しデータ本体となります。

これは非同期通信の例ですが、同期通信する方法もあるようです。
が、出番は少ないとの事なので割愛します。


# 構築
## メインプロセス側
メインウィンドウの記述は端折ってます。

```javascript:index.js
const electron = require("electron");
const app = electron.app;

// メインプロセス側のIPCメンバ
const ipcMain = electron.ipcMain;

let loginWindow;
let isLoginWindow = false;

// ログイン要求時に発火するイベント
app.on("login", (event, webContents, request, authInfo, callback)=>{

    // プロキシサーバーからの要求だったら続行
    if(authInfo.isProxy){

        // 重複発火対策
        if(!isLoginWindow){
            isLoginWindow = true;
        }
        else{
            return;
        }

        // 認証情報が送信されるまで待機
        event.preventDefault();

        loginWindow = new electron.BrowserWindow({
            width: 210,
            height: 140,
            resizable: false
        });

        // 入力ウィンドウをロード
        loginWindow.setMenu(null);
        loginWindow.loadURL('file://' + __dirname + '/login.html');

        // IPCチャネル"proxy-auth"で受信待機
        ipcMain.on("proxy-auth", (event, username, password)=>{

            // 受信した認証情報をプロキシサーバーへ転送
            callback(username, password);
            isLoginWindow = false;
        });
    }
});
```
途中、2連続でloginイベントが発火する、原因不明の現象に見舞われました。
1回目の発火でフラグを立てて、2回目以降はエスケープする処理を入れました。


## レンダラープロセス側
認証情報(ユーザー名/パスワード)の入力ページを組み立てます。

```html:login.html
<body>
    <input type="text" id="username">
    <input type="password" id="password">
    <button onclick="sendIpc();">ログイン</button>
</body>
```
HTMLはごく普通の入力フォームです。

```javascript
const electron = require("electron");

// レンダラープロセス側のIPCメンバ
const ipcRenderer = electron.ipcRenderer;

// レンダラープロセス側からメインプロセス側を操作するメンバ
const remote = electron.remote;

// ログインボタン
function sendIpc(){

    // 入力ウィンドウをカレントにする
    const currentWindow = remote.getCurrentWindow();

    // IPCチャネル"proxy-auth"でユーザー名/パスワードを送信
    ipcRenderer.send(
        "proxy-auth",
        document.getElementById("username").value,
        document.getElementById("password").value
    );

    // 入力ウィンドウを自動的に閉じる
    currentWindow.close();
}
```
これで基本的な実装は完成しました。

# まとめ
プロセス間通信って、名前聞いただけで凄く難しそうなイメージでしたが、何の事はありませんでした。

イメージしてた、VisualStudio Codeのプロキシ認証画面のような感じに出来たので、満足です。

(IPCでパスワードのやり取りが大丈夫なのかは、組んだ後にちょっと気になった)