@arifwidianto/rpc-agent
Version:
RPC Agent for both client and server, extends more methods easily
305 lines • 11.4 kB
JavaScript
"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