-
-
Notifications
You must be signed in to change notification settings - Fork 735
/
http.ts
149 lines (136 loc) · 4.39 KB
/
http.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import {
HttpRequestError,
type HttpRequestErrorType as HttpRequestErrorType_,
TimeoutError,
type TimeoutErrorType,
} from '../../errors/request.js'
import type { ErrorType } from '../../errors/utils.js'
import type { RpcRequest, RpcResponse } from '../../types/rpc.js'
import {
type WithTimeoutErrorType,
withTimeout,
} from '../promise/withTimeout.js'
import { stringify } from '../stringify.js'
import { idCache } from './id.js'
export type HttpRpcClientOptions = {
/** Request configuration to pass to `fetch`. */
fetchOptions?: Omit<RequestInit, 'body'> | undefined
/** A callback to handle the request. */
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. */
timeout?: number | undefined
}
export type HttpRequestParameters<
body extends RpcRequest | RpcRequest[] = RpcRequest,
> = {
/** The RPC request body. */
body: body
/** Request configuration to pass to `fetch`. */
fetchOptions?: HttpRpcClientOptions['fetchOptions'] | undefined
/** 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. */
timeout?: HttpRpcClientOptions['timeout'] | undefined
}
export type HttpRequestReturnType<
body extends RpcRequest | RpcRequest[] = RpcRequest,
> = body extends RpcRequest[] ? RpcResponse[] : RpcResponse
export type HttpRequestErrorType =
| HttpRequestErrorType_
| TimeoutErrorType
| WithTimeoutErrorType
| ErrorType
export type HttpRpcClient = {
request<body extends RpcRequest | RpcRequest[]>(
params: HttpRequestParameters<body>,
): Promise<HttpRequestReturnType<body>>
}
export function getHttpRpcClient(
url: string,
options: HttpRpcClientOptions = {},
): HttpRpcClient {
return {
async request(params) {
const {
body,
onRequest = options.onRequest,
onResponse = options.onResponse,
timeout = options.timeout ?? 10_000,
} = params
const fetchOptions = {
...(options.fetchOptions ?? {}),
...(params.fetchOptions ?? {}),
}
const { headers, method, signal: signal_ } = fetchOptions
try {
const response = await withTimeout(
async ({ signal }) => {
const init: RequestInit = {
...fetchOptions,
body: Array.isArray(body)
? stringify(
body.map((body) => ({
jsonrpc: '2.0',
id: body.id ?? idCache.take(),
...body,
})),
)
: stringify({
jsonrpc: '2.0',
id: body.id ?? idCache.take(),
...body,
}),
headers: {
'Content-Type': 'application/json',
...headers,
},
method: method || 'POST',
signal: signal_ || (timeout > 0 ? signal : null),
}
const request = new Request(url, init)
if (onRequest) await onRequest(request)
const response = await fetch(url, init)
return response
},
{
errorInstance: new TimeoutError({ body, url }),
timeout,
signal: true,
},
)
if (onResponse) await onResponse(response)
let data: any
if (
response.headers.get('Content-Type')?.startsWith('application/json')
)
data = await response.json()
else {
data = await response.text()
data = JSON.parse(data || '{}')
}
if (!response.ok) {
throw new HttpRequestError({
body,
details: stringify(data.error) || response.statusText,
headers: response.headers,
status: response.status,
url,
})
}
return data
} catch (err) {
if (err instanceof HttpRequestError) throw err
if (err instanceof TimeoutError) throw err
throw new HttpRequestError({
body,
cause: err as Error,
url,
})
}
},
}
}