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
This commit is contained in:
SaelixCode
2026-03-19 13:57:48 -04:00
parent ac5032d363
commit 67c7078128
4 changed files with 14 additions and 7 deletions
+2
View File
@@ -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`).
+7 -5
View File
@@ -31,7 +31,11 @@ function AppContent() {
);
}
return <EditorLayout />;
return (
<NodeProvider>
<EditorLayout />
</NodeProvider>
);
}
import { Toaster } from 'sonner';
@@ -39,10 +43,8 @@ import { Toaster } from 'sonner';
function App() {
return (
<AuthProvider>
<NodeProvider>
<AppContent />
<Toaster position="bottom-right" richColors />
</NodeProvider>
<AppContent />
<Toaster position="bottom-right" richColors />
</AuthProvider>
);
}
+3
View File
@@ -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 }> => {
+2 -2
View File
@@ -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');
}