From 0f643e4a5d8918039e8648f7c1323731ef15927a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Sat, 11 Jul 2026 10:29:23 +0000 Subject: [PATCH] fix(captions): avoid GSAP SRI mismatch in previews --- .../scripts/preview-frames.cjs | 35 ++++++++++++++----- .../scripts/preview-frames.test.cjs | 15 ++++++++ 2 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 skills/embedded-captions/scripts/preview-frames.test.cjs diff --git a/skills/embedded-captions/scripts/preview-frames.cjs b/skills/embedded-captions/scripts/preview-frames.cjs index 56131d7bfd..43265de85f 100644 --- a/skills/embedded-captions/scripts/preview-frames.cjs +++ b/skills/embedded-captions/scripts/preview-frames.cjs @@ -23,6 +23,15 @@ const path = require("path"); const fs = require("fs"); const os = require("os"); +const { pathToFileURL } = require("url"); + +function stripGsapSri(html) { + return html.replace(/]*\bsrc=["'][^"']*gsap[^"']*["'][^>]*>/gi, (tag) => + tag + .replace(/\s+integrity\s*=\s*(?:"[^"]*"|'[^']*')/gi, "") + .replace(/\s+crossorigin\s*=\s*(?:"[^"]*"|'[^']*')/gi, ""), + ); +} const HF_ROOTS = [ process.env.HYPERFRAMES_ROOT, @@ -66,7 +75,7 @@ for (const r of HF_ROOTS) { if (g) gsapSource = fs.readFileSync(g, "utf8"); } } -if (!puppeteer || !sharp) { +if (require.main === module && (!puppeteer || !sharp)) { console.error("[preview] need puppeteer+sharp — set HYPERFRAMES_ROOT"); process.exit(0); } @@ -80,9 +89,13 @@ async function shotAt(browser, file, W, H, t) { // evaluation runs while document.head is still null, and gsap's init then // throws "appendChild of null" — which killed previews for theme projects. await page.setRequestInterception(true); + const documentUrl = pathToFileURL(file).href; + const documentHtml = stripGsapSri(fs.readFileSync(file, "utf8")); page.on("request", (req) => { const u = req.url(); - if (req.resourceType() === "script" && /gsap/i.test(u) && /^https?:/i.test(u)) { + if (req.isNavigationRequest() && u === documentUrl) { + req.respond({ status: 200, contentType: "text/html", body: documentHtml }); + } else if (req.resourceType() === "script" && /gsap/i.test(u) && /^https?:/i.test(u)) { if (gsapSource) req.respond({ status: 200, contentType: "application/javascript", body: gsapSource }); else req.continue(); // no local bundle — let the CDN load (online machines) @@ -90,7 +103,7 @@ async function shotAt(browser, file, W, H, t) { req.abort(); // a-roll pixels come from frames_bg else req.continue(); }); - await page.goto(`file://${file}`, { waitUntil: "load", timeout: 15000 }); + await page.goto(documentUrl, { waitUntil: "load", timeout: 15000 }); const t0 = Date.now(); let tlReady = false; while (Date.now() - t0 < 15000) { @@ -260,9 +273,13 @@ async function main() { await Promise.race([browser.close().catch(() => {}), new Promise((r) => setTimeout(r, 8000))]); } } -main() - .then(() => process.exit(0)) - .catch((e) => { - console.error("[preview]", e.message); - process.exit(1); - }); +if (require.main === module) { + main() + .then(() => process.exit(0)) + .catch((e) => { + console.error("[preview]", e.message); + process.exit(1); + }); +} + +module.exports = { stripGsapSri }; diff --git a/skills/embedded-captions/scripts/preview-frames.test.cjs b/skills/embedded-captions/scripts/preview-frames.test.cjs new file mode 100644 index 0000000000..5d3f8ca4a5 --- /dev/null +++ b/skills/embedded-captions/scripts/preview-frames.test.cjs @@ -0,0 +1,15 @@ +const assert = require("node:assert/strict"); +const test = require("node:test"); +const { stripGsapSri } = require("./preview-frames.cjs"); + +test("removes SRI attributes only from GSAP script tags", () => { + const html = ` + + + `; + const result = stripGsapSri(html); + + assert.match(result, /gsap\.min\.js"><\/script>/); + assert.match(result, /app\.js" integrity="sha384-app" crossorigin="anonymous"/); + assert.doesNotMatch(result.match(/]*gsap[^>]*>/i)[0], /integrity|crossorigin/i); +});