electronでkintoneのフォームブリッジを呼び出し操作する

素のkintoneは、フォームなどからレコード追加時にメールでお知らせがひどくショボいので、Form Bridgeを利用してフォーム送信時に管理者にメール通知をするようセットしてあります(送信先はTeamsのメールアドレス宛)。

このForm Bridgeは外部サイト等に埋め込めるようにiframeのタグなどを出力してくれるのですが、そのままだと本当に埋め込むだけで、尚且electronのようなアプリから連動もできません(カスタマイズも出来ない)そこで、今回electronで使えるようにする為にいくつか取り組んでみました。

※但しこの方法、値を代入してもformブリッジ側の仕様で送信しようとすると、値が入っていても入っていないと言われるので、この問題はPuppeteerで解決することにしました。

今回使用する機能

  • electronのwebview機能(iframeのようなものだけれど色々操作が可能)

今回は、form bridgeのフォームをelectron内で表示させ、社員IDや社員名、メールアドレスなどを予め設定してあるデータから自動挿入して送れるようにするのが目的です。

こちらの方法を使えば、単独では素のHTMLでform bridgeをカスタマイズ可能な状態で使えるのですが、この方法だと

  1. Bot対策をオンにしていると動かない
  2. 多言語対応していると動かない
  3. BrwoserWindowのnodeIntegrationtrueだと動かない
  4. 3.でfalseにした場合、動くものの送信時にCSRFの問題で送信が出来ない。単純なformとinputで出来てるわけじゃないみたい。
  5. 直接BrowserWindowのloadurlでiframe内のURLを指定すれば動くものの、カスタマイズが出来ない

といった具合で、正直electronで操作するには非常に辛い。ので、今回はwebviewで呼び出して操作する方式を取りました。

事前準備

今回の事前準備として、Form Bridge側の設定を行います。

自動返信メール

自動返信メールの設定を行っておきます。送信者に対して入力内容の証左としてメールを送るようにしてあげます。HTMLメールとし、本文に差し込む形で入力内容を配置しておきます。

図:自動応答はもはやフォームの基本

回答後処理

回答後処理の上記の自動応答の後に、管理者に対しても通知を行います。メールですが送られる度にメールが飛んでこられても迷惑なので、メールの送信先をMicrosoft365のTeamsのチャンネルに設定してあるメアドに対して送ります。上記のようにHTMLとして送ることで、Teams側に対して通知が来るので、チームメンバーの誰かが、タスクをピックアップする事が可能になります。

図:こんな感じでフローを構築する

図:Teamsに投稿内容が飛んでくる

iframeのURLを取り出す

最後にelectron側で呼び出すURLを取得します。form bridge編集を完了後に、右上にあるiframe用の埋め込みコードをクリックして取るのですが、この中に入ってるURLを利用します。

iframe内でsrcで指定されているURLがそれになります。これを使いますのでコピーしておきましょう。

図:iframeの中にあるURLが呼び出し対象

ソースコード

今回は、nodeIntegrationをfalse(electron5.0以降はこれがデフォルト)のままで、レンダラー側でIPC通信出来るように、preload指定等を入れています。またwebviewを利用する為、メインプロセス、レンダラープロセスに加えて、webviewプロセスが登場するため、webviewプロセスとレンダラプロセス側で通信を出来るようにする必要があります。

Electron v12より

Electron v12より、remoteの廃止およびpreload.jsの使用がデフォルトとなった為、以下に記述の方法では通信ができません。IPC通信の呼び出し方が変わっているので、以下のエントリーを参考に書き直す必要性があります。

最新のNode.jsとElectron環境でkeytarを動かしてみるテスト

メインプロセス側

index.js

function questwindowopen(){
    // メイン画面の表示。ウィンドウの幅、高さを指定できる
    questWindow = new BrowserWindow({
        'width': 950,
        'height': 750,
        'autoHideMenuBar':true,
        //nodeIntegrationを有効にしないとrenderProcessでrequireを使えない。v5.0.0ではデフォルトで廃止
        webPreferences: {
            nodeIntegration: false,
            contextIsolation: false,
            preload: __dirname + '/preload.js',
            webviewTag: true
        },
        'resizable':true,
        'fullscreenable':false,
        'fullscreen':false,
        'alwaysOnTop':false,
        'modal':true,
        'parent': mainWindow  //モーダル表示にしておく
    });

  //questWindow.toggleDevTools();

    //初期ページの表示
    questWindow.loadURL('file://' + __dirname + '/form.html');

    questWindow.on('closed', function() {
        electron.session.defaultSession.clearCache(() => {})
        questWindow = null;
    });

    // 全てのウィンドウが閉じたときの処理
    app.on('window-all-closed', () => {
        //なにもしない
        //これをいれておかないと、全部のウィンドウが閉じられるとアプリまで閉じてしまう、。
    });
}
  • いつもの新しいbrowserWindow生成時コードですが、webPreferencesに少しオプションを加えています。
  • nodeIntegrationfalseにします。
  • contextIsolationfalseにします。これをオフにしないとpreloadスクリプトを使ってプロセス間で情報共有できない。
  • preloadでは、使用するpreload用JSファイルを指定。これがnodeIntegrationをtrueにした時の現在の代替策です。
  • webviewTagtrueにします。これでiframeのように自分のHTML内に外部HTMLを呼び込めます。

preload.js

