Files
MediaCrawler/webui/src/hooks/useWebSocket.ts
程序员阿江(Relakkes) 65ddaa8828 refactor: 将 WebUI 源码纳入仓库,移除打包产物
- 新增 webui/ 前端源码目录(React+Vite+TypeScript+Tailwind)
- 删除 api/webui/ 中旧的打包静态资源
- 更新 .gitignore 忽略 api/webui/ 构建输出和 webui/node_modules
- 更新 README 增加前端构建说明
2026-07-01 23:05:03 +08:00

112 lines
2.9 KiB
TypeScript

import { useEffect, useRef } from 'react'
import { useCrawlerStore } from '@/store/crawlerStore'
import type { LogEntry } from '@/types/crawler'
// 模块级单例,确保全局只有一个 WebSocket 连接
let globalWs: WebSocket | null = null
let globalReconnectTimer: ReturnType<typeof setTimeout> | null = null
let connectionCount = 0 // 跟踪连接使用者数量
export function useLogWebSocket() {
const addLog = useCrawlerStore((state) => state.addLog)
const addLogRef = useRef(addLog)
// 保持 addLog 引用最新
useEffect(() => {
addLogRef.current = addLog
}, [addLog])
useEffect(() => {
connectionCount++
const connect = () => {
if (globalReconnectTimer) {
clearTimeout(globalReconnectTimer)
globalReconnectTimer = null
}
// 如果已经连接或正在连接,不重复创建
if (globalWs && (globalWs.readyState === WebSocket.OPEN || globalWs.readyState === WebSocket.CONNECTING)) {
return
}
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
const host = window.location.host
const wsUrl = `${protocol}//${host}/api/ws/logs`
const ws = new WebSocket(wsUrl)
globalWs = ws
ws.onopen = () => {
if (globalWs !== ws) return
console.log('WebSocket connected')
}
ws.onmessage = (event) => {
if (globalWs !== ws) return
if (event.data === 'ping') {
ws.send('pong')
return
}
if (event.data === 'pong') {
return
}
try {
const log: LogEntry = JSON.parse(event.data)
if (log.id && log.message) {
addLogRef.current(log)
}
} catch (e) {
console.warn('Failed to parse WebSocket message:', event.data)
}
}
ws.onclose = () => {
if (globalWs !== ws) return
console.log('WebSocket disconnected')
globalWs = null
// 只在还有使用者时才重连
if (connectionCount > 0) {
globalReconnectTimer = setTimeout(connect, 2000)
}
}
ws.onerror = (error) => {
if (globalWs !== ws) return
console.error('WebSocket error:', error)
}
}
// 首次连接
connect()
// Heartbeat
const heartbeat = setInterval(() => {
if (globalWs && globalWs.readyState === WebSocket.OPEN) {
globalWs.send('ping')
}
}, 30000)
return () => {
connectionCount--
clearInterval(heartbeat)
// 只在没有使用者时才断开连接
if (connectionCount === 0) {
if (globalReconnectTimer) {
clearTimeout(globalReconnectTimer)
globalReconnectTimer = null
}
if (globalWs) {
const ws = globalWs
globalWs = null
ws.close()
}
}
}
}, []) // 空依赖数组
return { ws: globalWs }
}