@apicart/js-utils
Version:
A small set of useful utilities for easier development
272 lines (231 loc) • 7.43 kB
text/typescript
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();