diff --git a/apps/yaak-client/components/responseViewers/PdfViewer.tsx b/apps/yaak-client/components/responseViewers/PdfViewer.tsx index f85507d0c..54a3174b3 100644 --- a/apps/yaak-client/components/responseViewers/PdfViewer.tsx +++ b/apps/yaak-client/components/responseViewers/PdfViewer.tsx @@ -3,18 +3,15 @@ import "react-pdf/dist/Page/AnnotationLayer.css"; import "./PdfViewer.css"; import type { PDFDocumentProxy } from "pdfjs-dist"; import { useMemo, useRef, useState } from "react"; -import { Document, Page } from "react-pdf"; +import { Document, Page, pdfjs } from "react-pdf"; import { useContainerSize } from "@yaakapp-internal/ui"; -import { fireAndForget } from "../../lib/fireAndForget"; -fireAndForget( - import("react-pdf").then(({ pdfjs }) => { - pdfjs.GlobalWorkerOptions.workerSrc = new URL( - "pdfjs-dist/build/pdf.worker.min.mjs", - import.meta.url, - ).toString(); - }), -); +// Document can start loading during render, so configure its worker synchronously. +// Vite bundles the installed PDF.js worker as a local asset, including offline builds. +pdfjs.GlobalWorkerOptions.workerSrc = new URL( + "pdfjs-dist/build/pdf.worker.min.mjs", + import.meta.url, +).toString(); interface Props { /** A URL for the body the host already stored. */ @@ -59,6 +56,8 @@ export function PdfViewer({ bodyUrl, data }: Props) { return (
{ + const result = await build({ + configFile: false, + define: { "process.env.NODE_ENV": JSON.stringify("production") }, + root, + logLevel: "silent", + resolve: { + // Keep the real sizing hook without pulling unrelated UI/platform code into this fixture. + alias: { + "@yaakapp-internal/ui": path.join(root, "packages/ui/src/hooks/useContainerSize.ts"), + }, + }, + plugins: [ + { + name: "pdf-viewer-test-entry", + resolveId(id) { + if (id === "pdf-viewer-test") return id; + }, + load(id) { + if (id !== "pdf-viewer-test") return; + return ` + import { PdfViewer } from ${JSON.stringify(viewer)}; + import { pdfjs } from 'react-pdf'; + globalThis.pdfViewerTest = { PdfViewer, workerSrc: pdfjs.GlobalWorkerOptions.workerSrc, version: pdfjs.version }; + `; + }, + }, + ], + build: { + target: "esnext", + modulePreload: false, + cssCodeSplit: false, + write: false, + minify: true, + rolldownOptions: { + input: "pdf-viewer-test", + output: { format: "es", codeSplitting: false }, + }, + }, + }); + if (Array.isArray(result) || !("output" in result)) throw new Error("Expected one bundle"); + const entry = result.output.find((chunk) => chunk.type === "chunk" && chunk.isEntry); + if (!entry || entry.type !== "chunk") throw new Error("Missing viewer entry"); + + // Evaluate as a browser (no Node process), and inspect immediately, before promise callbacks. + // This checks initialization timing and assets, not rendering or native asset protocols. + const context = vm.createContext({ + URL, + navigator: { userAgent: "" }, + DOMMatrix: class {}, + window: { requestAnimationFrame: () => {}, location: new URL(`${origin}/`) }, + document: { + documentElement: { style: {} }, + createElement: () => ({ style: {} }), + addEventListener: () => {}, + }, + console, + self: { location: { href: `${origin}/index.html` } }, + }); + // Supply the module URL to the script VM without changing the emitted asset path. + vm.runInContext( + entry.code.replaceAll( + "import.meta", + `({ url: ${JSON.stringify(`${origin}/assets/entry.js`)} })`, + ), + context, + ); + const { workerSrc, version } = context.pdfViewerTest as { workerSrc: string; version: string }; + expect(workerSrc).toContain("/assets/"); + const workerUrl = new URL(workerSrc); + expect(workerUrl.protocol).toBe(new URL(origin).protocol); + expect(workerUrl.host).toBe(new URL(origin).host); + const worker = result.output.find((asset) => `/${asset.fileName}` === workerUrl.pathname); + expect(worker?.type).toBe("asset"); + if (!worker || worker.type !== "asset") + throw new Error("Worker URL missing from production assets"); + + // Resolve from React-PDF itself so a second, mismatched PDF.js installation is caught. + const require = createRequire(path.join(root, "apps/yaak-client/package.json")); + const reactPdfRequire = createRequire(require.resolve("react-pdf")); + const pdfjsDir = path.dirname(reactPdfRequire.resolve("pdfjs-dist/package.json")); + const installed = JSON.parse(await readFile(path.join(pdfjsDir, "package.json"), "utf8")); + expect(version).toBe(installed.version); + expect(Buffer.from(worker.source)).toEqual( + await readFile(path.join(pdfjsDir, "build/pdf.worker.min.mjs")), + ); + }, + 30_000, +);