axios-etag-cache
Version:
Axios etag interceptor to enable If-None-Match request with ETAG support
258 lines (248 loc) • 9.09 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
const byLowerCase = toFind => value => toLowerCase(value) === toFind;
const toLowerCase = value => value.toLowerCase();
const getKeys = headers => Object.keys(headers);
const getHeaderCaseInsensitive = (headerName, headers = {}) => {
const key = getKeys(headers).find(byLowerCase(headerName));
return key ? headers[key] : undefined;
};
const cyrb53 = (str, seed = 0) => {
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
};
class BaseCache {
}
class DefaultCache extends BaseCache {
constructor() {
super(...arguments);
this.cache = {};
}
get(key) {
return Promise.resolve(this.cache[key]);
}
set(key, value) {
this.cache[key] = value;
}
flushAll() {
this.cache = {};
}
}
class LocalStorageCache extends BaseCache {
flushAll() {
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key !== null && key.startsWith('aec-')) {
localStorage.removeItem(key);
}
}
}
get(key) {
return new Promise((resolve) => {
try {
const payload = localStorage.getItem('aec-' + key);
if (payload !== null) {
resolve(JSON.parse(payload));
}
else {
resolve(undefined);
}
}
catch (e) {
resolve(undefined);
}
});
}
set(key, value) {
try {
const payload = JSON.stringify(value);
localStorage.setItem('aec-' + key, payload);
}
catch (e) {
console.log(e);
}
}
}
let instance;
let cache;
const makeSingleton = (cacheClass) => {
/** Closure of the singleton's value to keep it private */
cache = new cacheClass();
/** Only the accessors are returned */
return {
get(uuid) {
return __awaiter(this, void 0, void 0, function* () {
let payload = yield cache.get(uuid);
if (payload) {
payload = Object.assign(Object.assign({}, payload), { lastHitAt: Date.now() });
cache.set(uuid, payload);
}
return payload;
});
},
set(uuid, etag, value) {
return cache.set(uuid, { etag, value, createdAt: Date.now(), lastHitAt: 0 });
},
reset() {
cache.flushAll();
}
};
};
const getCacheInstance = (cacheClass) => {
if (!instance) {
instance = makeSingleton(cacheClass);
return instance;
}
return instance;
};
let Cache;
let cacheableMethods = ['GET', 'HEAD'];
function isCacheableMethod(config) {
if (!config.method) {
return false;
}
return ~cacheableMethods.indexOf(config.method.toUpperCase());
}
function getUrlByAxiosConfig(config) {
return config.url;
}
const getCacheByAxiosConfig = (config) => __awaiter(void 0, void 0, void 0, function* () {
const url = getUrlByAxiosConfig(config);
if (url) {
if (config.data) {
const hash = cyrb53(config.data);
return yield Cache.get(hash + url);
}
else {
return yield Cache.get(url);
}
}
return undefined;
});
function requestInterceptor(config) {
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
if (isCacheableMethod(config)) {
const url = getUrlByAxiosConfig(config);
if (!url) {
reject(config);
return;
}
let lastCachedResult;
if (config.data) {
try {
const hash = cyrb53(JSON.stringify(config.data));
lastCachedResult = yield Cache.get(hash + url);
}
catch (e) {
console.error(e);
}
}
else {
lastCachedResult = yield Cache.get(url);
}
if (lastCachedResult) {
config.headers = Object.assign(Object.assign({}, config.headers), { 'If-None-Match': lastCachedResult.etag });
}
}
resolve(config);
}));
}
function responseInterceptor(response) {
return new Promise((resolve, reject) => {
if (isCacheableMethod(response.config)) {
const responseETAG = getHeaderCaseInsensitive('etag', response.headers);
if (responseETAG) {
const url = getUrlByAxiosConfig(response.config);
if (!url) {
reject(null);
return;
}
if (response.config.data) {
try {
const hash = cyrb53(response.config.data);
Cache.set(hash + url, responseETAG, response.data);
}
catch (e) {
console.error(e);
}
}
else {
Cache.set(url, responseETAG, response.data);
}
}
}
resolve(response);
});
}
function responseErrorInterceptor(error) {
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
if (error.response && error.response.status === 304) {
const getCachedResult = yield getCacheByAxiosConfig(error.response.config);
if (!getCachedResult) {
reject(error);
return;
}
const newResponse = error.response;
newResponse.status = 200;
newResponse.data = getCachedResult.value;
resolve(newResponse);
return;
}
reject(error);
}));
}
function resetCache() {
return __awaiter(this, void 0, void 0, function* () {
yield Cache.reset();
});
}
function axiosETAGCache(axiosInstance, options) {
if (options === null || options === void 0 ? void 0 : options.cacheClass) {
Cache = getCacheInstance(options.cacheClass);
}
else {
Cache = getCacheInstance(DefaultCache);
}
if ((options === null || options === void 0 ? void 0 : options.enablePost) === true) {
cacheableMethods.push('POST');
}
axiosInstance.interceptors.request.use(requestInterceptor);
axiosInstance.interceptors.response.use(responseInterceptor, responseErrorInterceptor);
return axiosInstance;
}
exports.BaseCache = BaseCache;
exports.DefaultCache = DefaultCache;
exports.LocalStorageCache = LocalStorageCache;
exports.axiosETAGCache = axiosETAGCache;
exports.getCacheByAxiosConfig = getCacheByAxiosConfig;
exports.getCacheInstance = getCacheInstance;
exports.resetCache = resetCache;
//# sourceMappingURL=index.js.map