Re-check session on tab focus to fix stale user name in sidebar footer

If a different user logs into a child app while the portal tab is open,
the portal cookie changes but the in-memory user state goes stale. Now
AuthGate re-fetches /api/auth/me on visibilitychange and focus events,
updating state only when the user identity actually changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-15 10:48:52 +00:00
parent d0fb78da51
commit 2b4de33289

View file

@ -18,16 +18,44 @@ export function AuthGate({ children }: Props) {
const [checking, setChecking] = useState(true) const [checking, setChecking] = useState(true)
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation() const location = useLocation()
const userRef = useRef<User | null>(null)
useEffect(() => { function fetchMe(isInitial = false) {
fetch('/api/auth/me', { credentials: 'include' }) fetch('/api/auth/me', { credentials: 'include' })
.then(r => r.ok ? r.json() : null) .then(r => r.ok ? r.json() : null)
.then(data => { .then(data => {
if (data) setUser(data) if (data) {
else navigate('/login', { replace: true, state: { from: location.pathname } }) // Only update state if the user identity has changed, to avoid re-renders on every focus
if (!userRef.current || userRef.current.id !== data.id) {
userRef.current = data
setUser(data)
}
} else {
navigate('/login', { replace: true, state: isInitial ? { from: location.pathname } : undefined })
}
}) })
.catch(() => navigate('/login', { replace: true })) .catch(() => navigate('/login', { replace: true }))
.finally(() => setChecking(false)) .finally(() => { if (isInitial) setChecking(false) })
}
useEffect(() => {
fetchMe(true)
// Re-check session on tab focus / visibility — catches the case where a
// different user logged into a child app and the portal cookie changed.
let pending = false
function onVisible() {
if (document.visibilityState !== 'visible' || pending) return
pending = true
setTimeout(() => { pending = false; fetchMe() }, 100)
}
document.addEventListener('visibilitychange', onVisible)
window.addEventListener('focus', onVisible)
return () => {
document.removeEventListener('visibilitychange', onVisible)
window.removeEventListener('focus', onVisible)
}
}, []) }, [])
if (checking) { if (checking) {