From 4191cd213842126593517a100c62febd457b347e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Mon, 1 Jun 2026 21:19:25 +0000 Subject: [PATCH 1/4] fix(producer): localize remote @font-face src URLs before render MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remote font URLs in @font-face blocks fail with a CORS rejection when the renderer fetches them from http://localhost:PORT (S3 does not echo the local origin in Access-Control-Allow-Origin). Chrome falls back to the next font in the stack (e.g. Arial), producing wrong typography. localizeRemoteFontFaces() scans `; + const { html: result, remoteMediaAssets } = await localizeRemoteFontFaces(html, dl); + expect(result).not.toContain(FONT_URL); + expect(result).toContain("_remote_media/"); + expect(remoteMediaAssets.size).toBe(1); + } finally { + globalThis.fetch = orig; + } + }); + + it("ignores url() references outside @font-face (e.g. background-image)", async () => { + const orig = globalThis.fetch; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (globalThis as any).fetch = async () => new Response(new Uint8Array(16), { status: 200 }); + try { + const dl = mkdtempSync(join(tmpdir(), "hf-ff-bg-")); + const BG_URL = "https://cdn.example.com/bg.png"; + const html = ``; + const { html: result } = await localizeRemoteFontFaces(html, dl); + // Font URL rewritten, background URL untouched + expect(result).not.toContain(FONT_URL); + expect(result).toContain(BG_URL); + } finally { + globalThis.fetch = orig; + } + }); + + it("preserves original URL when download fails", async () => { + const orig = globalThis.fetch; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (globalThis as any).fetch = async () => new Response(null, { status: 403 }); + try { + const dl = mkdtempSync(join(tmpdir(), "hf-ff-fail-")); + const FAIL_URL = "https://fail-font.example.com/f.ttf"; + const html = ``; + const { html: result, remoteMediaAssets } = await localizeRemoteFontFaces(html, dl); + expect(result).toContain(FAIL_URL); + expect(remoteMediaAssets.size).toBe(0); + } finally { + globalThis.fetch = orig; + } + }); + + it("deduplicates: same font URL in two @font-face blocks → 1 download", async () => { + const orig = globalThis.fetch; + let fetchCount = 0; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (globalThis as any).fetch = async () => { + fetchCount++; + return new Response(new Uint8Array(16), { status: 200 }); + }; + try { + const dl = mkdtempSync(join(tmpdir(), "hf-ff-dedup-")); + const DEDUP_URL = "https://dedup-font.example.com/d.ttf"; + const html = ``; + const { remoteMediaAssets } = await localizeRemoteFontFaces(html, dl); + expect(fetchCount).toBe(1); + expect(remoteMediaAssets.size).toBe(1); + } finally { + globalThis.fetch = orig; + } + }); + + it("no-ops when no @font-face blocks are present", async () => { + const dl = mkdtempSync(join(tmpdir(), "hf-ff-noop-")); + const html = ``; + const { html: result, remoteMediaAssets } = await localizeRemoteFontFaces(html, dl); + expect(result).toBe(html); + expect(remoteMediaAssets.size).toBe(0); + }); + + it("ignores local (non-HTTP) @font-face src URLs", async () => { + const dl = mkdtempSync(join(tmpdir(), "hf-ff-local-")); + const html = ``; + const { html: result, remoteMediaAssets } = await localizeRemoteFontFaces(html, dl); + expect(result).toBe(html); + expect(remoteMediaAssets.size).toBe(0); + }); +}); + describe("discoverAudioVolumeAutomationFromTimeline", () => { it("samples video-derived audio volume without firing GSAP callbacks", async () => { class TestAudioElement {} diff --git a/packages/producer/src/services/htmlCompiler.ts b/packages/producer/src/services/htmlCompiler.ts index 85edddbc0..cd9c713c5 100644 --- a/packages/producer/src/services/htmlCompiler.ts +++ b/packages/producer/src/services/htmlCompiler.ts @@ -888,40 +888,26 @@ const REMOTE_MEDIA_TAG_RE = /<(?:video|audio)\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi; /** - * Download any remote `src` URLs on `