@koloss777/nokk 0.1.28-alpha → 0.1.29-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -53,7 +53,7 @@ const { chromium } = require("playwright");
53
53
  Starts the CDP server on a free port and resolves to a `NokkServer`. Options
54
54
  (all optional): `host`, `port`, `workers`, `maxContexts`, `proxy`,
55
55
  `sessionStore`, `rotateFingerprint`, `geoipTimezone`, `allowTrackers`,
56
- `chromeVersion`, `args`, `env`, `timeout`.
56
+ `chromeVersion`, `autoSolve`, `args`, `env`, `timeout`.
57
57
 
58
58
  ```js
59
59
  // Each browser context looks like a different machine, timezone matched to its proxy:
@@ -63,6 +63,33 @@ const server = await nokk.launch({ rotateFingerprint: true, geoipTimezone: true
63
63
  `server.wsEndpoint` → hand to `puppeteer.connect` / `chromium.connectOverCDP`.
64
64
  `server.close()` stops it; it's also killed when the Node process exits.
65
65
 
66
+ ## Cloudflare challenges
67
+
68
+ With `autoSolve`, a navigation that lands on Cloudflare's "Just a moment…" page
69
+ or a Turnstile widget returns only once it is through: `page.goto()` resolves
70
+ on the real page. `true` gives each challenge 30 s; a number sets the budget in
71
+ seconds.
72
+
73
+ ```js
74
+ const server = await nokk.launch({ autoSolve: true });
75
+ // …
76
+ await page.goto("https://www.scrapingcourse.com/cloudflare-challenge");
77
+ console.log(await page.title()); // the page behind the challenge
78
+ ```
79
+
80
+ Or on demand, for a Puppeteer page, a Playwright page or a raw CDP session:
81
+
82
+ ```js
83
+ const state = await nokk.challengeState(page);
84
+ // { kind: "cloudflare-interstitial" | "turnstile-widget" | "datadome" | "none", solvable, … }
85
+ if (state.solvable) {
86
+ const r = await nokk.solveChallenge(page, { timeout: 30000 });
87
+ // { status: "cleared" | "token-issued" | "cleared-but-stuck" | "timeout", solved, presses, remaining, … }
88
+ }
89
+ ```
90
+
91
+ Nobody passes selectors: the engine finds and presses the checkbox itself.
92
+
66
93
  ## CLI
67
94
 
68
95
  ```bash
