223 lines
7.7 KiB
JavaScript
223 lines
7.7 KiB
JavaScript
import { axios } from '@bundled-es-modules/axios';
|
|
import { LionSingleton } from '@lion/core';
|
|
import {
|
|
cancelInterceptorFactory,
|
|
cancelPreviousOnNewRequestInterceptorFactory,
|
|
addAcceptLanguageHeaderInterceptorFactory,
|
|
} from './interceptors.js';
|
|
import { jsonPrefixTransformerFactory } from './transformers.js';
|
|
|
|
/**
|
|
* `AjaxClass` creates the singleton instance {@link:ajax}. It is a promise based system for
|
|
* fetching data, based on [axios](https://github.com/axios/axios).
|
|
*/
|
|
export class AjaxClass extends LionSingleton {
|
|
/**
|
|
* @property {Object} proxy the axios instance that is bound to the AjaxClass instance
|
|
*/
|
|
|
|
/**
|
|
* @param {Object} config configuration for the AjaxClass instance
|
|
* @param {string} config.jsonPrefix prefixing the JSON string in this manner is used to help
|
|
* prevent JSON Hijacking. The prefix renders the string syntactically invalid as a script so
|
|
* that it cannot be hijacked. This prefix should be stripped before parsing the string as JSON.
|
|
* @param {string} config.lang language
|
|
* @param {string} config.languageHeader the Accept-Language request HTTP header advertises
|
|
* which languages the client is able to understand, and which locale variant is preferred.
|
|
* @param {string} config.cancelable if request can be canceled
|
|
* @param {string} config.cancelPreviousOnNewRequest prevents concurrent requests
|
|
*/
|
|
constructor(config) {
|
|
super();
|
|
|
|
this.__config = {
|
|
lang: document.documentElement.getAttribute('lang'),
|
|
languageHeader: true,
|
|
cancelable: false,
|
|
cancelPreviousOnNewRequest: false,
|
|
...config,
|
|
};
|
|
|
|
this.proxy = axios.create(this.__config);
|
|
this.__setupInterceptors();
|
|
|
|
this.requestInterceptors = [];
|
|
this.requestErrorInterceptors = [];
|
|
|
|
this.requestDataTransformers = [];
|
|
this.requestDataErrorTransformers = [];
|
|
|
|
this.responseDataTransformers = [];
|
|
this.responseDataErrorTransformers = [];
|
|
|
|
this.responseInterceptors = [];
|
|
this.responseErrorInterceptors = [];
|
|
|
|
this.__isInterceptorsSetup = false;
|
|
|
|
if (this.__config.languageHeader) {
|
|
this.requestInterceptors.push(addAcceptLanguageHeaderInterceptorFactory(this.__config.lang));
|
|
}
|
|
|
|
if (this.__config.cancelable) {
|
|
this.requestInterceptors.push(cancelInterceptorFactory(this));
|
|
}
|
|
|
|
if (this.__config.cancelPreviousOnNewRequest) {
|
|
this.requestInterceptors.push(cancelPreviousOnNewRequestInterceptorFactory());
|
|
}
|
|
|
|
if (this.__config.jsonPrefix) {
|
|
const transformer = jsonPrefixTransformerFactory(this.__config.jsonPrefix);
|
|
this.responseDataTransformers.push(transformer);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the config for the instance
|
|
* TODO: rename to 'config', because of conflict with options() request method on axios
|
|
*/
|
|
set options(config) {
|
|
this.__config = config;
|
|
}
|
|
|
|
get options() {
|
|
return this.__config;
|
|
}
|
|
|
|
/**
|
|
* Dispatches a request
|
|
* @see https://github.com/axios/axios
|
|
* @param {AxiosRequestConfig} config the config specific for this request
|
|
* @returns {AxiosResponseSchema}
|
|
*/
|
|
request(url, config) {
|
|
return this.proxy.request.apply(this, [url, { ...this.__config, ...config }]);
|
|
}
|
|
|
|
/**
|
|
* Dispatches a {@link AxiosRequestConfig} with method 'get' predefined
|
|
* @param {string} url the endpoint location
|
|
* @param {AxiosRequestConfig} config the config specific for this request
|
|
* @returns {AxiosResponseSchema}
|
|
*/
|
|
get(url, config) {
|
|
return this.proxy.get.apply(this, [url, { ...this.__config, ...config }]);
|
|
}
|
|
|
|
/**
|
|
* Dispatches a {@link AxiosRequestConfig} with method 'delete' predefined
|
|
* @param {string} url the endpoint location
|
|
* @param {AxiosRequestConfig} config the config specific for this request
|
|
* @returns {AxiosResponseSchema}
|
|
*/
|
|
delete(url, config) {
|
|
return this.proxy.delete.apply(this, [url, { ...this.__config, ...config }]);
|
|
}
|
|
|
|
/**
|
|
* Dispatches a {@link AxiosRequestConfig} with method 'head' predefined
|
|
* @param {string} url the endpoint location
|
|
* @param {AxiosRequestConfig} config the config specific for this request
|
|
* @returns {AxiosResponseSchema}
|
|
*/
|
|
head(url, config) {
|
|
return this.proxy.head.apply(this, [url, { ...this.__config, ...config }]);
|
|
}
|
|
|
|
/**
|
|
* Dispatches a {@link AxiosRequestConfig} with method 'options' predefined
|
|
* @param {string} url the endpoint location
|
|
* @param {AxiosRequestConfig} config the config specific for this request
|
|
* @returns {AxiosResponseSchema}
|
|
* TODO: consider reenable after rename of options to config
|
|
*/
|
|
// options(url, config) {
|
|
// return this.proxy.options.apply(this, [url, { ...this.__config, ...config }]);
|
|
// }
|
|
|
|
/**
|
|
* Dispatches a {@link AxiosRequestConfig} with method 'post' predefined
|
|
* @param {string} url the endpoint location
|
|
* @param {Object} data the data to be sent to the endpoint
|
|
* @param {AxiosRequestConfig} config the config specific for this request
|
|
* @returns {AxiosResponseSchema}
|
|
*/
|
|
post(url, data, config) {
|
|
return this.proxy.post.apply(this, [url, data, { ...this.__config, ...config }]);
|
|
}
|
|
|
|
/**
|
|
* Dispatches a {@link AxiosRequestConfig} with method 'put' predefined
|
|
* @param {string} url the endpoint location
|
|
* @param {Object} data the data to be sent to the endpoint
|
|
* @param {AxiosRequestConfig} config the config specific for this request
|
|
* @returns {AxiosResponseSchema}
|
|
*/
|
|
put(url, data, config) {
|
|
return this.proxy.put.apply(this, [url, data, { ...this.__config, ...config }]);
|
|
}
|
|
|
|
/**
|
|
* Dispatches a {@link AxiosRequestConfig} with method 'patch' predefined
|
|
* @see https://github.com/axios/axios (Request Config)
|
|
* @param {string} url the endpoint location
|
|
* @param {Object} data the data to be sent to the endpoint
|
|
* @param {Object} config the config specific for this request.
|
|
* @returns {AxiosResponseSchema}
|
|
*/
|
|
patch(url, data, config) {
|
|
return this.proxy.patch.apply(this, [url, data, { ...this.__config, ...config }]);
|
|
}
|
|
|
|
__setupInterceptors() {
|
|
this.proxy.interceptors.request.use(
|
|
config => {
|
|
const configWithTransformers = this.__setupTransformers(config);
|
|
return this.requestInterceptors.reduce((c, i) => i(c), configWithTransformers);
|
|
},
|
|
error => {
|
|
this.requestErrorInterceptors.forEach(i => i(error));
|
|
return Promise.reject(error);
|
|
},
|
|
);
|
|
|
|
this.proxy.interceptors.response.use(
|
|
response => this.responseInterceptors.reduce((r, i) => i(r), response),
|
|
error => {
|
|
this.responseErrorInterceptors.forEach(i => i(error));
|
|
return Promise.reject(error);
|
|
},
|
|
);
|
|
}
|
|
|
|
__setupTransformers(config) {
|
|
const axiosTransformRequest = config.transformRequest[0];
|
|
const axiosTransformResponse = config.transformResponse[0];
|
|
return {
|
|
...config,
|
|
transformRequest: (data, headers) => {
|
|
try {
|
|
const ourData = this.requestDataTransformers.reduce((d, t) => t(d, headers), data);
|
|
// axios does a lot of smart things with the request that people rely on
|
|
// and must be the last request data transformer to do this job
|
|
return axiosTransformRequest(ourData, headers);
|
|
} catch (error) {
|
|
this.requestDataErrorTransformers.forEach(t => t(error));
|
|
throw error;
|
|
}
|
|
},
|
|
transformResponse: data => {
|
|
try {
|
|
// axios does a lot of smart things with the response that people rely on
|
|
// and must be the first response data transformer to do this job
|
|
const axiosData = axiosTransformResponse(data);
|
|
return this.responseDataTransformers.reduce((d, t) => t(d), axiosData);
|
|
} catch (error) {
|
|
this.responseDataErrorTransformers.forEach(t => t(error));
|
|
throw error;
|
|
}
|
|
},
|
|
};
|
|
}
|
|
}
|