【Google Chrome】NativeMessagingを使って拡張機能とC#クライアントアプリとで通信してみた

C#,WEB拡張,開発

おはようございます。

超久しぶりに技術ネタ。

Google Chrome(Edge、Firefox)の拡張機能を(自作)利用すると

クライアント側のアプリ(ソフト)と連携ができると知ったのでちょっと試してみました。

なかなか興味深いです。

スポンサーリンク

Native Messaging とは

Google Chrome公式ドキュメントより(翻訳)

拡張機能とアプリは、他のメッセージパッシングAPIと同様のAPIを使用して、ネイティブアプリケーションとメッセージを交換できます。

この機能をサポートするネイティブアプリケーションは、拡張機能との通信方法を知っているネイティブメッセージングホストを登録する必要があります。

Chromeは別のプロセスでホストを起動し、標準入力ストリームと標準出力ストリームを使用してホストと通信します。

簡単に言えば、クライアントアプリとWEB拡張機能の間でメッセージのやり取りができる仕組みです。

これを用いることにより、ブラウザだけではできなかったことが出来たりします。

拡張機能

拡張機能側は、manifest に拡張機能自体の情報や、利用する html や css、javascript などの情報を記述します。

manifest.json

{
	"manifest_version": 2,
	"name": "Native Messaging Extension Sample ",
	"version": "1.0.0.0",
	"description": "Native Messaging Extension Sample.",
	"content_scripts": [
		{
			"matches": ["*://*/*"],
			"js": ["content_scripts.js"]
		}
	],
	"background": {
		"scripts": [
			"background.js"
		],
		"persistent": false
	},
	"permissions": ["nativeMessaging"],
	"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
}

Native Messaging を利用するために「Permissions」に権限を記述します。

content_scripts.js

/** ------------------------------------------------------------------------------------------------
 *  コンテンツ側のスクリプト
 *  ・ページに対しての操作が可能(DOM等)
 *  ・クライアントアプリ(exe)呼び出し不可
 ** ------------------------------------------------------------------------------------------------ */
const main = () => {

	chrome.runtime.sendMessage(
		null, // background.js に渡すデータ
		function (response) {
			
			// クライアント処理結果
			if (response.return) {
				alert(response.msg);
			} else {
				alert("NG");
			}
		}
	);
};

document.addEventListener('DOMContentLoaded', main());

WEBサイトを表示する度に実行されるスクリプトです。

(manifest で matches に全てのWEBサイトで動作するように指定してあります)

background.js

/** ------------------------------------------------------------------------------------------------
 *  拡張機能側のスクリプト
 *  ・ブラウザが起動している間は常駐している
 *  ・拡張機能のインストール、ブラウザ起動などのイベントや
 *    コンテンツ側からの呼び出しが可能
 *  ・クライアントアプリ(exe)はここからのみ呼び出し可能
 *  ・console は 拡張機能詳細ページの検証から確認可能
 ** ------------------------------------------------------------------------------------------------ */
const APP_ID = 'doraxdora.native.messaging.sample';

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

/**
 * ブラウザ起動時
 *
 */ 
chrome.runtime.onStartup.addListener(function() {
	console.log("onStartup Start");
});


/**
 * content script から起動される処理
 *
 */ 
chrome.runtime.onMessage.addListener(function(request,sender,sendResponse) {
	console.log("onMessage Start");
	
	try {
		
		// exe を起動
		chrome.runtime.sendNativeMessage(
			APP_ID,
			{ action: "hello" },
			function(response) {
				
				// content scripts にレスポンス返却
				sendResponse({
					return: true,
					msg: response.message,
				});
			}
		);
		
		
	} catch (error) {
		// レスポンス返却
		sendResponse({
			return: false,
			msg:	"error"
		});
	}
	
	return true;
	
});

background.js 側で、SendMessage イベントをハンドリング。

また、その中で「sendNativeMessage」でアプリケーションとの通信を実行しています。

拡張機能のインストール

Google Chrome の設定>メニュー>拡張機能で拡張機能管理画面を表示します。

拡張機能管理

右上のデベロッパーモードをオンにします。

拡張機能の読み込み

左上に出てくる、「パッケージ化されていない拡張機能を読み込む」をクリックし、自作した拡張機能ファイルが格納されているフォルダを選択します。

