Skip to main content

PuppeteerHandler — Browser Automation & PDF Generation

PuppeteerHandler is a Puppeteer wrapper in io-oidis-hub providing two core capabilities: browser automation via the Extract API and PDF generation via the Export API and the Print Pipeline. It is available to all Hub-based projects.

Two Use Cases at a Glance​

Use CaseAPIPackageExample
Browser automation (scraping, portal login, data export)Extract()io-oidis-hubEDC portal automation in IRMA
PDF generation (HTML to PDF, label printing)Export() + Print Pipelineio-oidis-hub + io-oidis-hub-guiDelivery stickers, report PDFs

Browser Automation — Extract API​

Extract Method​

public async Extract(
$input : IPuppeteerExtractOptions,
$convertor : ($page : any) => Promise<any>
) : Promise<any>

Opens a URL or renders inline HTML in a Chromium browser page, then hands the Puppeteer Page object to a custom converter function. The converter interacts with the page (evaluate DOM, click buttons, download files) and returns the result.

IPuppeteerExtractOptions​

PropertyTypeDefaultDescription
urlstring""URL to navigate to. Mutually exclusive with html.
htmlstring""Inline HTML content to render. Mutually exclusive with url.
verbosebooleanfalseForward browser console and pageerror events to LogIt.
cookiesany[][]Cookies to set before navigation (Puppeteer setCookie format).
debugbooleanfalseRun headed (visible browser window) instead of headless.

How Extract Works​

  1. Merges $input with defaults (empty url/html, verbose false, debug false).
  2. Acquires a page handler from the pool via getHandler(). If the requested page is busy, waits up to maxWaitTime iterations (polling with incremental delays).
  3. URL mode: sets cookies if provided, navigates to URL with waitUntil: ["load", "networkidle0"]. Skips navigation if the page already has the same URL loaded.
  4. Inline HTML mode: sets page content via setContent() with waitUntil: "networkidle0".
  5. Calls the converter function with the Puppeteer Page object.
  6. Marks the page handler as no longer processing.
  7. On error: logs the exception, attempts graceful browser.close(), falls back to process.kill(), resets browser to null for re-launch on next call.
  8. Returns null on any failure (timeout or exception).

Example — Portal Automation​

This pattern is used in IRMA's EDCHelper for automated data export from the Czech EDC energy portal:

export class EDCHelper extends BaseConnector {
private readonly browser : PuppeteerHandler;

constructor() {
super();
// Per-instance browser (not singleton) — isolated browser state
this.browser = new PuppeteerHandler();
this.browser.setConfig({proxy: {enabled: false}, urlThreads: 0});
}

private getExtractOptions() : IPuppeteerExtractOptions {
// Tie headed/headless mode to CLI --debug flag
return {debug: Loader.getInstance().getProgramArgs().IsDebug(), verbose: false};
}

private async logIn() : Promise<boolean> {
return await this.browser.Extract(JsonUtils.Extend({
url: "https://portal.edc-cr.cz/"
}, this.getExtractOptions()), async ($page : any) : Promise<boolean> => {
await $page.waitForSelector("#loginButton");
await $page.evaluate(($user : string, $pass : string) => {
(<HTMLInputElement>document.getElementById("username")).value = $user;
(<HTMLInputElement>document.getElementById("password")).value = $pass;
(<HTMLElement>document.getElementById("loginButton")).click();
}, config.user, config.password);
return true;
});
}
}
Per-instance vs Singleton

For portal automation, use new PuppeteerHandler() (per-instance) rather than the singleton. This gives you isolated browser state (cookies, sessions) and you typically set urlThreads: 0 since you only need one page.

PDF Generation — Export API​

Export Method​

public async Export($input : IPuppeteerExportOptions, $options? : IPDFOptions) : Promise<Buffer>

High-level wrapper around Extract() that renders HTML or a URL to PDF (or screenshot). Returns the raw PDF binary as a Buffer.

IPuppeteerExportOptions​

Extends IPuppeteerExtractOptions with:

PropertyTypeDefaultDescription
formatstring"pdf"Output format: "pdf" or "screenshot".

Plus all inherited properties from IPuppeteerExtractOptions.

IPDFOptions​

PropertyTypeDefaultDescription
namestring—Output filename.
formatstring"A4"Paper format (when width/height not set).
displayHeaderFooterbooleanfalseShow header/footer in PDF.
landscapebooleanfalseLandscape orientation.
marginIPDFMargin{top:0, right:0, bottom:0, left:0}Page margins (CSS units or mm).
widthstring—Page width (e.g., "210mm").
heightstring—Page height (e.g., "297mm").
languagestring"en"HTML lang attribute.
cssstring""Custom CSS appended after Bootstrap base styles.
cookiesany[][]Cookies for URL-based rendering.
inputsany[]Form inputs to populate before triggering print ({name, value} pairs).
asStreamboolean—Return as base64 stream vs raw buffer.
licensestring(auto-generated)BSD-3-Clause copyright header injected into HTML.

