@bigmi/core
Version: 
TypeScript library for Bitcoin apps.
151 lines (138 loc) • 4.35 kB
text/typescript
import { HttpRequestError, TimeoutError } from '../errors/request.js'
import type { RpcResponse } from '../types/rpc.js'
import type { MaybePromise } from '../types/utils.js'
import { stringify } from '../utils/stringify.js'
import { withTimeout } from '../utils/withTimeout.js'
export type RpcRequest = {
  jsonrpc?: '2.0'
  method: string
  params?: any
  id?: number
}
export type HttpRequestReturnType<
  body extends RpcRequest | RpcRequest[] = RpcRequest,
> = body extends RpcRequest[] ? RpcResponse[] : RpcResponse
export type HttpRpcClientOptions = {
  /** Request configuration to pass to `fetch`. */
  fetchOptions?: Omit<RequestInit, 'body'> | undefined
  /** A callback to handle the request. */
  onRequest?:
    | ((
        request: Request,
        init: RequestInit
      ) => MaybePromise<
        void | undefined | (RequestInit & { url?: string | undefined })
      >)
    | 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,
> = {
  url?: string
  /** 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 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: body ? stringify(body) : undefined,
              headers: {
                ...(method !== 'GET'
                  ? { 'Content-Type': 'application/json' }
                  : undefined),
                ...headers,
              },
              method: method || 'POST',
              signal: signal_ || (timeout > 0 ? signal : null),
            }
            const request = new Request(params.url ?? url, init)
            if (onRequest) {
              await onRequest(request, init)
            }
            const response = await fetch(params.url ?? url, init)
            return response
          },
          {
            errorInstance: new TimeoutError({
              body: body ?? {},
              url: params.url ?? url,
              timeout,
            }),
            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,
        })
      }
    },
  }
}