クロスドメインなJavaScriptを使ったTwitterクライアント「twicli」開発中

最近、クロスドメインJavaScriptを使ってWebブラウザだけで動くTwitterクライアント「twicli」というものを開発中です。
http://www.geocities.jp/twicli/ で公開しています。


動作はFirefox, Safari(iPhoneのMobileSafariでもOK), Operaで確認しています。IE7/8ではCookieのブロックの設定でtwitter.comのCookieをブロックしないよう変更が必要です。(Firefoxでも「サードパーティCookieも保存する」が有効になっている必要があります(デフォルトは有効)。)


twicliは専用サーバなしでWebベースのTwitterクライアントを実装できるか?というProof of Conceptとして開発しています。そのためHTMLファイル1つと画像数個あれば動くというシンプルな構成となっています(ただし中身は…)。

クロスドメインJavaScriptについて

Twitterでは他のサイトやアプリケーションからサービスが利用できるよう、Web APIを提供しています。TwitterAPIについては以下に日本語訳があります。
[観] Twitter API 仕様書 (勝手に日本語訳シリーズ)


Twitterクライアント機能をもつWebサービスは専用サーバを介してTwitterにアクセスするものが多いのですが、これは次のような理由によります*1

TwitterAPIJSON形式で結果を取得できるAPIを提供しています。JSONJavaScriptから引数呼び出しするには都合が良いのですが、実際に他のWebサービスからAjaxAPIを呼び出そうとすると、ドメインが異なるサイトへのリクエストは投げられないというAjaxのセキュリティ制約に引っかかってしまい、うまくいきません。(これに対応したJS側の動向は JavaScript 最新動向 - Shibuya.JS in Kyoto 資料が詳しい。 )


そのため対策として、
1. プロキシの役割を果たすCGIWebサービスと同じドメインに設置し、それを経由してAPIにアクセスする(例:iTwitsとか)
2. JSONPとSCRIPTタグ挿入を使う
3. ユーザ側にBookmarkletGreaseMonkeyを入れてもらう(例:id:monjudohのTwittalienとか)
という3択があります。参考:http://d.hatena.ne.jp/shinichitomita/20080811/1218459773


1はproxy CGIを設置できるサーバを用意するなど手間がかかりますが、API提供元と柔軟なやりとりが可能になります。普通Webサービスを提供するためにはサーバが必要ですから、この方法をとるのが簡単でしょう。
一方2はproxy CGIと比べて制約が増えますが、JavaScriptオンリーで構築できる(CGI不要)、自分のサーバの負荷などを気にしなくてすむ(Twitterサーバの負荷は要考慮)といったメリットがあります。
3はユーザにとって手間がかかる場合がありますが比較的何でも出来ますし、CGIも不要です。


twicliは、2のアプローチで、果たして現在提供されているAPITwitterクライアントが作れるのか?というPoCとして開発してみています。
制約としては、認証やセッション管理のためのCookieをどう取得するか、POSTで投げる必要のあるリクエストにどう対処するかがポイントになります。

APIの呼び出し方法

GETで呼び出すAPI

Twitter APIJSONで結果が帰ってくるものに対して callback=fn という引数をつけると、JSONP( fn(〜) で囲まれた形 )で結果が返されます。
そこで、

<script src="http://〜?callback=fn"></script>

というタグを生成して動的にページ内に埋め込むことで、APIを非同期に呼び出せます。
これを実現するには

// クロスドメインなJavaScriptを呼び出し
function loadXDomainScript(url, ele) {
	if (ele)
		ele.parentNode.removeChild(ele);
	ele = document.createElement("script");
	ele.src = url;
	ele.type = "text/javascript";
	document.body.appendChild(ele);
	return ele;
}

という関数を用意しておき、

script_ele = loadXDomainScript('http://〜&callback=fn', script_ele);

を呼び出せば良いでしょう。*2

追記 2008/8/31

上記はscript_eleの宣言などが面倒なので、クラス化したバージョンを使った方が良いと思います。

問題点として、現在のTwitter APIではエラーが発生した場合などにJSONPにならずにJSONが返ってきてしまうようで、うまくエラーハンドリングできなかったり、JavaScriptエラーになるという現象が起きてしまいます。対処方法は……エラーになるようなことをしない(汗)とか一定時間応答が無かったらエラーと見なすとか…苦し紛れですが。

認証

Twitter APIには認証が必要なAPIがありますが、現在は認証はTwitterとブラウザ任せになっています。つまり、あらかじめTwitterにログインしていることを前提に、API呼び出し時にブラウザが勝手にTwitter認証用のCookieを投げてくれることを期待しています。

実はこの期待はIEでは裏切られ、ローカルのCookieTwitterに投げようとした上、デフォルト設定ではCookieがブロックされてしまうため、認証が必要なAPIは動作しませんでした。。。
追記:twitter.comのCookieのブロックを設定で解除することで動作するようになります。


本当は認証用のCookieを取得するAPIもあるにはあるのですが、どうも取得したCookieをブラウザが覚えてくれない様子でうまくいっていません。。。

おかげでtwicliはログインしてる自分の名前すら認識せずに動いていたりします。でもそれじゃやっぱり困るので(自分宛/発の発言に色付けしたいなど)、自分の最新の発言を1件取得してそこから名前を得ています。しかしこれだと発言したことのないユーザだと名前が取れないという罠も…。→現在はAPI仕様変更により改善されています。

POSTで呼び出すAPI

JSONP(scriptタグを埋め込む)では、GETメソッドしか投げられないため、POSTが必要な発言(update)やfav(お気に入り)関連のAPIが呼べません。
そこで、非表示のiframe (style="display:none"にしておく)を用意しておき、formのtargetをそのiframeにしてsubmitします。例えば発言フォームであれば

<iframe style="display:none;" name="tx"></iframe>
<form name="frm" action="http://twitter.com/statuses/update.xml" method="POST" target="tx">
    <input id="fst" type="text" name="status">
</form>

のようにします。これで document.frm.submit() を呼ぶか、ユーザがフィールド内でenterキーが押すと、フォームがsubmitされてPOSTでAPIを叩いたことになります。


追記:上記ではPOST先のフレームををあらかじめ用意しましたが、動的に生成する方法も考えられます。


なお、BASIC認証が必要なAPIの場合、その場でブラウザが聞いてくるというお粗末仕様です。
また、この方法だとリクエストを投げっぱなしで、結果は受け取れませんし接続エラーなどになっても分かりません(iframeの中身は他ドメインになるのでJavaScriptからは触れなくなる)。二重iframeフレームを使ってなんとかする方法もあるのですが、ちょっと手を出せずにいます。



というわけで、細かく見ると出来てないことだらけで大変問題なのですが、それなりに動いていたりするので、とりあえず公開しちゃってる次第です。(汗々
ちなみにコードはCodeReposに入っています。

*1:もちろんケータイ用など、そもそもJavaScriptが使えないようなものはまた別です

*2:ここでは多重にscriptを呼ばないことを前提に古いscriptタグをloadXDomainScript内で除去していますが、一般的には結果を処理するfn内で除去した方が良いかもしれません。