Auto-Injected Bootstrap CSS​

When rendering inline HTML ($input.html), Export automatically wraps it in a full HTML document with Bootstrap 5 base styles injected via a <style> block. This includes:

  • Box-sizing reset, font stack (Segoe UI, Roboto, Arial)
  • Bootstrap grid classes: .container-fluid, .row, .col, .col-2, .col-xl-4
  • Flexbox utilities: .d-flex, .flex-column, .h-100, .mt-auto, .justify-content-center, .text-center
  • Table styling with page-break controls
  • .d-none utility

This means inline HTML can use Bootstrap layout classes without importing any stylesheet. Custom CSS can be appended via $options.css.

Example — Simple HTML to PDF​

Using the Convert.HtmlToPDF() convenience wrapper (recommended for simple cases):

import { Convert } from "@io-oidis-hub/Io/Oidis/Hub/Utils/Convert.js";

const pdfBuffer : Buffer = await Convert.HtmlToPDF(
"<h1>Report Title</h1><p>Content here</p>",
{width: "210mm", height: "297mm"}
);

Using PuppeteerHandler.Export() directly for more control:

const pdf : Buffer = await PuppeteerHandler.getInstanceSingleton().Export({
html: "<div class='container-fluid'><div class='row'><div class='col'>Column</div></div></div>"
}, {
width: "210mm",
height: "297mm",
language: "cs",
css: ".custom { color: red; }"
});

The Print Pipeline is the complete server-side PDF generation workflow used when the application needs to render a full panel or page as PDF — not just raw HTML strings.

Architecture​

Frontend                              Backend
──────── ───────
PrintBinding PrintManager
│ │
├── PrintToPDF() ├── @Extern GeneratePDF()
│ └── PrintConnector └── Convert.HtmlToPDF()
│ .GeneratePDF(html) ──WS──> └── PuppeteerHandler.Export({html})
│ │
└── POST /print ────────────REST──> └── @Route Print()
├── Schema validation + throttling
├── PuppeteerHandler.Export({url})
│ navigates to /#/print/{user}/{token}
│ waits #printStart → clicks → waits #printComplete
└── Returns PDF as StreamResponse

PrintManager (Backend)​

Location: io-oidis-hub/Utils/PrintManager.ts

WebSocket methods (via @Extern()):

  • GeneratePDF($input : string, $options? : IPDFOptions) — converts HTML string to PDF via Convert.HtmlToPDF(), returns IExportStatus with base64-encoded PDF data
  • getPrintOptions($id : string) — retrieves stored print request settings by ID

REST endpoints (via @Route()):

  • POST /print and POST /v1/print — full server-side rendering via StreamResponse

Throttling:

  • apiConfig.maxPrintRequests — maximum concurrent print requests (returns HTTP 403 if exceeded)
  • apiConfig.maxIdsPerPrintRequests — maximum item IDs per single request

Format validation:

  • PrintType.A4 — forces 210x297mm, limit 8 items per page
  • PrintType.Own — allows custom dimensions

Default filename: Print_DDMMYYYY_HHMMSS.pdf

PrintConnector (Frontend)​

Location: io-oidis-hub-gui/Connectors/PrintConnector.ts

Singleton WebSocket client mapping to Io.Oidis.Hub.Utils.PrintManager:

const status : IExportStatus = await PrintConnector.getClient().GeneratePDF(htmlString, {
width: "210mm",
height: "297mm"
});

PrintBinding (Frontend UI)​

Location: io-oidis-hub-gui/Bindings/PrintBinding.ts

Generic class PrintBinding<PrintItem, Data> managing the print UI flow:

  • Bind() — connects UI containers and callbacks, optionally creates #printStart/#printComplete DOM triggers
  • PrintToPDF() — renders items into print layout, calls PrintConnector.GeneratePDF(), opens result via FileFetch.Open()
  • PrintPreview() — renders items and injects @page CSS for browser print dialog
  • printItems() — internal layout engine using PrintPage, PrintRow, PrintContainer with auto-scaling

#printStart / #printComplete Protocol​

When the backend needs to render a full application panel as PDF (not just raw HTML), it uses a handshake protocol:

  1. PrintBinding injects hidden <input id="printId"> and <span id="printStart"> into the DOM
  2. Backend PuppeteerHandler.Export({url}) navigates to /#/print/{userName}/{token}
  3. Backend fills #printId input value and clicks #printStart
  4. Frontend onclick handler fires: authenticates, loads data, renders print items
  5. Frontend appends <span id="printComplete"> when done
  6. Backend detects #printComplete, renders the page to PDF

Browser Management​

Singleton vs Per-Instance​

