The communications platform that puts data protection first.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
Rocket.Chat/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js

286 lines
8.2 KiB

import moment from 'moment';
import { callJoinRoom, messageContainsHighlight, parseMessageTextPerUser, replaceMentionedUsernamesWithFullNames } 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';
const sendNotification = ({
subscription,
sender,
hasMentionToAll,
hasMentionToHere,
message,
notificationMessage,
room,
mentionIds,
disableAllMessageNotifications,
}) => {
// don't notify the sender
if (subscription.u._id === sender._id) {
return;
}
// notifications disabled
if (subscription.disableNotifications) {
return;
}
// dont send notification to users who ignored the sender
if (Array.isArray(subscription.ignored) && subscription.ignored.includes(sender._id)) {
return;
}
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;
}
const receiver = RocketChat.models.Users.findOneById(subscription.u._id);
if (!receiver || !receiver.active) {
return;
}
const roomType = room.t;
// If the user doesn't have permission to view direct messages, don't send notification of direct messages.
if (roomType === 'd' && !RocketChat.authz.hasPermission(subscription.u._id, 'view-d-room')) {
return;
}
notificationMessage = parseMessageTextPerUser(notificationMessage, message, receiver);
const isHighlighted = messageContainsHighlight(message, subscription.userHighlights);
const {
audioNotifications,
desktopNotifications,
mobilePushNotifications,
emailNotifications,
} = subscription;
let notificationSent = false;
// busy users don't receive audio notification
if (shouldNotifyAudio({
disableAllMessageNotifications,
status: receiver.status,
audioNotifications,
hasMentionToAll,
hasMentionToHere,
isHighlighted,
hasMentionToUser,
roomType,
})) {
notifyAudioUser(subscription.u._id, message, room);
}
// busy users don't receive desktop notification
if (shouldNotifyDesktop({
disableAllMessageNotifications,
status: receiver.status,
desktopNotifications,
hasMentionToAll,
hasMentionToHere,
isHighlighted,
hasMentionToUser,
roomType,
})) {
notificationSent = true;
notifyDesktopUser({
notificationMessage,
userId: subscription.u._id,
user: sender,
message,
room,
duration: subscription.desktopNotificationDuration,
});
}
if (shouldNotifyMobile({
disableAllMessageNotifications,
mobilePushNotifications,
hasMentionToAll,
isHighlighted,
hasMentionToUser,
statusConnection: receiver.statusConnection,
roomType,
})) {
notificationSent = true;
sendSinglePush({
notificationMessage,
room,
message,
userId: subscription.u._id,
senderUsername: sender.username,
senderName: sender.name,
receiverUsername: receiver.username,
});
}
if (receiver.emails && shouldNotifyEmail({
disableAllMessageNotifications,
statusConnection: receiver.statusConnection,
emailNotifications,
isHighlighted,
hasMentionToUser,
hasMentionToAll,
roomType,
})) {
receiver.emails.some((email) => {
if (email.verified) {
sendEmail({ message, receiver, subscription, room, emailAddress: email.address, hasMentionToUser });
return true;
}
return false;
});
}
if (notificationSent) {
RocketChat.Sandstorm.notify(message, [subscription.u._id], `@${ sender.username }: ${ message.msg }`, room.t === 'p' ? 'privateMessage' : 'message');
}
};
function sendAllNotifications(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;
}
if (!room || room.t == null) {
return message;
}
const sender = RocketChat.roomTypes.getConfig(room.t).getMsgSender(message.u._id);
if (!sender) {
return message;
}
const mentionIds = (message.mentions || []).map(({ _id }) => _id);
const mentionIdsWithoutGroups = mentionIds.filter((_id) => _id !== 'all' && _id !== 'here');
const hasMentionToAll = mentionIds.includes('all');
const hasMentionToHere = mentionIds.includes('here');
let notificationMessage = RocketChat.callbacks.run('beforeSendMessageNotifications', message.msg);
if (mentionIds.length > 0 && RocketChat.settings.get('UI_Use_Real_Name')) {
notificationMessage = replaceMentionedUsernamesWithFullNames(message.msg, message.mentions);
}
// Don't fetch all users if room exceeds max members
const maxMembersForNotification = RocketChat.settings.get('Notifications_Max_Room_Members');
const roomMembersCount = RocketChat.models.Subscriptions.findByRoomId(room._id).count();
const disableAllMessageNotifications = roomMembersCount > maxMembersForNotification && maxMembersForNotification !== 0;
const query = {
rid: room._id,
$or: [{
'userHighlights.0': { $exists: 1 },
}],
};
['audio', 'desktop', 'mobile', 'email'].forEach((kind) => {
const notificationField = `${ kind === 'mobile' ? 'mobilePush' : kind }Notifications`;
const filter = { [notificationField]: 'all' };
if (disableAllMessageNotifications) {
filter[`${ kind }PrefOrigin`] = { $ne: 'user' };
}
query.$or.push(filter);
if (mentionIdsWithoutGroups.length > 0) {
query.$or.push({
[notificationField]: 'mentions',
'u._id': { $in: mentionIdsWithoutGroups },
});
} else if (!disableAllMessageNotifications && (hasMentionToAll || hasMentionToHere)) {
query.$or.push({
[notificationField]: 'mentions',
});
}
const serverField = kind === 'email' ? 'emailNotificationMode' : `${ kind }Notifications`;
const serverPreference = RocketChat.settings.get(`Accounts_Default_User_Preferences_${ serverField }`);
if ((room.t === 'd' && serverPreference !== 'nothing') || (!disableAllMessageNotifications && (serverPreference === 'all' || hasMentionToAll || hasMentionToHere))) {
query.$or.push({
[notificationField]: { $exists: false },
});
} else if (serverPreference === 'mentions' && mentionIdsWithoutGroups.length) {
query.$or.push({
[notificationField]: { $exists: false },
'u._id': { $in: mentionIdsWithoutGroups },
});
}
});
// 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.
const subscriptions = RocketChat.models.Subscriptions.findNotificationPreferencesByRoom(query);
subscriptions.forEach((subscription) => sendNotification({
subscription,
sender,
hasMentionToAll,
hasMentionToHere,
message,
notificationMessage,
room,
mentionIds,
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') {
// get subscriptions from users already in room (to not send them a notification)
const mentions = [...mentionIdsWithoutGroups];
RocketChat.models.Subscriptions.findByRoomIdAndUserIds(room._id, mentionIdsWithoutGroups, { fields: { 'u._id': 1 } }).forEach((subscription) => {
const index = mentions.indexOf(subscription.u._id);
if (index !== -1) {
mentions.splice(index, 1);
}
});
Promise.all(mentions
.map(async(userId) => {
await callJoinRoom(userId, room._id);
return userId;
})
).then((users) => {
users.forEach((userId) => {
const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, userId);
sendNotification({
subscription,
sender,
hasMentionToAll,
hasMentionToHere,
message,
notificationMessage,
room,
mentionIds,
});
});
}).catch((error) => {
throw new Meteor.Error(error);
});
}
return message;
}
RocketChat.callbacks.add('afterSaveMessage', sendAllNotifications, RocketChat.callbacks.priority.LOW, 'sendNotificationsOnMessage');
export { sendNotification };