UNPKG

ox

Version:

Ethereum Standard Library

118 lines 4.38 kB
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