import { DOM } from 'aurelia-pal' ; import { HttpClientConfiguration } from './http-client-configuration' ; import { Interceptor , RequestInit , ValidInterceptorMethodName } from './interfaces' ; import { RetryInterceptor } from './retry-interceptor' ; /**
 * An HTTP client based on the Fetch API.
 */ export class HttpClient { /**
     * The current number of active requests.
     * Requests being processed by interceptors are considered active.
     */ activeRequestCount : number = 0 ; /**
     * Indicates whether or not the client is currently making one or more requests.
     */ isRequesting : boolean = false ; /**
     * Indicates whether or not the client has been configured.
     */ isConfigured : boolean = false ; /**
     * The base URL set by the config.
     */ baseUrl : string = '' ; /**
     * The default request init to merge with values specified at request time.
     */ defaults : RequestInit = null ; /**
     * The interceptors to be run during requests.
     */ interceptors : Interceptor [ ] = [ ] ; /**
     * Creates an instance of HttpClient.
     */ constructor ( ) { if ( typeof fetch === 'undefined' ) { throw new Error ( 'HttpClient requires a Fetch API implementation, but the current environment doesn\'t support it. You may need to load a polyfill such as https://github.com/github/fetch' ) ; } } param config returns chainable configure ( config ) { let normalizedConfig ; if ( typeof config === 'object' ) { normalizedConfig = { defaults : config as RequestInit } as HttpClientConfiguration ; } else if ( typeof config === 'function' ) { normalizedConfig = new HttpClientConfiguration ( ) ; normalizedConfig . baseUrl = this . baseUrl ; normalizedConfig . defaults = Object . assign ( { } , this . defaults ) ; normalizedConfig . interceptors = this . interceptors ; let c = config ( normalizedConfig ) ; if ( HttpClientConfiguration . prototype . isPrototypeOf ( c as any ) ) { normalizedConfig = c as any ; } } else { throw new Error ( 'invalid config' ) ; } let defaults = normalizedConfig . defaults ; if ( defaults && Headers . prototype . isPrototypeOf ( defaults . headers ) ) { throw new Error ( 'Default headers must be a plain object.' ) ; } let interceptors = normalizedConfig . interceptors ; if ( interceptors && interceptors . length ) { if ( interceptors . filter ( x => RetryInterceptor . prototype . isPrototypeOf ( x ) ) . length > 1 ) { throw new Error ( 'Only one RetryInterceptor is allowed.' ) ; } const retryInterceptorIndex = interceptors . findIndex ( x => RetryInterceptor . prototype . isPrototypeOf ( x ) ) ; if ( retryInterceptorIndex >= 0 && retryInterceptorIndex !== interceptors . length - 1 ) { throw new Error ( 'The retry interceptor must be the last interceptor defined.' ) ; } } this . baseUrl = normalizedConfig . baseUrl ; this . defaults = defaults ; this . interceptors = normalizedConfig . interceptors || [ ] ; this . isConfigured = true ; return this ; } param input param init returns fetch ( input , init ? ) { trackRequestStart ( this ) ; let request = this . buildRequest ( input , init ) ; return processRequest ( request , this . interceptors , this ) . then ( result => { let response = null ; if ( Response . prototype . isPrototypeOf ( result ) ) { response = Promise . resolve ( result ) ; } else if ( Request . prototype . isPrototypeOf ( result ) ) { request = result ; response = fetch ( result ) ; } else { throw new Error ( `An invalid result was returned by the interceptor chain. Expected a Request or Response instance, but got [${ result }]` ) ; } return processResponse ( response , this . interceptors , request , this ) ; } ) . then ( result => { if ( Request . prototype . isPrototypeOf ( result ) ) { return this . fetch ( result ) ; } return result ; } ) . then ( result => { trackRequestEnd ( this ) ; return result ; } , error => { trackRequestEnd ( this ) ; throw error ; } ) ; } buildRequest ( input , init ) { let defaults = this . defaults || { } ; let request ; let body ; let requestContentType ; let parsedDefaultHeaders = parseHeaderValues ( defaults . headers ) ; if ( Request . prototype . isPrototypeOf ( input ) ) { request = input as Request ; requestContentType = new Headers ( request . headers ) . get ( 'Content-Type' ) ; } else { if ( ! init ) { init = { } as RequestInit ; } body = init . body ; let bodyObj = body ? { body } : null ; let requestInit = Object . assign ( { } , defaults , { headers : { } } , init , bodyObj ) ; requestContentType = new Headers ( requestInit . headers ) . get ( 'Content-Type' ) ; request = new Request ( getRequestUrl ( this . baseUrl , input as string ) , requestInit ) ; } if ( ! requestContentType ) { if ( new Headers ( parsedDefaultHeaders ) . has ( 'content-type' ) ) { request . headers . set ( 'Content-Type' , new Headers ( parsedDefaultHeaders ) . get ( 'content-type' ) ) ; } else if ( body && isJSON ( body ) ) { request . headers . set ( 'Content-Type' , 'application/json' ) ; } } setDefaultHeaders ( request . headers , parsedDefaultHeaders ) ; if ( body && Blob . prototype . isPrototypeOf ( body ) && body . type ) { request . headers . set ( 'Content-Type' , body . type ) ; } return request ; } param input param init returns get ( input , init ? ) { return this . fetch ( input , init ) ; } param input param body param init returns post ( input , body ? , init ? ) { return callFetch ( this , input , body , init , 'POST' ) ; } param input param body param init returns put ( input , body ? , init ? ) { return callFetch ( this , input , body , init , 'PUT' ) ; } param input param body param init returns patch ( input , body ? , init ? ) { return callFetch ( this , input , body , init , 'PATCH' ) ; } param input param body param init returns delete ( input , body ? , init ? ) { return callFetch ( this , input , body , init , 'DELETE' ) ; } } const absoluteUrlRegexp = /^([a-z][a-z0-9+\-.]*:)?\/\//i ; function trackRequestStart ( client ) { client . isRequesting = ! ! ( ++ client . activeRequestCount ) ; if ( client . isRequesting ) { let evt = DOM . createCustomEvent ( 'aurelia-fetch-client-request-started' , { bubbles : true , cancelable : true } ) ; setTimeout ( ( ) => DOM . dispatchEvent ( evt ) , 1 ) ; } } function trackRequestEnd ( client ) { client . isRequesting = ! ! ( -- client . activeRequestCount ) ; if ( ! client . isRequesting ) { let evt = DOM . createCustomEvent ( 'aurelia-fetch-client-requests-drained' , { bubbles : true , cancelable : true } ) ; setTimeout ( ( ) => DOM . dispatchEvent ( evt ) , 1 ) ; } } function parseHeaderValues ( headers ) { let parsedHeaders = { } ; for ( let name in headers || { } ) { if ( headers . hasOwnProperty ( name ) ) { parsedHeaders [ name ] = ( typeof headers [ name ] === 'function' ) ? headers [ name ] ( ) : headers [ name ] ; } } return parsedHeaders ; } function getRequestUrl ( baseUrl , url ) { if ( absoluteUrlRegexp . test ( url ) ) { return url ; } return ( baseUrl || '' ) + url ; } function setDefaultHeaders ( headers , defaultHeaders ) { for ( let name in defaultHeaders || { } ) { if ( defaultHeaders . hasOwnProperty ( name ) && ! headers . has ( name ) ) { headers . set ( name , defaultHeaders [ name ] ) ; } } } function processRequest ( request , interceptors , http ) { return applyInterceptors ( request , interceptors , 'request' , 'requestError' , http ) ; } function processResponse ( response , interceptors , request , http ) { return applyInterceptors ( response , interceptors , 'response' , 'responseError' , request , http ) ; } function applyInterceptors ( input , interceptors , successName , errorName , ... interceptorArgs ) { return ( interceptors || [ ] ) . reduce ( ( chain , interceptor ) => { let successHandler = interceptor [ successName ] ; let errorHandler = interceptor [ errorName ] ; return chain . then ( successHandler && ( value => successHandler . call ( interceptor , value , ... interceptorArgs ) ) || identity , errorHandler && ( reason => errorHandler . call ( interceptor , reason , ... interceptorArgs ) ) || thrower ) ; } , Promise . resolve ( input ) ) ; } function isJSON ( str ) { try { JSON . parse ( str ) ; } catch ( err ) { return false ; } return true ; } function identity ( x ) { return x ; } function thrower ( x ) { throw x ; } function callFetch ( client , input , body , init , method ) { if ( ! init ) { init = { } ; } init . method = method ; if ( body ) { init . body = body ; } return client . fetch ( input , init ) ; }