'use client' import { useEffect, useRef, useState } from 'react' import { io } from 'socket.io-client' import { CHAT_SOCKET_NAMESPACE, CHAT_SOCKET_URL } from '@/config/chatSocket' import useAuth from '@/hooks/useAuth' import { reportBrowserEvent } from '@/lib/observability/client' import type { AdminChatMessage } from '@/services/adminChat' type OversightSocketStatus = 'connecting' | 'connected' | 'error' interface OversightAck { ok: boolean error?: string conversationId?: string } const useAdminChatSocket = (conversationId: string, onMessage: (message: AdminChatMessage) => void) => { const { user } = useAuth() const onMessageRef = useRef(onMessage) const [status, setStatus] = useState('connecting') const [error, setError] = useState(null) useEffect(() => { onMessageRef.current = onMessage }, [onMessage]) useEffect(() => { if (!conversationId || !user?.accessToken) return let disposed = false let joined = false let disconnected = false let disconnectTimer: ReturnType | undefined let retryTimer: ReturnType | undefined const socket = io(`${CHAT_SOCKET_URL}${CHAT_SOCKET_NAMESPACE}`, { transports: ['websocket'], autoConnect: true, auth: { token: user.accessToken }, }) const disconnect = () => { if (disconnected) return disconnected = true if (disconnectTimer) clearTimeout(disconnectTimer) if (retryTimer) clearTimeout(retryTimer) socket.removeAllListeners() socket.disconnect() } const join = (attempt = 1) => { if (disposed) return setStatus('connecting') socket.timeout(5000).emit('admin:conversation:join', { conversationId }, (timeoutError: Error | null, ack?: OversightAck) => { if (disposed) return if (timeoutError || !ack?.ok) { const reason = timeoutError ? 'ACK_TIMEOUT' : (ack?.error ?? 'UNKNOWN') if (attempt === 1 && (timeoutError || reason === 'UNAUTHORIZED')) { retryTimer = setTimeout(() => { join(2) }, 400) return } setStatus('error') setError('اتصال زنده به گفتگو برقرار نشد.') reportBrowserEvent({ name: 'admin_chat_socket_join_failure', level: 'warning', attributes: { error: reason }, }) disconnect() return } joined = true setError(null) setStatus('connected') }) } socket.on('connect', join) socket.on('connect_error', () => { if (disposed) return setStatus('error') setError('ارتباط زنده با سرور برقرار نشد.') }) socket.on('message:new', (payload: { message?: AdminChatMessage }) => { if (disposed || payload.message?.conversationId !== conversationId) return onMessageRef.current(payload.message) }) return () => { disposed = true if (!joined || !socket.connected) { disconnect() return } const finishDisconnect = () => { disconnect() } disconnectTimer = setTimeout(finishDisconnect, 800) socket.timeout(750).emit('admin:conversation:leave', { conversationId }, finishDisconnect) } }, [conversationId, user?.accessToken]) return { status, error } } export default useAdminChatSocket