「node.js」と一致するもの

これまでにPassportでFacebook認証をやったことはありますが、OAuth2.0のプロバイダを提供するためにどんなライブラリがあるのか、というのが今回の話です。

OAuth2.0のクライアントライブラリは多くありますが、プロバイダライブラリはどうでしょうか。思ったよりは多かったです。条件は、express/connectのmiddlewareとして使えることです。

現時点でのStar数順に並べます。

今回は、一つずつコメントして行こうと思います。

まず、oauth2orizeですが、これはだいぶリッチな感じです。toolkitとあるように、色々な機能があります。connect middlewareにもなるようですが、standaloneでも動かすこともできる様子。testやexampleも豊富で信頼感あります。

node-oauth2-providerは歴史がある感じです。機能は多くなく、シンプルです。ストレージもコーディングしないといけませんが、個人的には一番好みのライブラリです。

oauth2_server_nodeは、最近更新されていません。oauth2の仕様もdraft10とのことです。

node-auth2-serverは、まだactive developmentということでドキュメントが追いついていないようです。しかし、ドキュメントを見る限りでは機能も豊富で、シンプルなmiddlewareとしても使えそうなので、stableになれば、oauth2orizeよりはexpressで使いやすそうです。

OAuthも、READMEによるとdraftの仕様を参照しており、その後メンテされていない様子です。

oauth2-expressは、READMEが空です。よく見たらコードも未完成でした。

connect-oauth2も、WIPとなっていて1年ほどコミットがないので、中断したプロジェクトでしょうか。いいパッケージ名なのに惜しい。

node-oauth20-providerは、StarもFolkもゼロというプロジェクトですが、見た感じはだいぶしっかりしています。最近コーディングを始めた様子。コミットログをよく見ると、node-oauth2-providerの存在を知らなかったようですね。node-oauth2-serverと比較すると、redis storageの対応が進んでいますが、全体的には劣る感じがします。また、コミュニティがついていないのが痛いですね。今後、作者の進め方次第では状況は変わることはあるでしょう。

以上、まとめると、現時点では、 シンプルで安定しているけど自分でコーディングしないといけないnode-oauth2-providerか、機能豊富でアクティブなnode-oauth2-serverがよさそうです。

でも、どっちも使ってみたわけではないので、ほんの参考程度です。

Railsより簡単にSNSを作れるようにすることを目指したライブラリ social-cms-backend を改良しました。

これまでは、認証機構はFacebook連携のみが実装されていたのですが、 Facebookに依存したくないアプリを作るケースも考え、 今回HTTP Digest認証も入れました。 Formによるパスワード認証も一応あったのですが、これは動作確認用で、not for production扱いです。 やはり生のパスワードがネット上を流れるのはよくないということで。 (SSLなしの想定)

Digest認証のサーバ側は passport-http を使いました。 Digest認証をそのまま使うのは不便なので、2つ工夫をしました。 一つは、RememberMeクッキーを併用することで、 もう一つは、JavaScriptでDigestを生成して認証することです。 Digest認証の最大の問題はUIがブラウザ依存だということです。 realmで指定する文字列しか指定できません。 また、ブラウザがパスワードを覚えてしまうので、アプリ側からログアウトできません。 そこで、DigestのヘッダーをJavaScriptで生成しました。 これは、完全な仕様通りではありません。細かい話は省きますが、 今回のサーバ実装ではたまたまnonceのサーバ側のチェックを省略していたのでできてしまったまでです。 ただ、もしチェックではじかれた場合でも通常のブラウザのDigest認証に fallbackするだけなので、リスクが増えるわけではないはずです。 逆に、パスワードを一回目で間違えた場合もfallbackしてしまいます。 このあたり、いい解決法がないようです。 (そもそもDigest認証の仕様に従わず、なんちゃってDigestにしてしまえばいいのかも)

Digest認証のクライアント側として request をunit testで使いました。使おうとしてから気づいたのですが、 Digest認証の実装が不十分だったので、直してPull Requestしておきました。 無事マージされました。

social-cms-backendはangular.jsでTwitterっぽいアプリを作るのに 便利なのですが、ちょっと使い方にくせがあります。 もっとチュートリアルを増やさないと使えるレベルにならないかも。

Node.jsでオフラインで動くHTML5のWebアプリを作るときに、connect-cache-manifestを使う に書いた2つの不満を解消するために connect-cache-manifest を改良しました。

