const path = require('path');
const fs = require('fs');
const { WebCache, VersionResolveError } = require('./WebCache');
const {
getIntegrityEntry,
verifyWebCacheIntegrity,
} = require('./WebCacheIntegrity');
/**
* LocalWebCache - Fetches a WhatsApp Web version from a local file store
* @param {object} options - options
* @param {string} options.path - Path to the directory where cached versions are saved, default is: "./.wwebjs_cache/"
* @param {boolean} options.strict - If true, will throw an error if the requested version can't be fetched. Defaults to true.
* @param {boolean} options.readOnly - When true, cache misses are not persisted.
* @param {Record<string, {sha256: string, sizeBytes: number}>} options.integrity - Optional immutable artifact manifest.
*/
class LocalWebCache extends WebCache {
constructor(options = {}) {
super();
this.path = options.path || './.wwebjs_cache/';
this.strict = options.strict !== false;
this.readOnly = options.readOnly === true;
this.integrity = options.integrity;
}
getFilePath(version) {
const cachePath = path.resolve(this.path);
const filePath = path.resolve(cachePath, `${version}.html`);
const relativePath = path.relative(cachePath, filePath);
if (
!relativePath ||
relativePath.startsWith(`..${path.sep}`) ||
path.isAbsolute(relativePath)
) {
throw new VersionResolveError(
`Invalid WhatsApp Web version ${version}`,
);
}
return { cachePath, filePath };
}
async resolve(version) {
const { filePath } = this.getFilePath(version);
let content;
try {
content = fs.readFileSync(filePath);
} catch (ignoredError) {
if (this.strict)
throw new VersionResolveError(
`Couldn't load version ${version} from the cache`,
);
return null;
}
verifyWebCacheIntegrity(content, version, this.integrity);
return content.toString('utf8');
}
async persist(indexHtml, version) {
if (this.readOnly || getIntegrityEntry(this.integrity, version)) return;
const { cachePath, filePath } = this.getFilePath(version);
fs.mkdirSync(cachePath, { recursive: true });
try {
if (fs.lstatSync(filePath).isSymbolicLink()) {
throw new VersionResolveError(
`Refusing to persist WhatsApp Web version ${version} through a symbolic link`,
);
}
} catch (error) {
if (error instanceof VersionResolveError) throw error;
if (error?.code !== 'ENOENT') throw error;
}
const noFollow = fs.constants.O_NOFOLLOW || 0;
let descriptor;
try {
descriptor = fs.openSync(
filePath,
fs.constants.O_WRONLY |
fs.constants.O_CREAT |
fs.constants.O_TRUNC |
noFollow,
0o600,
);
} catch (error) {
if (error?.code === 'ELOOP') {
throw new VersionResolveError(
`Refusing to persist WhatsApp Web version ${version} through a symbolic link`,
);
}
throw error;
}
try {
fs.writeFileSync(descriptor, indexHtml);
} finally {
fs.closeSync(descriptor);
}
}
}
module.exports = LocalWebCache;