/** * @fileoverview Manages global chat state and polling functionality. * This file implements a singleton ChatManager that handles: * - Message polling and state management * - New message processing and event triggering * - Connection state tracking * - Resource cleanup */ // Global state and polling management if (typeof window.ChatManager === 'undefined') { window.ChatManager = (function() { let instance = null; let pollInterval = null; /** * @typedef {Object} ConnectionState * @property {boolean} hasJoined - Whether the user has joined the conversation * @property {boolean} isConnected - Current connection status * @property {number|null} lastMessageId - ID of the last received message * @property {number} pollAttempts - Number of failed polling attempts */ /** * @typedef {Object} ChatState * @property {Set} addedMessageIds - Set of message IDs that have been processed * @property {Set} messageQueue - Queue of messages waiting to be processed * @property {ConnectionState} connectionState - Current connection state */ const state = { addedMessageIds: new Set(), messageQueue: new Set(), connectionState: { hasJoined: false, isConnected: true, lastMessageId: null, pollAttempts: 0 } }; /** * Initializes a new ChatManager instance for a conversation. * Sets up message tracking and starts polling for new messages. * @function * @param {string} conversationId - The ID of the conversation to manage * @returns {Object} The ChatManager instance */ function init(conversationId) { if (instance) { console.log('[ChatManager] Instance already exists, returning existing instance'); return instance; } console.log('[ChatManager] Initializing new instance for conversation:', conversationId); // Initialize message IDs from existing messages $('.message').each(function() { const messageId = $(this).data('message-id'); if (messageId) { state.addedMessageIds.add(messageId); state.connectionState.lastMessageId = Math.max(state.connectionState.lastMessageId || 0, messageId); console.log('[ChatManager] Initialized with existing message:', { messageId: messageId, lastMessageId: state.connectionState.lastMessageId }); } }); // Start polling for new messages startPolling(conversationId); instance = { state: state, cleanup: cleanup }; return instance; } /** * Starts polling for new messages in the conversation. * Polls every 3 seconds and performs an initial fetch. * @function * @param {string} conversationId - The ID of the conversation to poll */ function startPolling(conversationId) { console.log('[ChatManager] Starting polling for conversation:', conversationId); // Clear any existing polling if (pollInterval) { console.log('[ChatManager] Clearing existing polling interval'); clearInterval(pollInterval); } // Poll every 3 seconds pollInterval = setInterval(() => { console.log('[ChatManager] Polling interval triggered'); fetchNewMessages(conversationId); }, 3000); // Initial fetch console.log('[ChatManager] Performing initial message fetch'); fetchNewMessages(conversationId); } /** * Fetches new messages from the server. * Uses the last message ID to only fetch new messages. * @function * @param {string} conversationId - The ID of the conversation to fetch messages from */ function fetchNewMessages(conversationId) { const url = `/conversations/${conversationId}/messages`; const params = new URLSearchParams(); if (state.connectionState.lastMessageId) { params.append('last_message_id', state.connectionState.lastMessageId); } console.log('[ChatManager] Fetching new messages:', { url: url, params: params.toString(), lastMessageId: state.connectionState.lastMessageId }); fetch(`${url}?${params.toString()}`) .then(response => response.json()) .then(data => { console.log('[ChatManager] Received messages response:', { success: data.success, messageCount: data.messages ? data.messages.length : 0, messages: data.messages }); if (data.success && data.messages) { state.connectionState.pollAttempts = 0; processNewMessages(data.messages); } }) .catch(error => { console.error('[ChatManager] Error fetching messages:', error); state.connectionState.pollAttempts++; // If we've had too many failed attempts, try to reconnect if (state.connectionState.pollAttempts > 5) { console.log('[ChatManager] Too many failed attempts, restarting polling'); startPolling(conversationId); } }); } /** * Processes new messages and triggers events for each new message. * Updates the last message ID and tracks processed messages. * @function * @param {Array} messages - Array of new message objects to process */ function processNewMessages(messages) { console.log('[ChatManager] Processing new messages:', { messageCount: messages.length, currentLastMessageId: state.connectionState.lastMessageId, existingMessageIds: Array.from(state.addedMessageIds) }); messages.forEach(message => { console.log('[ChatManager] Processing message:', { messageId: message.id, alreadyAdded: state.addedMessageIds.has(message.id), currentLastMessageId: state.connectionState.lastMessageId }); if (!state.addedMessageIds.has(message.id)) { state.addedMessageIds.add(message.id); state.connectionState.lastMessageId = Math.max(state.connectionState.lastMessageId || 0, message.id); console.log('[ChatManager] Triggering new_message event for message:', message.id); // Trigger the new message event $(document).trigger('new_message', [message]); } else { console.log('[ChatManager] Skipping already added message:', message.id); } }); } /** * Cleans up polling resources and resets the instance. * Should be called when the chat is no longer needed. * @function */ function cleanup() { console.log('[ChatManager] Cleaning up polling'); if (pollInterval) { clearInterval(pollInterval); pollInterval = null; } instance = null; } return { /** * Gets or creates a ChatManager instance for a conversation. * @function * @param {string} conversationId - The ID of the conversation to manage * @returns {Object} The ChatManager instance */ getInstance: function(conversationId) { if (!instance) { instance = init(conversationId); } return instance; } }; })(); }