Chrome拡張機能を作ってみよう②

[前回](https://qiita.com/andkirato/items/864a107c628d935a6f7b)はChrome拡張機能からファイルのダウンロードまでを投稿しました。
今回はその続きです。

## Chrome拡張機能とローカルプログラムの連携
Chrome拡張機能とローカルプログラムとの連携について
今回はローカルプログラムをvisual studioで作ったC#のプログラムとします。

##下準備
まずは連携するための下準備をします。

最初に連携するプログラムの配置場所を決めます。
今回は
c:\extensionSample\program
の配下に置くことにします。

___連携用JSONファイルを準備___
場所に決まりはないですが連携用のjsonファイルをprogramフォルダの配下に配置する

```program.josn
{
  "name": "put.message",
  "description": "特定のURLでメッセージを表示する。",
  "path": "C:\\extensionSample\\program\\program.exe",
  "type": "stdio",
  "allowed_origins": [
    "chrome-extension://gfoihpmphbkdehkldlglbbcgkcpdfmam/"
  ]
}
```
nameは連携に必要です。今回は「put.message」としています。

pathにはプログラムのパスを指定します。

allowed_originsは拡張機能のIDを指定します。
chromeのアドレスバーに「chrome://extensions」と入力して
前回追加した拡張機能のIDを記載します。

___レジストリに登録___
次に、レジストリにjsonファイルを登録します。
コンピューター\HKEY_LOCAL_MACHINE\SOFTWARE\Google\Chrome\NativeMessagingHosts
にjsonファイルのnameに指定した「put.message」キーを作成する。
既定のデータにjsonファイルパスを指定する。
今回は「c:\extensionSample\program\program.josn」とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123857/16123f4c-3efb-ee77-4ead-b83e6cdbe132.png)


## C#プログラム
では実際にC＃のプログラムです。
（結構適当に書いています。ご容赦を。。。）

```c#
using Microsoft.JScript;
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;

namespace program
{
    /// <summary>
    /// ローカルプログラム
    /// </summary>
    class localprogram
    {
        private static bool NativeMessageFlg = true;
        private static readonly Encoding enc = Encoding.UTF8;

        /// <summary>
        /// メイン処理
        /// </summary>
        static void Main(string[] args)
        {
#if DEBUG
            // デバッグ時のみ、プロセスにアタッチ
            System.Diagnostics.Debugger.Launch();
#endif
            try
            {
                // 起動処理の確認（Chrome拡張機能 or その他（直接起動））
                if (args.Length > 0 && args[0].IndexOf("chrome-extension") >= 0)
                {
                    // Chrome拡張機能
                    NativeMessageFlg = true;
                }
                else
                {
                    // その他（直接起動）
                    NativeMessageFlg = false;
                }

                Request request = new Request();

                if (NativeMessageFlg)
                {
                    // Chrome拡張機能から取得
                    Stream stdin = Console.OpenStandardInput();
                    byte[] bytes = new byte[4];
                    stdin.Read(bytes, 0, 4);

                    int length = BitConverter.ToInt32(bytes, 0);

                    string input = "";
                    for (int i = 0; i < length; i++)
                    {
                        input += (char)stdin.ReadByte();
                    }
                    stdin.Close();
                    using (var ms = new MemoryStream(enc.GetBytes(input)))
                    {
                        var jsonSer = new DataContractJsonSerializer(typeof(Request));
                        request = (Request)jsonSer.ReadObject(ms);
                    }
                }
                // Chrome拡張機能から受け取った「Action」によって処理を分ける
                Response response = new Response();
                int limit = 1024 * 1024 - 2;
                string stringText = string.Empty;
                switch (request.Action)
                {
                    case "putmessage":
                        string responsJson;
                        response.Code = 0;
                        response.Data = request.Data;
                        responsJson = ConvertJson(response, typeof(Response));
                        string stringText1 = GlobalObject.encodeURIComponent(responsJson);
                        while (stringText1.Length >= limit)
                        {
                            WriteString("\"" + stringText1.Substring(0, limit) + "\"");
                            stringText1 = stringText1.Substring(limit);
                        }
                        WriteString("\"" + stringText1 + "\"");
                        break;
                    default:
                        response.Code = 9;
                        response.Data = "エラー";
                        responsJson = ConvertJson(response, typeof(Response));
                        string stringTextErr = GlobalObject.encodeURIComponent(responsJson);
                        while (stringTextErr.Length >= limit)
                        {
                            WriteString("\"" + stringTextErr.Substring(0, limit) + "\"");
                            stringTextErr = stringTextErr.Substring(limit);
                        }
                        WriteString("\"" + stringTextErr + "\"");
                        break;
                }
            }
            catch (Exception ex)
            {
            }

            Environment.Exit(0);
        }

        /// <summary>
        /// オブジェクト→JSONへの変換
        /// </summary>
        private static string ConvertJson(object obj, Type type)
        {
            using (var ms = new MemoryStream())
            {
                var serializer = new DataContractJsonSerializer(type);
                serializer.WriteObject(ms, obj);
                return enc.GetString(ms.ToArray());
            }
        }

        private static void WriteString(string stringData)
        {
            byte[] bytes = BitConverter.GetBytes(stringData.Length);
            Stream stdout = Console.OpenStandardOutput();
            for (int i = 0; i < 4; i++)
            {
                stdout.WriteByte(bytes[i]);
            }
            byte[] b = Encoding.UTF8.GetBytes(stringData);
            stdout.Write(b, 0, b.Length);
            stdout.Flush();
            stdout.Close();
        }

        /// <summary>
        /// リクエストパラメータ
        /// </summary>
        [DataContract]
        struct Request
        {
            [DataMember(Name = "Action")]
            public string Action { get; set; }
            [DataMember(Name = "Data")]
            public string Data { get; set; }
        }

        /// <summary>
        /// レスポンス
        /// </summary>
        [DataContract]
        struct Response
        {
            [DataMember(Name = "Code")]
            public int Code { get; set; }
            [DataMember(Name = "Data")]
            public string Data { get; set; }
        }
    }
}
```