package/index.d.ts CHANGED
@@ -19,6 +19,12 @@ export interface LaunchOptions {
19
19
  allowTrackers?: boolean;
20
20
  /** Chrome major version to emulate (TLS + JS together), e.g. 148. */
21
21
  chromeVersion?: number;
22
+ /**
23
+ * Solve Cloudflare challenges (interstitial or Turnstile widget) on every
24
+ * navigation before it reports the load. `true` = 30 s budget; a number =
25
+ * seconds. A browser context created with `autoSolve: false` opts out.
26
+ */
27
+ autoSolve?: boolean | number;
22
28
  /** Extra raw CLI arguments passed to the binary. */
23
29
  args?: string[];
24
30
  /** Extra environment variables for the server process. */
@@ -45,3 +51,56 @@ export declare function launch(options?: LaunchOptions): Promise<NokkServer>;
45
51
 
46
52
  /** Absolute path to the bundled `nokk` binary (override with NOKK_BINARY). */
47
53
  export declare function binaryPath(): string;
54
+
55
+ /** The gate a page shows. Only the Cloudflare kinds are solvable. */
56
+ export type ChallengeKind =
57
+ | "none"
58
+ | "cloudflare-interstitial"
59
+ | "turnstile-widget"
60
+ | "datadome";
61
+
62
+ export type ChallengeStatus = "cleared" | "token-issued" | "cleared-but-stuck" | "timeout";
63
+
64
+ export interface ChallengeState {
65
+ kind: ChallengeKind;
66
+ title: string;
67
+ url: string;
68
+ /** A `cf_clearance` cookie is in the jar for this site. */
69
+ cleared: boolean;
70
+ /** A Turnstile widget on the page has issued its token. */
71
+ token: boolean;
72
+ solvable: boolean;
73
+ }
74
+
75
+ export interface ChallengeOutcome {
76
+ status: ChallengeStatus;
77
+ /** `status` is "cleared" or "token-issued". */
78
+ solved: boolean;
79
+ /** Checkbox presses it took (0 for a non-interactive challenge). */
80
+ presses: number;
81
+ elapsedMs: number;
82
+ /** The gate still on the page afterwards ("none" when through). */
83
+ remaining: ChallengeKind;
84
+ title: string;
85
+ url: string;
86
+ cleared: boolean;
87
+ token: boolean;
88
+ }
89
+
90
+ /** Anything the helpers can reach a page's CDP session through. */
91
+ export type PageLike =
92
+ | { createCDPSession(): Promise<{ send(method: string, params?: object): Promise<any> }> }
93
+ | { context(): { newCDPSession(page: any): Promise<{ send(method: string, params?: object): Promise<any> }> } }
94
+ | { send(method: string, params?: object): Promise<any> };
95
+
96
+ /**
97
+ * Solve the challenge the page shows now, pressing its checkbox if it puts
98
+ * one up. Accepts a Puppeteer Page, a Playwright Page or a raw CDP session.
99
+ */
100
+ export declare function solveChallenge(
101
+ page: PageLike,
102
+ options?: { /** Budget in ms (default: the server's, else 30000). */ timeout?: number }
103
+ ): Promise<ChallengeOutcome>;
104
+
105
+ /** What gate the page shows right now. */
106
+ export declare function challengeState(page: PageLike): Promise<ChallengeState>;
package/index.js CHANGED
@@ -2,9 +2,11 @@
2
2
  //
3
3
  // const nokk = require("nokk");
4
4
  // const puppeteer = require("puppeteer");
5
- // const server = await nokk.launch({ rotateFingerprint: true });
5
+ // const server = await nokk.launch({ autoSolve: true });
6
6
  // const browser = await puppeteer.connect({ browserWSEndpoint: server.wsEndpoint });
7
- // ...
7
+ // const page = await browser.newPage();
8
+ // await page.goto("https://www.scrapingcourse.com/cloudflare-challenge");
9
+ // // a Cloudflare challenge on the way was solved before goto resolved
8
10
  // await server.close();
9
11
  "use strict";
10
12
 
@@ -109,6 +111,9 @@ class NokkServer {
109
111
  * @param {boolean} [opts.geoipTimezone]
110
112
  * @param {boolean} [opts.allowTrackers]
111
113
  * @param {number} [opts.chromeVersion] e.g. 148
114
+ * @param {boolean|number} [opts.autoSolve] solve Cloudflare challenges on every
115
+ * navigation before it reports the load;
116
+ * `true` = 30 s budget, a number = seconds
112
117
  * @param {string[]}[opts.args] extra raw CLI args
113
118
  * @param {object} [opts.env]
114
119
  * @param {number} [opts.timeout=30000] ms to wait for readiness
@@ -125,6 +130,8 @@ async function launch(opts = {}) {
125
130
  if (opts.geoipTimezone) args.push("--geoip-timezone");
126
131
  if (opts.allowTrackers) args.push("--allow-trackers");
127
132
  if (opts.chromeVersion != null) args.push("--chrome-version", String(opts.chromeVersion));
133
+ if (opts.autoSolve === true) args.push("--auto-solve");
134
+ else if (typeof opts.autoSolve === "number") args.push("--auto-solve", String(opts.autoSolve));
128
135
  if (opts.args) args.push(...opts.args);
129
136
 
130
137
  const proc = spawn(binaryPath(), args, {
@@ -154,4 +161,55 @@ async function launch(opts = {}) {
154
161
  throw new Error(`nokk did not become ready within ${timeout}ms`);
155
162
  }
156
163
 
157
- module.exports = { launch, binaryPath, NokkServer };
164
+ // ---------------------------------------------------------------------------
165
+ // Challenges. nokk answers two non-standard CDP commands on a page's session —
166
+ // `Nokk.solveChallenge` and `Nokk.challengeState` — and sends a `Nokk.challenge`
167
+ // event when a navigation lands on a gate. These helpers take whatever the
168
+ // caller has: a Puppeteer Page, a Playwright Page, or a raw CDP session.
169
+
170
+ const _sessions = new WeakMap();
171
+
172
+ /** A CDP session bound to `target`'s page (cached per page). */
173
+ async function _session(target) {
174
+ if (!target) throw new TypeError("expected a Puppeteer/Playwright Page or a CDP session");
175
+ if (_sessions.has(target)) return _sessions.get(target);
176
+ let s;
177
+ if (typeof target.createCDPSession === "function") {
178
+ s = await target.createCDPSession(); // Puppeteer Page
179
+ } else if (typeof target.context === "function" && typeof target.context().newCDPSession === "function") {
180
+ s = await target.context().newCDPSession(target); // Playwright Page
181
+ } else if (typeof target.send === "function") {
182
+ s = target; // already a CDP session
183
+ } else {
184
+ throw new TypeError("expected a Puppeteer/Playwright Page or a CDP session");
185
+ }
186
+ _sessions.set(target, s);
187
+ return s;
188
+ }
189
+
190
+ /**
191
+ * Solve the challenge the page shows now — a Cloudflare interstitial or a
192
+ * Turnstile widget — pressing its checkbox if it puts one up. Resolves to
193
+ * `{ status, solved, presses, elapsedMs, remaining, title, url, cleared, token }`.
194
+ *
195
+ * @param {object} page Puppeteer Page, Playwright Page or CDP session
196
+ * @param {object} [opts]
197
+ * @param {number} [opts.timeout] budget in ms (default: the server's, else 30000)
198
+ */
199
+ async function solveChallenge(page, opts = {}) {
200
+ const s = await _session(page);
201
+ const params = opts.timeout != null ? { timeoutMs: opts.timeout } : {};
202
+ return s.send("Nokk.solveChallenge", params);
203
+ }
204
+
205
+ /**
206
+ * What gate the page shows: `{ kind, title, url, cleared, token, solvable }`, where
207
+ * `kind` is "none", "cloudflare-interstitial", "turnstile-widget" or another
208
+ * vendor's gate that nokk recognises but does not solve.
209
+ */
210
+ async function challengeState(page) {
211
+ const s = await _session(page);
212
+ return s.send("Nokk.challengeState");
213
+ }
214
+
215
+ module.exports = { launch, binaryPath, NokkServer, solveChallenge, challengeState };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koloss777/nokk",
3
- "version": "0.1.28-alpha",
3
+ "version": "0.1.29-alpha",
4
4
  "description": "Undetectable headless-browser engine — a lightweight, Chrome-fingerprinted CDP server for Puppeteer/Playwright scraping. npm install and connect, no browser download.",
5
5
  "keywords": [
6
6
  "scraping",