fix(ui): login page refresh loop — guard 401 redirect + gate background polls on auth

The global mesh-detection poll (added for the device-detected modal)
fired on the login page, 401'd, and the session-expired handler
redirected to /login — reloading the page we were already on, forever.
Background polls now run only when authenticated, and a 401 never
redirects when the path is already /login.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-07-14 09:16:25 -04:00
parent 4e11f80771
commit 23e214a85c
2 changed files with 12 additions and 3 deletions

View File

@ -117,8 +117,12 @@ class RPCClient {
try { localStorage.removeItem('neode-auth') } catch { /* noop */ }
const isOnboarding = window.location.pathname.startsWith('/onboarding')
// Already on the login page: redirecting = a full reload of the
// page we're on. Any background poll that 401s would refresh
// /login in a loop forever (seen with the global mesh poll).
const isLogin = window.location.pathname.startsWith('/login')
console.warn(`[RPC] 401 on ${method} | path=${window.location.pathname} | onboarding=${isOnboarding} | redirecting=${RPCClient._sessionExpiredRedirecting}`)
if (!isOnboarding && !RPCClient._sessionExpiredRedirecting) {
if (!isOnboarding && !isLogin && !RPCClient._sessionExpiredRedirecting) {
RPCClient._sessionExpiredRedirecting = true
console.warn(`[RPC] Session expired — redirecting to /login in 300ms`)
setTimeout(() => {

View File

@ -280,9 +280,14 @@ export const useMeshStore = defineStore('mesh', () => {
* (the Mesh view's own 5s poll takes over while it is mounted). */
function startGlobalDetection(intervalMs = 15000) {
if (globalDetectTimer) return
void fetchStatus()
// Never poll unauthenticated — a 401 from a background poll on the
// login page caused a login-refresh loop (2026-07-14, .116).
const authed = () => {
try { return !!localStorage.getItem('neode-auth') } catch { return false }
}
if (authed()) void fetchStatus()
globalDetectTimer = setInterval(() => {
if (document.visibilityState === 'visible') void fetchStatus()
if (document.visibilityState === 'visible' && authed()) void fetchStatus()
}, intervalMs)
}