Created
September 15, 2025 10:55
-
-
Save digitaldrreamer/174b484964f600e358013f34a5cb5712 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import express from "express" | |
| import puppeteer from "puppeteer" | |
| const router = express.Router() | |
| /** | |
| * Test endpoint that generates a Puppeteer PDF of the transaction document demo page | |
| * Uses page.print() with PDF settings for high-quality output | |
| * | |
| * Query Parameters: | |
| * - type: 'completed' | 'pending' | 'failed' | 'refunded' (default: 'completed') | |
| * - format: 'A4' | 'Letter' (default: 'Letter') | |
| * - orientation: 'portrait' | 'landscape' (default: 'portrait') | |
| * - margin: 'none' | 'minimum' | 'default' (default: 'default') | |
| * - scale: number between 0.1 and 2 (default: 1) | |
| * - displayHeaderFooter: boolean (default: false) | |
| * - printBackground: boolean (default: true) | |
| */ | |
| router.get("/generate-pdf", async (req, res) => { | |
| const timer = createTimer("pdf-generation") | |
| try { | |
| // * Parse query parameters | |
| const type = req.query.type || "completed" | |
| const format = req.query.format || "Letter" | |
| const orientation = req.query.orientation || "portrait" | |
| const margin = req.query.margin || "default" | |
| const scale = parseFloat(req.query.scale || "1") | |
| const displayHeaderFooter = req.query.displayHeaderFooter === "true" | |
| const printBackground = req.query.printBackground !== "false" | |
| // * Validate parameters | |
| const validTypes = ["completed", "pending", "failed", "refunded"] | |
| const validFormats = ["A4", "Letter"] | |
| const validOrientations = ["portrait", "landscape"] | |
| const validMargins = ["none", "minimum", "default"] | |
| if (!validTypes.includes(type)) { | |
| return res.status(400).json({ | |
| success: false, | |
| error: `Invalid type. Must be one of: ${validTypes.join(", ")}`, | |
| }) | |
| } | |
| if (!validFormats.includes(format)) { | |
| return res.status(400).json({ | |
| success: false, | |
| error: `Invalid format. Must be one of: ${validFormats.join(", ")}`, | |
| }) | |
| } | |
| if (!validOrientations.includes(orientation)) { | |
| return res.status(400).json({ | |
| success: false, | |
| error: `Invalid orientation. Must be one of: ${validOrientations.join(", ")}`, | |
| }) | |
| } | |
| if (!validMargins.includes(margin)) { | |
| return res.status(400).json({ | |
| success: false, | |
| error: `Invalid margin. Must be one of: ${validMargins.join(", ")}`, | |
| }) | |
| } | |
| if (scale < 0.1 || scale > 2) { | |
| return res.status(400).json({ | |
| success: false, | |
| error: "Scale must be between 0.1 and 2", | |
| }) | |
| } | |
| // * Get the base URL for the demo page | |
| const baseUrl = `${req.protocol}://${req.get("host")}` | |
| const demoUrl = `${baseUrl}/demo/transaction-document?type=${type}` | |
| console.log(`Generating PDF for transaction type: ${type}`) | |
| console.log(`Demo URL: ${demoUrl}`) | |
| // * Launch Puppeteer | |
| const browser = await puppeteer.launch({ | |
| headless: "new", | |
| executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || undefined, | |
| args: [ | |
| "--no-sandbox", | |
| "--disable-setuid-sandbox", | |
| "--disable-dev-shm-usage", | |
| "--disable-accelerated-2d-canvas", | |
| "--no-first-run", | |
| "--no-zygote", | |
| "--disable-gpu", | |
| "--disable-web-security", | |
| "--disable-features=VizDisplayCompositor", | |
| "--single-process", | |
| "--disable-background-timer-throttling", | |
| "--disable-backgrounding-occluded-windows", | |
| "--disable-renderer-backgrounding", | |
| "--disable-extensions", | |
| "--disable-plugins", | |
| "--disable-images", | |
| "--disable-default-apps", | |
| "--disable-sync", | |
| "--disable-translate", | |
| "--hide-scrollbars", | |
| "--mute-audio", | |
| "--no-default-browser-check", | |
| "--no-pings", | |
| "--disable-logging", | |
| "--disable-background-networking", | |
| "--disable-client-side-phishing-detection", | |
| "--disable-component-extensions-with-background-pages", | |
| "--disable-domain-reliability", | |
| "--disable-features=TranslateUI", | |
| "--disable-hang-monitor", | |
| "--disable-ipc-flooding-protection", | |
| "--disable-popup-blocking", | |
| "--disable-prompt-on-repost", | |
| "--disable-web-resources", | |
| "--metrics-recording-only", | |
| "--safebrowsing-disable-auto-update", | |
| "--enable-automation", | |
| "--password-store=basic", | |
| "--use-mock-keychain", | |
| ], | |
| timeout: 30000, | |
| protocolTimeout: 30000, | |
| }) | |
| try { | |
| const page = await browser.newPage() | |
| await page.setViewport({ | |
| width: 1200, | |
| height: 800, | |
| deviceScaleFactor: 2, | |
| }) | |
| await page.evaluateOnNewDocument(() => { | |
| const style = document.createElement("style") | |
| style.textContent = ` | |
| body { background: white !important; } | |
| main { background: white !important; } | |
| .document-container { background: white !important; } | |
| ` | |
| document.head.appendChild(style) | |
| }) | |
| await page.goto(demoUrl, { | |
| waitUntil: "networkidle0", | |
| timeout: 30000, | |
| }) | |
| await page.waitForSelector(".document-container", { timeout: 10000 }) | |
| await new Promise((resolve) => setTimeout(resolve, 1000)) | |
| const pdfBuffer = await page.pdf({ | |
| format, | |
| landscape: orientation === "landscape", | |
| margin: { | |
| top: margin === "none" ? "0" : margin === "minimum" ? "0.2in" : "0.5in", | |
| right: margin === "none" ? "0" : margin === "minimum" ? "0.2in" : "0.5in", | |
| bottom: margin === "none" ? "0" : margin === "minimum" ? "0.2in" : "0.5in", | |
| left: margin === "none" ? "0" : margin === "minimum" ? "0.2in" : "0.5in", | |
| }, | |
| scale, | |
| displayHeaderFooter, | |
| printBackground, | |
| preferCSSPageSize: false, | |
| tagged: true, | |
| outline: false, | |
| omitBackground: !printBackground, | |
| timeout: 30000, | |
| }) | |
| const duration = timer.end() | |
| console.log(`PDF generated successfully in ${duration.toFixed(2)}ms`) | |
| res.setHeader("Content-Type", "application/pdf") | |
| res.setHeader( | |
| "Content-Disposition", | |
| `attachment; filename="transaction-${type}-${Date.now()}.pdf"` | |
| ) | |
| res.setHeader("Content-Length", pdfBuffer.length.toString()) | |
| res.setHeader("Cache-Control", "no-cache") | |
| return res.send(pdfBuffer) | |
| } finally { | |
| await browser.close() | |
| } | |
| } catch (error) { | |
| const duration = timer.end() | |
| console.error("PDF generation failed:", error) | |
| return res.status(500).json({ | |
| success: false, | |
| error: error.message, | |
| duration: `${duration.toFixed(2)}ms`, | |
| }) | |
| } | |
| }) | |
| /** | |
| * Performance timer utility | |
| */ | |
| function createTimer(label) { | |
| const startTime = performance.now() | |
| return { | |
| end: () => { | |
| const duration = performance.now() - startTime | |
| console.log(`Performance: ${label} - ${duration.toFixed(2)}ms`) | |
| return duration | |
| }, | |
| } | |
| } | |
| export default router |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment