import { fetch } from "./fetch" ; import { HttpFetchError , HttpResponseError , HttpParseError } from "./errors" ; import InterceptorManager from "./InterceptorManager" ; export interface HttpRequestConfig { url ? : string ; method ? : string ; data ? : any ; meta ? : Record < string , any > ; options ? : HttpOptions ; } export interface HttpResponse < T = any > { data : T ; status : number ; statusText : string ; headers : Headers ; config : HttpRequestConfig ; } export interface HttpError { config : HttpRequestConfig ; error : HttpFetchError | HttpResponseError | HttpParseError ; } function isNil ( value ) { return value === undefined || value === null ; } const base = document . querySelector ( "base" ) ; const fullBaseHref = base ? base . href : location . origin + "/" ; export type HttpParams = URLSearchParams | { [ key ] : any ; } ; export interface RequestCustomOptions { observe ? : "data" | "response" ; responseType ? : "json" | "blob" | "arrayBuffer" | "text" ; interceptorParams ? : any ; } export type HttpCustomOptions = RequestCustomOptions & { params ? : HttpParams ; } ; export type HttpOptions = HttpCustomOptions & RequestInit ; const createError = ( error , config ) : HttpError => { return { error , config , } ; } ; const request = async < T > ( url , init ? , config ? ) : Promise < HttpResponse < T > > => { return new Promise ( async ( resolve , reject ) => { let response ; config . url = url ; let { responseType } = config . options || { } ; if ( ! responseType ) { responseType = "json" ; } try { response = await fetch ( url , init ) ; } catch ( e ) { reject ( createError ( new HttpFetchError ( e . toString ( ) ) , config ) ) ; return ; } if ( ! response . ok ) { let responseJson ; try { responseJson = await response . json ( ) ; } catch ( e ) { } reject ( createError ( new HttpResponseError ( response , responseJson ) , config ) ) ; return ; } let data ; try { data = await response [ responseType ] ( ) ; } catch ( e ) { reject ( createError ( new HttpParseError ( response ) , config ) ) ; return ; } const res = { config , status : response . status , statusText : response . statusText , headers : response . headers , data , } ; resolve ( res ) ; } ) ; } ; const getUrlWithParams = ( url , params ? ) : string => { if ( params ) { const parsedUrl = new URL ( url , fullBaseHref ) ; if ( params instanceof URLSearchParams ) { params . forEach ( function ( value , key ) { parsedUrl . searchParams . append ( key , value ) ; } ) ; } else { Object . entries ( params ) . forEach ( ( [ key , value ] ) => { if ( Array . isArray ( value ) ) { value . forEach ( ( v ) => { parsedUrl . searchParams . append ( key , isNil ( v ) ? "" : v ) ; } ) ; } else { parsedUrl . searchParams . append ( key , isNil ( value ) ? "" : value ) ; } } ) ; } const { href } = parsedUrl ; if ( href . startsWith ( fullBaseHref ) ) { return href . substr ( fullBaseHref . length ) ; } return href ; } return url ; } ; const getBodyAndHeaders = ( data , headers ) : { body ? : BodyInit ; headers ? : HeadersInit ; } => { if ( data !== undefined ) { const parsedHeaders = new Headers ( headers || { } ) ; const contentTypeHeader = "Content-Type" ; let body = data ; if ( ! parsedHeaders . has ( contentTypeHeader ) ) { if ( typeof data === "string" ) { parsedHeaders . set ( contentTypeHeader , "application/x-www-form-urlencoded" ) ; } else if ( data instanceof FormData ) { } else { parsedHeaders . set ( contentTypeHeader , "application/json" ) ; body = JSON . stringify ( data ) ; } } return { body , headers : parsedHeaders , } ; } return { headers } ; } ; const simpleRequest = < T = any > ( method , url , config ) : Promise < HttpResponse < T > > => { const { params , responseType , interceptorParams , ... requestInit } = config . options || { } ; return request < T > ( getUrlWithParams ( url , params ) , { ... requestInit , method , } , config ) ; } ; const requestWithBody = < T = any > ( method , url , data ? , config ? ) : Promise < HttpResponse < T > > => { const { params , responseType , interceptorParams , headers , ... requestInit } = config . options || { } ; return request < T > ( getUrlWithParams ( url , params ) , { ... requestInit , method , ... getBodyAndHeaders ( data , headers ) , } , config ) ; } ; class Http { public interceptors : { request : InterceptorManager < HttpRequestConfig > ; response : InterceptorManager < HttpResponse > ; } ; public defaults = { } ; constructor ( ) { this . interceptors = { request : new InterceptorManager ( ) , response : new InterceptorManager ( ) , } ; } request = async < T > ( url , init ? , options ? ) : Promise < T > => { const { body , method , ... requestInit } = init || { } ; return this . fetch ( { ... this . defaults , url , data : body , method , options : { ... ( options || { } ) , ... requestInit } , } ) ; } ; simpleRequest = < T = any > ( method , url , options = { } ) : Promise < T > => { return this . fetch ( { ... this . defaults , url , method , options } ) ; } ; requestWithBody = < T = any > ( method , url , data ? , options = { } ) : Promise < T > => { return this . fetch ( { ... this . defaults , url , method , data , options } ) ; } ; private dispatchRequest < T > ( config ) { const { url , method , data , options = { } } = config ; if ( [ "DELETE" , "GET" , "HEAD" ] . includes ( config . method ) ) { return simpleRequest < T > ( method , url , config ) ; } return requestWithBody < T > ( method , url , data , config ) ; } private fetch ( config ) { const chain = [ ] ; let promise = Promise . resolve ( config ) ; this . interceptors . request . forEach ( ( interceptor ) => { chain . push ( interceptor . fulfilled , interceptor . rejected ) ; } ) ; chain . push ( this . dispatchRequest , undefined ) ; this . interceptors . response . forEach ( ( interceptor ) => { chain . push ( interceptor . fulfilled , interceptor . rejected ) ; } ) ; while ( chain . length ) { promise = promise . then ( chain . shift ( ) , chain . shift ( ) ) ; } return promise ; } getUrlWithParams ( url , params ? ) { return getUrlWithParams ( url , params ) ; } getBodyAndHeaders ( data , headers ) { return getBodyAndHeaders ( data , headers ) ; } get < T > ( url , options ? ) { return this . fetch ( { ... this . defaults , url , method : "GET" , options } ) ; } delete < T > ( url , options ? ) { return this . fetch ( { ... this . defaults , url , method : "DELETE" , options } ) ; } head < T > ( url , options ? ) { return this . fetch ( { ... this . defaults , url , method : "HEAD" , options } ) ; } post < T > ( url , data ? , options ? ) { return this . fetch ( { ... this . defaults , url , method : "POST" , data , options } ) ; } put < T > ( url , data ? , options ? ) { return this . fetch ( { ... this . defaults , url , method : "PUT" , data , options } ) ; } patch < T > ( url , data ? , options ? ) { return this . fetch ( { ... this . defaults , url , method : "PATCH" , data , options , } ) ; } } export const http = new Http ( ) ;