UNPKG

@mcp-abap-adt/connection

Version:

ABAP connection layer for MCP ABAP ADT server

136 lines (135 loc) 4.28 kB
"use strict"; 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;