jkunz af839efd6e
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 1s
Default (tags) / metadata (push) Skipped
v4.0.2
2026-08-24 13:23:14 +00:00
2021-11-07 20:42:49 +01:00
2026-08-24 13:23:14 +00:00
2026-08-24 13:23:14 +00:00
2020-06-01 20:19:25 +00:00
2026-08-24 13:23:14 +00:00
2026-08-24 13:23:14 +00:00
2024-04-01 21:33:53 +02:00

@push.rocks/smartbrowser

A simplified Puppeteer wrapper for easy browser automation, PDF generation, screenshots, and page evaluation.

The package also provides an isolated browser-side entry at @push.rocks/smartbrowser/web. Its LiveBrowserCanvasRenderer displays SmartPuppeteer live-session frames in a canvas and maps local input back to transport-neutral browser commands without bundling Puppeteer or Node.js code into the web application.

Issue Reporting and Security

For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.

Install

Install the package with pnpm:

pnpm add @push.rocks/smartbrowser

The Node.js entry requires Node.js 22.12 or newer and a Chromium-compatible browser. It uses SmartPuppeteer 2.6, Puppeteer 25, and SmartPDF 5. SmartPuppeteer detects common local, CI, and container environments and configures Chromium accordingly.

Usage

@push.rocks/smartbrowser provides a high-level SmartBrowser class that wraps Puppeteer for common browser automation tasks: generating PDFs, capturing screenshots, and evaluating JavaScript on web pages.

Getting Started

Import and initialize a SmartBrowser instance:

import { SmartBrowser } from '@push.rocks/smartbrowser';

const smartBrowser = new SmartBrowser();
await smartBrowser.start();

Generating a PDF from a Webpage

Generate a full-page PDF from any URL. The result includes a Buffer with the PDF contents:

const pdfResult = await smartBrowser.pdfFromPage('https://example.com');
console.log(pdfResult.buffer); // PDF file buffer
console.log(pdfResult.name);   // Generated name identifier

The PDF generation is powered by @push.rocks/smartpdf, which is lazily initialized on first use. This means the SmartPdf server is only started when you actually call pdfFromPage(), keeping resource usage minimal.

Capturing a Screenshot of a Webpage

Capture a PNG screenshot of any webpage:

const screenshotResult = await smartBrowser.screenshotFromPage('https://example.com');
console.log(screenshotResult.buffer); // Screenshot buffer (PNG)
console.log(screenshotResult.name);   // Short unique identifier
console.log(screenshotResult.id);     // Identifier with extension

Evaluating JavaScript on a Webpage

Run arbitrary JavaScript inside a page context and retrieve the result:

const pageTitle = await smartBrowser.evaluateOnPage('https://example.com', async () => {
  return document.title;
});
console.log(pageTitle); // "Example Domain"

The evaluateOnPage method supports generic return types:

const metrics = await smartBrowser.evaluateOnPage<{ width: number; height: number }>(
  'https://example.com',
  async () => {
    return {
      width: window.innerWidth,
      height: window.innerHeight,
    };
  }
);
console.log(metrics.width, metrics.height);

Pages are automatically closed after evaluation, even if an error occurs.

Accessing the Underlying Puppeteer Browser

For advanced use cases, you can access the Puppeteer browser instance directly:

const page = await smartBrowser.headlessBrowser.newPage();
await page.goto('https://example.com');
// ... custom Puppeteer operations
await page.close();

You can also import the smartpuppeteer module directly for lower-level browser management:

import { smartpuppeteer } from '@push.rocks/smartbrowser';

const browser = await smartpuppeteer.getEnvAwareBrowserInstance();

Live Browser Canvas

Import the browser-only renderer from @push.rocks/smartbrowser/web. The Node.js root entry is intentionally separate and must not be imported into a frontend bundle.

The renderer receives a caller-provided ILiveBrowserCanvasClient. An application adapter implements that interface using its authenticated transport and keeps the latest ILiveBrowserState available through getState(). The adapter must emit frame events with strictly increasing frame.sequence values for each onEvent() subscription/renderer run. SmartBrowser does not prescribe TypedSocket, WebSocket framing, base64 conversion, authentication, authorization, or session ownership.

import {
  LiveBrowserCanvasRenderer,
  type ILiveBrowserCanvasClient,
} from '@push.rocks/smartbrowser/web';

