2009-05-30

Google Friend Connect の two-legged OAuth とその使いどころを確認してみた

こんばんは。なかじまんです。なんか Google Wave の熱気がスゴイんですが、Google Friend Connect ネタでいきます。

はじめに Google Friend Connect (GFC) の two-legged OAuth を使って、ユーザのプロフィールを取得して表示してみました。スクリーンショットはこんな感じです。



そして、ソースコードはこんな感じです。いつもどおり Aptana Jaxer を使っています。JavaScript なのでクライアントサイドに見えるかもしれませんが、サーバサイドで動かしています。ややこしくてごめんなさい。

<script type="text/javascript" src="sha1.js" runat="server"></script>
<script type="text/javascript" src="oauth.js" runat="server"></script>
<script type="text/javascript" src="jquery.js" runat="both"></script>

リクエストの署名は Google Code の OAuth JavaScript ライブラリを使っています。表立って JavaScript ライブラリのリンクは存在しませんが、SVN や Google Group を探るとたどり着けます。

<script type="text/javascript" runat="server">
jQuery(function($) {

var action = 'http://www.google.com/friendconnect/api/people/@me/@self';

var accessor = {
consumerSecret: '{consumerSecret}'
};

var message = {
method: 'GET',
action: action,
parameters: {},
};

OAuth.setParameter(message, 'oauth_consumer_key', '*:03382920006806842951');
OAuth.setParameter(message, 'oauth_version', '1.0');
OAuth.setParameter(message, 'oauth_timestamp', OAuth.timestamp());
OAuth.setParameter(message, 'oauth_nonce', OAuth.nonce(6));
OAuth.setParameter(message, 'xoauth_requestor_id', '12976690996215323511');

OAuth.SignatureMethod.sign(message, accessor);

コンシューマキーとそのシークレットを使って、リクエストに署名しています。



↑コンシューマキーとシークレットは GFC サイトで入手できます。

var url = OAuth.addToURL(action, message.parameters);

var text, person;
try {
text = Jaxer.Web.get(url, { as: 'text' });
person = eval('(' + text + ')').entry;
} catch (e) {
throw new Error(''+e);
}

$('#person span').text(person.displayName);
$('#person img').attr('src', person.thumbnailUrl);
$('#person textarea').val(text);
});
</script>

署名したリクエストを発行して、該当ユーザのプロフィールを取得しています。そして、プロフィールを HTML として表示しています。

<p id="person">
<span></span><br />
<img /><br />
<textarea rows="10" cols="80"></textarea>
</p>

プロフィールを表示する HTML です。textarea 要素にレスポンスのテキストをそのまま表示しています。

GFC のドキュメントでは RESTful API 仕様どおり @me の部分に guid を指定できる と解説があるのですが、次のパターンを試してみたのですが、すべて 401 エラーとなってしまいます。あれれ。何か間違っていますでしょうか。ですので、今回は @me として xoauth_requestor_id でユーザ ID を指定しています。

people/12976690996215323511/@self
people/friendconnect.google.com:12976690996215323511/@self
people/friendconnect.google.com%3A12976690996215323511/@self

GFC の two-legged OAuth が使えることが確認できたので、次はその使いどころです。

GFC の Server-side Integration のデモとして、次のような The Chow Down というサイトとその ソースコード が公開されています。その中で two-legged OAuth を使っています。



two-legged OAuth の主な使いどころは、(あたり前ですが)ユーザが GFC にサインインしていないときです。The Chow Down は、サイト独自のユーザ登録による認証と GFC による認証を併用しています。

GFC にサインインしているときは、Cookie の fcauth トークン を使えば The Chow Down のすべてのリソースにアクセスできるので、このとき two-legged OAuth を使う絶対的な理由はありません。

サイト独自の認証でサインインしているとき、The Chow Down リソースにアクセスするために two-legged OAuth が必要になります。プロフィールなどのデータは手元に保存してあるので two-legged OAuth は必要ないのでは ... と疑問に思いましたが、GFC のドキュメントをよく読んでみると、取得したプロフィールなどのデータはキャッシュはしていいけれど、ユーザID 以外は永続的に保持してはならないとあります。