window.ipcRenderer = require('electron').ipcRenderer;
window.remote = require('electron').remote;
window.addEventListener('load', () => {
    window.$ = window.jQuery = require('jquery');
});
  • 今回は、ipcRendererを使えるようにセット。
  • これで、レンダラプロセス側で、window.ipcRendererでこれまでのrequireと同じ事が出来るようになります。
  • これを入れておかないと、nodeIntegrationがfalseであるため、レンダラプロセス側でrequireは使えなくなってるので、Node.js側と通信が出来ません。
  • jQueryをnpmで入れている場合には、addEventListnerのloadでjqueryライブラリを読み込みさせることが可能です。CDNのURL書きでも可。

レンダラプロセス側

form.html

<!DOCTYPE html>
<html>
    <head>
        <title>IT問い合わせ申請フォーム</title>
        <style>
        ::-webkit-scrollbar {
          background-color: #fff;
          width: .8em
          display:none;
        }

        ::-webkit-scrollbar-thumb:window-inactive,
        ::-webkit-scrollbar-thumb {
                background:  black
                display:none;
        }
        </style>

    </head>
    <body>
      <div style="width:100%; height:100%">
          <webview
            src="https://form.kintoneapp.com/public/form/show/formの番号をここに入れる?iframe=true"
            border="0"
            autosize="on"
            style="min-width:850px; min-height:800px"
            preload="injector.js"
            id="wv">
          </webview>
      </div>
      <script>
        //メインプロセスとIPC通信を行う
        var ipcRenderer = window.ipcRenderer;

        //webviewを取得する
        const webview = document.querySelector('webview')

        //webview読み込み時イベント(webviewプロセスとIPC通信を行う)
        webview.addEventListener('dom-ready', () => {
          //意味もなくwebview側ページタイトルを取得してみる
          let titlePage = webview.getTitle();

          //webview側にCSSを手動適用
          webview.insertCSS('html,body{ background-color: #d3ffcc !important;}')

          //webview側のデベロッパーツールを開く
          webview.openDevTools();

          //webview側へデータを送る
          webview.send("changeman",{
            id: "社員ID",
            name: "苫東太郎",
            mail: "tomato@gmail.com"
          });
        })

      </script>
    </body>
</html>
  • webviewに高さと幅を入れただけでは反応しないので、外側にdivを1個入れてサイズ指定しています。
  • webviewタグのsrcにはForm bridgeのiframeの中で指定されてるURLを使用します。
  • webviewタグのpreloadには、レンダラプロセスとwebviewプロセス間で通信するためのJSファイルを指定します。
  • addEventListnerではdom-readyがwebviewタグ読み込み時イベントになりますので、追加しておきます。
  • webview側のconsole.logはelectron側ではそのままでは表示されないので、別途openDevToolsにて開かせることが可能です。
  • webview側のchangemanに対して3つの引数を渡しています。
  • これで読み込み完了後のフォームに、社員番号、氏名、メールアドレスが自動で入るので、メインプロセス側の例えばMySQLから引っ張ってきた情報を橋渡ししてフォームに自動入力可能です。
  • webview.insertCSSにてwebview側に対してカスタムCSSを適用可能です。

injector.js

const { ipcRenderer } = require('electron')
ipcRenderer.on("changeman",function(event,data){
    //FormにNAME属性を追加する
    var forms = document.forms[0].setAttribute("name","kinoko");

    //社員番号、社員名、メールアドレスに値を入れる
    document.kinoko.elements[0].value = data.id;
    document.kinoko.elements[1].value = data.name;
    document.kinoko.elements[2].value = data.mail;

    //詳細入力欄の高さを変更
    document.kinoko.elements[5].style.height = "300px";
});
  • レンダラプロセスとwebviewプロセスとの間で、IPC通信を確立する為に使います。
  • 例えば、レンダラプロセス側でchangemanwebview.send("changeman",引数)で送ると、webviewの中に対して影響を与える事が可能です
  • 逆に、webviewプロセス側からレンダラプロセス側にデータを送る場合には、ipcRenderer.sendToHostで同じような事が可能。レンダラプロセス側は、ipc-messageイベントにてこれをキャッチすることが出来ます。
  • 上記のipc-messageイベントはwebviewに対してaddEventListnerでイベント割当をしなければなりません。その場合の事例は以下の通り
  • 下記の事例の場合、injector.jsから送る際には、ipcRenderer.sendToHost("tomato",引数)で送れます。
  • form bridge内のformはname属性がついていないので、追加しています。
  • その中にある各種elementに対して、レンダラプロセス側から受け取った引数を、流し込んでいます。
  • webview内に対してCSSカスタマイズやJavaScriptで色々追加や削除、イベントの追加などの操作がこれで可能になります。
  • 値はformブリッジに入れられますが、入っていないものと見なされる・・・・・通常のフォーム類であれば問題なく入力して送信が可能です。
//webviewタグを取得する
var webview = document.getElementById(webviewタグのID);

//webview側からのIPC通信を受け取る
webview.addEventListener('ipc-message', function(event) {
 switch(event.channel){
   case "tomato":
     //event.argsで引数を取れる
     var argment = event.args[0];
     break;
 }
});
  • 今回の事例ではwebview側から特に何も情報を取得しないので、使いません。
  • getElementByIdだけでなく、document.querySelector('webview')でも可能です。

図:electronからform bridgeを操作出来た

関連リンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)