@greychrist/react-cache-buster
Version:
This package allows clients to automatically check the new version or git commit hash when a new version is released in the production environment, and if a new version is published, clearing the cache and reload the page.
214 lines (182 loc) • 7.26 kB
JavaScript
import { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import 'compare-versions';
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _createForOfIteratorHelperLoose(o, allowArrayLike) {
var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
if (it) return (it = it.call(o)).next.bind(it);
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
if (it) o = it;
var i = 0;
return function () {
if (i >= o.length) return {
done: true
};
return {
done: false,
value: o[i++]
};
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
// A type of promise-like that resolves synchronously and supports only one observer
const _iteratorSymbol = /*#__PURE__*/ typeof Symbol !== "undefined" ? (Symbol.iterator || (Symbol.iterator = Symbol("Symbol.iterator"))) : "@@iterator";
const _asyncIteratorSymbol = /*#__PURE__*/ typeof Symbol !== "undefined" ? (Symbol.asyncIterator || (Symbol.asyncIterator = Symbol("Symbol.asyncIterator"))) : "@@asyncIterator";
// Asynchronously call a function and send errors to recovery continuation
function _catch(body, recover) {
try {
var result = body();
} catch(e) {
return recover(e);
}
if (result && result.then) {
return result.then(void 0, recover);
}
return result;
}
function CacheBuster(_ref) {
var _ref$children = _ref.children,
children = _ref$children === void 0 ? null : _ref$children,
currentValue = _ref.currentValue,
propertyToCheck = _ref.propertyToCheck,
_ref$isEnabled = _ref.isEnabled,
isEnabled = _ref$isEnabled === void 0 ? false : _ref$isEnabled,
_ref$isVerboseMode = _ref.isVerboseMode,
isVerboseMode = _ref$isVerboseMode === void 0 ? false : _ref$isVerboseMode,
_ref$loadingComponent = _ref.loadingComponent,
loadingComponent = _ref$loadingComponent === void 0 ? null : _ref$loadingComponent,
onCacheClear = _ref.onCacheClear;
if (propertyToCheck !== 'version' && propertyToCheck !== 'hash') {
console.error("CacheBuster: invalid propertyToCheck given: " + propertyToCheck + ". Must be either 'version' or 'hash'");
}
if (!currentValue) {
currentValue = 'notfound';
}
var _useState = useState({
loading: true,
isLatestVersion: false
}),
cacheStatus = _useState[0],
setCacheStatus = _useState[1];
var log = function log(message, isError) {
isVerboseMode && (isError ? console.error(message) : console.log(message));
};
useEffect(function () {
isEnabled ? checkCacheStatus() : log('React Cache Buster is disabled.');
}, []);
var checkCacheStatus = function checkCacheStatus() {
try {
return Promise.resolve(_catch(function () {
return Promise.resolve(fetch('/meta.json?r=' + Math.random())).then(function (res) {
return Promise.resolve(res.json()).then(function (metaJson) {
var metaValue = metaJson[propertyToCheck];
console.log("Cache Buster is examining the property: " + propertyToCheck);
console.log("Current " + propertyToCheck + ": " + currentValue + ", meta " + propertyToCheck + ": " + metaValue);
var shouldForceRefresh = isThereNewVersion(metaValue, currentValue);
if (shouldForceRefresh) {
var ssName = 'reloadRequest';
var beenHereBefore = sessionStorage.getItem(ssName);
if (beenHereBefore) {
console.warn('CacheBuster: Refresh has already run once...cancelling to avoid infinite loop. Please check your comparison values.');
sessionStorage.removeItem(ssName);
setCacheStatus({
loading: false,
isLatestVersion: true
});
return;
} else {
sessionStorage.setItem(ssName, true);
}
log("There is a new " + propertyToCheck + " (v" + metaValue + "). Should force refresh.");
setCacheStatus({
loading: false,
isLatestVersion: false
});
} else {
log("There is no new " + propertyToCheck + ". No cache refresh needed.");
setCacheStatus({
loading: false,
isLatestVersion: true
});
}
});
});
}, function (error) {
log('An error occurred while checking cache status.', true);
log(error, true);
!isVerboseMode && setCacheStatus({
loading: false,
isLatestVersion: true
});
}));
} catch (e) {
return Promise.reject(e);
}
};
var isThereNewVersion = function isThereNewVersion(metaVal, currentVal) {
return metaVal !== currentVal;
};
var refreshCacheAndReload = function refreshCacheAndReload() {
try {
return Promise.resolve(_catch(function () {
var _temp = function () {
var _window;
if ((_window = window) !== null && _window !== void 0 && _window.caches) {
var _window2 = window,
caches = _window2.caches;
return Promise.resolve(caches.keys()).then(function (cacheNames) {
for (var _iterator = _createForOfIteratorHelperLoose(cacheNames), _step; !(_step = _iterator()).done;) {
var cacheName = _step.value;
caches["delete"](cacheName);
}
log('The cache has been deleted.');
window.location.reload(true);
});
}
}();
if (_temp && _temp.then) return _temp.then(function () {});
}, function (error) {
log('An error occurred while deleting the cache.', true);
log(error, true);
}));
} catch (e) {
return Promise.reject(e);
}
};
if (!isEnabled) {
return children;
} else {
if (cacheStatus.loading) {
return loadingComponent;
}
if (!cacheStatus.loading && !cacheStatus.isLatestVersion) {
onCacheClear && onCacheClear();
refreshCacheAndReload();
return null;
}
return children;
}
}
CacheBuster.propTypes = {
children: PropTypes.element.isRequired,
currentValue: PropTypes.string.isRequired,
propertyToCheck: PropTypes.string.isRequired,
isEnabled: PropTypes.bool.isRequired,
isVerboseMode: PropTypes.bool,
loadingComponent: PropTypes.element,
onCacheClear: PropTypes.func
};
export { CacheBuster };