Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
95836b4
Send active users via stream
sampaiodiego Nov 6, 2018
3cb073f
Change to REST
sampaiodiego Nov 6, 2018
1f3d5e9
Better handling of statuses
sampaiodiego Nov 7, 2018
b3a94d1
Change fullUserData publication
sampaiodiego Nov 7, 2018
445da32
Notify status changes only for users in each instance
sampaiodiego Nov 13, 2018
d259895
Use stream from presence package
sampaiodiego Nov 22, 2018
b5d6c83
Retry if users.actives request fails
sampaiodiego Nov 23, 2018
7da3bf4
Merge branch 'develop' into change-status-to-streams
sampaiodiego Dec 3, 2018
5813df6
Merge branch 'develop' into change-status-to-streams
sampaiodiego Dec 3, 2018
bc5232b
Merge branch 'develop' into change-status-to-streams
sampaiodiego Dec 5, 2018
763ddc7
Emit presence status through either stream or callback
sampaiodiego Dec 5, 2018
8cddec2
Remove memoize
sampaiodiego Dec 6, 2018
3362d75
Let StreamCast process user default status
sampaiodiego Dec 6, 2018
28c1877
Fix new users without status
sampaiodiego Dec 7, 2018
c3fc29f
Remove updateAt from default status update
sampaiodiego Dec 7, 2018
3be1d35
Update messages.js
ggazzo Dec 10, 2018
55856be
Update UsersNameChanged.js
ggazzo Dec 10, 2018
9de8b69
Merge branch 'develop' into change-status-to-streams
ggazzo Dec 10, 2018
e03ce36
Add test
sampaiodiego Dec 11, 2018
c8fa471
Retrieve active users again on reconnection
sampaiodiego Dec 13, 2018
a2e62b5
Merge branch 'develop' into change-status-to-streams
sampaiodiego Dec 14, 2018
30d94d9
Notify everyone about a status change
sampaiodiego Dec 14, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions client/notifications/UsersNameChanged.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Meteor.startup(function() {
'u._id': _id,
}, {
$set: {
'u.username': username,
'u.name': name,
},
}, {
Expand All @@ -18,6 +19,7 @@ Meteor.startup(function() {
},
}, {
$set: {
'mentions.$.username': username,
'mentions.$.name': name,
},
}, {
Expand Down
1 change: 0 additions & 1 deletion client/routes/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ FlowRouter.subscriptions = function() {
Tracker.autorun(() => {
if (Meteor.userId()) {
this.register('userData', Meteor.subscribe('userData'));
this.register('activeUsers', Meteor.subscribe('activeUsers'));
}
});
};
Expand Down
1 change: 0 additions & 1 deletion client/startup/startup.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ Meteor.startup(function() {
return;
}
Meteor.subscribe('userData');
Meteor.subscribe('activeUsers');
computation.stop();
});

Expand Down
1 change: 1 addition & 0 deletions imports/startup/client/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
import '../../message-read-receipt/client';
import '../../personal-access-tokens/client';
import './listenActiveUsers';
62 changes: 62 additions & 0 deletions imports/startup/client/listenActiveUsers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import _ from 'underscore';

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._collection.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 } });
Comment thread
ggazzo marked this conversation as resolved.
if (found) {
return;
}

Meteor.users._collection.insert(user);
};

let retry = 0;
const getActiveUsers = _.debounce(() => {
RocketChat.API.v1.get('users.active')
.then((result) => {
result.users.forEach((user) => {
saveUser(user);
});
})
.catch(() => setTimeout(getActiveUsers, retry++ * 2000));
}, 1000);

Tracker.autorun(() => {
if (!Meteor.userId() || !Meteor.status().connected) {
return;
}
getActiveUsers();
});

Meteor.startup(function() {
RocketChat.Notifications.onLogged('user-status', (user) => {
saveUser(user, true);
});

RocketChat.Notifications.onLogged('Users:NameChanged', ({ _id, username }) => {
if (!username) {
return;
}
Meteor.users._collection.upsert({ _id }, {
$set: {
username,
},
});
});
});
1 change: 1 addition & 0 deletions imports/startup/server/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
import '../../message-read-receipt/server';
import '../../personal-access-tokens/server';
import '../../users-presence/server';
20 changes: 20 additions & 0 deletions imports/users-presence/server/activeUsers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { UserPresenceEvents } from 'meteor/konecty:user-presence';

UserPresenceEvents.on('setUserStatus', (user, status/* , statusConnection*/) => {
const {
_id,
username,
name,
utcOffset,
} = user;

// since this callback can be called by only one instance in the cluster
// we need to brodcast the change to all instances
RocketChat.Notifications.notifyLogged('user-status', {
_id,
username,
name,
status,
utcOffset,
});
});
1 change: 1 addition & 0 deletions imports/users-presence/server/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './activeUsers';
15 changes: 15 additions & 0 deletions packages/rocketchat-api/server/v1/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -529,3 +529,18 @@ RocketChat.API.v1.addRoute('users.removePersonalAccessToken', { authRequired: tr
return RocketChat.API.v1.success();
},
});

RocketChat.API.v1.addRoute('users.active', { authRequired: true }, {
get() {
return RocketChat.API.v1.success({
users: RocketChat.models.Users.findUsersNotOffline({
fields: {
username: 1,
name: 1,
status: 1,
utcOffset: 1,
},
}).fetch(),
});
},
});
7 changes: 7 additions & 0 deletions packages/rocketchat-lib/client/models/FullUser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

