This repository has been archived on 2023-10-27. You can view files and clone it, but cannot push or open issues or pull requests.
Files
node-fetch-cache/index.js

131 lines
3.7 KiB
JavaScript
Raw Normal View History

2021-07-05 14:17:21 -04:00
import fetch from 'node-fetch';
import fs from 'fs';
import { URLSearchParams } from 'url';
import crypto from 'crypto';
import locko from 'locko';
2021-07-05 18:14:42 -04:00
import { NFCResponse } from './classes/response.js';
2021-07-05 14:17:21 -04:00
import { MemoryCache } from './classes/caching/memory_cache.js';
2021-06-12 19:26:05 -04:00
2021-07-05 18:14:42 -04:00
const CACHE_VERSION = 3;
2020-04-17 16:31:22 -04:00
function md5(str) {
return crypto.createHash('md5').update(str).digest('hex');
}
2021-06-12 19:26:05 -04:00
// Since the bounday in FormData is random,
// we ignore it for purposes of calculating
// the cache key.
function getFormDataCacheKey(formData) {
const cacheKey = { ...formData };
const boundary = formData.getBoundary();
// eslint-disable-next-line no-underscore-dangle
delete cacheKey._boundary;
2020-11-28 00:01:16 +01:00
2021-06-12 19:26:05 -04:00
const boundaryReplaceRegex = new RegExp(boundary, 'g');
2020-04-17 16:31:22 -04:00
2021-06-12 19:26:05 -04:00
// eslint-disable-next-line no-underscore-dangle
cacheKey._streams = cacheKey._streams.map((s) => {
if (typeof s === 'string') {
return s.replace(boundaryReplaceRegex, '');
2020-04-17 16:31:22 -04:00
}
2021-06-12 19:26:05 -04:00
return s;
});
return cacheKey;
}
function getBodyCacheKeyJson(body) {
if (!body) {
return body;
} if (typeof body === 'string') {
2020-04-17 16:31:22 -04:00
return body;
2021-06-12 19:26:05 -04:00
} if (body instanceof URLSearchParams) {
return body.toString();
} if (body instanceof fs.ReadStream) {
return body.path;
} if (body.toString && body.toString() === '[object FormData]') {
return getFormDataCacheKey(body);
2020-04-17 16:31:22 -04:00
}
2021-06-12 19:26:05 -04:00
throw new Error('Unsupported body type. Supported body types are: string, number, undefined, null, url.URLSearchParams, fs.ReadStream, FormData');
2020-04-17 16:31:22 -04:00
}
2021-06-12 19:26:05 -04:00
function getCacheKey(requestArguments) {
const resource = requestArguments[0];
const init = requestArguments[1] || {};
2020-04-17 16:31:22 -04:00
2021-06-12 19:26:05 -04:00
if (typeof resource !== 'string') {
throw new Error('The first argument must be a string (fetch.Request is not supported).');
2020-04-17 16:31:22 -04:00
}
2021-06-12 19:26:05 -04:00
const resourceCacheKeyJson = { url: resource };
const initCacheKeyJson = { ...init };
2020-04-17 16:31:22 -04:00
2021-06-12 19:26:05 -04:00
resourceCacheKeyJson.body = getBodyCacheKeyJson(resourceCacheKeyJson.body);
initCacheKeyJson.body = getBodyCacheKeyJson(initCacheKeyJson.body);
2020-04-17 16:31:22 -04:00
delete resourceCacheKeyJson.agent;
delete initCacheKeyJson.agent;
2021-06-12 19:26:05 -04:00
return md5(JSON.stringify([resourceCacheKeyJson, initCacheKeyJson, CACHE_VERSION]));
2020-04-17 16:31:22 -04:00
}
2021-06-12 19:26:05 -04:00
async function getResponse(cache, requestArguments) {
const cacheKey = getCacheKey(requestArguments);
let cachedValue = await cache.get(cacheKey);
2020-04-17 16:31:22 -04:00
2021-06-12 19:26:05 -04:00
const ejectSelfFromCache = () => cache.remove(cacheKey);
if (cachedValue) {
2021-07-05 18:14:42 -04:00
return NFCResponse.fromCachedResponse(
cachedValue.bodyStream,
cachedValue.metaData,
ejectSelfFromCache,
);
2021-06-12 19:26:05 -04:00
}
await locko.lock(cacheKey);
try {
cachedValue = await cache.get(cacheKey);
if (cachedValue) {
return NFCResponse.fromCachedResponse(
cachedValue.bodyStream,
cachedValue.metaData,
ejectSelfFromCache,
);
}
const fetchResponse = await fetch(...requestArguments);
const nfcResponse = NFCResponse.fromNodeFetchResponse(fetchResponse, ejectSelfFromCache);
const contentLength = Number.parseInt(nfcResponse.headers.get('content-length'), 10) || 0;
const nfcResponseSerialized = nfcResponse.serialize();
2021-07-05 21:40:53 -04:00
await cache.set(
cacheKey,
nfcResponseSerialized.bodyStream,
nfcResponseSerialized.metaData,
contentLength,
);
2021-07-05 21:40:53 -04:00
return nfcResponse;
} finally {
locko.unlock(cacheKey);
}
2020-04-17 16:31:22 -04:00
}
2021-06-12 19:26:05 -04:00
function createFetchWithCache(cache) {
const fetchCache = (...args) => getResponse(cache, args);
fetchCache.withCache = createFetchWithCache;
return fetchCache;
}
const defaultFetch = createFetchWithCache(new MemoryCache());
2021-07-05 14:17:21 -04:00
export default defaultFetch;
export const fetchBuilder = defaultFetch;
export { MemoryCache } from './classes/caching/memory_cache.js';
export { FileSystemCache } from './classes/caching/file_system_cache.js';