11import { manageFavicon } from '@rocket.chat/favicon' ;
22import { useSession , useSessionDispatch , useUserPreference , useUserSubscriptions } from '@rocket.chat/ui-contexts' ;
3- import { useEffect } from 'react' ;
3+ import { useEffect , useRef } from 'react' ;
44
55import { useFireGlobalEvent } from '../../../../hooks/useFireGlobalEvent' ;
66
77const query = { open : { $ne : false } , hideUnreadStatus : { $ne : true } , archived : { $ne : true } } ;
88const options = { fields : { unread : 1 , alert : 1 , rid : 1 , t : 1 , name : 1 , ls : 1 , unreadAlert : 1 , fname : 1 , prid : 1 } } ;
99const updateFavicon = manageFavicon ( ) ;
1010
11+ type UnreadData = { unread : number ; alert : boolean | undefined ; unreadAlert : string | undefined } ;
12+
1113export const useUnread = ( ) => {
1214 const unreadAlertEnabled = useUserPreference ( 'unreadAlert' ) ;
1315 const setUnread = useSessionDispatch ( 'unread' ) ;
@@ -18,37 +20,48 @@ export const useUnread = () => {
1820
1921 const subscriptions = useUserSubscriptions ( query , options ) ;
2022
23+ // We keep a lightweight snapshot of the last emitted per-subscription unread state so we only
24+ // fire "unread-changed-by-subscription" for subscriptions whose unread-relevant fields changed.
25+ // Previously we emitted one global event per subscription on ANY change, which scaled O(N)
26+ // with the user subscription count (thousands) for every single message event, dominating CPU.
27+ const prevSubsRef = useRef ( new Map < string , UnreadData > ( ) ) ;
28+
2129 useEffect ( ( ) => {
22- let unreadAlert : false | '•' = false ;
30+ let badgeIndicator : false | '•' = false ;
31+ let unreadCount = 0 ;
32+ const nextSnapshot = new Map < string , UnreadData > ( ) ;
2333
24- const unreadCount = subscriptions . reduce ( ( ret , subscription ) => {
25- fireEventUnreadChangedBySubscription ( subscription ) ;
34+ for ( const subscription of subscriptions ) {
35+ const { rid, unread : unreadValue , alert, unreadAlert : subscriptionUnreadAlert } = subscription ;
36+ const prev = prevSubsRef . current . get ( rid ) ;
37+ // Emit per-sub event only if something that influences unread UI changed.
38+ if ( ! prev || prev . unread !== unreadValue || prev . alert !== alert || prev . unreadAlert !== subscriptionUnreadAlert ) {
39+ fireEventUnreadChangedBySubscription ( subscription ) ;
40+ }
41+ nextSnapshot . set ( rid , { unread : unreadValue , alert, unreadAlert : subscriptionUnreadAlert } ) ;
2642
27- if ( subscription . alert || subscription . unread > 0 ) {
28- // Increment the total unread count.
29- if ( subscription . alert === true && subscription . unreadAlert !== 'nothing' ) {
30- if ( subscription . unreadAlert === 'all' || unreadAlertEnabled !== false ) {
31- unreadAlert = '•' ;
43+ if ( alert || unreadValue > 0 ) {
44+ if ( alert === true && subscriptionUnreadAlert !== 'nothing' ) {
45+ if ( subscriptionUnreadAlert === 'all' || unreadAlertEnabled !== false ) {
46+ badgeIndicator = '•' ;
3247 }
3348 }
34- return ret + subscription . unread ;
49+ unreadCount += unreadValue ;
3550 }
36- return ret ;
37- } , 0 ) ;
51+ }
52+
53+ prevSubsRef . current = nextSnapshot ; // swap snapshot
3854
3955 if ( unreadCount > 0 ) {
40- if ( unreadCount > 999 ) {
41- setUnread ( '999+' ) ;
42- } else {
43- setUnread ( unreadCount ) ;
44- }
45- } else if ( unreadAlert !== false ) {
46- setUnread ( unreadAlert ) ;
56+ setUnread ( unreadCount > 999 ? '999+' : unreadCount ) ;
57+ } else if ( badgeIndicator !== false ) {
58+ setUnread ( badgeIndicator ) ;
4759 } else {
4860 setUnread ( '' ) ;
4961 }
62+
5063 fireEventUnreadChanged ( unreadCount ) ;
51- } , [ setUnread , unread , subscriptions , unreadAlertEnabled , fireEventUnreadChangedBySubscription , fireEventUnreadChanged ] ) ;
64+ } , [ setUnread , subscriptions , unreadAlertEnabled , fireEventUnreadChangedBySubscription , fireEventUnreadChanged ] ) ;
5265
5366 useEffect ( ( ) => {
5467 updateFavicon ( unread ) ;
0 commit comments