chrome.extension.sendRequestへの不満

どうも, 使いにくい.

まず, いいところを先に言っておこう.

chrome.extension.sendRequestの良いところ, それはコンテンツスクリプトへのマルチキャストができること.

分かってる人ならこれ以上のことは説明しないでも分かってくれると思う.


なにが不満かというと, スクリプト全体でメッセージを受け取るということ.

最初にこのAPIを使うときは, 誰でもこんな感じのコードになると思う.

chrome.extension.onRequest.addEventListener (function (e, sender, response) {
  switch (e.action) {
    'foo': ...
    'bar': ...
    'zee': ...
});

こういうaddEventListenerがスクリプトのひーとつ.

でもでも, だんだんこれじゃぁ満足できなくなってくる. どういうことかというと,

function Foo () {
}
Foo.prototype.init = function () {
  chrome.extension.onRequest.addEventListener (function (e, sender, response) {
    switch (e.action) {
      'foo': ...
    }
  });
};

var foo1 = new Foo;
var foo2 = new Foo;
var foo3 = new Foo;

みたいに, どんどんインスタンスを作っちゃう.

すると, どうなるか. 一つのsendRequestに対してfoo1くんもfoo2くんもfoo3くんもワーワー喚き出す.

なんというか, これはこれで使い道がある気がするけど, 僕は他のタブからfoo1くんだけに話したいのだ.

こういうことを考えてると, こうなってくる.

function Foo () {
  this.id = createUnique ();
}
Foo.prototype.init = function () {
  var self = this;
  chrome.extension.onRequest.addEventListener (function (e, sender, response) {
    switch (e.action) {
      'foo':
        if (e.id === self.id) {...
        }
    }
  });
};

問題ないではないか... はっはっは.

しかし, そもそも送り主はメインのページのオブジェクトのidをどうやって知るのだろう.



話が抽象的すぎて分からないかもしれない. 今考えてる具体的な問題は至ってシンプルで, 以下のようなものだ.

Twitterのクライアントの認証を通す画面, あのPINを自動でアプリケーションに送る方法だ.

色々な実装の仕方が多分あって, 十人十JSとはこのことだと思う.

某クライアントのコードを読んでたら, 次のようにやってた.

Contents scriptでどんなページにもスクリプトを埋め込み, URLがhttps://api.twitter.com/oauth/authorizeであれば, PINコードを探してアプリケーションに飛ばす, そしてタブを閉じる.

問題は, 果たしてこのようなケースにContents scriptを使うのが妥当だろうか?という話しである.

勿論NOだ. Contents scriptってのは全てのページでスクリプトがゼロから動く.

Contents scriptにjqueryなんかを使ってる拡張もあるけど, ありゃなんだって話... いやホント.

Twitterの認証のためだけにContents scriptは相応しくないですよ.


今考えてる実装は, 簡単に言うと次のような感じ.

URLを作る → chrome.tabs.create でtabidをゲット → chrome.tabs.executeScript でPINゲット

これで確かに, PINはゲットできた.


最初に言ってたFooっていうオブジェクト, これは, Twitter Consumerだ.

え? 一つのアプリケーションは一つのConsumerだって?

ごめんちょっと何言ってるのか分かんない.

var consumer1 = new TwitterConsumer (consumerKey, consumerSecret);
var consumer2 = new TwitterConsumer (consumerKey, consumerSecret);
consumer1.requestToken ({
  success: function (data) {
    chrome.tab.create ({
      url: this.createPinUrl (data)
    }, function (obj) {
        var tabid = obj.id;
        this.getPinTimeout = setTimeout (function () {
          chrome.tabs.executeScript (tabid, { file: "getpin.js" });
        }, 1000);
    });
  }
});
consumer2.requestToken ( ... );

なんかこういうの. メチャメチャ適当なコード(こういうthisとか, ぜーーーったいに動かないけど... )で申し訳ないけど, 順番はこんな感じ.

あ, いやもうちょっとマシなコード書いてますよ. ホント.


ここで, さっきの問題が生じる. getpin.jsにどうやってconsumer1とかconsumer2の情報を送る?

2つのConsumerが開いた2つの認証画面から, 間違えずにPINを返すには?

イメージとしては,

          chrome.tabs.executeScript (tabid, { file: "getpin.js?id=" + this.id });

みたいなん.

ってこういうのは無理なのよ...

...





... ココまで勢いで書いてきたけど, 文章にしてみればあまり難しくなかったなって...

一つの汚いやり方は, PINのタブで実行するコードを動的に変える方法.

          chrome.tabs.executeScript (tabid, { code: createCode (self.id) });

おそらく, createCodeは文字列結合でコードを作るみたいな, あまりやりたくない方法.


二つ目, これはたった今思いついた方法.

getpin.jsに, アプリ側のconsumerの番号を教えようっていうのがダメな考え方なんだ...

getpin.jsからtabのidをpinと一緒に送ってやればいい. まさに逆転の発想.

consumer側は, tabを作った時にそのidは知ることができてるよね.

だから, 帰ってきた{ pin: ..., tabid: ... }のtabidと比較したらいい.



あーなんか自己解決しちゃったわー

JavaScriptオモシレェー

Google Chromeオモシレーわ, やっぱ

なんかchrome.extension.sendRequestでもあんま問題ないか


追記: OAuth2.0についてはこちら


更に追記: JavaScriptのコードを埋めこまなくてもOAuth2.0ならアプリのオリジンのアドレスにリダイレクトされるので, document.URLからaccess_tokenを読み取って, それを本体アプリの方にsendRequestしてからwindow.closeすればいい. 詳しくは何時か書く.