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:
commit
c1336ad5b3
21 files changed
+877
-181
No files matched your search
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user