ですので、The Chow Down は、GFC と関連付くユーザのプロフィールを参照するとき、キャッシュを調べて、キャッシュに存在しないとき、two-legged OAuth でプロフィールを取得してキャッシュするようになっています。

じゃぁ逆にいうと Cookie の fcauth トークンは不要では... と感じてくるのですが、少なくとも GFC にサインインしないと、該当ユーザの ID が判別できません。また、The Chow Down は GFC にサインインしているとき、@viewer/@friends を使って GFC ネットワークの友達関係を持ち込んで、アプリケーション上でうまく活用しています。

The Chow Down の設計方針は
The Chow Down - Server-side integration walkthrough
に解説があるので、GFC を自サイトに統合しようと考えているときは、まずはじめに一読するとよいと思います。

2009-05-29

Google Code Project Hosting の Wiki をローカライズする方法

おはようございます。なかじまんです。

Google Code Project Hosting の Wiki コンテンツを、日本語と英語などの複数の言語で編集して、公開する方法を試してみました。

Project Hosting サイトで編集した Wiki コンテンツは、次のように SVN リポジトリに保存されます。wiki/*.wiki がそれぞれのページです。

http://{YOUR PROJECT}.googlecode.com/svn/wiki/
Tabs.wiki
MiniMessage.wiki
Flash.wiki

日本語のページを用意したいときは、次のように ja ディレクトリを作成して、そのディレクトリ中に Wiki コンテンツを保存します。wiki/ja/*.wiki が日本語のページです。

http://{YOUR PROJECT}.googlecode.com/svn/wiki/
ja/
Tabs.wiki
MiniMessage.wiki
Tabs.wiki
MiniMessage.wiki
Flash.wiki

言語設定が日本語のブラウザから閲覧すると、次のように wiki/ja ディレクトリ中の日本語のページが表示されます。Locales で ja が選択されていることからも確認できます。



言語設定が英語のブラウザから閲覧すると、次のように wiki ディレクトリ中の英語のページが表示されます。Locales で en が選択されていることからも確認できます。



言語設定が日本語でも英語でもないブラウザから閲覧すると、wiki ディレクトリ中の英語のページを表示します。また、日本語のブラウザから閲覧し、wiki/ja ディレクトリにページが存在しないときは wiki ディレクトリ中のページを表示します。上の例だと、Flash.wiki は常に英語のページを表示します。

日本語以外の多言語のページを用意したいときは、wiki/ja ディレクトリと同じように、ディレクトリ名を言語コード(地域も含む) にして、そのディレクトリ中にファイルを保存すればよいです。

Wiki ページのコメント投稿は各言語で共用されました。どの言語からコメントを投稿しても、各言語のページで表示されます。

いくつか不都合な点もあります。

ブラウザから編集できるのは wiki ディレクトリ中の英語のページのみです。日本語のページは、ファイルを編集して SVN クライアントからコミットしないといけません。上の例だと、日本語のページに Edit this page がないことが分かります。※でも編集できてもよさそうな気はするんですよね。何か方法はないだろうか。

Wiki ページの一覧は、ブラウザの言語設定によらず、wiki ディレクトリ中の英語ページの #summary が表示されます。※言語設定で切り替えてくれるといいんだけどなぁ。



wiki ディレクトリの言語を英語以外で構成するときは、次のように Administer タブの Wiki セクションの Default Language を変更すればよいです。Default Language は wiki ディレクトリ中のページの言語を表すもので、wiki/ja などのディレクトリを選択するものではないので注意が必要です。



↑といったところを総合すると、Default Language は英語として、wiki ディレクトリは英語のページとする。そして、必要に応じて言語のディレクトリとページを追加する。そうすると、言語のディレクトリがあれば該当言語のページを表示するし、ディレクトリがなければ英語のページを表示することになるので、グローバルな Wiki として運用しても不都合ないんじゃないかな。

2009-05-22

Google Friend Connect API の initOpenSocialApi をもっとよく知る

おはようございます。なかじまんです。

はじめにお知らせです。

Google Developer Day 2009 は 6月9日(火) です。Google Developer Day 2009 Japan ハッカソンの募集 もはじまっています。ハッカソンは 10日(水) と 11日(木) です。昨年はあっという間に定員に達しましたのでお早めに。

本題です。

Google Friend Connect API (GFC API) の initOpenSocialApi メソッド は、ユーザのアイデンティティに変化があったとき、コールバックするという仕様なのですが、そのコールバックのタイミングとか状態の関係がどうも分かりにくいです。

そこで、次のソースコードを、様々な条件下で動かして、initOpenSocialApi メソッドの振る舞いを確認してみました。

google.friendconnect.container.loadOpenSocialApi({
site: '03382920006806842951',
onload: function() {
console.log(arguments);
console.log(document.cookie);
console.log(opensocial.hasPermission());
}
});

コールバックのタイミング

ページをロードしたとき、コールバックします。このコールバックは、GFC API が使える状況が整ったというタイミングと言い換えることができます。このコールバック後でないと、一部の GFC API は正しく動作しません。

サインインしたとき、コールバックします。サインインとは renderSignInButton メソッドのボタンや requestSignIn メソッドを指します。サインアウトしたときも同様です。サインアウトとは requestSignOut メソッドを指します。

requestSettings メソッドで、プロフィールや設定を変更したときも、コールバックします。なお requestInvite などその他のメソッドでは、コールバックしませんでした。

コールバックの引数

コールバックは、常に2つの引数をとります。1つ目は、セキュリティトークンを表す文字列です。コールバックの度にトークンは変化します。2つ目は、何かよく分からない数値です。コールバックする度に変化しますが、パターンがありそうな気もしますが、その意味や使い道は把握できていません。

cookie と有効期限

サインインしているとき、fcauth{siteId} という cookie にトークンが格納されます。cookie は、サインインのタイミングで変化し、有効期限は1年程度のようです。requestSettings メソッドは cookie 値は変化しませんでした。サインアウトすると cookie は削除されます。

また fcauth{siteId}-s という cookie もあります。fcauth{siteId} と値は同じですが、有効期限はブラウザセッションです。また、fcauth{siteId}-s は、自サイトで明示的にサインイン(requestSettings メソッドも含む)したときのみ格納されます。

サインインの有無

cookie の有無からサインインしているか判断できます。

opensocial.hasPermission は、サインインしていても、サインアウトしていても、常に false でした。パーミッションを表現していないので未実装なのでしょう。ですので、サインインの判断には使えません。

また、GFC API の実例の中には opensocial.DataRequest で VIEWER のプロフィールを取得して、成功したか、失敗したかの結果から、サインインの有無を判断するものもありました。

どうしようか考えてること

↑ということなので、initOpenSocialApi メソッドのコールバックは、何が引き金となって呼び出されたのか、判断することができません。ですが、何回目のコールバックか、cookie の有無とその変化を見て判断できそうな気もします。

もうしばらく検討します。何かよい方法があったらお伝えします。

2009-05-18

opensocial-jquery 1.0.2 をリリースしましたのでざっくりと紹介します。

こんばんは。なかじまんです。opensocial-jquery 1.0.2 をリリースしましたので、ざっくりと紹介します。

キャッシュ制御

jQuery.container.cache を使って、外部データのキャッシュ有無を、まとめて切り替えられるようにしました。開発中のデバッグで役に立つんじゃないかと期待しています。

jQuery.container.cache = false とすると、次のとおり、gadgets.io.makeRequest、gadgets.io.getProxyUrl、gadgets.flash.embedCachedFlash, embedFlash など、外部データのキャッシュをすべて無効にできます。

$.container.cache = false; // nocache=1

// gadgets.io.makeRequest
$.getJSON('http://example.com/data.json',
function(data, status) {}
);

// gadgets.io.getProxyUrl
$('<img/>').attr('src',
$.proxy('http://example.com/data.gif')
);

// gadgets.flash.embedCachedFlash, embedFlash
$('#container').flash('http://example.com/data.swf');

jQuery.container.cache は URL パラメータの nocache と連動します。nocache=1 のときは false となり、それ以外のときは true となります。nocache=1 のときは、自動的にキャッシュが無効になるため、明示的に jQuery.container.cache を使う機会は少ないかもしれません。

mixi Platform の改善

jQuery.ajax とショートカットを使って、外部データを取得できるようになりました。mixi の gadgets.io.makeRequest は、コールバックするデータに HTTP ステータスコードを表す rc プロパティを含まないため、正常と判断できず、常にエラーとなっていたようです。

jQuery.container を使って mixi Platform かどうかを判別できるようにしました。

if ($.container.mixi) {
// // This container is mixi.
}

さらに HTML 要素に対して、mixi Platform を表すクラスを自動的に追加し、CSS のクラスを使って mixi Platform だけの CSS を適用できるようにしました。

.mixi div.photo img {
/* This container is mixi Platform. */
}