インストール後

インストールされると管理画面に表示されるようになります。

※表示されている「ID」を控え、アプリ側の manifest.json に記述する必要があります。

これでひとまず拡張機能側の準備は完了です。

C#プログラム

manifest.json

{
  "name": "doraxdora.native.messaging.sample",
  "description": "Native Message Example.",
  "path": ".\\bin\\Release\\NativeMessagingHost.exe",
  "type": "stdio",
  "allowed_origins": ["chrome-extension://kfmilhmcbacccbjaneofbflopdbkmpef/"]
}

「name」に指定した文字列が、拡張機能側から呼び出す時と、レジストリに設定するキーと一致している必要があります。

「allowed_origins」に拡張機能の管理画面で控えた文字列を記述します。

レジストリ登録

下記をコマンドプロンプトやバッチファイルから実行します。

※manifest.json と同じディレクトリで実行する

REG ADD "HKCU\Software\Google\Chrome\NativeMessagingHosts\doraxdora.native.messaging.sample" /ve /t REG_SZ /d "%~dp0manifest.json" /f

Json用のライブラリを追加

NuGetパッケージ管理

ソリューションエクスプローラーからプロジェクトを右クリックし、「NuGetパッケージの管理」を選択します。

パッケージインストール

検索ボックスに「json」と入力、一覧から「Newtonsoft.Json」を選択しインストールします。

確認ダイアログ

確認ダイアログが表示された場合は、「OK」ボタンをクリックします。

Program.cs

メインの処理です。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.IO;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace NativeMessagingHost
{
    class Program
    {
        static void Main(string[] args)
        {
            // 拡張機能から送信されたデータの読み取り
            JObject requestData = Read();

            // 拡張機能から指定された処理を実行して結果を返す
            var action = requestData["action"].Value<string>();
            switch (action)
            {
                case "hello":

                    JObject json = new JObject();
                    json["message"] = action + " chrome extension!";

                    Write(json);
                    break;
                default:
                    break;
            }

            return;

        }

        /// <summary>
        /// 拡張機能から送られてきたデータの読み込み
        /// </summary>
        /// <returns></returns>
        private static JObject Read()
        {
            var stdin = Console.OpenStandardInput();
            var length = 0;

            var lengthBytes = new byte[4];
            stdin.Read(lengthBytes, 0, 4);
            length = BitConverter.ToInt32(lengthBytes, 0);

            var buffer = new char[length];
            using (var reader = new StreamReader(stdin))
            {
                while (reader.Peek() >= 0)
                {
                    reader.Read(buffer, 0, buffer.Length);
                }
            }

            return (JObject)JsonConvert.DeserializeObject<JObject>(new string(buffer));
        }

        /// <summary>
        /// 拡張機能に返却するデータの書き込み
        /// </summary>
        /// <param name="json"></param>
        private static void Write(JObject json)
        {
            var bytes = System.Text.Encoding.UTF8.GetBytes(json.ToString(Formatting.None));

            var stdout = Console.OpenStandardOutput();
            stdout.WriteByte((byte)((bytes.Length >> 0) & 0xFF));
            stdout.WriteByte((byte)((bytes.Length >> 8) & 0xFF));
            stdout.WriteByte((byte)((bytes.Length >> 16) & 0xFF));
            stdout.WriteByte((byte)((bytes.Length >> 24) & 0xFF));
            stdout.Write(bytes, 0, bytes.Length);
            stdout.Flush();
        }

    }
}

拡張機能とのやり取りは、Json でしか行えません。

起動後に拡張機能から送られてきたメッセージを取得し、メッセージによって返却する文字列を変更する処理としました。

起動してみる

実行サンプル

適当なページを表示した際に、拡張機能が実行されアプリから返却された文字列をアラート表示します。

うまくいきましたね。

エラーサンプル

エラーサンプル

上記は、拡張機能、レジストリ、アプリ(manifest)に記述したアプリを判別するキーが一致しなかった場合に出るエラーです。

エラー原因までなんとなく表示してくれるので大体調べたら分かりそうな感じですね。

まとめ

とにかくシンプルな仕組みでサンプルを作ってみました。

他にも色々出来そうなので、機会があったら続編も書きます。

何かのお役に立てれば。

ではでは。

 

スポンサーリンク


関連するコンテンツ