import axios , { AxiosInstance , AxiosError , AxiosResponse , AxiosRequestConfig , } from "axios" ; import { Readable } from "stream" ; import { HTTPError , ReadError , RequestError } from "./exceptions" ; import * as fileType from "file-type" ; import * as qs from "querystring" ; const pkg = require ( "../package.json" ) ; interface httpClientConfig extends Partial < AxiosRequestConfig > { baseURL ? : string ; defaultHeaders ? : any ; responseParser ? : < T > ( res : AxiosResponse ) => T ; } export default class HTTPClient { private instance : AxiosInstance ; private config : httpClientConfig ; constructor ( config = { } ) { this . config = config ; const { baseURL , defaultHeaders } = config ; this . instance = axios . create ( { baseURL , headers : Object . assign ( { } , defaultHeaders , { "User-Agent" : `${ pkg . name }/${ pkg . version }` , } ) , } ) ; this . instance . interceptors . response . use ( res => res , err => Promise . reject ( this . wrapError ( err ) ) ) ; } public async get < T > ( url , params ? ) { const res = await this . instance . get ( url , { params } ) ; return res . data ; } public async getStream ( url , params ? ) { const res = await this . instance . get ( url , { params , responseType : "stream" , } ) ; return res . data as Readable ; } public async post < T > ( url , body ? , config ? ) { const res = await this . instance . post ( url , body , { headers : { "Content-Type" : "application/json" , ... ( config && config . headers ) , } , ... config , } ) ; return this . responseParse ( res ) ; } private responseParse ( res ) { const { responseParser } = this . config ; if ( responseParser ) return responseParser ( res ) ; else return res . data ; } public async put < T > ( url , body ? , config ? ) { const res = await this . instance . put ( url , body , { headers : { "Content-Type" : "application/json" , ... ( config && config . headers ) , } , ... config , } ) ; return this . responseParse ( res ) ; } public async postForm < T > ( url , body ? ) { const res = await this . instance . post ( url , qs . stringify ( body ) , { headers : { "Content-Type" : "application/x-www-form-urlencoded" } , } ) ; return res . data ; } public async toBuffer ( data ) { if ( Buffer . isBuffer ( data ) ) { return data ; } else if ( data instanceof Readable ) { return await new Promise < Buffer > ( ( resolve , reject ) => { const buffers = [ ] ; let size = 0 ; data . on ( "data" , ( chunk ) => { buffers . push ( chunk ) ; size += chunk . length ; } ) ; data . on ( "end" , ( ) => resolve ( Buffer . concat ( buffers , size ) ) ) ; data . on ( "error" , reject ) ; } ) ; } else { throw new Error ( "invalid data type for binary data" ) ; } } public async postBinary < T > ( url , data , contentType ? ) { const buffer = await this . toBuffer ( data ) ; const res = await this . instance . post ( url , buffer , { headers : { "Content-Type" : contentType || ( await fileType . fromBuffer ( buffer ) ) . mime , "Content-Length" : buffer . length , } , } ) ; return res . data ; } public async delete < T > ( url , params ? ) { const res = await this . instance . delete ( url , { params } ) ; return res . data ; } private wrapError ( err ) { if ( err . response ) { return new HTTPError ( err . message , err . response . status , err . response . statusText , err ) ; } else if ( err . code ) { return new RequestError ( err . message , err . code , err ) ; } else if ( err . config ) { return new ReadError ( err ) ; } return err ; } }