node-image-from-html
Version:
This single-dependency library provides a simple API to render HTML content using <a href="https://github.com/puppeteer/puppeteer">Puppeteer</a> (A headless chromium brower) into png and jpeg images, insuring security and efficiency in the process. Writte
111 lines (110 loc) • 4.26 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BrowserHandler = void 0;
const puppeteer_1 = __importDefault(require("puppeteer"));
const ExtendedPage_js_1 = require("./ExtendedPage.js");
/**
* @class BrowserHandler
* This class is used to handle the browser and pages, allocating pages for use and releasing them back to the handler when idle.
* To start rendering, <BrowserHandler>.start() must first be called.
*/
class BrowserHandler {
/**
* Constructor for the BrowserHandler class.
* @param options The options for the BrowserHandler.
*/
constructor(options = {}) {
this._browser = null;
this.pages = [];
this.options = options;
}
/**
* Returns the underlying Puppeteer browser instance, throwing an exception if it's not started.
*/
get browser() {
if (this._browser == null) {
throw "Puppeteer Engine not started. Please await <BrowserHandler>.launch() before rendering.";
}
return this._browser;
}
/**
* Starts the BrowserHandler engine & launches a new browser.
* @param puppeteerOptions The options for the launched instance of Puppeteer.
*/
async start(puppeteerOptions = {}) {
if (this._browser != null) {
throw "Puppeteer Engine already started!";
}
this._browser = await puppeteer_1.default.launch(puppeteerOptions);
let spawnTasks = [];
for (let x = 0; x < (this.options.concurrency ? this.options.concurrency : 1); x++) {
spawnTasks.push(this.browser.newPage());
}
this.pages = await Promise.all(spawnTasks)
.then((pages) => pages.map(p => new ExtendedPage_js_1.ExtendedPage(p)));
// Apply the network/js settings.
for (const epage of this.pages) {
if (this.options.disableJavaScript) {
epage.page.setJavaScriptEnabled(false);
}
if (this.options.disableNetwork) {
epage.page.setRequestInterception(true);
}
}
}
/**
* Returns a page, waiting for one to be free if necessary.
* @returns A promise that resolves to an ExtendedPage object.
*/
async waitForPage() {
let elapsed = 0;
while (true) {
for (const page of this.pages) {
if (page.inUse == false) {
page.inUse = true;
return page;
}
}
await new Promise(resolve => setTimeout(resolve, 100));
elapsed += 100;
if (elapsed > (this.options.timeout ? this.options.timeout : 15000)) {
throw "Timeout waiting for page to become available. Perhaps attempt a higher concurrency?";
}
}
}
/**
* Closes the browser and cleans up any resources.
*/
async dispose() {
await this.browser.close();
}
/**
* Renders an HTML string to an image format using the first available page, waiting for one to be free if necessary.
* @param html HTML to render
* @param screenshotOptions The options for the screenshot.
* @returns Your rendered HTML as a Buffer.
*/
async render(html, screenshotOptions) {
const epage = await this.waitForPage();
// Uri format supports 2mb of text.
const uri = `data:text/html,${encodeURIComponent(html)}`;
await epage.page.goto(uri);
let target;
if (screenshotOptions.selector) {
target = await epage.page.$(screenshotOptions.selector);
if (!target) {
throw "Could not find element with selector: " + screenshotOptions.selector;
}
}
else {
target = await epage.page.mainFrame();
}
const screenshot = await target.screenshot(screenshotOptions);
epage.inUse = false;
return screenshot;
}
}
exports.BrowserHandler = BrowserHandler;