Search / scripts /copy_reader_vendor.mjs
OpenCode
Complete Reader recovery and static asset updates
5084a8e
Raw History Blame Contribute Delete
3.27 kB
import { createHash } from "node:crypto";
import { mkdirSync, writeFileSync } from "node:fs";
import { get } from "node:https";
import { dirname, join } from "node:path";
import { gunzipSync } from "node:zlib";
import { createRequire } from "node:module";
const resources = createRequire(import.meta.url)(join(process.cwd(), "static/reader-resources.js"));
const output = process.argv[2] || "static/vendor";
mkdirSync(output, { recursive: true });
function download(url, redirects = 0) {
return new Promise((resolve, reject) => {
const request = get(url, (response) => {
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location && redirects < 5) {
response.resume();
return download(new URL(response.headers.location, url), redirects + 1).then(resolve, reject);
}
if (response.statusCode !== 200) {
response.resume();
return reject(new Error(`${url}: HTTP ${response.statusCode}`));
}
const chunks = [];
response.on("data", (chunk) => chunks.push(chunk));
response.on("end", () => resolve(Buffer.concat(chunks)));
response.on("error", reject);
});
request.setTimeout(60000, () => request.destroy(new Error(`${url}: request timed out`)));
request.on("error", reject);
});
}
async function downloadWithRetry(url) {
let lastError;
for (let attempt = 0; attempt < 4; attempt++) {
try { return await download(url); }
catch (error) {
lastError = error;
if (attempt < 3) await new Promise((resolve) => setTimeout(resolve, 1000 * (2 ** attempt)));
}
}
throw lastError;
}
async function main() {
const pdfArchive = await downloadWithRetry(resources.pdfArchive.url);
const pdfArchiveHash = createHash("sha256").update(pdfArchive).digest("hex");
if (pdfArchiveHash !== resources.pdfArchive.sha256) throw new Error(`pdfjs-dist archive SHA-256 ${pdfArchiveHash} does not match`);
const tar = gunzipSync(pdfArchive);
for (let offset = 0; offset + 512 <= tar.length;) {
const header = tar.subarray(offset, offset + 512), name = header.subarray(0, 100).toString().replace(/\0.*$/, "");
if (!name) break;
const size = parseInt(header.subarray(124, 136).toString().replace(/\0.*$/, "").trim() || "0", 8);
const relative = name.replace(/^package\//, "");
if ((relative.startsWith("cmaps/") || relative.startsWith("standard_fonts/") || relative.startsWith("wasm/")) && !relative.endsWith("/")) {
const target = join(output, relative);
mkdirSync(dirname(target), { recursive: true });
writeFileSync(target, tar.subarray(offset + 512, offset + 512 + size));
}
offset += 512 + Math.ceil(size / 512) * 512;
}
for (const {url, file: target, sha256: expected, path} of Object.values(resources.vendors)) {
const bytes = await downloadWithRetry(url);
const actual = createHash("sha256").update(bytes).digest("hex");
if (actual !== expected) throw new Error(`${url}: SHA-256 ${actual} does not match ${expected}`);
mkdirSync(dirname(join(output, target)), { recursive: true });
const versioned = path.slice("vendor/".length);
mkdirSync(dirname(join(output, versioned)), { recursive: true });
writeFileSync(join(output, versioned), bytes);
}
}
main().catch((error) => { console.error(error); process.exitCode = 1; });