export const omit =
(ks: string[], original: object) =>
Object.keys(original)
.filter((k) => !ks.includes(k))
.reduce((filtered, k) => Object.assign(original, { [k]: original[k] }), {});
type RetryCallback = () => Promise;
export const generateRID = (() => {
let rid = 1;
return () => rid++ && String(rid);
})();
const failureTreshold = 3
const retryTime = 180000 // 3 minutes;
const updateCircuitBreakerStatus = (failureCount: number, key: string) => {
sessionStorage.setItem(key, JSON.stringify({ failureCount, lastFailureDate: new Date() }))
}
const doRequest = (attempt: number = 0, fn: RetryCallback, key: string) => {
return fn().catch((err) => {
const failureCount = attempt + 1
updateCircuitBreakerStatus(failureCount, key);
if (!err.response || err.response?.status < 500 || failureCount >= failureTreshold) throw err;
return doRequest(failureCount, fn, key);
})
}
/**
*
* @param key widgetId + MerchantId - key of the session storage value
* @returns failureCount and lastFailureDate of the matched key
*/
const getCircuitBreakerStatus = (key: string): { failureCount: number; lastFailureDate: Date; } => {
const findifyCircuitBreaker = sessionStorage.getItem(key);
return findifyCircuitBreaker ? JSON.parse(findifyCircuitBreaker) : { failureCount: null, lastFailureDate: null };
}
const tryAgainCheck = (lastFailure: Date) => {
const lastAttempt = new Date(lastFailure);
const now = new Date();
return now.getTime() - lastAttempt?.getTime() >= retryTime
}
const blockRequest = (failureCount: number, lastFailureDate: Date) =>
failureCount >= failureTreshold && !tryAgainCheck(lastFailureDate)
const clearCircuitBreakerStatus = (key) => {
sessionStorage.removeItem(key);
}
/**
*
* @param fn Request to execute
* @param key ${merchantId}_${widgetId} for session storage
* @returns
*/
export const requestHandler = (fn: RetryCallback, key) =>
new Promise((resolve, reject) => {
const { failureCount, lastFailureDate } = getCircuitBreakerStatus(key);
if (blockRequest(failureCount, lastFailureDate)) return reject('Too many attemps, please retry after a few minutes.');
doRequest(failureCount, fn, key)
.then((result) => {
clearCircuitBreakerStatus(key);
return resolve(result);
})
.catch((err) => reject(err))
});