diff --git a/.changeset/chatty-berries-brake.md b/.changeset/chatty-berries-brake.md new file mode 100644 index 0000000000..e753fb5a39 --- /dev/null +++ b/.changeset/chatty-berries-brake.md @@ -0,0 +1,5 @@ +--- +"viem": patch +--- + +Added `onFetchRequest` to `http` transport. diff --git a/site/pages/docs/clients/transports/http.md b/site/pages/docs/clients/transports/http.md index 92e5b95150..32ccf84c53 100644 --- a/site/pages/docs/clients/transports/http.md +++ b/site/pages/docs/clients/transports/http.md @@ -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` diff --git a/src/clients/transports/http.test.ts b/src/clients/transports/http.test.ts index a4bf596515..69b99f49d9 100644 --- a/src/clients/transports/http.test.ts +++ b/src/clients/transports/http.test.ts @@ -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' })) diff --git a/src/clients/transports/http.ts b/src/clients/transports/http.ts index 3f21b478f1..061799e454 100644 --- a/src/clients/transports/http.ts +++ b/src/clients/transports/http.ts @@ -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 @@ -78,6 +78,7 @@ export function http( fetchOptions, key = 'http', name = 'HTTP JSON-RPC', + onFetchRequest, onFetchResponse, retryDelay, } = config @@ -91,6 +92,7 @@ export function http( const rpcClient = getHttpRpcClient(url_, { fetchOptions, + onRequest: onFetchRequest, onResponse: onFetchResponse, timeout, }) diff --git a/src/utils/rpc/http.test.ts b/src/utils/rpc/http.test.ts index 36eafae31c..c174e1e8af 100644 --- a/src/utils/rpc/http.test.ts +++ b/src/utils/rpc/http.test.ts @@ -151,6 +151,32 @@ describe('request', () => { 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' })) @@ -303,12 +329,12 @@ describe('http (batch)', () => { ).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", }, diff --git a/src/utils/rpc/http.ts b/src/utils/rpc/http.ts index 76d4125538..8131716712 100644 --- a/src/utils/rpc/http.ts +++ b/src/utils/rpc/http.ts @@ -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 | undefined - // A callback to handle the response. + /** A callback to handle the request. */ + onRequest?: ((request: Request) => Promise | void) | undefined + /** A callback to handle the response. */ onResponse?: ((response: Response) => Promise | 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) | undefined + /** A callback to handle the response. */ onResponse?: ((response: Response) => Promise | void) | undefined - // The timeout (in ms) for the request. + /** The timeout (in ms) for the request. */ timeout?: HttpRpcClientOptions['timeout'] | undefined } @@ -60,6 +64,7 @@ export function getHttpRpcClient( const { body, fetchOptions = {}, + onRequest = options.onRequest, onResponse = options.onResponse, timeout = options.timeout ?? 10_000, } = params @@ -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( @@ -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 }, { @@ -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({