# サーバーを使わずにクロスドメインなスタンプラリーシステム。 _published: 2011/10/12_ ![alt](http://b.hatena.ne.jp/entry/image/http://d.hatena.ne.jp/shunsuk/20111012/1318420264) オフィスの近くに寿司屋があるんですが、平日は500円で寿司ランチを食べることができます。一方、某社のレンタルサーバーのスタンダードプランは月額500円です。ということは、レンタルサーバーを解約すれば、毎月寿司ランチを食べることができます! そこで、サーバーを使わないスタンプラリーのシステムを作りました。いくつかのネットショップがあって、ショップのサイトを全部まわるとプレゼントに応募できるという状況をイメージしてください。サイトはすべて違うドメインです。 ![[5973616623_01d7c32103_c.jpg|500]] ## デモ。 デモを用意しました。メインページ1つと寿司屋のサイト3つです。ドメインが用意できなくて、すべて同じドメインに置いてます。これが全部バラバラのドメインでも動きます。寿司屋のサイトでスタンプを押すと、メインページにスタンプが貯まっていき、3つそろうと何か起こります。 - [http://shunsuk.net/rally/rally_server.html](http://shunsuk.net/rally/rally_server.html) - [http://shunsuk.net/rally/rally1.html](http://shunsuk.net/rally/rally1.html) - [http://shunsuk.net/rally/rally2.html](http://shunsuk.net/rally/rally2.html) - [http://shunsuk.net/rally/rally3.html](http://shunsuk.net/rally/rally3.html) こんな感じでページを並べて表示しておくと、寿司屋のスタンプを押すと自動的にメインページに反映されるのがわかります。 ![[20111012173524.png|400]] スタンプを消すには、メインページの「データを消す」をクリックしてください。 ## サーバーレス。 このシステムはサーバーサイドのプログラムとデータベースを一切使っていません。HTMLとJavaScriptだけなので、HTMLファイルが置けるところならどこでも動きます。もちろん、HTMLファイルにアクセスするためにはサーバーが必要なのですが、Dropboxのパブリックフォルダに置くとサーバーを用意する必要がありません。 Dropboxのパブリックフォルダにファイルを置くと、固有のURLが発行されます。そのURLを知っていれば、誰でもそのファイルにアクセスすることができます。みなさん、使ってます?そんな感じで「サーバーを使わずに」って書きました。厳密に言えばサーバーは必要なのですが、サーバー側でプログラムを動かしたりデータベースを用意する必要はありません。 ## ショップ側はソースに1行足すだけ。 ショップ側は、HTMLファイルに1行足すだけです。 ```html <script type="text/javascript" src="rally.js"></script> ``` ショップ側にいろいろ足すのは運営上大変だろうと思って、1行で済むようにしました。これで、スタンプを押すエリアが自動的に追加されます。 ## 仕組み。 だいぶ引っ張ってきましたが、種明かしです。 `localStorage` と、HTMLの `window.postMessage` を使っています。 `localStorage` はシングルドメイン限定なので、今回のように複数のドメインをまたがることができません。そこで、サーバー役のHTMLをショップ側の `iframe` に読み込んで、 `window.postMessage` でサーバー役のHTMLを置いているドメインで `localStorage` にアクセスします。 寿司屋のスタンプを押すとメインページが自動更新してるように見えますが、メインページでは数秒おきに `localStorage` を見に行ってるだけです。なので、ここがポイントですが、寿司屋とメインページの間で通信が行われてるわけではないのです。スタンプを押しても、ネットワークへのアクセスは発生しません。あくまで、ローカルにデータを保存しているだけです。 ![[20111012202324.jpg|400]] 今回のデモのソースコードでは、スタンプラリーの本拠地ページと、iframeで表示されるページを同じファイルにしてしまったので、わかりにくくなっています。 ## コード。 ちょろっと載せときます。まずは、ショップ側に見えない `iframe` を追加し、そこにサーバー役のページを表示します。 ```js this._iframe = document.createElement("iframe"); this._iframe.style.cssText = "position:absolute;width:1px;height:1px;left:-9999px;"; document.body.appendChild(this._iframe); ``` そして、 `iframe` に表示したページに `postMessage` します。 ```js _sendRequest: function(data) { this._requests[data.request.id] = data; this._iframe.contentWindow.postMessage(JSON.stringify(data.request), this.origin); }, ``` ショップ側のスタンプの表示はjQueryでやってますが、酷いコードでもうしわけないです。 ```js var stamp = $('<div>') .css({ 'margin-top': '8px', 'margin-bottom': '8px', 'margin-left': 'auto', 'margin-right': 'auto', 'width': '100px', 'height': '100px', 'line-height': '100px', }) .text('click!') .click(function(e) { remoteStorage.setValue(keyForUrl, location.href, function(key, value){ console.log('set - ' + key + ': ' + value); }); stamp.text('★') .css({ 'font-size': '100px', 'color': 'red', 'opacity': 0, }) .animate({ 'opacity': 1, }, 6 * 1000) .unbind(e); }); ``` サーバー役では、 `postMessage` をハンドルするようにしておきます。 ```js if(window.addEventListener){ window.addEventListener("message", handleRequest, false); } else if (window.attachEvent){ window.attachEvent("onmessage", handleRequest); } ``` `localStorage` の読み書き。 ```js if (value === undefined) { value = localStorage.getItem(data.key); } else { localStorage.setItem(data.key, value); } ``` いちおうクラスとオブジェクト的なライブラリにしました。JavaScriptでライブラリ作るの難しい。 ```js var server = new RallyServer({ whitelist: [ 'miyazaki-no-macbook-pro.local', 'shunsuk.net', ], pages: { 'sushi1': 'http://shunsuk.net/rally/rally1.html', 'sushi2': 'http://shunsuk.net/rally/rally2.html', 'sushi3': 'http://shunsuk.net/rally/rally3.html', }, }); if (server.completed()) { $('#twitter').show(1 * 1000); } ``` ## セキュリティ的なもの。 `postMessage` を受け取って `localStorage` に書きこむページでは、ホワイトリスト方式で送信元のドメインをチェックするようにしています。また、 `localStorage` の中身はユーザーや攻撃者が勝手に読み書きできるという前提でなければなりません。今回は、重要なデータを持たないとか、不正をされても大丈夫なキャンペーンにするとかして逃げています。 あと、Firebugとかのコンソールを見ると、セキュリティの警告が出ます。まあ、そんなもんだと思います。ChromeだかFirefoxだかは、起動オプションで警告を出さないようにもできます。 ## ソースコード。 githubで公開しています。JavaScriptでライブラリつくるの難しいですね。設計が残念な感じでもうしわけないです。 - [shunsuk/stamp_rally - GitHub](https://github.com/shunsuk/stamp_rally) ## 参考資料。 もちろん自分で考えたわけじゃなくて、パクリです。私が言ってることは、たいていパクリです。XAuth(Twitterのアレと関係ない)という、ドメイン間でユーザー認証を行う仕組みがあります。Meeboといういろんなチャットサービスを統合するサービスで使われているっぽいです。表立ってやってるかわかりませんが。 - [meebo.com](http://www.meebo.com/) ### XAuth XAuthのサイトです、いろいろ説明が載ってます。それだけじゃなく、ブラウザで実際に試せるようになっています。 - [XAuth](http://xauth.org/) ### GoogleとYahoo!の技術ブログ。 GoogleやYahoo!の技術ブログで紹介されています。 - [Using XAuth to simplify the social web - The official Google Code blog](http://googlecode.blogspot.com/2010/04/using-xauth-to-simplify-social-web.html) - [XAuth, OAuth, and Yahoo! OpenID · YDN Blog](http://developer.yahoo.com/blogs/ydn/posts/2010/04/xauth_oauth_and_yahoo_openid/) ### おすすめ。 あえて最後に書きましたが、この記事を読むだけでもいいかもしれません。わかりやすく説明してあって、サンプルコードも簡略化してあります。今回のスタンプラリーのコア部分はここのコードを使いました。 - [Learning from XAuth: Cross-domain localStorage | NCZOnline](http://www.nczonline.net/blog/2010/09/07/learning-from-xauth-cross-domain-localstorage/) JavaScript。いっかい時間とって勉強しないとつらい。。スシが食べたい。。。