Skip to content
43 changes: 41 additions & 2 deletions apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IMessage, IRoom, IUser, RoomType } from '@rocket.chat/core-typings';
import type { IMessage, IRoom, ISubscription, IUser, RoomType } from '@rocket.chat/core-typings';
import { isEditedMessage } from '@rocket.chat/core-typings';
import type { Updater } from '@rocket.chat/models';
import { Subscriptions, Rooms } from '@rocket.chat/models';
Expand Down Expand Up @@ -84,6 +84,21 @@ const getUnreadSettingCount = (roomType: RoomType): UnreadCountType => {
return settings.get(unreadSetting);
};

const isDesktopDefaultAllMessages = (): boolean => settings.get('Accounts_Default_User_Preferences_desktopNotifications') === 'all';

const isMobileDefaultAllMessages = (): boolean => settings.get('Accounts_Default_User_Preferences_pushNotifications') === 'all';

const shouldCountAllMessagesForSubscription = ({
unreadAlert,
desktopNotifications,
mobilePushNotifications,
}: Pick<ISubscription, 'unreadAlert' | 'desktopNotifications' | 'mobilePushNotifications'>): boolean =>
unreadAlert === 'all' ||
desktopNotifications === 'all' ||
mobilePushNotifications === 'all' ||
(!desktopNotifications && isDesktopDefaultAllMessages()) ||
(!mobilePushNotifications && isMobileDefaultAllMessages());

async function updateUsersSubscriptions(message: IMessage, room: IRoom): Promise<void> {
if (!room || message.tmid) {
return;
Expand All @@ -98,6 +113,8 @@ async function updateUsersSubscriptions(message: IMessage, room: IRoom): Promise

const userMentionInc = getUserMentions(room.t, unreadCount as Exclude<UnreadCountType, 'group_mentions_only'>);
const groupMentionInc = getGroupMentions(room.t, unreadCount as Exclude<UnreadCountType, 'user_mentions_only'>);
const includeSubscriptionsWithDefaultDesktopNotification = isDesktopDefaultAllMessages();
const includeSubscriptionsWithDefaultMobileNotification = isMobileDefaultAllMessages();

// find all subscriptions that will need to be notified after the update.
// we need to use toArray() here and keep results in memory because we'll update the them later
Expand All @@ -106,6 +123,8 @@ async function updateUsersSubscriptions(message: IMessage, room: IRoom): Promise
uidsExclude: [message.u._id],
uidsInclude: userIds,
onlyRead: !toAll && !toHere && !unreadAllMessages,
includeSubscriptionsWithDefaultDesktopNotification,
includeSubscriptionsWithDefaultMobileNotification,
}).toArray();

// Give priority to user mentions over group mentions
Expand All @@ -119,6 +138,26 @@ async function updateUsersSubscriptions(message: IMessage, room: IRoom): Promise
await Subscriptions.incUnreadForRoomIdExcludingUserIds(room._id, [...userIds, message.u._id], 1);
}

const subscriptionIdsToIncrementUnread = subs
.filter((sub) => {
if (!shouldCountAllMessagesForSubscription(sub)) {
return false;
}

const hasUserMention = userIds.includes(sub.u._id);
const alreadyIncrementedUnread =
(hasUserMention && Boolean(userMentionInc)) ||
((toAll || toHere) && Boolean(groupMentionInc)) ||
(!toAll && !toHere && unreadAllMessages && !hasUserMention);

return !alreadyIncrementedUnread;
})
.map(({ _id }) => _id);

if (subscriptionIdsToIncrementUnread.length) {
await Subscriptions.incUnreadForIds(subscriptionIdsToIncrementUnread, 1);
}

