'use client'; import { useCallback, useEffect, useRef, useState } from 'react'; import type { Message } from '@/api/chat'; type WsEvent = | { type: 'connection_established' } | { type: 'chat_message'; message: Message } | { type: 'error'; error?: string } | { type: string; [k: string]: unknown }; export function useChatWebSocket(options: { chatUuid: string | null; enabled?: boolean; onMessage?: (m: Message) => void; }) { const { chatUuid, enabled = true, onMessage } = options; const [isConnected, setIsConnected] = useState(false); const wsRef = useRef(null); const onMessageRef = useRef(onMessage); onMessageRef.current = onMessage; const disconnect = useCallback(() => { if (wsRef.current) { wsRef.current.close(); wsRef.current = null; } setIsConnected(false); }, []); const connect = useCallback(() => { if (!enabled || !chatUuid) return; const token = typeof window !== 'undefined' ? localStorage.getItem('access_token') : null; if (!token) return; let apiUrl = process.env.NEXT_PUBLIC_API_URL || window.location.origin; apiUrl = apiUrl.replace(/\/api\/?$/, '').replace(/\/api\//, '/'); apiUrl = apiUrl.replace(/\/$/, ''); const wsProtocol = apiUrl.startsWith('https') ? 'wss:' : 'ws:'; const wsHost = apiUrl.replace(/^https?:\/\//, ''); const wsUrl = `${wsProtocol}//${wsHost}/ws/chat/${chatUuid}/?token=${token}`; const ws = new WebSocket(wsUrl); wsRef.current = ws; ws.onopen = () => setIsConnected(true); ws.onclose = () => setIsConnected(false); ws.onerror = () => setIsConnected(false); ws.onmessage = (ev) => { try { const data = JSON.parse(ev.data) as WsEvent; if (data.type === 'chat_message' && (data as any).message) { onMessageRef.current?.((data as any).message as Message); } } catch { // ignore } }; }, [chatUuid, enabled]); useEffect(() => { disconnect(); connect(); return () => disconnect(); }, [connect, disconnect]); return { isConnected }; }