From a2ac1dd340e1079ffbf6a5c84cc9ae49f677ccdd Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 1 Jul 2019 14:54:19 -0300 Subject: [PATCH 01/19] canSendMessage async --- .../server/functions/canSendMessage.js | 50 ++++++++----------- app/models/server/raw/BaseRaw.js | 4 ++ app/models/server/raw/Rooms.js | 26 ++++++++++ app/models/server/raw/Subscriptions.js | 10 ++-- app/models/server/raw/index.js | 3 ++ 5 files changed, 59 insertions(+), 34 deletions(-) create mode 100644 app/models/server/raw/Rooms.js diff --git a/app/authorization/server/functions/canSendMessage.js b/app/authorization/server/functions/canSendMessage.js index 7ca6a41d3646..efd8d15d11ab 100644 --- a/app/authorization/server/functions/canSendMessage.js +++ b/app/authorization/server/functions/canSendMessage.js @@ -1,44 +1,36 @@ -import { Meteor } from 'meteor/meteor'; -import { TAPi18n } from 'meteor/tap:i18n'; -import { Random } from 'meteor/random'; - -import { canAccessRoom } from './canAccessRoom'; -import { hasPermission } from './hasPermission'; -import { Notifications } from '../../../notifications'; -import { Rooms, Subscriptions } from '../../../models'; +import { canAccessRoomAsync } from './canAccessRoom'; +import { hasPermissionAsync } from './hasPermission'; +import { Subscriptions, Rooms } from '../../../models/server/raw'; +const subscriptionProjection = { + blocked: 1, + blocker: 1, +}; -export const canSendMessage = (rid, { uid, username }, extraData) => { - const room = Rooms.findOneById(rid); +export const canSendMessageAsync = async (rid, { uid, username }, extraData) => { + const room = await Rooms.findOneById(rid); - if (!canAccessRoom.call(this, room, { _id: uid, username }, extraData)) { - throw new Meteor.Error('error-not-allowed'); + if (!await canAccessRoomAsync(room, { _id: uid, username }, extraData)) { + throw new Error('error-not-allowed'); } - const subscription = Subscriptions.findOneByRoomIdAndUserId(rid, uid); - if (subscription && (subscription.blocked || subscription.blocker)) { - throw new Meteor.Error('room_is_blocked'); + const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, uid, { subscription: subscriptionProjection }); + if (subscription.blocked || subscription.blocker) { + throw new Error('room_is_blocked'); } - if (room.ro === true) { - if (!hasPermission(Meteor.userId(), 'post-readonly', room._id)) { - // Unless the user was manually unmuted - if (!(room.unmuted || []).includes(username)) { - Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: room._id, - ts: new Date(), - msg: TAPi18n.__('room_is_read_only'), - }); - - throw new Meteor.Error('You can\'t send messages because the room is readonly.'); - } + if (room.ro === true && !await hasPermissionAsync(uid, 'post-readonly', rid)) { + // Unless the user was manually unmuted + if (!(room.unmuted || []).includes(username)) { + throw new Error('You can\'t send messages because the room is readonly.'); } } if ((room.muted || []).includes(username)) { - throw new Meteor.Error('You_have_been_muted'); + throw new Error('You_have_been_muted'); } return room; }; + +export const canSendMessage = (...args) => Promise.await(canSendMessageAsync(...args)); diff --git a/app/models/server/raw/BaseRaw.js b/app/models/server/raw/BaseRaw.js index 2fe2c81767b0..a2668cca3779 100644 --- a/app/models/server/raw/BaseRaw.js +++ b/app/models/server/raw/BaseRaw.js @@ -3,6 +3,10 @@ export class BaseRaw { this.col = col; } + findOneById(_id, options) { + return this.findOne({ _id }, options); + } + findOne(...args) { return this.col.findOne(...args); } diff --git a/app/models/server/raw/Rooms.js b/app/models/server/raw/Rooms.js new file mode 100644 index 000000000000..6ed8c7c09225 --- /dev/null +++ b/app/models/server/raw/Rooms.js @@ -0,0 +1,26 @@ +import { BaseRaw } from './BaseRaw'; + +export class RoomsRaw extends BaseRaw { + findOneByRoomIdAndUserId(roomId, userId, options) { + const query = { + rid: roomId, + 'u._id': userId, + }; + + return this.col.findOne(query, options); + } + + isUserInRole(userId, roleName, rid) { + if (rid == null) { + return; + } + + const query = { + 'u._id': userId, + rid, + roles: roleName, + }; + + return this.findOne(query, { fields: { roles: 1 } }); + } +} diff --git a/app/models/server/raw/Subscriptions.js b/app/models/server/raw/Subscriptions.js index 79a5e0f4b10d..ac7115393b41 100644 --- a/app/models/server/raw/Subscriptions.js +++ b/app/models/server/raw/Subscriptions.js @@ -1,22 +1,22 @@ import { BaseRaw } from './BaseRaw'; export class SubscriptionsRaw extends BaseRaw { - findOneByRoomIdAndUserId(roomId, userId, options) { + findOneByRoomIdAndUserId(rid, uid, options) { const query = { - rid: roomId, - 'u._id': userId, + rid, + 'u._id': uid, }; return this.col.findOne(query, options); } - isUserInRole(userId, roleName, rid) { + isUserInRole(uid, roleName, rid) { if (rid == null) { return; } const query = { - 'u._id': userId, + 'u._id': uid, rid, roles: roleName, }; diff --git a/app/models/server/raw/index.js b/app/models/server/raw/index.js index 818f2a9c1a24..9a56a1f09e21 100644 --- a/app/models/server/raw/index.js +++ b/app/models/server/raw/index.js @@ -8,9 +8,12 @@ import SettingsModel from '../models/Settings'; import { SettingsRaw } from './Settings'; import UsersModel from '../models/Users'; import { UsersRaw } from './Users'; +import RoomsModel from '../models/Rooms'; +import { RoomsRaw } from './Rooms'; export const Permissions = new PermissionsRaw(PermissionsModel.model.rawCollection()); export const Roles = new RolesRaw(RolesModel.model.rawCollection()); export const Subscriptions = new SubscriptionsRaw(SubscriptionsModel.model.rawCollection()); export const Settings = new SettingsRaw(SettingsModel.model.rawCollection()); export const Users = new UsersRaw(UsersModel.model.rawCollection()); +export const Rooms = new RoomsRaw(RoomsModel.model.rawCollection()); From f642fb1da06a12249a5d0f52f1fca10e6a2d10f0 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 2 Jul 2019 11:31:44 -0300 Subject: [PATCH 02/19] callbacks names --- app/2fa/server/loginHandler.js | 2 +- app/callbacks/lib/callbacks.js | 14 ++-- .../server/hooks/joinDiscussionOnMessage.js | 2 +- app/dolphin/lib/common.js | 2 +- app/e2e/server/index.js | 2 +- app/google-vision/server/googlevision.js | 2 +- .../resolvers/messages/chatMessageAdded.js | 2 +- app/integrations/server/triggers.js | 16 ++-- app/livechat/server/hooks/externalMessage.js | 47 +++++------ .../server/hooks/markRoomResponded.js | 13 ++- .../server/hooks/saveAnalyticsData.js | 82 +++++++++---------- app/search/server/events/events.js | 4 +- imports/message-read-receipt/server/hooks.js | 4 +- 13 files changed, 92 insertions(+), 100 deletions(-) diff --git a/app/2fa/server/loginHandler.js b/app/2fa/server/loginHandler.js index 3afc20b6cf21..a70f582f946b 100644 --- a/app/2fa/server/loginHandler.js +++ b/app/2fa/server/loginHandler.js @@ -36,4 +36,4 @@ callbacks.add('onValidateLogin', (login) => { throw new Meteor.Error('totp-invalid', 'TOTP Invalid'); } } -}); +}, callbacks.priority.MEDIUM, '2fa'); diff --git a/app/callbacks/lib/callbacks.js b/app/callbacks/lib/callbacks.js index 589764076924..8208c64f61b0 100644 --- a/app/callbacks/lib/callbacks.js +++ b/app/callbacks/lib/callbacks.js @@ -36,10 +36,12 @@ const getHooks = (hookName) => callbacks[hookName] || []; * @param {Function} callback - The callback function */ -callbacks.add = function(hook, callback, priority, id = Random.id()) { - if (!_.isNumber(priority)) { - priority = callbacks.priority.MEDIUM; - } +callbacks.add = function( + hook, + callback, + priority = callbacks.priority.MEDIUM, + id = Random.id() +) { callback.priority = priority; callback.id = id; callbacks[hook] = getHooks(hook); @@ -53,9 +55,7 @@ callbacks.add = function(hook, callback, priority, id = Random.id()) { return; } callbacks[hook].push(callback); - callbacks[hook] = _.sortBy(callbacks[hook], function(callback) { - return callback.priority || callbacks.priority.MEDIUM; - }); + callbacks[hook] = _.sortBy(callbacks[hook], (callback) => callback.priority || callbacks.priority.MEDIUM); }; diff --git a/app/discussion/server/hooks/joinDiscussionOnMessage.js b/app/discussion/server/hooks/joinDiscussionOnMessage.js index 08eb79071dc4..4884d47e1346 100644 --- a/app/discussion/server/hooks/joinDiscussionOnMessage.js +++ b/app/discussion/server/hooks/joinDiscussionOnMessage.js @@ -19,4 +19,4 @@ callbacks.add('beforeSaveMessage', (message, room) => { Meteor.runAsUser(message.u._id, () => Meteor.call('joinRoom', room._id)); return message; -}); +}, callbacks.priority.MEDIUM, 'joinDiscussionOnMessage'); diff --git a/app/dolphin/lib/common.js b/app/dolphin/lib/common.js index 36c95452a975..6f88e4c8b149 100644 --- a/app/dolphin/lib/common.js +++ b/app/dolphin/lib/common.js @@ -57,7 +57,7 @@ if (Meteor.isServer) { ServiceConfiguration.configurations.upsert({ service: 'dolphin' }, { $set: data }); } - callbacks.add('beforeCreateUser', DolphinOnCreateUser, callbacks.priority.HIGH); + callbacks.add('beforeCreateUser', DolphinOnCreateUser, callbacks.priority.HIGH, 'dolphin'); } else { Meteor.startup(() => Tracker.autorun(function() { diff --git a/app/e2e/server/index.js b/app/e2e/server/index.js index fdf3db07c92d..c9cfc3faca30 100644 --- a/app/e2e/server/index.js +++ b/app/e2e/server/index.js @@ -12,4 +12,4 @@ import './methods/requestSubscriptionKeys'; callbacks.add('afterJoinRoom', (user, room) => { Notifications.notifyRoom('e2e.keyRequest', room._id, room.e2eKeyId); -}); +}, callbacks.priority.MEDIUM, 'e2e'); diff --git a/app/google-vision/server/googlevision.js b/app/google-vision/server/googlevision.js index 658ef00f2631..35717a623243 100644 --- a/app/google-vision/server/googlevision.js +++ b/app/google-vision/server/googlevision.js @@ -35,7 +35,7 @@ class GoogleVision { callbacks.remove('beforeSaveMessage', 'googlevision-blockunsafe'); } }); - callbacks.add('afterFileUpload', this.annotate.bind(this)); + callbacks.add('afterFileUpload', this.annotate.bind(this), callbacks.priority.MEDIUM, 'GoogleVision'); } incCallCount(count) { diff --git a/app/graphql/server/resolvers/messages/chatMessageAdded.js b/app/graphql/server/resolvers/messages/chatMessageAdded.js index f8e3e54c455a..4d2f83a3e820 100644 --- a/app/graphql/server/resolvers/messages/chatMessageAdded.js +++ b/app/graphql/server/resolvers/messages/chatMessageAdded.js @@ -44,7 +44,7 @@ const resolver = { callbacks.add('afterSaveMessage', (message) => { publishMessage(message); -}, null, 'chatMessageAddedSubscription'); +}, callbacks.priority.MEDIUM, 'joinDiscussionOnMessage', 'chatMessageAddedSubscription'); export { schema, diff --git a/app/integrations/server/triggers.js b/app/integrations/server/triggers.js index 576e7f1801af..c7c39c50cd5c 100644 --- a/app/integrations/server/triggers.js +++ b/app/integrations/server/triggers.js @@ -7,11 +7,11 @@ const callbackHandler = function _callbackHandler(eventType) { }; }; -callbacks.add('afterSaveMessage', callbackHandler('sendMessage'), callbacks.priority.LOW); -callbacks.add('afterCreateChannel', callbackHandler('roomCreated'), callbacks.priority.LOW); -callbacks.add('afterCreatePrivateGroup', callbackHandler('roomCreated'), callbacks.priority.LOW); -callbacks.add('afterCreateUser', callbackHandler('userCreated'), callbacks.priority.LOW); -callbacks.add('afterJoinRoom', callbackHandler('roomJoined'), callbacks.priority.LOW); -callbacks.add('afterLeaveRoom', callbackHandler('roomLeft'), callbacks.priority.LOW); -callbacks.add('afterRoomArchived', callbackHandler('roomArchived'), callbacks.priority.LOW); -callbacks.add('afterFileUpload', callbackHandler('fileUploaded'), callbacks.priority.LOW); +callbacks.add('afterSaveMessage', callbackHandler('sendMessage'), callbacks.priority.LOW, 'integrations-sendMessage'); +callbacks.add('afterCreateChannel', callbackHandler('roomCreated'), callbacks.priority.LOW, 'integrations-roomCreated'); +callbacks.add('afterCreatePrivateGroup', callbackHandler('roomCreated'), callbacks.priority.LOW, 'integrations-roomCreated'); +callbacks.add('afterCreateUser', callbackHandler('userCreated'), callbacks.priority.LOW, 'integrations-userCreated'); +callbacks.add('afterJoinRoom', callbackHandler('roomJoined'), callbacks.priority.LOW, 'integrations-roomJoined'); +callbacks.add('afterLeaveRoom', callbackHandler('roomLeft'), callbacks.priority.LOW, 'integrations-roomLeft'); +callbacks.add('afterRoomArchived', callbackHandler('roomArchived'), callbacks.priority.LOW, 'integrations-roomArchived'); +callbacks.add('afterFileUpload', callbackHandler('fileUploaded'), callbacks.priority.LOW, 'integrations-fileUploaded'); diff --git a/app/livechat/server/hooks/externalMessage.js b/app/livechat/server/hooks/externalMessage.js index 91b9448be491..2ef76fbb88f1 100644 --- a/app/livechat/server/hooks/externalMessage.js +++ b/app/livechat/server/hooks/externalMessage.js @@ -1,4 +1,3 @@ -import { Meteor } from 'meteor/meteor'; import { HTTP } from 'meteor/http'; import _ from 'underscore'; @@ -39,32 +38,30 @@ callbacks.add('afterSaveMessage', function(message, room) { return message; } - Meteor.defer(() => { - try { - const response = HTTP.post('https://api.api.ai/api/query?v=20150910', { - data: { - query: message.msg, - lang: apiaiLanguage, - sessionId: room._id, - }, - headers: { - 'Content-Type': 'application/json; charset=utf-8', - Authorization: `Bearer ${ apiaiKey }`, - }, - }); + try { + const response = HTTP.post('https://api.api.ai/api/query?v=20150910', { + data: { + query: message.msg, + lang: apiaiLanguage, + sessionId: room._id, + }, + headers: { + 'Content-Type': 'application/json; charset=utf-8', + Authorization: `Bearer ${ apiaiKey }`, + }, + }); - if (response.data && response.data.status.code === 200 && !_.isEmpty(response.data.result.fulfillment.speech)) { - LivechatExternalMessage.insert({ - rid: message.rid, - msg: response.data.result.fulfillment.speech, - orig: message._id, - ts: new Date(), - }); - } - } catch (e) { - SystemLogger.error('Error using Api.ai ->', e); + if (response.data && response.data.status.code === 200 && !_.isEmpty(response.data.result.fulfillment.speech)) { + LivechatExternalMessage.insert({ + rid: message.rid, + msg: response.data.result.fulfillment.speech, + orig: message._id, + ts: new Date(), + }); } - }); + } catch (e) { + SystemLogger.error('Error using Api.ai ->', e); + } return message; }, callbacks.priority.LOW, 'externalWebHook'); diff --git a/app/livechat/server/hooks/markRoomResponded.js b/app/livechat/server/hooks/markRoomResponded.js index 4f629f901aaf..ed1be72106e6 100644 --- a/app/livechat/server/hooks/markRoomResponded.js +++ b/app/livechat/server/hooks/markRoomResponded.js @@ -1,4 +1,3 @@ -import { Meteor } from 'meteor/meteor'; import { callbacks } from '../../../callbacks'; import { Rooms } from '../../../models'; @@ -19,13 +18,11 @@ callbacks.add('afterSaveMessage', function(message, room) { return message; } - Meteor.defer(() => { - Rooms.setResponseByRoomId(room._id, { - user: { - _id: message.u._id, - username: message.u.username, - }, - }); + Rooms.setResponseByRoomId(room._id, { + user: { + _id: message.u._id, + username: message.u.username, + }, }); return message; diff --git a/app/livechat/server/hooks/saveAnalyticsData.js b/app/livechat/server/hooks/saveAnalyticsData.js index 582fa0d9cc37..58bed9f144d4 100644 --- a/app/livechat/server/hooks/saveAnalyticsData.js +++ b/app/livechat/server/hooks/saveAnalyticsData.js @@ -1,5 +1,3 @@ -import { Meteor } from 'meteor/meteor'; - import { callbacks } from '../../../callbacks'; import { Rooms } from '../../../models'; @@ -14,54 +12,54 @@ callbacks.add('afterSaveMessage', function(message, room) { return message; } - Meteor.defer(() => { - const now = new Date(); - let analyticsData; - // if the message has a token, it was sent by the visitor - if (!message.token) { - const visitorLastQuery = room.metrics && room.metrics.v ? room.metrics.v.lq : room.ts; - const agentLastReply = room.metrics && room.metrics.servedBy ? room.metrics.servedBy.lr : room.ts; - const agentJoinTime = room.servedBy && room.servedBy.ts ? room.servedBy.ts : room.ts; + const now = new Date(); + let analyticsData; + + // if the message has a token, it was sent by the visitor + if (!message.token) { + const visitorLastQuery = room.metrics && room.metrics.v ? room.metrics.v.lq : room.ts; + const agentLastReply = room.metrics && room.metrics.servedBy ? room.metrics.servedBy.lr : room.ts; + const agentJoinTime = room.servedBy && room.servedBy.ts ? room.servedBy.ts : room.ts; - const isResponseTt = room.metrics && room.metrics.response && room.metrics.response.tt; - const isResponseTotal = room.metrics && room.metrics.response && room.metrics.response.total; + const isResponseTt = room.metrics && room.metrics.response && room.metrics.response.tt; + const isResponseTotal = room.metrics && room.metrics.response && room.metrics.response.total; - if (agentLastReply === room.ts) { // first response - const firstResponseDate = now; - const firstResponseTime = (now.getTime() - visitorLastQuery) / 1000; - const responseTime = (now.getTime() - visitorLastQuery) / 1000; - const avgResponseTime = ((isResponseTt ? room.metrics.response.tt : 0) + responseTime) / ((isResponseTotal ? room.metrics.response.total : 0) + 1); + if (agentLastReply === room.ts) { // first response + const firstResponseDate = now; + const firstResponseTime = (now.getTime() - visitorLastQuery) / 1000; + const responseTime = (now.getTime() - visitorLastQuery) / 1000; + const avgResponseTime = ((isResponseTt ? room.metrics.response.tt : 0) + responseTime) / ((isResponseTotal ? room.metrics.response.total : 0) + 1); - const firstReactionDate = now; - const firstReactionTime = (now.getTime() - agentJoinTime) / 1000; - const reactionTime = (now.getTime() - agentJoinTime) / 1000; + const firstReactionDate = now; + const firstReactionTime = (now.getTime() - agentJoinTime) / 1000; + const reactionTime = (now.getTime() - agentJoinTime) / 1000; - analyticsData = { - firstResponseDate, - firstResponseTime, - responseTime, - avgResponseTime, - firstReactionDate, - firstReactionTime, - reactionTime, - }; - } else if (visitorLastQuery > agentLastReply) { // response, not first - const responseTime = (now.getTime() - visitorLastQuery) / 1000; - const avgResponseTime = ((isResponseTt ? room.metrics.response.tt : 0) + responseTime) / ((isResponseTotal ? room.metrics.response.total : 0) + 1); + analyticsData = { + firstResponseDate, + firstResponseTime, + responseTime, + avgResponseTime, + firstReactionDate, + firstReactionTime, + reactionTime, + }; + } else if (visitorLastQuery > agentLastReply) { // response, not first + const responseTime = (now.getTime() - visitorLastQuery) / 1000; + const avgResponseTime = ((isResponseTt ? room.metrics.response.tt : 0) + responseTime) / ((isResponseTotal ? room.metrics.response.total : 0) + 1); - const reactionTime = (now.getTime() - visitorLastQuery) / 1000; + const reactionTime = (now.getTime() - visitorLastQuery) / 1000; + + analyticsData = { + responseTime, + avgResponseTime, + reactionTime, + }; + } // ignore, its continuing response + } - analyticsData = { - responseTime, - avgResponseTime, - reactionTime, - }; - } // ignore, its continuing response - } + Rooms.saveAnalyticsDataByRoomId(room, message, analyticsData); - Rooms.saveAnalyticsDataByRoomId(room, message, analyticsData); - }); return message; }, callbacks.priority.LOW, 'saveAnalyticsData'); diff --git a/app/search/server/events/events.js b/app/search/server/events/events.js index 82fac3527e87..fb9ab1e58239 100644 --- a/app/search/server/events/events.js +++ b/app/search/server/events/events.js @@ -24,11 +24,11 @@ const eventService = new EventService(); */ callbacks.add('afterSaveMessage', function(m) { eventService.promoteEvent('message.save', m._id, m); -}); +}, callbacks.priority.MEDIUM, 'search-events'); callbacks.add('afterDeleteMessage', function(m) { eventService.promoteEvent('message.delete', m._id); -}); +}, callbacks.priority.MEDIUM, 'search-events'); /** * Listen to user and room changes via cursor diff --git a/imports/message-read-receipt/server/hooks.js b/imports/message-read-receipt/server/hooks.js index eee2b1f3d795..6d2242a59cf0 100644 --- a/imports/message-read-receipt/server/hooks.js +++ b/imports/message-read-receipt/server/hooks.js @@ -15,8 +15,8 @@ callbacks.add('afterSaveMessage', (message, room) => { // mark message as read as well ReadReceipt.markMessageAsReadBySender(message, room._id, message.u._id); -}); +}, callbacks.priority.MEDIUM, 'message-read-receipt-afterSaveMessage'); callbacks.add('afterReadMessages', (rid, { userId, lastSeen }) => { ReadReceipt.markMessagesAsRead(rid, userId, lastSeen); -}); +}, callbacks.priority.MEDIUM, 'message-read-receipt-afterReadMessages'); From 8efb9bfde07da552ff15a37285e53ddc41cbbfb4 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 2 Jul 2019 12:11:08 -0300 Subject: [PATCH 03/19] Update app/authorization/server/functions/canSendMessage.js Co-Authored-By: Diego Sampaio --- app/authorization/server/functions/canSendMessage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/authorization/server/functions/canSendMessage.js b/app/authorization/server/functions/canSendMessage.js index efd8d15d11ab..19237e206161 100644 --- a/app/authorization/server/functions/canSendMessage.js +++ b/app/authorization/server/functions/canSendMessage.js @@ -33,4 +33,4 @@ export const canSendMessageAsync = async (rid, { uid, username }, extraData) => return room; }; -export const canSendMessage = (...args) => Promise.await(canSendMessageAsync(...args)); +export const canSendMessage = (rid, { uid, username }, extraData) => Promise.await(canSendMessageAsync(rid, { uid, username }, extraData)); From 1e022f37322dbccfb0f31276459a9931391e98e2 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 2 Jul 2019 12:11:25 -0300 Subject: [PATCH 04/19] Update app/models/server/raw/Rooms.js Co-Authored-By: Diego Sampaio --- app/models/server/raw/Rooms.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/server/raw/Rooms.js b/app/models/server/raw/Rooms.js index 6ed8c7c09225..82d95e47962b 100644 --- a/app/models/server/raw/Rooms.js +++ b/app/models/server/raw/Rooms.js @@ -1,7 +1,7 @@ import { BaseRaw } from './BaseRaw'; export class RoomsRaw extends BaseRaw { - findOneByRoomIdAndUserId(roomId, userId, options) { + findOneByRoomIdAndUserId(rid, uid, options) { const query = { rid: roomId, 'u._id': userId, From 68dc6d755f97a7dfc2dd44f85dafc78a58b1836b Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 2 Jul 2019 12:11:37 -0300 Subject: [PATCH 05/19] Update app/authorization/server/functions/canSendMessage.js Co-Authored-By: Diego Sampaio --- app/authorization/server/functions/canSendMessage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/authorization/server/functions/canSendMessage.js b/app/authorization/server/functions/canSendMessage.js index 19237e206161..7080cb7bfa4a 100644 --- a/app/authorization/server/functions/canSendMessage.js +++ b/app/authorization/server/functions/canSendMessage.js @@ -14,7 +14,7 @@ export const canSendMessageAsync = async (rid, { uid, username }, extraData) => throw new Error('error-not-allowed'); } - const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, uid, { subscription: subscriptionProjection }); + const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, uid, subscriptionOptions); if (subscription.blocked || subscription.blocker) { throw new Error('room_is_blocked'); } From e7d4bc629462cfc07ffc2c6a7b8085dd18fa03b9 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 2 Jul 2019 12:12:06 -0300 Subject: [PATCH 06/19] Update canSendMessage.js --- app/authorization/server/functions/canSendMessage.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/authorization/server/functions/canSendMessage.js b/app/authorization/server/functions/canSendMessage.js index 7080cb7bfa4a..fbae305e07ac 100644 --- a/app/authorization/server/functions/canSendMessage.js +++ b/app/authorization/server/functions/canSendMessage.js @@ -2,9 +2,11 @@ import { canAccessRoomAsync } from './canAccessRoom'; import { hasPermissionAsync } from './hasPermission'; import { Subscriptions, Rooms } from '../../../models/server/raw'; -const subscriptionProjection = { - blocked: 1, - blocker: 1, +const subscriptionOptions = { + projection: { + blocked: 1, + blocker: 1, + }, }; export const canSendMessageAsync = async (rid, { uid, username }, extraData) => { From 9c0359325906463aa8c6b5e677de6dfe0fe16170 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 2 Jul 2019 14:17:40 -0300 Subject: [PATCH 07/19] Update Rooms.js --- app/models/server/raw/Rooms.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/server/raw/Rooms.js b/app/models/server/raw/Rooms.js index 82d95e47962b..d04e5aadee3a 100644 --- a/app/models/server/raw/Rooms.js +++ b/app/models/server/raw/Rooms.js @@ -3,20 +3,20 @@ import { BaseRaw } from './BaseRaw'; export class RoomsRaw extends BaseRaw { findOneByRoomIdAndUserId(rid, uid, options) { const query = { - rid: roomId, - 'u._id': userId, + rid, + 'u._id': uid, }; return this.col.findOne(query, options); } - isUserInRole(userId, roleName, rid) { + isUserInRole(uid, roleName, rid) { if (rid == null) { return; } const query = { - 'u._id': userId, + 'u._id': uid, rid, roles: roleName, }; From 4037a6f002f29f32ad86c2af205ff792b3b30970 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 2 Jul 2019 15:40:11 -0300 Subject: [PATCH 08/19] perf --- app/callbacks/lib/callbacks.js | 123 ++++++++++++++---------- app/lib/server/functions/sendMessage.js | 2 +- app/metrics/server/callbacksMetrics.js | 9 +- 3 files changed, 78 insertions(+), 56 deletions(-) diff --git a/app/callbacks/lib/callbacks.js b/app/callbacks/lib/callbacks.js index 8208c64f61b0..6148b7961ae8 100644 --- a/app/callbacks/lib/callbacks.js +++ b/app/callbacks/lib/callbacks.js @@ -2,6 +2,12 @@ import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; import _ from 'underscore'; +let timed = false; + +if (Meteor.isClient) { + const { getConfig } = require('../../ui-utils/client/config'); + timed = [getConfig('debug'), getConfig('timed-callbacks')].includes('true'); +} /* * Callback hooks provide an easy way to add extra steps to common operations. * @namespace RocketChat.callbacks @@ -9,15 +15,51 @@ import _ from 'underscore'; export const callbacks = {}; -if (Meteor.isServer) { - callbacks.showTime = true; - callbacks.showTotalTime = true; -} else { - callbacks.showTime = false; - callbacks.showTotalTime = false; -} +const wrapCallback = (callback) => (...args) => { + const time = Date.now(); + const result = callback(...args); + const currentTime = Date.now() - time; + let stack = callback.stack + && typeof callback.stack.split === 'function' + && callback.stack.split('\n'); + stack = stack && stack[2] && (stack[2].match(/\(.+\)/) || [])[0]; + console.log(String(currentTime), callback.hook, callback.id, stack); + return result; +}; +const wrapRun = (hook, fn) => (...args) => { + const time = Date.now(); + const ret = fn(...args); + const totalTime = Date.now() - time; + console.log(`${ hook }:`, totalTime); + return ret; +}; +const handleResult = (fn) => { + const ret = (result, constant) => { + const callbackResult = callbacks.runItem({ callback: fn, result, constant }); + return typeof callbackResult === 'undefined' ? result : callbackResult; + }; + return ret; +}; + + +const empty = (e) => e; +const combine = (f, x) => (e, ...constants) => x(f(e, ...constants), ...constants); +const createCallback = (hook, callbacks) => callbacks.map(handleResult).reduce(combine, empty); + +const createCallbackTimed = (hook, callbacks) => + wrapRun(hook, + callbacks + .map(wrapCallback) + .map(handleResult) + .reduce(combine, empty) + ); + +const create = (hook, cbs) => + (timed ? createCallbackTimed(hook, cbs) : createCallback(hook, cbs)); +const combinedCallbacks = new Map(); +this.combinedCallbacks = combinedCallbacks; /* * Callback priorities */ @@ -42,20 +84,18 @@ callbacks.add = function( priority = callbacks.priority.MEDIUM, id = Random.id() ) { - callback.priority = priority; - callback.id = id; callbacks[hook] = getHooks(hook); - - if (callbacks.showTime === true) { - const err = new Error(); - callback.stack = err.stack; - } - - if (callbacks[hook].find((cb) => cb.id === callback.id)) { + if (callbacks[hook].find((cb) => cb.id === id)) { return; } + callback.hook = hook; + callback.priority = priority; + callback.id = id; + callback.stack = new Error().stack; + callbacks[hook].push(callback); callbacks[hook] = _.sortBy(callbacks[hook], (callback) => callback.priority || callbacks.priority.MEDIUM); + combinedCallbacks.set(hook, create(hook, callbacks[hook])); }; @@ -67,11 +107,10 @@ callbacks.add = function( callbacks.remove = function(hook, id) { callbacks[hook] = getHooks(hook).filter((callback) => callback.id !== id); + combinedCallbacks.set(hook, create(hook, callbacks[hook])); }; -callbacks.runItem = function({ callback, result, constant /* , hook */ }) { - return callback(result, constant); -}; +callbacks.runItem = ({ callback, result, constant /* , hook */ }) => callback(result, constant); /* * Successively run all of a hook's callbacks on an item @@ -82,38 +121,18 @@ callbacks.runItem = function({ callback, result, constant /* , hook */ }) { */ callbacks.run = function(hook, item, constant) { - const callbackItems = callbacks[hook]; - if (!callbackItems || !callbackItems.length) { + const runner = combinedCallbacks.get(hook); + if (!runner) { return item; } - let totalTime = 0; - const result = callbackItems.reduce(function(result, callback) { - const time = callbacks.showTime === true || callbacks.showTotalTime === true ? Date.now() : 0; - - const callbackResult = callbacks.runItem({ hook, callback, result, constant, time }); - - if (callbacks.showTime === true || callbacks.showTotalTime === true) { - const currentTime = Date.now() - time; - totalTime += currentTime; - if (callbacks.showTime === true) { - if (!Meteor.isServer) { - let stack = callback.stack && typeof callback.stack.split === 'function' && callback.stack.split('\n'); - stack = stack && stack[2] && (stack[2].match(/\(.+\)/) || [])[0]; - console.log(String(currentTime), hook, callback.id, stack); - } - } - } - return typeof callbackResult === 'undefined' ? result : callbackResult; - }, item); + return runner(item, constant); - if (callbacks.showTotalTime === true) { - if (!Meteor.isServer) { - console.log(`${ hook }:`, totalTime); - } - } + // return callbackItems.reduce(function(result, callback) { + // const callbackResult = callbacks.runItem({ hook, callback, result, constant }); - return result; + // return typeof callbackResult === 'undefined' ? result : callbackResult; + // }, item); }; @@ -124,12 +143,10 @@ callbacks.run = function(hook, item, constant) { * @param {Object} [constant] - An optional constant that will be passed along to each callback */ -callbacks.runAsync = function(hook, item, constant) { - const callbackItems = callbacks[hook]; - if (Meteor.isServer && callbackItems && callbackItems.length) { - Meteor.defer(function() { - callbackItems.forEach((callback) => callback(item, constant)); - }); +callbacks.runAsync = Meteor.isServer ? function(hook, item, constant) { + const callbackItems = combinedCallbacks[hook]; + if (callbackItems && callbackItems.length) { + callbackItems.forEach((callback) => Meteor.defer(function() { callback(item, constant); })); } return item; -}; +} : () => { throw new Error('callbacks.runAsync on client server not allowed'); }; diff --git a/app/lib/server/functions/sendMessage.js b/app/lib/server/functions/sendMessage.js index 290218f14e3d..964bd7f9630c 100644 --- a/app/lib/server/functions/sendMessage.js +++ b/app/lib/server/functions/sendMessage.js @@ -211,7 +211,7 @@ export const sendMessage = function(user, message, room, upsert = false) { Defer other updates as their return is not interesting to the user */ // Execute all callbacks - Meteor.defer(() => callbacks.run('afterSaveMessage', message, room, user._id)); + callbacks.run('afterSaveMessage', message, room, user._id); return message; } }; diff --git a/app/metrics/server/callbacksMetrics.js b/app/metrics/server/callbacksMetrics.js index 8402f6fa9f8e..5cb5b2469e2e 100644 --- a/app/metrics/server/callbacksMetrics.js +++ b/app/metrics/server/callbacksMetrics.js @@ -18,12 +18,17 @@ callbacks.run = function(hook, item, constant) { return result; }; -callbacks.runItem = function({ callback, result, constant, hook, time }) { +callbacks.runItem = function({ callback, result, constant, hook, time = Date.now() }) { const rocketchatCallbacksEnd = metrics.rocketchatCallbacks.startTimer({ hook, callback: callback.id }); const newResult = originalRunItem({ callback, result, constant }); - StatsTracker.timing('callbacks.time', Date.now() - time, [`hook:${ hook }`, `callback:${ callback.id }`]); + if (time) { + StatsTracker.timing('callbacks.time', Date.now() - time, [ + `hook:${ hook }`, + `callback:${ callback.id }`, + ]); + } rocketchatCallbacksEnd(); From 97f58962e5498dbd52c2b6a34bc778d0b45a9130 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 2 Jul 2019 19:16:36 -0300 Subject: [PATCH 09/19] Update app/callbacks/lib/callbacks.js Co-Authored-By: Diego Sampaio --- app/callbacks/lib/callbacks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/callbacks/lib/callbacks.js b/app/callbacks/lib/callbacks.js index 6148b7961ae8..59dfce32b515 100644 --- a/app/callbacks/lib/callbacks.js +++ b/app/callbacks/lib/callbacks.js @@ -6,7 +6,7 @@ let timed = false; if (Meteor.isClient) { const { getConfig } = require('../../ui-utils/client/config'); - timed = [getConfig('debug'), getConfig('timed-callbacks')].includes('true'); + timed = [getConfig('debug'), getConfig('timed-callbacks')].includes('true'); } /* * Callback hooks provide an easy way to add extra steps to common operations. From a2766791a505ad47a0bc86374e1612d46e813db4 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 2 Jul 2019 19:22:12 -0300 Subject: [PATCH 10/19] Update app/callbacks/lib/callbacks.js Co-Authored-By: Diego Sampaio --- app/callbacks/lib/callbacks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/callbacks/lib/callbacks.js b/app/callbacks/lib/callbacks.js index 59dfce32b515..cfa07c160947 100644 --- a/app/callbacks/lib/callbacks.js +++ b/app/callbacks/lib/callbacks.js @@ -36,7 +36,7 @@ const wrapRun = (hook, fn) => (...args) => { }; const handleResult = (fn) => { - const ret = (result, constant) => { + return (result, constant) => { const callbackResult = callbacks.runItem({ callback: fn, result, constant }); return typeof callbackResult === 'undefined' ? result : callbackResult; }; From 18715bb715471546d87c77e66249a2725a339b03 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 2 Jul 2019 19:22:59 -0300 Subject: [PATCH 11/19] Update callbacks.js --- app/callbacks/lib/callbacks.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/callbacks/lib/callbacks.js b/app/callbacks/lib/callbacks.js index cfa07c160947..2d740a271b60 100644 --- a/app/callbacks/lib/callbacks.js +++ b/app/callbacks/lib/callbacks.js @@ -40,7 +40,6 @@ const handleResult = (fn) => { const callbackResult = callbacks.runItem({ callback: fn, result, constant }); return typeof callbackResult === 'undefined' ? result : callbackResult; }; - return ret; }; From ec8cad82652d47118e939e163f6887a14712d1ad Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 2 Jul 2019 19:32:27 -0300 Subject: [PATCH 12/19] Update callbacks.js --- app/callbacks/lib/callbacks.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/callbacks/lib/callbacks.js b/app/callbacks/lib/callbacks.js index 2d740a271b60..43dcdb1f09a1 100644 --- a/app/callbacks/lib/callbacks.js +++ b/app/callbacks/lib/callbacks.js @@ -35,11 +35,9 @@ const wrapRun = (hook, fn) => (...args) => { return ret; }; -const handleResult = (fn) => { - return (result, constant) => { - const callbackResult = callbacks.runItem({ callback: fn, result, constant }); - return typeof callbackResult === 'undefined' ? result : callbackResult; - }; +const handleResult = (fn) => (result, constant) => { + const callbackResult = callbacks.runItem({ callback: fn, result, constant }); + return typeof callbackResult === 'undefined' ? result : callbackResult; }; From 9fddad8e6570225a69f8e6862d3004c64e9947b2 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 2 Jul 2019 19:32:53 -0300 Subject: [PATCH 13/19] Update app/callbacks/lib/callbacks.js Co-Authored-By: Diego Sampaio --- app/callbacks/lib/callbacks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/callbacks/lib/callbacks.js b/app/callbacks/lib/callbacks.js index 43dcdb1f09a1..6d1856f50aea 100644 --- a/app/callbacks/lib/callbacks.js +++ b/app/callbacks/lib/callbacks.js @@ -141,7 +141,7 @@ callbacks.run = function(hook, item, constant) { */ callbacks.runAsync = Meteor.isServer ? function(hook, item, constant) { - const callbackItems = combinedCallbacks[hook]; + const callbackItems = callbacks[hook]; if (callbackItems && callbackItems.length) { callbackItems.forEach((callback) => Meteor.defer(function() { callback(item, constant); })); } From 90fe79794505aa24b68769f32a9cae50cf1a2add Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 2 Jul 2019 19:33:08 -0300 Subject: [PATCH 14/19] Update app/graphql/server/resolvers/messages/chatMessageAdded.js Co-Authored-By: Diego Sampaio --- app/graphql/server/resolvers/messages/chatMessageAdded.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/graphql/server/resolvers/messages/chatMessageAdded.js b/app/graphql/server/resolvers/messages/chatMessageAdded.js index 4d2f83a3e820..dc8ee0ddba7c 100644 --- a/app/graphql/server/resolvers/messages/chatMessageAdded.js +++ b/app/graphql/server/resolvers/messages/chatMessageAdded.js @@ -44,7 +44,7 @@ const resolver = { callbacks.add('afterSaveMessage', (message) => { publishMessage(message); -}, callbacks.priority.MEDIUM, 'joinDiscussionOnMessage', 'chatMessageAddedSubscription'); +}, callbacks.priority.MEDIUM, 'chatMessageAddedSubscription'); export { schema, From b1ae5401390326811a257caa01b6241f060c2c29 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 2 Jul 2019 19:33:27 -0300 Subject: [PATCH 15/19] Apply suggestions from code review Co-Authored-By: Diego Sampaio --- app/search/server/events/events.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/search/server/events/events.js b/app/search/server/events/events.js index fb9ab1e58239..79e3c8aa8142 100644 --- a/app/search/server/events/events.js +++ b/app/search/server/events/events.js @@ -28,7 +28,7 @@ callbacks.add('afterSaveMessage', function(m) { callbacks.add('afterDeleteMessage', function(m) { eventService.promoteEvent('message.delete', m._id); -}, callbacks.priority.MEDIUM, 'search-events'); +}, callbacks.priority.MEDIUM, 'search-events-delete'); /** * Listen to user and room changes via cursor From b145bd7a1f1a96ecd44775bce85079a9e7a393c9 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 3 Jul 2019 17:19:21 -0300 Subject: [PATCH 16/19] Add name to callback --- app/emoji-emojione/server/callbacks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/emoji-emojione/server/callbacks.js b/app/emoji-emojione/server/callbacks.js index fb88919f10b0..cd06854f3023 100644 --- a/app/emoji-emojione/server/callbacks.js +++ b/app/emoji-emojione/server/callbacks.js @@ -4,5 +4,5 @@ import emojione from 'emojione'; import { callbacks } from '../../callbacks'; Meteor.startup(function() { - callbacks.add('beforeSendMessageNotifications', (message) => emojione.shortnameToUnicode(message)); + callbacks.add('beforeSendMessageNotifications', (message) => emojione.shortnameToUnicode(message), callbacks.priority.MEDIUM, 'emojione-shortnameToUnicode'); }); From 1bccaa7857d414c534c895582220bd862bf140cf Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 3 Jul 2019 17:22:12 -0300 Subject: [PATCH 17/19] Fix callback hook name --- app/callbacks/lib/callbacks.js | 2 +- app/metrics/server/callbacksMetrics.js | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/app/callbacks/lib/callbacks.js b/app/callbacks/lib/callbacks.js index 6d1856f50aea..06975f18419a 100644 --- a/app/callbacks/lib/callbacks.js +++ b/app/callbacks/lib/callbacks.js @@ -36,7 +36,7 @@ const wrapRun = (hook, fn) => (...args) => { }; const handleResult = (fn) => (result, constant) => { - const callbackResult = callbacks.runItem({ callback: fn, result, constant }); + const callbackResult = callbacks.runItem({ hook: fn.hook, callback: fn, result, constant }); return typeof callbackResult === 'undefined' ? result : callbackResult; }; diff --git a/app/metrics/server/callbacksMetrics.js b/app/metrics/server/callbacksMetrics.js index 5cb5b2469e2e..86221f5d55f2 100644 --- a/app/metrics/server/callbacksMetrics.js +++ b/app/metrics/server/callbacksMetrics.js @@ -23,12 +23,10 @@ callbacks.runItem = function({ callback, result, constant, hook, time = Date.now const newResult = originalRunItem({ callback, result, constant }); - if (time) { - StatsTracker.timing('callbacks.time', Date.now() - time, [ - `hook:${ hook }`, - `callback:${ callback.id }`, - ]); - } + StatsTracker.timing('callbacks.time', Date.now() - time, [ + `hook:${ hook }`, + `callback:${ callback.id }`, + ]); rocketchatCallbacksEnd(); From 09261aaaf65e7feb64efd3d742aecc9f37d13ab3 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 3 Jul 2019 17:32:53 -0300 Subject: [PATCH 18/19] Update app/lib/server/functions/sendMessage.js Co-Authored-By: Diego Sampaio --- app/lib/server/functions/sendMessage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/server/functions/sendMessage.js b/app/lib/server/functions/sendMessage.js index 964bd7f9630c..290218f14e3d 100644 --- a/app/lib/server/functions/sendMessage.js +++ b/app/lib/server/functions/sendMessage.js @@ -211,7 +211,7 @@ export const sendMessage = function(user, message, room, upsert = false) { Defer other updates as their return is not interesting to the user */ // Execute all callbacks - callbacks.run('afterSaveMessage', message, room, user._id); + Meteor.defer(() => callbacks.run('afterSaveMessage', message, room, user._id)); return message; } }; From 0206cdf3a419688c8253899509ea91ca17ccbaf6 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Wed, 3 Jul 2019 18:17:08 -0300 Subject: [PATCH 19/19] Apply suggestions from code review --- app/callbacks/lib/callbacks.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/callbacks/lib/callbacks.js b/app/callbacks/lib/callbacks.js index 06975f18419a..193b6be06ed6 100644 --- a/app/callbacks/lib/callbacks.js +++ b/app/callbacks/lib/callbacks.js @@ -41,16 +41,16 @@ const handleResult = (fn) => (result, constant) => { }; -const empty = (e) => e; -const combine = (f, x) => (e, ...constants) => x(f(e, ...constants), ...constants); -const createCallback = (hook, callbacks) => callbacks.map(handleResult).reduce(combine, empty); +const identity = (e) => e; +const pipe = (f, g) => (e, ...constants) => g(f(e, ...constants), ...constants); +const createCallback = (hook, callbacks) => callbacks.map(handleResult).reduce(pipe, identity); const createCallbackTimed = (hook, callbacks) => wrapRun(hook, callbacks .map(wrapCallback) .map(handleResult) - .reduce(combine, empty) + .reduce(pipe, identity) ); const create = (hook, cbs) =>