UNPKG

@mobileaction/ui-modules

Version:

Mobile Action common modules for Vue projects

252 lines (221 loc) 8.87 kB
/** * * 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;