mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-19 13:08:12 +02:00
fix: recover gracefully from stale sessions causing repeated 401s
The SPA's session check (/api/auth/session) is cached indefinitely by react-query (staleTime: Infinity, no refetch on focus/interval), so once a protected page mounts successfully, the app never re-verifies auth. If the server-side session later becomes invalid (server restart with in-memory sessions, cookie/session expiry, etc.), every subsequent API call (pages, scheduled-posts, audio-tracks, media, media/upload, ...) starts failing with 401 in a loop with no way for the user to recover short of a manual page reload. Add a shared handleUnauthorized() in queryClient.ts that redirects to /login on any non-auth API 401, wired into both the shared fetch helpers (apiRequest/getQueryFn) and the raw fetch() calls used for file uploads and Remotion rendering, which also lacked this recovery path. Also add the missing credentials: "include" to those raw fetch() calls for consistency with the rest of the app's API requests.
This commit is contained in:
9 files changed
+81
-24
No files matched your search
@@ -1,7 +1,30 @@
|
||||
import { QueryClient, QueryFunction } from "@tanstack/react-query";
|
||||
|
||||
async function throwIfResNotOk(res: Response) {
|
||||
// Les routes d'authentification elles-mêmes ne doivent jamais déclencher
|
||||
// de redirection automatique (ex: mauvais mot de passe sur /login).
|
||||
function isAuthRoute(url: string): boolean {
|
||||
return url.startsWith("/api/auth/");
|
||||
}
|
||||
|
||||
// Si la session serveur a expiré (ex: redémarrage du serveur, expiration du
|
||||
// cookie) alors que la SPA pense toujours l'utilisateur connecté (le check
|
||||
// de session initial est mis en cache indéfiniment par react-query), toutes
|
||||
// les requêtes suivantes échouent silencieusement en 401 sans jamais
|
||||
// renvoyer l'utilisateur vers l'écran de connexion. On force donc un
|
||||
// rechargement vers /login dès qu'une requête API (hors routes d'auth)
|
||||
// renvoie 401, ce qui réinitialise aussi le cache react-query.
|
||||
export function handleUnauthorized(url: string) {
|
||||
if (isAuthRoute(url) || window.location.pathname === "/login") {
|
||||
return;
|
||||
}
|
||||
window.location.href = "/login";
|
||||
}
|
||||
|
||||
async function throwIfResNotOk(res: Response, url: string) {
|
||||
if (!res.ok) {
|
||||
if (res.status === 401) {
|
||||
handleUnauthorized(url);
|
||||
}
|
||||
const text = (await res.text()) || res.statusText;
|
||||
throw new Error(`${res.status}: ${text}`);
|
||||
}
|
||||
@@ -19,7 +42,7 @@ export async function apiRequest(
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
await throwIfResNotOk(res);
|
||||
await throwIfResNotOk(res, url);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -29,7 +52,8 @@ export const getQueryFn: <T>(options: {
|
||||
}) => QueryFunction<T> =
|
||||
({ on401: unauthorizedBehavior }) =>
|
||||
async ({ queryKey }) => {
|
||||
const res = await fetch(queryKey.join("/") as string, {
|
||||
const url = queryKey.join("/") as string;
|
||||
const res = await fetch(url, {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
@@ -37,7 +61,7 @@ export const getQueryFn: <T>(options: {
|
||||
return null;
|
||||
}
|
||||
|
||||
await throwIfResNotOk(res);
|
||||
await throwIfResNotOk(res, url);
|
||||
return await res.json();
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user