214 lines
8.3 KiB
JavaScript
214 lines
8.3 KiB
JavaScript
/**
|
|
* @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<number>} addedMessageIds - Set of message IDs that have been processed
|
|
* @property {Set<Object>} 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<Object>} 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;
|
|
}
|
|
};
|
|
})();
|
|
}
|