Files
docupulse/static/js/chat-manager.js

151 lines
5.2 KiB
JavaScript

// 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;
}
};
})();
}