app.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /*global require:true, __dirname:true */
  2. var conf = {
  3. port: 8881,
  4. debug: false,
  5. dbPort: 6379,
  6. dbHost: '127.0.0.1',
  7. dbOptions: {},
  8. mainroom: 'Lobby'
  9. };
  10. var express = require('express');
  11. var http = require('http');
  12. var path = require('path');
  13. var bodyParser = require('body-parser');
  14. var events = require('events');
  15. var _ = require('underscore');
  16. var sanitize = require('validator').sanitize;
  17. var app = express(),
  18. server = http.createServer(app);
  19. server.listen(conf.port);
  20. app.use(bodyParser.json());
  21. app.use(bodyParser.urlencoded({ extended: false }));
  22. app.use(express.static(path.join(__dirname, 'app')));
  23. var io = require('socket.io')(server);
  24. var redis = require('socket.io-redis');
  25. io.adapter(redis({ host: conf.dbHost, port: conf.dbPort }));
  26. var db = require('redis').createClient(conf.dbPort,conf.dbHost);
  27. var logger = new events.EventEmitter();
  28. logger.on('newEvent', function(event, data) {
  29. console.log('%s: %s', event, JSON.stringify(data));
  30. });
  31. // ***************************************************************************
  32. // Express routes helpers
  33. // ***************************************************************************
  34. // Only authenticated users should be able to use protected methods
  35. var requireAuthentication = function(req, res, next) {
  36. // TODO
  37. next();
  38. };
  39. // Sanitize message to avoid security problems
  40. var sanitizeMessage = function(req, res, next) {
  41. if (req.body.msg) {
  42. req.sanitizedMessage = sanitize(req.body.msg).xss();
  43. next();
  44. } else {
  45. res.send(400, "No message provided");
  46. }
  47. };
  48. // Send a message to all active rooms
  49. var sendBroadcast = function(text) {
  50. _.each(io.nsps['/'].adapter.rooms, function(room) {
  51. if (room) {
  52. var message = {'room':room, 'username':'Radio-Robbot', 'msg':text, 'date':new Date()};
  53. io.to(room).emit('newMessage', message);
  54. }
  55. });
  56. logger.emit('newEvent', 'newBroadcastMessage', {'msg':text});
  57. };
  58. // ***************************************************************************
  59. // Express routes
  60. // ***************************************************************************
  61. // Welcome message
  62. app.get('/', function(req, res) {
  63. res.send(200, "Welcome to chat server");
  64. });
  65. // Broadcast message to all connected users
  66. app.post('/api/broadcast/', requireAuthentication, sanitizeMessage, function(req, res) {
  67. sendBroadcast(req.sanitizedMessage);
  68. res.send(201, "Message sent to all rooms");
  69. });
  70. // ***************************************************************************
  71. // Socket.io events
  72. // ***************************************************************************
  73. io.sockets.on('connection', function(socket) {
  74. // Welcome message on connection
  75. socket.emit('connected', 'Welcome to the chat server');
  76. logger.emit('newEvent', 'userConnected', {'socket':socket.id});
  77. // Store user data in db
  78. db.hset([socket.id, 'connectionDate', new Date()], redis.print);
  79. db.hset([socket.id, 'socketID', socket.id], redis.print);
  80. db.hset([socket.id, 'username', 'anonymous'], redis.print);
  81. // Join user to 'MainRoom'
  82. socket.join(conf.mainroom);
  83. logger.emit('newEvent', 'userJoinsRoom', {'socket':socket.id, 'room':conf.mainroom});
  84. // Confirm subscription to user
  85. socket.emit('subscriptionConfirmed', {'room':conf.mainroom});
  86. // Notify subscription to all users in room
  87. var data = {'room':conf.mainroom, 'username':'anonymous', 'msg':'----- Joined the room -----', 'id':socket.id};
  88. io.to(conf.mainroom).emit('userJoinsRoom', data);
  89. // User wants to subscribe to [data.rooms]
  90. socket.on('subscribe', function(data) {
  91. // Get user info from db
  92. db.hget([socket.id, 'username'], function(err, username) {
  93. // Subscribe user to chosen rooms
  94. _.each(data.rooms, function(room) {
  95. room = room.replace(" ","");
  96. socket.join(room);
  97. logger.emit('newEvent', 'userJoinsRoom', {'socket':socket.id, 'username':username, 'room':room});
  98. // Confirm subscription to user
  99. socket.emit('subscriptionConfirmed', {'room': room});
  100. // Notify subscription to all users in room
  101. var message = {'room':room, 'username':username, 'msg':'----- Joined the room -----', 'id':socket.id};
  102. io.to(room).emit('userJoinsRoom', message);
  103. });
  104. });
  105. });
  106. // User wants to unsubscribe from [data.rooms]
  107. socket.on('unsubscribe', function(data) {
  108. // Get user info from db
  109. db.hget([socket.id, 'username'], function(err, username) {
  110. // Unsubscribe user from chosen rooms
  111. _.each(data.rooms, function(room) {
  112. if (room != conf.mainroom) {
  113. socket.leave(room);
  114. logger.emit('newEvent', 'userLeavesRoom', {'socket':socket.id, 'username':username, 'room':room});
  115. // Confirm unsubscription to user
  116. socket.emit('unsubscriptionConfirmed', {'room': room});
  117. // Notify unsubscription to all users in room
  118. var message = {'room':room, 'username':username, 'msg':'----- Left the room -----', 'id': socket.id};
  119. io.to(room).emit('userLeavesRoom', message);
  120. }
  121. });
  122. });
  123. });
  124. // User wants to know what rooms he has joined
  125. socket.on('getRooms', function(data) {
  126. socket.emit('roomsReceived', socket.rooms);
  127. logger.emit('newEvent', 'userGetsRooms', {'socket':socket.id});
  128. });
  129. // Get users in given room
  130. socket.on('getUsersInRoom', function(data) {
  131. var usersInRoom = [];
  132. var socketsInRoom = _.keys(io.nsps['/'].adapter.rooms[data.room]);
  133. for (var i=0; i<socketsInRoom.length; i++) {
  134. db.hgetall(socketsInRoom[i], function(err, obj) {
  135. usersInRoom.push({'room':data.room, 'username':obj.username, 'id':obj.socketID});
  136. // When we've finished with the last one, notify user
  137. if (usersInRoom.length == socketsInRoom.length) {
  138. socket.emit('usersInRoom', {'users':usersInRoom});
  139. }
  140. });
  141. }
  142. });
  143. // User wants to change his nickname
  144. socket.on('setNickname', function(data) {
  145. // Get user info from db
  146. db.hget([socket.id, 'username'], function(err, username) {
  147. // Store user data in db
  148. db.hset([socket.id, 'username', data.username], redis.print);
  149. logger.emit('newEvent', 'userSetsNickname', {'socket':socket.id, 'oldUsername':username, 'newUsername':data.username});
  150. // Notify all users who belong to the same rooms that this one
  151. _.each(socket.rooms, function(room) {
  152. if (room) {
  153. var info = {'room':room, 'oldUsername':username, 'newUsername':data.username, 'id':socket.id};
  154. io.to(room).emit('userNicknameUpdated', info);
  155. }
  156. });
  157. });
  158. });
  159. // New message sent to group
  160. socket.on('newMessage', function(data) {
  161. db.hgetall(socket.id, function(err, obj) {
  162. if (err) return logger.emit('newEvent', 'error', err);
  163. // Check if user is subscribed to room before sending his message
  164. if (_.contains(_.values(socket.rooms), data.room)) {
  165. var message = {'room':data.room, 'username':obj.username, 'msg':data.msg, 'date':new Date()};
  166. // Send message to room
  167. io.to(data.room).emit('newMessage', message);
  168. logger.emit('newEvent', 'newMessage', message);
  169. }
  170. });
  171. });
  172. // Clean up on disconnect
  173. socket.on('disconnect', function() {
  174. // Get current rooms of user
  175. var rooms = socket.rooms;
  176. // Get user info from db
  177. db.hgetall(socket.id, function(err, obj) {
  178. if (err) return logger.emit('newEvent', 'error', err);
  179. logger.emit('newEvent', 'userDisconnected', {'socket':socket.id, 'username':obj.username});
  180. // Notify all users who belong to the same rooms that this one
  181. _.each(rooms, function(room) {
  182. if (room) {
  183. var message = {'room':room, 'username':obj.username, 'msg':'----- Left the room -----', 'id':obj.socketID};
  184. io.to(room).emit('userLeavesRoom', message);
  185. }
  186. });
  187. });
  188. // Delete user from db
  189. db.del(socket.id, redis.print);
  190. });
  191. });
  192. // Automatic message generation (for testing purposes)
  193. if (conf.debug) {
  194. setInterval(function() {
  195. var text = 'Testing rooms';
  196. sendBroadcast(text);
  197. }, 60000);
  198. }