特にこれと言って注意点はないと思います。

System.Diagnostics.Debugger.Launch();
の箇所はデバックするときに便利なので付けています。

ビルドしたらprogram.exeファイルを
C:\extensionSample\program\に配置します。

次に「___background.js___」を修正します。

```background.js
/*
  初期起動時の処理
*/
// インストール時かバージョンアップ時
chrome.runtime.onInstalled.addListener(function() {
  initialize();
});

// ブラウザ起動時
chrome.runtime.onStartup.addListener(function() {
  initialize();
});


function initialize() {
  // ファイルダウンロード先
  var dlFileName = "http://localhost/sample.txt";
  // ファイルを取得
  $.ajax({
    url: dlFileName,
    type: "GET",
    dataType: 'binary',
    responseType:'arraybuffer',
    timeout: 500
  })
  // 成功時
  .done(errorHandle(function (response) {
    var data = response;
    // ArrayBufferで取得するので
    var textfiledata = String.fromCharCode(...new Uint8Array(data));
    console.log(textfiledata);
    // 渡す前にbase64エンコードしておく
    textfiledata = btoa(textfiledata); 
    // ダウンロードした内容をprogramに連携する
    chrome.runtime.sendNativeMessage('put.message',
    {Action: "putmessage", Data: textfiledata},
    errorHandle(function(response, thread){
      // デコードしてJSON形式にする
      var getData = JSON.parse(decodeURIComponent(response));
      // 受け取ったコードがエラーの場合
      if(getData.Code != 0){
        throw new Error('programでエラーが発生');
      }
      // 受け取ったデータをlocalStorageに保存しておく
      localStorage.setItem('urllist', atob(getData.Data));
      return true;
    }));
    // 成功した場合は拡張機能のアイコンを切り替える
    chrome.browserAction.setIcon({path:"images/success.png"});
    // localStorageにステータスを保存
    localStorage.setItem('Status', 'ok');
  }))
  // 失敗時
  .fail(errorHandle(function () {
    // localStorageにステータスを保存
    localStorage.setItem('Status', 'ng');
  }));
  return true;
}
/**
 * 例外をまとめて処理する
 */
function errorHandle(process) {
  return function(){
    try {
      return process.apply(this, arguments);
    } catch (e) {
      chrome.browserAction.setIcon({path:"images/abnormal.png"});
      console.error(e);
    }
  };
}
```

chrome.runtime.sendNativeMessage([program.josnで指定したname],渡す値)
がローカルプログラム呼び出しの部分です。
データのやり取りはjson形式で行います。

## 動かしてみる
Chromeブラウザのアドレスバーに「chrome://extensions」と入力し
拡張機能を更新する。

バックグランドページを開いて、ApplicationのLocal Storageから対象の拡張機能IDを選択する。
urllistにsample.txtの内容が入っていれば成功です。

* * *
今回はここまで
[次回](https://qiita.com/andkirato/items/0f766c2586e866a005df)はURL監視について書いていきます。

（urlエンコードはいらなかったかな）