From 67c7078128560a7823c6a190ce90ea5b3278a455 Mon Sep 17 00:00:00 2001 From: SaelixCode Date: Thu, 19 Mar 2026 13:57:48 -0400 Subject: [PATCH] fix: stop infinite page reload caused by premature NodeProvider mount and 401 hard-redirect - Move NodeProvider inside the authenticated branch in App.tsx so it only mounts after auth is confirmed; previously it mounted on boot causing refreshNodes to fire before any session existed - Replace window.location.href='/' on 401 in apiFetch with a sencho-unauthorized custom event; AuthContext listens and transitions appStatus to notAuthenticated without a full browser reload --- CHANGELOG.md | 2 ++ frontend/src/App.tsx | 12 +++++++----- frontend/src/context/AuthContext.tsx | 3 +++ frontend/src/lib/api.ts | 4 ++-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10480ec3..a3a18323 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - **Fixed:** Memory leak in `GlobalObservabilityView` SSE mode — log array now capped at 10,000 entries (`.slice(-10000)`) to prevent unbounded accumulation across long sessions. - **Fixed:** Infinite re-fetch loop in `NodeContext` — `refreshNodes` useCallback no longer depends on `activeNode` state; replaced with a `useRef` to read current node inside the callback without being a reactive dependency. +- **Fixed:** Infinite page reload loop — `apiFetch` was calling `window.location.href = '/'` on every 401, causing a full browser reload before auth could complete. Replaced with a `sencho-unauthorized` custom event that `AuthContext` handles by setting `appStatus` to `notAuthenticated`. +- **Fixed:** `NodeProvider` was mounted outside the auth gate in `App.tsx`, causing `refreshNodes` to fire before authentication was established (hitting 401 immediately on boot). Moved `NodeProvider` inside the authenticated branch so it only mounts after login. - **Removed:** SSH/SFTP file adapters and remote Docker TCP connections (net negative ~500 lines of code). - **Added:** Distributed API proxying using http-proxy-middleware for HTTP and WebSockets. - **Added:** Long-lived JWT generation for Sencho-to-Sencho API authentication (`POST /api/auth/generate-node-token`). diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5303b3d6..d1c03e58 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -31,7 +31,11 @@ function AppContent() { ); } - return ; + return ( + + + + ); } import { Toaster } from 'sonner'; @@ -39,10 +43,8 @@ import { Toaster } from 'sonner'; function App() { return ( - - - - + + ); } diff --git a/frontend/src/context/AuthContext.tsx b/frontend/src/context/AuthContext.tsx index 9cbb9816..55b308ea 100644 --- a/frontend/src/context/AuthContext.tsx +++ b/frontend/src/context/AuthContext.tsx @@ -47,6 +47,9 @@ export function AuthProvider({ children }: { children: ReactNode }) { useEffect(() => { checkAuth(); + const handleUnauthorized = () => setAppStatus('notAuthenticated'); + window.addEventListener('sencho-unauthorized', handleUnauthorized); + return () => window.removeEventListener('sencho-unauthorized', handleUnauthorized); }, []); const login = async (username: string, password: string): Promise<{ success: boolean; error?: string }> => { diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index da58afe6..7059763f 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -19,8 +19,8 @@ export async function apiFetch( const response = await fetch(url, { ...defaultOptions, ...options }); if (response.status === 401) { - // Clear auth state and redirect to login - window.location.href = '/'; + // Signal auth failure to AuthContext without a hard page reload + window.dispatchEvent(new Event('sencho-unauthorized')); throw new Error('Unauthorized'); }