UNPKG

@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
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 };