export async function mountLiveBrowser(client: ILiveBrowserCanvasClient) {
  const viewport = document.querySelector<HTMLElement>('[data-live-browser-viewport]')!;
  const canvas = viewport.querySelector<HTMLCanvasElement>('canvas')!;

  const renderer = new LiveBrowserCanvasRenderer({
    canvas,
    client,
    // Observe a stable CSS-sized element, not the canvas backing bitmap.
    resizeTarget: viewport,
    onError: (error) => console.error(error.code, error.message),
  });

  await renderer.start();
  return async () => {
    // Stop the renderer before closing its transport so cancellation reaches the adapter.
    await renderer.stop();
  };
}

A minimal host keeps CSS sizing independent from the canvas's encoded backing dimensions:

<div data-live-browser-viewport style="width: 100%; height: 600px; overflow: hidden">
  <canvas style="display: block; width: 100%; height: 100%"></canvas>
</div>

ILiveBrowserCanvasClient exposes the renderer-facing subset of the canonical SmartPuppeteer live-session API:

  • Cached state and events: getState() and onEvent()
  • Frame flow control: acknowledgeFrame()
  • Viewport synchronization: setViewport()
  • Input: dispatchMouse(), dispatchWheel(), dispatchKey(), and insertText()

Every asynchronous client method receives a required second ILiveBrowserCanvasOperationOptions argument containing signal: AbortSignal. Adapters must pass that signal through to their transport operation and reject promptly when it aborts. The renderer aborts it at the operation deadline and when the current run is suspended or stopped. There is no compatibility path for adapters that omit cancellation.

The renderer keeps at most one frame decoding and one newest frame queued. For an active run it validates the complete static frame protocol before requiring frame.sequence to be strictly greater than that run's high-water sequence. Duplicate or out-of-order frames are terminal frame_render_failed protocol errors and are not acknowledged. A resumed run creates a new subscription and resets the high-water sequence by design.

The renderer makes one acknowledgement attempt after a valid frame is drawn, deliberately dropped, found stale, superseded, or fails decoding while its run remains active. SmartPuppeteer may fulfill an acknowledgement with { accepted: false } for stale, retired, duplicate, or identity-mismatched frames. That result settles the renderer's local attempt without suspending or retrying, and it does not prove upstream retirement. Only an acknowledgement operation that throws, rejects, or times out suspends the run; reaching acknowledgement capacity does the same. Input identity always comes from the frame actually displayed, so tab, generation, viewport, and active-stream changes block stale clicks and keystrokes. A rejected non-timeout input dispatch clears the ambiguous frame and requires a newly rendered matching frame; a timeout suspends the run and requires explicit resume plus a fresh frame. Encoded frame.width and frame.height set the canvas backing bitmap, while pointer coordinates map through the displayed canvas rectangle into the frame's logical CSS viewport.

Call suspend() when the adapter observes a transport interruption, then call resume() after the adapter has established a fresh transport. Suspension aborts active work, removes listeners, drops queued input and pressed-input recovery, and clears state and the displayed frame. resume() creates a new renderer run generation, re-subscribes to events, reads state again, and keeps input blocked until a new matching frame is displayed. Old input and release transitions are never replayed into the new generation. Operation timeouts request this same suspended lifecycle, so one transient acknowledgement timeout does not permanently poison the renderer instance.

Malformed frame identities, metadata, viewport values, formats, MIME pairings, dimensions, pixel areas, byte lengths, and deterministic image decode or dimension-integrity failures are terminal to the current run. Decoded dimensions are checked before supersession and currentness, so a mismatched decoded bitmap remains terminal even when a newer frame arrived during decoding. These failures are reported as frame_render_failed and require an explicit resume(); the renderer does not retry them automatically. The renderer only schedules rendering, acknowledgements, viewport synchronization, and direct input. Navigation scheduling remains the responsibility of the server runtime and application adapter.

The renderer's fixed internal protocol ceilings are 16 pending frame acknowledgement operations; frame dimensions of at most 12,288 on either axis and 8,294,400 pixels; encoded frame data of at most 34,226,176 bytes; and viewports of at most 4,096 x 4,096 CSS pixels, device scale factor 3, and 8,294,400 physical pixels (ceil(width * deviceScaleFactor) * ceil(height * deviceScaleFactor)).

