@mobileaction/ui-modules
Version:
Mobile Action common modules for Vue projects
252 lines (221 loc) • 8.87 kB
JavaScript
/**
*
* This module holds an ajax pool which makes requests by extending CORS headers
* and enables aborting of all requests in case of page changes
*
* settings:
* cacheEnabled => default: false
* if set to true will return result if cached otherwise makes the request and cached the result
*
* id => default: current url
* if given uses the pool with given id
*
* cacheDelay => default: 10
* delay to fire cached result event handlers
*/
import axios from 'axios';
import MaLogger from '../libs/MaLogger.js';
import { getGlobalInstallInstance, validateVueInstall } from '../PluginUtils.js';
export const ALPHANUMERIC_CHARS = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'];
export const randomString = (len = 8) => [...new Array(len)]
.map(() => ALPHANUMERIC_CHARS[Math.floor(Math.random() * ALPHANUMERIC_CHARS.length)])
.join('');
// Standard settings that should be used as default for all ajax calls
export const DEFAULT_OPTIONS = {
cacheEnabled: false,
cacheDelay: 10,
disableLoginCheck: false,
preRequestCheck: () => true,
alreadyRetried: false,
};
class MaRequestPoolerCls {
/**
* Initializes an axios instance
*/
constructor(axiosCfg, cfg) {
this.pools = {};
this.axiosCfg = Object.assign({
responseType: 'json',
// transformResponse: [data => convertDataToNorm(data)],
withCredentials: true,
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
},
}, axiosCfg);
this.poolerAxios = axios.create(this.axiosCfg);
this.app = cfg.app;
this.$log = new MaLogger(cfg.app, { name: 'MaRequestPooler', isDebug: cfg.isDebug || false });
this.cfg = Object.assign({
REQUEST_PREFIX: 'MA_REQ_',
notLoggedIn: (...args) => this.$log.error('Login failed:', ...args),
}, cfg);
}
/**
* clears the page pool with given pool_id
* @param poolId
*/
clearPool(poolId) {
if (!poolId) {
this.$log.error('Invalid id given:', poolId);
return;
}
if (!(poolId in this.pools)) {
this.$log.error('Tried to clean non existing pool with id:', poolId);
return;
}
const p = this.pools[poolId];
Object.keys(p).forEach((reqId) => {
p[reqId].cancel(); // cancel calls complete thus erases this
});
if (this.pools[poolId]) {
delete this.pools[poolId];
}
}
/**
* makes a request to given url with ajax options along with pooler options
*
* @param url
* @param axiosCfg
* @param poolerOpts
* @returns {Promise<any>}
*/
makeRequest(url, axiosCfg, poolerOpts) {
// load axios config with defaults
let _axiosCfg = axiosCfg || {};
this.$log.debug('Making a request to:', url); // eslint-disable-line no-console
// load pooler options with defaults
const _poolerOpts = Object.assign(
{ id: window.location.href },
DEFAULT_OPTIONS,
poolerOpts
);
// generate a random request id
const reqId = randomString(16);
// generate a cancel source for use later on
const cancelSource = axios.CancelToken.source();
// will return a promise I promise :)
const p = new Promise((resolve, reject) => {
// check whether if cache enabled and we cached it
if (_poolerOpts.cacheEnabled) {
if (!this.app.$maSessionStorage) {
throw Error('MaSessionStorage plugin not enabled');
}
// check cached response and if it exists return it
const cachedResponse = this.app.$maSessionStorage.get(this.cfg.REQUEST_PREFIX + url);
if (cachedResponse) {
this.$log.debug(`Using cached data(${ cachedResponse.ts }) for: ${ url }`);
setTimeout(() => {
resolve(cachedResponse.data);
}, _poolerOpts.cacheDelay);
return;
}
}
// if not cached make a request and return Promise
// add xhr to pool and return
this.pools[_poolerOpts.id] = this.pools[_poolerOpts.id] || {};
this.pools[_poolerOpts.id][reqId] = {
id: reqId,
cancel: cancelSource.cancel,
ts: Date.now(),
};
const headers = Object.assign({}, _axiosCfg.headers);
Object.assign(_axiosCfg, {
url,
headers,
cancelToken: cancelSource.token,
});
const canContinue = _poolerOpts.preRequestCheck(url, _axiosCfg, _poolerOpts);
if (canContinue === false) {
return reject({ errors: ['PRE_REQUEST_CHECK_FAILED'] });
}
this.poolerAxios
.request(_axiosCfg)
.then(({ data, status, statusText }) => {
// eslint-disable-next-line no-console
this.$log.debug(`Data received: ${ status }-${ statusText } data:`, data);
// if we requested json file and received json response return it in success format
let _data = null;
if (/\.json$/.test(url.split('?')[0]) && !Object.hasOwnProperty.bind(data)('success')) {
_data = { success: true, data };
}
_data = _data || data;
if (_data.success) {
if (_poolerOpts.cacheEnabled) {
const ts = new Date().getTime();
this.app.$maSessionStorage.set(this.cfg.REQUEST_PREFIX + url, {
data: _data.data,
ts,
});
this.$log.debug(`Caching data at ${ ts } for: ${ url }`);
}
resolve(_data.data);
} else {
const errors = _data.errors || [];
if (!_poolerOpts.disableLoginCheck && errors.find(e => e === 'NOT_LOGGED_IN')) {
this.cfg.notLoggedIn();
}
reject(_data);
}
})
.catch((error) => { // normally no request should respond with fail
// default to have FETCH_ERROR to know it came from us
this.$log.error('Failed request:', error);
const status = error.response
? error.response.status
: error.message;
if (!_poolerOpts.disableLoginCheck &&
(status === 401 || (error.response && error.response.data.errorCode === 'AUTHENTICATION_FAILED'))) {
this.cfg.notLoggedIn();
}
let errCode = (error.response && error.response.data) || error.message;
errCode = errCode && ([errCode.errorCode] || errCode.errors);
reject({ errors: [...errCode, 'FETCH_ERROR'] }, status);
});
});
// set pool, request id, and cancel function for future cancel/clears
p.poolId = _poolerOpts.id;
p.reqId = reqId;
p.cancel = cancelSource.cancel;
return p;
}
/**
* clears all page pools if no id is given otherwise clears only given pool
*
* @param id
*/
clear(id) {
// if id is given just destroy given id
if (id || id in this.pools) {
this.clearPool(id);
return;
}
// check all pools of all ids
Object.keys(this.pools).forEach((poolId) => {
this.clearPool(poolId);
});
}
/**
* Clears a given request
* @param poolId
* @param reqId
*/
cancelRequest(poolId, reqId) {
if (!poolId || !reqId) {
this.$log.error('Received invalid poolId-reqId pair: ', poolId, reqId);
return;
}
if (!this.pools[poolId] || !this.pools[poolId][reqId]) {
this.$log.error('Pool or request does not exists:', poolId, reqId);
return;
}
this.pools[poolId][reqId].cancel();
}
}
export function MaRequestPooler(app, axiosCfg, cfg) {
if (!validateVueInstall(app, MaRequestPooler, 'MaRequestPooler')) {
return;
}
getGlobalInstallInstance(app).$req = new MaRequestPoolerCls(axiosCfg, cfg);
}
export default MaRequestPooler;