NativeMessagingの処理とNativeMessagingHostを作るときのTips

試しに WebExtensions と Native Messaging Host を作って message のやり取りをしてみた時にハマったのでメモ。

WebExtensions側

  1. runtime.connectNative() で Host を起動する。起動したら runtime.Port が取得できる
  2. Host との communication はこの Port を通じて行う
  3. Port の diconnect() を呼ぶと起動した Host は終了する
    • disconnect() を呼ばずに破棄すると起動した Host は迷子プロセスになる
    • 例えば Extension を作る際になんらかの View Framework を利用している場合、この Framework の component の lifecycle と整合させる必要がある
  4. この Port には isConnected() のような API は存在せず、何らかの理由で disconnected な Port に message を送ると Error が発生する
    • runtime.onDisconnect プロパティで接続断は検知できるので、そっちのイベントで処理する

Native Messaging Host側

勝手が分かるまではなかなかつらい。

  1. STDIN, STDOUT が WebExtensions に握られるのでいつものデバッグ方法は使えない
    • STDERR に出力すればブラウザに何かメッセージを出すことはできるが、それも connection が keep できている場合だけなので、例えば例外のある言語の場合は例外の起きそうな場所で拾いまくる必要がある
  2. 先頭 32bit でその長さ + JSONエンコード済みのデータ(これの長さが先頭32bitに入る)のデータを読み書き
    • native endian で write はともかく read はちょっと気を使う
  3. 拾い損ねた例外や普段 STDOUT に出力したい情報は Logger などで外に出していくしかない
    • 例えば Ruby では $@ で最後の Backtrace を拾えるので、at_exit でこいつを Logger などに渡してやる
    • この時点ですでに WebExtensions との connection は切れている
  4. STDIN からの入力はいつ来るか分からないので無限ループを作る必要がある
    • 終了は WebExtensions から行われるので WebExtensions にバグがなければ問題はない(危険)

サンプル

ブラウザがHost起動時に渡す情報が変わる

これがめちゃくちゃハマった。

Native Messaging Host を起動する際に環境変数の渡し方がブラウザによって違う。この環境変数に依存する部分があると挙動が変わる。

これによって何が起きるかというと、例えば

#! /usr/bin/env ruby

のような shebang から Ruby を起動する場合に、以下のように異なる Ruby インタプリタを起動してしまう。

  • web-ext 経由の Firefox は rbenv 経由の Ruby
  • Chrome は system の Ruby

結果、Firefox と Chrome で WebExtensions の挙動が変わってしまっているように見える。

web-ext 経由でない Firefox では試していないが、Host 起動時に利用できる環境が変わっている可能性は十分にあるので、起動したプロセスから確認するクセをつけた方がよさそう。

cf.

感想

Native Messaging Host の開発は STDIN, STDOUT を奪われるのでだいぶ勝手の違う開発になるのと、普段から daemon process を扱っていないと勘所が分からずに苦労する。本当に力不足を感じましたとさ。

先にこの辺の取り回しをよくするライブラリを整備するといいんだろうなぁ。あと処理が複雑になると message に構造を持たせてそれで処理を分ける必要が出てくるだろうから、その router のようなものも欲しくなりそうな気がする。

あーあれだ。dRuby 使えばいいのか?

More