/** * Client for PdfService (issue #14) — renders a PO's export page to a real PDF. * * The app's own /api/po/:id/export?format=pdf produces a print-styled HTML page; * PdfService (headless Chromium) navigates to it and returns PDF bytes. We pass a * short-lived service token so the export route serves the page without a user * session. Configured via: * PDF_SERVICE_URL — e.g. http://localhost:3005 * PDF_SERVICE_TOKEN — shared secret echoed by the export route * APP_INTERNAL_URL — base URL PdfService can reach the app at (falls back to NEXTAUTH_URL) */ export class PdfServiceError extends Error {} export function isPdfServiceConfigured(): boolean { return !!process.env.PDF_SERVICE_URL && !!process.env.PDF_SERVICE_TOKEN; } /** Render a PO to a PDF buffer via PdfService. Throws PdfServiceError on failure. */ export async function renderPoPdf(poId: string): Promise { const serviceUrl = process.env.PDF_SERVICE_URL; const token = process.env.PDF_SERVICE_TOKEN; if (!serviceUrl || !token) { throw new PdfServiceError("PDF service is not configured."); } const appBase = (process.env.APP_INTERNAL_URL ?? process.env.NEXTAUTH_URL ?? "http://localhost:3000").replace(/\/$/, ""); const exportUrl = `${appBase}/api/po/${poId}/export?format=pdf&pdf=1&svc=${encodeURIComponent(token)}`; let res: Response; try { res = await fetch(`${serviceUrl.replace(/\/$/, "")}/pdf`, { method: "POST", headers: { "Content-Type": "application/json", "x-pdf-token": token }, body: JSON.stringify({ url: exportUrl }), }); } catch (e) { throw new PdfServiceError(`PDF service unreachable: ${String(e)}`); } if (!res.ok) { throw new PdfServiceError(`PDF service returned ${res.status}`); } return Buffer.from(await res.arrayBuffer()); }