npm.io
1.1.0 • Published 2d ago

obscura-node

Licence
ISC
Version
1.1.0
Deps
1
Size
50 kB
Vulns
0
Weekly
298
Install scriptsThis package runs scripts during installation (preinstall/install/postinstall)

obscura-node

Node.js wrapper for Obscura — the Rust-powered headless browser built for AI agents and web scraping. Drop-in replacement for headless Chrome with Puppeteer & Playwright. Includes a one-shot ObscuraFetch API for lightning-fast page fetching.

npm version GitHub Obscura engine License Platform


What is Obscura?

Obscura is an open-source headless browser engine written from scratch in Rust — not a fork or fork of Chromium. It runs real JavaScript via V8 (deno_core), builds a live DOM from parsed HTML, and exposes the Chrome DevTools Protocol (CDP) over WebSocket. This means it works as a drop-in replacement for headless Chrome with Puppeteer and Playwright — but at a fraction of the resource cost.

obscura-node gives you two ways to use Obscura:

  • loadObscura() — spawns obscura serve, returns a CDP WebSocket endpoint for use with Puppeteer, Playwright, or any CDP client.
  • ObscuraFetch() — one-shot obscura fetch call. Fetches a URL, renders the page (JS, DOM), and returns the output as a string. No server setup, no WebSocket, no cleanup.

obscura-node is a Node.js wrapper around Obscura — the incredible Rust headless browser built by @h4ckf0r0day. All credit for the browser engine, stealth, benchmarks, and CDP implementation goes to the original author. This package just makes it easy to use from Node.js.


Why Obscura over Headless Chrome?

Metric Obscura Headless Chrome Advantage
Memory per instance ~30 MB 200+ MB 6-10x less
Binary size ~70 MB 300+ MB 4x smaller
Page load (static) ~51 ms ~500 ms 10x faster
Page load (dynamic) ~85 ms ~800 ms 9x faster
Startup time Instant (~50 ms) ~2 seconds 40x faster
Anti-detection Built-in (--stealth) None
Tracker blocking 3,520 domains None
SSRF protection Built-in None
DOM → Markdown Native CDP method None
Puppeteer / Playwright Full CDP Identical
Single binary Yes Needs Chrome
Head-to-head benchmark (from obscura-benchmark)

Framework pages (cold start):

Page Obscura Headless Chrome Advantage
React 88 ms, 30 MB 1,097 ms, 185 MB 12x faster, 6x less memory
Preact 59 ms, 29 MB 1,032 ms, 186 MB 18x faster, 6x less memory
Vue 97 ms, 32 MB 1,144 ms, 184 MB 12x faster, 6x less memory

Concurrent throughput (8 workers):

Engine Throughput Peak memory
Obscura 21 pg/s 132 MB
Headless Chrome 2.8 pg/s 7.1 GB

Real-world corpus (24 public sites):

Metric Obscura Headless Chrome
Render success 91.7% 91.7%
Median latency 2.0 s 1.5 s
Median peak RSS 35.6 MB 191.8 MB

Source: obscura-benchmark — conformance, obstacle course, vs-Chrome, and real-world corpus benchmarks.

Key takeaway: Independent benchmarks confirm Obscura uses ~30 MB memory (vs Chrome's 200-300 MB), has dramatically lower variance (σ 16ms vs Chrome's 76ms on HN), and delivers identical rendering results on real pages.


Features

  • Two APIsloadObscura() for CDP-based multi-page sessions (Puppeteer/Playwright), ObscuraFetch() for one-shot page fetching
  • One-shot ObscuraFetch — fetch any URL and get rendered output (HTML, text, markdown, links, assets) in a single call. Significantly faster than spinning up a full CDP server
  • Zero dependencies — single Rust binary, no Chrome/Node.js runtime required
  • Automatic download — installs the right binary for your platform (Linux x64, macOS ARM/x64, Windows x64)
  • CDP WebSocket endpoint — use with Puppeteer, Playwright, or any CDP client
  • Stealth mode — pass stealth: true for anti-fingerprinting + tracker blocking
  • Flexible port/host — auto-find free port or specify one
  • Graceful shutdown — proper SIGTERM → SIGKILL cleanup
  • Custom args — pass extra flags to the Obscura binary
  • Configurable timeout — control how long to wait for startup

Installation

npm install obscura-node

The Obscura binary downloads automatically on the first loadObscura() or ObscuraFetch() call if not present.


Quick Start

One-shot fetch (v1.1.0+)
import { ObscuraFetch } from "obscura-node";

const html = await ObscuraFetch("https://example.com");
console.log(html);

// Get text content instead
const text = await ObscuraFetch("https://example.com", { dump: "text" });
console.log(text);
CDP server (Puppeteer / Playwright)
import loadObscura from "obscura-node";

const browser = await loadObscura();

console.log(browser.endpoint);    // http://127.0.0.1:58291
console.log(browser.wsEndpoint);  // ws://127.0.0.1:58291/...

// Use with Puppeteer:
import puppeteer from "puppeteer";
const browser = await puppeteer.connect({
  browserWSEndpoint: obscura.wsEndpoint,
});
const page = await browser.newPage();
await page.goto("https://example.com");
console.log(await page.title());
await browser.close();

// Cleanup
await obscura.close();

API Reference

ObscuraFetch(url, options?) — v1.1.0+

