From 7d5350309cbcbba0ca36b2835df6be87d1fe9f8e Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 26 Apr 2018 20:30:04 -0300 Subject: [PATCH 01/18] Notification logic improvements --- .../server/lib/sendNotificationsOnMessage.js | 344 +++++++++++++++++- .../server/models/Subscriptions.js | 48 +++ 2 files changed, 384 insertions(+), 8 deletions(-) diff --git a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js index aee2caa865ff..381ed206b9a1 100644 --- a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js +++ b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js @@ -104,16 +104,12 @@ function notifyAudioUser(userId, message, room) { function messageContainsHighlight(message, highlights) { if (! highlights || highlights.length === 0) { return false; } - let has = false; - highlights.some(function(highlight) { + return highlights.some(function(highlight) { const regexp = new RegExp(s.escapeRegExp(highlight), 'i'); if (regexp.test(message.msg)) { - has = true; return true; } }); - - return has; } function getBadgeCount(userId) { @@ -150,8 +146,31 @@ const sendPushNotifications = (userIdsToPushNotify = [], message, room, push_roo } }; -const callJoin = (user, rid) => user.active && Meteor.runAsUser(user._id, () => Meteor.call('joinRoom', rid)); -RocketChat.callbacks.add('afterSaveMessage', function(message, room, userId) { +const callJoin = (user, rid) => new Promise((resolve, reject) => { + Meteor.runAsUser(user._id, () => Meteor.call('joinRoom', rid, (error, result) => { + if (error) { + return reject(error); + } + return resolve(result); + })); +}); + +const sendSinglePush = ({ room, roomId, roomName, username, message, payload, userId, receiverUsername}) => { + RocketChat.PushNotification.send({ + roomId, + roomName, + username, + message, + // badge: getBadgeCount(userIdToNotify), + payload, + usersTo: { + userId + }, + category: canSendMessageToRoom(room, receiverUsername) ? CATEGORY_MESSAGE : CATEGORY_MESSAGE_NOREPLY + }); +}; + +function sendNotificationOnMessage(message, room, userId) { // skips this callback if the message was edited if (message.editedAt) { @@ -454,6 +473,12 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room, userId) { userIdsToPushNotify = _.without(_.compact(_.unique(userIdsToPushNotify)), message.u._id); userIdsForAudio = _.without(_.compact(_.unique(userIdsForAudio)), message.u._id); + + console.log('desktop ->',userIdsToNotify.length); + console.log('audio ->',userIdsForAudio.length); + console.log('push ->',userIdsToPushNotify.length); + + console.time('sending'); for (const usersOfMentionId of userIdsToNotify) { const duration = settings.desktopNotificationDurations[usersOfMentionId]; notifyDesktopUser(usersOfMentionId, user, message, room, duration); @@ -466,8 +491,311 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room, userId) { const allUserIdsToNotify = _.unique(userIdsToNotify.concat(userIdsToPushNotify)); RocketChat.Sandstorm.notify(message, allUserIdsToNotify, `@${ user.username }: ${ message.msg }`, room.t === 'p' ? 'privateMessage' : 'message'); + console.timeEnd('sending'); } return message; -}, RocketChat.callbacks.priority.LOW, 'sendNotificationOnMessage'); +} + + +let pushNumber = 0; +let desktopNumber = 0; +let audioNumber = 0; +let totalSubs = 0; +const sendNotification = ({ + subscription, + sender, + toAll, + toHere, + message, + room, + mentionIds, + alwaysNotifyMobileBoolean, + push_room, + push_username, + push_message +}) => { + totalSubs++; + + // don't notify the sender + if (subscription.u._id === sender._id) { + console.log('return; sender'); + return; + } + + // notifications disabled + if (subscription.disableNotifications) { + console.log('return; disableNotifications'); + return; + } + + // dont send notification to users who ignored the sender + if (Array.isArray(subscription.ignored) && subscription.ignored.find(sender._id)) { + console.log('return; ignored'); + return; + } + + // mute group notifications (@here and @all) + if (subscription.muteGroupMentions && (toAll || toHere)) { + console.log('return; muteGroupMentions'); + return; + } + + const receiver = RocketChat.models.Users.findOneById(subscription.u._id); + + const hasHighlight = messageContainsHighlight(message, receiver.settings && receiver.settings.preferences && receiver.settings.preferences.highlights); + + const { + audioNotifications, + desktopNotifications, + mobilePushNotifications + } = subscription; + + let notificationSent = false; + + if (toAll || toHere || hasHighlight || audioNotifications === 'all' || mentionIds.includes(subscription.u._id)) { + + // busy users don't receive audio notification + if (receiver.status !== 'busy') { + // settings.alwaysNotifyAudioUsers.push(subscription.u._id); + // userIdsForAudio.push(subscription.u._id); + notifyAudioUser(subscription.u._id, message, room); + + ++audioNumber; + // console.log('audio ->', ++audioNumber); + } + } + + if (toAll || toHere || hasHighlight || desktopNotifications === 'all' || mentionIds.includes(subscription.u._id)) { + + // busy users don't receive desktop notification + if (receiver.status !== 'busy') { + // userIdsToNotify.push(subscription.u._id); + + notificationSent = true; + + ++desktopNumber; + notifyDesktopUser(subscription.u._id, sender, message, room, subscription.desktopNotificationDuration); + // console.log('desktop ->', ++desktopNumber, toAll, toHere, hasHighlight, desktopNotifications === 'all', mentionIds.includes(subscription.u._id)); + } + } + + if (toAll || hasHighlight || mobilePushNotifications === 'all' || mentionIds.includes(subscription.u._id)) { + + // only offline users will receive a push notification + if (alwaysNotifyMobileBoolean || receiver.statusConnection !== 'online') { + // userIdsToPushNotify.push(subscription.u._id); + // pushUsernames[receiver._id] = receiver.username; + + notificationSent = true; + + sendSinglePush({ + room, + roomId: message.rid, + roomName: push_room, + username: push_username, + message: push_message, + // badge: getBadgeCount(userIdToNotify), + payload: { + host: Meteor.absoluteUrl(), + rid: message.rid, + sender: message.u, + type: room.t, + name: room.name + }, + userId: subscription.u._id, + receiverUsername: receiver.username + }); + pushNumber++; + // console.log('push ->', ++pushNumber, toAll, hasHighlight, mobilePushNotifications === 'all', mentionIds.includes(subscription.u._id)); + } + } + + if (notificationSent) { + // const allUserIdsToNotify = _.unique(userIdsToNotify.concat(userIdsToPushNotify)); + RocketChat.Sandstorm.notify(message, [subscription.u._id], `@${ sender.username }: ${ message.msg }`, room.t === 'p' ? 'privateMessage' : 'message'); + } + + // settings.audioNotificationValues[subscription.u._id] = subscription.audioNotificationValue; + // settings.desktopNotificationDurations[subscription.u._id] = subscription.desktopNotificationDuration; +}; + +function notifyGroups(message, room, userId) { + + // skips this callback if the message was edited + if (message.editedAt) { + return message; + } + + if (message.ts && Math.abs(moment(message.ts).diff()) > 60000) { + return message; + } + + if (room.t == null || room.t === 'd') { + return message; + } + + // const pushUsernames = {}; + + const sender = (room.t !== 'l') ? RocketChat.models.Users.findOneById(message.u._id) : room.v; + if (!sender) { + return message; + } + + /* + Increment unread couter if direct messages + */ + // const settings = { + // alwaysNotifyDesktopUsers: [], + // dontNotifyDesktopUsers: [], + + // alwaysNotifyMobileUsers: [], + // dontNotifyMobileUsers: [], + + // alwaysNotifyAudioUsers: [], + // dontNotifyAudioUsers: [], + + // desktopNotificationDurations: {}, + // audioNotificationValues: {}, + // dontNotifyUsersOnGroupMentions: [] + // }; + + // Don't fetch all users if room exceeds max members + const maxMembersForNotification = RocketChat.settings.get('Notifications_Max_Room_Members'); + const disableAllMessageNotifications = room.usernames.length > maxMembersForNotification && maxMembersForNotification !== 0; + + // console.time('findSubscriptions'); + + // @TODO maybe should also force find mentioned people + let subscriptions = []; + if (disableAllMessageNotifications) { + // @TODO get only preferences set to all (because they have precedence over `max_room_members`) + subscriptions = RocketChat.models.Subscriptions.findAllMessagesNotificationPreferencesByRoom(room._id) || []; + } else { + + // @TODO get all subscriptions + subscriptions = RocketChat.models.Subscriptions.findNotificationPreferencesByRoom(room._id) || []; + } + // console.timeEnd('findSubscriptions'); + + // const userIdsForAudio = []; + // const userIdsToNotify = []; + // const userIdsToPushNotify = []; + + const alwaysNotifyMobileBoolean = RocketChat.settings.get('Notifications_Always_Notify_Mobile'); + + const mentionIds = (message.mentions || []).map(({_id}) => _id); + const toAll = mentionIds.includes('all'); + const toHere = mentionIds.includes('here'); + + //Set variables depending on Push Notification settings + let push_message = ' '; + if (RocketChat.settings.get('Push_show_message')) { + push_message = parseMessageText(message, userId); + } + + let push_username = ''; + let push_room = ''; + if (RocketChat.settings.get('Push_show_username_room')) { + push_username = sender.username; + push_room = `#${ room.name }`; + } + + // @TODO mentions for a person that is not on the channel + + + // console.time('eachSubscriptions'); + + pushNumber = 0; + desktopNumber = 0; + audioNumber = 0; + totalSubs = 0; + subscriptions.forEach((subscription) => sendNotification({ + subscription, + sender, + toAll, + toHere, + message, + room, + mentionIds, + alwaysNotifyMobileBoolean, + push_room, + push_username, + push_message + })); + // console.timeEnd('eachSubscriptions'); + + if (room.t === 'c') { + Promise.all(message.mentions + .filter(user => !room.usernames.includes(user.username)) + .map(async(user) => { + await callJoin(user, room._id); + + return user._id; + }) + ).then((users) => { + users.forEach((userId) => { + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, userId); + + sendNotification({ + subscription, + sender, + toAll, + toHere, + message, + room, + mentionIds, + alwaysNotifyMobileBoolean, + push_room, + push_username, + push_message + }); + }); + }); + } + + console.log('pushNumber ->',pushNumber); + console.log('desktopNumber ->',desktopNumber); + console.log('audioNumber ->',audioNumber); + console.log('totalSubs ->',totalSubs); + + // console.time('sending'); + + // console.time('sendDesktopNotifications'); + // for (const usersOfMentionId of userIdsToNotify) { + // const duration = settings.desktopNotificationDurations[usersOfMentionId]; + // notifyDesktopUser(usersOfMentionId, sender, message, room, duration); + // } + // console.timeEnd('sendDesktopNotifications'); + + // console.time('sendAudioNotifications'); + // for (const usersOfMentionId of userIdsForAudio) { + // notifyAudioUser(usersOfMentionId, message, room); + // } + // console.timeEnd('sendAudioNotifications'); + + // console.time('sendPushNotifications'); + // sendPushNotifications(userIdsToPushNotify, message, room, push_room, push_username, push_message, pushUsernames); + // console.timeEnd('sendPushNotifications'); + + // console.time('sendSandstormNotifications'); + // const allUserIdsToNotify = _.unique(userIdsToNotify.concat(userIdsToPushNotify)); + // RocketChat.Sandstorm.notify(message, allUserIdsToNotify, `@${ sender.username }: ${ message.msg }`, room.t === 'p' ? 'privateMessage' : 'message'); + // console.timeEnd('sendSandstormNotifications'); + + // console.timeEnd('sending'); + + // console.log('desktop ->', userIdsToNotify.length); + // console.log('sound ->', userIdsForAudio.length); + // console.log('push ->', userIdsToPushNotify.length); + + return message; + +} + + +// RocketChat.callbacks.add('afterSaveMessage', sendNotificationOnMessage, RocketChat.callbacks.priority.LOW, 'sendNotificationOnMessage'); + +RocketChat.callbacks.add('afterSaveMessage', notifyGroups, RocketChat.callbacks.priority.LOW, 'sendNotificationGroupsOnMessage'); + diff --git a/packages/rocketchat-push-notifications/server/models/Subscriptions.js b/packages/rocketchat-push-notifications/server/models/Subscriptions.js index ce988d39a332..7ad0153d000c 100644 --- a/packages/rocketchat-push-notifications/server/models/Subscriptions.js +++ b/packages/rocketchat-push-notifications/server/models/Subscriptions.js @@ -220,3 +220,51 @@ RocketChat.models.Subscriptions.findWithSendEmailByRoomId = function(roomId) { return this.find(query, { fields: { emailNotifications: 1, u: 1 } }); }; + + +RocketChat.models.Subscriptions.findNotificationPreferencesByRoom2 = function(roomId) { + const query = { + rid: roomId, + 'u._id': {$exists: true} + }; + + return this.find(query, { + fields: { + 'u._id': 1, + audioNotifications: 1, + audioNotificationValue: 1, + desktopNotificationDuration: 1, + desktopNotifications: 1, + mobilePushNotifications: 1, + disableNotifications: 1, + muteGroupMentions: 1 + } + }); +}; + +RocketChat.models.Subscriptions.findAllMessagesNotificationPreferencesByRoom = function(roomId) { + const query = { + rid: roomId, + 'u._id': {$exists: true}, + $or: [ + {audioNotifications: {$exists: true}}, + {desktopNotifications: {$exists: true}}, + {mobilePushNotifications: {$exists: true}}, + {disableNotifications: {$exists: true}}, + {muteGroupMentions: {$exists: true}} + ] + }; + + return this.find(query, { + fields: { + 'u._id': 1, + audioNotifications: 1, + audioNotificationValue: 1, + desktopNotificationDuration: 1, + desktopNotifications: 1, + mobilePushNotifications: 1, + disableNotifications: 1, + muteGroupMentions: 1 + } + }); +}; From 06d74e29787f2408b42c1afa4dc356238d11ba48 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 30 Apr 2018 09:08:18 -0300 Subject: [PATCH 02/18] Use rawCollection for slow operations --- .../server/lib/notifyUsersOnMessage.js | 116 +++++++++++++----- .../server/lib/sendNotificationsOnMessage.js | 16 ++- .../rocketchat-lib/server/models/Rooms.js | 4 +- .../server/models/Subscriptions.js | 50 ++++++-- .../server/models/Subscriptions.js | 8 +- 5 files changed, 146 insertions(+), 48 deletions(-) diff --git a/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js b/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js index f673c56ec103..b79afbb8d4fa 100644 --- a/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js +++ b/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js @@ -2,7 +2,30 @@ import _ from 'underscore'; import s from 'underscore.string'; import moment from 'moment'; -RocketChat.callbacks.add('afterSaveMessage', function(message, room) { +/** + * Chechs if a messages contains a user highlight + * + * @param {string} message + * @param {array|undefined} highlights + * + * @returns {boolean} + */ +function messageContainsHighlight(message, highlights) { + if (! highlights || highlights.length === 0) { return false; } + + let has = false; + highlights.some(function(highlight) { + const regexp = new RegExp(s.escapeRegExp(highlight), 'i'); + if (regexp.test(message.msg)) { + has = true; + return true; + } + }); + + return has; +} + +function notifyUsersOnMessage(message, room) { // skips this callback if the message was edited and increments it if the edit was way in the past (aka imported) if (message.editedAt && Math.abs(moment(message.editedAt).diff()) > 60000) { //TODO: Review as I am not sure how else to get around this as the incrementing of the msgs count shouldn't be in this callback @@ -22,28 +45,6 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { return message; } - /** - * Chechs if a messages contains a user highlight - * - * @param {string} message - * @param {array|undefined} highlights - * - * @returns {boolean} - */ - function messageContainsHighlight(message, highlights) { - if (! highlights || highlights.length === 0) { return false; } - - let has = false; - highlights.some(function(highlight) { - const regexp = new RegExp(s.escapeRegExp(highlight), 'i'); - if (regexp.test(message.msg)) { - has = true; - return true; - } - }); - - return has; - } if (room != null) { let toAll = false; @@ -79,11 +80,17 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { const unreadCountDM = RocketChat.settings.get('Unread_Count_DM'); if (unreadCountDM === 'all_messages') { + console.time('incUnreadForRoomIdExcludingUserId'); RocketChat.models.Subscriptions.incUnreadForRoomIdExcludingUserId(room._id, message.u._id); + console.timeEnd('incUnreadForRoomIdExcludingUserId'); } else if (toAll || toHere) { + console.time('incGroupMentionsAndUnreadForRoomIdExcludingUserId1'); RocketChat.models.Subscriptions.incGroupMentionsAndUnreadForRoomIdExcludingUserId(room._id, message.u._id, 1, 1); + console.timeEnd('incGroupMentionsAndUnreadForRoomIdExcludingUserId1'); } else if ((mentionIds && mentionIds.length > 0) || (highlightsIds && highlightsIds.length > 0)) { + console.time('incUserMentionsAndUnreadForRoomIdAndUserIds'); RocketChat.models.Subscriptions.incUserMentionsAndUnreadForRoomIdAndUserIds(room._id, _.compact(_.unique(mentionIds.concat(highlightsIds))), 1, 1); + console.timeEnd('incUserMentionsAndUnreadForRoomIdAndUserIds'); } } else { const unreadCount = RocketChat.settings.get('Unread_Count'); @@ -93,26 +100,77 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { if (['all_messages', 'group_mentions_only', 'user_and_group_mentions_only'].includes(unreadCount)) { incUnread = 1; } - RocketChat.models.Subscriptions.incGroupMentionsAndUnreadForRoomIdExcludingUserId(room._id, message.u._id, 1, incUnread); + console.time('incGroupMentionsAndUnreadForRoomIdExcludingUserId2'); + RocketChat.models.Subscriptions.incGroupMentionsAndUnreadForRoomIdExcludingUserId(room._id, message.u._id, 1, incUnread) + .then(() => { + console.timeEnd('incGroupMentionsAndUnreadForRoomIdExcludingUserId2'); + }) + .catch(error => { + console.error('error ->', error); + console.timeEnd('incGroupMentionsAndUnreadForRoomIdExcludingUserId2'); + throw error; + }); + } else if ((mentionIds && mentionIds.length > 0) || (highlightsIds && highlightsIds.length > 0)) { let incUnread = 0; if (['all_messages', 'user_mentions_only', 'user_and_group_mentions_only'].includes(unreadCount)) { incUnread = 1; } - RocketChat.models.Subscriptions.incUserMentionsAndUnreadForRoomIdAndUserIds(room._id, _.compact(_.unique(mentionIds.concat(highlightsIds))), 1, incUnread); + console.time('incUserMentionsAndUnreadForRoomIdAndUserIds'); + RocketChat.models.Subscriptions.incUserMentionsAndUnreadForRoomIdAndUserIds(room._id, _.compact(_.unique(mentionIds.concat(highlightsIds))), 1, incUnread) + .then(() => { + console.timeEnd('incUserMentionsAndUnreadForRoomIdAndUserIds'); + }) + .catch(error => { + console.error('error ->', error); + console.timeEnd('incUserMentionsAndUnreadForRoomIdAndUserIds'); + throw error; + }); } else if (unreadCount === 'all_messages') { - RocketChat.models.Subscriptions.incUnreadForRoomIdExcludingUserId(room._id, message.u._id); + console.time('incUnreadForRoomIdExcludingUserId'); + RocketChat.models.Subscriptions.incUnreadForRoomIdExcludingUserId(room._id, message.u._id) + .then(() => { + console.timeEnd('incUnreadForRoomIdExcludingUserId'); + }) + .catch(error => { + console.error('error ->', error); + console.timeEnd('incUnreadForRoomIdExcludingUserId'); + throw error; + }); } } } // Update all the room activity tracker fields - RocketChat.models.Rooms.incMsgCountAndSetLastMessageById(message.rid, 1, message.ts, RocketChat.settings.get('Store_Last_Message') && message); + console.time('incMsgCountAndSetLastMessageById'); + RocketChat.models.Rooms.incMsgCountAndSetLastMessageById(message.rid, 1, message.ts, RocketChat.settings.get('Store_Last_Message') && message) + .then(() => { + console.timeEnd('incMsgCountAndSetLastMessageById'); + }) + .catch(error => { + console.error('error ->', error); + console.timeEnd('incMsgCountAndSetLastMessageById'); + throw error; + }); // Update all other subscriptions to alert their owners but witout incrementing // the unread counter, as it is only for mentions and direct messages - RocketChat.models.Subscriptions.setAlertForRoomIdExcludingUserId(message.rid, message.u._id); + console.time('setAlertForRoomIdExcludingUserId'); + Promise.all([ + RocketChat.models.Subscriptions.setAlertForRoomIdExcludingUserId(message.rid, message.u._id), + RocketChat.models.Subscriptions.setOpenForRoomIdExcludingUserId(message.rid, message.u._id) + ]) + .then(() => { + console.timeEnd('setAlertForRoomIdExcludingUserId'); + }) + .catch(error => { + console.error('error ->', error); + console.timeEnd('setAlertForRoomIdExcludingUserId'); + throw error; + }); return message; -}, RocketChat.callbacks.priority.LOW, 'notifyUsersOnMessage'); +} + +RocketChat.callbacks.add('afterSaveMessage', notifyUsersOnMessage, RocketChat.callbacks.priority.LOW, 'notifyUsersOnMessage'); diff --git a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js index 381ed206b9a1..625a0b68392e 100644 --- a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js +++ b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js @@ -526,7 +526,7 @@ const sendNotification = ({ // notifications disabled if (subscription.disableNotifications) { - console.log('return; disableNotifications'); + // console.log('return; disableNotifications'); return; } @@ -544,6 +544,11 @@ const sendNotification = ({ const receiver = RocketChat.models.Users.findOneById(subscription.u._id); + if (!receiver) { + console.log('no receiver ->', subscription.u._id); + return; + } + const hasHighlight = messageContainsHighlight(message, receiver.settings && receiver.settings.preferences && receiver.settings.preferences.highlights); const { @@ -632,7 +637,7 @@ function notifyGroups(message, room, userId) { return message; } - if (room.t == null || room.t === 'd') { + if (!room || room.t == null) { return message; } @@ -665,6 +670,10 @@ function notifyGroups(message, room, userId) { const maxMembersForNotification = RocketChat.settings.get('Notifications_Max_Room_Members'); const disableAllMessageNotifications = room.usernames.length > maxMembersForNotification && maxMembersForNotification !== 0; + console.log('room.usernames.length ->', room.usernames.length); + console.log('maxMembersForNotification ->', maxMembersForNotification); + console.log('disableAllMessageNotifications ->', disableAllMessageNotifications); + // console.time('findSubscriptions'); // @TODO maybe should also force find mentioned people @@ -673,8 +682,6 @@ function notifyGroups(message, room, userId) { // @TODO get only preferences set to all (because they have precedence over `max_room_members`) subscriptions = RocketChat.models.Subscriptions.findAllMessagesNotificationPreferencesByRoom(room._id) || []; } else { - - // @TODO get all subscriptions subscriptions = RocketChat.models.Subscriptions.findNotificationPreferencesByRoom(room._id) || []; } // console.timeEnd('findSubscriptions'); @@ -704,6 +711,7 @@ function notifyGroups(message, room, userId) { // @TODO mentions for a person that is not on the channel + console.log('count ->', subscriptions.count()); // console.time('eachSubscriptions'); diff --git a/packages/rocketchat-lib/server/models/Rooms.js b/packages/rocketchat-lib/server/models/Rooms.js index de9f17a8038f..da77f7678b0d 100644 --- a/packages/rocketchat-lib/server/models/Rooms.js +++ b/packages/rocketchat-lib/server/models/Rooms.js @@ -14,6 +14,8 @@ class ModelRooms extends RocketChat.models._Base { this.cache.ignoreUpdatedFields = ['msgs', 'lm']; this.cache.ensureIndex(['t', 'name'], 'unique'); this.cache.options = {fields: {usernames: 0}}; + + this.rawCollection = this.model.rawCollection(); } findOneByIdOrName(_idOrName, options) { @@ -562,7 +564,7 @@ class ModelRooms extends RocketChat.models._Base { update.$set.lastMessage = lastMessage; } - return this.update(query, update); + return this.rawCollection.update(query, this.setUpdatedAt(update)); } setLastMessageById(_id, lastMessage) { diff --git a/packages/rocketchat-lib/server/models/Subscriptions.js b/packages/rocketchat-lib/server/models/Subscriptions.js index 116751db301d..df69f583ae5a 100644 --- a/packages/rocketchat-lib/server/models/Subscriptions.js +++ b/packages/rocketchat-lib/server/models/Subscriptions.js @@ -11,7 +11,22 @@ class ModelSubscriptions extends RocketChat.models._Base { this.tryEnsureIndex({ 'u._id': 1, 'name': 1, 't': 1, 'code': 1 }, { unique: 1 }); this.tryEnsureIndex({ 'open': 1 }); this.tryEnsureIndex({ 'alert': 1 }); - this.tryEnsureIndex({ 'unread': 1 }); + + // @TODO evalute find by this property if we can remove it + // this.tryEnsureIndex({ 'unread': 1 }); + + this.tryEnsureIndex({ + rid: 1, + 'u._id': 1, + alert: 1 + }); + + this.tryEnsureIndex({ + rid: 1, + 'u._id': 1, + open: 1 + }); + this.tryEnsureIndex({ 'ts': 1 }); this.tryEnsureIndex({ 'ls': 1 }); this.tryEnsureIndex({ 'audioNotifications': 1 }, { sparse: 1 }); @@ -26,6 +41,8 @@ class ModelSubscriptions extends RocketChat.models._Base { this.cache.ensureIndex('name', 'array'); this.cache.ensureIndex(['rid', 'u._id'], 'unique'); this.cache.ensureIndex(['name', 'u._id'], 'unique'); + + this.rawCollection = this.model.rawCollection(); } @@ -407,7 +424,7 @@ class ModelSubscriptions extends RocketChat.models._Base { } }; - return this.update(query, update, { multi: true }); + return this.rawCollection.update(query, this.setUpdatedAt(update), { multi: true }); } incGroupMentionsAndUnreadForRoomIdExcludingUserId(roomId, userId, incGroup = 1, incUnread = 1) { @@ -429,7 +446,7 @@ class ModelSubscriptions extends RocketChat.models._Base { } }; - return this.update(query, update, { multi: true }); + return this.rawCollection.update(query, this.setUpdatedAt(update), { multi: true }); } incUserMentionsAndUnreadForRoomIdAndUserIds(roomId, userIds, incUser = 1, incUnread = 1) { @@ -451,7 +468,7 @@ class ModelSubscriptions extends RocketChat.models._Base { } }; - return this.update(query, update, { multi: true }); + return this.rawCollection.update(query, this.setUpdatedAt(update), { multi: true }); } ignoreUser({_id, ignoredUser : ignored, ignore = true}) { @@ -475,19 +492,32 @@ class ModelSubscriptions extends RocketChat.models._Base { 'u._id': { $ne: userId }, - $or: [ - { alert: { $ne: true } }, - { open: { $ne: true } } - ] + alert: { $ne: true } + }; + + const update = { + $set: { + alert: true + } + }; + return this.rawCollection.update(query, this.setUpdatedAt(update), { multi: true }); + } + + setOpenForRoomIdExcludingUserId(roomId, userId) { + const query = { + rid: roomId, + 'u._id': { + $ne: userId + }, + open: { $ne: true } }; const update = { $set: { - alert: true, open: true } }; - return this.update(query, update, { multi: true }); + return this.rawCollection.update(query, this.setUpdatedAt(update), { multi: true }); } setBlockedByRoomId(rid, blocked, blocker) { diff --git a/packages/rocketchat-push-notifications/server/models/Subscriptions.js b/packages/rocketchat-push-notifications/server/models/Subscriptions.js index 7ad0153d000c..95bb83936117 100644 --- a/packages/rocketchat-push-notifications/server/models/Subscriptions.js +++ b/packages/rocketchat-push-notifications/server/models/Subscriptions.js @@ -189,7 +189,7 @@ RocketChat.models.Subscriptions.findDontNotifyMobileUsersByRoomId = function(roo return this.find(query); }; -RocketChat.models.Subscriptions.findNotificationPreferencesByRoom = function(roomId, explicit) { +RocketChat.models.Subscriptions.findNotificationPreferencesByRoomOld = function(roomId, explicit) { const query = { rid: roomId, 'u._id': {$exists: true} @@ -222,13 +222,13 @@ RocketChat.models.Subscriptions.findWithSendEmailByRoomId = function(roomId) { }; -RocketChat.models.Subscriptions.findNotificationPreferencesByRoom2 = function(roomId) { +RocketChat.models.Subscriptions.findNotificationPreferencesByRoom = function(roomId) { const query = { rid: roomId, 'u._id': {$exists: true} }; - return this.find(query, { + return this._db.find(query, { fields: { 'u._id': 1, audioNotifications: 1, @@ -255,7 +255,7 @@ RocketChat.models.Subscriptions.findAllMessagesNotificationPreferencesByRoom = f ] }; - return this.find(query, { + return this._db.find(query, { fields: { 'u._id': 1, audioNotifications: 1, From bde1ee27711886e6683be3ce390a63b381c4e111 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 30 Apr 2018 21:34:40 -0300 Subject: [PATCH 03/18] Denormalize user notification preferences --- .../server/lib/sendNotificationsOnMessage.js | 7 +++- .../server/models/Subscriptions.js | 36 +++++++++++++++++++ .../server/models/Subscriptions.js | 6 +++- server/methods/saveUserPreferences.js | 18 ++++++++-- 4 files changed, 63 insertions(+), 4 deletions(-) diff --git a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js index 625a0b68392e..e113dab48de1 100644 --- a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js +++ b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js @@ -676,6 +676,11 @@ function notifyGroups(message, room, userId) { // console.time('findSubscriptions'); + // @TODO we should change the find based on default server preferences Mobile_Notifications_Default_Alert and Desktop_Notifications_Default_Alert + // if default is 'all' -> exclude only 'nothing' + // if default is 'mentions' -> idk + // if default is 'nothing' -> idk + // @TODO maybe should also force find mentioned people let subscriptions = []; if (disableAllMessageNotifications) { @@ -736,7 +741,7 @@ function notifyGroups(message, room, userId) { if (room.t === 'c') { Promise.all(message.mentions - .filter(user => !room.usernames.includes(user.username)) + .filter(({ _id, username }) => _id !== 'here' && _id !== 'all' && !room.usernames.includes(username)) .map(async(user) => { await callJoin(user, room._id); diff --git a/packages/rocketchat-lib/server/models/Subscriptions.js b/packages/rocketchat-lib/server/models/Subscriptions.js index df69f583ae5a..7950ac5d1b13 100644 --- a/packages/rocketchat-lib/server/models/Subscriptions.js +++ b/packages/rocketchat-lib/server/models/Subscriptions.js @@ -626,6 +626,42 @@ class ModelSubscriptions extends RocketChat.models._Base { return this.update(query, update, { multi: true }); } + updateDesktopNotificationUserPreferences(userId, desktopNotifications) { + const query = { + 'u._id': userId, + desktopPrefOrigin: { + $ne: 'subscription' + } + }; + + const update = { + $set: { + desktopNotifications, + desktopPrefOrigin: 'user' + } + }; + + return this.update(query, update, { multi: true }); + } + + updateMobileNotificationUserPreferences(userId, mobilePushNotifications) { + const query = { + 'u._id': userId, + desktopPrefOrigin: { + $ne: 'subscription' + } + }; + + const update = { + $set: { + mobilePushNotifications, + mobilePrefOrigin: 'user' + } + }; + + return this.update(query, update, { multi: true }); + } + // INSERT createWithRoomAndUser(room, user, extraData) { const subscription = { diff --git a/packages/rocketchat-push-notifications/server/models/Subscriptions.js b/packages/rocketchat-push-notifications/server/models/Subscriptions.js index 95bb83936117..180506acd478 100644 --- a/packages/rocketchat-push-notifications/server/models/Subscriptions.js +++ b/packages/rocketchat-push-notifications/server/models/Subscriptions.js @@ -225,7 +225,9 @@ RocketChat.models.Subscriptions.findWithSendEmailByRoomId = function(roomId) { RocketChat.models.Subscriptions.findNotificationPreferencesByRoom = function(roomId) { const query = { rid: roomId, - 'u._id': {$exists: true} + 'u._id': {$exists: true}, + desktopNotifications: { $ne: 'nothing' }, + mobilePushNotifications: { $ne: 'nothing' } }; return this._db.find(query, { @@ -246,6 +248,8 @@ RocketChat.models.Subscriptions.findAllMessagesNotificationPreferencesByRoom = f const query = { rid: roomId, 'u._id': {$exists: true}, + desktopNotifications: { $ne: 'nothing' }, + mobilePushNotifications: { $ne: 'nothing' }, $or: [ {audioNotifications: {$exists: true}}, {desktopNotifications: {$exists: true}}, diff --git a/server/methods/saveUserPreferences.js b/server/methods/saveUserPreferences.js index a694c51dbb12..96f2248bd5af 100644 --- a/server/methods/saveUserPreferences.js +++ b/server/methods/saveUserPreferences.js @@ -45,6 +45,11 @@ Meteor.methods({ return false; } + const { + desktopNotifications: oldDesktopNotifications, + mobileNotifications: oldMobileNotifications + } = user.settings || {}; + if (user.settings == null) { RocketChat.models.Users.clearSettings(user._id); } @@ -57,14 +62,23 @@ Meteor.methods({ settings.mergeChannels = ['1', true].includes(settings.mergeChannels); } - - if (settings.roomsListExhibitionMode != null) { settings.roomsListExhibitionMode = ['category', 'unread', 'activity'].includes(settings.roomsListExhibitionMode) ? settings.roomsListExhibitionMode : 'category'; } RocketChat.models.Users.setPreferences(user._id, settings); + // propagate changed notification preferences + Meteor.defer(() => { + if (oldDesktopNotifications !== settings.desktopNotifications) { + RocketChat.models.Subscriptions.updateDesktopNotificationUserPreferences(user._id, settings.desktopNotifications); + } + + if (oldMobileNotifications !== settings.mobileNotifications) { + RocketChat.models.Subscriptions.updateMobileNotificationUserPreferences(user._id, settings.mobileNotifications); + } + }); + return true; } }); From dae18bf7e2e5b87875823684303fdb8fdb4dc359 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 2 May 2018 10:39:00 -0300 Subject: [PATCH 04/18] Split notification functions into multiple files --- .../server/functions/notifications/audio.js | 23 ++ .../server/functions/notifications/desktop.js | 68 ++++ .../server/functions/notifications/email.js | 151 +++++++ .../server/functions/notifications/index.js | 48 +++ .../server/functions/notifications/mobile.js | 72 ++++ .../rocketchat-lib/server/lib/roomTypes.js | 3 + .../server/lib/sendEmailOnMessage.js | 380 +++++------------- .../server/lib/sendNotificationsOnMessage.js | 302 ++++---------- .../server/models/Subscriptions.js | 19 +- 9 files changed, 562 insertions(+), 504 deletions(-) create mode 100644 packages/rocketchat-lib/server/functions/notifications/audio.js create mode 100644 packages/rocketchat-lib/server/functions/notifications/desktop.js create mode 100644 packages/rocketchat-lib/server/functions/notifications/email.js create mode 100644 packages/rocketchat-lib/server/functions/notifications/index.js create mode 100644 packages/rocketchat-lib/server/functions/notifications/mobile.js diff --git a/packages/rocketchat-lib/server/functions/notifications/audio.js b/packages/rocketchat-lib/server/functions/notifications/audio.js new file mode 100644 index 000000000000..01883df02975 --- /dev/null +++ b/packages/rocketchat-lib/server/functions/notifications/audio.js @@ -0,0 +1,23 @@ +export function shouldNotifyAudio({ disableAllMessageNotifications, status, audioNotifications, toAll, toHere, isHighlighted, isMentioned}) { + if (disableAllMessageNotifications && audioNotifications == null) { + return false; + } + + if (status === 'busy' || audioNotifications === 'nothing') { + return false; + } + + return toAll || toHere || isHighlighted || audioNotifications === 'all' || isMentioned; +} + +export function notifyAudioUser(userId, message, room) { + RocketChat.Notifications.notifyUser(userId, 'audioNotification', { + payload: { + _id: message._id, + rid: message.rid, + sender: message.u, + type: room.t, + name: room.name + } + }); +} diff --git a/packages/rocketchat-lib/server/functions/notifications/desktop.js b/packages/rocketchat-lib/server/functions/notifications/desktop.js new file mode 100644 index 000000000000..e0a55700fd70 --- /dev/null +++ b/packages/rocketchat-lib/server/functions/notifications/desktop.js @@ -0,0 +1,68 @@ +import { parseMessageText } from './index'; + +/** + * Replaces @username with full name + * + * @param {string} message The message to replace + * @param {object[]} mentions Array of mentions used to make replacements + * + * @returns {string} + */ +function replaceMentionedUsernamesWithFullNames(message, mentions) { + if (!mentions || !mentions.length) { + return message; + } + mentions.forEach((mention) => { + const user = RocketChat.models.Users.findOneById(mention._id); + if (user && user.name) { + message = message.replace(`@${ mention.username }`, user.name); + } + }); + return message; +} + +/** + * Send notification to user + * + * @param {string} userId The user to notify + * @param {object} user The sender + * @param {object} room The room send from + * @param {number} duration Duration of notification + */ +export function notifyDesktopUser(userId, user, message, room, duration) { + + const UI_Use_Real_Name = RocketChat.settings.get('UI_Use_Real_Name') === true; + message.msg = parseMessageText(message, userId); + + if (UI_Use_Real_Name) { + message.msg = replaceMentionedUsernamesWithFullNames(message.msg, message.mentions); + } + let title = UI_Use_Real_Name ? user.name : `@${ user.username }`; + if (room.t !== 'd' && room.name) { + title += ` @ #${ room.name }`; + } + RocketChat.Notifications.notifyUser(userId, 'notification', { + title, + text: message.msg, + duration, + payload: { + _id: message._id, + rid: message.rid, + sender: message.u, + type: room.t, + name: room.name + } + }); +} + +export function shouldNotifyDesktop({ disableAllMessageNotifications, status, desktopNotifications, toAll, toHere, isHighlighted, isMentioned}) { + if (disableAllMessageNotifications && desktopNotifications == null) { + return false; + } + + if (status === 'busy' || desktopNotifications === 'nothing') { + return false; + } + + return toAll || toHere || isHighlighted || desktopNotifications === 'all' || isMentioned; +} diff --git a/packages/rocketchat-lib/server/functions/notifications/email.js b/packages/rocketchat-lib/server/functions/notifications/email.js new file mode 100644 index 000000000000..b23014edae73 --- /dev/null +++ b/packages/rocketchat-lib/server/functions/notifications/email.js @@ -0,0 +1,151 @@ +import s from 'underscore.string'; + +const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || ''); +let footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || ''); +const divisorMessage = '
'; + +function getEmailContent({ message, user, room }) { + const lng = user && user.language || RocketChat.settings.get('language') || 'en'; + + const roomName = `#${ RocketChat.settings.get('UI_Allow_room_names_with_special_chars') ? room.fname || room.name : room.name }`; + const userName = RocketChat.settings.get('UI_Use_Real_Name') ? message.u.name || message.u.username : message.u.username; + + const header = TAPi18n.__(room.t === 'd' ? 'User_sent_a_message_to_you' : 'User_sent_a_message_on_channel', { + username: userName, + channel: roomName, + lng + }); + + let messageContent; + if (message.msg !== '') { + messageContent = s.escapeHTML(message.msg); + message = RocketChat.callbacks.run('renderMessage', message); + if (message.tokens && message.tokens.length > 0) { + message.tokens.forEach((token) => { + token.text = token.text.replace(/([^\$])(\$[^\$])/gm, '$1$$$2'); + messageContent = messageContent.replace(token.token, token.text); + }); + } + messageContent = messageContent.replace(/\n/gm, '
'); + } + + if (messageContent) { + return `${ header }

${ messageContent }`; + } + + if (message.file) { + const fileHeader = TAPi18n.__(room.t === 'd' ? 'User_uploaded_a_file_to_you' : 'User_uploaded_a_file_on_channel', { + username: userName, + channel: roomName, + lng + }); + + let content = `${ TAPi18n.__('Attachment_File_Uploaded') }: ${ message.file.name }`; + + if (message.attachments && message.attachments.length === 1 && message.attachments[0].description !== '') { + content += `

${ message.attachments[0].description }`; + } + + return `${ fileHeader }

${ content }`; + } + + if (message.attachments.length > 0) { + const [ attachment ] = message.attachments; + + let content = ''; + + if (attachment.title) { + content += `${ attachment.title }
`; + } + if (attachment.text) { + content += `${ attachment.text }
`; + } + + return `${ header }

${ content }`; + } + + return header; +} + +function getMessageLink(room, sub) { + const roomPath = RocketChat.roomTypes.getRouteLink(room.t, sub); + const path = Meteor.absoluteUrl(roomPath ? roomPath.replace(/^\//, '') : ''); + const style = [ + 'color: #fff;', + 'padding: 9px 12px;', + 'border-radius: 4px;', + 'background-color: #04436a;', + 'text-decoration: none;' + ].join(' '); + const message = TAPi18n.__('Offline_Link_Message'); + return `

${ message }`; +} + +export function sendEmail({ message, user, subscription, room, emailAddress, toAll }) { + let emailSubject; + const username = RocketChat.settings.get('UI_Use_Real_Name') ? message.u.name : message.u.username; + + if (room.t === 'd') { + emailSubject = RocketChat.placeholders.replace(RocketChat.settings.get('Offline_DM_Email'), { + user: username, + room: RocketChat.roomTypes.getRoomName(room.t, room) + }); + } else if (toAll) { + emailSubject = RocketChat.placeholders.replace(RocketChat.settings.get('Offline_Mention_All_Email'), { + user: username, + room: RocketChat.roomTypes.getRoomName(room.t, room) + }); + } else { + emailSubject = RocketChat.placeholders.replace(RocketChat.settings.get('Offline_Mention_Email'), { + user: username, + room: RocketChat.roomTypes.getRoomName(room.t, room) + }); + } + const content = getEmailContent({ + message, + user, + room + }); + + const link = getMessageLink(room, subscription); + + if (RocketChat.settings.get('Direct_Reply_Enable')) { + footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer_Direct_Reply') || ''); + } + + const email = { + to: emailAddress, + subject: emailSubject, + html: header + content + divisorMessage + link + footer + }; + + // using user full-name/channel name in from address + if (room.t === 'd') { + email.from = `${ message.u.name } <${ RocketChat.settings.get('From_Email') }>`; + } else { + email.from = `${ room.name } <${ RocketChat.settings.get('From_Email') }>`; + } + // If direct reply enabled, email content with headers + if (RocketChat.settings.get('Direct_Reply_Enable')) { + email.headers = { + // Reply-To header with format "username+messageId@domain" + 'Reply-To': `${ RocketChat.settings.get('Direct_Reply_Username').split('@')[0].split(RocketChat.settings.get('Direct_Reply_Separator'))[0] }${ RocketChat.settings.get('Direct_Reply_Separator') }${ message._id }@${ RocketChat.settings.get('Direct_Reply_Username').split('@')[1] }` + }; + } + + Meteor.defer(() => { + Email.send(email); + }); +} + +export function shouldNotifyEmail({ disableAllMessageNotifications, statusConnection, emailNotifications, isHighlighted, isMentioned }) { + if (disableAllMessageNotifications && emailNotifications == null) { + return false; + } + + if (statusConnection === 'online') { + return false; + } + + return isHighlighted || emailNotifications === 'all' || isMentioned; +} diff --git a/packages/rocketchat-lib/server/functions/notifications/index.js b/packages/rocketchat-lib/server/functions/notifications/index.js new file mode 100644 index 000000000000..66b4b7fb1d79 --- /dev/null +++ b/packages/rocketchat-lib/server/functions/notifications/index.js @@ -0,0 +1,48 @@ +import s from 'underscore.string'; + +/** +* This function returns a string ready to be shown in the notification +* +* @param {object} message the message to be parsed +*/ +export function parseMessageText(message, userId) { + const user = RocketChat.models.Users.findOneById(userId); + const lng = user && user.language || RocketChat.settings.get('language') || 'en'; + + if (!message.msg && message.attachments && message.attachments[0]) { + message.msg = message.attachments[0].image_type ? TAPi18n.__('User_uploaded_image', {lng}) : TAPi18n.__('User_uploaded_file', {lng}); + } + message.msg = RocketChat.callbacks.run('beforeNotifyUser', message.msg); + + return message.msg; +} + +/** + * Checks if a message contains a user highlight + * + * @param {string} message + * @param {array|undefined} highlights + * + * @returns {boolean} + */ +export function messageContainsHighlight(message, highlights) { + if (! highlights || highlights.length === 0) { return false; } + + return highlights.some(function(highlight) { + const regexp = new RegExp(s.escapeRegExp(highlight), 'i'); + if (regexp.test(message.msg)) { + return true; + } + }); +} + +export function callJoinRoom(user, rid) { + return new Promise((resolve, reject) => { + Meteor.runAsUser(user._id, () => Meteor.call('joinRoom', rid, (error, result) => { + if (error) { + return reject(error); + } + return resolve(result); + })); + }); +} diff --git a/packages/rocketchat-lib/server/functions/notifications/mobile.js b/packages/rocketchat-lib/server/functions/notifications/mobile.js new file mode 100644 index 000000000000..fe47bf424123 --- /dev/null +++ b/packages/rocketchat-lib/server/functions/notifications/mobile.js @@ -0,0 +1,72 @@ + +const CATEGORY_MESSAGE = 'MESSAGE'; +const CATEGORY_MESSAGE_NOREPLY = 'MESSAGE_NOREPLY'; + +// function getBadgeCount(userId) { +// const subscriptions = RocketChat.models.Subscriptions.findUnreadByUserId(userId).fetch(); + +// return subscriptions.reduce((unread, sub) => { +// return sub.unread + unread; +// }, 0); +// } + +function canSendMessageToRoom(room, username) { + return !((room.muted || []).includes(username)); +} + +// const sendPushNotifications = (userIdsToPushNotify = [], message, room, push_room, push_username, push_message, pushUsernames) => { +// if (userIdsToPushNotify.length > 0 && Push.enabled === true) { +// // send a push notification for each user individually (to get his/her badge count) +// userIdsToPushNotify.forEach((userIdToNotify) => { +// RocketChat.PushNotification.send({ +// roomId: message.rid, +// roomName: push_room, +// username: push_username, +// message: push_message, +// badge: getBadgeCount(userIdToNotify), +// payload: { +// host: Meteor.absoluteUrl(), +// rid: message.rid, +// sender: message.u, +// type: room.t, +// name: room.name +// }, +// usersTo: { +// userId: userIdToNotify +// }, +// category: canSendMessageToRoom(room, pushUsernames[userIdToNotify]) ? CATEGORY_MESSAGE : CATEGORY_MESSAGE_NOREPLY +// }); +// }); +// } +// }; + +export function sendSinglePush({ room, roomId, roomName, username, message, payload, userId, receiverUsername}) { + RocketChat.PushNotification.send({ + roomId, + roomName, + username, + message, + // badge: getBadgeCount(userIdToNotify), + payload, + usersTo: { + userId + }, + category: canSendMessageToRoom(room, receiverUsername) ? CATEGORY_MESSAGE : CATEGORY_MESSAGE_NOREPLY + }); +} + +export function shouldNotifyMobile({ disableAllMessageNotifications, mobilePushNotifications, toAll, isHighlighted, isMentioned, alwaysNotifyMobileBoolean, statusConnection }) { + if (disableAllMessageNotifications && mobilePushNotifications == null) { + return false; + } + + if (mobilePushNotifications === 'nothing') { + return false; + } + + if (!alwaysNotifyMobileBoolean && statusConnection === 'online') { + return false; + } + + return toAll || isHighlighted || mobilePushNotifications === 'all' || isMentioned; +} diff --git a/packages/rocketchat-lib/server/lib/roomTypes.js b/packages/rocketchat-lib/server/lib/roomTypes.js index 68aec9b5ef6f..e6ee83c8bf70 100644 --- a/packages/rocketchat-lib/server/lib/roomTypes.js +++ b/packages/rocketchat-lib/server/lib/roomTypes.js @@ -31,6 +31,9 @@ RocketChat.roomTypes = new class roomTypesServer extends RoomTypesCommon { return this.roomTypes[roomType] && this.roomTypes[roomType].roomFind; } + getRoomName(roomType, roomData) { + return this.roomTypes[roomType] && this.roomTypes[roomType].roomName && this.roomTypes[roomType].roomName(roomData); + } /** * Run the publish for a room type diff --git a/packages/rocketchat-lib/server/lib/sendEmailOnMessage.js b/packages/rocketchat-lib/server/lib/sendEmailOnMessage.js index c4351cc46c7c..156215a80615 100644 --- a/packages/rocketchat-lib/server/lib/sendEmailOnMessage.js +++ b/packages/rocketchat-lib/server/lib/sendEmailOnMessage.js @@ -1,269 +1,111 @@ -import moment from 'moment'; -import s from 'underscore.string'; - -function getEmailContent({ messageContent, message, user, room }) { - const lng = user && user.language || RocketChat.settings.get('language') || 'en'; - - const roomName = `#${ RocketChat.settings.get('UI_Allow_room_names_with_special_chars') ? room.fname || room.name : room.name }`; - - const userName = RocketChat.settings.get('UI_Use_Real_Name') ? message.u.name || message.u.username : message.u.username; - - const header = TAPi18n.__(room.t === 'd' ? 'User_sent_a_message_to_you' : 'User_sent_a_message_on_channel', { - username: userName, - channel: roomName, - lng - }); - - if (messageContent) { - return `${ header }

${ messageContent }`; - } - - if (message.file) { - const fileHeader = TAPi18n.__(room.t === 'd' ? 'User_uploaded_a_file_to_you' : 'User_uploaded_a_file_on_channel', { - username: userName, - channel: roomName, - lng - }); - - let content = `${ TAPi18n.__('Attachment_File_Uploaded') }: ${ message.file.name }`; - - if (message.attachments && message.attachments.length === 1 && message.attachments[0].description !== '') { - content += `

${ message.attachments[0].description }`; - } - - return `${ fileHeader }

${ content }`; - } - - if (message.attachments.length > 0) { - const [ attachment ] = message.attachments; - - let content = ''; - - if (attachment.title) { - content += `${ attachment.title }
`; - } - if (attachment.text) { - content += `${ attachment.text }
`; - } - - return `${ header }

${ content }`; - } - - return header; -} - -RocketChat.callbacks.add('afterSaveMessage', function(message, room) { - // skips this callback if the message was edited - if (message.editedAt) { - return message; - } - - if (message.ts && Math.abs(moment(message.ts).diff()) > 60000) { - return message; - } - - const getMessageLink = (room, sub) => { - const roomPath = RocketChat.roomTypes.getRouteLink(room.t, sub); - const path = Meteor.absoluteUrl(roomPath ? roomPath.replace(/^\//, '') : ''); - const style = [ - 'color: #fff;', - 'padding: 9px 12px;', - 'border-radius: 4px;', - 'background-color: #04436a;', - 'text-decoration: none;' - ].join(' '); - const message = TAPi18n.__('Offline_Link_Message'); - return `

${ message }`; - }; - - const divisorMessage = '


'; - - let messageHTML; - - if (message.msg !== '') { - messageHTML = s.escapeHTML(message.msg); - message = RocketChat.callbacks.run('renderMessage', message); - if (message.tokens && message.tokens.length > 0) { - message.tokens.forEach((token) => { - token.text = token.text.replace(/([^\$])(\$[^\$])/gm, '$1$$$2'); - messageHTML = messageHTML.replace(token.token, token.text); - }); - } - messageHTML = messageHTML.replace(/\n/gm, '
'); - } - - const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || ''); - let footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || ''); - - const usersToSendEmail = {}; - if (room.t === 'd') { - usersToSendEmail[message.rid.replace(message.u._id, '')] = 'direct'; - } else { - let isMentionAll = message.mentions.find(mention => mention._id === 'all'); - - if (isMentionAll) { - const maxMembersForNotification = RocketChat.settings.get('Notifications_Max_Room_Members'); - if (maxMembersForNotification !== 0 && room.usernames.length > maxMembersForNotification) { - isMentionAll = undefined; - } - } - - let query; - if (isMentionAll) { - // Query all users in room limited by the max room members setting - query = RocketChat.models.Subscriptions.findByRoomId(room._id); - } else { - // Query only mentioned users, will be always a few users - const userIds = message.mentions.map(mention => mention._id); - query = RocketChat.models.Subscriptions.findByRoomIdAndUserIdsOrAllMessages(room._id, userIds); - } - - query.forEach(sub => { - if (sub.disableNotifications) { - return delete usersToSendEmail[sub.u._id]; - } - - const { emailNotifications, muteGroupMentions } = sub; - - if (emailNotifications === 'nothing') { - return delete usersToSendEmail[sub.u._id]; - } - - if (isMentionAll && muteGroupMentions) { - return delete usersToSendEmail[sub.u._id]; - } - - const mentionedUser = isMentionAll || message.mentions.find(mention => mention._id === sub.u._id); - - if (emailNotifications === 'default' || emailNotifications == null) { - if (mentionedUser) { - return usersToSendEmail[sub.u._id] = 'default'; - } - return delete usersToSendEmail[sub.u._id]; - } - - if (emailNotifications === 'mentions' && mentionedUser) { - return usersToSendEmail[sub.u._id] = 'mention'; - } - - if (emailNotifications === 'all') { - return usersToSendEmail[sub.u._id] = 'all'; - } - }); - } - const userIdsToSendEmail = Object.keys(usersToSendEmail); - - let defaultLink; - - const linkByUser = {}; - if (RocketChat.roomTypes.hasCustomLink(room.t)) { - RocketChat.models.Subscriptions.findByRoomIdAndUserIds(room._id, userIdsToSendEmail).forEach((sub) => { - linkByUser[sub.u._id] = getMessageLink(room, sub); - }); - } else { - defaultLink = getMessageLink(room, { - name: room.name - }); - } - - if (userIdsToSendEmail.length > 0) { - const usersOfMention = RocketChat.models.Users.getUsersToSendOfflineEmail(userIdsToSendEmail).fetch(); - - if (usersOfMention && usersOfMention.length > 0) { - usersOfMention.forEach((user) => { - const emailNotificationMode = RocketChat.getUserPreference(user, 'emailNotificationMode'); - if (usersToSendEmail[user._id] === 'default') { - if (emailNotificationMode === 'all') { //Mention/DM - usersToSendEmail[user._id] = 'mention'; - } else { - return; - } - } - - if (usersToSendEmail[user._id] === 'direct') { - const userEmailPreferenceIsDisabled = emailNotificationMode === 'disabled'; - const directMessageEmailPreference = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(message.rid, message.rid.replace(message.u._id, '')).emailNotifications; - - if (directMessageEmailPreference === 'nothing') { - return; - } - - if ((directMessageEmailPreference === 'default' || directMessageEmailPreference == null) && userEmailPreferenceIsDisabled) { - return; - } - } - - // Checks if user is in the room he/she is mentioned (unless it's public channel) - if (room.t !== 'c' && room.usernames.indexOf(user.username) === -1) { - return; - } - - // Footer in case direct reply is enabled. - if (RocketChat.settings.get('Direct_Reply_Enable')) { - footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer_Direct_Reply') || ''); - } - - let emailSubject; - const username = RocketChat.settings.get('UI_Use_Real_Name') ? message.u.name : message.u.username; - const roomName = RocketChat.settings.get('UI_Allow_room_names_with_special_chars') ? room.fname : room.name; - switch (usersToSendEmail[user._id]) { - case 'all': - emailSubject = RocketChat.placeholders.replace(RocketChat.settings.get('Offline_Mention_All_Email'), { - user: username, - room: roomName || room.label - }); - break; - case 'direct': - emailSubject = RocketChat.placeholders.replace(RocketChat.settings.get('Offline_DM_Email'), { - user: username, - room: roomName - }); - break; - case 'mention': - emailSubject = RocketChat.placeholders.replace(RocketChat.settings.get('Offline_Mention_Email'), { - user: username, - room: roomName - }); - break; - } - user.emails.some((email) => { - if (email.verified) { - const content = getEmailContent({ - messageContent: messageHTML, - message, - user, - room - }); - email = { - to: email.address, - subject: emailSubject, - html: header + content + divisorMessage + (linkByUser[user._id] || defaultLink) + footer - }; - // using user full-name/channel name in from address - if (room.t === 'd') { - email.from = `${ message.u.name } <${ RocketChat.settings.get('From_Email') }>`; - } else { - email.from = `${ room.name } <${ RocketChat.settings.get('From_Email') }>`; - } - // If direct reply enabled, email content with headers - if (RocketChat.settings.get('Direct_Reply_Enable')) { - email.headers = { - // Reply-To header with format "username+messageId@domain" - 'Reply-To': `${ RocketChat.settings.get('Direct_Reply_Username').split('@')[0].split(RocketChat.settings.get('Direct_Reply_Separator'))[0] }${ RocketChat.settings.get('Direct_Reply_Separator') }${ message._id }@${ RocketChat.settings.get('Direct_Reply_Username').split('@')[1] }` - }; - } - - Meteor.defer(() => { - Email.send(email); - }); - - return true; - } - }); - }); - } - } - - return message; - -}, RocketChat.callbacks.priority.LOW, 'sendEmailOnMessage'); +// import moment from 'moment'; +// import s from 'underscore.string'; + +// RocketChat.callbacks.add('afterSaveMessage', function(message, room) { +// // skips this callback if the message was edited +// if (message.editedAt) { +// return message; +// } + +// if (message.ts && Math.abs(moment(message.ts).diff()) > 60000) { +// return message; +// } + +// const usersToSendEmail = {}; +// if (room.t === 'd') { +// usersToSendEmail[message.rid.replace(message.u._id, '')] = 'direct'; +// } else { +// let isMentionAll = message.mentions.find(mention => mention._id === 'all'); + +// if (isMentionAll) { +// const maxMembersForNotification = RocketChat.settings.get('Notifications_Max_Room_Members'); +// if (maxMembersForNotification !== 0 && room.usernames.length > maxMembersForNotification) { +// isMentionAll = undefined; +// } +// } + +// let query; +// if (isMentionAll) { +// // Query all users in room limited by the max room members setting +// query = RocketChat.models.Subscriptions.findByRoomId(room._id); +// } else { +// // Query only mentioned users, will be always a few users +// const userIds = message.mentions.map(mention => mention._id); +// query = RocketChat.models.Subscriptions.findByRoomIdAndUserIdsOrAllMessages(room._id, userIds); +// } + +// query.forEach(sub => { +// if (sub.disableNotifications) { +// return delete usersToSendEmail[sub.u._id]; +// } + +// const { emailNotifications, muteGroupMentions } = sub; + +// if (emailNotifications === 'nothing') { +// return delete usersToSendEmail[sub.u._id]; +// } + +// if (isMentionAll && muteGroupMentions) { +// return delete usersToSendEmail[sub.u._id]; +// } + +// const mentionedUser = isMentionAll || message.mentions.find(mention => mention._id === sub.u._id); + +// if (emailNotifications === 'default' || emailNotifications == null) { +// if (mentionedUser) { +// return usersToSendEmail[sub.u._id] = 'default'; +// } +// return delete usersToSendEmail[sub.u._id]; +// } + +// if (emailNotifications === 'mentions' && mentionedUser) { +// return usersToSendEmail[sub.u._id] = 'mention'; +// } + +// if (emailNotifications === 'all') { +// return usersToSendEmail[sub.u._id] = 'all'; +// } +// }); +// } +// const userIdsToSendEmail = Object.keys(usersToSendEmail); + +// if (userIdsToSendEmail.length > 0) { +// const usersOfMention = RocketChat.models.Users.getUsersToSendOfflineEmail(userIdsToSendEmail).fetch(); + +// if (usersOfMention && usersOfMention.length > 0) { +// usersOfMention.forEach((user) => { +// const emailNotificationMode = RocketChat.getUserPreference(user, 'emailNotificationMode'); +// if (usersToSendEmail[user._id] === 'default') { +// if (emailNotificationMode === 'all') { //Mention/DM +// usersToSendEmail[user._id] = 'mention'; +// } else { +// return; +// } +// } + +// if (usersToSendEmail[user._id] === 'direct') { +// const userEmailPreferenceIsDisabled = emailNotificationMode === 'disabled'; +// const directMessageEmailPreference = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(message.rid, message.rid.replace(message.u._id, '')).emailNotifications; + +// if (directMessageEmailPreference === 'nothing') { +// return; +// } + +// if ((directMessageEmailPreference === 'default' || directMessageEmailPreference == null) && userEmailPreferenceIsDisabled) { +// return; +// } +// } + +// // Checks if user is in the room he/she is mentioned (unless it's public channel) +// if (room.t !== 'c' && room.usernames.indexOf(user.username) === -1) { +// return; +// } + + +// }); +// } +// } + +// return message; + +// }, RocketChat.callbacks.priority.LOW, 'sendEmailOnMessage'); diff --git a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js index e113dab48de1..3771bbfd9f44 100644 --- a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js +++ b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js @@ -1,174 +1,12 @@ /* globals Push */ import _ from 'underscore'; -import s from 'underscore.string'; import moment from 'moment'; -const CATEGORY_MESSAGE = 'MESSAGE'; -const CATEGORY_MESSAGE_NOREPLY = 'MESSAGE_NOREPLY'; - -/** - * Replaces @username with full name - * - * @param {string} message The message to replace - * @param {object[]} mentions Array of mentions used to make replacements - * - * @returns {string} - */ -function replaceMentionedUsernamesWithFullNames(message, mentions) { - if (!mentions || !mentions.length) { - return message; - } - mentions.forEach((mention) => { - const user = RocketChat.models.Users.findOneById(mention._id); - if (user && user.name) { - message = message.replace(`@${ mention.username }`, user.name); - } - }); - return message; -} - -function canSendMessageToRoom(room, username) { - return !((room.muted || []).includes(username)); -} - -/** - * This function returns a string ready to be shown in the notification - * - * @param {object} message the message to be parsed - */ -function parseMessageText(message, userId) { - const user = RocketChat.models.Users.findOneById(userId); - const lng = user && user.language || RocketChat.settings.get('language') || 'en'; - - if (!message.msg && message.attachments && message.attachments[0]) { - message.msg = message.attachments[0].image_type ? TAPi18n.__('User_uploaded_image', {lng}) : TAPi18n.__('User_uploaded_file', {lng}); - } - message.msg = RocketChat.callbacks.run('beforeNotifyUser', message.msg); - - return message.msg; -} -/** - * Send notification to user - * - * @param {string} userId The user to notify - * @param {object} user The sender - * @param {object} room The room send from - * @param {number} duration Duration of notification - */ -function notifyDesktopUser(userId, user, message, room, duration) { - - const UI_Use_Real_Name = RocketChat.settings.get('UI_Use_Real_Name') === true; - message.msg = parseMessageText(message, userId); - - if (UI_Use_Real_Name) { - message.msg = replaceMentionedUsernamesWithFullNames(message.msg, message.mentions); - } - let title = UI_Use_Real_Name ? user.name : `@${ user.username }`; - if (room.t !== 'd' && room.name) { - title += ` @ #${ room.name }`; - } - RocketChat.Notifications.notifyUser(userId, 'notification', { - title, - text: message.msg, - duration, - payload: { - _id: message._id, - rid: message.rid, - sender: message.u, - type: room.t, - name: room.name - } - }); -} - -function notifyAudioUser(userId, message, room) { - RocketChat.Notifications.notifyUser(userId, 'audioNotification', { - payload: { - _id: message._id, - rid: message.rid, - sender: message.u, - type: room.t, - name: room.name - } - }); -} - -/** - * Checks if a message contains a user highlight - * - * @param {string} message - * @param {array|undefined} highlights - * - * @returns {boolean} - */ -function messageContainsHighlight(message, highlights) { - if (! highlights || highlights.length === 0) { return false; } - - return highlights.some(function(highlight) { - const regexp = new RegExp(s.escapeRegExp(highlight), 'i'); - if (regexp.test(message.msg)) { - return true; - } - }); -} - -function getBadgeCount(userId) { - const subscriptions = RocketChat.models.Subscriptions.findUnreadByUserId(userId).fetch(); - - return subscriptions.reduce((unread, sub) => { - return sub.unread + unread; - }, 0); -} - -const sendPushNotifications = (userIdsToPushNotify = [], message, room, push_room, push_username, push_message, pushUsernames) => { - if (userIdsToPushNotify.length > 0 && Push.enabled === true) { - // send a push notification for each user individually (to get his/her badge count) - userIdsToPushNotify.forEach((userIdToNotify) => { - RocketChat.PushNotification.send({ - roomId: message.rid, - roomName: push_room, - username: push_username, - message: push_message, - badge: getBadgeCount(userIdToNotify), - payload: { - host: Meteor.absoluteUrl(), - rid: message.rid, - sender: message.u, - type: room.t, - name: room.name - }, - usersTo: { - userId: userIdToNotify - }, - category: canSendMessageToRoom(room, pushUsernames[userIdToNotify]) ? CATEGORY_MESSAGE : CATEGORY_MESSAGE_NOREPLY - }); - }); - } -}; - -const callJoin = (user, rid) => new Promise((resolve, reject) => { - Meteor.runAsUser(user._id, () => Meteor.call('joinRoom', rid, (error, result) => { - if (error) { - return reject(error); - } - return resolve(result); - })); -}); - -const sendSinglePush = ({ room, roomId, roomName, username, message, payload, userId, receiverUsername}) => { - RocketChat.PushNotification.send({ - roomId, - roomName, - username, - message, - // badge: getBadgeCount(userIdToNotify), - payload, - usersTo: { - userId - }, - category: canSendMessageToRoom(room, receiverUsername) ? CATEGORY_MESSAGE : CATEGORY_MESSAGE_NOREPLY - }); -}; +import { parseMessageText, callJoinRoom, messageContainsHighlight } from '../functions/notifications/'; +import { sendEmail, shouldNotifyEmail } from '../functions/notifications/email'; +import { sendSinglePush, shouldNotifyMobile } from '../functions/notifications/mobile'; +import { notifyDesktopUser, shouldNotifyDesktop } from '../functions/notifications/desktop'; +import { notifyAudioUser, shouldNotifyAudio } from '../functions/notifications/audio'; function sendNotificationOnMessage(message, room, userId) { @@ -498,11 +336,12 @@ function sendNotificationOnMessage(message, room, userId) { } - let pushNumber = 0; let desktopNumber = 0; let audioNumber = 0; +let emailNumber = 0; let totalSubs = 0; + const sendNotification = ({ subscription, sender, @@ -514,7 +353,8 @@ const sendNotification = ({ alwaysNotifyMobileBoolean, push_room, push_username, - push_message + push_message, + disableAllMessageNotifications }) => { totalSubs++; @@ -544,77 +384,89 @@ const sendNotification = ({ const receiver = RocketChat.models.Users.findOneById(subscription.u._id); - if (!receiver) { + if (!receiver || !receiver.active) { console.log('no receiver ->', subscription.u._id); return; } - const hasHighlight = messageContainsHighlight(message, receiver.settings && receiver.settings.preferences && receiver.settings.preferences.highlights); + const isHighlighted = messageContainsHighlight(message, receiver.settings && receiver.settings.preferences && receiver.settings.preferences.highlights); + + const isMentioned = mentionIds.includes(subscription.u._id); const { audioNotifications, desktopNotifications, - mobilePushNotifications + mobilePushNotifications, + emailNotifications } = subscription; let notificationSent = false; - if (toAll || toHere || hasHighlight || audioNotifications === 'all' || mentionIds.includes(subscription.u._id)) { + // busy users don't receive audio notification + if (shouldNotifyAudio({ disableAllMessageNotifications, status: receiver.status, audioNotifications, toAll, toHere, isHighlighted, isMentioned})) { - // busy users don't receive audio notification - if (receiver.status !== 'busy') { - // settings.alwaysNotifyAudioUsers.push(subscription.u._id); - // userIdsForAudio.push(subscription.u._id); - notifyAudioUser(subscription.u._id, message, room); + // settings.alwaysNotifyAudioUsers.push(subscription.u._id); + // userIdsForAudio.push(subscription.u._id); + notifyAudioUser(subscription.u._id, message, room); - ++audioNumber; - // console.log('audio ->', ++audioNumber); - } + ++audioNumber; + // console.log('audio ->', ++audioNumber); } - if (toAll || toHere || hasHighlight || desktopNotifications === 'all' || mentionIds.includes(subscription.u._id)) { - - // busy users don't receive desktop notification - if (receiver.status !== 'busy') { - // userIdsToNotify.push(subscription.u._id); + // busy users don't receive desktop notification + if (shouldNotifyDesktop({ disableAllMessageNotifications, status: receiver.status, desktopNotifications, toAll, toHere, isHighlighted, isMentioned})) { + // userIdsToNotify.push(subscription.u._id); - notificationSent = true; + notificationSent = true; - ++desktopNumber; - notifyDesktopUser(subscription.u._id, sender, message, room, subscription.desktopNotificationDuration); - // console.log('desktop ->', ++desktopNumber, toAll, toHere, hasHighlight, desktopNotifications === 'all', mentionIds.includes(subscription.u._id)); - } + ++desktopNumber; + notifyDesktopUser(subscription.u._id, sender, message, room, subscription.desktopNotificationDuration); + // console.log('desktop ->', ++desktopNumber, toAll, toHere, isHighlighted, desktopNotifications === 'all', isMentioned); } - if (toAll || hasHighlight || mobilePushNotifications === 'all' || mentionIds.includes(subscription.u._id)) { + if (shouldNotifyMobile({ disableAllMessageNotifications, mobilePushNotifications, toAll, isHighlighted, isMentioned, alwaysNotifyMobileBoolean, statusConnection: receiver.statusConnection })) { // only offline users will receive a push notification - if (alwaysNotifyMobileBoolean || receiver.statusConnection !== 'online') { - // userIdsToPushNotify.push(subscription.u._id); - // pushUsernames[receiver._id] = receiver.username; - - notificationSent = true; - - sendSinglePush({ - room, - roomId: message.rid, - roomName: push_room, - username: push_username, - message: push_message, - // badge: getBadgeCount(userIdToNotify), - payload: { - host: Meteor.absoluteUrl(), - rid: message.rid, - sender: message.u, - type: room.t, - name: room.name - }, - userId: subscription.u._id, - receiverUsername: receiver.username - }); - pushNumber++; - // console.log('push ->', ++pushNumber, toAll, hasHighlight, mobilePushNotifications === 'all', mentionIds.includes(subscription.u._id)); - } + // userIdsToPushNotify.push(subscription.u._id); + // pushUsernames[receiver._id] = receiver.username; + + notificationSent = true; + + sendSinglePush({ + room, + roomId: message.rid, + roomName: push_room, + username: push_username, + message: push_message, + // badge: getBadgeCount(userIdToNotify), + payload: { + host: Meteor.absoluteUrl(), + rid: message.rid, + sender: message.u, + type: room.t, + name: room.name + }, + userId: subscription.u._id, + receiverUsername: receiver.username + }); + pushNumber++; + // console.log('push ->', ++pushNumber, toAll, isHighlighted, mobilePushNotifications === 'all', isMentioned); + } + + if (shouldNotifyEmail({ disableAllMessageNotifications, statusConnection: receiver.statusConnection, emailNotifications, isHighlighted, isMentioned })) { + + // @TODO validate if user have verified emails: 'emails.verified' === true + + // @TODO send email + receiver.emails.some((email) => { + if (email.verified) { + sendEmail({ message, receiver, subscription, room, emailAddress: email.address }); + + return true; + } + }); + + emailNumber++; } if (notificationSent) { @@ -678,8 +530,8 @@ function notifyGroups(message, room, userId) { // @TODO we should change the find based on default server preferences Mobile_Notifications_Default_Alert and Desktop_Notifications_Default_Alert // if default is 'all' -> exclude only 'nothing' - // if default is 'mentions' -> idk - // if default is 'nothing' -> idk + // if default is 'mentions' -> exclude 'nothing' + // if default is 'nothing' -> search only with 'all' or 'mentions' // @TODO maybe should also force find mentioned people let subscriptions = []; @@ -711,7 +563,7 @@ function notifyGroups(message, room, userId) { let push_room = ''; if (RocketChat.settings.get('Push_show_username_room')) { push_username = sender.username; - push_room = `#${ room.name }`; + push_room = `#${ RocketChat.roomTypes.getRoomName(room.t, room) }`; } // @TODO mentions for a person that is not on the channel @@ -723,6 +575,7 @@ function notifyGroups(message, room, userId) { pushNumber = 0; desktopNumber = 0; audioNumber = 0; + emailNumber = 0; totalSubs = 0; subscriptions.forEach((subscription) => sendNotification({ subscription, @@ -735,7 +588,8 @@ function notifyGroups(message, room, userId) { alwaysNotifyMobileBoolean, push_room, push_username, - push_message + push_message, + disableAllMessageNotifications })); // console.timeEnd('eachSubscriptions'); @@ -743,7 +597,7 @@ function notifyGroups(message, room, userId) { Promise.all(message.mentions .filter(({ _id, username }) => _id !== 'here' && _id !== 'all' && !room.usernames.includes(username)) .map(async(user) => { - await callJoin(user, room._id); + await callJoinRoom(user, room._id); return user._id; }) @@ -771,6 +625,7 @@ function notifyGroups(message, room, userId) { console.log('pushNumber ->',pushNumber); console.log('desktopNumber ->',desktopNumber); console.log('audioNumber ->',audioNumber); + console.log('emailNumber ->',emailNumber); console.log('totalSubs ->',totalSubs); // console.time('sending'); @@ -804,7 +659,6 @@ function notifyGroups(message, room, userId) { // console.log('push ->', userIdsToPushNotify.length); return message; - } diff --git a/packages/rocketchat-push-notifications/server/models/Subscriptions.js b/packages/rocketchat-push-notifications/server/models/Subscriptions.js index 180506acd478..9d041cde9c18 100644 --- a/packages/rocketchat-push-notifications/server/models/Subscriptions.js +++ b/packages/rocketchat-push-notifications/server/models/Subscriptions.js @@ -226,8 +226,9 @@ RocketChat.models.Subscriptions.findNotificationPreferencesByRoom = function(roo const query = { rid: roomId, 'u._id': {$exists: true}, - desktopNotifications: { $ne: 'nothing' }, - mobilePushNotifications: { $ne: 'nothing' } + desktopNotifications: { $ne: 'nothing' }, // also matches empty values + mobilePushNotifications: { $ne: 'nothing' }, + emailNotifications: { $ne: 'nothing' } }; return this._db.find(query, { @@ -238,6 +239,7 @@ RocketChat.models.Subscriptions.findNotificationPreferencesByRoom = function(roo desktopNotificationDuration: 1, desktopNotifications: 1, mobilePushNotifications: 1, + emailNotifications: 1, disableNotifications: 1, muteGroupMentions: 1 } @@ -248,15 +250,9 @@ RocketChat.models.Subscriptions.findAllMessagesNotificationPreferencesByRoom = f const query = { rid: roomId, 'u._id': {$exists: true}, - desktopNotifications: { $ne: 'nothing' }, - mobilePushNotifications: { $ne: 'nothing' }, - $or: [ - {audioNotifications: {$exists: true}}, - {desktopNotifications: {$exists: true}}, - {mobilePushNotifications: {$exists: true}}, - {disableNotifications: {$exists: true}}, - {muteGroupMentions: {$exists: true}} - ] + desktopNotifications: { $in: ['all', 'mentions'] }, + mobilePushNotifications: { $in: ['all', 'mentions'] }, + emailNotifications: { $in: ['all', 'mentions'] } }; return this._db.find(query, { @@ -267,6 +263,7 @@ RocketChat.models.Subscriptions.findAllMessagesNotificationPreferencesByRoom = f desktopNotificationDuration: 1, desktopNotifications: 1, mobilePushNotifications: 1, + emailNotifications: 1, disableNotifications: 1, muteGroupMentions: 1 } From 1884e999bfa5e4d5cb1792fc07cb67a84187e6c1 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 2 May 2018 15:47:37 -0300 Subject: [PATCH 05/18] Code cleanup --- .../server/functions/notifications/mobile.js | 27 -- .../server/lib/sendNotificationsOnMessage.js | 404 +----------------- server/startup/migrations/v116.js | 73 ++++ 3 files changed, 77 insertions(+), 427 deletions(-) create mode 100644 server/startup/migrations/v116.js diff --git a/packages/rocketchat-lib/server/functions/notifications/mobile.js b/packages/rocketchat-lib/server/functions/notifications/mobile.js index fe47bf424123..0fb8a0956034 100644 --- a/packages/rocketchat-lib/server/functions/notifications/mobile.js +++ b/packages/rocketchat-lib/server/functions/notifications/mobile.js @@ -1,4 +1,3 @@ - const CATEGORY_MESSAGE = 'MESSAGE'; const CATEGORY_MESSAGE_NOREPLY = 'MESSAGE_NOREPLY'; @@ -14,32 +13,6 @@ function canSendMessageToRoom(room, username) { return !((room.muted || []).includes(username)); } -// const sendPushNotifications = (userIdsToPushNotify = [], message, room, push_room, push_username, push_message, pushUsernames) => { -// if (userIdsToPushNotify.length > 0 && Push.enabled === true) { -// // send a push notification for each user individually (to get his/her badge count) -// userIdsToPushNotify.forEach((userIdToNotify) => { -// RocketChat.PushNotification.send({ -// roomId: message.rid, -// roomName: push_room, -// username: push_username, -// message: push_message, -// badge: getBadgeCount(userIdToNotify), -// payload: { -// host: Meteor.absoluteUrl(), -// rid: message.rid, -// sender: message.u, -// type: room.t, -// name: room.name -// }, -// usersTo: { -// userId: userIdToNotify -// }, -// category: canSendMessageToRoom(room, pushUsernames[userIdToNotify]) ? CATEGORY_MESSAGE : CATEGORY_MESSAGE_NOREPLY -// }); -// }); -// } -// }; - export function sendSinglePush({ room, roomId, roomName, username, message, payload, userId, receiverUsername}) { RocketChat.PushNotification.send({ roomId, diff --git a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js index 3771bbfd9f44..eb1c2ef9ff75 100644 --- a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js +++ b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js @@ -1,5 +1,3 @@ -/* globals Push */ -import _ from 'underscore'; import moment from 'moment'; import { parseMessageText, callJoinRoom, messageContainsHighlight } from '../functions/notifications/'; @@ -8,334 +6,6 @@ import { sendSinglePush, shouldNotifyMobile } from '../functions/notifications/m import { notifyDesktopUser, shouldNotifyDesktop } from '../functions/notifications/desktop'; import { notifyAudioUser, shouldNotifyAudio } from '../functions/notifications/audio'; -function sendNotificationOnMessage(message, room, userId) { - - // skips this callback if the message was edited - if (message.editedAt) { - return message; - } - - if (message.ts && Math.abs(moment(message.ts).diff()) > 60000) { - return message; - } - - const pushUsernames = {}; - - const user = (room.t !== 'l') ? RocketChat.models.Users.findOneById(message.u._id) : room.v; - - if (!user) { - return message; - } - - /* - Increment unread couter if direct messages - */ - const settings = { - alwaysNotifyDesktopUsers: [], - dontNotifyDesktopUsers: [], - alwaysNotifyMobileUsers: [], - dontNotifyMobileUsers: [], - desktopNotificationDurations: {}, - alwaysNotifyAudioUsers: [], - dontNotifyAudioUsers: [], - audioNotificationValues: {}, - dontNotifyUsersOnGroupMentions: [] - }; - - /** - * Checks if a given user can be notified - * - * @param {string} id - * @param {string} type - mobile|desktop - * - * @returns {boolean} - */ - function canBeNotified(id, type) { - const types = { - desktop: [ 'dontNotifyDesktopUsers', 'alwaysNotifyDesktopUsers' ], - mobile: [ 'dontNotifyMobileUsers', 'alwaysNotifyMobileUsers' ], - audio: [ 'dontNotifyAudioUsers', 'alwaysNotifyAudioUsers' ] - }; - - return (settings[types[type][0]].indexOf(id) === -1 || settings[types[type][1]].indexOf(id) !== -1); - } - - // Don't fetch all users if room exceeds max members - const maxMembersForNotification = RocketChat.settings.get('Notifications_Max_Room_Members'); - const disableAllMessageNotifications = room.usernames.length > maxMembersForNotification && maxMembersForNotification !== 0; - const subscriptions = RocketChat.models.Subscriptions.findNotificationPreferencesByRoom(room._id, disableAllMessageNotifications) || []; - const userIds = []; - subscriptions.forEach(s => userIds.push(s.u._id)); - const users = {}; - - RocketChat.models.Users.findUsersByIds(userIds, { fields: { 'settings.preferences': 1 } }).forEach((user) => { - users[user._id] = user; - }); - - subscriptions.forEach(subscription => { - if (subscription.disableNotifications) { - settings.dontNotifyDesktopUsers.push(subscription.u._id); - settings.dontNotifyMobileUsers.push(subscription.u._id); - settings.dontNotifyAudioUsers.push(subscription.u._id); - return; - } - - if (Array.isArray(subscription.ignored) && subscription.ignored.find(message.u._id)) { - return; - } - - const { - audioNotifications = RocketChat.getUserPreference(users[subscription.u._id], 'audioNotifications'), - desktopNotifications = RocketChat.getUserPreference(users[subscription.u._id], 'desktopNotifications'), - mobilePushNotifications = RocketChat.getUserPreference(users[subscription.u._id], 'mobileNotifications') - } = subscription; - - if (audioNotifications === 'all' && !disableAllMessageNotifications) { - settings.alwaysNotifyAudioUsers.push(subscription.u._id); - } - if (desktopNotifications === 'all' && !disableAllMessageNotifications) { - settings.alwaysNotifyDesktopUsers.push(subscription.u._id); - } else if (desktopNotifications === 'nothing') { - settings.dontNotifyDesktopUsers.push(subscription.u._id); - } - if (mobilePushNotifications === 'all' && !disableAllMessageNotifications) { - settings.alwaysNotifyMobileUsers.push(subscription.u._id); - } else if (mobilePushNotifications === 'nothing') { - settings.dontNotifyMobileUsers.push(subscription.u._id); - } - - settings.audioNotificationValues[subscription.u._id] = subscription.audioNotificationValue; - settings.desktopNotificationDurations[subscription.u._id] = subscription.desktopNotificationDuration; - - if (subscription.muteGroupMentions) { - settings.dontNotifyUsersOnGroupMentions.push(subscription.u._id); - } - }); - let userIdsForAudio = []; - let userIdsToNotify = []; - let userIdsToPushNotify = []; - const mentions = []; - const alwaysNotifyMobileBoolean = RocketChat.settings.get('Notifications_Always_Notify_Mobile'); - - const usersWithHighlights = RocketChat.models.Users.findUsersByUsernamesWithHighlights(room.usernames, { fields: { '_id': 1, 'settings.preferences.highlights': 1 }}).fetch() - .filter(user => messageContainsHighlight(message, user.settings.preferences.highlights)); - - let push_message = ' '; - //Set variables depending on Push Notification settings - if (RocketChat.settings.get('Push_show_message')) { - push_message = parseMessageText(message, userId); - } - - let push_username = ''; - let push_room = ''; - if (RocketChat.settings.get('Push_show_username_room')) { - push_username = user.username; - push_room = `#${ room.name }`; - } - - if (room.t == null || room.t === 'd') { - const userOfMentionId = message.rid.replace(message.u._id, ''); - const userOfMention = RocketChat.models.Users.findOne({ - _id: userOfMentionId - }, { - fields: { - username: 1, - statusConnection: 1 - } - }); - - // Always notify Sandstorm - if (userOfMention != null) { - RocketChat.Sandstorm.notify(message, [userOfMention._id], - `@${ user.username }: ${ message.msg }`, 'privateMessage'); - - if (canBeNotified(userOfMentionId, 'desktop')) { - const duration = settings.desktopNotificationDurations[userOfMention._id]; - notifyDesktopUser(userOfMention._id, user, message, room, duration); - } - - if (canBeNotified(userOfMentionId, 'mobile')) { - if (Push.enabled === true && (userOfMention.statusConnection !== 'online' || alwaysNotifyMobileBoolean === true)) { - RocketChat.PushNotification.send({ - roomId: message.rid, - username: push_username, - message: push_message, - badge: getBadgeCount(userOfMention._id), - payload: { - host: Meteor.absoluteUrl(), - rid: message.rid, - sender: message.u, - type: room.t, - name: room.name - }, - usersTo: { - userId: userOfMention._id - }, - category: canSendMessageToRoom(room, userOfMention.username) ? CATEGORY_MESSAGE : CATEGORY_MESSAGE_NOREPLY - }); - return message; - } - } - } - - } else { - const mentionIds = (message.mentions || []).map(({_id}) => _id); - const toAll = mentionIds.includes('all'); - const toHere = mentionIds.includes('here'); - - if (mentionIds.length + settings.alwaysNotifyDesktopUsers.length > 0) { - let desktopMentionIds = _.union(mentionIds, settings.alwaysNotifyDesktopUsers); - desktopMentionIds = _.difference(desktopMentionIds, settings.dontNotifyDesktopUsers); - - let usersOfDesktopMentions = RocketChat.models.Users.find({ - _id: { - $in: desktopMentionIds - } - }, { - fields: { - _id: 1, - username: 1, - active: 1 - } - }).fetch(); - mentions.push(...usersOfDesktopMentions); - if (room.t !== 'c') { - usersOfDesktopMentions = _.reject(usersOfDesktopMentions, (usersOfMentionItem) => { - return room.usernames.indexOf(usersOfMentionItem.username) === -1; - }); - } - - userIdsToNotify = _.pluck(usersOfDesktopMentions, '_id'); - } - - if (mentionIds.length + settings.alwaysNotifyMobileUsers.length > 0) { - let mobileMentionIds = _.union(mentionIds, settings.alwaysNotifyMobileUsers); - mobileMentionIds = _.difference(mobileMentionIds, settings.dontNotifyMobileUsers); - - const usersOfMobileMentionsQuery = { - _id: { - $in: mobileMentionIds - } - }; - - if (alwaysNotifyMobileBoolean !== true) { - usersOfMobileMentionsQuery.statusConnection = { $ne: 'online' }; - } - - let usersOfMobileMentions = RocketChat.models.Users.find(usersOfMobileMentionsQuery, { - fields: { - _id: 1, - username: 1, - statusConnection: 1, - active: 1 - } - }).fetch(); - - mentions.push(...usersOfMobileMentions); - if (room.t !== 'c') { - usersOfMobileMentions = _.reject(usersOfMobileMentions, usersOfMentionItem => !room.usernames.includes(usersOfMentionItem.username)); - } - - userIdsToPushNotify = usersOfMobileMentions.map(userMobile => { - pushUsernames[userMobile._id] = userMobile.username; - return userMobile._id; - }); - } - - if (mentionIds.length + settings.alwaysNotifyAudioUsers.length > 0) { - let audioMentionIds = _.union(mentionIds, settings.alwaysNotifyAudioUsers); - audioMentionIds = _.difference(audioMentionIds, userIdsToNotify); - - let usersOfAudioMentions = RocketChat.models.Users.find({ _id: { $in: audioMentionIds }, statusConnection: { - $ne:'offline' - } }, { - fields: { - _id: 1, - username: 1, - active: 1 - } - }).fetch(); - mentions.push(...usersOfAudioMentions); - if (room.t !== 'c') { - usersOfAudioMentions = _.reject(usersOfAudioMentions, (usersOfMentionItem) => { - return room.usernames.indexOf(usersOfMentionItem.username) === -1; - }); - } - - userIdsForAudio = _.pluck(usersOfAudioMentions, '_id'); - } - - if (room.t === 'c') { - mentions.filter(user => !room.usernames.includes(user.username)) - .forEach(user =>callJoin(user, room._id)); - } - - if ([toAll, toHere].some(e => e) && room.usernames && room.usernames.length > 0) { - RocketChat.models.Users.find({ - username: { $in: room.usernames }, - _id: { $ne: user._id } - }, { - fields: { - _id: 1, - username: 1, - status: 1, - statusConnection: 1 - } - }).forEach(function({ status, _id, username, statusConnection }) { // user - if (Array.isArray(settings.dontNotifyUsersOnGroupMentions) && settings.dontNotifyUsersOnGroupMentions.includes(_id)) { - return; - } - - if (['online', 'away', 'busy'].includes(status) && !(settings.dontNotifyDesktopUsers || []).includes(_id)) { - userIdsToNotify.push(_id); - userIdsForAudio.push(_id); - } - if (toAll && statusConnection !== 'online' && !(settings.dontNotifyMobileUsers || []).includes(_id)) { - pushUsernames[_id] = username; - return userIdsToPushNotify.push(_id); - } - if (toAll && statusConnection !== 'online') { - userIdsForAudio.push(_id); - } - }); - } - - if (usersWithHighlights.length > 0) { - const highlightsIds = _.pluck(usersWithHighlights, '_id'); - userIdsForAudio = userIdsForAudio.concat(highlightsIds); - userIdsToNotify = userIdsToNotify.concat(highlightsIds); - userIdsToPushNotify = userIdsToPushNotify.concat(highlightsIds); - } - - userIdsToNotify = _.without(_.compact(_.unique(userIdsToNotify)), message.u._id); - userIdsToPushNotify = _.without(_.compact(_.unique(userIdsToPushNotify)), message.u._id); - userIdsForAudio = _.without(_.compact(_.unique(userIdsForAudio)), message.u._id); - - - console.log('desktop ->',userIdsToNotify.length); - console.log('audio ->',userIdsForAudio.length); - console.log('push ->',userIdsToPushNotify.length); - - console.time('sending'); - for (const usersOfMentionId of userIdsToNotify) { - const duration = settings.desktopNotificationDurations[usersOfMentionId]; - notifyDesktopUser(usersOfMentionId, user, message, room, duration); - } - for (const usersOfMentionId of userIdsForAudio) { - notifyAudioUser(usersOfMentionId, message, room); - } - sendPushNotifications(userIdsToPushNotify, message, room, push_room, push_username, push_message, pushUsernames); - - const allUserIdsToNotify = _.unique(userIdsToNotify.concat(userIdsToPushNotify)); - RocketChat.Sandstorm.notify(message, allUserIdsToNotify, - `@${ user.username }: ${ message.msg }`, room.t === 'p' ? 'privateMessage' : 'message'); - console.timeEnd('sending'); - } - - return message; - -} - let pushNumber = 0; let desktopNumber = 0; let audioNumber = 0; @@ -454,10 +124,6 @@ const sendNotification = ({ } if (shouldNotifyEmail({ disableAllMessageNotifications, statusConnection: receiver.statusConnection, emailNotifications, isHighlighted, isMentioned })) { - - // @TODO validate if user have verified emails: 'emails.verified' === true - - // @TODO send email receiver.emails.some((email) => { if (email.verified) { sendEmail({ message, receiver, subscription, room, emailAddress: email.address }); @@ -473,12 +139,9 @@ const sendNotification = ({ // const allUserIdsToNotify = _.unique(userIdsToNotify.concat(userIdsToPushNotify)); RocketChat.Sandstorm.notify(message, [subscription.u._id], `@${ sender.username }: ${ message.msg }`, room.t === 'p' ? 'privateMessage' : 'message'); } - - // settings.audioNotificationValues[subscription.u._id] = subscription.audioNotificationValue; - // settings.desktopNotificationDurations[subscription.u._id] = subscription.desktopNotificationDuration; }; -function notifyGroups(message, room, userId) { +function sendAllNotifications(message, room, userId) { // skips this callback if the message was edited if (message.editedAt) { @@ -500,24 +163,6 @@ function notifyGroups(message, room, userId) { return message; } - /* - Increment unread couter if direct messages - */ - // const settings = { - // alwaysNotifyDesktopUsers: [], - // dontNotifyDesktopUsers: [], - - // alwaysNotifyMobileUsers: [], - // dontNotifyMobileUsers: [], - - // alwaysNotifyAudioUsers: [], - // dontNotifyAudioUsers: [], - - // desktopNotificationDurations: {}, - // audioNotificationValues: {}, - // dontNotifyUsersOnGroupMentions: [] - // }; - // Don't fetch all users if room exceeds max members const maxMembersForNotification = RocketChat.settings.get('Notifications_Max_Room_Members'); const disableAllMessageNotifications = room.usernames.length > maxMembersForNotification && maxMembersForNotification !== 0; @@ -528,18 +173,12 @@ function notifyGroups(message, room, userId) { // console.time('findSubscriptions'); - // @TODO we should change the find based on default server preferences Mobile_Notifications_Default_Alert and Desktop_Notifications_Default_Alert - // if default is 'all' -> exclude only 'nothing' - // if default is 'mentions' -> exclude 'nothing' - // if default is 'nothing' -> search only with 'all' or 'mentions' - // @TODO maybe should also force find mentioned people let subscriptions = []; if (disableAllMessageNotifications) { - // @TODO get only preferences set to all (because they have precedence over `max_room_members`) - subscriptions = RocketChat.models.Subscriptions.findAllMessagesNotificationPreferencesByRoom(room._id) || []; + subscriptions = RocketChat.models.Subscriptions.findAllMessagesNotificationPreferencesByRoom(room._id); } else { - subscriptions = RocketChat.models.Subscriptions.findNotificationPreferencesByRoom(room._id) || []; + subscriptions = RocketChat.models.Subscriptions.findNotificationPreferencesByRoom(room._id); } // console.timeEnd('findSubscriptions'); @@ -566,8 +205,6 @@ function notifyGroups(message, room, userId) { push_room = `#${ RocketChat.roomTypes.getRoomName(room.t, room) }`; } - // @TODO mentions for a person that is not on the channel - console.log('count ->', subscriptions.count()); // console.time('eachSubscriptions'); @@ -628,41 +265,8 @@ function notifyGroups(message, room, userId) { console.log('emailNumber ->',emailNumber); console.log('totalSubs ->',totalSubs); - // console.time('sending'); - - // console.time('sendDesktopNotifications'); - // for (const usersOfMentionId of userIdsToNotify) { - // const duration = settings.desktopNotificationDurations[usersOfMentionId]; - // notifyDesktopUser(usersOfMentionId, sender, message, room, duration); - // } - // console.timeEnd('sendDesktopNotifications'); - - // console.time('sendAudioNotifications'); - // for (const usersOfMentionId of userIdsForAudio) { - // notifyAudioUser(usersOfMentionId, message, room); - // } - // console.timeEnd('sendAudioNotifications'); - - // console.time('sendPushNotifications'); - // sendPushNotifications(userIdsToPushNotify, message, room, push_room, push_username, push_message, pushUsernames); - // console.timeEnd('sendPushNotifications'); - - // console.time('sendSandstormNotifications'); - // const allUserIdsToNotify = _.unique(userIdsToNotify.concat(userIdsToPushNotify)); - // RocketChat.Sandstorm.notify(message, allUserIdsToNotify, `@${ sender.username }: ${ message.msg }`, room.t === 'p' ? 'privateMessage' : 'message'); - // console.timeEnd('sendSandstormNotifications'); - - // console.timeEnd('sending'); - - // console.log('desktop ->', userIdsToNotify.length); - // console.log('sound ->', userIdsForAudio.length); - // console.log('push ->', userIdsToPushNotify.length); - return message; } - -// RocketChat.callbacks.add('afterSaveMessage', sendNotificationOnMessage, RocketChat.callbacks.priority.LOW, 'sendNotificationOnMessage'); - -RocketChat.callbacks.add('afterSaveMessage', notifyGroups, RocketChat.callbacks.priority.LOW, 'sendNotificationGroupsOnMessage'); +RocketChat.callbacks.add('afterSaveMessage', sendAllNotifications, RocketChat.callbacks.priority.LOW, 'sendNotificationGroupsOnMessage'); diff --git a/server/startup/migrations/v116.js b/server/startup/migrations/v116.js new file mode 100644 index 000000000000..6f2a14e554c6 --- /dev/null +++ b/server/startup/migrations/v116.js @@ -0,0 +1,73 @@ +RocketChat.Migrations.add({ + version: 116, + up() { + + // set pref origin to all existing preferences + RocketChat.models.Subscriptions.update({ + desktopNotifications: { $exists: true } + }, { + $set: { + desktopPrefOrigin: 'subscription' + } + }); + RocketChat.models.Subscriptions.update({ + mobilePushNotifications: { $exists: true } + }, { + $set: { + mobilePrefOrigin: 'subscription' + } + }); + RocketChat.models.Subscriptions.update({ + emailNotifications: { $exists: true } + }, { + $set: { + emailPrefOrigin: 'subscription' + } + }); + + // set user preferences on subscriptions + RocketChat.models.Users.find({ + $or: [ + { 'settings.preferences.desktopNotifications': { $exists: true } }, + { 'settings.preferences.mobileNotifications': { $exists: true } }, + { 'settings.preferences.emailNotificationMode': { $exists: true } } + ] + }).forEach(user => { + if (user.settings.preferences.desktopNotifications && user.settings.preferences.desktopNotifications !== 'default') { + RocketChat.models.Subscriptions.update({ + 'u._id': user._id, + desktopPrefOrigin: { $exists: false } + }, { + $set: { + desktopNotifications: user.settings.preferences.desktopNotifications, + desktopPrefOrigin: 'user' + } + }); + } + + if (user.settings.preferences.mobileNotifications && user.settings.preferences.mobileNotifications !== 'default') { + RocketChat.models.Subscriptions.update({ + 'u._id': user._id, + mobilePrefOrigin: { $exists: false } + }, { + $set: { + mobileNotifications: user.settings.preferences.mobileNotifications, + mobilePrefOrigin: 'user' + } + }); + } + + if (user.settings.preferences.emailNotificationMode && user.settings.preferences.emailNotificationMode !== 'default') { + RocketChat.models.Subscriptions.update({ + 'u._id': user._id, + emailPrefOrigin: { $exists: false } + }, { + $set: { + emailNotifications: user.settings.preferences.emailNotificationMode === 'disabled' ? 'nothing' : user.settings.preferences.emailNotificationMode, + emailPrefOrigin: 'user' + } + }); + } + }); + } +}); From b7124f174e973bb694f9db86be8721a6720d129c Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 2 May 2018 16:41:22 -0300 Subject: [PATCH 06/18] Remove rawCollection --- .../server/lib/notifyUsersOnMessage.js | 60 ++++--------------- .../rocketchat-lib/server/models/Rooms.js | 4 +- .../server/models/Subscriptions.js | 12 ++-- 3 files changed, 17 insertions(+), 59 deletions(-) diff --git a/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js b/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js index b79afbb8d4fa..d51e7e6a065b 100644 --- a/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js +++ b/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js @@ -101,15 +101,8 @@ function notifyUsersOnMessage(message, room) { incUnread = 1; } console.time('incGroupMentionsAndUnreadForRoomIdExcludingUserId2'); - RocketChat.models.Subscriptions.incGroupMentionsAndUnreadForRoomIdExcludingUserId(room._id, message.u._id, 1, incUnread) - .then(() => { - console.timeEnd('incGroupMentionsAndUnreadForRoomIdExcludingUserId2'); - }) - .catch(error => { - console.error('error ->', error); - console.timeEnd('incGroupMentionsAndUnreadForRoomIdExcludingUserId2'); - throw error; - }); + RocketChat.models.Subscriptions.incGroupMentionsAndUnreadForRoomIdExcludingUserId(room._id, message.u._id, 1, incUnread); + console.timeEnd('incGroupMentionsAndUnreadForRoomIdExcludingUserId2'); } else if ((mentionIds && mentionIds.length > 0) || (highlightsIds && highlightsIds.length > 0)) { let incUnread = 0; @@ -117,60 +110,29 @@ function notifyUsersOnMessage(message, room) { incUnread = 1; } console.time('incUserMentionsAndUnreadForRoomIdAndUserIds'); - RocketChat.models.Subscriptions.incUserMentionsAndUnreadForRoomIdAndUserIds(room._id, _.compact(_.unique(mentionIds.concat(highlightsIds))), 1, incUnread) - .then(() => { - console.timeEnd('incUserMentionsAndUnreadForRoomIdAndUserIds'); - }) - .catch(error => { - console.error('error ->', error); - console.timeEnd('incUserMentionsAndUnreadForRoomIdAndUserIds'); - throw error; - }); + RocketChat.models.Subscriptions.incUserMentionsAndUnreadForRoomIdAndUserIds(room._id, _.compact(_.unique(mentionIds.concat(highlightsIds))), 1, incUnread); + console.timeEnd('incUserMentionsAndUnreadForRoomIdAndUserIds'); } else if (unreadCount === 'all_messages') { console.time('incUnreadForRoomIdExcludingUserId'); - RocketChat.models.Subscriptions.incUnreadForRoomIdExcludingUserId(room._id, message.u._id) - .then(() => { - console.timeEnd('incUnreadForRoomIdExcludingUserId'); - }) - .catch(error => { - console.error('error ->', error); - console.timeEnd('incUnreadForRoomIdExcludingUserId'); - throw error; - }); + RocketChat.models.Subscriptions.incUnreadForRoomIdExcludingUserId(room._id, message.u._id); + console.timeEnd('incUnreadForRoomIdExcludingUserId'); } } } // Update all the room activity tracker fields console.time('incMsgCountAndSetLastMessageById'); - RocketChat.models.Rooms.incMsgCountAndSetLastMessageById(message.rid, 1, message.ts, RocketChat.settings.get('Store_Last_Message') && message) - .then(() => { - console.timeEnd('incMsgCountAndSetLastMessageById'); - }) - .catch(error => { - console.error('error ->', error); - console.timeEnd('incMsgCountAndSetLastMessageById'); - throw error; - }); + RocketChat.models.Rooms.incMsgCountAndSetLastMessageById(message.rid, 1, message.ts, RocketChat.settings.get('Store_Last_Message') && message); + console.timeEnd('incMsgCountAndSetLastMessageById'); // Update all other subscriptions to alert their owners but witout incrementing // the unread counter, as it is only for mentions and direct messages console.time('setAlertForRoomIdExcludingUserId'); - Promise.all([ - RocketChat.models.Subscriptions.setAlertForRoomIdExcludingUserId(message.rid, message.u._id), - RocketChat.models.Subscriptions.setOpenForRoomIdExcludingUserId(message.rid, message.u._id) - ]) - .then(() => { - console.timeEnd('setAlertForRoomIdExcludingUserId'); - }) - .catch(error => { - console.error('error ->', error); - console.timeEnd('setAlertForRoomIdExcludingUserId'); - throw error; - }); + RocketChat.models.Subscriptions.setAlertForRoomIdExcludingUserId(message.rid, message.u._id); + RocketChat.models.Subscriptions.setOpenForRoomIdExcludingUserId(message.rid, message.u._id); + console.timeEnd('setAlertForRoomIdExcludingUserId'); return message; - } RocketChat.callbacks.add('afterSaveMessage', notifyUsersOnMessage, RocketChat.callbacks.priority.LOW, 'notifyUsersOnMessage'); diff --git a/packages/rocketchat-lib/server/models/Rooms.js b/packages/rocketchat-lib/server/models/Rooms.js index da77f7678b0d..de9f17a8038f 100644 --- a/packages/rocketchat-lib/server/models/Rooms.js +++ b/packages/rocketchat-lib/server/models/Rooms.js @@ -14,8 +14,6 @@ class ModelRooms extends RocketChat.models._Base { this.cache.ignoreUpdatedFields = ['msgs', 'lm']; this.cache.ensureIndex(['t', 'name'], 'unique'); this.cache.options = {fields: {usernames: 0}}; - - this.rawCollection = this.model.rawCollection(); } findOneByIdOrName(_idOrName, options) { @@ -564,7 +562,7 @@ class ModelRooms extends RocketChat.models._Base { update.$set.lastMessage = lastMessage; } - return this.rawCollection.update(query, this.setUpdatedAt(update)); + return this.update(query, update); } setLastMessageById(_id, lastMessage) { diff --git a/packages/rocketchat-lib/server/models/Subscriptions.js b/packages/rocketchat-lib/server/models/Subscriptions.js index 7950ac5d1b13..cea32ec66e28 100644 --- a/packages/rocketchat-lib/server/models/Subscriptions.js +++ b/packages/rocketchat-lib/server/models/Subscriptions.js @@ -41,8 +41,6 @@ class ModelSubscriptions extends RocketChat.models._Base { this.cache.ensureIndex('name', 'array'); this.cache.ensureIndex(['rid', 'u._id'], 'unique'); this.cache.ensureIndex(['name', 'u._id'], 'unique'); - - this.rawCollection = this.model.rawCollection(); } @@ -424,7 +422,7 @@ class ModelSubscriptions extends RocketChat.models._Base { } }; - return this.rawCollection.update(query, this.setUpdatedAt(update), { multi: true }); + return this.update(query, update, { multi: true }); } incGroupMentionsAndUnreadForRoomIdExcludingUserId(roomId, userId, incGroup = 1, incUnread = 1) { @@ -446,7 +444,7 @@ class ModelSubscriptions extends RocketChat.models._Base { } }; - return this.rawCollection.update(query, this.setUpdatedAt(update), { multi: true }); + return this.update(query, update, { multi: true }); } incUserMentionsAndUnreadForRoomIdAndUserIds(roomId, userIds, incUser = 1, incUnread = 1) { @@ -468,7 +466,7 @@ class ModelSubscriptions extends RocketChat.models._Base { } }; - return this.rawCollection.update(query, this.setUpdatedAt(update), { multi: true }); + return this.update(query, update, { multi: true }); } ignoreUser({_id, ignoredUser : ignored, ignore = true}) { @@ -500,7 +498,7 @@ class ModelSubscriptions extends RocketChat.models._Base { alert: true } }; - return this.rawCollection.update(query, this.setUpdatedAt(update), { multi: true }); + return this.update(query, update, { multi: true }); } setOpenForRoomIdExcludingUserId(roomId, userId) { @@ -517,7 +515,7 @@ class ModelSubscriptions extends RocketChat.models._Base { open: true } }; - return this.rawCollection.update(query, this.setUpdatedAt(update), { multi: true }); + return this.update(query, update, { multi: true }); } setBlockedByRoomId(rid, blocked, blocker) { From f538d83b7278616e32e945154b3ab4a4398807e5 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 2 May 2018 16:42:19 -0300 Subject: [PATCH 07/18] Move specific code to their respective files --- .../server/functions/notifications/mobile.js | 29 ++++++++--- .../server/lib/sendNotificationsOnMessage.js | 51 +++---------------- 2 files changed, 29 insertions(+), 51 deletions(-) diff --git a/packages/rocketchat-lib/server/functions/notifications/mobile.js b/packages/rocketchat-lib/server/functions/notifications/mobile.js index 0fb8a0956034..552340f4fa1a 100644 --- a/packages/rocketchat-lib/server/functions/notifications/mobile.js +++ b/packages/rocketchat-lib/server/functions/notifications/mobile.js @@ -1,6 +1,14 @@ +import { parseMessageText } from './index'; + const CATEGORY_MESSAGE = 'MESSAGE'; const CATEGORY_MESSAGE_NOREPLY = 'MESSAGE_NOREPLY'; +let alwaysNotifyMobileBoolean; + +Meteor.startup(() => { + alwaysNotifyMobileBoolean = RocketChat.settings.get('Notifications_Always_Notify_Mobile'); +}); + // function getBadgeCount(userId) { // const subscriptions = RocketChat.models.Subscriptions.findUnreadByUserId(userId).fetch(); @@ -13,14 +21,21 @@ function canSendMessageToRoom(room, username) { return !((room.muted || []).includes(username)); } -export function sendSinglePush({ room, roomId, roomName, username, message, payload, userId, receiverUsername}) { +export function sendSinglePush({ room, message, userId, receiverUsername, senderUsername }) { RocketChat.PushNotification.send({ - roomId, - roomName, - username, - message, + roomId: message.rid, + // badge: getBadgeCount(userIdToNotify), + payload: { + host: Meteor.absoluteUrl(), + rid: message.rid, + sender: message.u, + type: room.t, + name: room.name + }, + roomName: RocketChat.settings.get('Push_show_username_room') ? `#${ RocketChat.roomTypes.getRoomName(room.t, room) }` : '', + username: RocketChat.settings.get('Push_show_username_room') ? senderUsername : '', + message: RocketChat.settings.get('Push_show_message') ? parseMessageText(message, userId) : ' ', // badge: getBadgeCount(userIdToNotify), - payload, usersTo: { userId }, @@ -28,7 +43,7 @@ export function sendSinglePush({ room, roomId, roomName, username, message, payl }); } -export function shouldNotifyMobile({ disableAllMessageNotifications, mobilePushNotifications, toAll, isHighlighted, isMentioned, alwaysNotifyMobileBoolean, statusConnection }) { +export function shouldNotifyMobile({ disableAllMessageNotifications, mobilePushNotifications, toAll, isHighlighted, isMentioned, statusConnection }) { if (disableAllMessageNotifications && mobilePushNotifications == null) { return false; } diff --git a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js index eb1c2ef9ff75..9761ca124eea 100644 --- a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js +++ b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js @@ -1,6 +1,6 @@ import moment from 'moment'; -import { parseMessageText, callJoinRoom, messageContainsHighlight } from '../functions/notifications/'; +import { callJoinRoom, messageContainsHighlight } from '../functions/notifications/'; import { sendEmail, shouldNotifyEmail } from '../functions/notifications/email'; import { sendSinglePush, shouldNotifyMobile } from '../functions/notifications/mobile'; import { notifyDesktopUser, shouldNotifyDesktop } from '../functions/notifications/desktop'; @@ -20,10 +20,6 @@ const sendNotification = ({ message, room, mentionIds, - alwaysNotifyMobileBoolean, - push_room, - push_username, - push_message, disableAllMessageNotifications }) => { totalSubs++; @@ -94,7 +90,7 @@ const sendNotification = ({ // console.log('desktop ->', ++desktopNumber, toAll, toHere, isHighlighted, desktopNotifications === 'all', isMentioned); } - if (shouldNotifyMobile({ disableAllMessageNotifications, mobilePushNotifications, toAll, isHighlighted, isMentioned, alwaysNotifyMobileBoolean, statusConnection: receiver.statusConnection })) { + if (shouldNotifyMobile({ disableAllMessageNotifications, mobilePushNotifications, toAll, isHighlighted, isMentioned, statusConnection: receiver.statusConnection })) { // only offline users will receive a push notification // userIdsToPushNotify.push(subscription.u._id); @@ -104,19 +100,9 @@ const sendNotification = ({ sendSinglePush({ room, - roomId: message.rid, - roomName: push_room, - username: push_username, - message: push_message, - // badge: getBadgeCount(userIdToNotify), - payload: { - host: Meteor.absoluteUrl(), - rid: message.rid, - sender: message.u, - type: room.t, - name: room.name - }, + message, userId: subscription.u._id, + senderUsername: sender.username, receiverUsername: receiver.username }); pushNumber++; @@ -141,7 +127,7 @@ const sendNotification = ({ } }; -function sendAllNotifications(message, room, userId) { +function sendAllNotifications(message, room) { // skips this callback if the message was edited if (message.editedAt) { @@ -186,25 +172,10 @@ function sendAllNotifications(message, room, userId) { // const userIdsToNotify = []; // const userIdsToPushNotify = []; - const alwaysNotifyMobileBoolean = RocketChat.settings.get('Notifications_Always_Notify_Mobile'); - const mentionIds = (message.mentions || []).map(({_id}) => _id); const toAll = mentionIds.includes('all'); const toHere = mentionIds.includes('here'); - //Set variables depending on Push Notification settings - let push_message = ' '; - if (RocketChat.settings.get('Push_show_message')) { - push_message = parseMessageText(message, userId); - } - - let push_username = ''; - let push_room = ''; - if (RocketChat.settings.get('Push_show_username_room')) { - push_username = sender.username; - push_room = `#${ RocketChat.roomTypes.getRoomName(room.t, room) }`; - } - console.log('count ->', subscriptions.count()); // console.time('eachSubscriptions'); @@ -222,10 +193,6 @@ function sendAllNotifications(message, room, userId) { message, room, mentionIds, - alwaysNotifyMobileBoolean, - push_room, - push_username, - push_message, disableAllMessageNotifications })); // console.timeEnd('eachSubscriptions'); @@ -249,11 +216,7 @@ function sendAllNotifications(message, room, userId) { toHere, message, room, - mentionIds, - alwaysNotifyMobileBoolean, - push_room, - push_username, - push_message + mentionIds }); }); }); @@ -268,5 +231,5 @@ function sendAllNotifications(message, room, userId) { return message; } -RocketChat.callbacks.add('afterSaveMessage', sendAllNotifications, RocketChat.callbacks.priority.LOW, 'sendNotificationGroupsOnMessage'); +RocketChat.callbacks.add('afterSaveMessage', sendAllNotifications, RocketChat.callbacks.priority.LOW, 'sendNotificationsOnMessage'); From bdc613092049efd01672af1566d009e7564ff088 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 3 May 2018 17:49:57 -0300 Subject: [PATCH 08/18] Respect server's default preferences --- .../server/functions/notifications/audio.js | 4 ++++ .../server/functions/notifications/desktop.js | 9 ++++++++ .../server/functions/notifications/email.js | 18 ++++++++++++++- .../server/functions/notifications/mobile.js | 9 ++++++++ .../server/lib/sendNotificationsOnMessage.js | 18 +++++++++------ .../server/models/Subscriptions.js | 22 ++++++++++++------- server/startup/migrations/v116.js | 12 ++++++++++ 7 files changed, 76 insertions(+), 16 deletions(-) diff --git a/packages/rocketchat-lib/server/functions/notifications/audio.js b/packages/rocketchat-lib/server/functions/notifications/audio.js index 01883df02975..9c8b276cd069 100644 --- a/packages/rocketchat-lib/server/functions/notifications/audio.js +++ b/packages/rocketchat-lib/server/functions/notifications/audio.js @@ -7,6 +7,10 @@ export function shouldNotifyAudio({ disableAllMessageNotifications, status, audi return false; } + if (!audioNotifications && RocketChat.settings.get('Accounts_Default_User_Preferences_audioNotifications') === 'all') { + return true; + } + return toAll || toHere || isHighlighted || audioNotifications === 'all' || isMentioned; } diff --git a/packages/rocketchat-lib/server/functions/notifications/desktop.js b/packages/rocketchat-lib/server/functions/notifications/desktop.js index e0a55700fd70..b91c4bb3b2b3 100644 --- a/packages/rocketchat-lib/server/functions/notifications/desktop.js +++ b/packages/rocketchat-lib/server/functions/notifications/desktop.js @@ -64,5 +64,14 @@ export function shouldNotifyDesktop({ disableAllMessageNotifications, status, de return false; } + if (!desktopNotifications) { + if (RocketChat.settings.get('Accounts_Default_User_Preferences_desktopNotifications') === 'all') { + return true; + } + if (RocketChat.settings.get('Accounts_Default_User_Preferences_desktopNotifications') === 'nothing') { + return false; + } + } + return toAll || toHere || isHighlighted || desktopNotifications === 'all' || isMentioned; } diff --git a/packages/rocketchat-lib/server/functions/notifications/email.js b/packages/rocketchat-lib/server/functions/notifications/email.js index b23014edae73..b35f7a83cebe 100644 --- a/packages/rocketchat-lib/server/functions/notifications/email.js +++ b/packages/rocketchat-lib/server/functions/notifications/email.js @@ -139,10 +139,26 @@ export function sendEmail({ message, user, subscription, room, emailAddress, toA } export function shouldNotifyEmail({ disableAllMessageNotifications, statusConnection, emailNotifications, isHighlighted, isMentioned }) { - if (disableAllMessageNotifications && emailNotifications == null) { + + // no user or room preference + if (emailNotifications == null) { + + if (disableAllMessageNotifications) { + return false; + } + + // default server preference is disabled + if (RocketChat.settings.get('Accounts_Default_User_Preferences_emailNotificationMode') === 'disabled') { + return false; + } + } + + // user/room preference to nothing + if (emailNotifications === 'nothing') { return false; } + // use connected (don't need to send him an email) if (statusConnection === 'online') { return false; } diff --git a/packages/rocketchat-lib/server/functions/notifications/mobile.js b/packages/rocketchat-lib/server/functions/notifications/mobile.js index 552340f4fa1a..bc34f6fa574e 100644 --- a/packages/rocketchat-lib/server/functions/notifications/mobile.js +++ b/packages/rocketchat-lib/server/functions/notifications/mobile.js @@ -56,5 +56,14 @@ export function shouldNotifyMobile({ disableAllMessageNotifications, mobilePushN return false; } + if (!mobilePushNotifications) { + if (RocketChat.settings.get('Accounts_Default_User_Preferences_mobileNotifications') === 'all') { + return true; + } + if (RocketChat.settings.get('Accounts_Default_User_Preferences_mobileNotifications') === 'nothing') { + return false; + } + } + return toAll || isHighlighted || mobilePushNotifications === 'all' || isMentioned; } diff --git a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js index 9761ca124eea..a07e0e594192 100644 --- a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js +++ b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js @@ -72,7 +72,6 @@ const sendNotification = ({ if (shouldNotifyAudio({ disableAllMessageNotifications, status: receiver.status, audioNotifications, toAll, toHere, isHighlighted, isMentioned})) { // settings.alwaysNotifyAudioUsers.push(subscription.u._id); - // userIdsForAudio.push(subscription.u._id); notifyAudioUser(subscription.u._id, message, room); ++audioNumber; @@ -109,7 +108,7 @@ const sendNotification = ({ // console.log('push ->', ++pushNumber, toAll, isHighlighted, mobilePushNotifications === 'all', isMentioned); } - if (shouldNotifyEmail({ disableAllMessageNotifications, statusConnection: receiver.statusConnection, emailNotifications, isHighlighted, isMentioned })) { + if (receiver.emails && shouldNotifyEmail({ disableAllMessageNotifications, statusConnection: receiver.statusConnection, emailNotifications, isHighlighted, isMentioned })) { receiver.emails.some((email) => { if (email.verified) { sendEmail({ message, receiver, subscription, room, emailAddress: email.address }); @@ -142,8 +141,6 @@ function sendAllNotifications(message, room) { return message; } - // const pushUsernames = {}; - const sender = (room.t !== 'l') ? RocketChat.models.Users.findOneById(message.u._id) : room.v; if (!sender) { return message; @@ -160,15 +157,22 @@ function sendAllNotifications(message, room) { // console.time('findSubscriptions'); // @TODO maybe should also force find mentioned people - let subscriptions = []; + let subscriptions; if (disableAllMessageNotifications) { subscriptions = RocketChat.models.Subscriptions.findAllMessagesNotificationPreferencesByRoom(room._id); } else { - subscriptions = RocketChat.models.Subscriptions.findNotificationPreferencesByRoom(room._id); + const mentionsFilter = { $in: ['all', 'mentions'] }; + const excludesNothingFilter = { $ne: 'nothing' }; + + subscriptions = RocketChat.models.Subscriptions.findNotificationPreferencesByRoom({ + roomId: room._id, + desktopFilter: RocketChat.settings.get('Accounts_Default_User_Preferences_desktopNotifications') === 'nothing' ? mentionsFilter : excludesNothingFilter, + emailFilter: RocketChat.settings.get('Accounts_Default_User_Preferences_emailNotificationMode') === 'disabled' ? mentionsFilter : excludesNothingFilter, + mobileFilter: RocketChat.settings.get('Accounts_Default_User_Preferences_mobileNotifications') === 'nothing' ? mentionsFilter : excludesNothingFilter + }); } // console.timeEnd('findSubscriptions'); - // const userIdsForAudio = []; // const userIdsToNotify = []; // const userIdsToPushNotify = []; diff --git a/packages/rocketchat-push-notifications/server/models/Subscriptions.js b/packages/rocketchat-push-notifications/server/models/Subscriptions.js index 9d041cde9c18..9d9e51d5cd98 100644 --- a/packages/rocketchat-push-notifications/server/models/Subscriptions.js +++ b/packages/rocketchat-push-notifications/server/models/Subscriptions.js @@ -222,15 +222,19 @@ RocketChat.models.Subscriptions.findWithSendEmailByRoomId = function(roomId) { }; -RocketChat.models.Subscriptions.findNotificationPreferencesByRoom = function(roomId) { +RocketChat.models.Subscriptions.findNotificationPreferencesByRoom = function({ roomId: rid, desktopFilter: desktopNotifications, mobileFilter: mobilePushNotifications, emailFilter: emailNotifications }) { const query = { - rid: roomId, + rid, 'u._id': {$exists: true}, - desktopNotifications: { $ne: 'nothing' }, // also matches empty values - mobilePushNotifications: { $ne: 'nothing' }, - emailNotifications: { $ne: 'nothing' } + $or: [ + { desktopNotifications }, + { mobilePushNotifications }, + { emailNotifications } + ] }; + console.log('query ->', JSON.stringify(query, null, 2)); + return this._db.find(query, { fields: { 'u._id': 1, @@ -250,9 +254,11 @@ RocketChat.models.Subscriptions.findAllMessagesNotificationPreferencesByRoom = f const query = { rid: roomId, 'u._id': {$exists: true}, - desktopNotifications: { $in: ['all', 'mentions'] }, - mobilePushNotifications: { $in: ['all', 'mentions'] }, - emailNotifications: { $in: ['all', 'mentions'] } + $or: [ + { desktopNotifications: { $in: ['all', 'mentions'] } }, + { mobilePushNotifications: { $in: ['all', 'mentions'] } }, + { emailNotifications: { $in: ['all', 'mentions'] } } + ] }; return this._db.find(query, { diff --git a/server/startup/migrations/v116.js b/server/startup/migrations/v116.js index 6f2a14e554c6..91258aaef63a 100644 --- a/server/startup/migrations/v116.js +++ b/server/startup/migrations/v116.js @@ -9,6 +9,8 @@ RocketChat.Migrations.add({ $set: { desktopPrefOrigin: 'subscription' } + }, { + multi: true }); RocketChat.models.Subscriptions.update({ mobilePushNotifications: { $exists: true } @@ -16,6 +18,8 @@ RocketChat.Migrations.add({ $set: { mobilePrefOrigin: 'subscription' } + }, { + multi: true }); RocketChat.models.Subscriptions.update({ emailNotifications: { $exists: true } @@ -23,6 +27,8 @@ RocketChat.Migrations.add({ $set: { emailPrefOrigin: 'subscription' } + }, { + multi: true }); // set user preferences on subscriptions @@ -42,6 +48,8 @@ RocketChat.Migrations.add({ desktopNotifications: user.settings.preferences.desktopNotifications, desktopPrefOrigin: 'user' } + }, { + multi: true }); } @@ -54,6 +62,8 @@ RocketChat.Migrations.add({ mobileNotifications: user.settings.preferences.mobileNotifications, mobilePrefOrigin: 'user' } + }, { + multi: true }); } @@ -66,6 +76,8 @@ RocketChat.Migrations.add({ emailNotifications: user.settings.preferences.emailNotificationMode === 'disabled' ? 'nothing' : user.settings.preferences.emailNotificationMode, emailPrefOrigin: 'user' } + }, { + multi: true }); } }); From de6d492a4399a9035d8b09b00a4842e77783b830 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Sat, 5 May 2018 14:52:10 -0300 Subject: [PATCH 09/18] Add default email preference to user's preferences --- .../server/functions/notifications/audio.js | 4 +- .../server/functions/notifications/desktop.js | 4 +- .../server/functions/notifications/email.js | 30 ++++--- .../server/functions/notifications/mobile.js | 10 +-- .../server/lib/sendNotificationsOnMessage.js | 87 +++---------------- .../server/models/Subscriptions.js | 68 ++++++++++++++- .../server/models/Subscriptions.js | 2 - .../client/accountPreferences.html | 3 +- .../client/accountPreferences.js | 8 ++ server/methods/saveUserPreferences.js | 25 +++++- 10 files changed, 136 insertions(+), 105 deletions(-) diff --git a/packages/rocketchat-lib/server/functions/notifications/audio.js b/packages/rocketchat-lib/server/functions/notifications/audio.js index 9c8b276cd069..a28c970fc5e2 100644 --- a/packages/rocketchat-lib/server/functions/notifications/audio.js +++ b/packages/rocketchat-lib/server/functions/notifications/audio.js @@ -1,4 +1,4 @@ -export function shouldNotifyAudio({ disableAllMessageNotifications, status, audioNotifications, toAll, toHere, isHighlighted, isMentioned}) { +export function shouldNotifyAudio({ disableAllMessageNotifications, status, audioNotifications, hasMentionToAll, hasMentionToHere, isHighlighted, hasMentionToUser}) { if (disableAllMessageNotifications && audioNotifications == null) { return false; } @@ -11,7 +11,7 @@ export function shouldNotifyAudio({ disableAllMessageNotifications, status, audi return true; } - return toAll || toHere || isHighlighted || audioNotifications === 'all' || isMentioned; + return hasMentionToAll || hasMentionToHere || isHighlighted || audioNotifications === 'all' || hasMentionToUser; } export function notifyAudioUser(userId, message, room) { diff --git a/packages/rocketchat-lib/server/functions/notifications/desktop.js b/packages/rocketchat-lib/server/functions/notifications/desktop.js index b91c4bb3b2b3..e4982334cb1f 100644 --- a/packages/rocketchat-lib/server/functions/notifications/desktop.js +++ b/packages/rocketchat-lib/server/functions/notifications/desktop.js @@ -55,7 +55,7 @@ export function notifyDesktopUser(userId, user, message, room, duration) { }); } -export function shouldNotifyDesktop({ disableAllMessageNotifications, status, desktopNotifications, toAll, toHere, isHighlighted, isMentioned}) { +export function shouldNotifyDesktop({ disableAllMessageNotifications, status, desktopNotifications, hasMentionToAll, hasMentionToHere, isHighlighted, hasMentionToUser}) { if (disableAllMessageNotifications && desktopNotifications == null) { return false; } @@ -73,5 +73,5 @@ export function shouldNotifyDesktop({ disableAllMessageNotifications, status, de } } - return toAll || toHere || isHighlighted || desktopNotifications === 'all' || isMentioned; + return hasMentionToAll || hasMentionToHere || isHighlighted || desktopNotifications === 'all' || hasMentionToUser; } diff --git a/packages/rocketchat-lib/server/functions/notifications/email.js b/packages/rocketchat-lib/server/functions/notifications/email.js index b35f7a83cebe..17bef781977d 100644 --- a/packages/rocketchat-lib/server/functions/notifications/email.js +++ b/packages/rocketchat-lib/server/functions/notifications/email.js @@ -1,7 +1,15 @@ import s from 'underscore.string'; -const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || ''); -let footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || ''); +let contentHeader; +RocketChat.settings.get('Email_Header', (key, value) => { + contentHeader = RocketChat.placeholders.replace(value || ''); +}); + +let contentFooter; +RocketChat.settings.get('Email_Footer', (key, value) => { + contentFooter = RocketChat.placeholders.replace(value || ''); +}); + const divisorMessage = '
'; function getEmailContent({ message, user, room }) { @@ -16,9 +24,8 @@ function getEmailContent({ message, user, room }) { lng }); - let messageContent; if (message.msg !== '') { - messageContent = s.escapeHTML(message.msg); + let messageContent = s.escapeHTML(message.msg); message = RocketChat.callbacks.run('renderMessage', message); if (message.tokens && message.tokens.length > 0) { message.tokens.forEach((token) => { @@ -26,11 +33,7 @@ function getEmailContent({ message, user, room }) { messageContent = messageContent.replace(token.token, token.text); }); } - messageContent = messageContent.replace(/\n/gm, '
'); - } - - if (messageContent) { - return `${ header }

${ messageContent }`; + return `${ header }

${ messageContent.replace(/\n/gm, '
') }`; } if (message.file) { @@ -110,13 +113,13 @@ export function sendEmail({ message, user, subscription, room, emailAddress, toA const link = getMessageLink(room, subscription); if (RocketChat.settings.get('Direct_Reply_Enable')) { - footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer_Direct_Reply') || ''); + contentFooter = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer_Direct_Reply') || ''); } const email = { to: emailAddress, subject: emailSubject, - html: header + content + divisorMessage + link + footer + html: contentHeader + content + divisorMessage + link + contentFooter }; // using user full-name/channel name in from address @@ -138,11 +141,10 @@ export function sendEmail({ message, user, subscription, room, emailAddress, toA }); } -export function shouldNotifyEmail({ disableAllMessageNotifications, statusConnection, emailNotifications, isHighlighted, isMentioned }) { +export function shouldNotifyEmail({ disableAllMessageNotifications, statusConnection, emailNotifications, isHighlighted, hasMentionToUser }) { // no user or room preference if (emailNotifications == null) { - if (disableAllMessageNotifications) { return false; } @@ -163,5 +165,5 @@ export function shouldNotifyEmail({ disableAllMessageNotifications, statusConnec return false; } - return isHighlighted || emailNotifications === 'all' || isMentioned; + return isHighlighted || emailNotifications === 'all' || hasMentionToUser; } diff --git a/packages/rocketchat-lib/server/functions/notifications/mobile.js b/packages/rocketchat-lib/server/functions/notifications/mobile.js index bc34f6fa574e..123c2b6dd061 100644 --- a/packages/rocketchat-lib/server/functions/notifications/mobile.js +++ b/packages/rocketchat-lib/server/functions/notifications/mobile.js @@ -4,9 +4,8 @@ const CATEGORY_MESSAGE = 'MESSAGE'; const CATEGORY_MESSAGE_NOREPLY = 'MESSAGE_NOREPLY'; let alwaysNotifyMobileBoolean; - -Meteor.startup(() => { - alwaysNotifyMobileBoolean = RocketChat.settings.get('Notifications_Always_Notify_Mobile'); +RocketChat.settings.get('Notifications_Always_Notify_Mobile', (key, value) => { + alwaysNotifyMobileBoolean = value; }); // function getBadgeCount(userId) { @@ -24,7 +23,6 @@ function canSendMessageToRoom(room, username) { export function sendSinglePush({ room, message, userId, receiverUsername, senderUsername }) { RocketChat.PushNotification.send({ roomId: message.rid, - // badge: getBadgeCount(userIdToNotify), payload: { host: Meteor.absoluteUrl(), rid: message.rid, @@ -43,7 +41,7 @@ export function sendSinglePush({ room, message, userId, receiverUsername, sender }); } -export function shouldNotifyMobile({ disableAllMessageNotifications, mobilePushNotifications, toAll, isHighlighted, isMentioned, statusConnection }) { +export function shouldNotifyMobile({ disableAllMessageNotifications, mobilePushNotifications, hasMentionToAll, isHighlighted, hasMentionToUser, statusConnection }) { if (disableAllMessageNotifications && mobilePushNotifications == null) { return false; } @@ -65,5 +63,5 @@ export function shouldNotifyMobile({ disableAllMessageNotifications, mobilePushN } } - return toAll || isHighlighted || mobilePushNotifications === 'all' || isMentioned; + return hasMentionToAll || isHighlighted || mobilePushNotifications === 'all' || hasMentionToUser; } diff --git a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js index a07e0e594192..d7b6798c4246 100644 --- a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js +++ b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js @@ -6,58 +6,46 @@ import { sendSinglePush, shouldNotifyMobile } from '../functions/notifications/m import { notifyDesktopUser, shouldNotifyDesktop } from '../functions/notifications/desktop'; import { notifyAudioUser, shouldNotifyAudio } from '../functions/notifications/audio'; -let pushNumber = 0; -let desktopNumber = 0; -let audioNumber = 0; -let emailNumber = 0; -let totalSubs = 0; - const sendNotification = ({ subscription, sender, - toAll, - toHere, + hasMentionToAll, + hasMentionToHere, message, room, mentionIds, disableAllMessageNotifications }) => { - totalSubs++; // don't notify the sender if (subscription.u._id === sender._id) { - console.log('return; sender'); return; } // notifications disabled if (subscription.disableNotifications) { - // console.log('return; disableNotifications'); return; } // dont send notification to users who ignored the sender if (Array.isArray(subscription.ignored) && subscription.ignored.find(sender._id)) { - console.log('return; ignored'); return; } // mute group notifications (@here and @all) - if (subscription.muteGroupMentions && (toAll || toHere)) { - console.log('return; muteGroupMentions'); + if (subscription.muteGroupMentions && (hasMentionToAll || hasMentionToHere)) { return; } const receiver = RocketChat.models.Users.findOneById(subscription.u._id); if (!receiver || !receiver.active) { - console.log('no receiver ->', subscription.u._id); return; } const isHighlighted = messageContainsHighlight(message, receiver.settings && receiver.settings.preferences && receiver.settings.preferences.highlights); - const isMentioned = mentionIds.includes(subscription.u._id); + const hasMentionToUser = mentionIds.includes(subscription.u._id); const { audioNotifications, @@ -69,32 +57,17 @@ const sendNotification = ({ let notificationSent = false; // busy users don't receive audio notification - if (shouldNotifyAudio({ disableAllMessageNotifications, status: receiver.status, audioNotifications, toAll, toHere, isHighlighted, isMentioned})) { - - // settings.alwaysNotifyAudioUsers.push(subscription.u._id); + if (shouldNotifyAudio({ disableAllMessageNotifications, status: receiver.status, audioNotifications, hasMentionToAll, hasMentionToHere, isHighlighted, hasMentionToUser})) { notifyAudioUser(subscription.u._id, message, room); - - ++audioNumber; - // console.log('audio ->', ++audioNumber); } // busy users don't receive desktop notification - if (shouldNotifyDesktop({ disableAllMessageNotifications, status: receiver.status, desktopNotifications, toAll, toHere, isHighlighted, isMentioned})) { - // userIdsToNotify.push(subscription.u._id); - + if (shouldNotifyDesktop({ disableAllMessageNotifications, status: receiver.status, desktopNotifications, hasMentionToAll, hasMentionToHere, isHighlighted, hasMentionToUser})) { notificationSent = true; - - ++desktopNumber; notifyDesktopUser(subscription.u._id, sender, message, room, subscription.desktopNotificationDuration); - // console.log('desktop ->', ++desktopNumber, toAll, toHere, isHighlighted, desktopNotifications === 'all', isMentioned); } - if (shouldNotifyMobile({ disableAllMessageNotifications, mobilePushNotifications, toAll, isHighlighted, isMentioned, statusConnection: receiver.statusConnection })) { - - // only offline users will receive a push notification - // userIdsToPushNotify.push(subscription.u._id); - // pushUsernames[receiver._id] = receiver.username; - + if (shouldNotifyMobile({ disableAllMessageNotifications, mobilePushNotifications, hasMentionToAll, isHighlighted, hasMentionToUser, statusConnection: receiver.statusConnection })) { notificationSent = true; sendSinglePush({ @@ -104,11 +77,9 @@ const sendNotification = ({ senderUsername: sender.username, receiverUsername: receiver.username }); - pushNumber++; - // console.log('push ->', ++pushNumber, toAll, isHighlighted, mobilePushNotifications === 'all', isMentioned); } - if (receiver.emails && shouldNotifyEmail({ disableAllMessageNotifications, statusConnection: receiver.statusConnection, emailNotifications, isHighlighted, isMentioned })) { + if (receiver.emails && shouldNotifyEmail({ disableAllMessageNotifications, statusConnection: receiver.statusConnection, emailNotifications, isHighlighted, hasMentionToUser })) { receiver.emails.some((email) => { if (email.verified) { sendEmail({ message, receiver, subscription, room, emailAddress: email.address }); @@ -116,12 +87,9 @@ const sendNotification = ({ return true; } }); - - emailNumber++; } if (notificationSent) { - // const allUserIdsToNotify = _.unique(userIdsToNotify.concat(userIdsToPushNotify)); RocketChat.Sandstorm.notify(message, [subscription.u._id], `@${ sender.username }: ${ message.msg }`, room.t === 'p' ? 'privateMessage' : 'message'); } }; @@ -150,13 +118,6 @@ function sendAllNotifications(message, room) { const maxMembersForNotification = RocketChat.settings.get('Notifications_Max_Room_Members'); const disableAllMessageNotifications = room.usernames.length > maxMembersForNotification && maxMembersForNotification !== 0; - console.log('room.usernames.length ->', room.usernames.length); - console.log('maxMembersForNotification ->', maxMembersForNotification); - console.log('disableAllMessageNotifications ->', disableAllMessageNotifications); - - // console.time('findSubscriptions'); - - // @TODO maybe should also force find mentioned people let subscriptions; if (disableAllMessageNotifications) { subscriptions = RocketChat.models.Subscriptions.findAllMessagesNotificationPreferencesByRoom(room._id); @@ -171,35 +132,21 @@ function sendAllNotifications(message, room) { mobileFilter: RocketChat.settings.get('Accounts_Default_User_Preferences_mobileNotifications') === 'nothing' ? mentionsFilter : excludesNothingFilter }); } - // console.timeEnd('findSubscriptions'); - - // const userIdsToNotify = []; - // const userIdsToPushNotify = []; const mentionIds = (message.mentions || []).map(({_id}) => _id); - const toAll = mentionIds.includes('all'); - const toHere = mentionIds.includes('here'); + const hasMentionToAll = mentionIds.includes('all'); + const hasMentionToHere = mentionIds.includes('here'); - console.log('count ->', subscriptions.count()); - - // console.time('eachSubscriptions'); - - pushNumber = 0; - desktopNumber = 0; - audioNumber = 0; - emailNumber = 0; - totalSubs = 0; subscriptions.forEach((subscription) => sendNotification({ subscription, sender, - toAll, - toHere, + hasMentionToAll, + hasMentionToHere, message, room, mentionIds, disableAllMessageNotifications })); - // console.timeEnd('eachSubscriptions'); if (room.t === 'c') { Promise.all(message.mentions @@ -216,8 +163,8 @@ function sendAllNotifications(message, room) { sendNotification({ subscription, sender, - toAll, - toHere, + hasMentionToAll, + hasMentionToHere, message, room, mentionIds @@ -226,12 +173,6 @@ function sendAllNotifications(message, room) { }); } - console.log('pushNumber ->',pushNumber); - console.log('desktopNumber ->',desktopNumber); - console.log('audioNumber ->',audioNumber); - console.log('emailNumber ->',emailNumber); - console.log('totalSubs ->',totalSubs); - return message; } diff --git a/packages/rocketchat-lib/server/models/Subscriptions.js b/packages/rocketchat-lib/server/models/Subscriptions.js index cea32ec66e28..0f0faa529749 100644 --- a/packages/rocketchat-lib/server/models/Subscriptions.js +++ b/packages/rocketchat-lib/server/models/Subscriptions.js @@ -624,6 +624,22 @@ class ModelSubscriptions extends RocketChat.models._Base { return this.update(query, update, { multi: true }); } + clearDesktopNotificationUserPreferences(userId) { + const query = { + 'u._id': userId, + desktopPrefOrigin: 'user' + }; + + const update = { + $unset: { + desktopNotifications: 1, + desktopPrefOrigin: 1 + } + }; + + return this.update(query, update, { multi: true }); + } + updateDesktopNotificationUserPreferences(userId, desktopNotifications) { const query = { 'u._id': userId, @@ -642,10 +658,26 @@ class ModelSubscriptions extends RocketChat.models._Base { return this.update(query, update, { multi: true }); } + clearMobileNotificationUserPreferences(userId) { + const query = { + 'u._id': userId, + mobilePrefOrigin: 'user' + }; + + const update = { + $unset: { + mobilePushNotifications: 1, + mobilePrefOrigin: 1 + } + }; + + return this.update(query, update, { multi: true }); + } + updateMobileNotificationUserPreferences(userId, mobilePushNotifications) { const query = { 'u._id': userId, - desktopPrefOrigin: { + mobilePrefOrigin: { $ne: 'subscription' } }; @@ -660,6 +692,40 @@ class ModelSubscriptions extends RocketChat.models._Base { return this.update(query, update, { multi: true }); } + clearEmailNotificationUserPreferences(userId) { + const query = { + 'u._id': userId, + emailPrefOrigin: 'user' + }; + + const update = { + $unset: { + emailNotifications: 1, + emailPrefOrigin: 1 + } + }; + + return this.update(query, update, { multi: true }); + } + + updateEmailNotificationUserPreferences(userId, emailNotifications) { + const query = { + 'u._id': userId, + emailPrefOrigin: { + $ne: 'subscription' + } + }; + + const update = { + $set: { + emailNotifications, + emailPrefOrigin: 'user' + } + }; + + return this.update(query, update, { multi: true }); + } + // INSERT createWithRoomAndUser(room, user, extraData) { const subscription = { diff --git a/packages/rocketchat-push-notifications/server/models/Subscriptions.js b/packages/rocketchat-push-notifications/server/models/Subscriptions.js index 9d9e51d5cd98..6a5c7f5b33f3 100644 --- a/packages/rocketchat-push-notifications/server/models/Subscriptions.js +++ b/packages/rocketchat-push-notifications/server/models/Subscriptions.js @@ -233,8 +233,6 @@ RocketChat.models.Subscriptions.findNotificationPreferencesByRoom = function({ r ] }; - console.log('query ->', JSON.stringify(query, null, 2)); - return this._db.find(query, { fields: { 'u._id': 1, diff --git a/packages/rocketchat-ui-account/client/accountPreferences.html b/packages/rocketchat-ui-account/client/accountPreferences.html index ac820da9c198..4e4e64807519 100644 --- a/packages/rocketchat-ui-account/client/accountPreferences.html +++ b/packages/rocketchat-ui-account/client/accountPreferences.html @@ -107,6 +107,7 @@

{{_ "Notifications"}}

@@ -319,7 +320,7 @@

{{_ "My Data"}}

- +
{{/if}} diff --git a/packages/rocketchat-ui-account/client/accountPreferences.js b/packages/rocketchat-ui-account/client/accountPreferences.js index 9dcbb99ff2b6..937e66eb6897 100644 --- a/packages/rocketchat-ui-account/client/accountPreferences.js +++ b/packages/rocketchat-ui-account/client/accountPreferences.js @@ -9,6 +9,11 @@ const notificationLabels = { nothing: 'Nothing' }; +const emailLabels = { + disabled: 'Email_Notification_Mode_Disabled', + all: 'Email_Notification_Mode_All' +}; + function checkedSelected(property, value, defaultValue=undefined) { if (defaultValue && defaultValue.hash) { defaultValue = undefined; @@ -84,6 +89,9 @@ Template.accountPreferences.helpers({ defaultMobileNotification() { return notificationLabels[RocketChat.settings.get('Accounts_Default_User_Preferences_mobileNotifications')]; }, + defaultEmailNotification() { + return emailLabels[RocketChat.settings.get('Accounts_Default_User_Preferences_emailNotificationMode')]; + }, showRoles() { return RocketChat.settings.get('UI_DisplayRoles'); }, diff --git a/server/methods/saveUserPreferences.js b/server/methods/saveUserPreferences.js index 96f2248bd5af..2ae56398f5b3 100644 --- a/server/methods/saveUserPreferences.js +++ b/server/methods/saveUserPreferences.js @@ -47,8 +47,9 @@ Meteor.methods({ const { desktopNotifications: oldDesktopNotifications, - mobileNotifications: oldMobileNotifications - } = user.settings || {}; + mobileNotifications: oldMobileNotifications, + emailNotificationMode: oldEmailNotifications + } = (user.settings && user.settings.preferences) || {}; if (user.settings == null) { RocketChat.models.Users.clearSettings(user._id); @@ -71,11 +72,27 @@ Meteor.methods({ // propagate changed notification preferences Meteor.defer(() => { if (oldDesktopNotifications !== settings.desktopNotifications) { - RocketChat.models.Subscriptions.updateDesktopNotificationUserPreferences(user._id, settings.desktopNotifications); + if (settings.desktopNotifications === 'default') { + RocketChat.models.Subscriptions.clearDesktopNotificationUserPreferences(user._id); + } else { + RocketChat.models.Subscriptions.updateDesktopNotificationUserPreferences(user._id, settings.desktopNotifications); + } } if (oldMobileNotifications !== settings.mobileNotifications) { - RocketChat.models.Subscriptions.updateMobileNotificationUserPreferences(user._id, settings.mobileNotifications); + if (settings.mobileNotifications === 'default') { + RocketChat.models.Subscriptions.clearMobileNotificationUserPreferences(user._id); + } else { + RocketChat.models.Subscriptions.updateMobileNotificationUserPreferences(user._id, settings.mobileNotifications); + } + } + + if (oldEmailNotifications !== settings.emailNotificationMode) { + if (settings.emailNotificationMode === 'default') { + RocketChat.models.Subscriptions.clearEmailNotificationUserPreferences(user._id); + } else { + RocketChat.models.Subscriptions.updateEmailNotificationUserPreferences(user._id, settings.emailNotificationMode === 'disabled' ? 'nothing' : settings.emailNotificationMode); + } } }); From 2bddb06b811371c46ac83364db635647f03c3ac4 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Sat, 5 May 2018 14:52:49 -0300 Subject: [PATCH 10/18] Change default value of auto away to true --- packages/rocketchat-lib/server/startup/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rocketchat-lib/server/startup/settings.js b/packages/rocketchat-lib/server/startup/settings.js index a471b8bda3cf..197b87e446f4 100644 --- a/packages/rocketchat-lib/server/startup/settings.js +++ b/packages/rocketchat-lib/server/startup/settings.js @@ -180,7 +180,7 @@ RocketChat.settings.addGroup('Accounts', function() { }); this.section('Accounts_Default_User_Preferences', function() { - this.add('Accounts_Default_User_Preferences_enableAutoAway', false, { + this.add('Accounts_Default_User_Preferences_enableAutoAway', true, { type: 'boolean', 'public': true, i18nLabel: 'Enable_Auto_Away' From 6ab4f88b2697519c6e3e7ce2ad92509ebd3ddd09 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Sat, 5 May 2018 18:01:47 -0300 Subject: [PATCH 11/18] Save notifications accordingly --- .../lib/getUserNotificationPreference.js | 29 +++++++++ packages/rocketchat-lib/package.js | 1 + .../server/functions/createRoom.js | 2 +- .../server/lib/notifyUsersOnMessage.js | 17 ----- .../server/models/Subscriptions.js | 24 ++++++- .../methods/saveNotificationSettings.js | 31 ++++++++- .../server/models/Subscriptions.js | 63 +++++++++---------- 7 files changed, 111 insertions(+), 56 deletions(-) create mode 100644 packages/rocketchat-lib/lib/getUserNotificationPreference.js diff --git a/packages/rocketchat-lib/lib/getUserNotificationPreference.js b/packages/rocketchat-lib/lib/getUserNotificationPreference.js new file mode 100644 index 000000000000..acf6e9a9eec3 --- /dev/null +++ b/packages/rocketchat-lib/lib/getUserNotificationPreference.js @@ -0,0 +1,29 @@ +RocketChat.getUserNotificationPreference = function _getUserNotificationPreference(user, pref) { + if (typeof user === 'string') { + user = RocketChat.models.Users.findOneById(user); + } + + let preferenceKey; + switch (pref) { + case 'desktop': preferenceKey = 'desktopNotifications'; break; + case 'mobile': preferenceKey = 'mobileNotifications'; break; + case 'email': preferenceKey = 'emailNotificationMode'; break; + } + + if (user && user.settings && user.settings.preferences && + user.settings.preferences.hasOwnProperty(preferenceKey) && user.settings.preferences[preferenceKey] !== 'default') { + return { + value: user.settings.preferences[preferenceKey], + origin: 'user' + }; + } + const serverValue = RocketChat.settings.get(`Accounts_Default_User_Preferences_${ preferenceKey }`); + if (serverValue) { + return { + value: serverValue, + origin: 'server' + }; + } + + return null; +}; diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index 9f3ba06887f2..2f8905e5f116 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -68,6 +68,7 @@ Package.onUse(function(api) { api.addFiles('lib/MessageTypes.js'); api.addFiles('lib/templateVarHandler.js'); + api.addFiles('lib/getUserNotificationPreference.js'); api.addFiles('lib/getUserPreference.js'); api.addFiles('server/lib/bugsnag.js', 'server'); diff --git a/packages/rocketchat-lib/server/functions/createRoom.js b/packages/rocketchat-lib/server/functions/createRoom.js index 8f78788af264..39c8ef191d54 100644 --- a/packages/rocketchat-lib/server/functions/createRoom.js +++ b/packages/rocketchat-lib/server/functions/createRoom.js @@ -64,7 +64,7 @@ RocketChat.createRoom = function(type, name, owner, members, readOnly, extraData room = RocketChat.models.Rooms.createWithFullRoomData(room); for (const username of members) { - const member = RocketChat.models.Users.findOneByUsername(username, { fields: { username: 1 }}); + const member = RocketChat.models.Users.findOneByUsername(username, { fields: { username: 1, 'settings.preferences': 1 }}); if (!member) { continue; } diff --git a/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js b/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js index d51e7e6a065b..981a757f7717 100644 --- a/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js +++ b/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js @@ -45,7 +45,6 @@ function notifyUsersOnMessage(message, room) { return message; } - if (room != null) { let toAll = false; let toHere = false; @@ -80,17 +79,11 @@ function notifyUsersOnMessage(message, room) { const unreadCountDM = RocketChat.settings.get('Unread_Count_DM'); if (unreadCountDM === 'all_messages') { - console.time('incUnreadForRoomIdExcludingUserId'); RocketChat.models.Subscriptions.incUnreadForRoomIdExcludingUserId(room._id, message.u._id); - console.timeEnd('incUnreadForRoomIdExcludingUserId'); } else if (toAll || toHere) { - console.time('incGroupMentionsAndUnreadForRoomIdExcludingUserId1'); RocketChat.models.Subscriptions.incGroupMentionsAndUnreadForRoomIdExcludingUserId(room._id, message.u._id, 1, 1); - console.timeEnd('incGroupMentionsAndUnreadForRoomIdExcludingUserId1'); } else if ((mentionIds && mentionIds.length > 0) || (highlightsIds && highlightsIds.length > 0)) { - console.time('incUserMentionsAndUnreadForRoomIdAndUserIds'); RocketChat.models.Subscriptions.incUserMentionsAndUnreadForRoomIdAndUserIds(room._id, _.compact(_.unique(mentionIds.concat(highlightsIds))), 1, 1); - console.timeEnd('incUserMentionsAndUnreadForRoomIdAndUserIds'); } } else { const unreadCount = RocketChat.settings.get('Unread_Count'); @@ -100,37 +93,27 @@ function notifyUsersOnMessage(message, room) { if (['all_messages', 'group_mentions_only', 'user_and_group_mentions_only'].includes(unreadCount)) { incUnread = 1; } - console.time('incGroupMentionsAndUnreadForRoomIdExcludingUserId2'); RocketChat.models.Subscriptions.incGroupMentionsAndUnreadForRoomIdExcludingUserId(room._id, message.u._id, 1, incUnread); - console.timeEnd('incGroupMentionsAndUnreadForRoomIdExcludingUserId2'); } else if ((mentionIds && mentionIds.length > 0) || (highlightsIds && highlightsIds.length > 0)) { let incUnread = 0; if (['all_messages', 'user_mentions_only', 'user_and_group_mentions_only'].includes(unreadCount)) { incUnread = 1; } - console.time('incUserMentionsAndUnreadForRoomIdAndUserIds'); RocketChat.models.Subscriptions.incUserMentionsAndUnreadForRoomIdAndUserIds(room._id, _.compact(_.unique(mentionIds.concat(highlightsIds))), 1, incUnread); - console.timeEnd('incUserMentionsAndUnreadForRoomIdAndUserIds'); } else if (unreadCount === 'all_messages') { - console.time('incUnreadForRoomIdExcludingUserId'); RocketChat.models.Subscriptions.incUnreadForRoomIdExcludingUserId(room._id, message.u._id); - console.timeEnd('incUnreadForRoomIdExcludingUserId'); } } } // Update all the room activity tracker fields - console.time('incMsgCountAndSetLastMessageById'); RocketChat.models.Rooms.incMsgCountAndSetLastMessageById(message.rid, 1, message.ts, RocketChat.settings.get('Store_Last_Message') && message); - console.timeEnd('incMsgCountAndSetLastMessageById'); // Update all other subscriptions to alert their owners but witout incrementing // the unread counter, as it is only for mentions and direct messages - console.time('setAlertForRoomIdExcludingUserId'); RocketChat.models.Subscriptions.setAlertForRoomIdExcludingUserId(message.rid, message.u._id); RocketChat.models.Subscriptions.setOpenForRoomIdExcludingUserId(message.rid, message.u._id); - console.timeEnd('setAlertForRoomIdExcludingUserId'); return message; } diff --git a/packages/rocketchat-lib/server/models/Subscriptions.js b/packages/rocketchat-lib/server/models/Subscriptions.js index 0f0faa529749..be8ea3243e6e 100644 --- a/packages/rocketchat-lib/server/models/Subscriptions.js +++ b/packages/rocketchat-lib/server/models/Subscriptions.js @@ -12,9 +12,6 @@ class ModelSubscriptions extends RocketChat.models._Base { this.tryEnsureIndex({ 'open': 1 }); this.tryEnsureIndex({ 'alert': 1 }); - // @TODO evalute find by this property if we can remove it - // this.tryEnsureIndex({ 'unread': 1 }); - this.tryEnsureIndex({ rid: 1, 'u._id': 1, @@ -747,6 +744,27 @@ class ModelSubscriptions extends RocketChat.models._Base { } }; + const { + desktopNotifications, + mobileNotifications, + emailNotificationMode + } = (user.settings && user.settings.preferences) || {}; + + if (desktopNotifications && desktopNotifications !== 'default') { + subscription.desktopNotifications = desktopNotifications; + subscription.desktopPrefOrigin = 'user'; + } + + if (mobileNotifications && mobileNotifications !== 'default') { + subscription.mobilePushNotifications = mobileNotifications; + subscription.mobilePrefOrigin = 'user'; + } + + if (emailNotificationMode && emailNotificationMode !== 'default') { + subscription.emailNotifications = emailNotificationMode === 'disabled' ? 'nothing' : user.settings.preferences.emailNotificationMode; + subscription.emailPrefOrigin = 'user'; + } + _.extend(subscription, extraData); return this.insert(subscription); diff --git a/packages/rocketchat-push-notifications/server/methods/saveNotificationSettings.js b/packages/rocketchat-push-notifications/server/methods/saveNotificationSettings.js index c4d3cdb3dbbb..18dd41e45cb2 100644 --- a/packages/rocketchat-push-notifications/server/methods/saveNotificationSettings.js +++ b/packages/rocketchat-push-notifications/server/methods/saveNotificationSettings.js @@ -12,13 +12,38 @@ Meteor.methods({ updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateAudioNotificationsById(subscription._id, value) }, 'desktopNotifications': { - updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateDesktopNotificationsById(subscription._id, value) + updateMethod: (subscription, value) => { + // @TODO if setting to 'default' should set user preferences + if (value === 'default') { + const userPref = RocketChat.getUserNotificationPreference(Meteor.userId(), 'desktop'); + RocketChat.models.Subscriptions.updateDesktopNotificationsById(subscription._id, userPref.origin === 'server' ? null : userPref); + } else { + RocketChat.models.Subscriptions.updateDesktopNotificationsById(subscription._id, { value, origin: 'subscription' }); + } + } }, 'mobilePushNotifications': { - updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateMobilePushNotificationsById(subscription._id, value) + updateMethod: (subscription, value) => { + + if (value === 'default') { + const userPref = RocketChat.getUserNotificationPreference(Meteor.userId(), 'mobile'); + RocketChat.models.Subscriptions.updateMobilePushNotificationsById(subscription._id, userPref.origin === 'server' ? null : userPref); + } else { + RocketChat.models.Subscriptions.updateMobilePushNotificationsById(subscription._id, { value, origin: 'subscription' }); + } + } }, 'emailNotifications': { - updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateEmailNotificationsById(subscription._id, value) + updateMethod: (subscription, value) => { + + if (value === 'default') { + const userPref = RocketChat.getUserNotificationPreference(Meteor.userId(), 'email'); + userPref.value = userPref.value === 'disabled' ? 'nothing' : userPref.value; + RocketChat.models.Subscriptions.updateEmailNotificationsById(subscription._id, userPref.origin === 'server' ? null : userPref); + } else { + RocketChat.models.Subscriptions.updateEmailNotificationsById(subscription._id, { value, origin: 'subscription' }); + } + } }, 'unreadAlert': { updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateUnreadAlertById(subscription._id, value) diff --git a/packages/rocketchat-push-notifications/server/models/Subscriptions.js b/packages/rocketchat-push-notifications/server/models/Subscriptions.js index 6a5c7f5b33f3..c769297354c8 100644 --- a/packages/rocketchat-push-notifications/server/models/Subscriptions.js +++ b/packages/rocketchat-push-notifications/server/models/Subscriptions.js @@ -35,10 +35,16 @@ RocketChat.models.Subscriptions.updateDesktopNotificationsById = function(_id, d const update = {}; - if (desktopNotifications === 'default') { - update.$unset = { desktopNotifications: 1 }; + if (desktopNotifications === null) { + update.$unset = { + desktopNotifications: 1, + desktopPrefOrigin: 1 + }; } else { - update.$set = { desktopNotifications }; + update.$set = { + desktopNotifications: desktopNotifications.value, + desktopPrefOrigin: desktopNotifications.origin + }; } return this.update(query, update); @@ -65,10 +71,16 @@ RocketChat.models.Subscriptions.updateMobilePushNotificationsById = function(_id const update = {}; - if (mobilePushNotifications === 'default') { - update.$unset = { mobilePushNotifications: 1 }; + if (mobilePushNotifications === null) { + update.$unset = { + mobilePushNotifications: 1, + mobilePrefOrigin: 1 + }; } else { - update.$set = { mobilePushNotifications }; + update.$set = { + mobilePushNotifications: mobilePushNotifications.value, + mobilePrefOrigin: mobilePushNotifications.origin + }; } return this.update(query, update); @@ -79,11 +91,19 @@ RocketChat.models.Subscriptions.updateEmailNotificationsById = function(_id, ema _id }; - const update = { - $set: { - emailNotifications - } - }; + const update = {}; + + if (emailNotifications === null) { + update.$unset = { + emailNotifications: 1, + emailPrefOrigin: 1 + }; + } else { + update.$set = { + emailNotifications: emailNotifications.value, + emailPrefOrigin: emailNotifications.origin + }; + } return this.update(query, update); }; @@ -189,27 +209,6 @@ RocketChat.models.Subscriptions.findDontNotifyMobileUsersByRoomId = function(roo return this.find(query); }; -RocketChat.models.Subscriptions.findNotificationPreferencesByRoomOld = function(roomId, explicit) { - const query = { - rid: roomId, - 'u._id': {$exists: true} - }; - - if (explicit) { - query.$or = [ - {audioNotifications: {$exists: true}}, - {audioNotificationValue: {$exists: true}}, - {desktopNotifications: {$exists: true}}, - {desktopNotificationDuration: {$exists: true}}, - {mobilePushNotifications: {$exists: true}}, - {disableNotifications: {$exists: true}}, - {muteGroupMentions: {$exists: true}} - ]; - } - - return this.find(query, { fields: { 'u._id': 1, audioNotifications: 1, audioNotificationValue: 1, desktopNotificationDuration: 1, desktopNotifications: 1, mobilePushNotifications: 1, disableNotifications: 1, muteGroupMentions: 1 } }); -}; - RocketChat.models.Subscriptions.findWithSendEmailByRoomId = function(roomId) { const query = { rid: roomId, From b529103dd2f10e8fc73d275889543d6a72f90a9f Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Sat, 5 May 2018 18:03:49 -0300 Subject: [PATCH 12/18] Use correct room name for email notifications --- packages/rocketchat-lib/server/functions/notifications/email.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rocketchat-lib/server/functions/notifications/email.js b/packages/rocketchat-lib/server/functions/notifications/email.js index 17bef781977d..ead686e5d04e 100644 --- a/packages/rocketchat-lib/server/functions/notifications/email.js +++ b/packages/rocketchat-lib/server/functions/notifications/email.js @@ -15,7 +15,7 @@ const divisorMessage = '

${ message.attachments[0].description }`; + content += `

${ s.escapeHTML(message.attachments[0].description) }`; } return `${ fileHeader }

${ content }`; @@ -58,10 +58,10 @@ function getEmailContent({ message, user, room }) { let content = ''; if (attachment.title) { - content += `${ attachment.title }
`; + content += `${ s.escapeHTML(attachment.title) }
`; } if (attachment.text) { - content += `${ attachment.text }
`; + content += `${ s.escapeHTML(attachment.text) }
`; } return `${ header }

${ content }`; @@ -124,9 +124,9 @@ export function sendEmail({ message, user, subscription, room, emailAddress, toA // using user full-name/channel name in from address if (room.t === 'd') { - email.from = `${ message.u.name } <${ RocketChat.settings.get('From_Email') }>`; + email.from = `${ String(message.u.name).replace(/@/g, '%40').replace(/[<>,]/g, '') } <${ RocketChat.settings.get('From_Email') }>`; } else { - email.from = `${ room.name } <${ RocketChat.settings.get('From_Email') }>`; + email.from = `${ String(room.name).replace(/@/g, '%40').replace(/[<>,]/g, '') } <${ RocketChat.settings.get('From_Email') }>`; } // If direct reply enabled, email content with headers if (RocketChat.settings.get('Direct_Reply_Enable')) { diff --git a/packages/rocketchat-lib/server/lib/sendEmailOnMessage.js b/packages/rocketchat-lib/server/lib/sendEmailOnMessage.js deleted file mode 100644 index 156215a80615..000000000000 --- a/packages/rocketchat-lib/server/lib/sendEmailOnMessage.js +++ /dev/null @@ -1,111 +0,0 @@ -// import moment from 'moment'; -// import s from 'underscore.string'; - -// RocketChat.callbacks.add('afterSaveMessage', function(message, room) { -// // skips this callback if the message was edited -// if (message.editedAt) { -// return message; -// } - -// if (message.ts && Math.abs(moment(message.ts).diff()) > 60000) { -// return message; -// } - -// const usersToSendEmail = {}; -// if (room.t === 'd') { -// usersToSendEmail[message.rid.replace(message.u._id, '')] = 'direct'; -// } else { -// let isMentionAll = message.mentions.find(mention => mention._id === 'all'); - -// if (isMentionAll) { -// const maxMembersForNotification = RocketChat.settings.get('Notifications_Max_Room_Members'); -// if (maxMembersForNotification !== 0 && room.usernames.length > maxMembersForNotification) { -// isMentionAll = undefined; -// } -// } - -// let query; -// if (isMentionAll) { -// // Query all users in room limited by the max room members setting -// query = RocketChat.models.Subscriptions.findByRoomId(room._id); -// } else { -// // Query only mentioned users, will be always a few users -// const userIds = message.mentions.map(mention => mention._id); -// query = RocketChat.models.Subscriptions.findByRoomIdAndUserIdsOrAllMessages(room._id, userIds); -// } - -// query.forEach(sub => { -// if (sub.disableNotifications) { -// return delete usersToSendEmail[sub.u._id]; -// } - -// const { emailNotifications, muteGroupMentions } = sub; - -// if (emailNotifications === 'nothing') { -// return delete usersToSendEmail[sub.u._id]; -// } - -// if (isMentionAll && muteGroupMentions) { -// return delete usersToSendEmail[sub.u._id]; -// } - -// const mentionedUser = isMentionAll || message.mentions.find(mention => mention._id === sub.u._id); - -// if (emailNotifications === 'default' || emailNotifications == null) { -// if (mentionedUser) { -// return usersToSendEmail[sub.u._id] = 'default'; -// } -// return delete usersToSendEmail[sub.u._id]; -// } - -// if (emailNotifications === 'mentions' && mentionedUser) { -// return usersToSendEmail[sub.u._id] = 'mention'; -// } - -// if (emailNotifications === 'all') { -// return usersToSendEmail[sub.u._id] = 'all'; -// } -// }); -// } -// const userIdsToSendEmail = Object.keys(usersToSendEmail); - -// if (userIdsToSendEmail.length > 0) { -// const usersOfMention = RocketChat.models.Users.getUsersToSendOfflineEmail(userIdsToSendEmail).fetch(); - -// if (usersOfMention && usersOfMention.length > 0) { -// usersOfMention.forEach((user) => { -// const emailNotificationMode = RocketChat.getUserPreference(user, 'emailNotificationMode'); -// if (usersToSendEmail[user._id] === 'default') { -// if (emailNotificationMode === 'all') { //Mention/DM -// usersToSendEmail[user._id] = 'mention'; -// } else { -// return; -// } -// } - -// if (usersToSendEmail[user._id] === 'direct') { -// const userEmailPreferenceIsDisabled = emailNotificationMode === 'disabled'; -// const directMessageEmailPreference = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(message.rid, message.rid.replace(message.u._id, '')).emailNotifications; - -// if (directMessageEmailPreference === 'nothing') { -// return; -// } - -// if ((directMessageEmailPreference === 'default' || directMessageEmailPreference == null) && userEmailPreferenceIsDisabled) { -// return; -// } -// } - -// // Checks if user is in the room he/she is mentioned (unless it's public channel) -// if (room.t !== 'c' && room.usernames.indexOf(user.username) === -1) { -// return; -// } - - -// }); -// } -// } - -// return message; - -// }, RocketChat.callbacks.priority.LOW, 'sendEmailOnMessage'); diff --git a/packages/rocketchat-push-notifications/server/methods/saveNotificationSettings.js b/packages/rocketchat-push-notifications/server/methods/saveNotificationSettings.js index 18dd41e45cb2..314cc081601d 100644 --- a/packages/rocketchat-push-notifications/server/methods/saveNotificationSettings.js +++ b/packages/rocketchat-push-notifications/server/methods/saveNotificationSettings.js @@ -13,7 +13,6 @@ Meteor.methods({ }, 'desktopNotifications': { updateMethod: (subscription, value) => { - // @TODO if setting to 'default' should set user preferences if (value === 'default') { const userPref = RocketChat.getUserNotificationPreference(Meteor.userId(), 'desktop'); RocketChat.models.Subscriptions.updateDesktopNotificationsById(subscription._id, userPref.origin === 'server' ? null : userPref); @@ -24,7 +23,6 @@ Meteor.methods({ }, 'mobilePushNotifications': { updateMethod: (subscription, value) => { - if (value === 'default') { const userPref = RocketChat.getUserNotificationPreference(Meteor.userId(), 'mobile'); RocketChat.models.Subscriptions.updateMobilePushNotificationsById(subscription._id, userPref.origin === 'server' ? null : userPref); @@ -35,7 +33,6 @@ Meteor.methods({ }, 'emailNotifications': { updateMethod: (subscription, value) => { - if (value === 'default') { const userPref = RocketChat.getUserNotificationPreference(Meteor.userId(), 'email'); userPref.value = userPref.value === 'disabled' ? 'nothing' : userPref.value; From 743839bbe6ea7c4024a108507e9d529491502382 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Sat, 5 May 2018 19:11:05 -0300 Subject: [PATCH 14/18] Fix auto away default value test --- tests/end-to-end/ui/11-admin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/end-to-end/ui/11-admin.js b/tests/end-to-end/ui/11-admin.js index d48070b8dc3e..95b0bcda7529 100644 --- a/tests/end-to-end/ui/11-admin.js +++ b/tests/end-to-end/ui/11-admin.js @@ -731,8 +731,8 @@ describe('[Administration]', () => { admin.accountsEnableAutoAwayFalse.isVisible().should.be.true; }); it('the enable auto away field value should be true', () => { - admin.accountsEnableAutoAwayTrue.isSelected().should.be.false; - admin.accountsEnableAutoAwayFalse.isSelected().should.be.true; + admin.accountsEnableAutoAwayTrue.isSelected().should.be.true; + admin.accountsEnableAutoAwayFalse.isSelected().should.be.false; }); it('it should show the idle timeout limit field', () => { From 46f73bc3c5b3bbb8d0ac1306db0e76dc111acc58 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 7 May 2018 12:24:13 -0300 Subject: [PATCH 15/18] Remove duplicated index and add some comments --- .../server/lib/notifyUsersOnMessage.js | 1 + .../server/lib/sendNotificationsOnMessage.js | 3 +++ .../rocketchat-lib/server/models/Subscriptions.js | 12 +----------- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js b/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js index 981a757f7717..450298c7e0b6 100644 --- a/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js +++ b/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js @@ -112,6 +112,7 @@ function notifyUsersOnMessage(message, room) { // Update all other subscriptions to alert their owners but witout incrementing // the unread counter, as it is only for mentions and direct messages + // We now set alert and open properties in two separate update commands. This proved to be more efficient on MongoDB - because it uses a more efficient index. RocketChat.models.Subscriptions.setAlertForRoomIdExcludingUserId(message.rid, message.u._id); RocketChat.models.Subscriptions.setOpenForRoomIdExcludingUserId(message.rid, message.u._id); diff --git a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js index d7b6798c4246..3916f684fa57 100644 --- a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js +++ b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js @@ -118,6 +118,8 @@ function sendAllNotifications(message, room) { const maxMembersForNotification = RocketChat.settings.get('Notifications_Max_Room_Members'); const disableAllMessageNotifications = room.usernames.length > maxMembersForNotification && maxMembersForNotification !== 0; + // the find bellow is crucial. all subscription records returned will receive at least one kind of notification. + // the query is defined by the server's default values and Notifications_Max_Room_Members setting. let subscriptions; if (disableAllMessageNotifications) { subscriptions = RocketChat.models.Subscriptions.findAllMessagesNotificationPreferencesByRoom(room._id); @@ -148,6 +150,7 @@ function sendAllNotifications(message, room) { disableAllMessageNotifications })); + // on public channels, if a mentioned user is not member of the channel yet, he will first join the channel and then be notified based on his preferences. if (room.t === 'c') { Promise.all(message.mentions .filter(({ _id, username }) => _id !== 'here' && _id !== 'all' && !room.usernames.includes(username)) diff --git a/packages/rocketchat-lib/server/models/Subscriptions.js b/packages/rocketchat-lib/server/models/Subscriptions.js index be8ea3243e6e..32881ca97283 100644 --- a/packages/rocketchat-lib/server/models/Subscriptions.js +++ b/packages/rocketchat-lib/server/models/Subscriptions.js @@ -12,17 +12,7 @@ class ModelSubscriptions extends RocketChat.models._Base { this.tryEnsureIndex({ 'open': 1 }); this.tryEnsureIndex({ 'alert': 1 }); - this.tryEnsureIndex({ - rid: 1, - 'u._id': 1, - alert: 1 - }); - - this.tryEnsureIndex({ - rid: 1, - 'u._id': 1, - open: 1 - }); + this.tryEnsureIndex({ rid: 1, 'u._id': 1, open: 1 }); this.tryEnsureIndex({ 'ts': 1 }); this.tryEnsureIndex({ 'ls': 1 }); From c4815d27e8953f86c456f9b0cda13b41de00a5c2 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 7 May 2018 12:25:21 -0300 Subject: [PATCH 16/18] Honor Notifications_Max_Room_Members for all notifications --- .../server/functions/notifications/audio.js | 12 ++++- .../server/functions/notifications/desktop.js | 12 ++++- .../server/functions/notifications/email.js | 11 ++++- .../server/functions/notifications/mobile.js | 2 +- .../server/lib/sendNotificationsOnMessage.js | 46 +++++++++++++++---- 5 files changed, 68 insertions(+), 15 deletions(-) diff --git a/packages/rocketchat-lib/server/functions/notifications/audio.js b/packages/rocketchat-lib/server/functions/notifications/audio.js index a28c970fc5e2..1c45b18f336a 100644 --- a/packages/rocketchat-lib/server/functions/notifications/audio.js +++ b/packages/rocketchat-lib/server/functions/notifications/audio.js @@ -1,4 +1,12 @@ -export function shouldNotifyAudio({ disableAllMessageNotifications, status, audioNotifications, hasMentionToAll, hasMentionToHere, isHighlighted, hasMentionToUser}) { +export function shouldNotifyAudio({ + disableAllMessageNotifications, + status, + audioNotifications, + hasMentionToAll, + hasMentionToHere, + isHighlighted, + hasMentionToUser +}) { if (disableAllMessageNotifications && audioNotifications == null) { return false; } @@ -11,7 +19,7 @@ export function shouldNotifyAudio({ disableAllMessageNotifications, status, audi return true; } - return hasMentionToAll || hasMentionToHere || isHighlighted || audioNotifications === 'all' || hasMentionToUser; + return (!disableAllMessageNotifications && (hasMentionToAll || hasMentionToHere)) || isHighlighted || audioNotifications === 'all' || hasMentionToUser; } export function notifyAudioUser(userId, message, room) { diff --git a/packages/rocketchat-lib/server/functions/notifications/desktop.js b/packages/rocketchat-lib/server/functions/notifications/desktop.js index e4982334cb1f..49c6451d3e2e 100644 --- a/packages/rocketchat-lib/server/functions/notifications/desktop.js +++ b/packages/rocketchat-lib/server/functions/notifications/desktop.js @@ -55,7 +55,15 @@ export function notifyDesktopUser(userId, user, message, room, duration) { }); } -export function shouldNotifyDesktop({ disableAllMessageNotifications, status, desktopNotifications, hasMentionToAll, hasMentionToHere, isHighlighted, hasMentionToUser}) { +export function shouldNotifyDesktop({ + disableAllMessageNotifications, + status, + desktopNotifications, + hasMentionToAll, + hasMentionToHere, + isHighlighted, + hasMentionToUser +}) { if (disableAllMessageNotifications && desktopNotifications == null) { return false; } @@ -73,5 +81,5 @@ export function shouldNotifyDesktop({ disableAllMessageNotifications, status, de } } - return hasMentionToAll || hasMentionToHere || isHighlighted || desktopNotifications === 'all' || hasMentionToUser; + return (!disableAllMessageNotifications && (hasMentionToAll || hasMentionToHere)) || isHighlighted || desktopNotifications === 'all' || hasMentionToUser; } diff --git a/packages/rocketchat-lib/server/functions/notifications/email.js b/packages/rocketchat-lib/server/functions/notifications/email.js index da4a6ffb8e97..2e060c8c4437 100644 --- a/packages/rocketchat-lib/server/functions/notifications/email.js +++ b/packages/rocketchat-lib/server/functions/notifications/email.js @@ -141,7 +141,14 @@ export function sendEmail({ message, user, subscription, room, emailAddress, toA }); } -export function shouldNotifyEmail({ disableAllMessageNotifications, statusConnection, emailNotifications, isHighlighted, hasMentionToUser }) { +export function shouldNotifyEmail({ + disableAllMessageNotifications, + statusConnection, + emailNotifications, + isHighlighted, + hasMentionToUser, + hasMentionToAll +}) { // no user or room preference if (emailNotifications == null) { @@ -165,5 +172,5 @@ export function shouldNotifyEmail({ disableAllMessageNotifications, statusConnec return false; } - return isHighlighted || emailNotifications === 'all' || hasMentionToUser; + return isHighlighted || emailNotifications === 'all' || hasMentionToUser || (!disableAllMessageNotifications && hasMentionToAll); } diff --git a/packages/rocketchat-lib/server/functions/notifications/mobile.js b/packages/rocketchat-lib/server/functions/notifications/mobile.js index 123c2b6dd061..291543216076 100644 --- a/packages/rocketchat-lib/server/functions/notifications/mobile.js +++ b/packages/rocketchat-lib/server/functions/notifications/mobile.js @@ -63,5 +63,5 @@ export function shouldNotifyMobile({ disableAllMessageNotifications, mobilePushN } } - return hasMentionToAll || isHighlighted || mobilePushNotifications === 'all' || hasMentionToUser; + return (!disableAllMessageNotifications && hasMentionToAll) || isHighlighted || mobilePushNotifications === 'all' || hasMentionToUser; } diff --git a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js index 3916f684fa57..8ebe0dc431f1 100644 --- a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js +++ b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js @@ -32,8 +32,10 @@ const sendNotification = ({ return; } - // mute group notifications (@here and @all) - if (subscription.muteGroupMentions && (hasMentionToAll || hasMentionToHere)) { + const hasMentionToUser = mentionIds.includes(subscription.u._id); + + // mute group notifications (@here and @all) if not directly mentioned as well + if (!hasMentionToUser && subscription.muteGroupMentions && (hasMentionToAll || hasMentionToHere)) { return; } @@ -45,8 +47,6 @@ const sendNotification = ({ const isHighlighted = messageContainsHighlight(message, receiver.settings && receiver.settings.preferences && receiver.settings.preferences.highlights); - const hasMentionToUser = mentionIds.includes(subscription.u._id); - const { audioNotifications, desktopNotifications, @@ -57,17 +57,40 @@ const sendNotification = ({ let notificationSent = false; // busy users don't receive audio notification - if (shouldNotifyAudio({ disableAllMessageNotifications, status: receiver.status, audioNotifications, hasMentionToAll, hasMentionToHere, isHighlighted, hasMentionToUser})) { + if (shouldNotifyAudio({ + disableAllMessageNotifications, + status: receiver.status, + audioNotifications, + hasMentionToAll, + hasMentionToHere, + isHighlighted, + hasMentionToUser + })) { notifyAudioUser(subscription.u._id, message, room); } // busy users don't receive desktop notification - if (shouldNotifyDesktop({ disableAllMessageNotifications, status: receiver.status, desktopNotifications, hasMentionToAll, hasMentionToHere, isHighlighted, hasMentionToUser})) { + if (shouldNotifyDesktop({ + disableAllMessageNotifications, + status: receiver.status, + desktopNotifications, + hasMentionToAll, + hasMentionToHere, + isHighlighted, + hasMentionToUser + })) { notificationSent = true; notifyDesktopUser(subscription.u._id, sender, message, room, subscription.desktopNotificationDuration); } - if (shouldNotifyMobile({ disableAllMessageNotifications, mobilePushNotifications, hasMentionToAll, isHighlighted, hasMentionToUser, statusConnection: receiver.statusConnection })) { + if (shouldNotifyMobile({ + disableAllMessageNotifications, + mobilePushNotifications, + hasMentionToAll, + isHighlighted, + hasMentionToUser, + statusConnection: receiver.statusConnection + })) { notificationSent = true; sendSinglePush({ @@ -79,7 +102,14 @@ const sendNotification = ({ }); } - if (receiver.emails && shouldNotifyEmail({ disableAllMessageNotifications, statusConnection: receiver.statusConnection, emailNotifications, isHighlighted, hasMentionToUser })) { + if (receiver.emails && shouldNotifyEmail({ + disableAllMessageNotifications, + statusConnection: receiver.statusConnection, + emailNotifications, + isHighlighted, + hasMentionToUser, + hasMentionToAll + })) { receiver.emails.some((email) => { if (email.verified) { sendEmail({ message, receiver, subscription, room, emailAddress: email.address }); From 1c8200bde6290a78f5e9c11e4fcbeb66489f44b8 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 7 May 2018 17:39:17 -0300 Subject: [PATCH 17/18] Remove unread index --- server/startup/migrations/v116.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/startup/migrations/v116.js b/server/startup/migrations/v116.js index 91258aaef63a..3df0f661678e 100644 --- a/server/startup/migrations/v116.js +++ b/server/startup/migrations/v116.js @@ -1,6 +1,9 @@ RocketChat.Migrations.add({ version: 116, up() { + RocketChat.models.Subscriptions.tryDropIndex({ + unread: 1 + }); // set pref origin to all existing preferences RocketChat.models.Subscriptions.update({ From d71d48a5e912253cdf5bba3b197e24bab4c0fc0f Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 8 May 2018 17:56:49 -0300 Subject: [PATCH 18/18] Minor code improvements --- .../lib/getUserNotificationPreference.js | 3 +-- .../server/functions/notifications/desktop.js | 17 ++++++++++++---- .../server/functions/notifications/email.js | 20 +++++++++---------- .../server/functions/notifications/index.js | 4 +--- .../server/functions/notifications/mobile.js | 9 ++++++++- .../server/lib/notifyUsersOnMessage.js | 13 ++++-------- .../server/lib/sendNotificationsOnMessage.js | 1 + 7 files changed, 38 insertions(+), 29 deletions(-) diff --git a/packages/rocketchat-lib/lib/getUserNotificationPreference.js b/packages/rocketchat-lib/lib/getUserNotificationPreference.js index acf6e9a9eec3..27ae25337cc0 100644 --- a/packages/rocketchat-lib/lib/getUserNotificationPreference.js +++ b/packages/rocketchat-lib/lib/getUserNotificationPreference.js @@ -10,8 +10,7 @@ RocketChat.getUserNotificationPreference = function _getUserNotificationPreferen case 'email': preferenceKey = 'emailNotificationMode'; break; } - if (user && user.settings && user.settings.preferences && - user.settings.preferences.hasOwnProperty(preferenceKey) && user.settings.preferences[preferenceKey] !== 'default') { + if (user && user.settings && user.settings.preferences && user.settings.preferences[preferenceKey] !== 'default') { return { value: user.settings.preferences[preferenceKey], origin: 'user' diff --git a/packages/rocketchat-lib/server/functions/notifications/desktop.js b/packages/rocketchat-lib/server/functions/notifications/desktop.js index 49c6451d3e2e..a520c216ea4f 100644 --- a/packages/rocketchat-lib/server/functions/notifications/desktop.js +++ b/packages/rocketchat-lib/server/functions/notifications/desktop.js @@ -37,13 +37,22 @@ export function notifyDesktopUser(userId, user, message, room, duration) { if (UI_Use_Real_Name) { message.msg = replaceMentionedUsernamesWithFullNames(message.msg, message.mentions); } - let title = UI_Use_Real_Name ? user.name : `@${ user.username }`; - if (room.t !== 'd' && room.name) { - title += ` @ #${ room.name }`; + + let title = ''; + let text = ''; + if (room.t === 'd') { + title = UI_Use_Real_Name ? user.name : `@${ user.username }`; + text = message.msg; + } else if (room.name) { + title = `#${ room.name }`; + text = `${ UI_Use_Real_Name ? user.name : user.username }: ${ message.msg }`; + } else { + return; } + RocketChat.Notifications.notifyUser(userId, 'notification', { title, - text: message.msg, + text, duration, payload: { _id: message._id, diff --git a/packages/rocketchat-lib/server/functions/notifications/email.js b/packages/rocketchat-lib/server/functions/notifications/email.js index 2e060c8c4437..e0a887855cf5 100644 --- a/packages/rocketchat-lib/server/functions/notifications/email.js +++ b/packages/rocketchat-lib/server/functions/notifications/email.js @@ -150,6 +150,16 @@ export function shouldNotifyEmail({ hasMentionToAll }) { + // use connected (don't need to send him an email) + if (statusConnection === 'online') { + return false; + } + + // user/room preference to nothing + if (emailNotifications === 'nothing') { + return false; + } + // no user or room preference if (emailNotifications == null) { if (disableAllMessageNotifications) { @@ -162,15 +172,5 @@ export function shouldNotifyEmail({ } } - // user/room preference to nothing - if (emailNotifications === 'nothing') { - return false; - } - - // use connected (don't need to send him an email) - if (statusConnection === 'online') { - return false; - } - return isHighlighted || emailNotifications === 'all' || hasMentionToUser || (!disableAllMessageNotifications && hasMentionToAll); } diff --git a/packages/rocketchat-lib/server/functions/notifications/index.js b/packages/rocketchat-lib/server/functions/notifications/index.js index 66b4b7fb1d79..d9f6e74d65bb 100644 --- a/packages/rocketchat-lib/server/functions/notifications/index.js +++ b/packages/rocketchat-lib/server/functions/notifications/index.js @@ -30,9 +30,7 @@ export function messageContainsHighlight(message, highlights) { return highlights.some(function(highlight) { const regexp = new RegExp(s.escapeRegExp(highlight), 'i'); - if (regexp.test(message.msg)) { - return true; - } + return regexp.test(message.msg); }); } diff --git a/packages/rocketchat-lib/server/functions/notifications/mobile.js b/packages/rocketchat-lib/server/functions/notifications/mobile.js index 291543216076..820839b3fad9 100644 --- a/packages/rocketchat-lib/server/functions/notifications/mobile.js +++ b/packages/rocketchat-lib/server/functions/notifications/mobile.js @@ -41,7 +41,14 @@ export function sendSinglePush({ room, message, userId, receiverUsername, sender }); } -export function shouldNotifyMobile({ disableAllMessageNotifications, mobilePushNotifications, hasMentionToAll, isHighlighted, hasMentionToUser, statusConnection }) { +export function shouldNotifyMobile({ + disableAllMessageNotifications, + mobilePushNotifications, + hasMentionToAll, + isHighlighted, + hasMentionToUser, + statusConnection +}) { if (disableAllMessageNotifications && mobilePushNotifications == null) { return false; } diff --git a/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js b/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js index 450298c7e0b6..45502db7b461 100644 --- a/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js +++ b/packages/rocketchat-lib/server/lib/notifyUsersOnMessage.js @@ -10,19 +10,14 @@ import moment from 'moment'; * * @returns {boolean} */ -function messageContainsHighlight(message, highlights) { + +export function messageContainsHighlight(message, highlights) { if (! highlights || highlights.length === 0) { return false; } - let has = false; - highlights.some(function(highlight) { + return highlights.some(function(highlight) { const regexp = new RegExp(s.escapeRegExp(highlight), 'i'); - if (regexp.test(message.msg)) { - has = true; - return true; - } + return regexp.test(message.msg); }); - - return has; } function notifyUsersOnMessage(message, room) { diff --git a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js index 8ebe0dc431f1..e36e02776134 100644 --- a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js +++ b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js @@ -157,6 +157,7 @@ function sendAllNotifications(message, room) { const mentionsFilter = { $in: ['all', 'mentions'] }; const excludesNothingFilter = { $ne: 'nothing' }; + // evaluate if doing three specific finds is better than evaluting all results subscriptions = RocketChat.models.Subscriptions.findNotificationPreferencesByRoom({ roomId: room._id, desktopFilter: RocketChat.settings.get('Accounts_Default_User_Preferences_desktopNotifications') === 'nothing' ? mentionsFilter : excludesNothingFilter,