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 Case | API | Package | Example |
|---|---|---|---|
| Browser automation (scraping, portal login, data export) | Extract() | io-oidis-hub | EDC portal automation in IRMA |
| PDF generation (HTML to PDF, label printing) | Export() + Print Pipeline | io-oidis-hub + io-oidis-hub-gui | Delivery 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
| Property | Type | Default | Description |
|---|---|---|---|
url | string | "" | URL to navigate to. Mutually exclusive with html. |
html | string | "" | Inline HTML content to render. Mutually exclusive with url. |
verbose | boolean | false | Forward browser console and pageerror events to LogIt. |
cookies | any[] | [] | Cookies to set before navigation (Puppeteer setCookie format). |
debug | boolean | false | Run headed (visible browser window) instead of headless. |
How Extract Works
- Merges
$inputwith defaults (empty url/html, verbose false, debug false). - Acquires a page handler from the pool via
getHandler(). If the requested page is busy, waits up tomaxWaitTimeiterations (polling with incremental delays). - URL mode: sets cookies if provided, navigates to URL with
waitUntil: ["load", "networkidle0"]. Skips navigation if the page already has the same URL loaded. - Inline HTML mode: sets page content via
setContent()withwaitUntil: "networkidle0". - Calls the converter function with the Puppeteer
Pageobject. - Marks the page handler as no longer processing.
- On error: logs the exception, attempts graceful
browser.close(), falls back toprocess.kill(), resets browser tonullfor re-launch on next call. - Returns
nullon 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;
});
}
}
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:
| Property | Type | Default | Description |
|---|---|---|---|
format | string | "pdf" | Output format: "pdf" or "screenshot". |
Plus all inherited properties from IPuppeteerExtractOptions.
IPDFOptions
| Property | Type | Default | Description |
|---|---|---|---|
name | string | — | Output filename. |
format | string | "A4" | Paper format (when width/height not set). |
displayHeaderFooter | boolean | false | Show header/footer in PDF. |
landscape | boolean | false | Landscape orientation. |
margin | IPDFMargin | {top:0, right:0, bottom:0, left:0} | Page margins (CSS units or mm). |
width | string | — | Page width (e.g., "210mm"). |
height | string | — | Page height (e.g., "297mm"). |
language | string | "en" | HTML lang attribute. |
css | string | "" | Custom CSS appended after Bootstrap base styles. |
cookies | any[] | [] | Cookies for URL-based rendering. |
inputs | any | [] | Form inputs to populate before triggering print ({name, value} pairs). |
asStream | boolean | — | Return as base64 stream vs raw buffer. |
license | string | (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-noneutility
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; }"
});
Print Pipeline (Full Stack)
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 viaConvert.HtmlToPDF(), returnsIExportStatuswith base64-encoded PDF datagetPrintOptions($id : string)— retrieves stored print request settings by ID
REST endpoints (via @Route()):
POST /printandPOST /v1/print— full server-side rendering viaStreamResponse
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 pagePrintType.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/#printCompleteDOM triggersPrintToPDF()— renders items into print layout, callsPrintConnector.GeneratePDF(), opens result viaFileFetch.Open()PrintPreview()— renders items and injects@pageCSS for browser print dialogprintItems()— internal layout engine usingPrintPage,PrintRow,PrintContainerwith 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:
PrintBindinginjects hidden<input id="printId">and<span id="printStart">into the DOM- Backend
PuppeteerHandler.Export({url})navigates to/#/print/{userName}/{token} - Backend fills
#printIdinput value and clicks#printStart - Frontend
onclickhandler fires: authenticates, loads data, renders print items - Frontend appends
<span id="printComplete">when done - Backend detects
#printComplete, renders the page to PDF
Browser Management
Singleton vs Per-Instance
| Mode | Constructor | Use case |
|---|---|---|
| Singleton | PuppeteerHandler.getInstanceSingleton() | PDF generation (PrintManager, Convert.HtmlToPDF()) — shared browser for the application |
| Per-instance | new 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()orExport()call - On error during extraction: attempts
browser.close()→process.kill(browser.process().pid)→ setsbrowser = null - Setting
browser = nullensures a fresh browser is launched on the next call - No explicit public
close()ordestroy()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:
| OS | Architecture | Platform ID |
|---|---|---|
| Windows | 64-bit | win64 |
| Windows | 32-bit | win32 |
| macOS | Intel | mac |
| macOS | Apple Silicon | mac_arm |
| Linux | any | linux |
Cache directory resolution (priority order):
puppeteer.cacheDirfrom config (explicit override){ProjectBase}/../../appData/puppeteer(when running from/build/target){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
}
}
| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable/disable PuppeteerHandler initialization at startup. |
urlThreads | number | 4 | Number of dedicated URL pages in the browser pool. |
maxWaitTime | number | 20 | Max polling iterations to wait for a busy page to become available. |
chromeBuildId | string | "127" | Chrome version to download via @puppeteer/browsers. |
proxy.enabled | boolean | false | Route browser traffic through a proxy server. |
proxy.location | string | null | Proxy server address (e.g., "socks5://127.0.0.1:9050"). |
cacheDir | string | null | Custom 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
}
}
| Key | Type | Description |
|---|---|---|
maxPrintRequests | number | Maximum concurrent print requests before returning HTTP 403. |
maxIdsPerPrintRequests | number | Maximum 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):
| Flag | Purpose |
|---|---|
--no-sandbox | Required for Docker / CI environments |
--disable-setuid-sandbox | Required for Docker / CI environments |
--font-render-hinting=none | Consistent font rendering across platforms |
--start-maximized | Only in debug mode — maximizes visible window |
--proxy-server=... | Only when proxy is enabled |
Package Distribution
| Component | Package | Role |
|---|---|---|
PuppeteerHandler | io-oidis-hub | Core browser automation + PDF rendering engine |
PrintManager | io-oidis-hub | REST/WebSocket print endpoint + orchestration |
Convert.HtmlToPDF() | io-oidis-hub | Convenience wrapper over PuppeteerHandler.Export() |
PrintConnector | io-oidis-hub-gui | Frontend WebSocket client for PrintManager |
PrintBinding | io-oidis-hub-gui | Frontend UI binding for print layout + PDF generation |
IPDFOptions | io-oidis-hub-gui | Shared PDF options interface |
PrintType | io-oidis-hub-gui | Enum: Own (0), A4 (1) |
See Also
- Configuration —
commons.jsonpstructure - ProgramArgs —
--debugflag for headed mode - Docker and CI — headless Chromium in Docker environments