UNPKG

@seriouslag/httpclient

Version:
213 lines (196 loc) 6.04 kB
import { IHttpClientAdaptor, Method, RequestConfig, ResponseType, HttpResponse, } from './Adaptors'; import { FetchClientAdaptor } from './FetchClientAdaptor'; import { HttpRequestStrategy, DefaultHttpRequestStrategy, } from './HttpRequestStrategies'; import { Logger } from './Logger'; import { AbortError } from './errors/AbortError'; import { ABORT_MESSAGE, ERROR_URL } from './strings'; /** Config used for setting up http calls */ export interface ApiConfig { /** If specified, a new axios instance is used instead of the one instantiated in the HttpClient's constructor */ noGlobal?: boolean; /** The headers that will be used in the HTTP call. Global headers will be added to these. * // TODO: Test when noGlobal is true if global headers are added to the request */ headers?: Record<string, string>; /** The body of the request that will be sent */ data?: any; /** The type of response that will be expected */ responseType?: ResponseType; /** The query parameters that will be sent with the HTTP call */ params?: any; /** The encoding of the response */ responseEncoding?: string; /** The strategy to use for this request, if not provided then the request that was provided with the HttpClient will be used */ httpRequestStrategy?: HttpRequestStrategy; } /** * HttpClient configuration options */ export interface HttpClientOptions { /** The strategy that will be used to handle http requests */ httpRequestStrategy?: HttpRequestStrategy; /** The logger the HttpClient will use */ logger?: Logger; baseUrl?: string; } /** Typed wrapper around axios that standardizes making HTTP calls and handling responses */ export class HttpClient { private logger: Logger | undefined; private httpRequestStrategy: HttpRequestStrategy; private baseUrl: string; constructor( private httpClientAdaptor: IHttpClientAdaptor = new FetchClientAdaptor(), options: HttpClientOptions = {}, ) { const { httpRequestStrategy, logger, baseUrl = '' } = options; this.httpRequestStrategy = httpRequestStrategy ?? new DefaultHttpRequestStrategy(); this.logger = logger; this.baseUrl = baseUrl; } /** * Sets the logger for the instance * @param {Logger|undefined} logger */ public setLogger(logger: Logger | undefined) { this.logger = logger; } /** HTTP GET request */ public get<T = unknown>( url: string, config: ApiConfig = {}, cancelToken?: AbortController, ): Promise<T> { const method: Method = 'get'; return this.dataRequest<T>(url, method, config, cancelToken); } /** HTTP POST request */ public post<T = unknown>( url: string, config: ApiConfig = {}, cancelToken?: AbortController, ): Promise<T> { const method: Method = 'post'; return this.dataRequest<T>(url, method, config, cancelToken); } /** HTTP PUT request */ public put<T = unknown>( url: string, config: ApiConfig = {}, cancelToken?: AbortController, ): Promise<T> { const method: Method = 'put'; return this.dataRequest<T>(url, method, config, cancelToken); } /** HTTP DELETE request */ public delete<T = unknown>( url: string, config: ApiConfig = {}, cancelToken?: AbortController, ): Promise<T> { const method: Method = 'delete'; return this.dataRequest<T>(url, method, config, cancelToken); } /** HTTP PATCH request */ public patch<T = unknown>( url: string, config: ApiConfig = {}, cancelToken?: AbortController, ): Promise<T> { const method: Method = 'patch'; return this.dataRequest<T>(url, method, config, cancelToken); } /** * HTTP request that returns the body of the HTTP response * * If a cancel token is passed in it will be aborted on request error. * * @returns {Promise<T>} body of the HTTP response */ public async dataRequest<T = unknown>( url: string, method: Method, config: ApiConfig = {}, cancelToken?: AbortController, ): Promise<T> { const response = await this.request<T>(url, method, config, cancelToken); return response.data; } /** * HTTP request * * If a cancel token is passed in it will be aborted on request error. * * @returns {Promise<HttpResponse<T>>} HttpResponse */ public async request<T = unknown>( url: string, method: Method, config: ApiConfig = {}, cancelToken?: AbortController, ): Promise<HttpResponse<T>> { if (cancelToken?.signal.aborted) { throw new AbortError(ABORT_MESSAGE); } try { return await this.doRequest<T>(url, method, config, cancelToken); } catch (e) { let message = `The ${method} request to ${url} failed`; if (e && typeof e === 'object' && 'status' in e) { message += ` with status ${e.status}`; } cancelToken?.abort(message); throw e; } } private async doRequest<T = unknown>( url: string, method: Method, config: ApiConfig = {}, cancelToken?: AbortController, ): Promise<HttpResponse<T>> { if (typeof url !== 'string') throw new Error(ERROR_URL); const { headers, data, params, responseEncoding, responseType, httpRequestStrategy, noGlobal, } = config; const strategyToUse = httpRequestStrategy ?? this.httpRequestStrategy; const requestConfig: RequestConfig = { url: this.baseUrl + url, method, headers, data, params, responseEncoding, responseType, cancelToken, noGlobal, }; try { const request = this.httpClientAdaptor.buildRequest<T>(requestConfig); this.logger?.debug(`HTTP - method: ${method}; url: ${url}`); const response = await strategyToUse.request<T>(request); this.logger?.debug( `HTTP ${response.status} - method: ${method}; url: ${url}`, ); return response; } catch (e) { this.logger?.error(`HTTP error - method: ${method}; url: ${url}`, e); throw e; } } }