UNPKG

jsreport-chrome-pdf

Version:

jsreport recipe which is rendering pdf from html using headless chrome

187 lines (153 loc) 5.17 kB
const conversion = require('./conversion') module.exports = ({ reporter, puppeteer, options }) => { const pool = [] const tasksQueue = [] const { numberOfWorkers } = options if (numberOfWorkers < 1) { throw new Error('"numberOfWorkers" must be a number greater or equal than 1') } const execute = async ({ htmlUrl, strategy, launchOptions, conversionOptions, req, imageExecution, allowLocalFilesAccess }) => { let browserInfo let page let releaseBrowser let crashError = false let timeoutError = false try { const timeout = reporter.getAvailableRenderTimeout(req, options.timeout) const result = await conversion({ reporter, getBrowser: async () => { const allocation = await allocateBrowser( puppeteer, pool, tasksQueue, { numberOfWorkers, launchOptions } ) browserInfo = allocation.browserInfo releaseBrowser = allocation.releaseBrowser return browserInfo.instance }, htmlUrl, strategy, timeout, req, allowLocalFilesAccess, imageExecution, options: conversionOptions }) page = result.page return { type: result.type, content: result.content } } catch (err) { if (err.workerCrashed) { crashError = true } else if (err.workerTimeout) { timeoutError = true } throw err } finally { if (releaseBrowser) { releaseBrowser() } if ((crashError || timeoutError) && browserInfo) { recycleBrowser(puppeteer, browserInfo, launchOptions).catch(() => {}) } else if (page && !page.isClosed()) { await page.close() } tryFlushTasksQueue(puppeteer, pool, tasksQueue) } } execute.kill = async () => { const op = [] pool.forEach(async (browserInfo) => { if (browserInfo.recycling) { op.push(browserInfo.recycling.then(() => { if (browserInfo.instance) { return browserInfo.instance.close() } })) } else if (browserInfo.instance) { op.push(browserInfo.instance.close()) } }) return Promise.all(op) } return execute } async function createBrowser (puppeteer, browserInfo, launchOptions) { browserInfo.instance = await puppeteer.launch(launchOptions) browserInfo.instance.on('disconnected', () => { // clean references when the browser is no longer active (it has crashed or closed) browserInfo.instance = null }) } async function allocateBrowser (puppeteer, pool, tasksQueue, options) { const { numberOfWorkers, launchOptions } = options let browserInfo if (pool.length < numberOfWorkers) { browserInfo = { instance: undefined, isBusy: true } pool.push(browserInfo) await createBrowser(puppeteer, browserInfo, launchOptions) } else { // simple round robin balancer across browsers, // get the first available browser from the list const availableBrowserIndex = pool.findIndex((b) => b.isBusy === false) if (availableBrowserIndex !== -1) { browserInfo = pool.splice(availableBrowserIndex, 1)[0] browserInfo.isBusy = true // ..and then if browser found then make it the last item in the list // to continue the rotation pool.push(browserInfo) // we check that instance exists, theorically there will be always an instance, // however there is small chance that an error while recycling a chrome instance happens // and we end with .instance being null, in which case we need to create it here if (browserInfo.instance == null) { await createBrowser(puppeteer, browserInfo, launchOptions) } } else { return new Promise((resolve, reject) => { tasksQueue.push({ resolve, reject, options }) }) } } return { browserInfo, releaseBrowser: () => { browserInfo.isBusy = false } } } async function recycleBrowser (puppeteer, browserInfo, launchOptions) { browserInfo.isBusy = true let resolveRecycling browserInfo.recycling = new Promise((resolve) => { resolveRecycling = resolve }) if (browserInfo.instance) { await browserInfo.instance.close() } // clean the property before trying to get new instance, this let us // create the instance later if for some reason the instance can not be // created during recycling browserInfo.instance = null try { await createBrowser(puppeteer, browserInfo, launchOptions) } finally { if (resolveRecycling) { resolveRecycling() } browserInfo.isBusy = false } } function tryFlushTasksQueue (puppeteer, pool, tasksQueue) { if (tasksQueue.length === 0) { return } const task = tasksQueue.shift() allocateBrowser(puppeteer, pool, tasksQueue, task.options).then(task.resolve).catch(task.reject) }