ModeConstructorUse case
SingletonPuppeteerHandler.getInstanceSingleton()PDF generation (PrintManager, Convert.HtmlToPDF()) — shared browser for the application
Per-instancenew PuppeteerHandler()Portal automation — isolated cookies, sessions, browser state

Page Pooling​

On first use, PuppeteerHandler launches Chromium and creates 1 + N page instances:

  • 1 page reserved for inline HTML (marked content: "inlineCode")
  • N pages for URL navigation (configurable via urlThreads)

URL pages are reused: if a page already has the requested URL loaded and is not processing, navigation is skipped. When all URL pages are busy, the handler picks the first non-processing page (or falls back to page index 1).

If only urlThreads: 0, only the single inline page is created — suitable for portal automation where you navigate sequentially.

Lazy Launch and Error Recovery​

  • Browser is launched lazily on first Extract() or Export() call
  • On error during extraction: attempts browser.close() → process.kill(browser.process().pid) → sets browser = null
  • Setting browser = null ensures a fresh browser is launched on the next call
  • No explicit public close() or destroy() method — lifecycle is error-driven

Chrome Binary Management — Init()​

public async Init($onProgress? : ($downloadedBytes : number, $totalBytes : number) => void) : Promise<string>

Downloads and verifies the Chromium binary via @puppeteer/browsers. Called automatically on first page creation.

Platform detection:

OSArchitecturePlatform ID
Windows64-bitwin64
Windows32-bitwin32
macOSIntelmac
macOSApple Siliconmac_arm
Linuxanylinux

Cache directory resolution (priority order):

  1. puppeteer.cacheDir from config (explicit override)
  2. {ProjectBase}/../../appData/puppeteer (when running from /build/target)
  3. {TempPath}/{ProjectName}/puppeteer (fallback)

Progress callback: optional $onProgress($downloadedBytes, $totalBytes) for UI feedback during download.

Configuration​

commons.jsonp — puppeteer section​

{
"puppeteer": {
"enabled": true,
"urlThreads": 4,
"maxWaitTime": 20,
"chromeBuildId": "127",
"proxy": {
"enabled": false,
"location": null
},
"cacheDir": null
}
}
KeyTypeDefaultDescription
enabledbooleantrueEnable/disable PuppeteerHandler initialization at startup.
urlThreadsnumber4Number of dedicated URL pages in the browser pool.
maxWaitTimenumber20Max polling iterations to wait for a busy page to become available.
chromeBuildIdstring"127"Chrome version to download via @puppeteer/browsers.
proxy.enabledbooleanfalseRoute browser traffic through a proxy server.
proxy.locationstringnullProxy server address (e.g., "socks5://127.0.0.1:9050").
cacheDirstringnullCustom Chrome binary cache directory (auto-resolved if null).

Runtime override via setConfig():

const browser : PuppeteerHandler = new PuppeteerHandler();
browser.setConfig({proxy: {enabled: false}, urlThreads: 0});

commons.jsonp — apiConfig (Print throttling)​

{
"apiConfig": {
"maxPrintRequests": 10,
"maxIdsPerPrintRequests": 100
}
}
KeyTypeDescription
maxPrintRequestsnumberMaximum concurrent print requests before returning HTTP 403.
maxIdsPerPrintRequestsnumberMaximum item IDs per single print request.

Debug Mode​

When debug: true in IPuppeteerExtractOptions:

  • Browser launches in headed mode (headless: false, --start-maximized, defaultViewport: null)
  • A visible browser window opens — useful for debugging page interactions

When debug: false (default): headless mode — no visible window.

Typical pattern in solution projects — tie debug to ProgramArgs.IsDebug() so that node loader.min.js start --debug automatically switches to headed mode:

private getExtractOptions() : IPuppeteerExtractOptions {
return {debug: Loader.getInstance().getProgramArgs().IsDebug(), verbose: false};
}

See also: ProgramArgs — --debug flag.

Chrome launch flags (always applied):

FlagPurpose
--no-sandboxRequired for Docker / CI environments
--disable-setuid-sandboxRequired for Docker / CI environments
--font-render-hinting=noneConsistent font rendering across platforms
--start-maximizedOnly in debug mode — maximizes visible window
--proxy-server=...Only when proxy is enabled

Package Distribution​

ComponentPackageRole
PuppeteerHandlerio-oidis-hubCore browser automation + PDF rendering engine
PrintManagerio-oidis-hubREST/WebSocket print endpoint + orchestration
Convert.HtmlToPDF()io-oidis-hubConvenience wrapper over PuppeteerHandler.Export()
PrintConnectorio-oidis-hub-guiFrontend WebSocket client for PrintManager
PrintBindingio-oidis-hub-guiFrontend UI binding for print layout + PDF generation
IPDFOptionsio-oidis-hub-guiShared PDF options interface
PrintTypeio-oidis-hub-guiEnum: Own (0), A4 (1)

See Also​