UNPKG

@apicart/js-utils

Version:

A small set of useful utilities for easier development

272 lines (231 loc) 7.43 kB
import * as httpsProcessor from 'https'; import * as httpProcessor from 'http'; import { Loops, Validators, Objects, Json } from '.'; interface AjaxParametersInterface { adapter: CallableFunction|null; cache: boolean; data: any; eventListeners: Record<string, any>; headers: Record<string, any>; method: string; queryParameters: Record<string, any>; timeout: number; url: string; withCredentials: boolean; } interface AjaxResponseInterface { config: AjaxParametersInterface; data: string|{}; headers: string; request: XMLHttpRequest; status: number; statusText: string; } class Ajax { public get(url: string, parameters: any = {}): Promise<any> { parameters.method = 'get'; parameters.url = url; return this.sendRequest(parameters); } public post(url: string, parameters: any = {}): Promise<any> { parameters.method = 'post'; parameters.url = url; return this.sendRequest(parameters); } public request(parameters: AjaxParametersInterface): Promise<any> { return this.sendRequest(parameters); } private createResponseObject(processedRequestConfig): AjaxResponseInterface { let headers: any = {}; let data: Record<string, any> | null = {}; let status: number; let statusText: string; if (processedRequestConfig.type === 'xhr') { Loops.forEach( processedRequestConfig.request.getAllResponseHeaders().trim().split(/[\r\n]+/), (line: string): void => { const parts = line.split(': '); headers[parts.shift()] = parts.join(': '); } ); data = processedRequestConfig.responseData; status = processedRequestConfig.request.status; statusText = processedRequestConfig.request.statusText; } else if (processedRequestConfig.type === 'nodeHttp') { data = processedRequestConfig.responseData; headers = processedRequestConfig.response ? processedRequestConfig.response.headers : null; status = processedRequestConfig.response ? processedRequestConfig.response.statusCode : null; statusText = processedRequestConfig.response ? processedRequestConfig.response.statusMessage: null; } return { config: processedRequestConfig.requestConfig, data: Json.isJson(data) ? Json.parse(data) : data, headers: headers, request: processedRequestConfig.request, status: status, statusText: statusText }; } private sendRequest(requestConfiguration: AjaxParametersInterface): Promise<any> { if (Validators.isEmpty(requestConfiguration.url)) { throw '@apicart/js-utils: No url provided for ajax request'; } const config: Record<any, any> = Objects.merge({ adapter: null, data: {}, eventListeners: Object, headers: {}, method: 'get', timeout: 5000, url: '', withCredentials: false, isGet() { return this.method === 'get'; }, isPost() { return this.method === 'post'; } }, requestConfiguration) as AjaxParametersInterface; config.url = new URL(config.url); if (config.isGet()) { Loops.forEach(config.data, function (value: any, key: string) { config.url.searchParams.append(key, encodeURIComponent(value)); }); } else if (typeof config.data !== 'string') { config.data = Json.stringify(config.data); } if (Validators.isEmpty(config.data)) { config.data = null; } const requestAdapter = config.adapter || null; let response = null; if (requestAdapter === 'function') { response = requestAdapter.call(this, config); } else if (typeof XMLHttpRequest !== 'undefined') { response = this.xhrAdapter(config); } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { response = this.httpAdapter(config); } else { throw '@apicart/js-utils: Request cannot be processed because no Adapter was configured' + 'or is not a callable function.'; } return response; } private xhrAdapter(requestConfiguration): Promise<any> { const createXHResponseObject = ( request: XMLHttpRequest, requestConfiguration: Record<string, any> ): Record<string, any> => { return this.createResponseObject({ type: 'xhr', request: request, responseData: request.responseText, requestConfig: requestConfiguration }); }; return new Promise((resolve: Function, reject: Function): void => { const request = new XMLHttpRequest(); Loops.forEach( requestConfiguration.eventListeners, function (eventName: string, eventCallback: Function): void { request.addEventListener(eventName, (event): void => eventCallback.call(request, event)); } ); request.addEventListener('load', (): void => { resolve(createXHResponseObject(request, requestConfiguration)); }); request.addEventListener('error', (): void => { reject(createXHResponseObject(request, requestConfiguration)); }); request.open(requestConfiguration.method, requestConfiguration.url.toString()); request.withCredentials = requestConfiguration.withCredentials; if (requestConfiguration.timeout > 0) { request.timeout = requestConfiguration.timeout; request.addEventListener('timeout', (): void => { reject(createXHResponseObject(request, requestConfiguration)); }); } if (!Validators.isEmpty(requestConfiguration.headers)) { Loops.forEach(requestConfiguration.headers, function (header: any, headerKey: string) { request.setRequestHeader(headerKey, header); }); } request.send(requestConfiguration.data); }); } private httpAdapter(requestConfiguration: Record<string, any>): Promise<any> { const options = { hostname: requestConfiguration.url.hostname, port: null, path: requestConfiguration.url.pathname, method: requestConfiguration.method, headers: requestConfiguration.headers, timeout: requestConfiguration.timeout }; if (requestConfiguration.isPost()) { options.headers['Content-Type'] = 'application/json'; options.headers['Content-Length'] = requestConfiguration.data ? requestConfiguration.data.length : 0; } let processor = null; const requestConfigurationProtocol = requestConfiguration.url.protocol; if (requestConfigurationProtocol === 'http:') { options.port = 80; processor = httpProcessor; } else if (requestConfigurationProtocol === 'https:') { options.port = 443; processor = httpsProcessor; } else { throw '@apicart/js-utils: No processor was found for URL protocol "' + requestConfigurationProtocol + '"'; } const createNodeHttpResponseObject = ( request: httpProcessor.ClientRequest, response: any, responseData: any ): Record<string, any> => { return this.createResponseObject({ type: 'nodeHttp', request: request, response: response, responseData: responseData, requestConfig: requestConfiguration }); }; return new Promise((resolve: Function): void => { const request = processor.request(options, (response) => { let responseData = ''; response.on('data', (data) => { responseData += data; }); response.on('end', () => { resolve(createNodeHttpResponseObject(request, response, responseData)); }); }); request.on('error', (error) => { resolve(createNodeHttpResponseObject(request, null, error)); }); request.on('timeout', () => { request.abort(); }); if (requestConfiguration.isPost()) { request.write(requestConfiguration.data); } request.end(); }); } } export default new Ajax();