Merge pull request #3834 from NodeBB/room-optimize

Room optimize
v1.18.x
Barış Soner Uşaklı 9 years ago
commit 9cb9531b8e

@ -22,7 +22,7 @@ define('admin/general/dashboard', ['semver'], function(semver) {
Admin.init = function() { Admin.init = function() {
app.enterRoom('admin'); app.enterRoom('admin');
socket.emit('meta.rooms.getAll', Admin.updateRoomUsage); socket.emit('admin.rooms.getAll', Admin.updateRoomUsage);
isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
@ -439,7 +439,7 @@ define('admin/general/dashboard', ['semver'], function(semver) {
intervals.rooms = setInterval(function() { intervals.rooms = setInterval(function() {
if (app.isFocused && app.isConnected) { if (app.isFocused && app.isConnected) {
socket.emit('meta.rooms.getAll', Admin.updateRoomUsage); socket.emit('admin.rooms.getAll', Admin.updateRoomUsage);
} }
}, realtime ? DEFAULTS.realtimeInterval : DEFAULTS.roomInterval); }, realtime ? DEFAULTS.realtimeInterval : DEFAULTS.roomInterval);

@ -130,11 +130,7 @@ app.cacheBuster = null;
app.enterRoom = function (room, callback) { app.enterRoom = function (room, callback) {
callback = callback || function() {}; callback = callback || function() {};
if (socket) { if (socket && app.user.uid && app.currentRoom !== room) {
if (app.currentRoom === room) {
return;
}
socket.emit('meta.rooms.enter', { socket.emit('meta.rooms.enter', {
enter: room, enter: room,
username: app.user.username, username: app.user.username,
@ -148,6 +144,7 @@ app.cacheBuster = null;
return app.alertError(err.message); return app.alertError(err.message);
} }
app.currentRoom = room; app.currentRoom = room;
callback();
}); });
} }
}; };

@ -8,12 +8,11 @@ define('forum/topic', [
'forum/topic/threadTools', 'forum/topic/threadTools',
'forum/topic/postTools', 'forum/topic/postTools',
'forum/topic/events', 'forum/topic/events',
'forum/topic/browsing',
'forum/topic/posts', 'forum/topic/posts',
'navigator', 'navigator',
'sort', 'sort',
'components' 'components'
], function(infinitescroll, threadTools, postTools, events, browsing, posts, navigator, sort, components) { ], function(infinitescroll, threadTools, postTools, events, posts, navigator, sort, components) {
var Topic = {}, var Topic = {},
currentUrl = ''; currentUrl = '';
@ -70,16 +69,6 @@ define('forum/topic', [
$(window).on('scroll', updateTopicTitle); $(window).on('scroll', updateTopicTitle);
if (app.user.uid) {
socket.emit('topics.enter', tid, function(err, data) {
if (err) {
return app.alertError(err.message);
}
browsing.onUpdateUsersInRoom(data);
});
}
handleTopicSearch(); handleTopicSearch();
$(window).trigger('action:topic.loaded'); $(window).trigger('action:topic.loaded');

@ -1,111 +0,0 @@
'use strict';
/* globals define, app, config, socket, ajaxify */
define('forum/topic/browsing', function() {
var Browsing = {};
Browsing.onUpdateUsersInRoom = function(data) {
if (data && data.room.indexOf('topic_' + ajaxify.data.tid) !== -1) {
$('[component="topic/browsing/list"]').parent().toggleClass('hidden', !data.users.length);
for (var i=0; i<data.users.length; ++i) {
addUserIcon(data.users[i]);
}
updateUserCount(data.hidden);
}
};
Browsing.onUserEnter = function(data) {
var browsingList = $('[component="topic/browsing/list"]');
var user = browsingList.find('a[data-uid="' + data.uid + '"]');
if (!user.length && browsingList.first().children().length < 10) {
addUserIcon(data);
} else if (user.length) {
user.attr('data-count', parseInt(user.attr('data-count'), 10) + 1);
} else {
increaseUserCount(1);
}
Browsing.onUserStatusChange(data);
};
Browsing.onUserLeave = function(uid) {
if (app.user.uid === parseInt(uid, 10)) {
return;
}
var browsingList = $('[component="topic/browsing/list"]');
var user = browsingList.find('a[data-uid="' + uid + '"]');
if (user.length) {
var count = Math.max(0, parseInt(user.attr('data-count'), 10) - 1);
user.attr('data-count', count);
if (count <= 0) {
user.parent().remove();
if (!browsingList.children(':not(.hidden)').length) {
browsingList.parent().addClass('hidden');
}
}
} else {
increaseUserCount(-1);
}
};
Browsing.onUserStatusChange = function(data) {
app.updateUserStatus($('[data-uid="' + data.uid + '"] [component="user/status"]'), data.status);
updateBrowsingUsers(data);
};
function updateBrowsingUsers(data) {
var browsingList = $('[component="topic/browsing/list"]');
var user = browsingList.find('a[data-uid="'+ data.uid + '"]');
if (user.length) {
user.parent().toggleClass('hidden', data.status === 'offline');
browsingList.parent().toggleClass('hidden', !browsingList.children(':not(.hidden)').length);
}
}
function addUserIcon(user) {
if (!user.userslug) {
return;
}
var browsingList = $('[component="topic/browsing/list"]');
var userEl = createUserIcon(user.uid, user.picture, user.userslug, user.username, user['icon:bgColor'], user['icon:text']);
var isSelf = parseInt(user.uid, 10) === parseInt(app.user.uid, 10);
if (isSelf) {
browsingList.prepend(userEl);
} else {
browsingList.append(userEl);
}
browsingList.find('a[data-uid]').tooltip({
placement: 'top'
});
}
function createUserIcon(uid, picture, userslug, username, iconBg, iconText) {
if (!$('[component="topic/browsing/list"]').find('[data-uid="' + uid + '"]').length) {
var imgOrIcon = picture ?
'<img src="'+ picture +'" />' :
'<div class="user-icon" style="background-color: ' + iconBg + ';">' + iconText + '</div>';
return $('<div class="inline-block"><a title="' + username + '" data-uid="' + uid + '" data-count="1" href="' + config.relative_path + '/user/' + userslug + '">' + imgOrIcon + '</a></div>');
}
}
function updateUserCount(count) {
count = parseInt(count, 10);
if (!count || count < 0) {
count = 0;
}
$('[component="topic/browsing/count"]').text(count).parent().toggleClass('hidden', count === 0);
}
function increaseUserCount(incr) {
updateUserCount(parseInt($('[component="topic/browsing/count"]').first().text(), 10) + incr);
}
return Browsing;
});

@ -4,19 +4,16 @@
/* globals config, app, ajaxify, define, socket, templates, translator, utils */ /* globals config, app, ajaxify, define, socket, templates, translator, utils */
define('forum/topic/events', [ define('forum/topic/events', [
'forum/topic/browsing',
'forum/topic/postTools', 'forum/topic/postTools',
'forum/topic/threadTools', 'forum/topic/threadTools',
'forum/topic/posts', 'forum/topic/posts',
'components' 'components'
], function(browsing, postTools, threadTools, posts, components) { ], function(postTools, threadTools, posts, components) {
var Events = {}; var Events = {};
var events = { var events = {
'event:user_enter': browsing.onUserEnter, 'event:user_status_change': onUserStatusChange,
'event:user_leave': browsing.onUserLeave,
'event:user_status_change': browsing.onUserStatusChange,
'event:voted': updatePostVotesAndUserReputation, 'event:voted': updatePostVotesAndUserReputation,
'event:favourited': updateFavouriteCount, 'event:favourited': updateFavouriteCount,
@ -69,6 +66,10 @@ define('forum/topic/events', [
} }
}; };
function onUserStatusChange(data) {
app.updateUserStatus($('[data-uid="' + data.uid + '"] [component="user/status"]'), data.status);
}
function updatePostVotesAndUserReputation(data) { function updatePostVotesAndUserReputation(data) {
var votes = components.get('post/vote-count', data.post.pid), var votes = components.get('post/vote-count', data.post.pid),
reputationElements = $('.reputation[data-uid="' + data.post.uid + '"]'); reputationElements = $('.reputation[data-uid="' + data.post.uid + '"]');

@ -10,9 +10,6 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
var chatsToggleEl = components.get('chat/dropdown'), var chatsToggleEl = components.get('chat/dropdown'),
chatsListEl = components.get('chat/list'); chatsListEl = components.get('chat/list');
// Sync open chats between all user socket sessions
module.sync();
chatsToggleEl.on('click', function() { chatsToggleEl.on('click', function() {
if (chatsToggleEl.parent().hasClass('open')) { if (chatsToggleEl.parent().hasClass('open')) {
return; return;
@ -93,42 +90,6 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
var modal = module.getModal(data.uid); var modal = module.getModal(data.uid);
app.updateUserStatus(modal.find('[component="user/status"]'), data.status); app.updateUserStatus(modal.find('[component="user/status"]'), data.status);
}); });
socket.on('query:chats.sync', function(data, callback) {
var chats = Array.prototype.map.call(taskbar.get('chat'), function(chatObj) {
return {
username: chatObj.options.title,
uid: chatObj.options.touid,
new: chatObj.element.hasClass('new')
};
});
callback(null, chats);
});
socket.on('event:chats.open', function(data) {
data.silent = true;
module.createModal(data);
});
socket.on('event:chats.close', function(uid) {
module.close(module.getModal(uid), true);
});
socket.on('event:chats.toggleNew', function(data) {
var uuid = module.getModal(data.uid).attr('UUID');
module.toggleNew(uuid, data.state, true);
});
$(window).on('action:taskbar.toggleNew', function(ev, uuid) {
var modal = $('.chat-modal[uuid="' + uuid + '"]'),
touid = modal.attr('touid');
socket.emit('modules.chats.toggleNew', {
uid: touid,
state: false
});
});
}; };
module.loadChatsDropdown = function(chatsListEl) { module.loadChatsDropdown = function(chatsListEl) {
@ -318,10 +279,6 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
state: '' state: ''
}); });
if (!data.silent) {
socket.emit('modules.chats.open', data);
}
$(window).trigger('action:chat.loaded', chatModal); $(window).trigger('action:chat.loaded', chatModal);
if (typeof callback === 'function') { if (typeof callback === 'function') {
@ -343,10 +300,6 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
taskbar.discard('chat', chatModal.attr('UUID')); taskbar.discard('chat', chatModal.attr('UUID'));
Chats.notifyTyping(chatModal.attr('touid'), false); Chats.notifyTyping(chatModal.attr('touid'), false);
if (!silent) {
socket.emit('modules.chats.close', chatModal.attr('touid'));
}
if (chatModal.attr('data-mobile')) { if (chatModal.attr('data-mobile')) {
module.disableMobileBehaviour(chatModal); module.disableMobileBehaviour(chatModal);
} }
@ -423,27 +376,7 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
socket.emit('modules.chats.canMessage', toUid, callback); socket.emit('modules.chats.canMessage', toUid, callback);
}; };
module.sync = function() {
socket.emit('modules.chats.sync', function(err, users) {
if (err) {
return app.alertError(err.message);
}
users.forEach(function(user) {
if (!module.modalExists(user.uid)) {
module.createModal({
username: user.username,
touid: user.uid,
silent: true
}, function(modal) {
if (user.new) {
module.toggleNew(modal.attr('UUID'), true, true);
}
});
}
});
});
};
return module; return module;
}); });

@ -1,8 +1,7 @@
'use strict'; 'use strict';
var cronJob = require('cron').CronJob, var cronJob = require('cron').CronJob;
db = require('./database'); var db = require('./database');
(function(Analytics) { (function(Analytics) {
@ -61,6 +60,8 @@ var cronJob = require('cron').CronJob,
db.incrObjectFieldBy('global', 'uniqueIPCount', uniqueIPCount); db.incrObjectFieldBy('global', 'uniqueIPCount', uniqueIPCount);
uniqueIPCount = 0; uniqueIPCount = 0;
} }
}; };
Analytics.getUnwrittenPageviews = function() { Analytics.getUnwrittenPageviews = function() {

@ -58,7 +58,7 @@ helpers.getUserDataByUserSlug = function(userslug, callerUID, callback) {
var self = parseInt(callerUID, 10) === parseInt(userData.uid, 10); var self = parseInt(callerUID, 10) === parseInt(userData.uid, 10);
userData.joindate = utils.toISOString(userData.joindate); userData.joindate = utils.toISOString(userData.joindate);
userData.lastonline = userData.lastonline ? utils.toISOString(userData.lastonline) : userData.joindate; userData.lastonlineISO = utils.toISOString(userData.lastonline || userData.joindate);
userData.age = userData.birthday ? Math.floor((new Date().getTime() - new Date(userData.birthday).getTime()) / 31536000000) : ''; userData.age = userData.birthday ? Math.floor((new Date().getTime() - new Date(userData.birthday).getTime()) / 31536000000) : '';
if (!(isAdmin || self || (userData.email && userSettings.showemail))) { if (!(isAdmin || self || (userData.email && userSettings.showemail))) {
@ -86,7 +86,7 @@ helpers.getUserDataByUserSlug = function(userslug, callerUID, callback) {
userData['email:confirmed'] = !!parseInt(userData['email:confirmed'], 10); userData['email:confirmed'] = !!parseInt(userData['email:confirmed'], 10);
userData.profile_links = results.profile_links; userData.profile_links = results.profile_links;
userData.sso = results.sso.associations; userData.sso = results.sso.associations;
userData.status = require('../../socket.io').isUserOnline(userData.uid) ? (userData.status || 'online') : 'offline'; userData.status = user.getStatus(userData);
userData.banned = parseInt(userData.banned, 10) === 1; userData.banned = parseInt(userData.banned, 10) === 1;
userData.website = validator.escape(userData.website); userData.website = validator.escape(userData.website);
userData.websiteLink = !userData.website.startsWith('http') ? 'http://' + userData.website : userData.website; userData.websiteLink = !userData.website.startsWith('http') ? 'http://' + userData.website : userData.website;

@ -125,6 +125,7 @@ topicsController.get = function(req, res, callback) {
}); });
}, },
function (topicData, next) { function (topicData, next) {
var breadcrumbs = [ var breadcrumbs = [
{ {
text: topicData.category.name, text: topicData.category.name,
@ -265,6 +266,18 @@ topicsController.get = function(req, res, callback) {
topics.increaseViewCount(tid); topics.increaseViewCount(tid);
if (req.uid) {
topics.markAsRead([tid], req.uid, function(err, markedRead) {
if (err) {
return callback(err);
}
if (markedRead) {
topics.pushUnreadCount(req.uid);
topics.markTopicNotificationsRead(tid, req.uid);
}
});
}
plugins.fireHook('filter:topic.build', {req: req, res: res, templateData: data}, function(err, data) { plugins.fireHook('filter:topic.build', {req: req, res: res, templateData: data}, function(err, data) {
if (err) { if (err) {
return callback(err); return callback(err);

@ -149,84 +149,6 @@ usersController.getUsersForSearch = function(req, res, next) {
}); });
}; };
usersController.getMap = function(req, res, next) {
var socketIO = require('../socket.io');
var rooms = require('../socket.io/rooms');
var roomNames = ['user_list', 'categories', 'unread_topics', 'recent_topics', 'popular_topics', 'tags'];
var links = {
user_list: '/users',
categories: '/categories',
unread_topics: '/unread',
recent_topics: '/recent',
popular_topics: '/popular',
tags: '/tags'
};
var keys = Object.keys(rooms.roomClients());
keys = keys.filter(function(key) {
return key.startsWith('topic_') || key.startsWith('category_');
});
roomNames = roomNames.concat(keys);
async.map(roomNames, function(roomName, next) {
socketIO.getUsersInRoom(0, roomName, 0, 39, function(err, data) {
if (err) {
return next(err);
}
if (roomName.startsWith('category_')) {
var cid = roomName.split('_')[1];
categories.getCategoryFields(cid, ['slug', 'name'], function(err, categoryData) {
if (err) {
return next(err);
}
data.room = validator.escape(categoryData.name);
data.link = '/category/' + categoryData.slug;
data.core = false;
next(null, data);
});
} else if (roomName.startsWith('topic_')) {
var tid = roomName.split('_')[1];
topics.getTopicFields(tid, ['slug', 'title'], function(err, topicData) {
if (err) {
return next(err);
}
data.room = validator.escape(topicData.title);
data.link = '/topic/' + topicData.slug;
data.core = false;
next(null, data);
});
} else {
data.core = true;
next(null, data);
}
});
}, function(err, data) {
if (err) {
return next(err);
}
data.sort(function(a, b) {
return b.users.length - a.users.length;
});
data.forEach(function(room) {
if (!room.link) {
room.link = links[room.room];
}
});
res.render('usersMap', {
rooms: data,
'reputation:disabled': parseInt(meta.config['reputation:disabled'], 10) === 1,
title: '[[pages:users/map]]',
breadcrumbs: helpers.buildBreadcrumbs([{text: '[[global:users]]', url: '/users'}, {text: '[[global:map]]'}])
});
});
};
function render(req, res, data, next) { function render(req, res, data, next) {
plugins.fireHook('filter:users.build', {req: req, res: res, templateData: data}, function(err, data) { plugins.fireHook('filter:users.build', {req: req, res: res, templateData: data}, function(err, data) {
if (err) { if (err) {

@ -321,7 +321,7 @@ module.exports = function(db, module) {
return callback(); return callback();
} }
value = helpers.valueToString(value); value = helpers.valueToString(value);
db.collection('objects').findOne({_key:key, value: value}, {fields:{_id: 0, score: 1}}, function(err, result) { db.collection('objects').findOne({_key: key, value: value}, {fields:{_id: 0, score: 1}}, function(err, result) {
callback(err, result ? result.score : null); callback(err, result ? result.score : null);
}); });
}; };

@ -266,7 +266,7 @@ var db = require('./database'),
db.isSortedSetMembers('uid:' + uid + ':chats:unread', uids, next); db.isSortedSetMembers('uid:' + uid + ':chats:unread', uids, next);
}, },
users: function(next) { users: function(next) {
user.getUsersFields(uids, ['uid', 'username', 'picture', 'status'] , next); user.getUsersFields(uids, ['uid', 'username', 'picture', 'status', 'lastonline'] , next);
}, },
teasers: function(next) { teasers: function(next) {
async.map(uids, function(fromuid, next) { async.map(uids, function(fromuid, next) {
@ -288,11 +288,11 @@ var db = require('./database'),
return callback(err); return callback(err);
} }
results.users.forEach(function(user, index) { results.users.forEach(function(userData, index) {
if (user && parseInt(user.uid, 10)) { if (userData && parseInt(userData.uid, 10)) {
user.unread = results.unread[index]; userData.unread = results.unread[index];
user.status = sockets.isUserOnline(user.uid) ? user.status : 'offline'; userData.status = user.getStatus(userData);
user.teaser = results.teasers[index]; userData.teaser = results.teasers[index];
} }
}); });
@ -416,8 +416,9 @@ var db = require('./database'),
}; };
function sendNotifications(fromuid, touid, messageObj, callback) { function sendNotifications(fromuid, touid, messageObj, callback) {
if (sockets.isUserOnline(touid)) { user.isOnline(touid, function(err, isOnline) {
return callback(); if (err || isOnline) {
return callback(err);
} }
notifications.create({ notifications.create({
@ -446,6 +447,7 @@ var db = require('./database'),
}); });
} }
}); });
});
} }
}(exports)); }(exports));

@ -52,7 +52,6 @@ module.exports = function(Meta) {
'public/src/client/recent.js', 'public/src/client/recent.js',
'public/src/client/unread.js', 'public/src/client/unread.js',
'public/src/client/topic.js', 'public/src/client/topic.js',
'public/src/client/topic/browsing.js',
'public/src/client/topic/events.js', 'public/src/client/topic/events.js',
'public/src/client/topic/flag.js', 'public/src/client/topic/flag.js',
'public/src/client/topic/fork.js', 'public/src/client/topic/fork.js',

@ -20,10 +20,7 @@ module.exports = function(Posts) {
user.getMultipleUserSettings(uids, next); user.getMultipleUserSettings(uids, next);
}, },
userData: function(next) { userData: function(next) {
user.getUsersFields(uids, ['uid', 'username', 'fullname', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned', 'status'], next); user.getUsersFields(uids, ['uid', 'username', 'fullname', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned', 'status', 'lastonline'], next);
},
online: function(next) {
require('../socket.io').isUsersOnline(uids, next);
} }
}, function(err, results) { }, function(err, results) {
if (err) { if (err) {
@ -47,7 +44,7 @@ module.exports = function(Posts) {
userData.selectedGroup = userData.groups[index]; userData.selectedGroup = userData.groups[index];
} }
}); });
userData.status = user.getStatus(userData.status, results.online[i]); userData.status = user.getStatus(userData);
}); });
async.map(userData, function(userData, next) { async.map(userData, function(userData, next) {

@ -62,7 +62,6 @@ function userRoutes(app, middleware, controllers) {
setupPageRoute(app, '/users/sort-posts', middleware, middlewares, controllers.users.getUsersSortedByPosts); setupPageRoute(app, '/users/sort-posts', middleware, middlewares, controllers.users.getUsersSortedByPosts);
setupPageRoute(app, '/users/sort-reputation', middleware, middlewares, controllers.users.getUsersSortedByReputation); setupPageRoute(app, '/users/sort-reputation', middleware, middlewares, controllers.users.getUsersSortedByReputation);
setupPageRoute(app, '/users/search', middleware, middlewares, controllers.users.getUsersForSearch); setupPageRoute(app, '/users/search', middleware, middlewares, controllers.users.getUsersForSearch);
setupPageRoute(app, '/users/map', middleware, middlewares, controllers.users.getMap);
} }

@ -28,6 +28,7 @@ var async = require('async'),
tags: require('./admin/tags'), tags: require('./admin/tags'),
rewards: require('./admin/rewards'), rewards: require('./admin/rewards'),
navigation: require('./admin/navigation'), navigation: require('./admin/navigation'),
rooms: require('./admin/rooms'),
themes: {}, themes: {},
plugins: {}, plugins: {},
widgets: {}, widgets: {},

@ -0,0 +1,73 @@
'use strict';
var validator = require('validator');
var topics = require('../../topics');
var SocketRooms = {};
SocketRooms.getAll = function(socket, data, callback) {
var websockets = require('../index');
var io = websockets.server;
if (!io) {
return;
}
var roomClients = io.sockets.adapter.rooms;
var socketData = {
onlineGuestCount: websockets.getOnlineAnonCount(),
onlineRegisteredCount: websockets.getOnlineUserCount(),
socketCount: websockets.getSocketCount(),
users: {
categories: roomClients.categories ? Object.keys(roomClients.categories).length : 0,
recent: roomClients.recent_topics ? Object.keys(roomClients.recent_topics).length : 0,
unread: roomClients.unread_topics ? Object.keys(roomClients.unread_topics).length: 0,
popular: roomClients.popular_topics ? Object.keys(roomClients.popular_topics).length: 0,
topics: 0,
category: 0
},
topics: {}
};
var topTenTopics = [],
tid;
for (var room in roomClients) {
if (roomClients.hasOwnProperty(room)) {
tid = room.match(/^topic_(\d+)/);
if (tid) {
var length = Object.keys(roomClients[room]).length;
socketData.users.topics += length;
topTenTopics.push({tid: tid[1], count: length});
} else if (room.match(/^category/)) {
socketData.users.category += Object.keys(roomClients[room]).length;
}
}
}
topTenTopics = topTenTopics.sort(function(a, b) {
return b.count - a.count;
}).slice(0, 10);
var topTenTids = topTenTopics.map(function(topic) {
return topic.tid;
});
topics.getTopicsFields(topTenTids, ['title'], function(err, titles) {
if (err) {
return callback(err);
}
topTenTopics.forEach(function(topic, index) {
socketData.topics[topic.tid] = {
value: topic.count || 0,
title: validator.escape(titles[index].title)
};
});
callback(null, socketData);
});
};
module.exports = SocketRooms;

@ -137,11 +137,6 @@ SocketCategories.getTopicCount = function(socket, cid, callback) {
categories.getCategoryField(cid, 'topic_count', callback); categories.getCategoryField(cid, 'topic_count', callback);
}; };
SocketCategories.getUsersInCategory = function(socket, cid, callback) {
var uids = websockets.getUidsInRoom('category_' + cid);
user.getUsersFields(uids, ['uid', 'userslug', 'username', 'picture'], callback);
};
SocketCategories.getCategoriesByPrivilege = function(socket, privilege, callback) { SocketCategories.getCategoriesByPrivilege = function(socket, privilege, callback) {
categories.getCategoriesByPrivilege('categories:cid', socket.uid, privilege, callback); categories.getCategoriesByPrivilege('categories:cid', socket.uid, privilege, callback);
}; };

@ -11,7 +11,6 @@ var SocketIO = require('socket.io'),
user = require('../user'), user = require('../user'),
logger = require('../logger'), logger = require('../logger'),
ratelimit = require('../middleware/ratelimit'), ratelimit = require('../middleware/ratelimit'),
rooms = require('./rooms'),
Sockets = {}, Sockets = {},
Namespaces = {}; Namespaces = {};
@ -46,12 +45,6 @@ function onConnection(socket) {
onConnect(socket); onConnect(socket);
// see https://github.com/Automattic/socket.io/issues/1814 and
// http://stackoverflow.com/questions/25830415/get-the-list-of-rooms-the-client-is-currently-in-on-disconnect-event
socket.onclose = function(reason) {
Object.getPrototypeOf(this).onclose.call(this, {reason: reason, rooms: socket.rooms.slice()});
};
socket.on('disconnect', function(data) { socket.on('disconnect', function(data) {
onDisconnect(socket, data); onDisconnect(socket, data);
}); });
@ -63,8 +56,8 @@ function onConnection(socket) {
function onConnect(socket) { function onConnect(socket) {
if (socket.uid) { if (socket.uid) {
rooms.enter(socket, 'uid_' + socket.uid); socket.join('uid_' + socket.uid);
rooms.enter(socket, 'online_users'); socket.join('online_users');
user.getUserFields(socket.uid, ['status'], function(err, userData) { user.getUserFields(socket.uid, ['status'], function(err, userData) {
if (err || !userData) { if (err || !userData) {
@ -76,25 +69,17 @@ function onConnect(socket) {
} }
}); });
} else { } else {
rooms.enter(socket, 'online_guests'); socket.join('online_guests');
} }
} }
function onDisconnect(socket, data) { function onDisconnect(socket) {
if (socket.uid) { if (socket.uid) {
var socketCount = Sockets.getUserSocketCount(socket.uid); var socketCount = Sockets.getUserSocketCount(socket.uid);
if (socketCount <= 1) { if (socketCount <= 1) {
socket.broadcast.emit('event:user_status_change', {uid: socket.uid, status: 'offline'}); socket.broadcast.emit('event:user_status_change', {uid: socket.uid, status: 'offline'});
} }
// see https://github.com/Automattic/socket.io/issues/1814
data.rooms.forEach(function(roomName) {
if (roomName.startsWith('topic')) {
io.in(roomName).emit('event:user_leave', socket.uid);
}
});
} }
rooms.leaveAll(socket, data.rooms);
} }
function onMessage(socket, payload) { function onMessage(socket, payload) {
@ -211,25 +196,32 @@ Sockets.in = function(room) {
}; };
Sockets.getSocketCount = function() { Sockets.getSocketCount = function() {
return rooms.socketCount(); if (!io) {
return 0;
}
return io.sockets.sockets.length;
}; };
Sockets.getUserSocketCount = function(uid) { Sockets.getUserSocketCount = function(uid) {
return rooms.clients('uid_' + uid).length; if (!io) {
return 0;
}
return io.sockets.adapter.rooms['uid_' + uid] ? Object.keys(io.sockets.adapter.rooms['uid_' + uid]).length : 0;
}; };
Sockets.getOnlineUserCount = function() { Sockets.getOnlineUserCount = function() {
var count = 0; if (!io) {
Object.keys(rooms.roomClients()).forEach(function(roomName) { return 0;
if (roomName.startsWith('uid_')) {
++ count;
} }
});
return count; return io.sockets.adapter.rooms.online_users ? Object.keys(io.sockets.adapter.rooms.online_users).length : 0;
}; };
Sockets.getOnlineAnonCount = function () { Sockets.getOnlineAnonCount = function () {
return rooms.clients('online_guests').length; if (!io) {
return 0;
}
return io.sockets.adapter.rooms.online_guests ? Object.keys(io.sockets.adapter.rooms.online_guests).length : 0;
}; };
Sockets.reqFromSocket = function(socket) { Sockets.reqFromSocket = function(socket) {
@ -249,76 +241,30 @@ Sockets.reqFromSocket = function(socket) {
}; };
Sockets.isUserOnline = function(uid) { Sockets.isUserOnline = function(uid) {
return !!rooms.clients('uid_' + uid).length; winston.warn('[deprecated] Sockets.isUserOnline')
return false;
}; };
Sockets.isUsersOnline = function(uids, callback) { Sockets.isUsersOnline = function(uids, callback) {
callback(null, uids.map(Sockets.isUserOnline)); winston.warn('[deprecated] Sockets.isUsersOnline')
callback(null, uids.map(function() { return false; }));
}; };
Sockets.getUsersInRoom = function (uid, roomName, start, stop, callback) { Sockets.getUsersInRoom = function (uid, roomName, start, stop, callback) {
if (!roomName) { winston.warn('[deprecated] Sockets.getUsersInRoom')
return;
}
var uids = Sockets.getUidsInRoom(roomName);
var total = uids.length;
if (stop !== -1) {
uids = uids.slice(start, stop);
}
if (uid && uids.indexOf(uid.toString()) === -1) {
uids = [uid].concat(uids);
}
if (!uids.length) {
return callback(null, {users: [], total: 0 , room: roomName});
}
user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'status'], function(err, users) {
if (err) {
return callback(err);
}
users = users.filter(function(user) {
return user && user.status !== 'offline';
});
callback(null, { callback(null, {
users: users, users: [],
room: roomName, room: roomName,
total: users.length ? total : 0, total: 0,
hidden: Math.max(0, total - uids.length) hidden: 0
});
}); });
return;
}; };
Sockets.getUidsInRoom = function(roomName, callback) { Sockets.getUidsInRoom = function(roomName, callback) {
winston.warn('[deprecated] Sockets.getUidsInRoom')
callback = callback || function() {}; callback = callback || function() {};
var uids = [];
var socketids = rooms.clients(roomName);
if (!Array.isArray(socketids) || !socketids.length) {
callback(null, []); callback(null, []);
return [];
}
for(var i=0; i<socketids.length; ++i) {
var socketRooms = rooms.clientRooms(socketids[i]);
if (Array.isArray(socketRooms)) {
socketRooms.forEach(function(roomName) {
if (roomName.startsWith('uid_')) {
var uid = roomName.split('_')[1];
if (uids.indexOf(uid) === -1) {
uids.push(uid);
}
}
});
}
}
callback(null, uids);
return uids;
}; };

@ -1,12 +1,9 @@
'use strict'; 'use strict';
var validator = require('validator'), var meta = require('../meta'),
meta = require('../meta'),
user = require('../user'), user = require('../user'),
topics = require('../topics'), topics = require('../topics'),
emitter = require('../emitter'), emitter = require('../emitter'),
rooms = require('./rooms'),
websockets = require('./'), websockets = require('./'),
@ -50,16 +47,8 @@ SocketMeta.rooms.enter = function(socket, data, callback) {
leaveCurrentRoom(socket); leaveCurrentRoom(socket);
if (data.enter) { if (data.enter) {
rooms.enter(socket, data.enter); socket.join(data.enter);
socket.currentRoom = data.enter; socket.currentRoom = data.enter;
if (data.enter.indexOf('topic') !== -1) {
data.uid = socket.uid;
data.picture = validator.escape(data.picture);
data.username = validator.escape(data.username);
data.userslug = validator.escape(data.userslug);
websockets.in(data.enter).emit('event:user_enter', data);
}
} }
callback(); callback();
}; };
@ -74,70 +63,11 @@ SocketMeta.rooms.leaveCurrent = function(socket, data, callback) {
function leaveCurrentRoom(socket) { function leaveCurrentRoom(socket) {
if (socket.currentRoom) { if (socket.currentRoom) {
rooms.leave(socket, socket.currentRoom); socket.leave(socket.currentRoom);
if (socket.currentRoom.indexOf('topic') !== -1) {
websockets.in(socket.currentRoom).emit('event:user_leave', socket.uid);
}
socket.currentRoom = ''; socket.currentRoom = '';
} }
} }
SocketMeta.rooms.getAll = function(socket, data, callback) {
var roomClients = rooms.roomClients();
var socketData = {
onlineGuestCount: websockets.getOnlineAnonCount(),
onlineRegisteredCount: websockets.getOnlineUserCount(),
socketCount: websockets.getSocketCount(),
users: {
categories: roomClients.categories ? roomClients.categories.length : 0,
recent: roomClients.recent_topics ? roomClients.recent_topics.length : 0,
unread: roomClients.unread_topics ? roomClients.unread_topics.length: 0,
popular: roomClients.popular_topics ? roomClients.popular_topics.length: 0,
topics: 0,
category: 0
},
topics: {}
};
var topTenTopics = [],
tid;
for (var room in roomClients) {
if (roomClients.hasOwnProperty(room)) {
tid = room.match(/^topic_(\d+)/);
if (tid) {
var length = roomClients[room].length;
socketData.users.topics += length;
topTenTopics.push({tid: tid[1], count: length});
} else if (room.match(/^category/)) {
socketData.users.category += roomClients[room].length;
}
}
}
topTenTopics = topTenTopics.sort(function(a, b) {
return b.count - a.count;
}).slice(0, 10);
var topTenTids = topTenTopics.map(function(topic) {
return topic.tid;
});
topics.getTopicsFields(topTenTids, ['title'], function(err, titles) {
if (err) {
return callback(err);
}
topTenTopics.forEach(function(topic, index) {
socketData.topics[topic.tid] = {
value: topic.count || 0,
title: validator.escape(titles[index].title)
};
});
callback(null, socketData);
});
};
module.exports = SocketMeta; module.exports = SocketMeta;

@ -7,7 +7,6 @@ var meta = require('../meta'),
async = require('async'), async = require('async'),
server = require('./'), server = require('./'),
rooms = require('./rooms'),
SocketModules = { SocketModules = {
chats: {}, chats: {},
@ -105,37 +104,6 @@ SocketModules.chats.getRecentChats = function(socket, data, callback) {
Messaging.getRecentChats(socket.uid, start, stop, callback); Messaging.getRecentChats(socket.uid, start, stop, callback);
}; };
SocketModules.chats.sync = function(socket, data, callback) {
var chats = [],
uids = [],
socketIds = rooms.clients('uid_' + socket.uid);
rooms.broadcast(socket, 'uid_' + socket.uid, 'query:chats.sync', {}, function(err, sessionData) {
sessionData.forEach(function(data) {
data.forEach(function(chat) {
if (uids.indexOf(chat.uid) === -1) {
chats.push(chat);
uids.push(chat.uid);
}
});
});
callback(err, chats);
});
};
SocketModules.chats.open = function(socket, data, callback) {
rooms.broadcast(socket, 'uid_' + socket.uid, 'event:chats.open', data);
};
SocketModules.chats.close = function(socket, data, callback) {
rooms.broadcast(socket, 'uid_' + socket.uid, 'event:chats.close', data);
};
SocketModules.chats.toggleNew = function(socket, data, callback) {
rooms.broadcast(socket, 'uid_' + socket.uid, 'event:chats.toggleNew', data);
};
/* Sounds */ /* Sounds */
SocketModules.sounds.getSounds = function(socket, data, callback) { SocketModules.sounds.getSounds = function(socket, data, callback) {

@ -63,15 +63,14 @@ module.exports = function(SocketPosts) {
async.parallel({ async.parallel({
admins: async.apply(groups.getMembers, 'administrators', 0, -1), admins: async.apply(groups.getMembers, 'administrators', 0, -1),
moderators: async.apply(groups.getMembers, 'cid:' + result.topic.cid + ':privileges:mods', 0, -1), moderators: async.apply(groups.getMembers, 'cid:' + result.topic.cid + ':privileges:mods', 0, -1)
uidsInTopic: async.apply(websockets.getUidsInRoom, 'topic_' + result.topic.tid)
}, function(err, results) { }, function(err, results) {
if (err) { if (err) {
return winston.error(err); return winston.error(err);
} }
var uids = results.uidsInTopic.filter(function(uid) { var uids = results.admins.concat(results.moderators).filter(function(uid, index, array) {
return (results.admins.indexOf(uid) !== -1 || results.moderators.indexOf(uid) !== -1) && parseInt(uid, 10) !== socket.uid; return uid && array.indexOf(uid) === index;
}); });
uids.forEach(function(uid) { uids.forEach(function(uid) {

@ -1,129 +0,0 @@
'use strict';
// Temp solution until
// https://github.com/NodeBB/NodeBB/issues/2486
// and
// https://github.com/Automattic/socket.io/issues/1945
// are closed.
// Once they are closed switch to .clients() and async calls
var pubsub = require('../pubsub'),
async = require('async');
var rooms = {};
var clientRooms = {};
var roomClients = {};
rooms.enter = function(socket, room) {
socket.join(room);
pubsub.publish('socket:join', {id: socket.id, room: room});
};
rooms.leave = function(socket, room) {
socket.leave(room);
pubsub.publish('socket:leave', {id: socket.id, room: room});
};
rooms.leaveAll = function(socket, roomsToLeave) {
roomsToLeave.forEach(function(room) {
rooms.leave(socket, room);
});
};
rooms.broadcast = function(socket, room, msg, data, callback) {
var io = require('./'),
socketIds = rooms.clients(room);
callback = callback || function() {};
// Filter out socketIds that aren't actually connected
socketIds = socketIds.filter(function(id) {
return io.server.sockets.connected.hasOwnProperty(id);
});
async.map(socketIds, function(id, next) {
var timeout,
timeoutPassed = false;
if (socket.id === id) {
return setImmediate(next, null, []);
}
timeout = setTimeout(function() {
timeoutPassed = true;
next(null, []);
}, 500);
io.server.sockets.connected[id].emit(msg, data || {}, function(err, returnData) {
clearTimeout(timeout);
if (!timeoutPassed) {
next(null, returnData);
}
});
}, callback);
};
pubsub.on('socket:join', onSocketJoin);
pubsub.on('socket:leave', onSocketLeave);
function onSocketJoin(data) {
clientRooms[data.id] = clientRooms[data.id] || [];
if (clientRooms[data.id].indexOf(data.room) === -1) {
clientRooms[data.id].push(data.room);
}
roomClients[data.room] = roomClients[data.room] || [];
if (roomClients[data.room].indexOf(data.id) === -1) {
roomClients[data.room].push(data.id);
}
}
function onSocketLeave(data) {
var index;
if (Array.isArray(clientRooms[data.id])) {
index = clientRooms[data.id].indexOf(data.room);
if (index !== -1) {
clientRooms[data.id].splice(index, 1);
if (!clientRooms[data.id].length) {
delete clientRooms[data.id];
}
}
}
if (Array.isArray(roomClients[data.room])) {
index = roomClients[data.room].indexOf(data.id);
if (index !== -1) {
roomClients[data.room].splice(index, 1);
if (!roomClients[data.room].length) {
delete roomClients[data.room];
}
}
}
}
rooms.clients = function(room) {
return Array.isArray(roomClients[room]) ? roomClients[room] : [];
};
rooms.clientRooms = function(id) {
return Array.isArray(clientRooms[id]) ? clientRooms[id] : [];
};
rooms.socketCount = function() {
return Object.keys(clientRooms || {}).length;
};
rooms.roomClients = function() {
return roomClients;
};
module.exports = rooms;

@ -74,22 +74,6 @@ SocketTopics.post = function(socket, data, callback) {
}); });
}; };
SocketTopics.enter = function(socket, tid, callback) {
if (!parseInt(tid, 10) || !socket.uid) {
return;
}
async.parallel({
markAsRead: function(next) {
SocketTopics.markAsRead(socket, [tid], next);
},
users: function(next) {
websockets.getUsersInRoom(socket.uid, 'topic_' + tid, 0, 9, next);
}
}, function(err, result) {
callback(err, result ? result.users : null);
});
};
SocketTopics.postcount = function(socket, tid, callback) { SocketTopics.postcount = function(socket, tid, callback) {
topics.getTopicField(tid, 'postcount', callback); topics.getTopicField(tid, 'postcount', callback);
}; };

@ -8,15 +8,12 @@ module.exports = function(SocketUser) {
if (!socket.uid) { if (!socket.uid) {
return callback('[[error:invalid-uid]]'); return callback('[[error:invalid-uid]]');
} }
var online = websockets.isUserOnline(uid);
if (!online) { user.getUserFields(uid, ['lastonline', 'status'], function(err, userData) {
return callback(null, 'offline');
}
user.getUserField(uid, 'status', function(err, status) {
if (err) { if (err) {
return callback(err); return callback(err);
} }
status = status || 'online'; var status = user.getStatus(userData);
callback(null, status); callback(null, status);
}); });
}; };

@ -272,13 +272,13 @@ module.exports = function(Topics) {
var tid = postData.tid; var tid = postData.tid;
var uid = postData.uid; var uid = postData.uid;
async.waterfall([ async.waterfall([
function(next) { function (next) {
Topics.markAsUnreadForAll(tid, next); Topics.markAsUnreadForAll(tid, next);
}, },
function(next) { function (next) {
Topics.markAsRead([tid], uid, next); Topics.markAsRead([tid], uid, next);
}, },
function(next) { function (markedRead, next) {
async.parallel({ async.parallel({
userInfo: function(next) { userInfo: function(next) {
posts.getUserInfoForPosts([postData.uid], uid, next); posts.getUserInfoForPosts([postData.uid], uid, next);
@ -294,7 +294,7 @@ module.exports = function(Topics) {
} }
}, next); }, next);
}, },
function(results, next) { function (results, next) {
postData.user = results.userInfo[0]; postData.user = results.userInfo[0];
postData.topic = results.topicInfo; postData.topic = results.topicInfo;

@ -161,18 +161,14 @@ module.exports = function(Topics) {
return callback(); return callback();
} }
async.waterfall([
function (next) {
async.parallel({ async.parallel({
topicScores: function(next) { topicScores: async.apply(db.sortedSetScores, 'topics:recent', tids),
db.sortedSetScores('topics:recent', tids, next); userScores: async.apply(db.sortedSetScores, 'uid:' + uid + ':tids_read', tids)
}, next);
}, },
userScores: function(next) { function (results, next) {
db.sortedSetScores('uid:' + uid + ':tids_read', tids, next);
}
}, function(err, results) {
if (err) {
return callback(err);
}
tids = tids.filter(function(tid, index) { tids = tids.filter(function(tid, index) {
return results.topicScores[index] && (!results.userScores[index] || results.userScores[index] < results.topicScores[index]); return results.topicScores[index] && (!results.userScores[index] || results.userScores[index] < results.topicScores[index]);
}); });
@ -187,26 +183,23 @@ module.exports = function(Topics) {
}); });
async.parallel({ async.parallel({
markRead: function(next) { markRead: async.apply(db.sortedSetAdd, 'uid:' + uid + ':tids_read', scores, tids),
db.sortedSetAdd('uid:' + uid + ':tids_read', scores, tids, next); topicData: async.apply( Topics.getTopicsFields, tids, ['cid'])
}, next);
}, },
topicData: function(next) { function (results, next) {
Topics.getTopicsFields(tids, ['cid'], next);
}
}, function(err, results) {
if (err) {
return callback(err);
}
var cids = results.topicData.map(function(topic) { var cids = results.topicData.map(function(topic) {
return topic && topic.cid; return topic && topic.cid;
}).filter(function(topic, index, array) { }).filter(function(topic, index, array) {
return topic && array.indexOf(topic) === index; return topic && array.indexOf(topic) === index;
}); });
categories.markAsRead(cids, uid, callback); categories.markAsRead(cids, uid, next);
}); },
}); function (next) {
next(null, true);
}
], callback);
}; };
Topics.markTopicNotificationsRead = function(tid, uid) { Topics.markTopicNotificationsRead = function(tid, uid) {

@ -1,12 +1,9 @@
'use strict'; 'use strict';
var async = require('async'), var async = require('async'),
nconf = require('nconf'),
gravatar = require('gravatar'),
plugins = require('./plugins'), plugins = require('./plugins'),
db = require('./database'), db = require('./database'),
meta = require('./meta'),
topics = require('./topics'), topics = require('./topics'),
privileges = require('./privileges'), privileges = require('./privileges'),
utils = require('../public/src/utils'); utils = require('../public/src/utils');
@ -91,7 +88,7 @@ var async = require('async'),
}; };
User.getUsers = function(uids, uid, callback) { User.getUsers = function(uids, uid, callback) {
var fields = ['uid', 'username', 'userslug', 'picture', 'status', 'banned', 'joindate', 'postcount', 'reputation', 'email:confirmed']; var fields = ['uid', 'username', 'userslug', 'picture', 'status', 'banned', 'joindate', 'postcount', 'reputation', 'email:confirmed', 'lastonline'];
plugins.fireHook('filter:users.addFields', {fields: fields}, function(err, data) { plugins.fireHook('filter:users.addFields', {fields: fields}, function(err, data) {
if (err) { if (err) {
return callback(err); return callback(err);
@ -105,9 +102,6 @@ var async = require('async'),
}, },
isAdmin: function(next) { isAdmin: function(next) {
User.isAdministrator(uids, next); User.isAdministrator(uids, next);
},
isOnline: function(next) {
require('./socket.io').isUsersOnline(uids, next);
} }
}, function(err, results) { }, function(err, results) {
if (err) { if (err) {
@ -118,7 +112,7 @@ var async = require('async'),
if (!user) { if (!user) {
return; return;
} }
user.status = User.getStatus(user.status, results.isOnline[index]); user.status = User.getStatus(user);
user.joindateISO = utils.toISOString(user.joindate); user.joindateISO = utils.toISOString(user.joindate);
user.administrator = results.isAdmin[index]; user.administrator = results.isAdmin[index];
user.banned = parseInt(user.banned, 10) === 1; user.banned = parseInt(user.banned, 10) === 1;
@ -135,8 +129,19 @@ var async = require('async'),
}); });
}; };
User.getStatus = function(status, isOnline) { User.getStatus = function(userData) {
return isOnline ? (status || 'online') : 'offline'; var isOnline = Date.now() - parseInt(userData.lastonline, 10) < 300000;
return isOnline ? (userData.status || 'online') : 'offline';
};
User.isOnline = function(uid, callback) {
db.sortedSetScore('users:online', uid, function(err, lastonline) {
if (err) {
return callback(err);
}
var isOnline = Date.now() - parseInt(lastonline, 10) < 300000;
callback(null, isOnline);
});
}; };
User.exists = function(uid, callback) { User.exists = function(uid, callback) {

@ -98,29 +98,16 @@ module.exports = function(User) {
function filterAndSortUids(uids, data, callback) { function filterAndSortUids(uids, data, callback) {
var sortBy = data.sortBy || 'joindate'; var sortBy = data.sortBy || 'joindate';
var fields = ['uid', 'status', sortBy]; var fields = ['uid', 'status', 'lastonline', sortBy];
async.parallel({ User.getUsersFields(uids, fields, function(err, userData) {
userData: function(next) {
User.getUsersFields(uids, fields, next);
},
isOnline: function(next) {
if (data.onlineOnly) {
require('../socket.io').isUsersOnline(uids, next);
} else {
next();
}
}
}, function(err, results) {
if (err) { if (err) {
return callback(err); return callback(err);
} }
var userData = results.userData;
if (data.onlineOnly) { if (data.onlineOnly) {
userData = userData.filter(function(user, index) { userData = userData.filter(function(user) {
return user && user.status !== 'offline' && results.isOnline[index]; return user && user.status !== 'offline' && (Date.now() - parseInt(user.lastonline, 10) < 300000);
}); });
} }

Loading…
Cancel
Save