Google Friend Connect ガジェットの対応

Google Friend Connect ガジェットに対応しました。OpenSocial API に加え Google Friend Connect 固有の @admins セレクタを使って、管理者のプロフィールを取得できます。

$.getData('/people/@owner/@admins', {},
function(people) {}
);

jQuery.container を使って Google Friend Connect かどうかを判別できるようにしました。

if ($.container.friendconnect) {
// // This container is Google Friend Connect.
}

さらに HTML 要素に対して、Google Friend Connect を表すクラスを自動的に追加し、CSS のクラスを使って Google Friend Connect だけの CSS を適用できるようにしました。

.friendconnectdiv.photo img {
/* This container is Google Friend Connect. */
}

なお、Google Friend Connect API (インページ)には対応していません。検討中です。

JSDeferred の強化

JSDeferred のクラスメソッドを jQuery.deferred, jQuery.parallel, jQuery.wait, jQuery.next, jQuery.call として使えるようにしました。また、JSDeferred のインスタンスメソッドとして JSDeferred.wait、jQuery.ajax とショートカットを追加しました。

opensocial-jquery は opensocial.DataRequest のバッチリクエストをサポートしませんが、jQuery.parallel を使うことで、いくつかのリクエストをまとめることができます。

$.parallel([
$.get('http://www.yahoo.co.jp/'),
$.getData('/people/@viewer/@self'),
$.getData('/people/@viewer/@friends')
]).next(function(data) {
console.log(data[0]); // http://www.yahoo.co.jp/
console.log(data[1]); // /people/@viewer/@self
console.log(data[2]); // /people/@viewer/@friends
}).error(function(e) {
console.log(''+e);
});

