refactoring-backend #3

Merged
lukas.pupkalipinski merged 403 commits from refactoring-backend into main 2026-05-20 20:23:46 +02:00
2 changed files with 25 additions and 39 deletions
Showing only changes of commit 3376009903 - Show all commits

View File

@@ -1,32 +1,3 @@
### Issue #49: HIGH - Dual Auth Error Handlers With No Deduplication Guard
**Where found**:
- `frontend/src/providers/AuthProvider.tsx:50-56`
- `frontend/src/api/client.ts:143` `setUnauthorizedHandler`
- `frontend/src/utils/fetchError.ts` `setAuthErrorHandler`
**Why this is needed**:
Both handlers fire independently on a 401. Without a deduplication guard, logout logic executes twice, causing a React state mutation race and potentially double-navigating to the login page.
**Goal**:
Ensure auth error handling fires exactly once per 401 response.
**What to do**:
1. Introduce a module-level `isLoggingOut` flag (or use a ref) that is set before the first logout dispatch and checked before the second.
2. Alternatively, consolidate into a single handler and remove one registration.
**Possible traps and issues**:
- The "defense-in-depth" rationale for two handlers may be intentional; confirm with team before removing one.
- The flag must be reset after the login page is reached.
**Docs changes needed**:
- Add comment in `AuthProvider.tsx` explaining the deduplication guard.
**Doc references**:
- `frontend/src/providers/AuthProvider.tsx` inline comment about cross-tab logout
---
### Issue #50: HIGH - Navigation Abort Signal Timing Bug
**Where found**:

View File

@@ -41,6 +41,12 @@ interface NavigationCancellationProviderProps {
*
* Detects route changes via useLocation and automatically aborts
* all AbortSignals obtained during the previous route.
*
* Timing contract:
* - AbortController is created synchronously in render when pathname changes,
* before any request can be initiated on the new route.
* - The old controller is aborted inside the effect (after render).
* - Strict Mode double-invocation: abort is idempotent, so double-abort is safe.
*/
export function NavigationCancellationProvider(
props: NavigationCancellationProviderProps,
@@ -48,22 +54,31 @@ export function NavigationCancellationProvider(
const { children } = props;
const location = useLocation();
// Current active AbortController for this route
const controllerRef = useRef<AbortController>(new AbortController());
// Current active AbortController for this route.
// Created synchronously during render when pathname changes,
// so the fresh signal is available before any request is initiated.
const controllerRef = useRef<AbortController | null>(null);
const prevPathnameRef = useRef<string>(location.pathname);
// Create new controller synchronously when pathname changes.
// This ensures getNavigationSignal() returns a non-aborted signal
// for requests initiated during the same render pass.
// Abort old controller BEFORE creating new one to prevent
// any request on the new route from using the old (aborted) signal.
if (controllerRef.current === null || location.pathname !== prevPathnameRef.current) {
controllerRef.current?.abort();
controllerRef.current = new AbortController();
}
const getNavigationSignal = useCallback((): AbortSignal => {
return controllerRef.current.signal;
return controllerRef.current!.signal;
}, []);
// When route pathname changes (not on mount), abort the old controller and create a new one
// Update prevPathnameRef after synchronous controller creation.
// This runs after render so we don't abort on Strict Mode remounts
// (where pathname is unchanged between renders).
useEffect(() => {
const currentPathname = location.pathname;
if (currentPathname !== prevPathnameRef.current) {
prevPathnameRef.current = currentPathname;
controllerRef.current?.abort();
controllerRef.current = new AbortController();
}
prevPathnameRef.current = location.pathname;
}, [location.pathname]);
const contextValue: NavigationCancellationContextType = {