UNPKG

jsonrpctts

Version:

A TypeScript JSON-RPC 2.0 client with axios

299 lines 13.6 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RPCClient = void 0; var errors_1 = require("./errors"); var axios_1 = __importDefault(require("axios")); /** * JSON-RPC 2.0 客户端实现 * @template T - 定义RPC方法的类型结构 */ var RPCClient = /** @class */ (function () { /** * 构造函数 * @param config 客户端配置 * { * endpoint: string, // RPC服务器地址 * timeout?: number, // 超时时间(毫秒) * headers?: Record<string, string> // 自定义请求头 * } */ function RPCClient(config) { // 合并默认配置和用户配置 this.config = __assign({ timeout: 5000 }, config // 允许覆盖默认值 ); this.id = 0; // 初始化ID计数器 this.abortController = new AbortController(); // 创建中断控制器 this.pendingRequests = new Map(); // 初始化请求跟踪Map } /** * 执行RPC调用 * @param method RPC方法名 * @param params 方法参数 * @returns 返回RPC调用结果的Promise */ RPCClient.prototype.call = function (method) { var params = []; for (var _i = 1; _i < arguments.length; _i++) { params[_i - 1] = arguments[_i]; } return __awaiter(this, void 0, void 0, function () { var requestId, request, abortController, response, error_1; return __generator(this, function (_a) { switch (_a.label) { case 0: requestId = this.nextID(); request = { jsonrpc: "2.0", // 遵循JSON-RPC 2.0规范 method: String(method), // 方法名转字符串 id: requestId // 使用生成的唯一ID }; // 如果有参数,进行验证并添加到请求中 if (params.length > 0) { this.validateParams(params); // 如果是单个参数直接使用,多个参数转为数组 request.params = params; } abortController = new AbortController(); this.pendingRequests.set(requestId, abortController); _a.label = 1; case 1: _a.trys.push([1, 3, 4, 5]); return [4 /*yield*/, axios_1.default.post(this.config.endpoint, request, { headers: __assign({ 'Content-Type': 'application/json' }, this.config.headers // 合并自定义头 ), timeout: this.config.timeout, // 设置超时 signal: abortController.signal // 绑定中断信号 })]; case 2: response = _a.sent(); // 处理响应 return [2 /*return*/, this.processResponse(response, request)]; case 3: error_1 = _a.sent(); // 捕获并处理错误 throw this.handleError(error_1); case 4: // 请求完成后从跟踪Map中移除 this.pendingRequests.delete(requestId); return [7 /*endfinally*/]; case 5: return [2 /*return*/]; } }); }); }; /** * 取消所有进行中的请求 */ RPCClient.prototype.abortAll = function () { // 终止主中断控制器 this.abortController.abort(); // 遍历并终止所有单独请求 this.pendingRequests.forEach(function (controller) { return controller.abort(); }); // 清空跟踪Map this.pendingRequests.clear(); // 重置中断控制器,以便后续请求 this.abortController = new AbortController(); }; /** * 处理HTTP响应并提取RPC结果 * @param response Axios响应对象 * @param request 原始RPC请求 * @returns 解析后的结果 * @throws 当响应不符合规范时抛出RPCError */ RPCClient.prototype.processResponse = function (response, request) { return __awaiter(this, void 0, void 0, function () { var jsonResponse; return __generator(this, function (_a) { switch (_a.label) { case 0: // 检查HTTP状态码是否成功(200-299) if (response.status < 200 || response.status >= 300) { throw new errors_1.RPCError("HTTP ".concat(response.status, " ").concat(response.statusText), response.status); } return [4 /*yield*/, this.parseJSONResponse(response.data)]; case 1: jsonResponse = _a.sent(); // 验证响应ID是否匹配请求ID if (jsonResponse.id !== request.id) { throw new errors_1.RPCError("Response ID (".concat(jsonResponse.id, ") does not match request ID (").concat(request.id, ")"), -32600); } // 如果响应包含错误,处理错误 if (jsonResponse.error) { this.handleErrorResponse(jsonResponse.error); } // 确保响应包含结果字段 if (!jsonResponse.hasOwnProperty('result')) { throw new errors_1.RPCError('Missing result in JSON-RPC response', -32600); } // 返回结果 return [2 /*return*/, jsonResponse.result]; } }); }); }; /** * 处理JSON-RPC错误响应 * @param error JSON-RPC错误对象 * @throws 根据错误类型抛出BackendError或RPCError */ RPCClient.prototype.handleErrorResponse = function (error) { var code = error.code, message = error.message, data = error.data; // 判断是否为服务器端错误(-32000到-32099) if (code >= -32099 && code <= -32000) { throw new errors_1.BackendError(message, code, data); } // 其他RPC协议错误 throw new errors_1.RPCError(message, code, data); }; /** * 解析JSON-RPC响应 * @param response 原始响应数据 * @returns 解析后的JSON-RPC响应 * @throws 当响应格式无效时抛出RPCError */ RPCClient.prototype.parseJSONResponse = function (response) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { // 检查响应是否为对象 if (response === null || typeof response !== 'object') { throw new errors_1.RPCError('Invalid JSON-RPC response format: expected object', -32603); } // 验证JSON-RPC版本 if (response.jsonrpc !== "2.0") { throw new errors_1.RPCError('Invalid JSON-RPC version', -32600); } return [2 /*return*/, response]; }); }); }; /** * 处理请求过程中的错误 * @param error 捕获的错误对象 * @returns 适当封装的错误对象 */ RPCClient.prototype.handleError = function (error) { // 检查是否为Axios错误 if (axios_1.default.isAxiosError(error)) { return this.handleAxiosError(error); } // 处理普通的Error对象 if (error instanceof Error) { return new errors_1.NetworkError("Request failed: ".concat(error.message)); } // 未知错误类型 return new errors_1.NetworkError("Unknown request error"); }; /** * 处理Axios特有的错误 * @param error Axios错误对象 * @returns 封装的网络或RPC错误 */ RPCClient.prototype.handleAxiosError = function (error) { // 请求超时错误 if (error.code === 'ECONNABORTED') { return new errors_1.NetworkError("Request timed out after ".concat(this.config.timeout, "ms"), -504); } // 请求被取消错误 if (error.code === 'ERR_CANCELED') { return new errors_1.NetworkError('Request was aborted', -499); } // 无响应的网络错误 if (!error.response) { return new errors_1.NetworkError("Network error: ".concat(error.message), -503); } // HTTP错误状态码 return new errors_1.RPCError("HTTP ".concat(error.response.status, " ").concat(error.response.statusText), error.response.status); }; /** * 验证参数是否符合JSON序列化要求 * @param params 参数数组 * @throws 当参数无效时抛出RPCError */ RPCClient.prototype.validateParams = function (params) { // 检查每个参数的基本类型是否有效 var isValid = params.every(function (p) { return p === null || p === undefined || typeof p === "object" || typeof p === "string" || typeof p === "number" || typeof p === "boolean"; }); // 如果发现无效参数类型 if (!isValid) { throw new errors_1.RPCError("Invalid parameters: must be JSON-serializable values", -32602); } // 尝试实际序列化参数 try { JSON.stringify(params); } catch (e) { throw new errors_1.RPCError("Parameters are not JSON-serializable: ".concat(e instanceof Error ? e.message : String(e)), -32602); } }; /** * 生成下一个请求ID * @returns 唯一且没有冲突的请求ID */ RPCClient.prototype.nextID = function () { // 循环递增ID,确保不会与已有请求冲突 do { this.id = (this.id + 1) % Number.MAX_SAFE_INTEGER; } while (this.pendingRequests.has(this.id)); return this.id; }; return RPCClient; }()); exports.RPCClient = RPCClient; //# sourceMappingURL=core.js.map