Skip to content

Commit

Permalink
feat(rpc): make fetch customizable (#574)
Browse files Browse the repository at this point in the history
  • Loading branch information
homura authored Nov 27, 2023
1 parent 68e4f5b commit ce8eead
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 23 deletions.
5 changes: 5 additions & 0 deletions .changeset/new-foxes-heal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ckb-lumos/rpc": minor
---

make `fetch` customizable
25 changes: 25 additions & 0 deletions packages/rpc/__tests__/custom-fetch.test.js
Original file line number Diff line number Diff line change
@@ -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"
);
});
});
23 changes: 13 additions & 10 deletions packages/rpc/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -37,10 +37,11 @@ export class CKBRPC extends Base {
return this.#resultFormatter;
}

constructor(url: string, config: RPCConfig = { timeout: 30000 }) {
constructor(url: string, config: Partial<RPCConfig> = {}) {
super();
this.setNode({ url });
this.#config = config;
const { timeout = 30000, fetch = fetch_ } = config;
this.#config = { timeout, fetch };

Object.defineProperties(this, {
addMethod: {
Expand All @@ -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);
});
}

Expand Down Expand Up @@ -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);

Expand Down
24 changes: 13 additions & 11 deletions packages/rpc/src/method.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -24,12 +24,13 @@ export class Method {
constructor(
node: CKBComponents.Node,
options: CKBComponents.Method,
config: RPCConfig = { timeout: 30000 }
config: Partial<RPCConfig> = {}
) {
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,
Expand All @@ -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) {
Expand Down
2 changes: 0 additions & 2 deletions packages/rpc/src/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions packages/rpc/src/types/common.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/// <reference lib="dom" />

export type RPCConfig = {
timeout: number;
fetch: typeof fetch;
};
28 changes: 28 additions & 0 deletions website/docs/migrations/migrate-to-v0.21.md
Original file line number Diff line number Diff line change
@@ -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 }) })
},
})
```

2 comments on commit ce8eead

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 New canary release: 0.0.0-canary-ce8eead-20231127085615

npm install @ckb-lumos/[email protected]

@vercel
Copy link

@vercel vercel bot commented on ce8eead Nov 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.