UNPKG

@coinbase/cdp-sdk

Version:

SDK for interacting with the Coinbase Developer Platform Wallet API

190 lines (173 loc) 7.16 kB
import { createLogger, type LogConfig, type Logger } from "../logging/logger.js"; import { join } from "../url/join.js"; import { EndpointSupplier } from "./EndpointSupplier.js"; import { getFetchFn } from "./getFetchFn.js"; import { makeRequest } from "./makeRequest.js"; import { requestWithRetries } from "./requestWithRetries.js"; import { Supplier } from "./Supplier.js"; export declare namespace PassthroughRequest { /** * Per-request options that can override the SDK client defaults. */ export interface RequestOptions { /** Override the default timeout for this request (in seconds). */ timeoutInSeconds?: number; /** Override the default number of retries for this request. */ maxRetries?: number; /** Additional headers to include in this request. */ headers?: Record<string, string>; /** Abort signal for this request. */ abortSignal?: AbortSignal; } /** * SDK client configuration used by the passthrough fetch method. */ export interface ClientOptions { /** The base URL or environment for the client. */ environment?: Supplier<string>; /** Override the base URL. */ baseUrl?: Supplier<string>; /** Default headers to include in requests. */ headers?: Record<string, unknown>; /** Default maximum time to wait for a response in seconds. */ timeoutInSeconds?: number; /** Default number of times to retry the request. Defaults to 2. */ maxRetries?: number; /** A custom fetch function. */ fetch?: typeof fetch; /** Logging configuration. */ logging?: LogConfig | Logger; /** A function that returns auth headers. */ getAuthHeaders?: () => Promise<Record<string, string>>; } } /** * Makes a passthrough HTTP request using the SDK's configuration (auth, retry, logging, etc.) * while mimicking the standard `fetch` API. * * @param input - The URL, path, or Request object. If a relative path, it will be resolved against the configured base URL. * @param init - Standard RequestInit options (method, headers, body, signal, etc.) * @param clientOptions - SDK client options (auth, default headers, logging, etc.) * @param requestOptions - Per-request overrides (timeout, retries, extra headers, abort signal). * @returns A standard Response object. */ export async function makePassthroughRequest( input: Request | string | URL, init: RequestInit | undefined, clientOptions: PassthroughRequest.ClientOptions, requestOptions?: PassthroughRequest.RequestOptions, ): Promise<Response> { const logger = createLogger(clientOptions.logging); // Extract URL and default init properties from Request object if provided let url: string; let effectiveInit: RequestInit | undefined = init; if (input instanceof Request) { url = input.url; // If no explicit init provided, extract properties from the Request object if (init == null) { effectiveInit = { method: input.method, headers: Object.fromEntries(input.headers.entries()), body: input.body, signal: input.signal, credentials: input.credentials, cache: input.cache as RequestCache, redirect: input.redirect, referrer: input.referrer, integrity: input.integrity, mode: input.mode, }; } } else { url = input instanceof URL ? input.toString() : input; } // Resolve the base URL const baseUrl = (clientOptions.baseUrl != null ? await Supplier.get(clientOptions.baseUrl) : undefined) ?? (clientOptions.environment != null ? await Supplier.get(clientOptions.environment) : undefined); // Determine the full URL let fullUrl: string; if (url.startsWith("http://") || url.startsWith("https://")) { fullUrl = url; } else if (baseUrl != null) { fullUrl = join(baseUrl, url); } else { fullUrl = url; } // Merge headers: SDK default headers -> auth headers -> user-provided headers const mergedHeaders: Record<string, string> = {}; // Apply SDK default headers (resolve suppliers) if (clientOptions.headers != null) { for (const [key, value] of Object.entries(clientOptions.headers)) { const resolved = await EndpointSupplier.get(value, { endpointMetadata: {} }); if (resolved != null) { mergedHeaders[key.toLowerCase()] = `${resolved}`; } } } // Apply auth headers if (clientOptions.getAuthHeaders != null) { const authHeaders = await clientOptions.getAuthHeaders(); for (const [key, value] of Object.entries(authHeaders)) { mergedHeaders[key.toLowerCase()] = value; } } // Apply user-provided headers from init if (effectiveInit?.headers != null) { const initHeaders = effectiveInit.headers instanceof Headers ? Object.fromEntries(effectiveInit.headers.entries()) : Array.isArray(effectiveInit.headers) ? Object.fromEntries(effectiveInit.headers) : effectiveInit.headers; for (const [key, value] of Object.entries(initHeaders)) { if (value != null) { mergedHeaders[key.toLowerCase()] = value; } } } // Apply per-request option headers (highest priority) if (requestOptions?.headers != null) { for (const [key, value] of Object.entries(requestOptions.headers)) { mergedHeaders[key.toLowerCase()] = value; } } const method = effectiveInit?.method ?? "GET"; const body = effectiveInit?.body; const timeoutInSeconds = requestOptions?.timeoutInSeconds ?? clientOptions.timeoutInSeconds; const timeoutMs = timeoutInSeconds != null ? timeoutInSeconds * 1000 : undefined; const maxRetries = requestOptions?.maxRetries ?? clientOptions.maxRetries; const abortSignal = requestOptions?.abortSignal ?? effectiveInit?.signal ?? undefined; const fetchFn = clientOptions.fetch ?? (await getFetchFn()); if (logger.isDebug()) { logger.debug("Making passthrough HTTP request", { method, url: fullUrl, hasBody: body != null, }); } const response = await requestWithRetries( async () => makeRequest( fetchFn, fullUrl, method, mergedHeaders, body ?? undefined, timeoutMs, abortSignal, effectiveInit?.credentials === "include", undefined, // duplex false, // disableCache ), maxRetries, ); if (logger.isDebug()) { logger.debug("Passthrough HTTP request completed", { method, url: fullUrl, statusCode: response.status, }); } return response; }