• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Tags
No Tags

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

Node.js sample application with Socket.IO


Commit MetaInfo

Revision51274ace76f331f34e729b043f5ebeaaea4a2ccd (tree)
Time2012-10-29 01:06:50
Authorhylom <hylom@hylo...>
Commiterhylom

Log Message

implement all features

Change Summary

Incremental Difference

--- a/app.js
+++ b/app.js
@@ -10,6 +10,7 @@ var express = require('express')
1010 , socketIO = require('socket.io');
1111
1212 var app = express();
13+var crypto = require('crypto');
1314
1415 app.configure(function(){
1516 app.set('port', process.env.PORT || 3000);
@@ -29,10 +30,9 @@ app.configure('development', function(){
2930
3031 app.get('/', routes.index);
3132 app.post('/room', routes.room);
32-app.get('/echo/:name', routes.echo);
3333
3434 var server = http.createServer(app);
35-var io = socketIO.listen(server);
35+var io = socketIO.listen(server, {log:false});
3636
3737 server.listen(app.get('port'), function(){
3838 console.log("Express server listening on port " + app.get('port'));
@@ -41,32 +41,148 @@ server.listen(app.get('port'), function(){
4141 // socket.ioのソケットを管理するオブジェクト
4242 var socketsOf = {};
4343
44+// 指定したroomIdに属するクライアントすべてに対しイベントを送信する
45+function emitToRoom(roomId, event, data, fn) {
46+ if (socketsOf[roomId] === undefined) {
47+ return;
48+ }
49+ var sockets = socketsOf[roomId];
50+ Object.keys(sockets).forEach(function (key) {
51+ sockets[key].emit(event, data, fn);
52+ });
53+};
54+
4455 // socket.ioのコネクション設定
4556 io.sockets.on('connection', function (socket) {
57+
4658 // コネクションが確立されたら'connected'メッセージを送信する
4759 socket.emit('connected', {});
4860
49- // クライアントは'connected'メッセージを受信したら、
50- // クライアントの情報とともに'regist request'メッセージを送信する
51- socket.on('regist request', function (client) {
52- console.log('regist client');
53- if (socketsOf[client.roomId] === undefined) {
61+ // 認証情報を確認する
62+ socket.on('hash password', function (password, fn) {
63+ var hashedPassword = '';
64+ var shasum = crypto.createHash('sha512');
65+
66+ if (password !== '') {
67+ shasum.update('initialhash');
68+ shasum.update(password);
69+ hashedPassword = shasum.digest('hex');
70+ }
71+ fn(hashedPassword);
72+ });
73+
74+ // 認証情報を確認する
75+ socket.on('check credential', function (client) {
76+ // クライアントはconnectedメッセージを受信したら、minichatオブジェクトを
77+ // 引数にこのメッセージを送信する
78+
79+ // 認証情報の確認
80+ if (client.mode == 'create') {
81+ // modeが'create'の場合、すでに対応するroomIdのチャットルームがないか
82+ // チェックする
83+ if (socketsOf[client.roomId] !== undefined) {
84+ socket.emit('room exists', {});
85+ return;
86+ }
5487 socketsOf[client.roomId] = {};
5588 }
56- socketsOf[client.roomId][client.userName] = socket;
89+
90+ if (client.mode == 'enter') {
91+ // 対応するroomIdのチャットルームの存在をチェックする
92+ if (socketsOf[client.roomId] === undefined) {
93+ socket.emit('invalid credential', {});
94+ return;
95+ }
96+ // ユーザー名がかぶっていないかチェックする
97+ if (socketsOf[client.roomId][client.userName] !== undefined) {
98+ socket.emit('userName exists', {});
99+ return;
100+ }
101+ }
102+
103+ // ソケットにクライアントの情報をセットする
104+ socket.set('client', client, function () {
105+ socketsOf[client.roomId][client.userName] = socket;
106+ if (client.userName) {
107+ console.log('user ' + client.userName + '@' + client.roomId + ' connected');
108+ }
109+ });
110+
111+ // 認証成功
112+ socket.emit('credential ok', {});
113+
114+ // 既存クライアントにメンバーの変更を通知する
115+ var members = Object.keys(socketsOf[client.roomId]);
116+ emitToRoom(client.roomId, 'update members', members);
117+
118+ var shasum = crypto.createHash('sha1')
119+ var message = {
120+ from: 'システムメッセージ',
121+ body: client.userName + 'さんが入室しました',
122+ roomId: client.roomId
123+ }
124+ message.date = _formatDate(new Date());
125+ shasum.update('-' + message.roomId);
126+ message.id = (new Date()).getTime() + '-' + shasum.digest('hex');
127+ emitToRoom(message.roomId, 'push message', message);
128+
129+ });
130+
131+ // ソケットが切断された場合、ソケット一覧からソケットを削除する
132+ socket.on('disconnect', function () {
133+ socket.get('client', function (err, client) {
134+ if (err || !client) {
135+ return;
136+ }
137+ var sockets = socketsOf[client.roomId];
138+ if(sockets !== undefined) {
139+ delete sockets[client.userName];
140+ }
141+ console.log('user ' + client.userName + '@' + client.roomId + ' disconnected');
142+ var members = Object.keys(sockets);
143+ if (members.length === 0) {
144+ delete socketsOf[client.roomId];
145+ } else {
146+ // 既存クライアントにメンバーの変更を通知する
147+ emitToRoom(client.roomId, 'update members', members);
148+ var message = {
149+ from: 'システムメッセージ',
150+ body: client.userName + 'さんが退室しました',
151+ roomId: client.roomId
152+ }
153+ var shasum = crypto.createHash('sha1')
154+ message.date = _formatDate(new Date());
155+ shasum.update('-' + message.roomId);
156+ message.id = (new Date()).getTime() + '-' + shasum.digest('hex');
157+ emitToRoom(message.roomId, 'push message', message);
158+
159+ }
160+ });
57161 });
58162
59163 // クライアントは'say'メッセージとともにチャットメッセージを送信する
60- socket.on('say', function (message) {
164+ socket.on('say', function (message, fn) {
61165 console.log('receive message');
62- socket.emit('say accept', {});
166+
167+ var shasum = crypto.createHash('sha1')
63168 message.date = _formatDate(new Date());
64- if (socketsOf[message.roomId] !== undefined) {
65- var sockets = socketsOf[message.roomId];
66- Object.keys(sockets).forEach(function (key) {
67- sockets[key].emit('push message', message);
169+ shasum.update(message.userName + '-' + message.roomId);
170+ message.id = (new Date()).getTime() + '-' + shasum.digest('hex');
171+ emitToRoom(message.roomId, 'push message', message);
172+ // クライアント側のコールバックを実行する
173+ fn();
174+ });
175+
176+ // クライアントはログが必要な場合'request log'メッセージを送信する
177+ socket.on('request log', function (data) {
178+ socket.get('client', function (err, client) {
179+ if (err || client === undefined) {
180+ return;
181+ }
182+ emitToRoom(client.roomId, 'request log', {}, function (log) {
183+ socket.emit('update log', log);
68184 });
69- }
185+ });
70186 });
71187
72188 });
--- a/public/js/room.js
+++ b/public/js/room.js
@@ -2,6 +2,7 @@
22
33 (function () {
44 var socket;
5+ var messageLogs = {};
56
67 // ページロード時の処理
78 $(document).ready(function () {
@@ -11,36 +12,108 @@
1112 // メッセージハンドラの定義
1213 // サーバーへの接続完了
1314 socket.on('connected', function(data) {
14- socket.emit('regist request', minichat);
15+ socket.emit('check credential', minichat);
1516 });
1617
17- // チャットメッセージ受信
18- socket.on('push message', function (message) {
19- var html = '<div class="message">'
20- + '<p class="postdate pull-right">' + message.date + '</p>'
21- + '<p class="author">' + message.from + ':</p>'
22- + '<p class="comment">' + message.body + '</p>'
23- + '</div>';
24- $('#messages').prepend(html);
18+ // 認証成功
19+ socket.on('credential ok', function(data) {
20+ socket.emit('request log', {});
21+ });
22+
23+ // 認証失敗:ルーム名/パスワードの不一致
24+ socket.on('invalid credential', function(data) {
25+ authRetry('ルーム名/パスワードが不正です');
26+ });
27+
28+ // 認証失敗:同名のルームがすでに存在
29+ socket.on('room exists', function(data) {
30+ authRetry('同名のルームがすでに存在します');
2531 });
2632
27- // チャットメッセージの送信完了
28- socket.on('say accept', function (data) {
29- $('#message').val('');
33+ // 認証失敗:ルームに同名のユーザーが存在
34+ socket.on('userName exists', function(data) {
35+ authRetry('その名前はすでに使われています');
3036 });
3137
32- // メッセージの送信ボタンを有効化
38+ // チャットログの送信
39+ socket.on('request log', function(data, callback) {
40+ callback(messageLogs);
41+ });
42+
43+ // チャットログの更新
44+ socket.on('update log', function(log) {
45+ Object.keys(log).forEach(function (key) {
46+ messageLogs[key] = log[key];
47+ });
48+ updateMessage();
49+ });
50+
51+ // チャットルームへのメンバー追加
52+ socket.on('update members', function (members) {
53+ $('#members').empty();
54+ for (var i = 0; i < members.length; i++) {
55+ var html = '<li>' + members[i] + '</li>';
56+ $('#members').append(html);
57+ }
58+ });
59+
60+ // チャットメッセージ受信
61+ socket.on('push message', function (message) {
62+ messageLogs[message.id] = message;
63+ prependMessage(message);
64+ });
65+
66+ // チャットメッセージ送信
3367 $('#post-message').on('click', function () {
3468 var message = {
3569 from: minichat.userName,
3670 body: $('#message').val(),
3771 roomId: minichat.roomId
3872 };
39- socket.emit('say', message);
73+ socket.emit('say', message, function () {
74+ // メッセージの送信に成功したらテキストボックスをクリアする
75+ $('#message').val('');
76+ });
77+ });
78+
79+ $('#credential-dialog-form').on('submit', function() {
80+ $('#credentialDialog').modal('hide');
81+ socket.emit('hash password', $('#new-password').val(), function (hashedPassword) {
82+ minichat.roomName = $('#new-room').val();
83+ minichat.userName = $('#new-name').val();
84+ minichat.password = hashedPassword;
85+ minichat.roomId = minichat.roomName + minichat.password;
86+ socket.emit('check credential', minichat);
87+ });
88+ return false;
4089 });
4190
4291 }); // document.ready()ここまで
4392
93+ function authRetry(message) {
94+ $('#credential-dialog-header').text(message);
95+ $('#new-room').val(minichat.roomName);
96+ $('#new-name').val(minichat.userName);
97+ $('#credentialDialog').modal('show');
98+ }
99+
100+ function prependMessage(message) {
101+ var html = '<div class="message" id="' + message.id + '">'
102+ + '<p class="postdate pull-right">' + message.date + '</p>'
103+ + '<p class="author">' + message.from + ':</p>'
104+ + '<p class="comment">' + message.body + '</p>'
105+ + '</div>';
106+ $('#messages').prepend(html);
107+ }
108+
109+ function updateMessage() {
110+ $('#messages').empty();
111+ var keys = Object.keys(messageLogs);
112+ keys.sort();
113+ keys.forEach(function (key) {
114+ prependMessage(messageLogs[key]);
115+ });
116+ }
44117
45118 }).apply(this);
46119
--- a/routes/index.js
+++ b/routes/index.js
@@ -8,37 +8,22 @@ exports.index = function(req, res){
88 res.render('index', { title: 'minichat' });
99 };
1010
11-exports.echo = function(req, res) {
12- var roomName = req.params.name;
13- var yourName = 'test';
14- var password = 'minichattest';
15- var hashedPassword = '';
16- var shasum = crypto.createHash('sha512');
17-
18- if (password !== '') {
19- shasum.update(password);
20- hashedPassword = shasum.digest('hex');
21- }
22-
23- var params = {
24- title: 'チャットルーム:' + roomName,
25- room: {
26- name: roomName,
27- password: hashedPassword
28- },
29- user: {name: yourName}
30- };
31- res.render('room', params);
32-};
33-
3411 exports.room = function(req, res){
3512 var roomName = req.body.roomName || '';
3613 var yourName = req.body.yourName || '';
3714 var password = req.body.password || '';
15+ var mode = req.body.mode;
16+
17+ if (mode === undefined) {
18+ res.send(500);
19+ return;
20+ };
21+
3822 var hashedPassword = '';
3923 var shasum = crypto.createHash('sha512');
4024
4125 if (password !== '') {
26+ shasum.update('initialhash');
4227 shasum.update(password);
4328 hashedPassword = shasum.digest('hex');
4429 }
@@ -49,7 +34,8 @@ exports.room = function(req, res){
4934 name: roomName,
5035 password: hashedPassword
5136 },
52- user: {name: yourName}
37+ user: {name: yourName},
38+ mode: mode
5339 };
5440 res.render('room', params);
5541 };
--- a/views/index.jade
+++ b/views/index.jade
@@ -24,12 +24,12 @@ block content
2424 .controls
2525 label
2626 input#new-password(type='password', name='password', placeholder='パスワードを入力...')
27- button#create(type='submit', class='btn', name='create') チャットルームを作成
27+ button.btn(type='submit', name='mode', value='create') チャットルームを作成
2828
2929 .span6
3030 h2 Enter:
3131 p 既存のチャットルームに入る
32- form#enter.form-horizontal(action='/enter/', method='post')
32+ form#enter.form-horizontal(action='/room/', method='post')
3333 .control-group
3434 label.control-label(for='enter-room') ルーム名
3535 .controls
@@ -43,7 +43,7 @@ block content
4343 .controls
4444 label
4545 input#enter-password(type='password', name='password', placeholder='パスワードを入力...')
46- button(type='submit', name='enter', class='btn') チャットルームへ入る
46+ button.btn(type='submit', name='mode', value='enter') チャットルームへ入る
4747
4848 hr
4949 footer
--- a/views/room.jade
+++ b/views/room.jade
@@ -6,7 +6,8 @@ block content
66 roomName: '#{room.name}',
77 password: '#{room.password}',
88 userName: '#{user.name}',
9- roomId: '#{room.name}#{room.password}'
9+ roomId: '#{room.name}#{room.password}',
10+ mode: '#{mode}'
1011 };
1112
1213 .navbar.navbar-inverse.navbar-fixed-top
@@ -18,7 +19,7 @@ block content
1819 a ルーム名:#{room.name}
1920 li
2021 a(href='/') 退出する
21- .container
22+ #main-contents.container
2223 .row
2324 .span4
2425 h3
@@ -30,14 +31,35 @@ block content
3031 #members-box
3132 h5 チャットメンバー:
3233 ul#members
33- li #{user.name}
3434 .span8
3535 #messages.well
36- .message
37- p.postdate.pull-right 10/23 12:34
38- p.author システムメッセージ:
39- p.comment チャットルーム「hogehoge」が作成されました。HASH: #{room.password}
4036
37+ // 入室時の確認ダイアログ
38+ // パスワードが不正
39+ #credentialDialog.modal.hide(data-backdrop='static', keyboard='false')
40+ .modal-header
41+ h3#credential-dialog-header 入室エラー
42+ .modal-body
43+ p#credential-dialog-message ルーム情報や名前を再入力してください:
44+ form#credential-dialog-form.form-horizontal
45+ .control-group
46+ label.control-label(for='new-room-name') ルーム名
47+ .controls
48+ input#new-room(type='text', name='roomName', placeholder='ルーム名を入力...')
49+ .control-group
50+ label.control-label(for='new-name') 表示するあなたの名前
51+ .controls
52+ input#new-name(type='text', name='yourName', placeholder='表示名を入力...')
53+ .control-group
54+ label.control-label(for='new-password') 認証用パスワード
55+ .controls
56+ label
57+ input#new-password(type='password', name='password', placeholder='パスワードを入力...')
58+ button.btn.btn-primary(type='submit') 再試行
59+
60+ .modal-footer
61+ a(href='/') トップページへ戻る
62+
4163
4264 hr
4365 footer