UNPKG

@arifwidianto/rpc-agent

Version:

RPC Agent for both client and server, extends more methods easily

305 lines 11.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AgentLibrary = exports.RpcClient = void 0; const net = __importStar(require("node:net")); const dgram = __importStar(require("node:dgram")); class RpcClient { availableExtensions = new Set(["echo"]); tcpClient = null; udpClient = null; requestId = 0; isClosing = false; reconnectAttempts = 0; maxReconnectAttempts = 3; reconnectDelay = 1000; pendingRequests = new Map(); config; debug; constructor(config, debug = false) { this.config = { ...config, udpPort: config.udpPort || 9102, }; this.debug = debug; if (this.debug) { console.log("[RpcClient] Initialized with config:", this.config); } } log(...args) { if (this.debug) { console.log("[RpcClient]", ...args); } } async init() { if (this.config.protocol === "tcp") { await this.initializeTcpClient(); } else if (this.config.protocol === "udp") { await this.initializeUdpClient(); } } async close() { if (this.isClosing) { return; } this.isClosing = true; for (const [id, { reject, timeout }] of this.pendingRequests) { clearTimeout(timeout); reject(new Error("Client closing")); this.pendingRequests.delete(id); } return new Promise((resolve) => { const cleanup = () => { if (this.tcpClient) { this.tcpClient.removeAllListeners(); this.tcpClient.end(); this.tcpClient = null; } if (this.udpClient) { try { this.udpClient.removeAllListeners(); this.udpClient.close(() => { this.udpClient = null; resolve(); }); } catch (error) { this.log("Error closing UDP socket:", error); this.udpClient = null; resolve(); } } else { resolve(); } }; setTimeout(cleanup, 1000); }).finally(() => { this.isClosing = false; this.reconnectAttempts = 0; }); } async initializeTcpClient() { return new Promise((resolve, reject) => { this.tcpClient = new net.Socket(); const connectionTimeout = setTimeout(() => { reject(new Error("Connection timeout")); this.tcpClient?.destroy(); }, 5000); this.tcpClient.connect(this.config.port, this.config.host, () => { clearTimeout(connectionTimeout); this.reconnectAttempts = 0; this.log("TCP client connected"); resolve(); }); this.tcpClient.on("error", (error) => { clearTimeout(connectionTimeout); this.log("TCP client error:", error); this.handleConnectionError(error).catch((e) => this.log("Reconnection failed:", e)); reject(error); }); this.tcpClient.on("close", () => { this.log("TCP connection closed"); if (!this.isClosing) { this.handleConnectionError(new Error("Connection closed")).catch((e) => this.log("Reconnection failed:", e)); } }); this.tcpClient.on("data", this.handleTcpResponse.bind(this)); this.tcpClient.setKeepAlive(true, 60000); }); } async initializeUdpClient() { return new Promise((resolve, reject) => { try { this.udpClient = dgram.createSocket({ type: "udp4", reuseAddr: true, }); this.udpClient.on("error", (error) => { this.log("UDP client error:", error); reject(error); }); this.udpClient.on("message", (msg, rinfo) => { this.handleUdpResponse(msg, rinfo); }); this.udpClient.bind(0, () => { const address = this.udpClient?.address(); this.log("UDP client bound to port:", address?.port); resolve(); }); } catch (error) { this.log("Failed to initialize UDP client:", error); reject(error); } }); } handleUdpResponse(data, rinfo) { try { const response = JSON.parse(data.toString()); this.log("Received UDP response:", response, rinfo ? `from ${rinfo?.address}:${rinfo?.port}` : ""); const pendingRequest = this.pendingRequests.get(response.id?.toString() || ""); if (pendingRequest) { const { resolve, reject, timeout } = pendingRequest; clearTimeout(timeout); this.pendingRequests.delete(response.id?.toString() || ""); if (response.error) { reject(new Error(response.error.message || "Unknown RPC error")); } else { resolve(response.result); } } else { this.log("No pending request found for response ID:", response.id); } } catch (error) { this.log("Error parsing UDP response:", error, rinfo ? `from ${rinfo?.address}:${rinfo?.port}` : ""); } } async handleConnectionError(error) { if (this.isClosing || this.reconnectAttempts >= this.maxReconnectAttempts) { throw error; } this.reconnectAttempts++; const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1); this.log(`Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts}) in ${delay}ms`); await new Promise((resolve) => setTimeout(resolve, delay)); await this.init(); } handleTcpResponse(data) { try { const response = JSON.parse(data.toString()); this.log("Received response:", response); const pendingRequest = this.pendingRequests.get(response.id?.toString() || ""); if (pendingRequest) { const { resolve, reject, timeout } = pendingRequest; clearTimeout(timeout); this.pendingRequests.delete(response.id?.toString() || ""); if (response.error) { reject(new Error(response.error.message || "Unknown RPC error")); } else { resolve(response.result); } } } catch (error) { this.log("Error parsing response:", error); } } async send(method, params) { if (this.config.protocol === "tcp") { return this.sendTcpRequest(method, params); } else if (this.config.protocol === "udp") { return this.sendUdpRequest(method, params); } else { throw new Error("Unsupported protocol"); } } async sendTcpRequest(method, params) { if (!this.tcpClient) { throw new Error("TCP client not initialized"); } this.log(`Sending TCP request - Method: ${method}, Params:`, params); return new Promise((resolve, reject) => { const requestId = Date.now().toString(); const request = { jsonrpc: "2.0", method, params, id: requestId, }; const timeout = setTimeout(() => { this.pendingRequests.delete(requestId); reject(new Error("Request timeout")); }, 30000); this.pendingRequests.set(requestId, { resolve, reject, timeout }); const data = JSON.stringify(request) + "\n"; if (!this.tcpClient?.write(data)) { this.tcpClient?.once("drain", () => { this.log("Write buffer drained"); }); } }); } async sendUdpRequest(method, params) { const client = this.udpClient; if (!client) { throw new Error("UDP client not initialized"); } this.log(`Sending UDP request - Method: ${method}, Params:`, params); return new Promise((resolve, reject) => { const requestId = Date.now().toString(); const request = { jsonrpc: "2.0", method, params, id: requestId, }; const timeout = setTimeout(() => { this.pendingRequests.delete(requestId); reject(new Error("Request timeout")); }, 5000); this.pendingRequests.set(requestId, { resolve, reject, timeout }); const data = JSON.stringify(request); client.send(data, this.config.udpPort, this.config.host, (error) => { if (error) { clearTimeout(timeout); this.pendingRequests.delete(requestId); reject(error); } }); }); } } exports.RpcClient = RpcClient; class AgentLibrary extends RpcClient { constructor(config, debug = false) { super(config, debug); } async getServerInfo() { return this.send("server.system", {}); } async listExtensions() { const extensions = await this.send("extensions.list", {}); return extensions || []; } async validatePort(port, type) { return this.send("network.validatePort", { port, type }); } async echo(message) { return this.send("echo.echo", { message }); } async getCurrentDate() { return this.send("date.now", {}); } } exports.AgentLibrary = AgentLibrary; //# sourceMappingURL=client.node.js.map