RocketChat.models.FullUser = new class extends RocketChat.models._Base {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this separation, I didn't understand the idea.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the issue is since I'm updating/inserting records on users collection on the client by hand, Meteor will throw out error if I subscribe to a publication that would add records to users collection.

so I had to change the fullUserData publication to publish on this new virtual collection.

constructor() {
super();
this._initModel('full_user');
}
};
1 change: 1 addition & 0 deletions packages/rocketchat-lib/package.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ Package.onUse(function(api) {
// CLIENT MODELS
api.addFiles('client/models/_Base.js', 'client');
api.addFiles('client/models/Avatars.js', 'client');
api.addFiles('client/models/FullUser.js', 'client');
api.addFiles('client/models/Uploads.js', 'client');

// CLIENT VIEWS
Expand Down
7 changes: 7 additions & 0 deletions packages/rocketchat-lib/server/functions/setUsername.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ RocketChat._setUsername = function(userId, u) {
fileStore.model.updateFileNameById(file._id, username);
}
}

RocketChat.Notifications.notifyLogged('Users:NameChanged', {
_id: user._id,
name: user.name,
username: user.username,
});

return user;
};

Expand Down
11 changes: 11 additions & 0 deletions packages/rocketchat-lib/server/models/Users.js
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,17 @@ class ModelUsers extends RocketChat.models._Base {
return this.update({ _id }, update);
}

updateDefaultStatus(_id, statusDefault) {
return this.update({
_id,
statusDefault: { $ne: statusDefault },
}, {
$set: {
statusDefault,
},
});
}

// INSERT
create(data) {
const user = {
Expand Down
5 changes: 2 additions & 3 deletions packages/rocketchat-ui-admin/client/users/adminUsers.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -99,7 +98,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 RocketChat.models.FullUser.find(query, { limit, sort: { username: 1, name: 1 } }).fetch();
};
});

Expand All @@ -124,7 +123,7 @@ Template.adminUsers.events({
},
'click .user-info'(e, instance) {
e.preventDefault();
instance.tabBarData.set(Meteor.users.findOne(this._id));
instance.tabBarData.set(RocketChat.models.FullUser.findOne(this._id));
instance.tabBar.open('admin-user-info');
},
'click .info-tabs button'(e) {
Expand Down
2 changes: 1 addition & 1 deletion packages/rocketchat-ui-flextab/client/tabs/userInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ Template.userInfo.onCreated(function() {
} else if (data && data._id != null) {
filter = { _id: data._id };
}
const user = Meteor.users.findOne(filter);
const user = RocketChat.models.FullUser.findOne(filter);

return this.user.set(user);
});
Expand Down
2 changes: 1 addition & 1 deletion packages/rocketchat-ui-master/client/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ Template.main.helpers({
return iframeEnabled && RocketChat.iframeLogin.reactiveIframeUrl.get();
},
subsReady() {
const routerReady = FlowRouter.subsReady('userData', 'activeUsers');
const routerReady = FlowRouter.subsReady('userData');
const subscriptionsReady = CachedChatSubscription.ready.get();
const settingsReady = RocketChat.settings.cachedCollection.ready.get();
const ready = (Meteor.userId() == null) || (routerReady && subscriptionsReady && settingsReady);
Expand Down
21 changes: 15 additions & 6 deletions server/publications/fullUserData.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,24 @@ Meteor.publish('fullUserData', function(filter, limit) {
return this.ready();
}

const result = RocketChat.getFullUserData({
const handle = RocketChat.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());
});
5 changes: 4 additions & 1 deletion server/stream/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ Meteor.startup(function() {
}
}

return RocketChat.models.Messages.on('change', function({ clientAction, id, data/* , oplog*/ }) {
return RocketChat.models.Messages.on('change', function({ clientAction, id, data, diff/* , oplog*/ }) {
if (diff && (diff['u.username'] || diff['editedBy.username'] || Object.keys(diff).some(function(k) { return ~k.indexOf('mentions.'); }))) {
return;
}
switch (clientAction) {
case 'inserted':
case 'updated':
Expand Down
26 changes: 23 additions & 3 deletions server/stream/streamBroadcast.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global InstanceStatus, DDP, LoggerManager */
/* global InstanceStatus, DDP, LoggerManager, UserPresence */

import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
Expand All @@ -8,6 +8,9 @@ import { DDPCommon } from 'meteor/ddp-common';
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;

Expand Down Expand Up @@ -46,7 +49,12 @@ function authorizeConnection(instance) {
return _authorizeConnection(instance);
}

const originalSetDefaultStatus = UserPresence.setDefaultStatus;
function startMatrixBroadcast() {
if (!startMonitor) {
UserPresence.setDefaultStatus = originalSetDefaultStatus;
}

const query = {
'extraInformation.port': {
$exists: true,
Expand Down Expand Up @@ -162,6 +170,12 @@ function startStreamCastBroadcast(value) {

logger.connection.info('connecting in', instance, value);

if (!startMonitor) {
UserPresence.setDefaultStatus = (id, status) => {
RocketChat.models.Users.updateDefaultStatus(id, status);
};
}

const connection = DDP.connect(value, {
_dontPrintErrors: LoggerManager.logLevel < 2,
});
Expand All @@ -188,11 +202,17 @@ 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);
} else {
return instance.emitWithoutBroadcast(eventName, args);
}
});

return connection.subscribe('stream');
Expand Down
33 changes: 33 additions & 0 deletions tests/end-to-end/api/01-users.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,39 @@ describe('[Users]', function() {
});
});

describe('[/users.active]', () => {
describe('Not logged in:', () => {
it('should return 401 unauthorized', (done) => {
request.get(api('users.active'))
.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 list', (done) => {
request.get(api('users.active'))
.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('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'))
Expand Down