From d2dfe6299309484461f5dcae9ac61222174260f5 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Sat, 7 Mar 2026 19:37:41 +0100 Subject: [PATCH] fix: recover gracefully from stale Server Action hashes after deployment Wrap all Server Action calls (getSessionAction, exchangeAuthCode, logoutAction) in try-catch so a cached browser bundle with old action IDs triggers a hard reload instead of an infinite loading spinner. --- CHANGELOG.md | 1 + app/auth/callback/page.tsx | 9 ++++++++- lib/auth/context.tsx | 19 ++++++++++++++++--- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbcb278..2fee81c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Fixed +- **Login no longer gets stuck after updates.** If you happened to have Pulse open when a new version was deployed, logging back in could get stuck on a loading screen. The app now automatically refreshes itself to pick up the latest version. - **City and region data is now accurate.** Location data was incorrectly showing the CDN server's location (e.g. Paris, Villeurbanne) instead of the visitor's actual city. Fixed by reading the correct visitor IP header from Bunny CDN. - **"Reset Data" now clears everything.** Previously, resetting a site's data in Settings only removed pageviews and daily stats. Uptime check history, uptime daily stats, and cached dashboard data were left behind. All collected data is now properly cleared when you reset, while your site configuration, goals, funnels, and uptime monitors are kept. diff --git a/app/auth/callback/page.tsx b/app/auth/callback/page.tsx index 2359b75..933fdce 100644 --- a/app/auth/callback/page.tsx +++ b/app/auth/callback/page.tsx @@ -22,7 +22,14 @@ function AuthCallbackContent() { const codeVerifier = localStorage.getItem('oauth_code_verifier') const redirectUri = typeof window !== 'undefined' ? window.location.origin + '/auth/callback' : '' if (!code) return - const result = await exchangeAuthCode(code, codeVerifier, redirectUri) + let result: Awaited> + try { + result = await exchangeAuthCode(code, codeVerifier, redirectUri) + } catch { + // * Stale build — cached JS has old Server Action hashes. Hard reload to fix. + window.location.reload() + return + } if (result.success && result.user) { // * Fetch full profile (including display_name) before navigating so header shows correct name on first paint try { diff --git a/lib/auth/context.tsx b/lib/auth/context.tsx index 95f8b39..d9adb92 100644 --- a/lib/auth/context.tsx +++ b/lib/auth/context.tsx @@ -90,7 +90,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { const logout = useCallback(async () => { setIsLoggingOut(true) - await logoutAction() + try { await logoutAction() } catch { /* stale build — continue with client-side cleanup */ } localStorage.removeItem('user') localStorage.removeItem('ciphera_token_refreshed_at') localStorage.removeItem('ciphera_last_activity') @@ -132,7 +132,15 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { useEffect(() => { const init = async () => { // * 1. Check server-side session (cookies) - let session = await getSessionAction() + let session: Awaited> = null + try { + session = await getSessionAction() + } catch { + // * Stale build — browser has cached JS with old Server Action hashes. + // * Force a hard reload to fetch fresh bundles from the server. + window.location.reload() + return + } // * 2. If no access_token but refresh_token may exist, try refresh (fixes 15-min inactivity logout) if (!session && typeof window !== 'undefined') { @@ -142,7 +150,12 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { credentials: 'include', }) if (refreshRes.ok) { - session = await getSessionAction() + try { + session = await getSessionAction() + } catch { + window.location.reload() + return + } } }