@@ -11,13 +11,11 @@ import type { Method, PathFor, OperationParams, OperationResult, UrlParams, Path
1111import type { UploadResult , ServerContextValue } from '@rocket.chat/ui-contexts' ;
1212import { ServerContext } from '@rocket.chat/ui-contexts' ;
1313import { Meteor } from 'meteor/meteor' ;
14- import { Tracker } from 'meteor/tracker' ;
1514import { compile } from 'path-to-regexp' ;
16- import { useMemo , type ReactNode } from 'react' ;
15+ import { useMemo , useSyncExternalStore , type ReactNode } from 'react' ;
1716
1817import { sdk } from '../../app/utils/client/lib/SDKClient' ;
1918import { Info as info } from '../../app/utils/rocketchat.info' ;
20- import { useReactiveValue } from '../hooks/useReactiveValue' ;
2119import { absoluteUrl } from '../lib/absoluteUrl' ;
2220import { ensureConnectedAndAuthenticated , getDdpSdk } from '../lib/sdk/ddpSdk' ;
2321import { isSdkTransportEnabled } from '../lib/sdk/sdkTransportEnabled' ;
@@ -99,16 +97,6 @@ const reconnect = sdkTransportEnabled
9997 }
10098 : ( ) => Meteor . reconnect ( ) ;
10199
102- // With SDK transport on, combine Meteor's DDP status with DDPSDK's so the
103- // ConnectionStatusBar / idle-connection hooks reflect the worst-case of both
104- // transports. With the flag off, route status straight through Meteor —
105- // Meteor.status() is already Tracker-reactive, so adding a second dependency
106- // via the meteor-backed proxy's emitter would just double-fire the autorun.
107- const ddpSdkStatusDep = sdkTransportEnabled ? new Tracker . Dependency ( ) : undefined ;
108- if ( sdkTransportEnabled ) {
109- getDdpSdk ( ) . connection . on ( 'connection' , ( ) => ddpSdkStatusDep ! . changed ( ) ) ;
110- }
111-
112100type CombinedStatus = ReturnType < typeof Meteor . status > ;
113101
114102const sdkStatusToMeteor = ( sdkStatus : string , meteor : CombinedStatus ) : CombinedStatus => {
@@ -132,21 +120,48 @@ const sdkStatusToMeteor = (sdkStatus: string, meteor: CombinedStatus): CombinedS
132120 }
133121} ;
134122
135- const getStatus = sdkTransportEnabled
136- ? ( ) => {
137- ddpSdkStatusDep ! . depend ( ) ;
138- return sdkStatusToMeteor ( getDdpSdk ( ) . connection . status , Meteor . status ( ) ) ;
139- }
140- : // useReactiveValue stores the snapshot in useSyncExternalStore, which
141- // compares by identity. Meteor.status() reuses the same internal object
142- // (mutated in place), so without spreading the snapshot reference never
143- // changes and ConnectionStatusBar stops re-rendering on connect/drop.
144- ( ) => ( { ...Meteor . status ( ) } ) ;
123+ // With SDK transport on, combine Meteor's DDP status with DDPSDK's so the
124+ // ConnectionStatusBar / idle-connection hooks reflect the worst-case of both
125+ // transports. With the flag off, route status straight through Meteor —
126+ // `meteorBackedSdk` already bridges Meteor's `_stream` events into
127+ // `sdk.connection.on('connection')`, so the same subscription works in both
128+ // modes.
129+ const computeStatus : ( ) => CombinedStatus = sdkTransportEnabled
130+ ? ( ) => sdkStatusToMeteor ( getDdpSdk ( ) . connection . status , Meteor . status ( ) )
131+ : ( ) => ( { ...Meteor . status ( ) } ) ;
132+
133+ const isStatusEqual = ( a : CombinedStatus , b : CombinedStatus ) : boolean =>
134+ a . status === b . status && a . connected === b . connected && a . retryCount === b . retryCount && a . retryTime === b . retryTime ;
135+
136+ let cachedStatus : CombinedStatus = computeStatus ( ) ;
137+ const statusListeners = new Set < ( ) => void > ( ) ;
138+ let statusBridgeStarted = false ;
139+
140+ const ensureStatusBridge = ( ) : void => {
141+ if ( statusBridgeStarted ) return ;
142+ statusBridgeStarted = true ;
143+ getDdpSdk ( ) . connection . on ( 'connection' , ( ) => {
144+ const next = computeStatus ( ) ;
145+ if ( isStatusEqual ( cachedStatus , next ) ) return ;
146+ cachedStatus = next ;
147+ statusListeners . forEach ( ( cb ) => cb ( ) ) ;
148+ } ) ;
149+ } ;
150+
151+ const subscribeStatus = ( cb : ( ) => void ) : ( ( ) => void ) => {
152+ ensureStatusBridge ( ) ;
153+ statusListeners . add ( cb ) ;
154+ return ( ) => {
155+ statusListeners . delete ( cb ) ;
156+ } ;
157+ } ;
158+
159+ const getStatusSnapshot = ( ) : CombinedStatus => cachedStatus ;
145160
146161type ServerProviderProps = { children ?: ReactNode } ;
147162
148163const ServerProvider = ( { children } : ServerProviderProps ) => {
149- const { connected, status, retryCount, retryTime } = useReactiveValue ( getStatus ) ;
164+ const { connected, status, retryCount, retryTime } = useSyncExternalStore ( subscribeStatus , getStatusSnapshot ) ;
150165
151166 const value = useMemo (
152167 ( ) : ServerContextValue => ( {
0 commit comments