opensocial-jquery は、すべてのリクエストを jQuery.ajax とショートカットで表現するため、上記のように gadgets.io.makeRequest と opensocial.DataRequest をまとめてリクエストできたりします。いずれかでエラーが発生すれば error メソッドに流れますので、エラーハンドリングも手軽です。

Google Developer Day 2009 はエンタープライズセッションに注目です

おはようございます。なかじまんです。

先日(5/12)、Google Developer Day 2009 Japan (GDD 2009) のサポーターミーティングに参加しました。そのミーティングの様子や内容は、次のとおり、他のサポータのみなさんが詳しく紹介しています。
GDD 2009 は GDD 2008 とは異なり、エンタープライズのセッションがあり、Google AdWords 関連、Google Apps 関連を予定しているとのことです。また、Google エンタープライズパートナー やその取り組みの紹介みたいなのもあるそうな。

Google Developer Day 2009 は 6/9(火) パシフィコ横浜です。もうすぐですね。

2009-05-15

REST protocolを使って Google Friend Connect をサーバサイドで統合できる雰囲気を確認してみた

おはようございます。なかじまんです。

Google Friend Connect (GFC) というと、ガジェットや JavaScript API といったクライアントサイドの実装を連想することが多いと思いますが、Google Friend Connect APIs は REST protocol にも対応するので、GFC をサーバサイドで統合するという選択もありうるわけです。

