Version 2.0
This commit is contained in:
138
index.js
138
index.js
@ -1,75 +1,115 @@
|
||||
const fetch = require('node-fetch');
|
||||
const fs = require('fs');
|
||||
const { URLSearchParams } = require('url');
|
||||
const crypto = require('crypto');
|
||||
const path = require('path');
|
||||
const Response = require('./classes/response.js');
|
||||
const MemoryCache = require('./classes/caching/memory_cache.js');
|
||||
const FileSystemCache = require('./classes/caching/file_system_cache.js');
|
||||
|
||||
const CACHE_VERSION = 2;
|
||||
|
||||
function md5(str) {
|
||||
return crypto.createHash('md5').update(str).digest('hex');
|
||||
}
|
||||
|
||||
async function getResponse(cacheDirPath, requestArguments, bodyFunctionName) {
|
||||
const [url, requestInit, ...rest] = requestArguments;
|
||||
const requestParams = requestInit && requestInit.body
|
||||
? ({ ...requestInit, body: typeof requestInit.body === 'object' ? requestInit.body.toString() : requestInit.body })
|
||||
: requestInit;
|
||||
// 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();
|
||||
|
||||
const cacheHash = md5(JSON.stringify([url, requestParams, ...rest]) + bodyFunctionName);
|
||||
const cachedFilePath = path.join(cacheDirPath, `${cacheHash}.json`);
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
delete cacheKey._boundary;
|
||||
|
||||
try {
|
||||
const body = JSON.parse(await fs.promises.readFile(cachedFilePath));
|
||||
if (bodyFunctionName === 'buffer') {
|
||||
return Buffer.from(body);
|
||||
const boundaryReplaceRegex = new RegExp(boundary, 'g');
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
cacheKey._streams = cacheKey._streams.map((s) => {
|
||||
if (typeof s === 'string') {
|
||||
return s.replace(boundaryReplaceRegex, '');
|
||||
}
|
||||
|
||||
return s;
|
||||
});
|
||||
|
||||
return cacheKey;
|
||||
}
|
||||
|
||||
function getBodyCacheKeyJson(body) {
|
||||
if (!body) {
|
||||
return body;
|
||||
} catch (err) {
|
||||
const fetchResponse = await fetch(...requestArguments);
|
||||
const bodyResponse = await fetchResponse[bodyFunctionName]();
|
||||
await fs.promises.writeFile(cachedFilePath, JSON.stringify(bodyResponse));
|
||||
return bodyResponse;
|
||||
} if (typeof body === 'string') {
|
||||
return body;
|
||||
} 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);
|
||||
}
|
||||
|
||||
throw new Error('Unsupported body type. Supported body types are: string, number, undefined, null, url.URLSearchParams, fs.ReadStream, FormData');
|
||||
}
|
||||
|
||||
class ResponseWrapper {
|
||||
constructor(cacheDirPath, requestArguments) {
|
||||
this.cacheDirPath = cacheDirPath;
|
||||
this.requestArguments = requestArguments;
|
||||
function getCacheKey(requestArguments) {
|
||||
const resource = requestArguments[0];
|
||||
const init = requestArguments[1] || {};
|
||||
|
||||
if (typeof resource !== 'string') {
|
||||
throw new Error('The first argument must be a string (fetch.Request is not supported).');
|
||||
}
|
||||
|
||||
text() {
|
||||
return getResponse(this.cacheDirPath, this.requestArguments, this.text.name);
|
||||
}
|
||||
const resourceCacheKeyJson = { url: resource };
|
||||
const initCacheKeyJson = { ...init };
|
||||
|
||||
json() {
|
||||
return getResponse(this.cacheDirPath, this.requestArguments, this.json.name);
|
||||
}
|
||||
resourceCacheKeyJson.body = getBodyCacheKeyJson(resourceCacheKeyJson.body);
|
||||
initCacheKeyJson.body = getBodyCacheKeyJson(initCacheKeyJson.body);
|
||||
|
||||
buffer() {
|
||||
return getResponse(this.cacheDirPath, this.requestArguments, this.buffer.name);
|
||||
}
|
||||
|
||||
textConverted() {
|
||||
return getResponse(this.cacheDirPath, this.requestArguments, this.textConverted.name);
|
||||
}
|
||||
return md5(JSON.stringify([resourceCacheKeyJson, initCacheKeyJson, CACHE_VERSION]));
|
||||
}
|
||||
|
||||
function createFetch(cacheDirPath) {
|
||||
let madeDir = false;
|
||||
async function createRawResponse(fetchRes) {
|
||||
const buffer = await fetchRes.buffer();
|
||||
|
||||
return async (...args) => {
|
||||
if (!madeDir) {
|
||||
try {
|
||||
await fs.promises.mkdir(cacheDirPath, { recursive: true });
|
||||
} catch (err) {
|
||||
// Ignore.
|
||||
}
|
||||
|
||||
madeDir = true;
|
||||
}
|
||||
|
||||
return new ResponseWrapper(cacheDirPath, args);
|
||||
return {
|
||||
status: fetchRes.status,
|
||||
statusText: fetchRes.statusText,
|
||||
type: fetchRes.type,
|
||||
url: fetchRes.url,
|
||||
ok: fetchRes.ok,
|
||||
headers: fetchRes.headers.raw(),
|
||||
redirected: fetchRes.redirected,
|
||||
bodyBuffer: buffer,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = createFetch;
|
||||
async function getResponse(cache, requestArguments) {
|
||||
const cacheKey = getCacheKey(requestArguments);
|
||||
const cachedValue = await cache.get(cacheKey);
|
||||
|
||||
const ejectSelfFromCache = () => cache.remove(cacheKey);
|
||||
|
||||
if (cachedValue) {
|
||||
return new Response(cachedValue, ejectSelfFromCache, true);
|
||||
}
|
||||
|
||||
const fetchResponse = await fetch(...requestArguments);
|
||||
const rawResponse = await createRawResponse(fetchResponse);
|
||||
await cache.set(cacheKey, rawResponse);
|
||||
return new Response(rawResponse, ejectSelfFromCache, false);
|
||||
}
|
||||
|
||||
function createFetchWithCache(cache) {
|
||||
const fetchCache = (...args) => getResponse(cache, args);
|
||||
fetchCache.withCache = createFetchWithCache;
|
||||
|
||||
return fetchCache;
|
||||
}
|
||||
|
||||
const defaultFetch = createFetchWithCache(new MemoryCache());
|
||||
|
||||
module.exports = defaultFetch;
|
||||
module.exports.fetchBuilder = defaultFetch;
|
||||
module.exports.MemoryCache = MemoryCache;
|
||||
module.exports.FileSystemCache = FileSystemCache;
|
||||
|
Reference in New Issue
Block a user