import AnalyticsBaseModel from '../AnalyticsModels/BaseModel'; import BaseModel from '../Models/BaseModel'; import HTTPErrorResponse from '../Types/API/HTTPErrorResponse'; import HTTPMethod from '../Types/API/HTTPMethod'; import HTTPResponse from '../Types/API/HTTPResponse'; import Headers from '../Types/API/Headers'; import Hostname from '../Types/API/Hostname'; import Protocol from '../Types/API/Protocol'; import Route from '../Types/API/Route'; import URL from '../Types/API/URL'; import Dictionary from '../Types/Dictionary'; import APIException from '../Types/Exception/ApiException'; import { JSONArray, JSONObject } from '../Types/JSON'; import axios, { AxiosError, AxiosResponse } from 'axios'; export default class API { private _protocol: Protocol = Protocol.HTTPS; public get protocol(): Protocol { return this._protocol; } public set protocol(v: Protocol) { this._protocol = v; } private _hostname!: Hostname; public get hostname(): Hostname { return this._hostname; } public set hostname(v: Hostname) { this._hostname = v; } private _baseRoute!: Route; public get baseRoute(): Route { return this._baseRoute; } public set baseRoute(v: Route) { this._baseRoute = v; } public constructor( protocol: Protocol, hostname: Hostname, baseRoute?: Route ) { this.protocol = protocol; this.hostname = hostname; if (baseRoute) { this.baseRoute = baseRoute; } else { this.baseRoute = new Route('/'); } } public async get< T extends JSONObject | JSONArray | BaseModel | Array >( path: Route, data?: JSONObject | JSONArray, headers?: Headers ): Promise | HTTPErrorResponse> { return await API.get( new URL( this.protocol, this.hostname, this.baseRoute.addRoute(path) ), data, headers ); } public async delete< T extends JSONObject | JSONArray | BaseModel | Array >( path: Route, data?: JSONObject | JSONArray, headers?: Headers ): Promise | HTTPErrorResponse> { return await API.delete( new URL( this.protocol, this.hostname, this.baseRoute.addRoute(path) ), data, headers ); } public async head< T extends JSONObject | JSONArray | BaseModel | Array >( path: Route, data?: JSONObject | JSONArray, headers?: Headers ): Promise | HTTPErrorResponse> { return await API.head( new URL( this.protocol, this.hostname, this.baseRoute.addRoute(path) ), data, headers ); } public async put< T extends JSONObject | JSONArray | BaseModel | Array >( path: Route, data?: JSONObject | JSONArray, headers?: Headers ): Promise | HTTPErrorResponse> { return await API.put( new URL( this.protocol, this.hostname, this.baseRoute.addRoute(path) ), data, headers ); } public async post< T extends JSONObject | JSONArray | BaseModel | Array >( path: Route, data?: JSONObject | JSONArray, headers?: Headers ): Promise | HTTPErrorResponse> { return await API.post( new URL( this.protocol, this.hostname, this.baseRoute.addRoute(path) ), data, headers ); } protected static handleError( error: HTTPErrorResponse | APIException ): HTTPErrorResponse | APIException { return error; } protected static async onResponseSuccessHeaders( headers: Dictionary ): Promise> { return Promise.resolve(headers); } public static getDefaultHeaders(_props?: any): Headers { const defaultHeaders: Headers = { 'Access-Control-Allow-Origin': '*', Accept: 'application/json', 'Content-Type': 'application/json;charset=UTF-8', }; return defaultHeaders; } protected static getHeaders(headers?: Headers): Headers { let defaultHeaders: Headers = this.getDefaultHeaders(); if (headers) { defaultHeaders = { ...defaultHeaders, ...headers, }; } return defaultHeaders; } public static async get< T extends | JSONObject | JSONArray | BaseModel | Array | AnalyticsBaseModel | Array >( url: URL, data?: JSONObject | JSONArray, headers?: Headers ): Promise | HTTPErrorResponse> { return await this.fetch(HTTPMethod.GET, url, data, headers); } public static async delete< T extends | JSONObject | JSONArray | BaseModel | Array | AnalyticsBaseModel | Array >( url: URL, data?: JSONObject | JSONArray, headers?: Headers ): Promise | HTTPErrorResponse> { return await this.fetch(HTTPMethod.DELETE, url, data, headers); } public static async head< T extends | JSONObject | JSONArray | BaseModel | Array | AnalyticsBaseModel | Array >( url: URL, data?: JSONObject | JSONArray, headers?: Headers ): Promise | HTTPErrorResponse> { return await this.fetch(HTTPMethod.HEAD, url, data, headers); } public static async put< T extends | JSONObject | JSONArray | BaseModel | Array | AnalyticsBaseModel | Array >( url: URL, data?: JSONObject | JSONArray, headers?: Headers ): Promise | HTTPErrorResponse> { return await this.fetch(HTTPMethod.PUT, url, data, headers); } public static async post< T extends | JSONObject | JSONArray | BaseModel | Array | AnalyticsBaseModel | Array >( url: URL, data?: JSONObject | JSONArray, headers?: Headers ): Promise | HTTPErrorResponse> { return await this.fetch(HTTPMethod.POST, url, data, headers); } public static async fetch< T extends | JSONObject | JSONArray | BaseModel | Array | AnalyticsBaseModel | Array >( method: HTTPMethod, url: URL, data?: JSONObject | JSONArray, headers?: Headers, params?: Dictionary ): Promise | HTTPErrorResponse> { const apiHeaders: Headers = this.getHeaders(headers); if (params) { url.addQueryParams(params); } try { const finalHeaders: Dictionary = { ...apiHeaders, ...headers, }; let finalBody: | JSONObject | JSONArray | URLSearchParams | undefined = data; // if content-type is form-url-encoded, then stringify the data if ( finalHeaders['Content-Type'] === 'application/x-www-form-urlencoded' && data ) { finalBody = new URLSearchParams(data as Dictionary); } const result: AxiosResponse = await axios({ method: method, url: url.toString(), headers: finalHeaders, data: finalBody, }); result.headers = await this.onResponseSuccessHeaders( result.headers as Dictionary ); const response: HTTPResponse = new HTTPResponse( result.status, result.data, result.headers as Dictionary ); return response; } catch (e) { const error: Error | AxiosError = e as Error | AxiosError; let errorResponse: HTTPErrorResponse; if (axios.isAxiosError(error)) { // Do whatever you want with native error errorResponse = this.getErrorResponse(error); } else { throw new APIException(error.message); } this.handleError(errorResponse); return errorResponse; } } private static getErrorResponse(error: AxiosError): HTTPErrorResponse { if (error.response) { return new HTTPErrorResponse( error.response.status, error.response.data as JSONObject | JSONArray, error.response.headers as Dictionary ); } // get url from error const url: string = error?.config?.url || ''; throw new APIException(`URL ${url ? url + ' ' : ''}is not available.`); } public static getFriendlyErrorMessage(error: AxiosError | Error): string { const errorString: string = error.message || error.toString(); if (errorString.toLocaleLowerCase().includes('network error')) { return 'Network Error.'; } if (errorString.toLocaleLowerCase().includes('timeout')) { return 'Timeout Error.'; } if (errorString.toLocaleLowerCase().includes('request aborted')) { return 'Request Aborted.'; } if (errorString.toLocaleLowerCase().includes('canceled')) { return 'Request Canceled.'; } if (errorString.toLocaleLowerCase().includes('connection refused')) { return 'Connection Refused.'; } if (errorString.toLocaleLowerCase().includes('connection reset')) { return 'Connection Reset.'; } if (errorString.toLocaleLowerCase().includes('connection closed')) { return 'Connection Closed.'; } if (errorString.toLocaleLowerCase().includes('connection failed')) { return 'Connection Failed.'; } if (errorString.toLocaleLowerCase().includes('enotfound')) { return 'Cannot Find Host.'; } if (errorString.toLocaleLowerCase().includes('econnreset')) { return 'Connection Reset.'; } if (errorString.toLocaleLowerCase().includes('econnrefused')) { return 'Connection Refused.'; } if (errorString.toLocaleLowerCase().includes('econnaborted')) { return 'Connection Aborted.'; } if ( errorString.toLocaleLowerCase().includes('certificate has expired') ) { return 'SSL Certificate Expired.'; } if(errorString.toLocaleLowerCase().includes('certificate signed by unknown authority')) { return 'SSL Certificate Signed By Unknown Authority.'; } if (errorString.toLocaleLowerCase().includes('self-signed certificate')) { return 'Self Signed Certificate.'; } return errorString; } }