// Global state and socket management if (typeof window.ChatManager === 'undefined') { window.ChatManager = (function() { let instance = null; let socket = null; const state = { addedMessageIds: new Set(), messageQueue: new Set(), connectionState: { hasJoined: false, isConnected: false, lastMessageId: null, connectionAttempts: 0, socketId: null } }; function init(conversationId) { if (instance) { return instance; } // Initialize message IDs from existing messages $('.message').each(function() { const messageId = $(this).data('message-id'); if (messageId) { state.addedMessageIds.add(messageId); } }); // Create socket instance socket = io(window.location.origin, { path: '/socket.io/', transports: ['polling', 'websocket'], upgrade: true, reconnection: true, reconnectionAttempts: Infinity, reconnectionDelay: 1000, reconnectionDelayMax: 5000, timeout: 20000, autoConnect: true, forceNew: true, multiplex: false, pingTimeout: 60000, pingInterval: 25000, upgradeTimeout: 10000, rememberUpgrade: true, rejectUnauthorized: false, extraHeaders: { 'X-Forwarded-Proto': 'https' } }); // Set up socket event handlers socket.on('connect', function() { state.connectionState.isConnected = true; state.connectionState.connectionAttempts++; state.connectionState.socketId = socket.id; console.log('Socket connected:', { attempt: state.connectionState.connectionAttempts, socketId: socket.id, existingSocketId: state.connectionState.socketId, transport: socket.io.engine.transport.name }); // Always rejoin the room on connect console.log('Joining conversation room:', conversationId); socket.emit('join_conversation', { conversation_id: conversationId, timestamp: new Date().toISOString(), socketId: socket.id }); state.connectionState.hasJoined = true; }); socket.on('disconnect', function(reason) { console.log('Disconnected from conversation:', { reason: reason, socketId: socket.id, connectionState: state.connectionState, transport: socket.io.engine?.transport?.name }); state.connectionState.isConnected = false; state.connectionState.socketId = null; }); socket.on('connect_error', function(error) { console.error('Connection error:', error); // Try to reconnect with polling if websocket fails if (socket.io.engine?.transport?.name === 'websocket') { console.log('WebSocket failed, falling back to polling'); socket.io.opts.transports = ['polling']; } }); socket.on('error', function(error) { console.error('Socket error:', error); }); // Add heartbeat to keep connection alive setInterval(function() { if (socket.connected) { socket.emit('heartbeat', { timestamp: new Date().toISOString(), socketId: socket.id, transport: socket.io.engine.transport.name }); } }, 15000); // Handle transport upgrade socket.io.engine.on('upgrade', function() { console.log('Transport upgraded to:', socket.io.engine.transport.name); }); socket.io.engine.on('upgradeError', function(err) { console.error('Transport upgrade error:', err); // Fall back to polling socket.io.opts.transports = ['polling']; }); instance = { socket: socket, state: state, cleanup: cleanup }; return instance; } function cleanup() { console.log('Cleaning up socket connection'); if (socket) { socket.off('new_message'); socket.disconnect(); socket = null; } instance = null; } return { getInstance: function(conversationId) { if (!instance) { instance = init(conversationId); } return instance; } }; })(); }