Fix bin/publish: use correct .env path for rspade_system Fix bin/publish script: prevent grep exit code 1 from terminating script 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
173 lines
6.6 KiB
JavaScript
Executable File
173 lines
6.6 KiB
JavaScript
Executable File
"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
|