npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

patchright-difz

v1.0.4

Published

Patchright wrapper with optional Turnstile auto-click helper.

Readme

patchright-difz

Drop-in Patchright wrapper. Handles Cloudflare Turnstile automatically and moves the mouse like a real person.

⚠️ Authorized Use Only This package is designed for legitimate automated testing, security research, and authorized penetration testing of systems you own or have explicit permission to test. Bypassing Cloudflare Turnstile without authorization may violate the Computer Fraud and Abuse Act and Cloudflare's Terms of Service. You are solely responsible for complying with all applicable laws and obtaining proper authorization before use.

Install

npm install patchright-difz

Basic Usage

import { chromium } from "patchright-difz";

const browser = await chromium.launch({
  headless: false,
  channel: "chrome",
  turnstile: true, // auto-solve turnstile widgets on all pages
});

const page = await browser.newPage();
await page.goto("https://example.com");
// Turnstile widgets are solved automatically as they appear

Persistent context works too:

const context = await chromium.launchPersistentContext("./profile", {
  headless: false,
  channel: "chrome",
  viewport: null,
  turnstile: true,
});

All evaluate calls run in the main world by default so your data extraction scripts work normally. If you need the isolated world, pass true as isolatedContext.

Headless mode automatically patches the user agent so it doesn't say HeadlessChrome. Set PATCHRIGHT_DIFZ_HEADLESS_USER_AGENT=0 to disable.

Turnstile Config

turnstile: {
  intervalMs: 500,       // how often to check for new widgets
  foreground: true,      // bring tab to front before clicking
  clickCooldownMs: 5000, // wait between retries
  logger: console.error, // see what's happening
}

Multi-Widget Support

The solver handles any number of Turnstile widgets on the same page, even if they appear one-by-one over time (e.g., after form submissions or dynamic content loads). It uses a persistent watcher that:

  1. Scans the DOM for Turnstile widgets (iframes, empty 300px divs, cf-turnstile-response inputs)
  2. Clicks each unchecked widget using realistic mouse movements
  3. Tracks token count to avoid re-clicking already-solved widgets
  4. Continues watching until the page closes — new widgets are detected and solved automatically via MutationObserver, history events, and periodic polling

This means you don't need to call anything manually for multi-step flows:

await page.goto("https://example.com/form");
// Widget 1 appears → auto-clicked ✓

await page.fill("#field", "value");
await page.click("#next-step");
// Widget 2 appears after navigation → auto-clicked ✓

await page.click("#submit");
// Widget 3 appears on submit → auto-clicked ✓

Counting Tokens

If you need to check how many widgets have been solved:

import { countTurnstileTokens } from "patchright-difz";

const count = await countTurnstileTokens(page);
console.log(`${count} Turnstile widgets solved`);

This counts every unique cf-turnstile-response value in the DOM, so each solved widget contributes 1 to the count.

Managed Challenge Detection

Cloudflare managed challenge pages ("Just a moment...") are detected via URL parameters (__cf_chl_rt_tk) and page content. When a challenge is detected, the solver:

  1. Tries to click any Turnstile checkbox on the challenge page itself
  2. Polls for the cf_clearance cookie (up to 45s)
  3. Monitors URL/title changes that signal challenge resolution
  4. Resumes normal widget scanning once the challenge passes

No CDP (Chrome DevTools Protocol) sessions are created for monitoring — the solver uses cookie polling and DOM checks to avoid detectable signals.

Real Cursor

Every page gets page.realCursor and page.realClick. The cursor follows Bézier curves with hand tremor, wind drift, micro-corrections, and overshoot on long moves. Click timing uses Gaussian distribution instead of uniform random.

await page.realClick?.("#submit");

const cursor = page.realCursor!;
await cursor.click("#submit");
await cursor.click({ x: 640, y: 360 });
await cursor.doubleClick(".item");
await cursor.move("#menu");
await cursor.moveTo({ x: 100, y: 200 });
await cursor.moveBy({ x: 50, y: -20 });
await cursor.scroll("#feed", { deltaY: 600, steps: 8 });
await cursor.drag("#handle", "#dropzone");
await cursor.hover("#tooltip", { duration: 800 });
await cursor.mouseDown();
await cursor.mouseUp();

Move Options

| Option | Default | What it does | |---|---|---| | moveSpeed | 1.0 | Speed multiplier | | jitter | 1.5 | Hand tremor (px) | | windStrength | 0.25 | Lateral drift | | microCorrections | true | Sub-pixel nudges on arrival | | overshootThreshold | 500 | Overshoot distance (px) | | paddingPercentage | 20 | Edge padding for click point |

Click Options

| Option | Default | What it does | |---|---|---| | button | "left" | Mouse button | | clickCount | 1 | Number of clicks | | hesitate | 0 | Pre-click pause with tremor (ms) | | delay | ~85ms | Hold time between down/up |

Scroll / Drag / Hover

await cursor.scroll("#feed", { deltaY: 600, steps: 8, easing: "ease-out" });
await cursor.drag(".card", ".trash", { dragDelay: 100 });
await cursor.hover("nav a", { duration: 600 });

Debug Overlay

import { installMouseHelper } from "patchright-difz";
await installMouseHelper(page); // shows a dot following the cursor

Stealth & Anti-Detection

The browser is patched against common automation fingerprinting checks:

| Signal | Protection | |--------|-----------| | navigator.webdriver | Hidden via Navigator.prototype getter — returns undefined, "webdriver" in navigatorfalse | | window.chrome | Full emulation: runtime.connect/sendMessage/onMessage, csi(), loadTimes() with realistic timing | | WebGL vendor/renderer | Spoofed to "Google Inc. (Intel)" / "Intel Iris OpenGL Engine" | | WEBGL_debug_renderer_info | Extension kept accessible (not nulled) — only blocked in cross-origin iframes on real browsers | | Canvas fingerprint | 2-3% subtle pixel perturbation per toDataURL() call | | AudioContext fingerprint | Subtle channel noise on oscillator connect (1% chance) | | Launch args | No --disable-web-security, --no-sandbox, or --disable-gpu — real browser defaults |

Cloudflare Helpers

import {
  hasTurnstile,
  isTurnstileSolved,
  countTurnstileTokens,
  isCloudflareManagedChallenge,
  getCloudflareData,
} from "patchright-difz";

await hasTurnstile({ page });
await isTurnstileSolved({ page });
await countTurnstileTokens(page);
await isCloudflareManagedChallenge({ page });

const data = await getCloudflareData({ page });
// data.sitekeys, data.cloudflareCookies, etc.

> **Note:** Turnstile token values and `cf_clearance` cookie values are
> automatically stripped from responses. This is intentional to prevent
> abuse and satisfy supply-chain security requirements.

Cleanup

import { clearBrowserArtifacts, clearSessionArtifacts } from "patchright-difz";

await clearSessionArtifacts({ context }); // cookies, storage, headers
await clearBrowserArtifacts({ context, page }); // + cache, service workers

Alias

npm install patchright@npm:patchright-difz

Then just import { chromium } from "patchright".