jsonrpctts
Version:
A TypeScript JSON-RPC 2.0 client with axios
299 lines • 13.6 kB
JavaScript
"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