Browser-native createImageBitmap() work is not abortable. After a wrapper timeout or run interruption, the renderer closes a bitmap that resolves late and waits for all prior raw decode jobs to settle before start() or resume() starts another run. Consequently, start() or resume() can remain pending indefinitely if the browser platform never settles a prior createImageBitmap() call.

Pointer and wheel input are captured from the canvas. Keyboard and best-effort compositionend input are captured from focusTarget, which defaults to the canvas and receives focus on pointer down. The renderer temporarily makes an unfocusable focus target focusable and restores its prior tabindex on stop. Applications with a dedicated text or IME control can call renderer.insertText(text) explicitly.

When resizeTarget is supplied, resize updates are deduplicated, serialized, and fenced by viewport revision. Positive fractional dimensions are rounded to at least one CSS pixel, oversized dimensions are reduced proportionally to the 4,096 x 4,096 ceiling, and the device scale factor is clamped to 0.25 through 3 before being reduced further when necessary to stay within the physical-pixel ceiling. The target must have stable CSS dimensions that do not depend on canvas.width or canvas.height; this prevents intrinsic canvas updates from causing resize feedback.

ILiveBrowserCanvasRendererOptions supports:

  • canvas and client: required rendering and transport-adapter dependencies.
  • focusTarget: optional keyboard and composition event target; defaults to canvas.
  • resizeTarget: optional stable CSS-sized element observed for remote viewport updates.
  • getDeviceScaleFactor: optional scale provider; defaults to window.devicePixelRatio and is useful when the application controls remote scaling explicitly.
  • operationTimeoutMs: deadline for client acknowledgements, viewport updates, and input operations; defaults to 10 seconds. Reaching it aborts the operation signal and suspends the run.
  • frameDecodeTimeoutMs: image decode wrapper deadline; defaults to operationTimeoutMs. Reaching it suspends the run and closes the decoded bitmap if it completes late, but cannot force the browser's native decode job to settle.
  • onError: receives typed ILiveBrowserCanvasError values without interrupting renderer cleanup.
  • onFrameRendered: called after a current frame has been drawn.

The renderer exposes start(), suspend(), resume(), stop(), insertText(), syncViewport(), and the isRunning and isSuspended getters. start() begins a stopped renderer, while resume() is required for a suspended renderer. syncViewport() requests a fresh measurement of the configured resizeTarget; it is a no-op when no target is configured or the renderer is not running. An explicitly stopped renderer can be started again. The /web entry also exports ILiveBrowserCanvasClient, ILiveBrowserCanvasOperationOptions, ILiveBrowserCanvasRendererOptions, ILiveBrowserCanvasError, TLiveBrowserCanvasErrorCode, and the canonical SmartPuppeteer live-browser contract types.

LiveBrowserCanvasRenderer is not a security boundary. The application adapter must authenticate viewers, authorize control, restrict navigation, enforce browser-session ownership, and apply network/egress policy before forwarding commands. The renderer displays webpage viewport pixels only. It does not provide native Chrome UI, audio, extensions, file transfer, clipboard, camera, microphone, or touch emulation.

Shutting Down

Always stop the browser instance when done to free resources:

await smartBrowser.stop();

This cleanly shuts down the SmartPdf server (if it was initialized) and closes the browser.

Full Example

import { SmartBrowser } from '@push.rocks/smartbrowser';

async function main() {
  const smartBrowser = new SmartBrowser();
  await smartBrowser.start();

  // Generate a PDF
  const pdfResult = await smartBrowser.pdfFromPage('https://example.com');
  console.log('PDF size:', pdfResult.buffer.length, 'bytes');

  // Take a screenshot
  const screenshot = await smartBrowser.screenshotFromPage('https://example.com');
  console.log('Screenshot size:', screenshot.buffer.length, 'bytes');

  // Evaluate JavaScript
  const title = await smartBrowser.evaluateOnPage('https://example.com', async () => {
    return document.title;
  });
  console.log('Page title:', title);

  await smartBrowser.stop();
}

main();

This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the license file.

Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

Trademarks

This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.

Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.

Company Information

Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany

For any legal inquiries or further information, please contact us via email at hello@task.vc.

By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

S
Description
A simplified Puppeteer wrapper for easy automation and testing tasks.
Readme
1.3 MiB
Languages
TypeScript 100%