@ai-capabilities-suite/mcp-debugger-core
Version:
Core debugging engine for Node.js and TypeScript applications. Provides Inspector Protocol integration, breakpoint management, variable inspection, execution control, profiling, hang detection, and source map support.
183 lines • 6.09 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InspectorClient = void 0;
const ws_1 = __importDefault(require("ws"));
const events_1 = require("events");
/**
* Inspector client for connecting to Node.js Inspector Protocol via WebSocket
* Implements CDP (Chrome DevTools Protocol) message handling
*/
class InspectorClient extends events_1.EventEmitter {
constructor(wsUrl) {
super();
this.wsUrl = wsUrl;
this.ws = null;
this.messageId = 0;
this.pendingRequests = new Map();
this.connected = false;
// Increase max listeners to handle multiple pause/resume cycles during sampling
this.setMaxListeners(100);
}
/**
* Connect to the Inspector Protocol WebSocket endpoint
*/
async connect() {
const maxRetries = 3;
const retryDelay = 200;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
await this.attemptConnect();
return;
}
catch (error) {
if (attempt === maxRetries - 1) {
throw error;
}
// Wait before retrying
await new Promise((resolve) => setTimeout(resolve, retryDelay));
}
}
}
attemptConnect() {
return new Promise((resolve, reject) => {
this.ws = new ws_1.default(this.wsUrl);
this.ws.on("open", () => {
this.connected = true;
this.emit("connected");
resolve();
});
this.ws.on("message", (data) => {
this.handleMessage(data.toString());
});
this.ws.on("error", (error) => {
this.emit("error", error);
reject(error);
});
this.ws.on("close", () => {
this.connected = false;
this.emit("disconnected");
this.cleanup();
});
});
}
/**
* Handle incoming CDP messages
*/
handleMessage(data) {
try {
const message = JSON.parse(data);
// Check if it's a response to a request
if ("id" in message) {
const pending = this.pendingRequests.get(message.id);
if (pending) {
this.pendingRequests.delete(message.id);
if (message.error) {
const error = new Error(message.error.message);
error.code = message.error.code;
error.data = message.error.data;
pending.reject(error);
}
else {
pending.resolve(message.result);
}
}
}
else if ("method" in message) {
// It's an event
this.emit("event", message);
this.emit(message.method, message.params);
}
}
catch (error) {
this.emit("error", error);
}
}
/**
* Send a CDP command and wait for response
* @param method CDP method name
* @param params Optional parameters
* @param timeout Timeout in milliseconds (default: 5000)
*/
async send(method, params, timeout = 5000) {
if (!this.connected || !this.ws) {
throw new Error("Inspector client is not connected");
}
const id = ++this.messageId;
const request = { id, method, params };
return new Promise((resolve, reject) => {
// Set up timeout
const timeoutId = setTimeout(() => {
this.pendingRequests.delete(id);
reject(new Error(`CDP command '${method}' timed out after ${timeout}ms`));
}, timeout);
this.pendingRequests.set(id, {
resolve: (value) => {
clearTimeout(timeoutId);
resolve(value);
},
reject: (error) => {
clearTimeout(timeoutId);
reject(error);
},
});
this.ws.send(JSON.stringify(request), (error) => {
if (error) {
clearTimeout(timeoutId);
this.pendingRequests.delete(id);
reject(error);
}
});
});
}
/**
* Check if the client is connected
*/
isConnected() {
return this.connected;
}
/**
* Disconnect from the Inspector Protocol
*/
async disconnect() {
if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
return new Promise((resolve) => {
const timeout = setTimeout(() => {
// Force close if not closed within timeout
if (this.ws) {
this.ws.terminate();
this.ws = null;
}
this.cleanup();
resolve();
}, 1000);
this.ws.once('close', () => {
clearTimeout(timeout);
this.ws = null;
this.cleanup();
resolve();
});
this.ws.close();
});
}
else {
this.ws = null;
this.cleanup();
}
}
/**
* Clean up resources
*/
cleanup() {
// Reject all pending requests
for (const [id, pending] of this.pendingRequests.entries()) {
pending.reject(new Error("Inspector client disconnected"));
}
this.pendingRequests.clear();
this.connected = false;
}
}
exports.InspectorClient = InspectorClient;
//# sourceMappingURL=inspector-client.js.map