-
Notifications
You must be signed in to change notification settings - Fork 3
/
index.js
142 lines (118 loc) · 4.14 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
'use strict';
const { LRUCache } = require('lru-cache');
const winston = require.main.require('winston');
const cache = new LRUCache({
max: 500,
ttl: 5000,
});
const groups = require.main.require('./src/groups');
const meta = require.main.require('./src/meta');
const user = require.main.require('./src/user');
const privileges = require.main.require('./src/privileges');
const socketPlugins = require.main.require('./src/socket.io/plugins');
const routeHelpers = require.main.require('./src/routes/helpers');
const socketIO = require.main.require('./src/socket.io');
const widgets = require.main.require('./src/widgets');
const plugin = module.exports;
plugin.init = async function (hookData) {
routeHelpers.setupAdminPageRoute(hookData.router, '/admin/plugins/browsing-users', renderAdmin);
};
plugin.addAdminNavigation = async function (menu) {
menu.plugins.push({
route: '/plugins/browsing-users',
icon: 'fa-group',
name: 'Browsing Users',
});
return menu;
};
plugin.filterTopicBuild = async function (hookData) {
// browsing users are rendered via websockets,
// this is just here so theme can check for plugin and import partial, see persona topic.tpl
hookData.templateData.browsingUsers = true;
return hookData;
};
async function renderAdmin(req, res) {
const groupsData = await groups.getNonPrivilegeGroups('groups:createtime', 0, -1);
groupsData.sort((a, b) => b.system - a.system);
res.render('admin/plugins/browsing-users', {
groups: groupsData,
title: 'Browsing Users',
});
}
socketPlugins.browsingUsers = {};
socketPlugins.browsingUsers.getBrowsingUsers = async function (socket, data) {
const canRead = await privileges.topics.can('read', data.tid, socket.uid);
if (!canRead) {
throw new Error('[[error:no-privileges]]');
}
const settings = await getSettings();
const isVisible = await widgets.checkVisibility(settings, socket.uid);
if (!isVisible) {
return [];
}
return await getUsersInTopic(socket, data, settings);
};
async function getSettings() {
const _settings = await meta.settings.get('browsing-users');
const settings = {
groups: [],
groupsHideFrom: [],
};
try {
settings.groups = _settings.groups ? JSON.parse(_settings.groups) : [];
settings.groupsHideFrom = _settings.groupsHideFrom ? JSON.parse(_settings.groupsHideFrom) : [];
} catch (e) {
winston.warn('[browsing-users/getSettings] Groups settings are invalid.');
}
return { ..._settings, ...settings };
}
function isUserInCache(browsingUsers, uid) {
if (parseInt(uid, 10) <= 0) {
return true;
}
return browsingUsers.find(user => parseInt(user.uid, 10) === parseInt(uid, 10));
}
async function getUsersInTopic(socket, data, settings) {
const uid = socket.uid;
const { tid, composing } = data;
const browsingUsers = cache.get('browsing:tid:' + tid) || [];
const composingUids = [];
socket.data.composing = composing ? Date.now() + 5000 : 0;
if (browsingUsers.length && isUserInCache(browsingUsers, uid)) {
return browsingUsers;
}
try {
const sockets = await socketIO.server.in(`topic_${tid}`).fetchSockets();
const uids = {};
for (const s of sockets) {
if (s.data.uid > 0) {
uids[s.data.uid] = 1;
if (s.data.composing && s.data.composing > Date.now() && !composingUids.includes(s.data.uid)) {
composingUids.push(s.data.uid);
}
}
}
settings.numUsers = Math.min(100, settings.numUsers || 10);
let userIds = Object.keys(uids).map(uid => parseInt(uid, 10));
// bump composing users to the front of the queue
const intersection = userIds.filter(uid => composingUids.includes(uid));
const remainder = userIds.filter(uid => !composingUids.includes(uid));
userIds = intersection.concat(remainder).slice(0, 100);
let userData = await user.getUsersFields(userIds, [
'uid', 'username', 'userslug', 'picture', 'status',
]);
userData = userData.filter(
u => u && u.uid > 0 && u.status !== 'offline'
).slice(0, settings.numUsers);
userData.forEach(function (user) {
user.composing = composingUids.includes(user.uid);
});
cache.set('browsing:tid:' + tid, userData);
return userData;
} catch (err) {
if (err.message === 'timeout reached while waiting for clients response') {
return null;
}
throw err;
}
}