import debugLib from 'debug';
import { isAsyncAction } from '../asyncActionMiddleware';
import { dataLayerCustomEvent, dataLayerTimingEvent } from './dataLayerUtils';
const debug = debugLib('SlimmingWorld:asyncTrackingMiddleware');
/** @module */
/**
* Time in ms until an event is considered as having timed out
* @type {number}
*/
const ASYNC_FULFILLED_TIMEOUT = 5000;
/**
* Minimum duration in ms of an async action before it will be tracked. Is to prevent
* tracking promise actions that resolve synchronously.
* @type {number}
*/
const ASYNC_THRESHOLD = 100;
/**
* Event category for the timing events pushed to the dataLayer
* @type {string}
*/
const TRACKING_EVENT_CATEGORY = 'asyncAction';
/**
* Event category for the timeout events pushed to the dataLayer
* @type {string}
*/
const TIMEOUT_EVENT_CATEGORY = 'asyncTracking';
/**
* Event action for the timeout events pushed to the dataLayer
* @type {string}
*/
const TIMEOUT_EVENT_ACTION = 'timeout';
/**
* Map that will contain all currently pending actions
* @type {Object}
*/
const pendingActions = {};
/**
* Helper class to track timing of a single async action
* @hidden
*/
class AsyncActionTracking {
constructor(actionType, getState) {
this.startTime = Date.now();
this.actionType = actionType;
this.getReduxState = getState;
this.timeout = setTimeout(this.trackTimeout, ASYNC_FULFILLED_TIMEOUT);
}
/**
* To be called when the action has timed out. Pushes a timeout event to the dataLayer
*/
trackTimeout = () => {
this.timeout = null;
dataLayerCustomEvent(
{
category: TIMEOUT_EVENT_CATEGORY,
action: TIMEOUT_EVENT_ACTION,
label: this.actionType,
value: ASYNC_FULFILLED_TIMEOUT,
},
this.getReduxState(),
);
};
/**
* To be called when a fulfilled action is dispatched. Pushes a timing event to the dataLayer
*/
fulfill = () => {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
const endTime = Date.now();
const duration = endTime - this.startTime;
if (duration > ASYNC_THRESHOLD) {
dataLayerTimingEvent(
TRACKING_EVENT_CATEGORY,
this.actionType,
duration,
'',
this.getReduxState(),
);
}
};
}
/**
* Middleware function that tracks the timing of all dispatched async actions. Should be added after
* the asyncActionMiddleware that dispatches these actions.
* @function asyncTrackingMiddleware
* @category tracking
*/
export default ({ getState }) => next => action => {
if (isAsyncAction(action) && action.type) {
if (action.meta.isFulfilled) {
if (pendingActions[action.meta.asyncId]) {
pendingActions[action.meta.asyncId].fulfill();
delete pendingActions[action.meta.asyncId];
}
} else {
if (pendingActions[action.meta.asyncId]) {
debug(`Unexpected duplicate async action id "${action.meta.asyncId}"`);
return next(action);
}
pendingActions[action.meta.asyncId] = new AsyncActionTracking(action.type, getState);
}
}
return next(action);
};