diff --git a/.changeset/new-foxes-heal.md b/.changeset/new-foxes-heal.md new file mode 100644 index 000000000..3c102ca39 --- /dev/null +++ b/.changeset/new-foxes-heal.md @@ -0,0 +1,5 @@ +--- +"@ckb-lumos/rpc": minor +--- + +make `fetch` customizable diff --git a/packages/rpc/__tests__/custom-fetch.test.js b/packages/rpc/__tests__/custom-fetch.test.js new file mode 100644 index 000000000..3147dd42e --- /dev/null +++ b/packages/rpc/__tests__/custom-fetch.test.js @@ -0,0 +1,25 @@ +const { RPC } = require(".."); + +describe("custom fetch", () => { + test("should work", async () => { + const customizedFetch = jest.fn((_, { body }) => + Promise.resolve({ + json: () => + Promise.resolve({ + jsonrpc: "2.0", + result: "0x8dd", + id: JSON.parse(body).id, + }), + }) + ); + + const rpc = new RPC("", { fetch: customizedFetch }); + await rpc.getTipBlockNumber(); + + expect(customizedFetch).toBeCalled(); + expect(JSON.parse(customizedFetch.mock.calls[0][1].body)).toHaveProperty( + "method", + "get_tip_block_number" + ); + }); +}); diff --git a/packages/rpc/src/index.ts b/packages/rpc/src/index.ts index cad961650..9405c55a9 100644 --- a/packages/rpc/src/index.ts +++ b/packages/rpc/src/index.ts @@ -9,7 +9,7 @@ import { PayloadInBatchException, } from "./exceptions"; import { RPCConfig } from "./types/common"; -import fetch from "cross-fetch"; +import fetch_ from "cross-fetch"; import { AbortController as CrossAbortController } from "abort-controller"; export const ParamsFormatter = paramsFormatter; @@ -37,10 +37,11 @@ export class CKBRPC extends Base { return this.#resultFormatter; } - constructor(url: string, config: RPCConfig = { timeout: 30000 }) { + constructor(url: string, config: Partial = {}) { super(); this.setNode({ url }); - this.#config = config; + const { timeout = 30000, fetch = fetch_ } = config; + this.#config = { timeout, fetch }; Object.defineProperties(this, { addMethod: { @@ -59,7 +60,7 @@ export class CKBRPC extends Base { }); Object.keys(this.rpcProperties).forEach((name) => { - this.addMethod({ name, ...this.rpcProperties[name] }, config); + this.addMethod({ name, ...this.rpcProperties[name] }, this.#config); }); } @@ -141,12 +142,14 @@ export class CKBRPC extends Base { ctx.#config.timeout ); - const batchRes = await fetch(ctx.#node.url, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify(payload), - signal: signal, - }).then((res) => res.json()); + const batchRes = await ctx.#config + .fetch(ctx.#node.url, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(payload), + signal: signal, + }) + .then((res) => res.json()); clearTimeout(timeout); diff --git a/packages/rpc/src/method.ts b/packages/rpc/src/method.ts index 947fcdebe..e671eff62 100644 --- a/packages/rpc/src/method.ts +++ b/packages/rpc/src/method.ts @@ -1,8 +1,8 @@ import { IdNotMatchException, ResponseException } from "./exceptions"; import { CKBComponents } from "./types/api"; import { RPCConfig } from "./types/common"; -import fetch from "cross-fetch"; import { AbortController as CrossAbortController } from "abort-controller"; +import fetch_ from "cross-fetch"; export class Method { #name: string; @@ -24,12 +24,13 @@ export class Method { constructor( node: CKBComponents.Node, options: CKBComponents.Method, - config: RPCConfig = { timeout: 30000 } + config: Partial = {} ) { this.#node = node; this.#options = options; this.#name = options.name; - this.#config = config; + const { timeout = 30000, fetch = fetch_ } = config; + this.#config = { timeout, fetch }; Object.defineProperty(this.call, "name", { value: options.name, @@ -46,14 +47,15 @@ export class Method { const timeout = setTimeout(() => controller.abort(), this.#config.timeout); - const res = await fetch(this.#node.url, { - method: "POST", - headers: { - "content-type": "application/json", - }, - body: JSON.stringify(payload), - signal, - }) + const res = await this.#config + .fetch(this.#node.url, { + method: "POST", + headers: { + "content-type": "application/json", + }, + body: JSON.stringify(payload), + signal, + }) .then((res) => res.json()) .then((res) => { if (res.id !== payload.id) { diff --git a/packages/rpc/src/types/api.ts b/packages/rpc/src/types/api.ts index 60385c92f..169a8eab8 100644 --- a/packages/rpc/src/types/api.ts +++ b/packages/rpc/src/types/api.ts @@ -50,8 +50,6 @@ export namespace CKBComponents { export type Since = string; export interface Node { url: string; - httpAgent?: any; - httpsAgent?: any; } export interface Method { name: string; diff --git a/packages/rpc/src/types/common.ts b/packages/rpc/src/types/common.ts index 030febe75..6d31656bb 100644 --- a/packages/rpc/src/types/common.ts +++ b/packages/rpc/src/types/common.ts @@ -1,3 +1,6 @@ +/// + export type RPCConfig = { timeout: number; + fetch: typeof fetch; }; diff --git a/website/docs/migrations/migrate-to-v0.21.md b/website/docs/migrations/migrate-to-v0.21.md new file mode 100644 index 000000000..a908d80f8 --- /dev/null +++ b/website/docs/migrations/migrate-to-v0.21.md @@ -0,0 +1,28 @@ +# Migrate to Lumos v0.21 + +### Deprecated the `httpAgent` and `httpsAgent` in `RPC.setNode` + +Please use the `fetch` option in `RPC` constructor instead. + +```diff +-const rpc = new RPC(url) ++const rpc = new RPC( ++ url, ++ { fetch: (request, init) => originalFetch(request, { ...init, keepalive: true }) }, ++) +-rpc.setNode({ url, httpAgent, httpsAgent }) ++rpc.setNode({ url }) +``` + +If you are still in working with Node.js(or Electron) runtime, you can migrate to `node-fetch` to continue using the customized agent + +```ts +import fetch from "node-fetch" +import { Agent } from "http" + +const rpc = new RPC(url, { + fetch: (request, init) => { + return fetch(request, { ...init, httpAgent: new Agent({ keepAlive: true }) }) + }, +}) +```