そのサーバサイドでの統合がどんな雰囲気になるのか、簡単なサンプルを書いて試してみました。GFC にサインインして VIEWER のプロフィールを取得して表示するというものです。どんな振る舞いをするのか、その動画を用意しました。



ソースコードは、次のとおりです。Aptana Jaxer を使っていますので、runat="server" がサーバサイドのコードです。

<script type="text/javascript" src="/js/jquery.js" runat="both"></script>
<script type="text/javascript" runat="server">
jQuery(function($) {
var fcauth = Jaxer.Util.Cookie.get('fcauth03382920006806842951');
if (!fcauth)
return $('.signin,.signout').toggle();

GFC にサインインすると cookie の "fcauth+サイトID" としてトークンが払い出されます。ですので、サーバサイドでは、この cookie があるかないかで、サインインしているか判断できます。ここでは cookie の有無で、サイインとサインアウトのリンクと切り替えています。

var url = 'http://www.google.com/friendconnect/api' +
'/people/@viewer/@self?' + $.param({ fcauth: fcauth });

VIEWER のプロフィールを取得する URL を組み立てます。このとき fcauth パラメータに、前述の cookie 値を指定します。

var text, person;
try {
text = Jaxer.Web.get(url, { as: 'text' });
person = eval('(' + text + ')').entry;
} catch (e) {
throw new Error(''+e);
}

VIEWER のプロフィールを取得します。JSON 形式のレスポンスをデコードしています。entry がプロフィールになります。

$('#person span').text(person.displayName);
$('#person img').attr('src', person.thumbnailUrl);
$('#person textarea').val(text);
});
</script>

取得したプロフィールから名前とサムネイルを取り出して表示します。あと、デバッグのためレスポンスをそのまま表示しています。

<script src="http://www.google.com/jsapi"></script>
<script type="text/javascript">google.load('friendconnect', '0.8');</script>
<script type="text/javascript">
google.friendconnect.container.setParentUrl('/');
google.friendconnect.container.loadOpenSocialApi({
site: '03382920006806842951',
onload: function() {
if (!window.times)
window.times = 1;
else
window.times++;
if (window.times > 1)
location.reload();
}
});
</script>

GFC のサインインは JavaScript API を使って、クライアントサイドで実現しています。

loadOpenSocialApi は、ウェブページを表示したときや、サインイン、サインアウトなど、GFC アカウントの状態に変化があったとき呼び出されます。ここでは window.times に呼び出された数を記憶しておいて、1回目はウェブページを表示したとき、2回目以降は、サインインまたはサインアウトと判断して、ウェブページをリロードを制御しています。

なお、(試していませんが)GFC の REST protocol は two-legged OAuth も使えます。

<p>
<a class="signin" style="display:none;" href="#signin"
onclick="google.friendconnect.requestSignIn();">Sign in</a>
<a class="signout" href="#signout"
onclick="google.friendconnect.requestSignOut();">Sign out</a>
</p>
<p id="person">
<span></span><br />
<img /><br />
<textarea rows="10" cols="80"></textarea>
</p>

サインインとサインアウトのリンク、VIEWER のプロフィールを表示するタグです。

クライアントサイドとサーバサイドとそれぞれを組み合わせ... といった統合のバリエーションが多数ありうるわけです。複雑すぎやしないかという印象もありますが、ウェブサイトに統合することを前提としたプロダクトであると思えば、結果的にこういう形になるのかなとか、まだまだどう評価してよいか判断できない感じです。

2009-05-03

OpenSocial Hackathon in April 2009 に参加してきました

おはようございます。なかじまんです。OpenSocial Hackathon in April 2009 に参加してきましたので、その成果をお伝えします。

今回の会場は、リクルートメディアテクノロジーラボ(銀座8丁目)で、そのオフィス内の共用スペースのようなところです。次の写真のとおり、いわゆる会社やホテルなどの会議室のようなホワイトな堅苦しいイメージはないので、仕事の気分から離れて楽しむことができました。

さらに、メディアテクノロジーラボさんから なんと無料でランチ までご馳走になってしまいました。また、その後の懇親会でも会費 1,000 円でいろいろ用意してもらったりして、財布の厳しいこのご時世に、本当にありがとうございます。


