140 lines
5.5 KiB
JavaScript
140 lines
5.5 KiB
JavaScript
|
|
import Headers from '../../Headers.js';
|
||
|
|
import FS from 'fs';
|
||
|
|
import Path from 'path';
|
||
|
|
import Crypto from 'crypto';
|
||
|
|
/**
|
||
|
|
* Fetch response cache file system implementation.
|
||
|
|
*/
|
||
|
|
export default class ResponseCacheFileSystem {
|
||
|
|
#createdDirectories = new Set();
|
||
|
|
#hashes = new Set();
|
||
|
|
#entries;
|
||
|
|
/**
|
||
|
|
* Constructor.
|
||
|
|
*
|
||
|
|
* @param entries Map of URL to cached responses.
|
||
|
|
*/
|
||
|
|
constructor(entries) {
|
||
|
|
this.#entries = entries;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Loads cache from file system.
|
||
|
|
*
|
||
|
|
* @param directory Directory to load from.
|
||
|
|
*/
|
||
|
|
async load(directory) {
|
||
|
|
if (!directory) {
|
||
|
|
throw new Error("Failed to execute 'load' on 'ResponseCacheFileSystem': Directory is not specified.");
|
||
|
|
}
|
||
|
|
const absoluteDirectory = Path.resolve(directory);
|
||
|
|
let files = [];
|
||
|
|
try {
|
||
|
|
files = await FS.promises.readdir(absoluteDirectory);
|
||
|
|
}
|
||
|
|
catch (error) {
|
||
|
|
// Ignore if the directory does not exist
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const promises = [];
|
||
|
|
const entries = this.#entries;
|
||
|
|
for (const file of files) {
|
||
|
|
if (file.endsWith('.json')) {
|
||
|
|
promises.push(FS.promises
|
||
|
|
.readFile(Path.join(absoluteDirectory, file), 'utf8')
|
||
|
|
.then((content) => {
|
||
|
|
const cachedResponse = JSON.parse(content);
|
||
|
|
if (cachedResponse.response && cachedResponse.request && !cachedResponse.virtual) {
|
||
|
|
cachedResponse.response.headers = new Headers(cachedResponse.response.headers);
|
||
|
|
cachedResponse.request.headers = new Headers(cachedResponse.request.headers);
|
||
|
|
return FS.promises
|
||
|
|
.readFile(Path.join(absoluteDirectory, file.split('.')[0] + '.data'))
|
||
|
|
.then((body) => {
|
||
|
|
cachedResponse.response.body = body;
|
||
|
|
let entry = entries.get(cachedResponse.response.url);
|
||
|
|
if (!entry) {
|
||
|
|
entry = [];
|
||
|
|
entries.set(cachedResponse.response.url, entry);
|
||
|
|
}
|
||
|
|
entry.push(cachedResponse);
|
||
|
|
})
|
||
|
|
.catch(() => {
|
||
|
|
let entry = entries.get(cachedResponse.response.url);
|
||
|
|
if (!entry) {
|
||
|
|
entry = [];
|
||
|
|
entries.set(cachedResponse.response.url, entry);
|
||
|
|
}
|
||
|
|
entry.push(cachedResponse);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.catch(() => { }));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
await Promise.all(promises);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Saves the cache to file system.
|
||
|
|
*
|
||
|
|
* @param directory Directory to store to.
|
||
|
|
*/
|
||
|
|
async save(directory) {
|
||
|
|
if (!directory) {
|
||
|
|
throw new Error("Failed to execute 'store' on 'ResponseCacheFileSystem': Directory is not specified.");
|
||
|
|
}
|
||
|
|
if (!this.#entries.size) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const absoluteDirectory = Path.resolve(directory);
|
||
|
|
const isDirectoryCreated = this.#createdDirectories.has(absoluteDirectory);
|
||
|
|
if (!isDirectoryCreated) {
|
||
|
|
this.#createdDirectories.add(absoluteDirectory);
|
||
|
|
try {
|
||
|
|
await FS.promises.mkdir(absoluteDirectory, {
|
||
|
|
recursive: true
|
||
|
|
});
|
||
|
|
}
|
||
|
|
catch (error) {
|
||
|
|
// Ignore if the directory already exists
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const entries = this.#entries;
|
||
|
|
const promises = [];
|
||
|
|
for (const entry of entries.values()) {
|
||
|
|
for (const cachedResponse of entry) {
|
||
|
|
if (!cachedResponse.virtual) {
|
||
|
|
const responseHeaders = {};
|
||
|
|
const requestHeaders = {};
|
||
|
|
for (const [key, value] of cachedResponse.response.headers.entries()) {
|
||
|
|
responseHeaders[key] = value;
|
||
|
|
}
|
||
|
|
for (const [key, value] of cachedResponse.request.headers.entries()) {
|
||
|
|
requestHeaders[key] = value;
|
||
|
|
}
|
||
|
|
const cacheableEntry = {
|
||
|
|
...cachedResponse,
|
||
|
|
response: {
|
||
|
|
...cachedResponse.response,
|
||
|
|
headers: responseHeaders,
|
||
|
|
body: null
|
||
|
|
},
|
||
|
|
request: {
|
||
|
|
...cachedResponse.request,
|
||
|
|
headers: requestHeaders
|
||
|
|
}
|
||
|
|
};
|
||
|
|
const json = JSON.stringify(cacheableEntry, null, 3);
|
||
|
|
const hash = Crypto.createHash('md5').update(json).digest('hex');
|
||
|
|
if (!this.#hashes.has(hash)) {
|
||
|
|
this.#hashes.add(hash);
|
||
|
|
promises.push(FS.promises.writeFile(Path.join(absoluteDirectory, `${hash}.json`), json));
|
||
|
|
if (cachedResponse.response.body) {
|
||
|
|
promises.push(FS.promises.writeFile(Path.join(absoluteDirectory, `${hash}.data`), cachedResponse.response.body));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
await Promise.all(promises);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
//# sourceMappingURL=ResponseCacheFileSystem.js.map
|