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:
Claude committed 2026-07-27 11:56:56 +00:00
1 parent 75e1d88c26
commit 2188fa458d
9 files changed
+81 -24

No files matched your search

+11 -3
View File
@@ -3,7 +3,7 @@ import { useDropzone } from "react-dropzone";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { useMutation, useQuery } from "@tanstack/react-query";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { apiRequest, queryClient, handleUnauthorized } from "@/lib/queryClient";
import { MediaThumbnail } from "@/components/media-thumbnail";
import { CloudUpload, Image as ImageIcon, Video, X, Upload, Loader2, ZoomIn, Camera } from "lucide-react";
import { useToast } from "@/hooks/use-toast";
@@ -30,8 +30,12 @@ export default function MediaUpload() {
const response = await fetch("/api/media/upload", {
method: "POST",
body: formData,
credentials: "include",
});
if (!response.ok) throw new Error("Upload failed");
if (!response.ok) {
if (response.status === 401) handleUnauthorized("/api/media/upload");
throw new Error("Upload failed");
}
return response.json();
},
onSuccess: (data) => {
@@ -55,8 +59,12 @@ export default function MediaUpload() {
const response = await fetch("/api/media/upload", {
method: "POST",
body: formData,
credentials: "include",
});
if (!response.ok) throw new Error("Upload failed");
if (!response.ok) {
if (response.status === 401) handleUnauthorized("/api/media/upload");
throw new Error("Upload failed");
}
successCount++;
return response.json();
} catch (error) {