今回の修正点は2つ。 1つはファイルを除外するオプション、もう一つはファイル名とURLのパスが同一でない場合に書き換えるオプションです。

説明よりコードを読んだほうが分かりやすいと思ます。

var express = require('express');
var cacheManifest = require('connect-cache-manifest');
var app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(cacheManifest({
  manifestPath: '/application.manifest',
  files: [{
    dir: __dirname + '/public',
    prefix: '/static/',
    ignore: function(x) {
      return (/(?:\.swp|~)$/).test(x);
    }
  }, {
    dir: __dirname + '/views',
    prefix: '/html/',
    ignore: function(x) {
      return (/(?:\.swp|~)$/).test(x);
    },
    replace: function(x) {
      return x.replace(/\.jade$/, '.html');
    }
  }],
  networks: ['*'],
  fallbacks: []
}));
app.use('/static', express.static(path.join(__dirname, 'public')));
app.get('/html/:name', function(req, res) {
  res.render(req.params.name);
});

このように、ignoreとreplaceがオプション指定できるようになりました。

これで、バックアップファイルは除外されるし、jadeファイルが増えてもリストアップする必要がありません。とても便利です。

以前の記事で紹介した、 connect-cache-manifestですが、 実は自分でちゃんと使ったことがありませんでした。

今回、一通り動くところまで使ってみたので、参考までに紹介します。 Expressと Jadeを使う前提です。

多くの場合、ディレクトリの構造は次のようになるのではないでしょうか。

|-- app.js
|-- public
|    |-- js
|    |    |-- foo.js
|    |    |-- bar.js
|    |-- css
|    |    |-- foo.css
|    |    |-- bar.css
|    |-- images
|         |-- foo.png
|         |-- bar.png
|-- views
     |-- xxx.jade
     |-- yyy.jade

このような場合での、connect-cache-manifestの設定は次のようになります。

var express = require('express');
var cacheManifest = require('connect-cache-manifest');
var app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(cacheManifest({
  manifestPath: '/application.manifest',
  files: [{
    dir: __dirname + '/public',
    prefix: '/static/'
  }, {
    file: __dirname + '/views/xxx.jade',
    path: '/html/xxx.html'
  }, {
    file: __dirname + '/views/yyy.jade',
    path: '/html/yyy.html'
  }],
  networks: ['*'],
  fallbacks: []
}));
app.use('/static', express.static(path.join(__dirname, 'public')));
app.get('/html/:name', function(req, res) {
  res.render(req.params.name);
});

これで、/application.manifestが生成されるようになります。 使ってみて不満が二つありました。一つは、emacsやvimで*~のバックアップファイルができた場合にそれもマニフェストに入ってしまうこと、もう一つは、jadeファイルが複数ある場合にリストアップしなければならないことです。そのうち気が向いたら改良しようかな。

これで、HTML5のマニフェストを使いつつ、ほとんど気にせずに普通にコーディングができます。

そうそう、ついでにもう一つ。ファイルを新規に追加したときは、nodeを再起動しないといけません。これは、node-supervisorやnodemonを使えば解決できるかもしれません。試していませんが。

FacebookのGraph APIで写真を投稿する場合には、/ALBUM_ID/photosのエンドポイントにHTTP POSTするのですが、そのときにmultipart/form-data形式にする必要があります。

https://developers.facebook.com/docs/reference/api/publishing/

詳しくは上記公式ドキュメントを参照してください。

ところで、以前紹介したfacebook-node-sdkでは、form-dataがサポートされていません。

https://github.com/Thuzi/facebook-node-sdk/issues/28

このようにIssueにもなっていましたが、現時点ではサポートされていないようです。PRしてくれとか言っているので、ちょっとのぞいてみましたがrequestが奥の方に埋まっているので、ちょっと手間がかかりそうでした。

とりあえず、動くものを作ってみようと思ったので、上記ライブラリは使わずに普通にrequestだけでやってみたところ、簡単にできてしまいました。

コードを貼り付けておきます。

var request = require('request');

function postMyPhoto(accessToken, message, file, callback) {
  var form = request.post('https://graph.facebook.com/me/photos', function(err, res, data) {
    if (err) return callback(err);
    if (!data || data.error) return callback(data ? data.error : 'unknown error');
    callback(null, data);
  }).form();
  form.append('access_token', accessToken);
  form.append('message', message);
  form.append('source', fs.createReadStream(file));
}

