Files
rspade_system/app/RSpade/resource/DebugProxy/dist/http/websocket.js
root f6fac6c4bc Fix bin/publish: copy docs.dist from project root
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>
2025-10-21 02:08:33 +00:00

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