import { ref } from 'vue' import { SseClient } from './SseClient.ts' import type { SseEvents } from './sseBus.ts' import { sseBus } from './sseBus.ts' import { useAccountStore } from '@/stores' /** SSE 连接 URL */ const SSE_URL = '/api/sse/subscribe' /** 是否正在刷新 token(防并发) */ let isRefreshing = false // ==================== 模块级单例 ==================== const isConnected = ref(false) const client = new SseClient(SSE_URL, () => useAccountStore().accessToken, { onConnected() { isConnected.value = true sseBus.emit('connected', undefined) }, onMessage(event: string, data: string) { try { const payload = JSON.parse(data) sseBus.emit(event as keyof SseEvents, payload) } catch { console.warn('[SSE] 事件数据解析失败:', event, data) } }, onError(message: string) { sseBus.emit('error', { message, retryCount: 0 }) }, onUnauthorized() { void refreshTokenAndReconnect() } }) // ==================== Token 刷新 ==================== async function refreshTokenAndReconnect(): Promise { if (isRefreshing) return const accountStore = useAccountStore() if (!accountStore.refreshToken) { sseBus.emit('error', { message: '登录已过期,请重新登录', retryCount: 0 }) return } isRefreshing = true try { const newToken = await accountStore.refreshAccessToken() if (newToken) { await client.connect() } } catch { sseBus.emit('error', { message: '实时连接异常,请刷新页面', retryCount: 0 }) } finally { isRefreshing = false } } // ==================== Composable ==================== /** * 全局 SSE 连接管理 * * SseClient 只管连接生命周期,业务消息通过 sseBus 分发。 * 组件只需 `useSse().connect()` / `useSse().disconnect()`。 */ export function useSse() { return { isConnected, connect: () => client.connect(), disconnect: () => { client.disconnect() isConnected.value = false } } }