[IMPROVE] Change user presence events to Meteor Streams (#14488)

pull/14572/head
Diego Sampaio 6 years ago committed by Guilherme Gazzo
parent fbb4ac4f1f
commit 40cab8f30f
  1. 2
      .meteor/versions
  2. 3
      app/api/server/api.js
  3. 48
      app/api/server/helpers/getUserInfo.js
  4. 3
      app/api/server/v1/misc.js
  5. 53
      app/api/server/v1/users.js
  6. 17
      app/lib/server/functions/getFullUserData.js
  7. 8
      app/lib/server/functions/setUsername.js
  8. 1
      app/models/client/index.js
  9. 10
      app/models/client/models/FullUser.js
  10. 21
      app/models/client/models/Users.js
  11. 23
      app/models/server/models/Users.js
  12. 6
      app/ui-admin/client/users/adminUsers.js
  13. 6
      app/ui-flextab/client/tabs/userInfo.js
  14. 12
      app/ui-master/client/main.js
  15. 28
      app/utils/server/functions/getDefaultUserFields.js
  16. 2
      client/notifications/UsersNameChanged.js
  17. 2
      client/routes/router.js
  18. 1
      client/startup/startup.js
  19. 2
      client/startup/usersObserve.js
  20. 1
      imports/startup/client/index.js
  21. 107
      imports/startup/client/listenActiveUsers.js
  22. 1
      imports/startup/server/index.js
  23. 26
      imports/users-presence/server/activeUsers.js
  24. 1
      imports/users-presence/server/index.js
  25. 1
      server/methods/saveUserPreferences.js
  26. 21
      server/publications/fullUserData.js
  27. 39
      server/publications/userData.js
  28. 25
      server/stream/streamBroadcast.js
  29. 43
      tests/end-to-end/api/00-miscellaneous.js
  30. 69
      tests/end-to-end/api/01-users.js

@ -70,7 +70,7 @@ konecty:change-case@2.3.0
konecty:delayed-task@1.0.0
konecty:mongo-counter@0.0.5_3
konecty:multiple-instances-status@1.1.0
konecty:user-presence@2.3.0
konecty:user-presence@2.4.0
launch-screen@1.1.1
less@2.8.0
littledata:synced-cron@1.5.1

@ -10,6 +10,7 @@ import { Logger } from '../../logger';
import { settings } from '../../settings';
import { metrics } from '../../metrics';
import { hasPermission, hasAllPermission } from '../../authorization';
import { getDefaultUserFields } from '../../utils/server/functions/getDefaultUserFields';
const logger = new Logger('API', {});
@ -382,6 +383,8 @@ class APIClass extends Restivus {
this.user = Meteor.users.findOne({
_id: auth.id,
}, {
fields: getDefaultUserFields(),
});
this.userId = this.user._id;

@ -1,41 +1,8 @@
import { settings } from '../../../settings';
import { getUserPreference, getURL } from '../../../utils';
import { settings } from '../../../settings/server';
import { getUserPreference, getURL } from '../../../utils/server';
import { API } from '../api';
const getInfoFromUserObject = (user) => {
const {
_id,
name,
emails,
status,
statusConnection,
username,
utcOffset,
active,
language,
roles,
settings,
customFields,
} = user;
return {
_id,
name,
emails,
status,
statusConnection,
username,
utcOffset,
active,
language,
roles,
settings,
customFields,
};
};
API.helperMethods.set('getUserInfo', function _getUserInfo(user) {
const me = getInfoFromUserObject(user);
API.helperMethods.set('getUserInfo', function _getUserInfo(me) {
const isVerifiedEmail = () => {
if (me && me.emails && Array.isArray(me.emails)) {
return me.emails.find((email) => email.verified);
@ -48,7 +15,7 @@ API.helperMethods.set('getUserInfo', function _getUserInfo(user) {
return allDefaultUserSettings.reduce((accumulator, setting) => {
const settingWithoutPrefix = setting.key.replace(defaultUserSettingPrefix, ' ').trim();
accumulator[settingWithoutPrefix] = getUserPreference(user, settingWithoutPrefix);
accumulator[settingWithoutPrefix] = getUserPreference(me, settingWithoutPrefix);
return accumulator;
}, {});
};
@ -57,8 +24,13 @@ API.helperMethods.set('getUserInfo', function _getUserInfo(user) {
me.avatarUrl = getURL(`/avatar/${ me.username }`, { cdn: false, full: true });
const userPreferences = (me.settings && me.settings.preferences) || {};
me.settings = {
preferences: getUserPreferences(),
preferences: {
...getUserPreferences(),
...userPreferences,
},
};
return me;

@ -8,6 +8,7 @@ import { Info } from '../../../utils';
import { Users } from '../../../models';
import { settings } from '../../../settings';
import { API } from '../api';
import { getDefaultUserFields } from '../../../utils/server/functions/getDefaultUserFields';
// DEPRECATED
@ -42,7 +43,7 @@ API.v1.addRoute('info', { authRequired: false }, {
API.v1.addRoute('me', { authRequired: true }, {
get() {
return API.v1.success(this.getUserInfo(Users.findOneById(this.userId)));
return API.v1.success(this.getUserInfo(Users.findOneById(this.userId, { fields: getDefaultUserFields() })));
},
});

@ -4,7 +4,7 @@ import { TAPi18n } from 'meteor/tap:i18n';
import _ from 'underscore';
import Busboy from 'busboy';
import { Users, Subscriptions } from '../../../models';
import { Users, Subscriptions } from '../../../models/server';
import { hasPermission } from '../../../authorization';
import { settings } from '../../../settings';
import { getURL } from '../../../utils';
@ -16,6 +16,7 @@ import {
setUserAvatar,
saveCustomFields,
} from '../../../lib';
import { getFullUserData } from '../../../lib/server/functions/getFullUserData';
import { API } from '../api';
API.v1.addRoute('users.create', { authRequired: true }, {
@ -149,17 +150,16 @@ API.v1.addRoute('users.info', { authRequired: true }, {
get() {
const { username } = this.getUserFromParams();
const { fields } = this.parseJsonQuery();
let user = {};
let result;
Meteor.runAsUser(this.userId, () => {
result = Meteor.call('getFullUserData', { username, limit: 1 });
});
if (!result || result.length !== 1) {
return API.v1.failure(`Failed to get the user data for the userId of "${ username }".`);
const result = getFullUserData({
userId: this.userId,
filter: username,
limit: 1,
});
if (!result || result.count() !== 1) {
return API.v1.failure(`Failed to get the user data for the userId of "${ this.userId }".`);
}
user = result[0];
const [user] = result.fetch();
const myself = user._id === this.userId;
if (fields.userRooms === 1 && (myself || hasPermission(this.userId, 'view-other-user-channels'))) {
user.rooms = Subscriptions.findByUserId(user._id, {
@ -450,6 +450,7 @@ API.v1.addRoute('users.setPreferences', { authRequired: true }, {
sidebarViewMode: Match.Optional(String),
sidebarHideAvatar: Match.Optional(Boolean),
sidebarGroupByType: Match.Optional(Boolean),
sidebarShowDiscussion: Match.Optional(Boolean),
muteFocusedConversations: Match.Optional(Boolean),
}),
});
@ -569,3 +570,35 @@ API.v1.addRoute('users.removePersonalAccessToken', { authRequired: true }, {
return API.v1.success();
},
});
API.v1.addRoute('users.presence', { authRequired: true }, {
get() {
const { from } = this.queryParams;
const options = {
fields: {
username: 1,
name: 1,
status: 1,
utcOffset: 1,
},
};
if (from) {
const ts = new Date(from);
const diff = (Date.now() - ts) / 1000 / 60;
if (diff < 10) {
return API.v1.success({
users: Users.findNotIdUpdatedFrom(this.userId, ts, options).fetch(),
full: false,
});
}
}
return API.v1.success({
users: Users.findUsersNotOffline(options).fetch(),
full: true,
});
},
});

@ -2,7 +2,7 @@ import s from 'underscore.string';
import { Logger } from '../../../logger';
import { settings } from '../../../settings';
import { Users } from '../../../models';
import { Users } from '../../../models/server';
import { hasPermission } from '../../../authorization';
const logger = new Logger('getFullUserData');
@ -56,7 +56,8 @@ settings.get('Accounts_CustomFields', (key, value) => {
export const getFullUserData = function({ userId, filter, limit: l }) {
const username = s.trim(filter);
const userToRetrieveFullUserData = Users.findOneByUsername(username);
const userToRetrieveFullUserData = Users.findOneByUsername(username, { fields: { username: 1 } });
const isMyOwnInfo = userToRetrieveFullUserData && userToRetrieveFullUserData._id === userId;
const viewFullOtherUserInfo = hasPermission(userId, 'view-full-other-user-info');
const limit = !viewFullOtherUserInfo ? 1 : l;
@ -65,9 +66,13 @@ export const getFullUserData = function({ userId, filter, limit: l }) {
return undefined;
}
const _customFields = isMyOwnInfo || viewFullOtherUserInfo ? customFields : publicCustomFields;
const _customFields = isMyOwnInfo || viewFullOtherUserInfo
? customFields
: publicCustomFields;
const fields = viewFullOtherUserInfo ? { ...defaultFields, ...fullFields, ..._customFields } : { ...defaultFields, ..._customFields };
const fields = isMyOwnInfo || viewFullOtherUserInfo
? { ...defaultFields, ...fullFields, ..._customFields }
: { ...defaultFields, ..._customFields };
const options = {
fields,
@ -78,9 +83,11 @@ export const getFullUserData = function({ userId, filter, limit: l }) {
if (!username) {
return Users.find({}, options);
}
if (limit === 1) {
return Users.findByUsername(username, options);
return Users.findByUsername(userToRetrieveFullUserData.username, options);
}
const usernameReg = new RegExp(s.escapeRegExp(username), 'i');
return Users.findByUsernameNameOrEmailAddress(usernameReg, options);
};

@ -7,6 +7,7 @@ import { settings } from '../../../settings';
import { Users, Messages, Subscriptions, Rooms, LivechatDepartmentAgents } from '../../../models';
import { hasPermission } from '../../../authorization';
import { RateLimiter } from '../lib';
import { Notifications } from '../../../notifications/server';
import { checkUsernameAvailability, setUserAvatar, getAvatarSuggestionForUser } from '.';
@ -85,6 +86,13 @@ export const _setUsername = function(userId, u) {
fileStore.model.updateFileNameById(file._id, username);
}
}
Notifications.notifyLogged('Users:NameChanged', {
_id: user._id,
name: user.name,
username: user.username,
});
return user;
};

@ -28,7 +28,6 @@ const Subscriptions = _.extend({}, subscriptions, ChatSubscription);
const Messages = _.extend({}, ChatMessage);
const Rooms = _.extend({}, ChatRoom);
export {
Base,
Avatars,

@ -0,0 +1,10 @@
import { Base } from './_Base';
class FullUser extends Base {
constructor() {
super();
this._initModel('full_user');
}
}
export default new FullUser();

@ -1,6 +1,7 @@
const Users = {};
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
Object.assign(Users, {
export const Users = {
isUserInRole(userId, roleName) {
const query = {
_id: userId,
@ -19,6 +20,18 @@ Object.assign(Users, {
return this.find(query, options);
},
});
};
// overwrite Meteor.users collection so records on it don't get erased whenever the client reconnects to websocket
Meteor.users = new Mongo.Collection(null);
Meteor.user = () => Meteor.users.findOne({ _id: Meteor.userId() });
export { Users };
// logged user data will come to this collection
const OwnUser = new Mongo.Collection('own_user');
// register an observer to logged user's collection and populate "original" Meteor.users with it
OwnUser.find().observe({
added: (record) => Meteor.users.upsert({ _id: record._id }, record),
changed: (record) => Meteor.users.update({ _id: record._id }, record),
removed: (_id) => Meteor.users.remove({ _id }),
});

@ -432,6 +432,18 @@ export class Users extends Base {
return this.find(query, options);
}
findNotIdUpdatedFrom(uid, from, options) {
const query = {
_id: { $ne: uid },
username: {
$exists: 1,
},
_updatedAt: { $gte: from },
};
return this.find(query, options);
}
findByRoomId(rid, options) {
const data = Subscriptions.findByRoomId(rid).fetch().map((item) => item.u._id);
const query = {
@ -1023,6 +1035,17 @@ export class Users extends Base {
return this.update({ _id }, update);
}
updateDefaultStatus(_id, statusDefault) {
return this.update({
_id,
statusDefault: { $ne: statusDefault },
}, {
$set: {
statusDefault,
},
});
}
// INSERT
create(data) {
const user = {

@ -1,4 +1,3 @@
import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { Tracker } from 'meteor/tracker';
import { FlowRouter } from 'meteor/kadira:flow-router';
@ -7,6 +6,7 @@ import _ from 'underscore';
import s from 'underscore.string';
import { SideNav, TabBar, RocketChatTabBar } from '../../../ui-utils';
import FullUser from '../../../models/client/models/FullUser';
Template.adminUsers.helpers({
searchText() {
@ -121,7 +121,7 @@ Template.adminUsers.onCreated(function() {
};
const limit = instance.limit && instance.limit.get();
return Meteor.users.find(query, { limit, sort: { username: 1, name: 1 } }).fetch();
return FullUser.find(query, { limit, sort: { username: 1, name: 1 } }).fetch();
};
});
@ -148,7 +148,7 @@ Template.adminUsers.events({
}, DEBOUNCE_TIME_FOR_SEARCH_USERS_IN_MS),
'click .user-info'(e, instance) {
e.preventDefault();
instance.tabBarData.set(Meteor.users.findOne(this._id));
instance.tabBarData.set(FullUser.findOne(this._id));
instance.tabBar.open('admin-user-info');
},
'click .info-tabs button'(e) {

@ -1,4 +1,3 @@
import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
@ -7,12 +6,13 @@ import _ from 'underscore';
import s from 'underscore.string';
import moment from 'moment';
import { getActions } from './userActions';
import { DateFormat } from '../../../lib';
import { popover } from '../../../ui-utils';
import { templateVarHandler } from '../../../utils';
import { RoomRoles, UserRoles, Roles } from '../../../models';
import { settings } from '../../../settings';
import FullUser from '../../../models/client/models/FullUser';
import { getActions } from './userActions';
import './userInfo.html';
const shownActionsCount = 2;
@ -292,7 +292,7 @@ Template.userInfo.onCreated(function() {
} else if (data && data._id != null) {
filter = { _id: data._id };
}
const user = Meteor.users.findOne(filter);
const user = FullUser.findOne(filter);
return this.user.set(user);
});

@ -7,8 +7,7 @@ import { FlowRouter } from 'meteor/kadira:flow-router';
import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
import { getConfig } from '../../ui-utils/client/config';
import { t, getUserPreference } from '../../utils';
import { t, getUserPreference } from '../../utils/client';
import { chatMessages } from '../../ui';
import { mainReady, Layout, iframeLogin, modal, popover, menu, fireGlobalEvent, RoomManager } from '../../ui-utils';
import { toolbarSearch } from '../../ui-sidenav';
@ -147,8 +146,6 @@ Template.main.onCreated(function() {
tooltip.init();
});
const skipActiveUsersToBeReady = [getConfig('experimental'), getConfig('skipActiveUsersToBeReady')].includes('true');
Template.main.helpers({
removeSidenav() {
return Layout.isEmbedded() && !/^\/admin/.test(FlowRouter.current().route.path);
@ -173,12 +170,7 @@ Template.main.helpers({
return iframeEnabled && iframeLogin.reactiveIframeUrl.get();
},
subsReady() {
const subscriptions = ['userData'];
if (!skipActiveUsersToBeReady) {
subscriptions.push('activeUsers');
}
const routerReady = FlowRouter.subsReady.apply(FlowRouter, subscriptions);
const routerReady = FlowRouter.subsReady('userData');
const subscriptionsReady = CachedChatSubscription.ready.get();
const settingsReady = settings.cachedCollection.ready.get();

@ -0,0 +1,28 @@
export const getDefaultUserFields = () => ({
name: 1,
username: 1,
emails: 1,
status: 1,
statusDefault: 1,
statusConnection: 1,
avatarOrigin: 1,
utcOffset: 1,
language: 1,
settings: 1,
enableAutoAway: 1,
idleTimeLimit: 1,
roles: 1,
active: 1,
defaultRoom: 1,
customFields: 1,
requirePasswordChange: 1,
requirePasswordChangeReason: 1,
'services.github': 1,
'services.gitlab': 1,
'services.tokenpass': 1,
'services.blockstack': 1,
'services.password.bcrypt': 1,
'services.totp.enabled': 1,
statusLivechat: 1,
banners: 1,
});

@ -9,6 +9,7 @@ Meteor.startup(function() {
'u._id': _id,
}, {
$set: {
'u.username': username,
'u.name': name,
},
}, {
@ -21,6 +22,7 @@ Meteor.startup(function() {
},
}, {
$set: {
'mentions.$.username': username,
'mentions.$.name': name,
},
}, {

@ -19,12 +19,10 @@ FlowRouter.subscriptions = function() {
Tracker.autorun(() => {
if (Meteor.userId()) {
this.register('userData', Meteor.subscribe('userData'));
this.register('activeUsers', Meteor.subscribe('activeUsers'));
}
});
};
FlowRouter.route('/', {
name: 'index',
action() {

@ -34,7 +34,6 @@ Meteor.startup(function() {
return;
}
Meteor.subscribe('userData');
Meteor.subscribe('activeUsers');
computation.stop();
});

@ -4,7 +4,7 @@ import { Session } from 'meteor/session';
import { RoomManager } from '../../app/ui-utils';
Meteor.startup(function() {
Meteor.users.find({}, { fields: { name: 1, username: 1, pictures: 1, status: 1, emails: 1, phone: 1, services: 1, utcOffset: 1 } }).observe({
Meteor.users.find({}, { fields: { name: 1, username: 1, status: 1, utcOffset: 1 } }).observe({
added(user) {
Session.set(`user_${ user.username }_status`, user.status);
RoomManager.updateUserStatus(user, user.status, user.utcOffset);

@ -1,2 +1,3 @@
import '../../message-read-receipt/client';
import '../../personal-access-tokens/client';
import './listenActiveUsers';

@ -0,0 +1,107 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { debounce } from 'underscore';
import { Notifications } from '../../../app/notifications/client';
import { APIClient } from '../../../app/utils/client';
// mirror of object in /imports/users-presence/server/activeUsers.js - keep updated
const STATUS_MAP = [
'offline',
'online',
'away',
'busy',
];
const saveUser = (user, force = false) => {
// do not update my own user, my user's status will come from a subscription
if (user._id === Meteor.userId()) {
return;
}
if (force) {
return Meteor.users.upsert({ _id: user._id }, {
$set: {
username: user.username,
// name: user.name,
// utcOffset: user.utcOffset,
status: user.status,
},
});
}
const found = Meteor.users.findOne(user._id, { fields: { _id: 1 } });
if (found) {
return;
}
Meteor.users.insert(user);
};
let lastStatusChange = null;
let retry = 0;
const getUsersPresence = debounce(async (isConnected) => {
try {
const params = {};
if (lastStatusChange) {
params.from = lastStatusChange.toISOString();
}
const {
users,
full,
} = await APIClient.v1.get('users.presence', params);
// if is reconnecting, set everyone else to offline
if (full && isConnected) {
Meteor.users.update({
_id: { $ne: Meteor.userId() },
}, {
$set: {
status: 'offline',
},
}, { multi: true });
}
users.forEach((user) => saveUser(user, full));
lastStatusChange = new Date();
} catch (e) {
setTimeout(() => getUsersPresence(isConnected), retry++ * 2000);
}
}, 1000);
let wasConnected = false;
Tracker.autorun(() => {
if (!Meteor.userId() || !Meteor.status().connected) {
return;
}
lastStatusChange = null;
getUsersPresence(wasConnected);
wasConnected = true;
});
Meteor.startup(function() {
Notifications.onLogged('user-status', ([_id, username, status]) => {
// only set after first request completed
if (lastStatusChange) {
lastStatusChange = new Date();
}
saveUser({ _id, username, status: STATUS_MAP[status] }, true);
});
Notifications.onLogged('Users:NameChanged', ({ _id, username }) => {
if (!username) {
return;
}
Meteor.users.upsert({ _id }, {
$set: {
username,
},
});
});
});

@ -1,2 +1,3 @@
import '../../message-read-receipt/server';
import '../../personal-access-tokens/server';
import '../../users-presence/server';

@ -0,0 +1,26 @@
import { UserPresenceEvents } from 'meteor/konecty:user-presence';
import { Notifications } from '../../../app/notifications/server';
// mirror of object in /imports/startup/client/listenActiveUsers.js - keep updated
const STATUS_MAP = {
offline: 0,
online: 1,
away: 2,
busy: 3,
};
UserPresenceEvents.on('setUserStatus', (user, status/* , statusConnection*/) => {
const {
_id,
username,
} = user;
// since this callback can be called by only one instance in the cluster
// we need to broadcast the change to all instances
Notifications.notifyLogged('user-status', [
_id,
username,
STATUS_MAP[status],
]);
});

@ -0,0 +1 @@
import './activeUsers';

@ -37,6 +37,7 @@ Meteor.methods({
sidebarViewMode: Match.Optional(String),
sidebarHideAvatar: Match.Optional(Boolean),
sidebarGroupByType: Match.Optional(Boolean),
sidebarShowDiscussion: Match.Optional(Boolean),
muteFocusedConversations: Match.Optional(Boolean),
};
check(settings, Match.ObjectIncluding(keys));

@ -7,15 +7,24 @@ Meteor.publish('fullUserData', function(filter, limit) {
return this.ready();
}
const result = getFullUserData({
const handle = getFullUserData({
userId: this.userId,
filter,
limit,
});
}).observeChanges({
added: (id, fields) => {
this.added('rocketchat_full_user', id, fields);
},
if (!result) {
return this.ready();
}
changed: (id, fields) => {
this.changed('rocketchat_full_user', id, fields);
},
removed: (id) => {
this.removed('rocketchat_full_user', id);
},
});
return result;
this.ready();
this.onStop(() => handle.stop());
});

@ -1,39 +1,22 @@
import { Meteor } from 'meteor/meteor';
import { Users } from '../../app/models';
import { getDefaultUserFields } from '../../app/utils/server/functions/getDefaultUserFields';
Meteor.publish('userData', function() {
if (!this.userId) {
return this.ready();
}
return Users.find(this.userId, {
fields: {
name: 1,
username: 1,
status: 1,
statusDefault: 1,
statusConnection: 1,
avatarOrigin: 1,
utcOffset: 1,
language: 1,
settings: 1,
enableAutoAway: 1,
idleTimeLimit: 1,
roles: 1,
active: 1,
defaultRoom: 1,
customFields: 1,
'services.github': 1,
'services.gitlab': 1,
'services.tokenpass': 1,
'services.blockstack': 1,
requirePasswordChange: 1,
requirePasswordChangeReason: 1,
'services.password.bcrypt': 1,
'services.totp.enabled': 1,
statusLivechat: 1,
banners: 1,
},
const handle = Users.find({ _id: this.userId }, {
fields: getDefaultUserFields(),
}).observeChanges({
added: (_id, record) => this.added('own_user', _id, record),
changed: (_id, record) => this.changed('own_user', _id, record),
removed: (_id, record) => this.removed('own_user', _id, record),
});
this.ready();
this.onStop(() => handle.stop());
});

@ -1,4 +1,5 @@
import { Meteor } from 'meteor/meteor';
import { UserPresence } from 'meteor/konecty:user-presence';
import { InstanceStatus } from 'meteor/konecty:multiple-instances-status';
import { check } from 'meteor/check';
import _ from 'underscore';
@ -9,10 +10,14 @@ import { Logger, LoggerManager } from '../../app/logger';
import { hasPermission } from '../../app/authorization';
import { settings } from '../../app/settings';
import { isDocker, getURL } from '../../app/utils';
import { Users } from '../../app/models/server/models/Users';
process.env.PORT = String(process.env.PORT).trim();
process.env.INSTANCE_IP = String(process.env.INSTANCE_IP).trim();
const startMonitor = typeof process.env.DISABLE_PRESENCE_MONITOR === 'undefined'
|| !['true', 'yes'].includes(String(process.env.DISABLE_PRESENCE_MONITOR).toLowerCase());
const connections = {};
this.connections = connections;
@ -51,7 +56,12 @@ function authorizeConnection(instance) {
return _authorizeConnection(instance);
}
const originalSetDefaultStatus = UserPresence.setDefaultStatus;
function startMatrixBroadcast() {
if (!startMonitor) {
UserPresence.setDefaultStatus = originalSetDefaultStatus;
}
const query = {
'extraInformation.port': {
$exists: true,
@ -167,6 +177,12 @@ function startStreamCastBroadcast(value) {
logger.connection.info('connecting in', instance, value);
if (!startMonitor) {
UserPresence.setDefaultStatus = (id, status) => {
Users.updateDefaultStatus(id, status);
};
}
const connection = DDP.connect(value, {
_dontPrintErrors: LoggerManager.logLevel < 2,
});
@ -193,11 +209,16 @@ function startStreamCastBroadcast(value) {
return 'not-authorized';
}
if (!Meteor.StreamerCentral.instances[streamName]) {
const instance = Meteor.StreamerCentral.instances[streamName];
if (!instance) {
return 'stream-not-exists';
}
return Meteor.StreamerCentral.instances[streamName]._emit(eventName, args);
if (instance.serverOnly) {
const scope = {};
return instance.emitWithScope(eventName, scope, args);
}
return instance.emitWithoutBroadcast(eventName, args);
});
return connection.subscribe('stream');

@ -106,12 +106,43 @@ describe('miscellaneous', function() {
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
const allUserPreferencesKeys = ['enableAutoAway', 'idleTimeLimit', 'desktopNotificationDuration', 'audioNotifications',
'desktopNotifications', 'mobileNotifications', 'unreadAlert', 'useEmojis', 'convertAsciiEmoji', 'autoImageLoad',
'saveMobileBandwidth', 'collapseMediaByDefault', 'hideUsernames', 'hideRoles', 'hideFlexTab', 'hideAvatars',
'sidebarViewMode', 'sidebarHideAvatar', 'sidebarShowUnread', 'sidebarShowDiscussion', 'sidebarShowFavorites', 'sidebarGroupByType',
'sendOnEnter', 'messageViewMode', 'emailNotificationMode', 'roomCounterSidebar', 'newRoomNotification', 'newMessageNotification',
'muteFocusedConversations', 'notificationsSoundVolume'];
const allUserPreferencesKeys = [
'audioNotifications',
// 'language',
'newRoomNotification',
'newMessageNotification',
// 'clockMode',
'useEmojis',
'convertAsciiEmoji',
'saveMobileBandwidth',
'collapseMediaByDefault',
'autoImageLoad',
'emailNotificationMode',
'unreadAlert',
'notificationsSoundVolume',
'desktopNotifications',
'mobileNotifications',
'enableAutoAway',
// 'highlights',
'desktopNotificationDuration',
'messageViewMode',
'hideUsernames',
'hideRoles',
'hideAvatars',
'hideFlexTab',
'sendOnEnter',
'roomCounterSidebar',
'idleTimeLimit',
'sidebarShowFavorites',
'sidebarShowUnread',
// 'sidebarSortby',
'sidebarViewMode',
'sidebarHideAvatar',
'sidebarGroupByType',
'muteFocusedConversations',
'sidebarShowDiscussion',
];
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('_id', credentials['X-User-Id']);
expect(res.body).to.have.property('username', login.user);

@ -294,6 +294,75 @@ describe('[Users]', function() {
});
});
describe('[/users.presence]', () => {
describe('Not logged in:', () => {
it('should return 401 unauthorized', (done) => {
request.get(api('users.presence'))
.expect('Content-Type', 'application/json')
.expect(401)
.expect((res) => {
expect(res.body).to.have.property('message');
})
.end(done);
});
});
describe('Logged in:', () => {
it('should return online users full list', (done) => {
request.get(api('users.presence'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('full', true);
expect(res.body).to.have.property('users').to.have.property('0').to.deep.have.all.keys(
'_id',
'username',
'name',
'status',
'utcOffset',
);
})
.end(done);
});
it('should return no online users updated after now', (done) => {
request.get(api(`users.presence?from=${ new Date().toISOString() }`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('full', false);
expect(res.body).to.have.property('users').that.is.an('array').that.has.lengthOf(0);
})
.end(done);
});
it('should return full list of online users for more than 10 minutes in the past', (done) => {
const date = new Date();
date.setMinutes(date.getMinutes() - 11);
request.get(api(`users.presence?from=${ date.toISOString() }`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('full', true);
expect(res.body).to.have.property('users').to.have.property('0').to.deep.have.all.keys(
'_id',
'username',
'name',
'status',
'utcOffset',
);
})
.end(done);
});
});
});
describe('[/users.list]', () => {
it('should query all users in the system', (done) => {
request.get(api('users.list'))

Loading…
Cancel
Save