import express from "express" export const htmlTemplateRoute = express.Router() import { mimeToExtension, templateOption } from "./report-template" // import fs from "fs" //import { chromium } from 'playwright' import puppeteer,{PDFOptions} from 'puppeteer' import Handlebars from 'handlebars' import e from "express" //import { createReport } from "docx-templates" // แก้ package.json ของ LibreOfficeFileConverter // https://github.com/microsoft/TypeScript/issues/52363#issuecomment-1659179354 //import { LibreOfficeFileConverter } from "libreoffice-file-converter" const TEMPLATE_FOLDER_NAME = "templates/html" const width_px = 1200; //TODO read from htmlOption /** * docxTemplate Uses docx-template to convert input data and template to output buffer. * SPA and lazy load page may not fully render(eg. pantip.com). * You have to handle exception throw by function * handlebars template support only content from Buffer * @param {Buffer|String} t template in buffer format or url to web page * @param {templateOption} tdata Template Information in JSON format * @param {String} outputMediaType output extension, support pdf, jpeg, png * @return {Promise} output buffer after apply template. */ export async function htmlTemplateX(t: Buffer | String, tdata: templateOption, outputMediaType: string = "pdf"): Promise { try { if (!["pdf", "jpeg", "png"].find((e) => e === outputMediaType)) { throw "FormatError" } const browser = await puppeteer.launch({ headless: true,args: ['--no-sandbox'] }); const page = await browser.newPage(); page.setDefaultNavigationTimeout(120000); await page.setViewport({ width: width_px, height: 800, deviceScaleFactor: 2, isMobile: false }); if (typeof t === 'string') { await page.goto(t, { waitUntil: 'networkidle0' }); } else { if (tdata.data) { const template = Handlebars.compile(t.toString()); const html = template(tdata.data); await page.setContent(html); } else { await page.setContent(t.toString()) } } /* // try to load whole page let x = await page.evaluate(async (tdata) => { const scrollableSection = (tdata.htmlOption?.querySelector && document.querySelector(tdata.htmlOption.querySelector)) ? document.querySelector(tdata.htmlOption.querySelector) : document.body if (scrollableSection) { const childElement = scrollableSection.firstElementChild; let scrollPosition = 0; let viewportHeight = window.innerHeight; if (childElement) while (scrollPosition < childElement.scrollHeight) { scrollableSection.scrollBy(0, viewportHeight); await new Promise(resolve => setTimeout(resolve, 500)); scrollPosition += viewportHeight; } return scrollPosition } return 0 }, tdata); //console.log("scrollPosition=" + x) */ //find real page height const totalHeight = await page.evaluate(async (tdata) => { let scrollableSection = (tdata.htmlOption?.querySelector && document.querySelector(tdata.htmlOption.querySelector) && document.querySelector(tdata.htmlOption.querySelector)) ? document.querySelector(tdata.htmlOption.querySelector) : null const childElement = scrollableSection? scrollableSection: document.body if(scrollableSection ==null) scrollableSection = document.body //const childElement = scrollableSection.firstElementChild; let scrollPosition = 0; let viewportHeight = window.innerHeight; while (scrollPosition < childElement.scrollHeight) { scrollableSection.scrollBy(0, viewportHeight); await new Promise(resolve => setTimeout(resolve, 500)); scrollPosition += viewportHeight; } return childElement.scrollHeight }, tdata); if (!totalHeight) { throw new Error(`Unable to determine the page height ${totalHeight}. The selector may not correct or no body tag`); } else { console.log("Page height adjusted to:", totalHeight); } console.log("set viewport ") await page.setViewport({ width: width_px, height: totalHeight, deviceScaleFactor: 2, isMobile: false }); ///// output to photo end here if (outputMediaType === "png" || outputMediaType === "jpeg") { const photoBuffer = await page.screenshot({ // path: 'url_pup.png', fullPage: true, type: outputMediaType // 'webp' }) await browser.close(); return photoBuffer } ///// output to PDF //TODO overide option from htmlTemplateOption let pdfOption:PDFOptions = { // path: './url_prop.pdf', // format:"A4", width: width_px, height: totalHeight, printBackground: true, scale: 1, displayHeaderFooter: false, margin: { top: 5, right: 5, bottom: 5, left: 5 } } const buffer = await page.pdf(pdfOption); await browser.close(); return buffer } catch (e) { //console.log(e) throw e } } /** javascript-obfuscator:disable * @swagger * /api/v1/report-template/html: * post: * summary: แปลหน้าเวปไปเป็น pdf , png, jpeg (ฟีเจอร์ html template ด้วย handlebars ยังไม่เสร็จ) ค่า template เป็น url ของหน้าเวปที่ต้องการ, reportName เป็นชื่อไฟล์ที่ต้องการ * tags: [report-template] * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/templateOption' * example: * template: https://bma-dashboard.frappet.synology.me/d/ANtkJay4z/4Lic4Li54LmJ4Lie4Li04LiB4Liy4Lij?orgId=1&kiosk * reportName: html-report * htmlOption: {"querySelector": ".scrollbar-view"} * responses: * 201: * description: เอกสารถูกสร้างขึ้น * content: * application/pdf: * schema: * type: string * format: binary * image/png: * schema: * type: string * format: binary * image/jpeg: * schema: * type: string * format: binary * 400: * description: format error * 500: * description: Server error * */ htmlTemplateRoute.post("/", async function (req, res) { try { if (!req.headers["content-type"] || !req.headers["accept"]) throw new Error("Require header content-type, accept") let inputType = mimeToExtension(req.headers["content-type"])// application/json let outputMediaType = mimeToExtension(req.headers["accept"]) let buffer = await htmlTemplateX(req.body.template, req.body, outputMediaType) res.statusCode = 201 res.setHeader("Content-Type", req.headers["accept"]) res.setHeader("Content-Disposition", `attachment;filename=${req.body.reportName}.${outputMediaType}`) res.setHeader("Content-Length", buffer.length) res.end(buffer) } catch (ex) { if(ex instanceof SyntaxError){ res.statusCode = 400 res.statusMessage = ex.message res.end(res.statusMessage) console.error("report-template/html: ", ex) }else{ res.statusCode = 500 res.statusMessage = "Internal Server Error during POST report-template/html" res.end(res.statusMessage) console.error("report-template/html: ", ex) } } })