// update subscriptions of other members of the room
await Promise.all([
Subscriptions.setAlertForRoomIdExcludingUserId(message.rid, message.u._id),
Expand All @@ -127,7 +166,7 @@ async function updateUsersSubscriptions(message: IMessage, room: IRoom): Promise

subs.forEach((sub) => {
const hasUserMention = userIds.includes(sub.u._id);
const shouldIncUnread = hasUserMention || toAll || toHere || unreadAllMessages;
const shouldIncUnread = hasUserMention || toAll || toHere || unreadAllMessages || shouldCountAllMessagesForSubscription(sub);
void notifyOnSubscriptionChanged(
{
...sub,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,14 @@ function safeDateNotEqualCheck(a: Date | string | undefined, b: Date | string |
return new Date(a).toISOString() !== new Date(b).toISOString();
}

function safeArrayNotEqualCheck(a: string[] | undefined, b: string[] | undefined): boolean {
if (!a || !b) {
return a !== b;
}

return a.length !== b.length || a.some((item, index) => item !== b[index]);
}

const keys: (keyof RoomListRowProps)[] = [
'id',
'style',
Expand Down Expand Up @@ -186,6 +194,27 @@ export default memo(SidebarItemTemplateWithData, (prevProps, nextProps) => {
if (prevProps.room.alert !== nextProps.room.alert) {
return false;
}
if (prevProps.room.unread !== nextProps.room.unread) {
return false;
}
if (prevProps.room.userMentions !== nextProps.room.userMentions) {
return false;
}
if (prevProps.room.groupMentions !== nextProps.room.groupMentions) {
return false;
}
if (safeArrayNotEqualCheck(prevProps.room.tunread, nextProps.room.tunread)) {
return false;
}
if (safeArrayNotEqualCheck(prevProps.room.tunreadUser, nextProps.room.tunreadUser)) {
return false;
}
if (prevProps.room.hideUnreadStatus !== nextProps.room.hideUnreadStatus) {
return false;
}
if (prevProps.room.hideMentionStatus !== nextProps.room.hideMentionStatus) {
return false;
}
if (isOmnichannelRoom(prevProps.room) && isOmnichannelRoom(nextProps.room) && prevProps.room?.v?.status !== nextProps.room?.v?.status) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ function safeDateNotEqualCheck(a: Date | string | undefined, b: Date | string |
return new Date(a).toISOString() !== new Date(b).toISOString();
}

function safeArrayNotEqualCheck(a: string[] | undefined, b: string[] | undefined): boolean {
if (!a || !b) {
return a !== b;
}

return a.length !== b.length || a.some((item, index) => item !== b[index]);
}

const keys: (keyof RoomListRowProps)[] = ['id', 'style', 't', 'videoConfActions'];

// eslint-disable-next-line react/no-multi-comp
Expand All @@ -111,6 +119,27 @@ export default memo(SidebarItemWithData, (prevProps, nextProps) => {
if (prevProps.room.alert !== nextProps.room.alert) {
return false;
}
if (prevProps.room.unread !== nextProps.room.unread) {
return false;
}
if (prevProps.room.userMentions !== nextProps.room.userMentions) {
return false;
}
if (prevProps.room.groupMentions !== nextProps.room.groupMentions) {
return false;
}
if (safeArrayNotEqualCheck(prevProps.room.tunread, nextProps.room.tunread)) {
return false;
}
if (safeArrayNotEqualCheck(prevProps.room.tunreadUser, nextProps.room.tunreadUser)) {
return false;
}
if (prevProps.room.hideUnreadStatus !== nextProps.room.hideUnreadStatus) {
return false;
}
if (prevProps.room.hideMentionStatus !== nextProps.room.hideMentionStatus) {
return false;
}
if (isOmnichannelRoom(prevProps.room) && isOmnichannelRoom(nextProps.room) && prevProps.room?.v?.status !== nextProps.room?.v?.status) {
return false;
}
Expand Down
4 changes: 4 additions & 0 deletions packages/model-typings/src/models/ISubscriptionsModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ export interface ISubscriptionsModel extends IBaseModel<ISubscription> {
uidsExclude?: ISubscription['u']['_id'][];
uidsInclude?: ISubscription['u']['_id'][];
onlyRead: boolean;
includeSubscriptionsWithDefaultDesktopNotification?: boolean;
includeSubscriptionsWithDefaultMobileNotification?: boolean;
},
options?: FindOptions<ISubscription>,
): FindCursor<ISubscription>;
Expand All @@ -114,6 +116,8 @@ export interface ISubscriptionsModel extends IBaseModel<ISubscription> {

incUnreadForRoomIdExcludingUserIds(roomId: IRoom['_id'], userIds: IUser['_id'][], inc: number): Promise<UpdateResult | Document>;

incUnreadForIds(ids: ISubscription['_id'][], inc: number): Promise<UpdateResult | Document>;

setAlertForRoomIdExcludingUserId(roomId: IRoom['_id'], userId: IUser['_id']): Promise<UpdateResult | Document>;

setOpenForRoomIdExcludingUserId(roomId: IRoom['_id'], userId: IUser['_id']): Promise<UpdateResult | Document>;
Expand Down
35 changes: 34 additions & 1 deletion packages/models/src/models/Subscriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,11 +358,15 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
uidsExclude,
uidsInclude,
onlyRead,
includeSubscriptionsWithDefaultDesktopNotification,
includeSubscriptionsWithDefaultMobileNotification,
}: {
roomId: ISubscription['rid'];
uidsExclude?: ISubscription['u']['_id'][];
uidsInclude?: ISubscription['u']['_id'][];
onlyRead: boolean;
includeSubscriptionsWithDefaultDesktopNotification?: boolean;
includeSubscriptionsWithDefaultMobileNotification?: boolean;
},
options?: FindOptions<ISubscription>,
) {
Expand All @@ -372,7 +376,16 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
'u._id': { $nin: uidsExclude },
}),
...(onlyRead && {
$or: [...(uidsInclude?.length ? [{ 'u._id': { $in: uidsInclude } }] : []), { alert: { $ne: true } }, { open: { $ne: true } }],
$or: [
...(uidsInclude?.length ? [{ 'u._id': { $in: uidsInclude } }] : []),
{ unreadAlert: 'all' as const },
{ desktopNotifications: 'all' as const },
{ mobilePushNotifications: 'all' as const },
...(includeSubscriptionsWithDefaultDesktopNotification ? [{ desktopNotifications: { $exists: false } }] : []),
...(includeSubscriptionsWithDefaultMobileNotification ? [{ mobilePushNotifications: { $exists: false } }] : []),
{ alert: { $ne: true } },
{ open: { $ne: true } },
],
}),
};

Expand Down Expand Up @@ -558,6 +571,26 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
return this.updateMany(query, update);
}

incUnreadForIds(ids: ISubscription['_id'][], inc = 1): Promise<UpdateResult | Document> {
const query = {
_id: {
$in: ids,
},
};

const update = {
$set: {
alert: true,
open: true,
},
$inc: {
unread: inc,
},
};

return this.updateMany(query, update);
}

setAlertForRoomIdExcludingUserId(roomId: IRoom['_id'], userId: IUser['_id']): Promise<UpdateResult | Document> {
const query = {
'rid': roomId,
Expand Down