88 lines
2.5 KiB
JavaScript
88 lines
2.5 KiB
JavaScript
// @ts-check
|
|
import fs from "node:fs";
|
|
import crypto from "node:crypto";
|
|
import { join, parse, relative } from "node:path";
|
|
import throwErrorIfUnsupported from "./throwErrorIfUnsupported.js";
|
|
import {
|
|
cwd,
|
|
fsCachePath,
|
|
supportedImageTypes,
|
|
} from "../../utils/runtimeChecks.js";
|
|
|
|
const { fileTypeFromBuffer } = await import("file-type");
|
|
|
|
// Retry mechanism with exponential backoff
|
|
async function retryWithBackoff(fn, retries = 3, baseDelay = 100) {
|
|
for (let i = 0; i < retries; i++) {
|
|
try {
|
|
return await fn();
|
|
} catch (error) {
|
|
if (i === retries - 1) {
|
|
throw error; // Last attempt failed
|
|
}
|
|
|
|
// Check if it's a file system error that we should retry
|
|
const isRetryableError = error.code === 'EBUSY' ||
|
|
error.code === 'ENOENT' ||
|
|
error.code === 'EPERM' ||
|
|
error.errno === -4094 || // UNKNOWN error on Windows
|
|
error.message.includes('UNKNOWN: unknown error');
|
|
|
|
if (!isRetryableError) {
|
|
throw error; // Don't retry non-transient errors
|
|
}
|
|
|
|
const delay = baseDelay * Math.pow(2, i); // Exponential backoff
|
|
console.warn(`Retry attempt ${i + 1}/${retries} for file operation after ${delay}ms delay:`, error.message);
|
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
}
|
|
}
|
|
}
|
|
|
|
export default async function getResolvedSrc(src) {
|
|
const token = crypto.createHash("md5").update(src).digest("hex");
|
|
let filepath = fsCachePath + token;
|
|
|
|
const fileExists = await retryWithBackoff(() => {
|
|
for (const type of supportedImageTypes) {
|
|
const fileExists = fs.existsSync(filepath + `.${type}`);
|
|
|
|
if (fileExists) {
|
|
filepath += `.${type}`;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
});
|
|
|
|
if (!fileExists) {
|
|
const buffer = Buffer.from(await (await fetch(src)).arrayBuffer());
|
|
|
|
const { ext } = (await fileTypeFromBuffer(buffer)) || {};
|
|
|
|
throwErrorIfUnsupported(src, ext);
|
|
|
|
filepath += `.${ext}`;
|
|
|
|
// Use retry mechanism for file write operations
|
|
await retryWithBackoff(() => {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
fs.writeFileSync(filepath, buffer);
|
|
resolve(undefined);
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
const base = /^https?:/.test(src)
|
|
? parse(new URL(src).pathname).name
|
|
: undefined;
|
|
|
|
src = join("/", relative(cwd, filepath));
|
|
|
|
return { src, base };
|
|
}
|