Skip to content

Commit

Permalink
feat: add fetchOptions to http transport (#89)
Browse files Browse the repository at this point in the history
* feat: add fetchOptions to http transport

* chore: changeset

* tests: update snapshots
  • Loading branch information
jxom authored Feb 22, 2023
1 parent 0ac32c2 commit 3e45853
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .changeset/brave-hairs-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Added `fetchOptions` to the `http` transport.
16 changes: 16 additions & 0 deletions site/docs/clients/transports/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ URL of the JSON-RPC API.
const transport = http('https://eth-mainnet.g.alchemy.com/v2/...')
```

### fetchOptions (optional)

- **Type:** [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/fetch)

[Fetch options](https://developer.mozilla.org/en-US/docs/Web/API/fetch) to pass to the internal `fetch` function. Useful for passing auth headers or cache options.

```ts
const transport = http('https://eth-mainnet.g.alchemy.com/v2/...', {
fetchOptions: { // [!code focus:5]
headers: {
'Authorization': 'Bearer ...'
}
}
})
```

### key (optional)

- **Type:** `string`
Expand Down
24 changes: 24 additions & 0 deletions src/clients/transports/http.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IncomingHttpHeaders } from 'http'
import { assertType, describe, expect, test } from 'vitest'

import { localhost } from '../../chains'
Expand Down Expand Up @@ -114,6 +115,29 @@ describe('request', () => {
expect(await transport.request({ method: 'eth_blockNumber' })).toBeDefined()
})

test('behavior: fetchOptions', async () => {
let headers: IncomingHttpHeaders = {}
const server = await createHttpServer((req, res) => {
headers = req.headers
res.end(JSON.stringify({ result: '0x1' }))
})

const transport = http(server.url, {
key: 'mock',
fetchOptions: {
headers: { 'x-wagmi': 'gm' },
cache: 'force-cache',
method: 'PATCH',
signal: null,
},
})({ chain: localhost })

await transport.request({ method: 'eth_blockNumber' })
expect(headers['x-wagmi']).toBeDefined()

await server.close()
})

test('behavior: retryCount', async () => {
let retryCount = -1
const server = await createHttpServer((req, res) => {
Expand Down
10 changes: 9 additions & 1 deletion src/clients/transports/http.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { UrlRequiredError } from '../../errors'
import { rpc } from '../../utils/rpc'
import type { HttpOptions } from '../../utils'
import { rpc } from '../../utils'
import type { Transport, TransportConfig } from './createTransport'
import { createTransport } from './createTransport'

export type HttpTransportConfig = {
/**
* Request configuration to pass to `fetch`.
* @link https://developer.mozilla.org/en-US/docs/Web/API/fetch
*/
fetchOptions?: HttpOptions['fetchOptions']
/** The key of the HTTP transport. */
key?: TransportConfig['key']
/** The name of the HTTP transport. */
Expand Down Expand Up @@ -32,6 +38,7 @@ export function http(
config: HttpTransportConfig = {},
): HttpTransport {
const {
fetchOptions,
key = 'http',
name = 'HTTP JSON-RPC',
retryDelay,
Expand All @@ -51,6 +58,7 @@ export function http(
method,
params,
},
fetchOptions,
timeout,
})
return result
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ test('exports utils', () => {
"getEventSelector": [Function],
"getFunctionSelector": [Function],
"getNodeError": [Function],
"getSocket": [Function],
"getTransactionError": [Function],
"gweiUnits": {
"ether": -9,
Expand Down
3 changes: 2 additions & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ export {

export { getEventSelector, getFunctionSelector, keccak256 } from './hash'

export { rpc } from './rpc'
export type { HttpOptions, RpcResponse, Socket } from './rpc'
export { getSocket, rpc } from './rpc'

export { stringify } from './stringify'

Expand Down
24 changes: 24 additions & 0 deletions src/utils/rpc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { numberToHex } from './encoding'
import type { RpcResponse } from './rpc'
import { getSocket, rpc } from './rpc'
import { wait } from './wait'
import { IncomingHttpHeaders } from 'http'

test('rpc', () => {
expect(rpc).toMatchInlineSnapshot(`
Expand Down Expand Up @@ -122,6 +123,29 @@ describe('http', () => {
await wait(500)
})

test('fetchOptions', async () => {
let headers: IncomingHttpHeaders = {}
const server = await createHttpServer((req, res) => {
headers = req.headers
res.end(JSON.stringify({ result: '0x1' }))
})

expect(
await rpc.http(server.url, {
body: { method: 'web3_clientVersion' },
fetchOptions: {
headers: { 'x-wagmi': 'gm' },
cache: 'force-cache',
method: 'PATCH',
signal: null,
},
}),
).toBeDefined()
expect(headers['x-wagmi']).toBeDefined()

await server.close()
})

test('http error', async () => {
const server = await createHttpServer((req, res) => {
res.writeHead(500, {
Expand Down
28 changes: 16 additions & 12 deletions src/utils/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,28 +52,32 @@ export type RpcResponse<TResult = any, TError = any> = {
///////////////////////////////////////////////////
// HTTP

export type HttpOptions = {
// The RPC request body.
body: RpcRequest
// Request configuration to pass to `fetch`.
fetchOptions?: Omit<RequestInit, 'body'>
// The timeout (in ms) for the request.
timeout?: number
}

async function http(
url: string,
{
body,
timeout = 10_000,
}: {
// The RPC request body.
body: RpcRequest
// The timeout (in ms) for the request.
timeout?: number
},
{ body, fetchOptions = {}, timeout = 10_000 }: HttpOptions,
) {
const { headers, method, signal: signal_ } = fetchOptions
try {
const response = await withTimeout(
async ({ signal }) => {
const response = await fetch(url, {
...fetchOptions,
body: stringify({ jsonrpc: '2.0', id: id++, ...body }),
headers: {
...headers,
'Content-Type': 'application/json',
},
method: 'POST',
body: stringify({ jsonrpc: '2.0', id: id++, ...body }),
signal: timeout > 0 ? signal : undefined,
method: method || 'POST',
signal: signal_ || (timeout > 0 ? signal : undefined),
})
return response
},
Expand Down

2 comments on commit 3e45853

@vercel
Copy link

@vercel vercel bot commented on 3e45853 Feb 22, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

viem-playground – ./playgrounds/browser

viem-playground-git-main-wagmi-dev.vercel.app
viem-playground-wagmi-dev.vercel.app
viem-playground.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 3e45853 Feb 22, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

viem-site – ./site

viem-site.vercel.app
www.viem.sh
viem.sh
viem-site-git-main-wagmi-dev.vercel.app
viem-site-wagmi-dev.vercel.app

Please sign in to comment.