UNPKG

@nzz/q-server

Version:

**Maintainer**: [Franco Gervasi](https://github.com/fgervasi)

291 lines (259 loc) 7.91 kB
const Boom = require("@hapi/boom"); const Joi = require("../../helper/custom-joi.js"); const getScreenshotImage = require("./helpers.js").getScreenshotImage; const getScreenshotInfo = require("./helpers.js").getScreenshotInfo; const getInnerWidth = require("./helpers.js").getInnerWidth; const queryFormat = { target: Joi.string().required(), width: Joi.number().required(), dpr: Joi.number().optional(), background: Joi.string().optional(), padding: Joi.string() .regex(/^$|^(([0-9.]+)(px|em|ex|%|in|cm|mm|pt|pc|vh|vw)?([ ])?){1,4}$/) .optional(), wait: Joi.optional(), toolRuntimeConfig: Joi.object().optional(), }; async function getScreenshotResponse(server, h, params, item) { const target = server.settings.app.targets.get(`/${params.target}`); if (!target) { throw Boom.badRequest("no such target"); } if (target.type !== "web") { throw Boom.badRequest("the target is not of type web"); } const toolRuntimeConfig = params.toolRuntimeConfig || {}; // check if there is a given width, if so, send it in toolRuntimeConfig to the rendering-info endpoint const width = getInnerWidth(params.width, params.padding); if (width) { toolRuntimeConfig.size = { width: [ { value: width, unit: "px", comparison: "=", }, ], }; } let response; try { response = await server.inject({ method: "POST", url: `/rendering-info/${params.target}`, payload: { toolRuntimeConfig: toolRuntimeConfig, item: item, ignoreInactive: true, }, }); } catch (err) { if (err.stack) { server.log(["error"], err.stack); } if (err.isBoom) { throw err; } else { server.log(["error"], err.message); } throw err; } if (response.statusCode !== 200) { throw new Boom.Boom( `Failed to get renderingInfo to load in headless chrome for screenshot for ${item.tool} and ${params.target} with the error: ${response.statusMessage}`, { statusCode: response.statusCode, } ); } const renderingInfo = JSON.parse(response.payload); let scripts = await server.methods.plugins.q.screenshot.getScripts( renderingInfo ); let stylesheets = await server.methods.plugins.q.screenshot.getStylesheets( renderingInfo ); // add scripts and stylesheets from publication config if (Array.isArray(target.context.scripts)) { scripts = target.scripts.context.concat(scripts); } if (Array.isArray(target.context.stylesheets)) { stylesheets = target.context.stylesheets.concat(stylesheets); } const config = { width: params.width, dpr: params.dpr || 1, padding: params.padding || "0", background: params.background, }; if (params.wait !== undefined) { if (!Number.isNaN(parseInt(params.wait))) { config.waitBeforeScreenshot = parseInt(params.wait); } } config.qId = item._id; config.qTool = item.tool; if (params.format === "png") { let screenshotBuffer; try { screenshotBuffer = await getScreenshotImage( `${server.info.protocol}://localhost:${server.info.port}/screenshot/empty-page.html`, renderingInfo.markup, scripts, stylesheets, config, server ); } catch (err) { if (err.stack) { server.log(["error"], err.stack); } if (err.isBoom) { throw err; } else { server.log(["error"], err.message); } throw err; } const imageResponse = h.response(screenshotBuffer); imageResponse.type("image/png"); return imageResponse; } else if (params.format === "json") { let screenshotInfo; try { screenshotInfo = await getScreenshotInfo( `${server.info.protocol}://localhost:${server.info.port}/screenshot/empty-page.html`, renderingInfo.markup, scripts, stylesheets, config, server ); } catch (err) { if (err.stack) { server.log(["error"], err.stack); } if (err.isBoom) { throw err; } else { server.log(["error"], err.message); } throw err; } const infoResponse = h.response(screenshotInfo); return infoResponse; } } module.exports = { getRoutes: function (cacheControlHeader) { return [ { path: "/screenshot/{id}.{format}", method: "GET", options: { validate: { params: { id: Joi.string().required(), format: Joi.string().valid("json", "png"), }, query: queryFormat, options: { allowUnknown: true, }, }, tags: ["api"], }, handler: async (request, h) => { // Cancelling the whole request chain is painful without Node 15+, but should be done during refactoring. // See the following answer: https://stackoverflow.com/a/37642079 request.raw.req.on("aborted", () => { return h.response().code(499); }); const item = await request.server.methods.db.item.getById({ id: request.params.id, ignoreInactive: request.query.ignoreInactive, session: { credentials: request.auth.credentials, artifacts: request.auth.artifacts, }, }); const screenshotConfig = Object.assign({}, request.query, { format: request.params.format, }); let response; try { response = await getScreenshotResponse( request.server, h, screenshotConfig, item ); } catch (err) { if (err.stack) { request.server.log(["error"], err.stack); } if (err.isBoom) { throw err; } else { request.server.log(["error"], err.message); } throw err; } response.header("cache-control", cacheControlHeader); return response; }, }, { path: "/screenshot.{format}", method: "POST", options: { validate: { params: { format: Joi.string().valid("json", "png"), }, payload: { item: Joi.object().required(), toolRuntimeConfig: Joi.object().optional(), }, query: queryFormat, options: { allowUnknown: true, }, }, tags: ["api"], }, handler: async (request, h) => { // Cancelling the whole request chain is painful without Node 15+, but should be done during refactoring. // See the following answer: https://stackoverflow.com/a/37642079 request.raw.req.on("aborted", () => { return h.response().code(499); }); const screenshotConfig = Object.assign({}, request.query, { format: request.params.format, }); if (request.payload.toolRuntimeConfig) { screenshotConfig.toolRuntimeConfig = Object.assign( screenshotConfig.toolRuntimeConfig || {}, request.payload.toolRuntimeConfig ); } const response = await getScreenshotResponse( request.server, h, screenshotConfig, request.payload.item ); response.header("cache-control", cacheControlHeader); return response; }, }, { path: "/screenshot/empty-page.html", method: "GET", handler: (request, h) => { return "<!DOCTYPE html><html><head></head><body></body></html>"; }, }, ]; }, };