@mcp-abap-adt/connection
Version:
ABAP connection layer for MCP ABAP ADT server
136 lines (135 loc) • 4.28 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.GenericWebSocketTransport = void 0;
const WS_OPEN = 1;
class GenericWebSocketTransport {
factory;
socket = null;
messageHandlers = [];
openHandlers = [];
errorHandlers = [];
closeHandlers = [];
constructor(factory) {
this.factory = factory;
}
async connect(url, options) {
if (this.socket && this.isConnected()) {
return;
}
const connectTimeoutMs = options?.connectTimeoutMs ?? 15000;
const socket = this.factory.create(url, options?.protocols, options);
this.socket = socket;
await new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
reject(new Error(`WebSocket connect timeout after ${connectTimeoutMs}ms`));
}, connectTimeoutMs);
socket.onopen = () => {
clearTimeout(timeoutId);
this.emitOpen().catch(() => {
// no-op
});
resolve();
};
socket.onerror = (event) => {
clearTimeout(timeoutId);
const err = this.normalizeError(event, 'WebSocket connection error');
this.emitError(err).catch(() => {
// no-op
});
reject(err);
};
socket.onclose = (event) => {
clearTimeout(timeoutId);
const info = {
code: typeof event?.code === 'number' ? event.code : 1006,
reason: event?.reason,
wasClean: event?.wasClean,
};
this.emitClose(info).catch(() => {
// no-op
});
};
socket.onmessage = (event) => {
this.handleIncomingMessage(event?.data).catch(() => {
// no-op
});
};
});
}
async disconnect(code, reason) {
if (!this.socket) {
return;
}
const socket = this.socket;
this.socket = null;
try {
socket.close(code, reason);
}
catch (error) {
const err = this.normalizeError(error, 'WebSocket close failed');
await this.emitError(err);
}
}
async send(message) {
if (!this.socket || this.socket.readyState !== WS_OPEN) {
throw new Error('WebSocket is not connected');
}
this.socket.send(JSON.stringify(message));
}
onMessage(handler) {
this.messageHandlers.push(handler);
}
onOpen(handler) {
this.openHandlers.push(handler);
}
onError(handler) {
this.errorHandlers.push(handler);
}
onClose(handler) {
this.closeHandlers.push(handler);
}
isConnected() {
return !!this.socket && this.socket.readyState === WS_OPEN;
}
async handleIncomingMessage(rawData) {
try {
const text = typeof rawData === 'string'
? rawData
: rawData instanceof Buffer
? rawData.toString('utf8')
: String(rawData ?? '');
const parsed = JSON.parse(text);
for (const handler of this.messageHandlers) {
await handler(parsed);
}
}
catch (error) {
await this.emitError(this.normalizeError(error, 'Failed to process WS message'));
}
}
normalizeError(error, fallback) {
if (error instanceof Error) {
return error;
}
if (typeof error === 'string' && error.trim()) {
return new Error(error.trim());
}
return new Error(fallback);
}
async emitOpen() {
for (const handler of this.openHandlers) {
await handler();
}
}
async emitError(error) {
for (const handler of this.errorHandlers) {
await handler(error);
}
}
async emitClose(info) {
for (const handler of this.closeHandlers) {
await handler(info);
}
}
}
exports.GenericWebSocketTransport = GenericWebSocketTransport;