2019-09-14

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 使えばいいのか?

About

例によって個人のなんちゃらです

Recent Posts

Categories

Tool 日々 Web Biz Net Apple MS ことば News Unix howto Food PHP Movie Edu Community Book Security Text TV Perl Ruby Music Pdoc 生き方 RDoc ViewCVS CVS Rsync Disk Mail FreeBSD Cygwin PDF Photo Zebedee Debian OSX Comic Cron Sysadmin Font Analog iCal Sunbird DNS Linux Wiki Emacs Thunderbird Sitecopy Terminal Drawing tDiary AppleScript Life Money Omni PukiWiki Xen XREA Zsh Screen CASL Firefox Fink zsh haXe Ecmascript PATH_INFO SQLite PEAR Lighttpd FastCGI Subversion au prototype.js jsUnit Apache Trac Template Java Rhino Mochikit Feed Bloglines CSS del.icio.us SBS qwikWeb gettext Ajax JSDoc Rails HTML CHM EPWING NDTP EB IE CLI ck ThinkPad Toy WSH RFC readline rlwrap ImageMagick epeg Frenzy sysprep Ubuntu MeCab DTP ERD DBMS eclipse Eclipse Awk RD Diigo XAMPP RubyGems PHPDoc iCab DOM YAML Camino Geekmonkey w3m Scheme Gauche Lisp JSAN Google VMware DSL SLAX Safari Markdown Textile IRC Jabber Fastladder MacPorts LLSpirit CPAN Mozilla Twitter OpenFL Rswatch ITS NTP GUI Pragger Yapra XML Mobile Git Study JSON VirtualBox Samba Pear Growl Mercurial Rack Capistrano Rake Win RSS Mechanize Sitemaps Android JavaScript Python RTM OOo iPod Yahoo Unicode Github iTunes God SBM friendfeed Friendfeed HokuUn Sinatra TDD Test Project Evernote iPad Geohash Location Map Search Simplenote Image WebKit RSpec Phone CSV WiMAX USB Chrome RubyKaigi RubyKaigi2011 Space CoffeeScript Nokogiri Hpricot Rubygems jQuery Node GTD CI UX Design VCS Kanazawa.rb Kindle Amazon Agile Vagrant Chef Windows Composer Dotenv PaaS Itamae SaaS Docker Swagger Grape WebAPI Microservices OmniAuth HTTP 分析基盤 CDN Terraform IaaS HCL Webpack Vue.js BigQuery Middleman CMS AWS PNG Laravel Selenium OAuth OpenAPI GitHub UML GCP TypeScript SQL Hanami Document SVG AsciiDoc Pandoc DocBook Develop Jekyll macOS Node.js Vite Heroku Transformer AI Data Cloud Wasm