"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebSocketHandler = void 0; const ws_1 = require("ws"); const logger_1 = require("../utils/logger"); class WebSocketHandler { wss = null; logger; clients = new Map(); authValidator; constructor(authValidator) { this.logger = new logger_1.Logger('WebSocketHandler'); this.authValidator = authValidator; } initialize(server) { this.wss = new ws_1.WebSocketServer({ noServer: true, path: '/_ide/debug/ws' }); // Handle upgrade requests server.on('upgrade', async (request, socket, head) => { this.logger.info(`WebSocket upgrade request: ${request.url}`); // Only handle our specific path if (request.url !== '/_ide/debug/ws') { socket.write('HTTP/1.1 404 Not Found\r\n\r\n'); socket.destroy(); return; } // Accept all connections - auth will be done via first message this.wss.handleUpgrade(request, socket, head, (ws) => { this.wss.emit('connection', ws, request); }); }); // Handle new connections this.wss.on('connection', (ws, _request) => { this.logger.info(`WebSocket client connected (awaiting auth)`); let sessionId = null; let authenticated = false; // Handle incoming messages ws.on('message', async (data) => { try { const message = JSON.parse(data.toString()); // First message must be auth if (!authenticated) { if (message.type !== 'auth') { this.logger.warn('First message was not auth, closing connection'); ws.close(1008, 'Authentication required'); return; } sessionId = message.data?.sessionId; const signature = message.data?.signature; if (!sessionId || !signature) { this.logger.warn('Missing auth credentials'); ws.close(1008, 'Invalid authentication'); return; } const isValid = await this.authValidator.validateWebSocketAuth(sessionId, signature); if (!isValid) { this.logger.warn(`Invalid auth for session: ${sessionId}`); ws.close(1008, 'Authentication failed'); return; } authenticated = true; this.clients.set(sessionId, ws); this.logger.info(`Client authenticated: ${sessionId}`); // Send welcome message this.sendMessage(ws, { type: 'welcome', data: { message: 'Authenticated successfully', sessionId: sessionId, timestamp: Date.now() } }); return; } // Handle regular messages after auth this.handleMessage(ws, sessionId, message); } catch (error) { this.logger.error('Failed to parse WebSocket message:', error); this.sendMessage(ws, { type: 'error', data: { message: 'Invalid message format' } }); } }); // Handle pong frames ws.on('pong', () => { if (sessionId) { this.logger.debug(`Pong received from ${sessionId}`); } }); // Handle disconnection ws.on('close', () => { if (sessionId) { this.logger.info(`WebSocket client disconnected: ${sessionId}`); this.clients.delete(sessionId); } }); // Handle errors ws.on('error', (error) => { if (sessionId) { this.logger.error(`WebSocket error for ${sessionId}:`, error); this.clients.delete(sessionId); } }); }); // Start heartbeat this.startHeartbeat(); } handleMessage(ws, sessionId, message) { this.logger.debug(`Message from ${sessionId}: ${message.type}`); switch (message.type) { case 'ping': // Respond to ping with pong this.sendMessage(ws, { type: 'pong', data: { originalData: message.data, timestamp: Date.now() } }); break; case 'hello': // Handle hello message this.sendMessage(ws, { type: 'hello_response', data: { message: `Hello ${message.data?.name || 'client'}! Pong from debug proxy`, timestamp: Date.now() } }); break; default: this.logger.warn(`Unknown message type: ${message.type}`); this.sendMessage(ws, { type: 'error', data: { message: `Unknown message type: ${message.type}` } }); } } sendMessage(ws, message) { if (ws.readyState === ws_1.WebSocket.OPEN) { ws.send(JSON.stringify(message)); } } startHeartbeat() { setInterval(() => { this.clients.forEach((ws, sessionId) => { if (ws.readyState === ws_1.WebSocket.OPEN) { ws.ping(); } else { this.clients.delete(sessionId); } }); }, 30000); // 30 second heartbeat } broadcast(message) { this.clients.forEach((ws) => { this.sendMessage(ws, message); }); } sendToSession(sessionId, message) { const ws = this.clients.get(sessionId); if (ws) { this.sendMessage(ws, message); } } } exports.WebSocketHandler = WebSocketHandler; //# sourceMappingURL=websocket.js.map