From 6cf72e204c38057e00b795e949228757182d51e1 Mon Sep 17 00:00:00 2001 From: devanshkansagra <125076549+devanshkansagra@users.noreply.github.com> Date: Tue, 19 May 2026 01:48:03 +0530 Subject: [PATCH] Fixes: 40461 --- .../lib/server/lib/notifyUsersOnMessage.ts | 43 ++++++++++++++++++- .../RoomList/SidebarItemTemplateWithData.tsx | 29 +++++++++++++ .../sidebar/RoomList/SidebarItemWithData.tsx | 29 +++++++++++++ .../src/models/ISubscriptionsModel.ts | 4 ++ packages/models/src/models/Subscriptions.ts | 35 ++++++++++++++- 5 files changed, 137 insertions(+), 3 deletions(-) diff --git a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts index 80cf172c9dcb1..26dbd7e220cd7 100644 --- a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts +++ b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts @@ -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'; @@ -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): boolean => + unreadAlert === 'all' || + desktopNotifications === 'all' || + mobilePushNotifications === 'all' || + (!desktopNotifications && isDesktopDefaultAllMessages()) || + (!mobilePushNotifications && isMobileDefaultAllMessages()); + async function updateUsersSubscriptions(message: IMessage, room: IRoom): Promise { if (!room || message.tmid) { return; @@ -98,6 +113,8 @@ async function updateUsersSubscriptions(message: IMessage, room: IRoom): Promise const userMentionInc = getUserMentions(room.t, unreadCount as Exclude); const groupMentionInc = getGroupMentions(room.t, unreadCount as Exclude); + 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 @@ -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 @@ -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), @@ -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, diff --git a/apps/meteor/client/sidebar/RoomList/SidebarItemTemplateWithData.tsx b/apps/meteor/client/sidebar/RoomList/SidebarItemTemplateWithData.tsx index d297e8c6a9416..1bd8b7f648b52 100644 --- a/apps/meteor/client/sidebar/RoomList/SidebarItemTemplateWithData.tsx +++ b/apps/meteor/client/sidebar/RoomList/SidebarItemTemplateWithData.tsx @@ -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', @@ -187,6 +195,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; } diff --git a/apps/meteor/client/views/navigation/sidebar/RoomList/SidebarItemWithData.tsx b/apps/meteor/client/views/navigation/sidebar/RoomList/SidebarItemWithData.tsx index 79b933e59ca3d..24fe7f1391837 100644 --- a/apps/meteor/client/views/navigation/sidebar/RoomList/SidebarItemWithData.tsx +++ b/apps/meteor/client/views/navigation/sidebar/RoomList/SidebarItemWithData.tsx @@ -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 @@ -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; } diff --git a/packages/model-typings/src/models/ISubscriptionsModel.ts b/packages/model-typings/src/models/ISubscriptionsModel.ts index 8273feec9f517..5daa034b821cf 100644 --- a/packages/model-typings/src/models/ISubscriptionsModel.ts +++ b/packages/model-typings/src/models/ISubscriptionsModel.ts @@ -88,6 +88,8 @@ export interface ISubscriptionsModel extends IBaseModel { uidsExclude?: ISubscription['u']['_id'][]; uidsInclude?: ISubscription['u']['_id'][]; onlyRead: boolean; + includeSubscriptionsWithDefaultDesktopNotification?: boolean; + includeSubscriptionsWithDefaultMobileNotification?: boolean; }, options?: FindOptions, ): FindCursor; @@ -114,6 +116,8 @@ export interface ISubscriptionsModel extends IBaseModel { incUnreadForRoomIdExcludingUserIds(roomId: IRoom['_id'], userIds: IUser['_id'][], inc: number): Promise; + incUnreadForIds(ids: ISubscription['_id'][], inc: number): Promise; + setAlertForRoomIdExcludingUserId(roomId: IRoom['_id'], userId: IUser['_id']): Promise; setOpenForRoomIdExcludingUserId(roomId: IRoom['_id'], userId: IUser['_id']): Promise; diff --git a/packages/models/src/models/Subscriptions.ts b/packages/models/src/models/Subscriptions.ts index 679080739d3ba..3b603eb1e61fc 100644 --- a/packages/models/src/models/Subscriptions.ts +++ b/packages/models/src/models/Subscriptions.ts @@ -358,11 +358,15 @@ export class SubscriptionsRaw extends BaseRaw 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, ) { @@ -372,7 +376,16 @@ export class SubscriptionsRaw extends BaseRaw 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 } }, + ], }), }; @@ -558,6 +571,26 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri return this.updateMany(query, update); } + incUnreadForIds(ids: ISubscription['_id'][], inc = 1): Promise { + 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 { const query = { 'rid': roomId,