@@ -3,8 +3,9 @@ import type { UserStatus } from '@rocket.chat/core-typings';
33import 'highlight.js/styles/github.css' ;
44import { sdk } from '../../app/utils/client/lib/SDKClient' ;
55import { onLoggedIn } from '../lib/loggedIn' ;
6- import { ensureConnectedAndAuthenticated , getDdpSdk } from '../lib/sdk/ddpSdk' ;
6+ import { clearStoredCredentials , ensureConnectedAndAuthenticated , getDdpSdk , isAuthError } from '../lib/sdk/ddpSdk' ;
77import { isSdkTransportEnabled } from '../lib/sdk/sdkTransportEnabled' ;
8+ import { STORAGE_KEYS , getStoredItem } from '../lib/sdk/storage' ;
89import { userIdStore } from '../lib/user' ;
910import { removeLocalUserData , synchronizeUserData } from '../lib/userData' ;
1011import { fireGlobalEvent } from '../lib/utils/fireGlobalEvent' ;
@@ -47,12 +48,33 @@ if (!sdkTransportEnabled) {
4748 // of userIdStore, so without sequencing the sub races the auth and
4849 // hits the rejection on every re-login. Await the SDK auth here so
4950 // the sub fires authenticated.
51+ const tokenBeforeSync = getStoredItem ( STORAGE_KEYS . LOGIN_TOKEN ) ;
5052 try {
5153 await ensureConnectedAndAuthenticated ( ) ;
5254 } catch {
5355 // non-fatal: sdk.stream queues until DDPSDK eventually auths
5456 }
55- const user = await synchronizeUserData ( uid ) ;
57+
58+ let user : Awaited < ReturnType < typeof synchronizeUserData > > ;
59+ try {
60+ user = await synchronizeUserData ( uid ) ;
61+ } catch ( error ) {
62+ // When the stored token is expired/revoked server-side, the userData
63+ // stream sub + /v1/me come back 401/403, so synchronizeUserData throws
64+ // and useUserDataSyncReady never flips true. useMainReady then stays
65+ // false forever (uid is still truthy from the stale credentials), so
66+ // Preload sits on PageLoading — and because Preload WRAPS
67+ // AuthenticationCheck, the user never reaches LoginPage and the
68+ // workspace looks stuck "loading"/grayed-out. Escalate the dead-session
69+ // signal to a credential wipe so userId drops to null and the router
70+ // falls through to the login screen. Token-stable guard: only clear
71+ // when localStorage still holds the token we synced with, so a parallel
72+ // re-auth that already rotated the token isn't kicked out.
73+ if ( isAuthError ( error ) && getStoredItem ( STORAGE_KEYS . LOGIN_TOKEN ) === tokenBeforeSync ) {
74+ clearStoredCredentials ( ) ;
75+ }
76+ throw error ;
77+ }
5678 if ( ! user ) return ;
5779
5880 const utcOffset = - new Date ( ) . getTimezoneOffset ( ) / 60 ;
0 commit comments