var SocketIO = require('socket.io').listen(global.server, { log:false }), cookie = require('cookie'), express = require('express'), user = require('./user.js'), posts = require('./posts.js'), favourites = require('./favourites.js'), utils = require('../public/src/utils.js'), util = require('util'), topics = require('./topics.js'), categories = require('./categories.js'), notifications = require('./notifications.js'), threadTools = require('./threadTools.js'), postTools = require('./postTools.js'), meta = require('./meta.js'), async = require('async'), RedisStoreLib = require('connect-redis')(express), redis = require('redis'), redisServer = redis.createClient(nconf.get('redis:port'), nconf.get('redis:host')), RedisStore = new RedisStoreLib({ client: redisServer, ttl: 60*60*24*14 }), socketCookieParser = express.cookieParser(nconf.get('secret')), admin = { 'categories': require('./admin/categories.js'), 'user': require('./admin/user.js') }, plugins = require('./plugins'), winston = require('winston'); (function(io) { var users = {}, userSockets = {}, rooms = {}; global.io = io; io.sockets.on('connection', function(socket) { var hs = socket.handshake, sessionID, uid; // Validate the session, if present socketCookieParser(hs, {}, function(err) { sessionID = socket.handshake.signedCookies["express.sid"]; RedisStore.get(sessionID, function(err, sessionData) { if (!err && sessionData && sessionData.passport && sessionData.passport.user) uid = users[sessionID] = sessionData.passport.user; else uid = users[sessionID] = 0; userSockets[uid] = userSockets[uid] || []; userSockets[uid].push(socket); if(uid) { socket.join('uid_' + uid); io.sockets.in('global').emit('api:user.isOnline', isUserOnline(uid)); user.getUserField(uid, 'username', function(err, username) { socket.emit('event:connect', {status: 1, username:username, uid:uid}); }); } }); }); socket.on('disconnect', function() { var index = userSockets[uid].indexOf(socket); if(index !== -1) { userSockets[uid].splice(index, 1); } if(userSockets[uid].length === 0) { delete users[sessionID]; if(uid) { io.sockets.in('global').emit('api:user.isOnline', isUserOnline(uid)); } } for(var roomName in rooms) { socket.leave(roomName); if(rooms[roomName][socket.id]) { delete rooms[roomName][socket.id]; } updateRoomBrowsingText(roomName); } }); socket.on('api:get_all_rooms', function(data) { socket.emit('api:get_all_rooms', io.sockets.manager.rooms); }) function updateRoomBrowsingText(roomName) { function getUidsInRoom(room) { var uids = []; for(var socketId in room) { if(uids.indexOf(room[socketId]) === -1) uids.push(room[socketId]); } return uids; } function getAnonymousCount(roomName) { var clients = io.sockets.clients(roomName); var anonCount = 0; for(var i=0; i' + users[i].username + ''; } var joiner = anonymousCount + userCount == 1 ? 'is' : 'are', userList = anonymousCount > 0 ? usernames.concat(util.format('%d guest%s', anonymousCount, anonymousCount > 1 ? 's' : '')) : usernames, lastUser = userList.length > 1 ? ' and ' + userList.pop() : ''; return util.format('%s%s %s browsing this thread', userList.join(', '), lastUser, joiner); } if (uids.length === 0) { io.sockets.in(roomName).emit('api:get_users_in_room', userList([], anonymousCount, 0)); } else { user.getMultipleUserFields(uids, ['username', 'userslug'], function(users) { io.sockets.in(roomName).emit('api:get_users_in_room', userList(users, anonymousCount, users.length)); }); } } socket.on('event:enter_room', function(data) { if (data.leave !== null) { socket.leave(data.leave); } socket.join(data.enter); rooms[data.enter] = rooms[data.enter] || {}; if (uid) { rooms[data.enter][socket.id] = uid; if (data.leave && rooms[data.leave] && rooms[data.leave][socket.id]) { delete rooms[data.leave][socket.id]; } } if(data.leave) updateRoomBrowsingText(data.leave); updateRoomBrowsingText(data.enter); if (data.enter != 'admin') io.sockets.in('admin').emit('api:get_all_rooms', io.sockets.manager.rooms); }); // BEGIN: API calls (todo: organize) socket.on('api:updateHeader', function(data) { if(uid) { user.getUserFields(uid, data.fields, function(err, fields) { if(!err && fields) { fields.uid = uid; socket.emit('api:updateHeader', fields); } }); } else { socket.emit('api:updateHeader', { uid:0, username: "Anonymous User", email: '', picture: require('gravatar').url('', {s:'24'}, https=nconf.get('https')) }); } }); socket.on('user.exists', function(data) { if(data.username) { user.exists(utils.slugify(data.username), function(exists){ socket.emit('user.exists', {exists: exists}); }); } }); socket.on('user.count', function(data) { user.count(socket, data); }); socket.on('post.stats', function(data) { posts.getTopicPostStats(socket); }); socket.on('user.latest', function(data) { user.latest(socket, data); }); socket.on('user.email.exists', function(data) { user.email.exists(socket, data.email); }); socket.on('user:reset.send', function(data) { user.reset.send(socket, data.email); }); socket.on('user:reset.valid', function(data) { user.reset.validate(socket, data.code); }); socket.on('user:reset.commit', function(data) { user.reset.commit(socket, data.code, data.password); }); function isUserOnline(uid) { return !!userSockets[uid] && userSockets[uid].length > 0; } socket.on('api:user.get_online_users', function(data) { var returnData = []; for(var i=0; i'; if(!isUserOnline(touid)) { notifications.create(notifText, 5, '#', 'notification_' + uid + '_' + touid, function(nid) { notifications.push(nid, [touid], function(success) { }); }); } require('./messaging').addMessage(uid, touid, msg, function(err, message) { var numSockets = 0; if(userSockets[touid]) { numSockets = userSockets[touid].length; for(var x=0; x 0) { if (parseInt(data.tid) > 0) { topics.getTopicData(data.tid, function(topicData) { if (data.body) topicData.body = data.body; socket.emit('api:composer.push', { tid: data.tid, title: topicData.title, body: topicData.body }); }); } else if (parseInt(data.cid) > 0) { user.getUserFields(uid, ['username', 'picture'], function(err, userData) { if(!err && userData) { socket.emit('api:composer.push', { tid: 0, cid: data.cid, username: userData.username, picture: userData.picture, title: undefined }); } }); } else if (parseInt(data.pid) > 0) { async.parallel([ function(next) { posts.getPostFields(data.pid, ['content', 'uploadedImages'], function(raw) { try { raw.uploadedImages = JSON.parse(raw.uploadedImages); } catch(e) { winston.err(e); raw.uploadedImages = []; } next(null, raw); }); }, function(next) { topics.getTitleByPid(data.pid, function(title) { next(null, title); }); } ], function(err, results) { socket.emit('api:composer.push', { title: results[1], pid: data.pid, body: results[0].content, uploadedImages: results[0].uploadedImages }); }); } } else { socket.emit('api:composer.push', { error: 'no-uid' }); } }); socket.on('api:composer.editCheck', function(pid) { posts.getPostField(pid, 'tid', function(tid) { postTools.isMain(pid, tid, function(isMain) { socket.emit('api:composer.editCheck', { titleEditable: isMain }); }) }) }); socket.on('api:post.privileges', function(pid) { postTools.privileges(pid, uid, function(privileges) { privileges.pid = parseInt(pid); socket.emit('api:post.privileges', privileges); }); }); socket.on('api:topic.followCheck', function(tid) { threadTools.isFollowing(tid, uid, function(following) { socket.emit('api:topic.followCheck', following); }); }); socket.on('api:topic.follow', function(tid) { if (uid && uid > 0) { threadTools.toggleFollow(tid, uid, function(follow) { if (follow.status === 'ok') socket.emit('api:topic.follow', follow); }); } else { socket.emit('api:topic.follow', { status: 'error', error: 'not-logged-in' }); } }); socket.on('api:topic.loadMore', function(data, callback) { var start = data.after, end = start + 9; topics.getTopicPosts(data.tid, start, end, uid, function(posts) { callback({posts:posts}); }); }); socket.on('api:category.loadMore', function(data, callback) { var start = data.after, end = start + 9; categories.getCategoryTopics(data.cid, start, end, uid, function(topics) { callback({topics:topics}); }); }); socket.on('api:topics.loadMoreRecentTopics', function(data, callback) { var start = data.after, end = start + 9; topics.getLatestTopics(uid, start, end, function(latestTopics) { callback(latestTopics); }); }); socket.on('api:topics.loadMoreUnreadTopics', function(data, callback) { var start = data.after, end = start + 9; topics.getUnreadTopics(uid, start, end, function(unreadTopics) { callback(unreadTopics); }); }); socket.on('api:users.loadMore', function(data, callback) { var start = data.after, end = start + 19; user.getUsers(data.set, start, end, function(err, data) { if(err) { winston.err(err); } else { callback({users:data}); } }); }); socket.on('api:admin.topics.getMore', function(data, callback) { topics.getAllTopics(data.limit, data.after, function(topics) { callback(JSON.stringify(topics)); }); }); socket.on('api:admin.categories.update', function(data) { admin.categories.update(data, socket); }); socket.on('api:admin.user.makeAdmin', function(theirid) { if(uid && uid > 0) { admin.user.makeAdmin(uid, theirid, socket); } }); socket.on('api:admin.user.removeAdmin', function(theirid) { if(uid && uid > 0) { admin.user.removeAdmin(uid, theirid, socket); } }); socket.on('api:admin.user.deleteUser', function(theirid) { if(uid && uid > 0) { admin.user.deleteUser(uid, theirid, socket); } }); socket.on('api:admin.user.banUser', function(theirid) { if(uid && uid > 0) { admin.user.banUser(uid, theirid, socket); } }); socket.on('api:admin.user.unbanUser', function(theirid) { if(uid && uid > 0) { admin.user.unbanUser(uid, theirid, socket); } }); socket.on('api:admin.user.search', function(username) { if(uid && uid > 0) { user.search(username, function(data) { socket.emit('api:admin.user.search', data); }); } else { socket.emit('api:admin.user.search', null); } }); socket.on('api:admin.themes.getInstalled', function(callback) { meta.themes.get(function(err, themeArr) { callback(themeArr); }); }); socket.on('api:admin.plugins.toggle', function(plugin_id) { plugins.toggleActive(plugin_id, function(status) { socket.emit('api:admin.plugins.toggle', status); }); }); socket.on('api:meta.buildTitle', function(text, callback) { meta.title.build(text, uid, function(err, title, numNotifications) { callback(title, numNotifications); }); }); }); }(SocketIO));