fixed messaging!
This commit is contained in:
@@ -1,127 +1,45 @@
|
||||
// Global state and socket management
|
||||
// Global state and polling management
|
||||
if (typeof window.ChatManager === 'undefined') {
|
||||
window.ChatManager = (function() {
|
||||
let instance = null;
|
||||
let socket = null;
|
||||
let pollInterval = null;
|
||||
|
||||
const state = {
|
||||
addedMessageIds: new Set(),
|
||||
messageQueue: new Set(),
|
||||
connectionState: {
|
||||
hasJoined: false,
|
||||
isConnected: false,
|
||||
isConnected: true,
|
||||
lastMessageId: null,
|
||||
connectionAttempts: 0,
|
||||
socketId: null
|
||||
pollAttempts: 0
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
// 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
|
||||
state.connectionState.lastMessageId = Math.max(state.connectionState.lastMessageId || 0, messageId);
|
||||
console.log('[ChatManager] Initialized with existing message:', {
|
||||
messageId: messageId,
|
||||
lastMessageId: state.connectionState.lastMessageId
|
||||
});
|
||||
}
|
||||
}, 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'];
|
||||
});
|
||||
// Start polling for new messages
|
||||
startPolling(conversationId);
|
||||
|
||||
instance = {
|
||||
socket: socket,
|
||||
state: state,
|
||||
cleanup: cleanup
|
||||
};
|
||||
@@ -129,12 +47,97 @@ if (typeof window.ChatManager === 'undefined') {
|
||||
return instance;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
console.log('Cleaning up socket connection');
|
||||
if (socket) {
|
||||
socket.off('new_message');
|
||||
socket.disconnect();
|
||||
socket = null;
|
||||
console.log('[ChatManager] Cleaning up polling');
|
||||
if (pollInterval) {
|
||||
clearInterval(pollInterval);
|
||||
pollInterval = null;
|
||||
}
|
||||
instance = null;
|
||||
}
|
||||
|
||||
@@ -3,26 +3,28 @@ $(document).ready(function() {
|
||||
const conversationId = window.conversationId; // Set this in the template
|
||||
const currentUserId = window.currentUserId; // Set this in the template
|
||||
const chat = ChatManager.getInstance(conversationId);
|
||||
const socket = chat.socket;
|
||||
const state = chat.state;
|
||||
|
||||
console.log('Initializing chat for conversation:', conversationId);
|
||||
console.log('[Conversation] Initializing chat for conversation:', conversationId);
|
||||
|
||||
// Join conversation room
|
||||
socket.on('connect', function() {
|
||||
if (!state.connectionState.hasJoined) {
|
||||
console.log('Joining conversation room:', conversationId);
|
||||
socket.emit('join_conversation', {
|
||||
conversation_id: conversationId,
|
||||
timestamp: new Date().toISOString(),
|
||||
socketId: socket.id
|
||||
});
|
||||
state.connectionState.hasJoined = true;
|
||||
}
|
||||
});
|
||||
// Keep track of messages we've already displayed
|
||||
const displayedMessageIds = new Set();
|
||||
|
||||
// Function to append a new message to the chat
|
||||
function appendMessage(message) {
|
||||
console.log('[Conversation] Attempting to append message:', {
|
||||
messageId: message.id,
|
||||
content: message.content,
|
||||
senderId: message.sender_id,
|
||||
currentUserId: currentUserId
|
||||
});
|
||||
|
||||
// Check if we've already displayed this message
|
||||
if (displayedMessageIds.has(message.id)) {
|
||||
return;
|
||||
}
|
||||
displayedMessageIds.add(message.id);
|
||||
|
||||
const isCurrentUser = message.sender_id === currentUserId;
|
||||
const messageHtml = `
|
||||
<div class="message ${isCurrentUser ? 'sent' : 'received'}" data-message-id="${message.id}">
|
||||
@@ -65,9 +67,18 @@ $(document).ready(function() {
|
||||
$('.text-center.text-muted').remove();
|
||||
|
||||
$('#chatMessages').append(messageHtml);
|
||||
console.log('[Conversation] Message appended to chat:', message.id);
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
// Initialize displayedMessageIds with existing messages
|
||||
$('.message').each(function() {
|
||||
const messageId = $(this).data('message-id');
|
||||
if (messageId) {
|
||||
displayedMessageIds.add(messageId);
|
||||
}
|
||||
});
|
||||
|
||||
// Scroll to bottom of chat messages
|
||||
function scrollToBottom() {
|
||||
const chatMessages = document.getElementById('chatMessages');
|
||||
@@ -75,41 +86,14 @@ $(document).ready(function() {
|
||||
}
|
||||
scrollToBottom();
|
||||
|
||||
// Message handling with deduplication and reconnection handling
|
||||
socket.on('new_message', function(message) {
|
||||
const timestamp = new Date().toISOString();
|
||||
const messageKey = `${message.id}-${socket.id}`;
|
||||
|
||||
console.log('Message received:', {
|
||||
id: message.id,
|
||||
timestamp: timestamp,
|
||||
socketId: socket.id,
|
||||
messageKey: messageKey,
|
||||
queueSize: state.messageQueue.size
|
||||
// Listen for new messages
|
||||
$(document).on('new_message', function(event, message) {
|
||||
console.log('[Conversation] Received new_message event:', {
|
||||
messageId: message.id,
|
||||
eventType: event.type,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
if (state.messageQueue.has(messageKey)) {
|
||||
console.log('Message already in queue:', messageKey);
|
||||
return;
|
||||
}
|
||||
|
||||
state.messageQueue.add(messageKey);
|
||||
|
||||
if (!state.addedMessageIds.has(message.id)) {
|
||||
console.log('Processing new message:', message.id);
|
||||
appendMessage(message);
|
||||
state.connectionState.lastMessageId = message.id;
|
||||
state.addedMessageIds.add(message.id);
|
||||
} else {
|
||||
console.log('Duplicate message detected:', {
|
||||
messageId: message.id,
|
||||
lastMessageId: state.connectionState.lastMessageId,
|
||||
socketId: socket.id
|
||||
});
|
||||
}
|
||||
|
||||
// Clean up message from queue after processing
|
||||
state.messageQueue.delete(messageKey);
|
||||
appendMessage(message);
|
||||
});
|
||||
|
||||
// Handle file selection
|
||||
@@ -123,14 +107,14 @@ $(document).ready(function() {
|
||||
}
|
||||
});
|
||||
|
||||
// Handle message form submission with better error handling
|
||||
// Handle message form submission
|
||||
let isSubmitting = false;
|
||||
$('#messageForm').off('submit').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (isSubmitting) {
|
||||
console.log('Message submission already in progress');
|
||||
console.log('[Conversation] Message submission already in progress');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -143,15 +127,14 @@ $(document).ready(function() {
|
||||
const files = Array.from(fileInput.files);
|
||||
|
||||
if (!message && files.length === 0) {
|
||||
console.log('Empty message submission attempted');
|
||||
console.log('[Conversation] Empty message submission attempted');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('Submitting message:', {
|
||||
console.log('[Conversation] Submitting message:', {
|
||||
hasText: !!message,
|
||||
fileCount: files.length,
|
||||
socketId: socket.id,
|
||||
connectionState: state.connectionState
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
isSubmitting = true;
|
||||
@@ -163,44 +146,46 @@ $(document).ready(function() {
|
||||
const formData = new FormData();
|
||||
formData.append('message', message);
|
||||
formData.append('csrf_token', $('input[name="csrf_token"]').val());
|
||||
formData.append('socket_id', socket.id);
|
||||
formData.append('file_count', files.length);
|
||||
|
||||
files.forEach((file, index) => {
|
||||
formData.append(`file_${index}`, file);
|
||||
});
|
||||
formData.append('file_count', files.length);
|
||||
|
||||
$.ajax({
|
||||
url: window.sendMessageUrl, // Set this in the template
|
||||
url: window.sendMessageUrl,
|
||||
method: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function(response) {
|
||||
console.log('Message sent successfully:', {
|
||||
console.log('[Conversation] Message sent successfully:', {
|
||||
response: response,
|
||||
socketId: socket.id
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
if (response.success) {
|
||||
messageInput.val('');
|
||||
fileInput.value = '';
|
||||
$('#selectedFiles').text('');
|
||||
|
||||
// If socket is disconnected, append message directly
|
||||
if (!state.connectionState.isConnected && response.message) {
|
||||
console.log('Socket disconnected, appending message directly');
|
||||
// Append the message directly since we sent it
|
||||
if (response.message) {
|
||||
console.log('[Conversation] Appending sent message directly:', response.message.id);
|
||||
// Update the ChatManager's lastMessageId
|
||||
chat.state.connectionState.lastMessageId = response.message.id;
|
||||
appendMessage(response.message);
|
||||
}
|
||||
} else {
|
||||
console.error('Message send failed:', response);
|
||||
console.error('[Conversation] Message send failed:', response);
|
||||
alert('Failed to send message. Please try again.');
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Failed to send message:', {
|
||||
console.error('[Conversation] Failed to send message:', {
|
||||
status: status,
|
||||
error: error,
|
||||
response: xhr.responseText
|
||||
response: xhr.responseText,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
alert('Failed to send message. Please try again.');
|
||||
},
|
||||
@@ -210,6 +195,7 @@ $(document).ready(function() {
|
||||
submitIcon.removeClass('d-none');
|
||||
spinner.addClass('d-none');
|
||||
isSubmitting = false;
|
||||
console.log('[Conversation] Message submission completed');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -218,6 +204,7 @@ $(document).ready(function() {
|
||||
|
||||
// Clean up on page unload
|
||||
$(window).on('beforeunload', function() {
|
||||
console.log('[Conversation] Cleaning up on page unload');
|
||||
chat.cleanup();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user