"use client"; import { useEffect, useRef } from "react"; import { useSession, signOut } from "next-auth/react"; import { useVoteStore } from "@/lib/store"; /** * 把服务端 /api/me 的真相态同步进本地 vote store。 * * - status === "authenticated" → 拉一次 /api/me,用 votedArtists 覆盖本地 * - status === "unauthenticated" → 清本地(避免上一个用户的票残留给下一个登录者) * - 切换用户(uid 变化) → 重新拉一次 * * 僵尸 session 兜底:NextAuth 用 JWT 策略,cookie 不会因 DB user 被删而失效。 * 当 /api/me 返回 401(签名失效) 或 NOT_FOUND(DB 里 user 已不存在) 时, * 自动 signOut() 清 cookie —— 避免页面"假登录"假象(显示已登录但拉不到数据)。 * * localStorage 仅作为本设备的缓存加速首屏渲染,服务端永远是唯一真相源。 */ export function useSyncMe() { const { data, status } = useSession(); const hydrateFromServer = useVoteStore((s) => s.hydrateFromServer); const reset = useVoteStore((s) => s.reset); const lastSyncedUidRef = useRef(null); const sessionUser = data?.user as { id?: string } | undefined; const uid = sessionUser?.id ?? null; useEffect(() => { if (status === "loading") return; if (status === "unauthenticated") { if (lastSyncedUidRef.current !== null) { reset(); lastSyncedUidRef.current = null; } return; } // authenticated if (!uid) return; if (lastSyncedUidRef.current === uid) return; let cancelled = false; const ctrl = new AbortController(); const timer = setTimeout(() => ctrl.abort(), 8000); fetch("/api/me", { credentials: "include", signal: ctrl.signal, }) .then(async (r) => { if (cancelled) return; const res = await r.json().catch(() => null); if (r.ok && res?.ok && Array.isArray(res.data?.votedArtists)) { hydrateFromServer(res.data.votedArtists as string[]); lastSyncedUidRef.current = uid; return; } // 僵尸 session:JWT 还有效,但 DB 里 user 已不存在(或鉴权失效)。 // 直接登出清 cookie,UI 状态切换为未登录,避免"显示已登录但拉不到数据"。 const code: string | undefined = res?.error?.code; if (r.status === 401 || code === "UNAUTHORIZED" || code === "NOT_FOUND") { signOut({ redirect: false }); reset(); lastSyncedUidRef.current = null; } }) .catch(() => { // 网络失败容忍 —— 下次 status 变化或手动刷新会再试 }) .finally(() => clearTimeout(timer)); return () => { cancelled = true; ctrl.abort(); clearTimeout(timer); }; }, [status, uid, hydrateFromServer, reset]); }