"use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { signIn } from "next-auth/react"; import AccessTimeOutlinedIcon from "@mui/icons-material/AccessTimeOutlined"; import { Alert, Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, Typography, } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import { useAuthStore } from "@/store/authStore"; const WARNING_WINDOW_MS = 15 * 60 * 1000; const EXPIRED_REDIRECT_DELAY_MS = 2_000; type SessionExpiryDialogProps = { expiresAt?: number; }; export const SessionExpiryDialog = ({ expiresAt, }: SessionExpiryDialogProps) => { const theme = useTheme(); const sessionExpired = useAuthStore((state) => state.sessionExpired); const reason = useAuthStore((state) => state.sessionExpiryReason); const [now, setNow] = useState(() => Date.now()); const [warningDismissed, setWarningDismissed] = useState(false); const redirectStartedRef = useRef(false); useEffect(() => { const timer = window.setInterval(() => setNow(Date.now()), 30_000); return () => window.clearInterval(timer); }, []); const isExpiringSoon = useMemo( () => !sessionExpired && !warningDismissed && typeof expiresAt === "number" && expiresAt > now && expiresAt - now <= WARNING_WINDOW_MS, [expiresAt, now, sessionExpired, warningDismissed], ); const handleReauthenticate = useCallback(() => { if (redirectStartedRef.current) return; redirectStartedRef.current = true; const callbackUrl = `${window.location.pathname}${window.location.search}`; void signIn("keycloak", { callbackUrl, redirect: true }).catch(() => { redirectStartedRef.current = false; }); }, []); useEffect(() => { if (!sessionExpired) { redirectStartedRef.current = false; return; } const timer = window.setTimeout( handleReauthenticate, EXPIRED_REDIRECT_DELAY_MS, ); return () => window.clearTimeout(timer); }, [handleReauthenticate, sessionExpired]); const isOpen = sessionExpired || isExpiringSoon; const title = sessionExpired ? "登录已过期" : "登录即将到期"; const detail = sessionExpired ? reason === "session_max_age" ? "已达到 12 小时的最长连续登录时间。请重新认证后继续。" : "无法续期当前登录。请重新认证后继续。" : "当前登录将在 15 分钟内到期。请先保存正在编辑的内容。"; return ( setWarningDismissed(true)} aria-labelledby="session-expiry-dialog-title" > {title} } severity={sessionExpired ? "warning" : "info"}> {detail} 重新认证不会自动重放已失败的写入请求;请在返回后确认内容并再次提交。 {sessionExpired && ( 即将自动跳转到登录页面。 )} {!sessionExpired && ( )} ); };