実は、streamを初めて使いました。使ったとは言わないか。 しかし、フローが分かりにくいです。明示的にstreamの終了はないものなのでしょうか。 興味はあるのでまたの機会に勉強しようと思います。

nodeで、というかexpressで、より正確にはconnectで、Railsのアセットパイプラインのようなことをする方法についてです。

意外と知られてないようなのですが、 connect-assets というモジュールがあります。

npm install connect-assets

でインストールできます。 Railsに慣れている人はいいのかもしれませんが、ちょっとexpressっぽくないというかconventionがあって戸惑うかもしれません。 個人的にはわかりにくいと思いました。 connect-cachifyの方がAPIとしては分かりやすい気がします。

さて、簡単な使い方です。expressには慣れているものとします。

var assets = require('connect-assets');
...
app.use(assets());

このオプション無しデフォルトだと、assetsディレクトリにjsファイルやcssファイルを置くことになります。さらに、jadeファイルに

!= css('hoge')
!= js('hoge')

と書いておくと、それぞれ、assets/hoge.cssとassets/hoge.jsが読み込まれることになります。

一応これだけなのですが、concatenationは直接はサポートされていなくて、lessのimport機能やjsの snoketsを使う必要があります。ここがややこしい。

具体的には、assets/hoge.lessに

@import 'foo.css';
@import 'bar.less';

のように書いたり、assets/hoge.jsに

//= require foo.js

のように書いたりするようです。統一感ないですね。

NODE_ENV=productionの時はminifyしてくれます。これは便利。

現状不満があるconnect-assetsですが、v3 branchの開発が進められているようです。クリーンに書き直したり、依存ライブラリを減らす計画のようですので、期待できそうです。

だいぶマニアックな情報ですが備忘メモとして書いておきます。

socket.io 1.0系の話です。0.9系は分かりません。

おそらく、普通のブラウザでsocket.io-clientを使う場合はクッキーは共通なので困らないと思いますが、node.jsからsocket.io-clientを使う場合はどうやるのでしょうか。

ところで、node.jsからsocket.io-clientを使いたい理由は、テストコードのためです。socket.io-clientはよくできていて、ブラウザでもnode.jsでも動くようです。

なぜ、クッキーを設定したいかというと、セッション維持のためです。サーバ側でなんらかの方法で認証して、セッションIDをクッキーに格納するというのはよくある方法だと思いますが、それをテストコードで動作確認したいわけです。

さて、本題です。socket.io-clientはengine.io-clientに依存していて、engine.io-clientはxmlhttpreuestやws (WebSocket)のライブラリに依存しています。socket.io-clientからそれらのライブラリにクッキーの情報を明示的に渡す手段は見つかりません。そもそも、テストコードを書くときくらいしか使わないのでしょう。

いろいろ調べた結果、engine.io-clientのv0.7.0からagentをオプションで渡せるようになっていました。socket.io-clientからengine.io-clientへはそっくりそのままオプションが引き継がれます。

agentというのは、httpのソケットを管理する仕組みなのですが、ちょっとそこを用途外利用することにしました。つまり、リクエストを登録しにきたところをフックして、クッキーのヘッダを追加してしまうのです。

コードはこんな感じです。

var http = require('http');
var socket_io_client = require('socket.io-client');
var cookie = 'aaa=123';
var myAgent = new http.Agent();
myAgent._addRequest = myAgent.addRequest;
myAgent.addRequest = function(req, host, port, localAddress) {
  var old = req._headers.cookie;
  req._headers.cookie = cookie + (old ? '; ' + old : '');
  req._headerNames['cookie'] = 'Cookie';
  return myAgent._addRequest(req, host, port, localAddress);
};
var client = socket_io_client('http://localhost:12345/', {
  agent: myAgent
});

クッキードメインのチェックは省略されています。テストコード以外では使えないでしょう。また、将来的に仕様が変わったら使えなくなる可能性もあります。

backbone.jsのemulateHTTPというのは、PUTやDELETEをPOSTに変換するものです。なぜそんなことが必要かというと、PUTやDELETEが通らないHTTP Proxyがあるからです。

emulateHTTPでは、X-HTTP-Method-Overrideというヘッダーを使います。正確には、_methodというパラメータも追加されるのですが、それは対応しないものとします。X-HTTP-Method-Overrideはnode.jsのexpressやconnectでサポートされます。

app.use(express.methodOverride());

とすれば有効になります。

