@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 +28 -1
- package/index.d.ts +59 -0
- package/index.js +61 -3
- package/package.json +1 -1
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({
|
|
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
|
-
|
|
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.
|
|
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",
|