Merge remote-tracking branch 'origin/ji' into ji

This commit is contained in:
吉鑫升 committed 2026-06-04 17:46:50 +08:00
commit 143e0478ef
2 files changed
+46 -11

No files matched your search

+37 -6
View File
@@ -1,4 +1,8 @@
import { getAuthorizationHeaderValue } from "@/core/auth"; import {
clearStoredAuth,
getAuthorizationHeaderValue,
getStoredAuth,
} from "@/core/auth";
import { withCsrfHeaders } from "./csrf"; import { withCsrfHeaders } from "./csrf";
function withAuthHeaders(headers?: HeadersInit): Headers { function withAuthHeaders(headers?: HeadersInit): Headers {
@@ -8,13 +12,40 @@ function withAuthHeaders(headers?: HeadersInit): Headers {
next.set("Authorization", authHeader); next.set("Authorization", authHeader);
} }
return next; return next;
} }
export function apiFetch(input: RequestInfo | URL, init?: RequestInit) { function requestUrl(input: RequestInfo | URL): string {
return fetch(input, { if (typeof input === "string") return input;
if (input instanceof URL) return input.href;
return (input as Request).url ?? "";
}
export async function apiFetch(input: RequestInfo | URL, init?: RequestInit) {
const response = await fetch(input, {
...init, ...init,
credentials: init?.credentials ?? "include", credentials: init?.credentials ?? "include",
headers: withAuthHeaders(init?.headers), headers: withAuthHeaders(init?.headers),
}); });
// Session is considered lost ONLY when the backend actually answers 401.
// (Previously a stray localStorage change could log the user out client-side
// even though the session was still valid.) Auth endpoints are excluded so a
// failed/transient login attempt never wipes state, and we only act when we
// believed we were logged in — avoiding redirect loops on the login page.
if (response.status === 401) {
const url = requestUrl(input);
const isAuthEndpoint = url.includes("/api/v1/auth/");
if (!isAuthEndpoint && getStoredAuth()) {
clearStoredAuth();
if (
typeof window !== "undefined" &&
!window.location.hash.startsWith("#/login")
) {
window.location.replace("#/login");
}
}
}
return response;
} }
+9 -5
View File
@@ -23,7 +23,12 @@ export function AuthSyncWatcher() {
useEffect(() => { useEffect(() => {
function handleStorage(event: StorageEvent) { function handleStorage(event: StorageEvent) {
if (event.key !== null && event.key !== AUTH_STORAGE_KEY) { // Only react to explicit changes of the auth key. Ignore unrelated keys
// AND `localStorage.clear()` (event.key === null) — embedded sub-apps /
// iframes wiping their own storage used to spuriously log the user out
// here even though the backend session was still valid. Genuine session
// loss is now detected centrally via 401 responses in apiFetch.
if (event.key !== AUTH_STORAGE_KEY) {
return; return;
} }
const nextUserId = readUserId(); const nextUserId = readUserId();
@@ -31,12 +36,11 @@ export function AuthSyncWatcher() {
return; return;
} }
lastUserIdRef.current = nextUserId; lastUserIdRef.current = nextUserId;
if (nextUserId === null) { // Only resync on a genuine account switch in another tab (different,
window.location.replace("#/login"); // non-null user). Do NOT force a login redirect on null here.
if (nextUserId !== null) {
window.location.reload(); window.location.reload();
return;
} }
window.location.reload();
} }
window.addEventListener("storage", handleStorage); window.addEventListener("storage", handleStorage);