mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-01 03:56:38 +08:00
完善应用功能与界面交互
This commit is contained in:
@@ -7,6 +7,32 @@ import { useAuthStore } from '../stores/auth'
|
||||
const FAST_RECONNECT_ATTEMPTS = 5
|
||||
const SLOW_RECONNECT_INTERVAL = 60_000
|
||||
|
||||
// token 过期前 30 秒即视为失效,给刷新留出余量。
|
||||
const TOKEN_EXPIRY_SKEW_MS = 30_000
|
||||
|
||||
// decodeJwtExpiryMs 解析 JWT 的 exp 声明并换算成毫秒时间戳。
|
||||
// 解析失败(非 JWT / 结构异常)返回 null,调用方据此放行重连。
|
||||
function decodeJwtExpiryMs(token: string): number | null {
|
||||
const segment = token.split('.')[1]
|
||||
if (!segment) return null
|
||||
try {
|
||||
// JWT 使用 base64url 编码,需先还原字符集并补齐 padding。
|
||||
const normalized = segment.replace(/-/g, '+').replace(/_/g, '/')
|
||||
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=')
|
||||
const claims = JSON.parse(atob(padded)) as { exp?: unknown }
|
||||
return typeof claims.exp === 'number' ? claims.exp * 1000 : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// isTokenExpired 判断 token 是否已过期或即将过期。
|
||||
function isTokenExpired(token: string): boolean {
|
||||
const expiry = decodeJwtExpiryMs(token)
|
||||
if (expiry === null) return false
|
||||
return Date.now() >= expiry - TOKEN_EXPIRY_SKEW_MS
|
||||
}
|
||||
|
||||
// useWebSocket opens a single connection to /api/ws and dispatches every
|
||||
// message to the supplied handler. Auto-reconnects with back-off while the
|
||||
// auth token is present; after the fast retries are exhausted it keeps a
|
||||
@@ -28,6 +54,13 @@ export function useWebSocket(onEvent: (topic: string, payload: unknown) => void)
|
||||
|
||||
const open = () => {
|
||||
if (closed) return
|
||||
// 过期的 token 握手必然 401。此前这里会以 60s 间隔无限重试,服务端
|
||||
// 日志里表现为每分钟一条 401。改为先走刷新流程:成功会更新 token 并
|
||||
// 让本 effect 重建连接,失败则清空会话停止重连。
|
||||
if (isTokenExpired(token)) {
|
||||
void useAuthStore.getState().tokenRefresh()
|
||||
return
|
||||
}
|
||||
const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||
const url = `${proto}//${window.location.host}/api/ws?token=${encodeURIComponent(token)}`
|
||||
const ws = new WebSocket(url)
|
||||
@@ -47,6 +80,11 @@ export function useWebSocket(onEvent: (topic: string, payload: unknown) => void)
|
||||
}
|
||||
ws.onclose = () => {
|
||||
if (closed) return
|
||||
// token 过期时不要继续退避重试,交给刷新流程处理。
|
||||
if (isTokenExpired(token)) {
|
||||
void useAuthStore.getState().tokenRefresh()
|
||||
return
|
||||
}
|
||||
reconnectAttempts += 1
|
||||
// 快速阶段保持原有线性退避,之后固定 60s 慢速重试;
|
||||
// timer 始终只有一个在途,cleanup 时统一清除,不会堆积。
|
||||
|
||||
Reference in New Issue
Block a user