さて、こんな機能は当然angular.jsで用意されていて一行くらいの設定で使えるようになるだろうと思っていたら、そんなことはありませんでした。 検索すると、

http://manikanta.com/blog/2013/07/25/decorate-angularjs-http-service-to-convert-put/

がヒットしました。 これでできそうかと思ったのですが、実装が中途半端です。 具体的には、

$http.put({...});

のときは想定どおりに動くのですが、

$http({method: 'PUT', ...});

のような使い方ではうまく動きません。 そこで書き直してみることにします。

var App = angular.module('App');
App.config(['$provide', function($provide) {
  $provide.decorator('$http', ['$delegate', function($delegate) {
    var $http = function(config) {
      var method = config.method.toUpperCase();
      if (method === 'DELETE' || method === 'PUT') {
        config.headers || (config.headers = {});    
        config.headers['X-HTTP-Method-Override'] = method;
        config.method = 'POST';
      }
      return $delegate(config);
    };
    angular.extend($http, $delegate);
    $http.delete = function(url, config) {
      return $http(angular.extend(config || {}, {method: 'DELETE', url: url}));
    };
    $http.put = function(url, data, config) {
      return $http(angular.extend(config || {}, {method: 'PUT', url: url, data: data}));
    };
    return $http;
  }]);
}]);

こんな感じになりました。 もう少しスマートな方法はないかと、思ってしまいますね。 しかし、おかげで$provide.decoratorの使い方を覚えました。

上記コードは動作確認してないので、使用レポートをお待ちします。

いつも思いますが、nodeではモジュール探しが大変です。

今回は、icalフォーマットを出力するためのモジュールを探します。 icalというのは通称で、通称としては他にもicalendarとかicsとかあって紛らわしいです。iCalというソフトの名称もあるのでなおさら。RFC5545と言えば一意に特定できるでしょう。

npmで探しました。上記のようにタグがicalだったりicalendarだったり統一されていません。それでも、5個見つけました。

  • peterbraden/ical.js これはパーザしかなく、今回の目的には使えず。
  • tritech/node-icalendar Staræ•°44でトップ。しかしIssue#5が無視されているのが気になるところ。
  • sunrise/vobject-js vobjectという名前なので一瞬分からない。新しそう。
  • shanebo なんとイベント一つ分しか出力できない。
  • sebbo2002/ical-generator APIはシンプルで分かりやすそう。しかし、save, saveSync, serveは余計な機能ではないか。

セオリー通りにいくとStar数トップのnode-icalendarでいくのですが、Issue#5が気になるのとドキュメントが貧弱なのがマイナスポイントです。ドキュメントの貧弱さは自分のプロジェクトをみると、人のこと言えないですが。

今回は、vobject-jsで行くことにしました。現時点でStar数2!今後の発展に期待します。

以前書いた、 AngularJSを使ってRailsより簡単にTwitterクローンを作りたいと思って作ったnode.jsのライブラリ「social-cms-backend」をちょっとずつ改良しています。

今回は、通知機能です。通知機能というのは、例えば、自分のつぶやきに誰かが返信した場合にお知らせしてくれる機能です。今のところ、social-cms-backendはFacebookのみと連携しているので、通知機能もFacebook連携にしようと思いました。

Facebookアプリは何年か前にも一度作ったことがあって、そのときにapp requestsという機能で通知したのを覚えています。ところが、最近ではapp notificationsという機能もあるのですね。

http://developers.facebook.com/docs/games/notifications/

これです。このAPIの方がずっと素直で分かりやすい。難点は現状ではモバイルアプリに対応していないこと。また、通知先もCanvas対応アプリしか使えないようです。Betaとなっているので、将来的には使えることを期待してapp notificationsで行こうと思います。しかし、また数年後には使えなくなっていそう。Facebook依存はそのあたりがつらいので、social-cms-backendの通知機能としてはFacebook連携以外もサポートしていきたいところです。

APIは/<userid>/notificationsにHTTP POSTするだけです。

facebook-node-sdkを使った呼び出しサンプルコードは次のようになります。

FB.api(facebook_user_id + '/notifications', 'post', {
  href: href,
  template: template
}, function(res) { 
  //result handling
});

hrefはジャンプ先の相対URL、templateにはメッセージを入れます。これをちゃんと動かすにはapp access tokenが必要ですが、その方法は、

HerokuでFacebook APIを使う時の注意点

を参照してください。

いかがでしょうか、簡単でしょう?