Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(http): onFetchRequest #2048

Merged
merged 5 commits into from Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/chatty-berries-brake.md
@@ -0,0 +1,5 @@
---
"viem": patch
---

Added `onFetchRequest` to `http` transport.
16 changes: 16 additions & 0 deletions site/pages/docs/clients/transports/http.md
Expand Up @@ -171,6 +171,22 @@ const transport = http('https://eth-mainnet.g.alchemy.com/v2/...', {
})
```

### onFetchRequest (optional)

- **Type:** `(request: Request) => void`

A callback to handle the fetch request. Useful for logging or debugging.

```ts twoslash
import { http } from 'viem'
// ---cut---
const transport = http('https://eth-mainnet.g.alchemy.com/v2/...', {
onFetchRequest(request) {
console.log(request) // [!code focus]
}
})
```

### onFetchResponse (optional)

- **Type:** `(response: Response) => void`
Expand Down
20 changes: 20 additions & 0 deletions src/clients/transports/http.test.ts
Expand Up @@ -271,6 +271,26 @@ describe('request', () => {
await server.close()
})

test('behavior: onFetchRequest', async () => {
const server = await createHttpServer((_, res) => {
res.end(JSON.stringify({ result: '0x1' }))
})

const requests: Request[] = []
const transport = http(server.url, {
key: 'mock',
onFetchRequest(request) {
requests.push(request)
},
})({ chain: localhost })

await transport.request({ method: 'eth_blockNumber' })

expect(requests.length).toBe(1)

await server.close()
})

test('behavior: onFetchResponse', async () => {
const server = await createHttpServer((_, res) => {
res.end(JSON.stringify({ result: '0x1' }))
Expand Down
8 changes: 5 additions & 3 deletions src/clients/transports/http.ts
Expand Up @@ -36,9 +36,9 @@ export type HttpTransportConfig = {
* @link https://developer.mozilla.org/en-US/docs/Web/API/fetch
*/
fetchOptions?: HttpRpcClientOptions['fetchOptions'] | undefined
/**
* A callback to handle the response from `fetch`.
*/
/** A callback to handle the response from `fetch`. */
onFetchRequest?: HttpRpcClientOptions['onRequest'] | undefined
/** A callback to handle the response from `fetch`. */
onFetchResponse?: HttpRpcClientOptions['onResponse'] | undefined
/** The key of the HTTP transport. */
key?: TransportConfig['key'] | undefined
Expand Down Expand Up @@ -78,6 +78,7 @@ export function http(
fetchOptions,
key = 'http',
name = 'HTTP JSON-RPC',
onFetchRequest,
onFetchResponse,
retryDelay,
} = config
Expand All @@ -91,6 +92,7 @@ export function http(

const rpcClient = getHttpRpcClient(url_, {
fetchOptions,
onRequest: onFetchRequest,
onResponse: onFetchResponse,
timeout,
})
Expand Down
30 changes: 28 additions & 2 deletions src/utils/rpc/http.test.ts
Expand Up @@ -151,6 +151,32 @@
await server.close()
})

test('onRequest', async () => {
const server = await createHttpServer((_, res) => {
res.end(JSON.stringify({ result: '0x1' }))
})

const requests: Request[] = []
const client = getHttpRpcClient(server.url, {
onRequest: (request) => {
requests.push(request)
},
})
await client.request({
body: { method: 'web3_clientVersion' },
})
await client.request({
body: { method: 'web3_clientVersion' },
onRequest: (request) => {
requests.push(request)
},
})

expect(requests.length).toBe(2)

await server.close()
})

test('onResponse', async () => {
const server = await createHttpServer((_, res) => {
res.end(JSON.stringify({ result: '0x1' }))
Expand Down Expand Up @@ -303,12 +329,12 @@
).toMatchInlineSnapshot(`
[
{
"id": 70,
"id": 74,
"jsonrpc": "2.0",
"result": "anvil/v0.2.0",
},
{
"id": 71,
"id": 75,
"jsonrpc": "2.0",
"result": "anvil/v0.2.0",
},
Expand All @@ -326,7 +352,7 @@
{ method: 'eth_getBlockByHash', params: ['0x0', false] },
],
}),
).toMatchInlineSnapshot(`

Check failure on line 355 in src/utils/rpc/http.test.ts

View workflow job for this annotation

GitHub Actions / Verify / Test (http, 2, 3)

src/utils/rpc/http.test.ts > http (batch) > invalid rpc params

Error: Snapshot `http (batch) > invalid rpc params 1` mismatched Anvil log output ======================================= eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining web3_clientVersion web3_clientVersion evm_setIntervalMining web3_clientVersion web3_clientVersion evm_setIntervalMining web3_clientVersion evm_setIntervalMining web3_clientVersion evm_setIntervalMining web3_clientVersion evm_setIntervalMining web3_clientVersion Anvil log output ======================================= eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining web3_clientVersion web3_clientVersion evm_setIntervalMining web3_clientVersion web3_clientVersion evm_setIntervalMining web3_clientVersion evm_setIntervalMining web3_clientVersion evm_setIntervalMining web3_clientVersion evm_setIntervalMining web3_clientVersion Anvil log output ======================================= eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setInterv

Check failure on line 355 in src/utils/rpc/http.test.ts

View workflow job for this annotation

GitHub Actions / Verify / Test (http, 2, 3)

src/utils/rpc/http.test.ts > http (batch) > invalid rpc params

Error: Snapshot `http (batch) > invalid rpc params 2` mismatched - Expected + Received [ { - "id": 72, + "id": 78, "jsonrpc": "2.0", "result": "anvil/v0.2.0", }, { "error": { "code": -32602, "message": "Odd number of digits", }, - "id": 73, + "id": 79, "jsonrpc": "2.0", }, ] ❯ src/utils/rpc/http.test.ts:355:7

Check failure on line 355 in src/utils/rpc/http.test.ts

View workflow job for this annotation

GitHub Actions / Verify / Test (http, 2, 3)

src/utils/rpc/http.test.ts > http (batch) > invalid rpc params

Error: Snapshot `http (batch) > invalid rpc params 3` mismatched - Expected + Received [ { - "id": 72, + "id": 80, "jsonrpc": "2.0", "result": "anvil/v0.2.0", }, { "error": { "code": -32602, "message": "Odd number of digits", }, - "id": 73, + "id": 81, "jsonrpc": "2.0", }, ] ❯ src/utils/rpc/http.test.ts:355:7

Check failure on line 355 in src/utils/rpc/http.test.ts

View workflow job for this annotation

GitHub Actions / Verify / Test (http, 2, 3)

src/utils/rpc/http.test.ts > http (batch) > invalid rpc params

Error: Snapshot `http (batch) > invalid rpc params 4` mismatched - Expected + Received [ { - "id": 72, + "id": 82, "jsonrpc": "2.0", "result": "anvil/v0.2.0", }, { "error": { "code": -32602, "message": "Odd number of digits", }, - "id": 73, + "id": 83, "jsonrpc": "2.0", }, ] ❯ src/utils/rpc/http.test.ts:355:7

Check failure on line 355 in src/utils/rpc/http.test.ts

View workflow job for this annotation

GitHub Actions / Verify / Test (http, 2, 3)

src/utils/rpc/http.test.ts > http (batch) > invalid rpc params

Error: Snapshot `http (batch) > invalid rpc params 1` mismatched Anvil log output ======================================= eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining web3_clientVersion web3_clientVersion evm_setIntervalMining web3_clientVersion web3_clientVersion evm_setIntervalMining web3_clientVersion evm_setIntervalMining web3_clientVersion evm_setIntervalMining web3_clientVersion evm_setIntervalMining web3_clientVersion Anvil log output ======================================= eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining web3_clientVersion web3_clientVersion evm_setIntervalMining web3_clientVersion web3_clientVersion evm_setIntervalMining web3_clientVersion evm_setIntervalMining web3_clientVersion evm_setIntervalMining web3_clientVersion evm_setIntervalMining web3_clientVersion Anvil log output ======================================= eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining web3_clientVersion web3_clientVersion evm_setIntervalMining web3_clientVersion web3_clientVersion evm_setIntervalMining web3_clientVersion evm_setIntervalMining web3_clientVersion evm_setIntervalMining web3_clientVersion evm_setIntervalMining web3_clientVersion Anvil log output ======================================= eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber eth_getBlockByNumber evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining evm_setIntervalMining web3_clientVersion web3_clientVersion evm_setIntervalMi

Check failure on line 355 in src/utils/rpc/http.test.ts

View workflow job for this annotation

GitHub Actions / Verify / Test (http, 2, 3)

src/utils/rpc/http.test.ts > http (batch) > invalid rpc params

Error: Snapshot `http (batch) > invalid rpc params 2` mismatched - Expected + Received [ { - "id": 72, + "id": 78, "jsonrpc": "2.0", "result": "anvil/v0.2.0", }, { "error": { "code": -32602, "message": "Odd number of digits", }, - "id": 73, + "id": 79, "jsonrpc": "2.0", }, ] ❯ src/utils/rpc/http.test.ts:355:7

Check failure on line 355 in src/utils/rpc/http.test.ts

View workflow job for this annotation

GitHub Actions / Verify / Test (http, 2, 3)

src/utils/rpc/http.test.ts > http (batch) > invalid rpc params

Error: Snapshot `http (batch) > invalid rpc params 3` mismatched - Expected + Received [ { - "id": 72, + "id": 80, "jsonrpc": "2.0", "result": "anvil/v0.2.0", }, { "error": { "code": -32602, "message": "Odd number of digits", }, - "id": 73, + "id": 81, "jsonrpc": "2.0", }, ] ❯ src/utils/rpc/http.test.ts:355:7

Check failure on line 355 in src/utils/rpc/http.test.ts

View workflow job for this annotation

GitHub Actions / Verify / Test (http, 2, 3)

src/utils/rpc/http.test.ts > http (batch) > invalid rpc params

Error: Snapshot `http (batch) > invalid rpc params 4` mismatched - Expected + Received [ { - "id": 72, + "id": 82, "jsonrpc": "2.0", "result": "anvil/v0.2.0", }, { "error": { "code": -32602, "message": "Odd number of digits", }, - "id": 73, + "id": 83, "jsonrpc": "2.0", }, ] ❯ src/utils/rpc/http.test.ts:355:7
[
{
"id": 72,
Expand Down
29 changes: 17 additions & 12 deletions src/utils/rpc/http.ts
Expand Up @@ -14,24 +14,28 @@ import { stringify } from '../stringify.js'
import { idCache } from './id.js'

export type HttpRpcClientOptions = {
// Request configuration to pass to `fetch`.
/** Request configuration to pass to `fetch`. */
fetchOptions?: Omit<RequestInit, 'body'> | undefined
// A callback to handle the response.
/** A callback to handle the request. */
onRequest?: ((request: Request) => Promise<void> | void) | undefined
Copy link
Member Author

Choose a reason for hiding this comment

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

Might be more useful to type with input and init instead.

(input: string | Request | URL, init?: RequestInit | undefined) => Promise<void> | void

/** A callback to handle the response. */
onResponse?: ((response: Response) => Promise<void> | void) | undefined
// The timeout (in ms) for the request.
/** The timeout (in ms) for the request. */
timeout?: number | undefined
}

export type HttpRequestParameters<
TBody extends RpcRequest | RpcRequest[] = RpcRequest,
> = {
// The RPC request body.
/** The RPC request body. */
body: TBody
// Request configuration to pass to `fetch`.
/** Request configuration to pass to `fetch`. */
fetchOptions?: HttpRpcClientOptions['fetchOptions'] | undefined
// A callback to handle the response.
/** A callback to handle the response. */
onRequest?: ((request: Request) => Promise<void> | void) | undefined
/** A callback to handle the response. */
onResponse?: ((response: Response) => Promise<void> | void) | undefined
// The timeout (in ms) for the request.
/** The timeout (in ms) for the request. */
timeout?: HttpRpcClientOptions['timeout'] | undefined
}

Expand Down Expand Up @@ -60,6 +64,7 @@ export function getHttpRpcClient(
const {
body,
fetchOptions = {},
onRequest = options.onRequest,
onResponse = options.onResponse,
timeout = options.timeout ?? 10_000,
} = params
Expand All @@ -72,7 +77,7 @@ export function getHttpRpcClient(
try {
const response = await withTimeout(
async ({ signal }) => {
const response = await fetch(url, {
const request = new Request(url, {
...fetchOptions,
body: Array.isArray(body)
? stringify(
Expand All @@ -94,6 +99,8 @@ export function getHttpRpcClient(
method: method || 'POST',
signal: signal_ || (timeout > 0 ? signal : null),
})
if (onRequest) await onRequest(request)
const response = await fetch(request)
return response
},
{
Expand All @@ -108,11 +115,9 @@ export function getHttpRpcClient(
let data: any
if (
response.headers.get('Content-Type')?.startsWith('application/json')
) {
)
data = await response.json()
} else {
data = await response.text()
}
else data = await response.text()

if (!response.ok) {
throw new HttpRequestError({
Expand Down