SSE鉴权失效停止重连清理会话跳登录并降噪4xx错误日志
This commit is contained in:
@@ -56,7 +56,8 @@ export function sendRouteError(
|
||||
logger(scope, fallbackMessage, {
|
||||
statusCode: classification.statusCode,
|
||||
errorCode: classification.errorCode,
|
||||
error,
|
||||
// 4xx 属预期失败(鉴权过期、参数错误等),带上完整错误对象只会刷十几行堆栈;仅 5xx 保留排查所需详情。
|
||||
...(classification.statusCode >= 500 ? { error } : {}),
|
||||
})
|
||||
res.status(classification.statusCode).json(buildErrorPayload(error, fallbackMessage))
|
||||
}
|
||||
|
||||
@@ -21,16 +21,26 @@ type UseRealtimeEventsOptions = {
|
||||
token: string
|
||||
onEvent: (event: RealtimeEvent) => void
|
||||
onConnected?: () => void
|
||||
/** 鉴权失效(401/403)时触发:连接停止重连,调用方应清理会话并跳转登录页。 */
|
||||
onAuthExpired?: () => void
|
||||
}
|
||||
|
||||
const RECONNECT_DELAY_MS = 3_000
|
||||
|
||||
/** 通过 fetch 保持带 Bearer 鉴权的 SSE 连接,避免把登录 token 暴露到 URL。 */
|
||||
export function useRealtimeEvents({ url, token, onEvent, onConnected }: UseRealtimeEventsOptions) {
|
||||
export function useRealtimeEvents({
|
||||
url,
|
||||
token,
|
||||
onEvent,
|
||||
onConnected,
|
||||
onAuthExpired,
|
||||
}: UseRealtimeEventsOptions) {
|
||||
const onEventRef = useRef(onEvent)
|
||||
const onConnectedRef = useRef(onConnected)
|
||||
const onAuthExpiredRef = useRef(onAuthExpired)
|
||||
onEventRef.current = onEvent
|
||||
onConnectedRef.current = onConnected
|
||||
onAuthExpiredRef.current = onAuthExpired
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) return
|
||||
@@ -50,6 +60,7 @@ export function useRealtimeEvents({ url, token, onEvent, onConnected }: UseRealt
|
||||
Authorization: `Bearer ${token}`,
|
||||
}
|
||||
if (lastEventId > 0) headers['Last-Event-ID'] = String(lastEventId)
|
||||
let authExpired = false
|
||||
void fetch(url, {
|
||||
method: 'GET',
|
||||
headers,
|
||||
@@ -57,6 +68,12 @@ export function useRealtimeEvents({ url, token, onEvent, onConnected }: UseRealt
|
||||
signal: abortController.signal,
|
||||
})
|
||||
.then(async (response) => {
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
// 会话已失效:重连只会无限 401,交给调用方清理会话跳登录。
|
||||
authExpired = true
|
||||
onAuthExpiredRef.current?.()
|
||||
return
|
||||
}
|
||||
if (!response.ok || !response.body) {
|
||||
throw new Error(`realtime_stream_${response.status}`)
|
||||
}
|
||||
@@ -73,7 +90,7 @@ export function useRealtimeEvents({ url, token, onEvent, onConnected }: UseRealt
|
||||
})
|
||||
.catch(() => undefined)
|
||||
.finally(() => {
|
||||
if (stopped || document.visibilityState !== 'visible') return
|
||||
if (authExpired || stopped || document.visibilityState !== 'visible') return
|
||||
reconnectTimer = window.setTimeout(connect, RECONNECT_DELAY_MS)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -87,6 +87,11 @@ export default function AdminLayout() {
|
||||
url: '/api/v1/admin/realtime',
|
||||
token: getAdminToken(),
|
||||
onEvent: (event) => applyAdminRealtimeEvent(event, queryClient),
|
||||
// 会话过期时清登录态回登录页,避免 SSE 带着失效 token 无限重连刷 401。
|
||||
onAuthExpired: () => {
|
||||
clearAdminSession()
|
||||
void navigate('/admin/login', { replace: true, state: { from: location } })
|
||||
},
|
||||
})
|
||||
|
||||
const menuItems = useMemo<MenuProps['items']>(() => {
|
||||
@@ -541,12 +546,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
}
|
||||
|
||||
type NotificationAlertKind =
|
||||
| 'withdraw'
|
||||
| 'recharge'
|
||||
| 'acceptance'
|
||||
| 'material'
|
||||
| 'reminder'
|
||||
| 'default'
|
||||
'withdraw' | 'recharge' | 'acceptance' | 'material' | 'reminder' | 'default'
|
||||
|
||||
function playNotificationSound(kind: NotificationAlertKind) {
|
||||
try {
|
||||
|
||||
@@ -45,6 +45,11 @@ export default function WorkerLayout() {
|
||||
url: '/api/v1/worker/realtime',
|
||||
token: getWorkerToken(),
|
||||
onEvent: (event) => applyWorkerRealtimeEvent(event, queryClient),
|
||||
// 会话过期时清登录态回登录页,避免 SSE 带着失效 token 无限重连刷 401。
|
||||
onAuthExpired: () => {
|
||||
clearWorkerSession()
|
||||
void navigate('/', { replace: true })
|
||||
},
|
||||
})
|
||||
|
||||
async function submitLogout() {
|
||||
|
||||
Reference in New Issue
Block a user