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

# Conflicts:
#	frontend-web/src/components/workspace/settings/memory-settings-page.tsx
#	frontend-web/src/core/auth/sync.tsx
This commit is contained in:
吉鑫升 committed 2026-06-04 17:49:26 +08:00
commit c1336ad5b3
21 files changed
+877 -181

No files matched your search

+40 -20
View File
@@ -1,50 +1,70 @@
"use client";
import { useEffect, useRef } from "react";
import { toast } from "sonner";
import { getStoredAuth } from "@/core/auth";
const AUTH_STORAGE_KEY = "deerflow.auth";
const SETTLE_DELAY_MS = 1200;
const IDENTITY_TOAST_ID = "auth-identity-changed";
function readUserId(): string | null {
return getStoredAuth()?.user_id ?? null;
}
/**
* Mount once at the app root. Watches `localStorage["deerflow.auth"]` for
* cross-tab changes — when another tab logs in as a different user, this tab
* still holds the previous user's cached state (chat list, thread ids, etc.)
* which then collides with the new token and surfaces as a 404 on "新对话".
* Reloading the tab is the simplest way to resync React state with the new
* identity.
* Mount once at the app root. Watches localStorage auth changes from other
* tabs and lets the user decide when to refresh on account switches.
*/
export function AuthSyncWatcher() {
const lastUserIdRef = useRef<string | null>(readUserId());
useEffect(() => {
function handleStorage(event: StorageEvent) {
// 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;
}
let settleTimer: ReturnType<typeof setTimeout> | null = null;
function evaluateSettledIdentity() {
const nextUserId = readUserId();
if (nextUserId === lastUserIdRef.current) {
return;
}
lastUserIdRef.current = nextUserId;
// Only resync on a genuine account switch in another tab (different,
// non-null user). Do NOT force a login redirect on null here.
if (nextUserId !== null) {
window.location.reload();
if (nextUserId === null) {
return;
}
toast.warning("检测到账号在其它标签页发生变化", {
id: IDENTITY_TOAST_ID,
description: "当前标签页仍是原会话。如需与新账号同步,请刷新页面。",
duration: Infinity,
action: {
label: "刷新同步",
onClick: () => window.location.reload(),
},
});
}
function handleStorage(event: StorageEvent) {
// Ignore unrelated keys and localStorage.clear(). Backend session loss is
// handled centrally through 401 responses.
if (event.key !== AUTH_STORAGE_KEY) {
return;
}
if (settleTimer) {
clearTimeout(settleTimer);
}
settleTimer = setTimeout(evaluateSettledIdentity, SETTLE_DELAY_MS);
}
window.addEventListener("storage", handleStorage);
return () => window.removeEventListener("storage", handleStorage);
return () => {
window.removeEventListener("storage", handleStorage);
if (settleTimer) {
clearTimeout(settleTimer);
}
};
}, []);
return null;