import { onMounted, onUnmounted } from 'vue' import { useContributionPolling } from './useContributionPolling.js' import { getActivitySocket } from '@/utils/socket/ActivitySocket.js' /** * 贡献实时推送 composable(WS 优先,断线降级为轮询) * @param {import('vue').Ref} activityId * @param {import('vue').Ref} isPageActive */ export function useContributionRealtime(activityId, isPageActive) { const MAX_RECORDS = 5 const { records, visible, loading, error, start: startPolling, stop: stopPolling, reset: resetPolling, highestIdRef, } = useContributionPolling(activityId, isPageActive) const socket = getActivitySocket() let usingWS = false function onWsMessage(payload) { if (!payload || Number(payload.activity_id) !== Number(activityId.value)) return if (!payload.record) return const record = payload.record if (record.id > highestIdRef()) { // 追加到列表末尾(与轮询的方向不同),保留最近 MAX_RECORDS 条 records.value = [...records.value, record].slice(-MAX_RECORDS) } } function onWsConnect() { if (usingWS) return usingWS = true stopPolling() // 停掉可能的轮询 socket.subscribe(activityId.value, ['contributions']) } function onWsDisconnect() { if (!usingWS) return usingWS = false startPolling() // 降级为轮询 } socket.onContributionsResponse(onWsMessage) socket.on('connect', onWsConnect) socket.on('disconnect', onWsDisconnect) onMounted(() => { // 总是调用 connect():SocketManager.connect() 内部会判断 token 是否变化 // (详见 useMessageRealtime.js 注释)。这样能确保用户切换登录后, // contribution channel 也能用新 token 重新订阅,而不是复用上一个用户的 WS。 const token = uni.getStorageSync('access_token') if (token) { socket.connect(token).catch(err => console.warn('[useContributionRealtime] connect error:', err)) } // 同步分支:如果 WS 已连接(单例复用导致 'connect' 事件不会再次触发), // 必须直接调 onWsConnect 停轮询,否则 polling 会一直跑。 // 异步分支:WS 还没连上时,先起 polling 兜底; // 等 'connect' 事件触发 onWsConnect 后会停掉轮询。 if (socket.isConnected) { onWsConnect() } else { startPolling() } }) onUnmounted(() => { if (usingWS) socket.unsubscribe(activityId.value, ['contributions']) socket.off('connect', onWsConnect) socket.off('disconnect', onWsDisconnect) socket.offContributionsResponse(onWsMessage) stopPolling() resetPolling() }) return { records, visible, loading, error, isUsingWS: () => usingWS, } }