ox
Version:
Ethereum Standard Library
118 lines • 4.38 kB
JavaScript
import * as Errors from './Errors.js';
import { getUrl } from './internal/errors.js';
import * as promise from './internal/promise.js';
import * as internal from './internal/rpcTransport.js';
/**
* Creates a HTTP JSON-RPC Transport from a URL.
*
* @example
* ```ts twoslash
* import { RpcTransport } from 'ox'
*
* const transport = RpcTransport.fromHttp(
* 'https://1.rpc.thirdweb.com'
* )
*
* const blockNumber = await transport.request({
* method: 'eth_blockNumber'
* })
* // @log: '0x1a2b3c'
* ```
*
* @param url - URL to perform the JSON-RPC requests to.
* @param options - Transport options.
* @returns HTTP JSON-RPC Transport.
*/
export function fromHttp(url, options = {}) {
return internal.create({
async request(body_, options_) {
const { fetchFn = options.fetchFn ?? fetch, fetchOptions: fetchOptions_ = options.fetchOptions, timeout = options.timeout ?? 10_000, } = options_;
const body = JSON.stringify(body_);
const fetchOptions = typeof fetchOptions_ === 'function'
? await fetchOptions_(body_)
: fetchOptions_;
const response = await promise.withTimeout(({ signal }) => {
const externalSignal = fetchOptions?.signal;
const timeoutSignal = timeout > 0 ? signal : null;
const signals = [externalSignal, timeoutSignal].filter((s) => Boolean(s));
const composedSignal = signals.length === 0
? null
: signals.length === 1
? signals[0]
: AbortSignal.any(signals);
const init = {
...fetchOptions,
body,
headers: {
'Content-Type': 'application/json',
...Object.fromEntries(new Headers(fetchOptions?.headers)),
},
method: fetchOptions?.method ?? 'POST',
signal: composedSignal,
};
return fetchFn(url, init);
}, {
timeout,
signal: true,
});
// Always read as text so we can handle empty bodies and non-JSON
// payloads (some servers return `Content-Type: application/json` with
// an empty body for error responses).
const data = await response.text().then((data) => {
if (data === '') {
if (response.ok)
throw new MalformedResponseError({ response: data });
return { error: undefined };
}
try {
return JSON.parse(data);
}
catch {
if (response.ok)
throw new MalformedResponseError({
response: data,
});
return { error: data };
}
});
if (!response.ok) {
const error = data?.error;
throw new HttpError({
body,
details: typeof error === 'string'
? error
: error
? JSON.stringify(error)
: response.statusText,
response,
url,
});
}
return data;
},
}, { raw: options.raw });
}
/** Thrown when a HTTP request fails. */
export class HttpError extends Errors.BaseError {
name = 'RpcTransport.HttpError';
constructor({ body, details, response, url, }) {
super('HTTP request failed.', {
details,
metaMessages: [
`Status: ${response.status}`,
`URL: ${getUrl(url)}`,
body ? `Body: ${JSON.stringify(body)}` : undefined,
],
});
}
}
/** Thrown when a HTTP response is malformed. */
export class MalformedResponseError extends Errors.BaseError {
name = 'RpcTransport.MalformedResponseError';
constructor({ response }) {
super('HTTP Response could not be parsed as JSON.', {
metaMessages: [`Response: ${response}`],
});
}
}
//# sourceMappingURL=RpcTransport.js.map