'use client' import { Suspense } from 'react' import { useEffect, useRef, useState } from 'react' import { useSearchParams } from 'next/navigation' import { Stack, Text, Loader } from '@mantine/core' const SSO_STATE_STORAGE_KEY = 'sso_login_state' function CallbackContent() { const searchParams = useSearchParams() const [error, setError] = useState(null) const callbackStarted = useRef(false) useEffect(() => { if (callbackStarted.current) return const code = searchParams.get('sso_code') const state = searchParams.get('state') const expectedState = window.sessionStorage.getItem(SSO_STATE_STORAGE_KEY) if (!code) { setError('缺少 sso_code 参数') return } if (!state || !expectedState || state !== expectedState) { window.sessionStorage.removeItem(SSO_STATE_STORAGE_KEY) setError('state 无效或已过期,请重新登录') return } callbackStarted.current = true window.sessionStorage.removeItem(SSO_STATE_STORAGE_KEY) // 用 code 调用本应用的 /api/callback 换取用户信息 fetch('/api/callback', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code }), }) .then(async (res) => { if (!res.ok) { const data = await res.json() throw new Error(data.error || '认证失败') } // 成功 -> 跳转到首页 window.location.href = '/' }) .catch((err) => { setError(err instanceof Error ? err.message : '认证失败') }) }, [searchParams]) if (error) { return ( 认证失败 {error} (window.location.href = '/')}> 返回首页重试 ) } return ( 正在验证 SSO 登录... ) } export default function CallbackPage() { return ( 正在验证 SSO 登录... }> ) }