From 0cd667b5c8f1141f8d4824176c4b77c4f3749d54 Mon Sep 17 00:00:00 2001 From: hailin Date: Sat, 10 Jan 2026 01:26:49 -0800 Subject: [PATCH] fix(websocket): use singleton socket to prevent disconnection on re-render --- .../chat/presentation/hooks/useChat.ts | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/packages/web-client/src/features/chat/presentation/hooks/useChat.ts b/packages/web-client/src/features/chat/presentation/hooks/useChat.ts index 10e9cdd..8291f54 100644 --- a/packages/web-client/src/features/chat/presentation/hooks/useChat.ts +++ b/packages/web-client/src/features/chat/presentation/hooks/useChat.ts @@ -2,6 +2,10 @@ import { useCallback, useEffect, useRef } from 'react'; import { io, Socket } from 'socket.io-client'; import { useChatStore, Message } from '../stores/chatStore'; +// Singleton socket instance to prevent multiple connections +let globalSocket: Socket | null = null; +let currentUserId: string | null = null; + export function useChat() { const socketRef = useRef(null); const { @@ -16,14 +20,32 @@ export function useChat() { setConnected, } = useChatStore(); - // Initialize WebSocket connection + // Initialize WebSocket connection (singleton pattern) useEffect(() => { if (!userId) return; + // If socket exists for same user, reuse it + if (globalSocket && currentUserId === userId && globalSocket.connected) { + socketRef.current = globalSocket; + setConnected(true); + return; + } + + // If socket exists for different user or disconnected, clean up + if (globalSocket) { + globalSocket.disconnect(); + globalSocket = null; + } + + currentUserId = userId; + const socket = io('/ws/conversation', { path: '/ws/conversation/socket.io', query: { userId }, transports: ['websocket'], + reconnection: true, + reconnectionAttempts: 5, + reconnectionDelay: 1000, }); socket.on('connect', () => { @@ -77,7 +99,6 @@ export function useChat() { socket.on('tool_result', (data) => { console.log('Tool result:', data); - // Could update message with tool result }); socket.on('error', (error) => { @@ -85,11 +106,10 @@ export function useChat() { setStreaming(false); }); + globalSocket = socket; socketRef.current = socket; - return () => { - socket.disconnect(); - }; + // Don't disconnect on cleanup - keep singleton alive }, [userId]); // Send message @@ -114,15 +134,16 @@ export function useChat() { }; addMessage(conversationId, userMessage); - // Send via WebSocket - if (socketRef.current?.connected) { + // Send via WebSocket (use globalSocket for reliability) + const socket = globalSocket || socketRef.current; + if (socket?.connected) { console.log('Sending message via WebSocket:', { conversationId, content: content.trim() }); - socketRef.current.emit('message', { + socket.emit('message', { conversationId, content: content.trim(), }); } else { - console.error('WebSocket not connected, cannot send message'); + console.error('WebSocket not connected, cannot send message. Socket state:', socket?.connected); } }, [userId, currentConversationId, addMessage],