Node.jsのチャットデータをNSFに保存してみる
それではいよいよ、リアルタイムの会話をNSFに保存してみます。プログラムコードのベースは、前回のDominoを離れてNode.jsだけでチャットの実装をしてみる - Chiburu Systemsのブログの「domtest2」になります。
チャットデータと、DominoのREST APIを取り持つ、新しいカスタムモジュールを準備します。/domtest2直下に「NodeChat.js」ファイルを作成します。
// RESTクライアントモジュールを使用します。 var Client = require('node-rest-client').Client; // NodeChatクラスを新設します。 function NodeChat(user) { this.user = user; // 基本認証を組み込んで、RESTクライアントを初期化します。 this.client = new Client({ user: user.username , password: user.password }); }
さらに、Chatオブジェクトにchatメソッドを追加します。
// チャットデータをNSFにPOSTします。 NodeChat.prototype.chat = function(data) { var userObj = this.user; var cliObj = this.client; return new Promise(function(resolve, reject) { var args = { 'parameters': { form: 'MainTopic' , computewithform: true } , 'headers': { 'Content-Type': 'application/json' } , 'data': { 'Subject': data.message } }; cliObj.post( 'http://localhost/NodeChat.nsf/api/data/documents' , args , function(retData, response) { if (retData.code && retData.code !== 200) reject(retData); else resolve(userObj); } ).on('error', reject); }); }
Dominoデータサービスによる文書の追加は、以下のリンクに情報があります。
IBM Notes and Domino Application Development wiki : IBM Domino Access Services 9.0.1
URLの指定先は、http(s)://(ホスト)/(NSF)/api/data/documentsとなります。 これに、クエリとしてform=MainTopic&computewithform=trueを付けることで、「フォームはMainTopic、保存時にフォーム全体を計算」を解釈してくれます。 フォームに保存するデータはJSON形式にし、「data」プロパティとして追加します。その時に「Content-Type: application/json」というヘッダー情報を付けるようにします。
締めくくりに、NodeChatクラスをエキスポートしておきます。
module.exports = NodeChat;
以上で、NodeChat.jsの実装は終わりです。次に、NodeChatを使用する側となる、「mySocket.js」にコードを追加していきましょう。
var Socket = require('socket.io'); var cookie = require('cookie'); var cookieParser = require('cookie-parser'); var NodeChat = require('./NodeChat'); var io;
モジュールをロードする場所に、「./NodeChat」を追加しておきます。次に、エキスポートする関数に一つ、Userという引数を追加しておきます。
module.exports = function(server, sessionStore, User) { io = new Socket(server); var getUser = function(socket) { return new Promise(function(resolve, reject) { User.findById(socket.session.passport.user.id, function(err, user) { if (err) reject(err); else resolve(user); }); }); };
前回は必要がなかったユーザーのパスワード情報が必要になるため、「getUser」というPromise対応関数を作ります。JavaScriptの「Promise」については、以下のようなリンクを参照して下さい。乱暴に簡潔に述べるなら、非同期メソッドチェーンを簡単にする技です。
続いて、ミドルウェアを追加するコードのあとに、チャットデータを受け取るとDominoにそのデータを送信するコードを追加します。
/** * ソケットサーバーにミドルウェアを追加します。 */ io.use(function(socket, next) { // ソケットにセッション情報を追加します。 var signedCookies = cookie.parse(socket.handshake.headers.cookie); var cookies = cookieParser.signedCookies(signedCookies, 'domtest2'); return sessionStore.get(cookies['connect.sid'], function(error, session) { if (error != null) return next(error); if (session.passport == null || !session.passport.user == null) return next('Guest'); socket.session = session; return next(); }); }); /** * ソケットサーバーに接続要求が来たら処理します。 */ io.on('connect', function(socket) { console.log(socket.id); // ソケットクライアントに'chat'リスナーを追加します。 socket.on('chat', function(data) { getUser(socket) .then(function(user) { var nodeChat = new NodeChat(user); return nodeChat.chat(data); }) .then(function(user) { data.username = user.username; io.sockets.emit('chat', data); }) .catch(function(err) { console.log(err); }); }); }); };
nodeChat.chatメソッドでDominoへのREST API送出に成功すると、チャットクライアントにメッセージを送信するようになります。Node.jsを介して、Socket.ioによるリアルタイム通信とDomino REST APIを使った情報のやり取りは、以上のようなコードで実現できます。
それでは、以下に前回のコードから変更になったソースを、順を追ってみていきます。
// auth.js var passport = require('passport'); var LocalStrategy = require('passport-local').Strategy; module.exports = function(User) { // セッションサポート用の直列化、非直列化。 passport.serializeUser(function(user, done) { done(null, { id: user._id, username: user.username }); }); passport.deserializeUser(function(user, done) { User.findById(user.id, function(err, user) { done(err, user); }); }); // ユーザ検証戦略の設定。 passport.use(new LocalStrategy( function (username, password, done) { User.findOne({ username: username }, function(err, user) { if (err) { return done(err); } if (!user) { return done(null, false, {message: 'ユーザID/パスワードが違います。'}); } if (!user.validPassword(password)) { return done(null, false, {message: 'ユーザID/パスワードが違います。'}); } return done(null, user); }) } )); return { passport: passport }; };
前回と比べ、Mongoose、MongoDBに関するパートを別のモジュールにし、認証に使う「User」のみ受け取れるようにしてあります。
そのMongoDBに関するモジュールは、dbinfo.jsとして新しくまとめました。
// dbinfo.js module.exports = function(mongoose) { // MongoDBに接続。 var db = mongoose.createConnection( "mongodb://localhost/domtest" , function(error, res){} ); // ユーザ情報のスキーマ。 var UserSchema = new mongoose.Schema({ username: {type: String, require: true} , password: {type: String, require: true} }); // ユーザ情報のモデル。 var User = db.model("User", UserSchema); // パスワード検証用メソッド。 User.prototype.validPassword = function(pass) { return this.password === pass; }; return { db: db , User: User }; };
Mongooseモジュールは、bin/wwwでロードしておき、mongooseを引数にしてdbinfoをロードするようにします。 その他のbin/wwwの変更点は以下の通りです。
// bin/www ... /** * データベース環境を初期化します。 */ var mongoose = require('mongoose'); var dbInfo = require('../dbInfo')(mongoose); /** * 認証系を初期化します。 */ var auth = require('../auth')(dbInfo.User); /** * セッションの初期設定をします。 * セッションの保存先にもMongoDBを使用します。 */ var session = require('express-session'); var MongoStore = require('connect-mongo')(session); var sessionStore = new MongoStore({ mongooseConnection: dbInfo.db }); ... // (中略) ... /** * ソケットの初期設定をします。 */ require('../mySocket')(server, sessionStore, dbInfo.User); ...
それと、クライアント用JSでも一点だけ変更があります。前回までのコードですと、送信済みのメッセージが残ったままになってしまうので、送信後にメッセージを消去するようにします。
// public/javascript/chat.js (function() { // クライアント側のソケットを初期化します。 var socket = io.connect('http://localhost:3000'); // 送信ボタンをクリックしたときの操作を定義します。 $('button#send').click(function() { var msg = $('#message').val(); console.log('Message', msg); socket.emit('chat', { message: msg }); $('#message').val(''); }); // Enterキーにも対応させる。 $('input#message').keypress(function(event) { if (event.which == 13) $('button#send').click(); }); // チャットメッセージを受信します。 socket.on('chat', function(data) { $('#output').append( '<p>' + '<strong>' + data.username + '</strong>: ' + data.message + '</p>' ); }); })()
以上がコードの追加、変更部分でした。
では実際に、チャットデータがNSFに保存される様子を見てみましょう。コマンドプロンプトでdomtest2フォルダに移動し、npm startとすればサーバが起動します。Dominoサーバも忘れずに起動しておきます。ブラウザでhttp://localhost:3000にアクセスして、ログインします。
チャットデータを保存することができたので、再ログインしたあとにチャットデータの履歴を表示することができます。次回はDominoからREST APIでデータを受け取り、チャットの履歴を表示してみましょう。