Files
cozsweet-frontend-nextjs/src/stores/auth/auth-status-checker.tsx
T

74 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
/**
* AuthStatusChecker 启动 + 状态变化检查器
*
* 触发时机:
* 1. App 启动时(mount)派发一次 `AuthStatusCheckSubmitted`
* 2. loginStatus 每次变化时(外部触发)也派发一次 —— 重新读 storage 校准
* 例:OAuth sync 完成 → loginStatus 变 "facebook" → re-check 一次确认 storage 一致
* 邮箱登录成功 → loginStatus 变 "email" → re-check 确认 token 已落盘
*
* 死循环防护(关键 —— 不加会无限递归):
* - 状态机收 `AuthStatusCheckSubmitted` → 进 `checkingAuthStatus` → 读 storage → onDone 写回 loginStatus
* - 如果 useEffect 直接依赖 `loginStatus`,写回本身触发 effect → 再 dispatch → 死循环
* - 用 `skipNextChangeRef` 抑制一次:
* 每次我们自己 dispatch 前把 flag 置 true
* effect 见到 loginStatus 变化且 flag 为 true 时跳过并清 flag。
*
* 与 <OAuthSessionSync /> 平级:
* - AuthStatusChecker: 启动 + 状态变化时 re-check"自己也能感知"
* - OAuthSessionSync: 持续监听 NextAuth session"别人也在写"
*/
import { useEffect, useRef } from "react";
import type { LoginStatus } from "@/models/auth/login-status";
import { useAuthDispatch, useAuthState } from "./auth-context";
export function AuthStatusChecker() {
const dispatch = useAuthDispatch();
const loginStatus = useAuthState().loginStatus;
// 上一次的 loginStatus(首次 mount 留 null —— 用来跳过首次 effect 跑)
const prevLoginStatusRef = useRef<LoginStatus | null>(null);
// 抑制下一次 loginStatus 变化触发的 effect(自己 dispatch 引起的写回要跳过)
const skipNextChangeRef = useRef(false);
// ─────────────────────────────────────────────────────────────
// ① App 启动时:派发一次
// ─────────────────────────────────────────────────────────────
useEffect(() => {
console.log("[auth-status-checker] mount → dispatch AuthStatusCheckSubmitted");
skipNextChangeRef.current = true; // 抑制启动 check 写回 loginStatus 引起的 effect
dispatch({ type: "AuthStatusCheckSubmitted" });
}, [dispatch]);
// ─────────────────────────────────────────────────────────────
// ② loginStatus 变化时:派发一次(但跳过我们自己 check 写回的那次)
// ─────────────────────────────────────────────────────────────
useEffect(() => {
const prev = prevLoginStatusRef.current;
prevLoginStatusRef.current = loginStatus;
if (prev === null) return; // 首次 mount —— 启动那次由 ① 处理
if (prev === loginStatus) return; // loginStatus 没变 —— 不触发
if (skipNextChangeRef.current) {
// 这是我们自己 check 写回的 —— 抑制本次
skipNextChangeRef.current = false;
console.log(
"[auth-status-checker] loginStatus change suppressed (self-triggered)",
{ from: prev, to: loginStatus },
);
return;
}
// 外部触发(OAuth sync / 邮箱登录 / 登出 等)的 loginStatus 变化 → 再 check 一次校准
skipNextChangeRef.current = true;
console.log(
"[auth-status-checker] loginStatus changed externally → re-check",
{ from: prev, to: loginStatus },
);
dispatch({ type: "AuthStatusCheckSubmitted" });
}, [loginStatus, dispatch]);
// 无 UI
return null;
}