steinad's picture
Initial commit
adca48b
/**
* Socket.IO connection and event management
*/
import { Logger } from '../core/logger.js';
import { appState } from '../core/state.js';
export class SocketManager {
constructor() {
this.socket = null;
this.eventHandlers = new Map();
this.isInitialized = false;
}
async initialize() {
if (this.isInitialized) {
Logger.warn('Socket', 'Already initialized');
return this.socket;
}
try {
Logger.debug('Socket', 'Initializing Socket.IO connection...');
this.socket = io({
transports: ['websocket', 'polling'],
timeout: 20000,
forceNew: true,
upgrade: true,
rememberUpgrade: true,
autoConnect: true,
reconnection: true,
reconnectionDelay: 1000,
reconnectionAttempts: 5,
maxHttpBufferSize: 1e6,
pingTimeout: 60000,
pingInterval: 25000
});
this.setupConnectionHandlers();
// Register any cached handlers after socket creation
this.registerCachedEventHandlers();
this.isInitialized = true;
Logger.debug('Socket', 'Socket.IO initialized successfully');
return this.socket;
} catch (e) {
Logger.error('Socket', 'Error initializing Socket.IO:', e);
throw e;
}
}
setupConnectionHandlers() {
this.socket.on('connect', () => {
Logger.debug('Socket', 'Socket connected successfully');
Logger.debug('Socket', 'Socket ID:', this.socket.id);
Logger.debug('Socket', 'Socket connected:', this.socket.connected);
Logger.debug('Socket', 'Socket transport:', this.socket.io.engine.transport.name);
// Clear timeouts and reset connection state on successful connect
appState.clearConnectionTimeout();
appState.setConnectionRetries(0);
appState.updateLastHeartbeat();
this.emit('connected', {
socketId: this.socket.id,
transport: this.socket.io.engine.transport.name
});
});
this.socket.on('disconnect', (reason) => {
Logger.debug('Socket', 'Socket disconnected');
Logger.debug('Socket', 'Disconnect reason:', reason);
Logger.debug('Socket', 'Socket connected:', this.socket.connected);
// Clear all timeouts and reset state on disconnect
appState.clearSolvingTimeout();
appState.clearConnectionTimeout();
appState.currentSessionId = null;
this.emit('disconnected', { reason });
});
this.socket.on('connect_error', (error) => {
Logger.error('Socket', 'Socket connection error:', error);
Logger.error('Socket', 'Error details:', error.message);
this.emit('connectionError', { error });
});
this.socket.io.on('error', (error) => {
Logger.error('Socket', 'Socket.IO error:', error);
this.emit('ioError', { error });
});
}
// Event subscription system
on(event, handler) {
if (!this.eventHandlers.has(event)) {
this.eventHandlers.set(event, []);
}
this.eventHandlers.get(event).push(handler);
Logger.debug('Socket', `Handler stored for event: ${event}`);
}
// Emit custom events (not socket events)
emit(event, data) {
if (this.eventHandlers.has(event)) {
this.eventHandlers.get(event).forEach(handler => {
try {
handler(data);
} catch (e) {
Logger.error('Socket', `Error in event handler for ${event}:`, e);
}
});
}
}
// Send data to server
send(event, data) {
if (!this.socket || !this.socket.connected) {
Logger.error('Socket', 'Cannot send - socket not connected');
return false;
}
try {
this.socket.emit(event, data);
Logger.debug('Socket', `Sent event: ${event}`, data);
return true;
} catch (e) {
Logger.error('Socket', `Error sending event ${event}:`, e);
return false;
}
}
// Connection utilities
isConnected() {
return this.socket && this.socket.connected;
}
getSocketId() {
return this.socket?.id || null;
}
disconnect() {
if (this.socket) {
this.socket.disconnect();
Logger.debug('Socket', 'Socket disconnected manually');
}
}
reconnect() {
if (this.socket) {
this.socket.disconnect();
setTimeout(() => {
this.socket.connect();
Logger.debug('Socket', 'Attempting manual reconnection');
}, 1000);
}
}
// Register all event handlers from the original monolithic code
registerEventHandlers(handlers) {
Object.entries(handlers).forEach(([event, handler]) => {
// Store in internal system for tracking
if (!this.eventHandlers.has(event)) {
this.eventHandlers.set(event, []);
}
this.eventHandlers.get(event).push(handler);
// Register directly with socket if it exists
if (this.socket) {
this.socket.on(event, handler);
Logger.debug('Socket', `Registered handler for event: ${event}`);
} else {
Logger.debug('Socket', `Cached handler for event: ${event} (socket not ready)`);
}
});
}
// Helper method to register all cached handlers after socket creation
registerCachedEventHandlers() {
console.log('[DEBUG] registerCachedEventHandlers called, handlers map:', this.eventHandlers);
this.eventHandlers.forEach((handlers, event) => {
handlers.forEach(handler => {
if (this.socket) {
this.socket.on(event, handler);
console.log(`[DEBUG] Registered cached handler for event: ${event}`);
Logger.debug('Socket', `Registered cached handler for event: ${event}`);
}
});
});
}
}
// Create singleton instance
export const socketManager = new SocketManager();