※写真は gooホーム Developer's Recipe からお借りしました。

なかじまんは @stomita さんとコラボして、ガジェット間(クロスドメイン)でのメッセージ交換を中心ネタとして、現実にどういったことが実現できるのか、技術調査も兼ねての Hackathon ということにしました。

今回は gooホーム が主催する Hackathon ということもあり、goo Social Platform がネタ元です。午前中を使って、ガジェットの骨格をコーディングしつつ、ガジェット間でのメッセージ交換の対応状況を試したところ、gadgets.rpc とそれをベースとした gadgets.pubsub の API が使えることが確認できました。

いいねぇ~これ!ってことで、午後の前半を使って、緯度経度の位置情報を軸として、3つのガジェットが協調して連携するデモを作りました。↓それがこれです。動画にしました。



左側のガジェットは、ホットペッパーのグルメサーチ API を使って、ある位置のグルメスポットをサムネイル表示するものです。上側のガジェットは、スマッチ!の物件情報検索 API を使って、ある位置の物件をリスト表示するものです。下側のガジェットは、単純に Google Maps API を使って地図を表示するものです。

それぞれのガジェットは、それ単体で完結して動作します。それに加えて、位置情報で連携するようにしています。物件を選択すると、その場所を地図で表示し、かつ物件の周囲にあるグルメスポットをサムネイル表示します。また、グルメスポットを選択すると、その場所を地図で表示し、かつグルメスポットの周囲にある物件をリスト表示します。

この仕組みを簡単に図示してみました。次の図は、グルメスポットを選択したとき、物件と地図が連携するシナリオを表したものです。



コンテナは、ガジェット間の仲立ちとなって、ガジェット間のメッセージを中継します。メッセージを受信するガジェットは gadgets.pubsub.subscribe メソッドを使って、メッセージの受信を待ちます。メッセージを送信するガジェットは gadgets.pubsub.publish を使って、メッセージを送信します。いわゆるデザインパターンでいうところの Observer パターンです。

グルメスポットを選択すると gadgets.pubsub.publish メソッドを使って、選択したグルメスポットの緯度経度をメッセージとして送信します。他のガジェットはgadgets.pubsub.subscribe メソッドを使って、その緯度経度のメッセージを受信して、ガジェットの内容を再表示します。

なお、メッセージはチャネルという経路を指定できるので、いろいろなガジェットとメッセージが混ざったとしても、異なるチャネルを使っていれば、ガジェット間で影響を受けることはありません。

そして、午後の後半を使って、さらに用途を広げたデモを作りました。コンテナを gooホームから Google Friend Connect に入れ替えたものです。↓それがこれです。動画にしました。



大きな地図は Google Maps API を使って、自分のサイト(任意のサイト)に地図を表示しています。そして、前述のガジェットをその地図上にフローティングして表示しています。グルメスポットを選択すると、大きな地図がグルメスポットの位置までスクロールします。物件を選択したときも同様です。

この仕組みを簡単に図示してみました。Google Friend Connect を導入すると、自分のサイトとガジェット間でメッセージ交換して連携できたりするんですね。



なお Google Friend Connect は gadgets.pubsub が使えないので、gadgets.rpc を使って gadgets.pubsub 相当を実現してやる必要があります。詳しくはソースコードを見てみてください。hackathon-jpgfcxd にあります。また @stomita さんが Google Friend Connect のクロスドメイン(gadgets.rpc)をさらに深く言及していますので、読んでみるといいでしょう。

Google Friend Connect API に gadgets.io.makeRequest 相当を自前で実装する
作るにあたって、Google Friend Connect API のアーキテクチャを若干解析してみた。GFCのOpenSocial API呼び出し時にサーバへの通信がおこなわれるが、これはすべてガジェット経由のフレーム間RPCになっている。OpenSocial API通信のためのガジェットみたいなのが用意されていて(http://www.google.com/friendconnect/gadgets/osapi-0.8.xml)、これらがコンテナ側(GFCスクリプトをロードしたページ)と協調動作する。