One-shot fetch. Spawns obscura fetch <url> --dump <format>, renders the page with full V8 JavaScript, and returns the output. No persistent server, no WebSocket, no cleanup needed.

import { ObscuraFetch } from "obscura-node";

const result = await ObscuraFetch("https://example.com", {
  dump: "markdown",
  stealth: true,
  timeout: 15,
});
Options (FetchOptions)
Property Type Default CLI flag Description
dump "html" | "text" | "markdown" | "links" | "assets" | "original" "html" --dump Output format
selector string --selector CSS selector to extract specific elements
wait number 5 --wait Seconds to wait before capturing
timeout number 30 --timeout Total timeout in seconds
waitUntil "domcontentloaded" | "load" | "networkidle2" | "networkidle0" "load" --wait-until When to consider navigation complete
userAgent string --user-agent Custom user agent
proxy string --proxy HTTP or SOCKS5 proxy URL
stealth boolean false --stealth Enable anti-fingerprinting + tracker blocking
eval string --eval JavaScript expression to evaluate in the page
output string --output Write output to a file instead of stdout
quiet boolean false --quiet Suppress info logs
verbose boolean false --verbose Enable debug logging
Dump formats
Format Description
html Rendered HTML after JavaScript execution
text Plain text with navigation/header/footer stripped
markdown Clean Markdown via native DOM-to-Markdown conversion (LP.getMarkdown)
links List of all links on the page
assets Inventory of page sub-resources
original Raw unmodified source

loadObscura(options?)

Starts a persistent CDP server (obscura serve) for multi-page sessions with Puppeteer, Playwright, or any CDP-compatible client.

Options
Property Type Default Description
host string "127.0.0.1" Bind address for the CDP server
port number Auto (free port) CDP WebSocket port
stealth boolean false Enable anti-detection + tracker blocking
startupTimeoutMs number 10000 Timeout for binary startup
extraArgs string[] [] Additional CLI flags passed to obscura serve
Returns
Property Type Description
endpoint string HTTP URL (e.g. http://127.0.0.1:9222)
wsEndpoint string WebSocket CDP URL (e.g. ws://127.0.0.1:9222/...)
close () => Promise<void> Kills the Obscura process

Advanced Usage

With Puppeteer
import loadObscura from "obscura-node";
import puppeteer from "puppeteer";

const obscura = await loadObscura({ stealth: true });
const browser = await puppeteer.connect({ browserWSEndpoint: obscura.wsEndpoint });
const page = await browser.newPage();

await page.goto("https://example.com");
const title = await page.title();
console.log(title);

await browser.close();
await obscura.close();
With Playwright
import loadObscura from "obscura-node";
import { chromium } from "playwright";

const obscura = await loadObscura();
const browser = await chromium.connectOverCDP(obscura.endpoint);
const page = await browser.newPage();

await page.goto("https://example.com");
console.log(await page.title());

await browser.close();
await obscura.close();
Stealth mode
const browser = await loadObscura({
  stealth: true,  // anti-fingerprinting + blocks 3,520 tracker domains
});

Equivalent to obscura serve --stealth. Enables per-session fingerprint randomization (GPU, screen, canvas, audio, battery), navigator.webdriver = undefined, native function masking, and TLS ClientHello randomization.

Custom host and port
const browser = await loadObscura({
  host: "0.0.0.0",  // listen on all interfaces
  port: 9222,        // fixed port
});
Extra CLI arguments
const browser = await loadObscura({
  stealth: true,
  extraArgs: [
    "--proxy", "socks5://127.0.0.1:9050",
    "--user-agent", "Mozilla/5.0 ...",
    "--workers", "4",
  ],
});

See the Obscura CLI reference for all available flags.


Environment Variables

Variable Default Description
OBSCURA_RELEASE_TAG v0.1.8 GitHub release tag for the Obscura binary
OBSCURA_DOWNLOAD_BASE_URL https://github.com/h4ckf0r0day/obscura/releases/download Override download URL
NODE_OBSCURA_SKIP_DOWNLOAD Set to "1" to skip binary download

Platform Support

OS Architecture Supported
Linux x64
macOS ARM64 (Apple Silicon)
macOS x64 (Intel)
Windows x64

Use Cases

  • AI agents — give agents a lightweight, stealth-capable browser for web research and interaction
  • Web scraping — extract data at scale with 10x less memory than headless Chrome
  • Automated testing — run Puppeteer/Playwright tests with faster startup and lower resource usage
  • MCP servers — Obscura ships a built-in MCP server for AI agent tools (obscura mcp)
  • CI/CD pipelines — single binary, no Chrome install needed, Docker-friendly (distroless image ~57 MB)

How it works

obscura-node (npm package)
       │
       ├── src/utils/install.ts  →  Downloads Obscura binary from GitHub releases
       │                            Extracts to src/binaries/<platform>/obscura
       │
       ├── ObscuraFetch(url, opts)
       │     └──  spawns `obscura fetch <url> --dump <format> ...`
       │          Captures stdout  →  returns rendered content as string
       │
       └── loadObscura(opts)
             └──  spawns `obscura serve --port <port>`
                  Polls CDP /json/version until ready
                  Returns { endpoint, wsEndpoint, close }


License

ISC


Credits

Obscura was created by @h4ckf0r0day. This Node.js wrapper is maintained separately but relies entirely on the original Obscura binary. If you find this useful, please also star the original repo.

Keywords