capskip 1.3.0 → 1.5.0

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/CHANGELOG.md CHANGED
@@ -5,6 +5,83 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.5.0] - 2026-11-02
9
+
10
+ ### Added
11
+
12
+ - **DataDome support** via `dataDome(captchaUrl, url, options)`, sent as
13
+ `method=datadome`, covering both DataDome challenges: the slider
14
+ (`/captcha/`) and the device check (`/interstitial/`). The arguments follow
15
+ 2Captcha's `datadome(captcha_url, pageurl, userAgent, proxy)`, with
16
+ `userAgent` and `proxy` as optional options, as on every other method.
17
+ - The result carries the cookie twice: `code` exactly as DataDome set it,
18
+ attributes included, and `cookie`, the `datadome=...` pair alone for a Cookie
19
+ header. `userAgent` is the User-Agent the cookie was earned with, the one sent
20
+ or CapSkip's own browser's.
21
+ - `proxy` in the result: when a task is sent without a proxy and the CapSkip app
22
+ solves it through its own DataDome proxy list, the result names that proxy as
23
+ `host:port`. The cookie only works from that address, so it is reported
24
+ rather than dropped. Polled results now carry a `proxy` field whenever the
25
+ server sends one.
26
+ - Parameter aliases `captchaUrl` / `captchaURL` for `captcha_url`.
27
+ - TypeScript definitions for `DataDomeOptions`, plus the new `SolveResult`
28
+ fields `cookie` and `proxy`.
29
+ - `examples/datadome.js`.
30
+
31
+ ### Notes
32
+
33
+ - Refused locally, before a round-trip: a `captcha_url` that is not an https
34
+ challenge address on captcha-delivery.com carrying `initialCid`, `hash` and
35
+ `cid`; a block page that bans the requesting address (`t=bv`); a `pageurl`
36
+ that is not http(s), or is on captcha-delivery.com; and a `userAgent` that is
37
+ not stock Chrome for Windows. CapSkip itself checks that the Chrome major
38
+ version matches its browser and answers `ERROR_WRONG_USERAGENT` when it does
39
+ not. An optional passed as `''` is treated as not sent.
40
+ - An iframe `src` copied HTML-escaped (`&`) is accepted, as the server
41
+ decodes it, and is sent unchanged.
42
+ - DataDome uses `recaptchaTimeout`: the challenge is passed in a real browser.
43
+
44
+ ## [1.4.0] - 2026-11-01
45
+
46
+ ### Added
47
+
48
+ - **AWS WAF support** via `amazonWaf(sitekey, url, options)`, sent as
49
+ `method=amazon_waf`. Only `sitekey` and `url` are required.
50
+ On a challenge page (the HTTP 405 page with `window.gokuProps`), `sitekey`,
51
+ `iv` and `context` are `gokuProps.key`, `.iv` and `.context`, and
52
+ `challenge_script` / `captcha_script` are the page's challenge.js and
53
+ captcha.js. On a widget page (`AwsWafCaptcha.renderCaptcha`), `sitekey` is the
54
+ widget's `apiKey` and `jsapiScript` the URL of jsapi.js.
55
+ An AWS WAF answer is **not a token**: the result expands into
56
+ `captcha_voucher` and `existing_token`, which the site's `voucher` endpoint
57
+ (beside challenge.js) trades for the `aws-waf-token` cookie. `code` keeps the
58
+ raw answer.
59
+ - Parameter aliases `challengeScript` for `challenge_script`, `captchaScript`
60
+ for `captcha_script`, and `jsapi_script` for `jsapiScript`.
61
+ - TypeScript definitions for `AmazonWafOptions`, plus the new `SolveResult`
62
+ fields.
63
+ - `examples/amazon_waf.js`.
64
+
65
+ ### Changed
66
+
67
+ - The Capy answer expansion is now a shared helper, used by Capy and AWS WAF
68
+ alike. Capy results are unchanged.
69
+ - The exported `version` constant now reports the package version; it had been
70
+ left at `1.1.0`.
71
+
72
+ ### Notes
73
+
74
+ - `iv` and `context` are optional but travel together: one without the other is
75
+ refused locally, as the server would refuse it. Left out, CapSkip reads both
76
+ from the page at solve time. An optional passed as `''` is treated as not
77
+ sent, the way the server reads it.
78
+ - Every script URL must be `https` on an `awswaf.com` host, with no credentials,
79
+ no port other than 443, and a path ending in `challenge.js`, `captcha.js` or
80
+ `jsapi.js`. CapSkip posts the solution to the host the script names, so any
81
+ other script is refused before the request is made.
82
+ - AWS WAF uses `recaptchaTimeout`: a real browser mints the token the voucher is
83
+ issued for.
84
+
8
85
  ## [1.3.0] - 2026-09-22
9
86
 
10
87
  ### Added
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
6
6
  [![Tests](https://github.com/capskip/capskip-node/actions/workflows/ci.yml/badge.svg)](https://github.com/capskip/capskip-node/actions/workflows/ci.yml)
7
7
 
8
- **Solve reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile, GeeTest, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha and image captchas from Node.js.**
8
+ **Solve reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile, GeeTest, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha, AWS WAF, DataDome and image captchas from Node.js.**
9
9
 
10
10
  Official Node.js client for [CapSkip](https://capskip.com), a **local captcha solver** that runs on your own machine. Licensed once, not billed per solve. TypeScript definitions included.
11
11
 
@@ -19,7 +19,7 @@ npm install capskip
19
19
 
20
20
  CapSkip is a desktop app. It does the solving on your machine and exposes the standard captcha-solver HTTP API — the same `in.php` / `res.php` endpoints every 2captcha-compatible client already speaks — on `127.0.0.1:8080`.
21
21
 
22
- This SDK is a thin wrapper over that API, with the method names you would expect: `normal()`, `recaptcha()`, `turnstile()`, `geetest()`, `altcha()`, `capy()`, `captchafox()`, `friendlyCaptcha()`. Nothing leaves your network, and there is no credit balance to keep an eye on.
22
+ This SDK is a thin wrapper over that API, with the method names you would expect: `normal()`, `recaptcha()`, `turnstile()`, `geetest()`, `altcha()`, `capy()`, `captchafox()`, `friendlyCaptcha()`, `amazonWaf()`, `dataDome()`. Nothing leaves your network, and there is no credit balance to keep an eye on.
23
23
 
24
24
  ## Supported captcha types
25
25
 
@@ -38,6 +38,9 @@ This SDK is a thin wrapper over that API, with the method names you would expect
38
38
  | **Capy Puzzle solver** (slide puzzle) | `solver.capy(sitekey, url)` |
39
39
  | **CaptchaFox solver** (widget) | `solver.captchafox(sitekey, url)` |
40
40
  | **Friendly Captcha solver** (proof-of-work) | `solver.friendlyCaptcha(sitekey, url, { version })` |
41
+ | **AWS WAF solver** (challenge page) | `solver.amazonWaf(sitekey, url, { iv, context })` |
42
+ | AWS WAF (widget) | `solver.amazonWaf(sitekey, url, { jsapiScript })` |
43
+ | **DataDome solver** (slider and device check) | `solver.dataDome(captchaUrl, url)` |
41
44
 
42
45
  **hCaptcha and FunCaptcha/Arkose are not supported.** hCaptcha is the one people misidentify most often, since it also puts a `data-sitekey` on the widget — check for `class="h-captcha"` or a `js.hcaptcha.com` script before reaching for `recaptcha()`.
43
46
 
@@ -135,7 +138,7 @@ const solver = new CapSkip({
135
138
  port: 8080, // CapSkip port from app settings
136
139
  defaultTimeout: 120, // seconds — image captcha polling timeout
137
140
  recaptchaTimeout: 300, // seconds — reCAPTCHA / Turnstile / GeeTest / CaptchaFox /
138
- // Friendly Captcha polling timeout
141
+ // Friendly Captcha / AWS WAF / DataDome polling timeout
139
142
  // (ALTCHA and Capy use defaultTimeout — neither is a browser solve)
140
143
  pollingInterval: 5, // max seconds between res.php polls (starts at 0.25s, backs off to this)
141
144
  });
@@ -313,6 +316,110 @@ a script URL, v1 is assumed. Pass `api_server: 'eu'` for a sitekey on the EU
313
316
  data-residency tenant. A v2 token is roughly six kilobytes, so size whatever
314
317
  carries it accordingly.
315
318
 
319
+ ### AWS WAF
320
+
321
+ AWS WAF puts its CAPTCHA either on a **challenge page** — the HTTP 405
322
+ interstitial that sets `window.gokuProps` — or in a **widget** an ordinary page
323
+ renders with `AwsWafCaptcha.renderCaptcha`. Only `sitekey` and `url` are required.
324
+
325
+ ```js
326
+ // Challenge page: sitekey, iv and context are gokuProps.key, .iv and .context
327
+ const result = await solver.amazonWaf(
328
+ 'AQIDAHjcYuEXAMPLEghicBgQ/7bFaQZ+m5FKCMDnO+vTbNg96AHDh0IR5vgzHNceHYqZR+GOAAAAfjB8',
329
+ 'https://example.com/protected',
330
+ {
331
+ iv: 'CgAFRDIjfgAAA5yY',
332
+ context: 'Rbp3r8fvbuhmrCqpUe8Mh1TLvs8OSKJZiSs2vCF4dt9rxVZI7xYJa2n5k/9r+XaXe7KUIu2I8nXTDHHkDvIOTDc2',
333
+ challenge_script: 'https://3f2a9c7d41b0.edge.sdk.awswaf.com/3f2a9c7d41b0/8e51d0c7a2f4/challenge.js',
334
+ },
335
+ );
336
+
337
+ // Widget page: sitekey is the widget's apiKey, plus the jsapi.js it loads
338
+ const result = await solver.amazonWaf(
339
+ 'Xr9kEXAMPLE2bWQx1vTq8LmZ0aYc4dHs7nFp3gJt6uKw5eRi9AbCdEfGh=_0_1',
340
+ 'https://example.com/protected',
341
+ { jsapiScript: 'https://5b7e0a19c3d8.us-east-1.captcha-sdk.awswaf.com/5b7e0a19c3d8/jsapi.js' },
342
+ );
343
+
344
+ result.captcha_voucher, result.existing_token;
345
+ ```
346
+
347
+ `iv` and `context` are optional and travel together: send both or neither. Left
348
+ out, CapSkip reads them from the page at solve time, which is the safer choice
349
+ when your copy of the page is more than a few minutes old.
350
+
351
+ The answer is not a token. POST `captcha_voucher` and `existing_token` as JSON
352
+ to the `voucher` endpoint beside the page's `challenge.js`; the response's
353
+ `token` is the `aws-waf-token` cookie. The pair is short-lived, so exchange it
354
+ immediately rather than storing it.
355
+
356
+ Every script URL (`challenge_script`, `captcha_script`, `jsapiScript`) must be an
357
+ https URL on an `awswaf.com` host. CapSkip posts the solution to the host the
358
+ script names, so anything else is refused before the request is made. See the
359
+ [AWS WAF API docs](https://capskip.com/api-docs/#aws-waf).
360
+
361
+ ### DataDome
362
+
363
+ DataDome answers a request it doubts with a block page (status 403, sometimes
364
+ 401) instead of the site. CapSkip passes the challenge on that page, a slider
365
+ or a device check, and returns the `datadome` cookie that lets your requests
366
+ through. The arguments follow 2Captcha's `datadome(captcha_url, pageurl,
367
+ userAgent, proxy)`, with `userAgent` and `proxy` as optional options.
368
+
369
+ ```js
370
+ const result = await solver.dataDome(
371
+ 'https://geo.captcha-delivery.com/captcha/?initialCid=...&hash=...&cid=...&t=fe&referer=...&s=45225&e=...&dm=cd',
372
+ 'https://www.example.com/', // the page that returned the block page
373
+ {
374
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
375
+ + '(KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36',
376
+ proxy: { type: 'HTTP', uri: 'login:password@203.0.113.10:8080' },
377
+ },
378
+ );
379
+
380
+ result.cookie; // "datadome=..." -- send it as the Cookie header
381
+ result.code; // the cookie exactly as DataDome set it: Max-Age, Domain, Path...
382
+ result.userAgent; // the User-Agent the cookie was earned with
383
+ result.proxy; // set only when CapSkip picked the proxy, see below
384
+ ```
385
+
386
+ `captchaUrl` is the address of the challenge iframe on
387
+ `geo.captcha-delivery.com`: `/captcha/` for a slider, `/interstitial/` for a
388
+ device check. Take the iframe's `src` when the block page has one; most build it
389
+ with a script instead, so assemble it from the page's `var dd={...}` object as
390
+ the [API docs](https://capskip.com/api-docs/#datadome) show. Each block page is
391
+ a fresh challenge, so submit it soon and fetch the page again for the next task.
392
+
393
+ **The cookie belongs to one IP address and one browser.** DataDome refuses it
394
+ from any other address, or with any other User-Agent, so:
395
+
396
+ - **Send the User-Agent you will use**, or none. It must be Chrome for Windows,
397
+ with the same major version as the browser CapSkip solves with; anything else
398
+ (Firefox, Safari, Edge, mobile, macOS) would earn a cookie the site rejects,
399
+ so it is refused before the request is sent, and a different Chrome major is
400
+ refused by CapSkip with `ERROR_WRONG_USERAGENT`. Send none and CapSkip uses
401
+ its own browser's: copy `result.userAgent` into your requests as it is.
402
+ - **Solve from the address you will use the cookie from.** If your requests go
403
+ out through a proxy, pass that proxy with the task. If they come from the
404
+ machine running CapSkip, pass none.
405
+ - **When CapSkip picks the proxy, it tells you which.** The CapSkip app has a
406
+ DataDome proxy list of its own (Settings, Captcha types, DataDome, Configure
407
+ Proxies). A task sent without a proxy is solved through that list, and the
408
+ result then carries `proxy` as `host:port`: send the cookie through that
409
+ proxy, or it will be refused. A proxy you send always takes priority over
410
+ the list, and then no `proxy` comes back.
411
+
412
+ ```js
413
+ const headers = { 'User-Agent': result.userAgent, Cookie: result.cookie };
414
+ // When result.proxy is set, route this request through it (it is named without
415
+ // its login: add your credentials if it has one), e.g. with undici's ProxyAgent.
416
+ const page = await fetch('https://www.example.com/', { headers });
417
+ ```
418
+
419
+ A block page whose `dd` object carries `'t':'bv'` means DataDome has banned the
420
+ address that fetched it; no solve from there can pass, so it is refused before
421
+ the request is sent. Change the address and request the page again.
422
+
316
423
  ### With a proxy (every method except image captcha)
317
424
 
318
425
  ```js
@@ -411,8 +518,12 @@ GeeTest additionally expands its answer into `challenge`, `validate`, and
411
518
  `seccode`, while `code` keeps the raw JSON string. CaptchaFox and Friendly
412
519
  Captcha add `token`, the same string as `code`. Capy expands its answer into
413
520
  `captchakey`, `challengekey` and `answer` — it returns an object rather than a
414
- token, and `code` keeps the raw answer. ALTCHA adds `token` (the same
415
- string as `code`) and `number`, the counter that solved it.
521
+ token, and `code` keeps the raw answer. AWS WAF likewise returns an object,
522
+ expanded into `captcha_voucher` and `existing_token`. ALTCHA adds `token` (the same
523
+ string as `code`) and `number`, the counter that solved it. DataDome adds
524
+ `cookie`, the `datadome=...` pair alone, while `code` keeps the cookie with its
525
+ attributes; it also carries `userAgent`, and `proxy` when CapSkip chose the
526
+ proxy.
416
527
 
417
528
  ---
418
529
 
@@ -457,7 +568,7 @@ const result: SolveResult = await solver.recaptcha('...', 'https://example.com')
457
568
 
458
569
  ### How do I solve a captcha in Node.js?
459
570
 
460
- Install the CapSkip desktop app, `npm install capskip`, then call the method that matches the widget — `recaptcha()`, `turnstile()`, `geetest()`, `altcha()`, `capy()`, `captchafox()`, `friendlyCaptcha()` or `normal()`. Each returns a Promise that resolves once CapSkip has an answer, giving you a token, or the recognized text in the case of an image captcha.
571
+ Install the CapSkip desktop app, `npm install capskip`, then call the method that matches the widget — `recaptcha()`, `turnstile()`, `geetest()`, `altcha()`, `capy()`, `captchafox()`, `friendlyCaptcha()`, `amazonWaf()`, `dataDome()` or `normal()`. Each returns a Promise that resolves once CapSkip has an answer, giving you a token (a cookie for DataDome), or the recognized text in the case of an image captcha.
461
572
 
462
573
  ### Is this a free captcha solver?
463
574
 
@@ -465,7 +576,7 @@ The SDK itself is MIT-licensed and free. Solving needs the CapSkip app, which is
465
576
 
466
577
  ### Which captchas can it solve?
467
578
 
468
- reCAPTCHA v2 (checkbox and invisible), reCAPTCHA v3, reCAPTCHA Enterprise, Cloudflare Turnstile, GeeTest v3, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha, and image/text captchas. Not hCaptcha, and not FunCaptcha/Arkose.
579
+ reCAPTCHA v2 (checkbox and invisible), reCAPTCHA v3, reCAPTCHA Enterprise, Cloudflare Turnstile, GeeTest v3, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha, AWS WAF, DataDome, and image/text captchas. Not hCaptcha, and not FunCaptcha/Arkose.
469
580
 
470
581
  ### Does it work with Puppeteer and Playwright?
471
582
 
@@ -9,7 +9,7 @@ POST http://<host>:<port>/in.php → submit captcha
9
9
  GET http://<host>:<port>/res.php → poll result
10
10
  ```
11
11
 
12
- The SDK only supports the four captcha types documented by CapSkip. Every solve
12
+ The SDK only supports the captcha types documented by CapSkip. Every solve
13
13
  method returns a `Promise`.
14
14
 
15
15
  ---
@@ -27,6 +27,8 @@ method returns a `Promise`.
27
27
  | Capy Puzzle (slide) | `capy()` | `capy` |
28
28
  | CaptchaFox (widget) | `captchafox()` | `captchafox` |
29
29
  | Friendly Captcha (proof-of-work) | `friendlyCaptcha()` | `friendly_captcha` |
30
+ | AWS WAF (challenge page or widget) | `amazonWaf()` | `amazon_waf` |
31
+ | DataDome (slider and device check) | `dataDome()` | `datadome` |
30
32
 
31
33
  **Proxy** is supported for every method except image captcha. For ALTCHA the
32
34
  proxy is used only for the `challengeUrl` fetch; for Capy, only for the
@@ -612,6 +614,238 @@ browser.
612
614
 
613
615
  ---
614
616
 
617
+ ## 10. AWS WAF — `amazonWaf(sitekey, url, { ... })`
618
+
619
+ AWS WAF puts its CAPTCHA either on a challenge page — the HTTP 405 interstitial
620
+ that sets `window.gokuProps` — or in a widget an ordinary page renders with
621
+ `AwsWafCaptcha.renderCaptcha`. CapSkip solves both in a real browser. See the
622
+ [CapSkip API docs](https://capskip.com/api-docs/#aws-waf).
623
+
624
+ ### POST `/in.php`
625
+
626
+ | Parameter | Type | Required | Description |
627
+ |---|---|---|---|
628
+ | `key` | string | Yes | CapSkip API key |
629
+ | `method` | string | Yes | `amazon_waf` |
630
+ | `sitekey` | string | Yes | Challenge page: `gokuProps.key`. Widget page: the widget's `apiKey` |
631
+ | `pageurl` | string | Yes | Full http or https URL of the protected page |
632
+ | `iv` | string | No | Challenge page: `gokuProps.iv`. Send together with `context` |
633
+ | `context` | string | No | Challenge page: `gokuProps.context`. Send together with `iv` |
634
+ | `challenge_script` | string | No | Full URL of the page's `challenge.js` |
635
+ | `captcha_script` | string | No | Full URL of the page's `captcha.js` |
636
+ | `jsapiScript` | string | No | Widget page: full URL of `jsapi.js` |
637
+ | `json` | int | No | `0` plain text, `1` JSON |
638
+ | `proxy` | string | No | Proxy address |
639
+ | `proxytype` | string | No | Proxy type |
640
+
641
+ The SDK also accepts `challengeScript`, `captchaScript` and `jsapi_script`, and
642
+ sends the documented spelling of each. An optional left `undefined`, `null` or
643
+ `''` is not sent — the server reads an empty value as absent.
644
+
645
+ ### Challenge page or widget page
646
+
647
+ | | Challenge page | Widget page |
648
+ |---|---|---|
649
+ | Recognized by | HTTP 405 with `window.gokuProps` in an inline script | `AwsWafCaptcha.renderCaptcha(container, { apiKey, … })` |
650
+ | `sitekey` | `gokuProps.key` | the widget's `apiKey` |
651
+ | `iv` / `context` | `gokuProps.iv` / `gokuProps.context`, or neither | not sent |
652
+ | Scripts | `challenge_script`, `captcha_script` | `jsapiScript` |
653
+
654
+ `iv` and `context` come off the same page and only work as a pair, so the SDK
655
+ refuses one without the other. Both are optional: left out, CapSkip reads them
656
+ from `pageurl` at solve time, which is the safer choice when your copy of the
657
+ page is more than a few minutes old.
658
+
659
+ ### Script URLs
660
+
661
+ CapSkip posts the solution to the host a script URL names, so it refuses any
662
+ script that is not on AWS WAF. The SDK applies the server's rule before the
663
+ request is made: the URL must be `https`, its host must end in `.awswaf.com`, it
664
+ must carry no credentials and no port other than 443, and its path must be one
665
+ or more segments followed by `challenge.js`, `captcha.js` or `jsapi.js`. A query
666
+ string is allowed. Anything else throws `ValidationException`, because CapSkip is
667
+ not an AWS WAF integration for scripts hosted elsewhere.
668
+
669
+ ### GET `/res.php`
670
+
671
+ The answer is **not a token**. With `json=1` it is an object in `request`:
672
+
673
+ ```json
674
+ {"status":1,"request":{"captcha_voucher":"…","existing_token":"…"}}
675
+ ```
676
+
677
+ and in plain text the same object as one line of JSON after `OK|`. The SDK
678
+ expands it into `captcha_voucher` and `existing_token`; see
679
+ [Return value](#return-value).
680
+
681
+ ### Exchanging the voucher
682
+
683
+ The two values only work as a pair. POST them as JSON to the `voucher` endpoint
684
+ beside the page's `challenge.js` (the same directory, with `voucher` in place of
685
+ `challenge.js`). The response's `token` is the `aws-waf-token` cookie. Exchange
686
+ them immediately rather than storing them for later.
687
+
688
+ ### SDK usage
689
+
690
+ ```js
691
+ // Challenge page
692
+ const result = await solver.amazonWaf(
693
+ 'AQIDAHjcYuEXAMPLEghicBgQ/7bFaQZ+m5FKCMDnO+vTbNg96AHDh0IR5vgzHNceHYqZR+GOAAAAfjB8',
694
+ 'https://example.com/protected',
695
+ {
696
+ iv: 'CgAFRDIjfgAAA5yY',
697
+ context: 'Rbp3r8fvbuhmrCqpUe8Mh1TLvs8OSKJZiSs2vCF4dt9rxVZI7xYJa2n5k/9r+XaXe7KUIu2I8nXTDHHkDvIOTDc2',
698
+ challenge_script: 'https://3f2a9c7d41b0.edge.sdk.awswaf.com/3f2a9c7d41b0/8e51d0c7a2f4/challenge.js',
699
+ },
700
+ );
701
+
702
+ // Widget page
703
+ const result = await solver.amazonWaf(
704
+ 'Xr9kEXAMPLE2bWQx1vTq8LmZ0aYc4dHs7nFp3gJt6uKw5eRi9AbCdEfGh=_0_1',
705
+ 'https://example.com/protected',
706
+ { jsapiScript: 'https://5b7e0a19c3d8.us-east-1.captcha-sdk.awswaf.com/5b7e0a19c3d8/jsapi.js' },
707
+ );
708
+
709
+ result.captcha_voucher, result.existing_token;
710
+ ```
711
+
712
+ Uses `recaptchaTimeout`: a real browser mints the token the voucher is issued
713
+ for, and the puzzle rounds go through AWS on top of that.
714
+
715
+ ---
716
+
717
+ ## 11. DataDome — `dataDome(captchaUrl, url, { ... })`
718
+
719
+ DataDome is the bot protection behind many large shops, news sites and
720
+ marketplaces. When it doubts a visitor, the site answers with a block page
721
+ (status 403, sometimes 401) holding a slider or a short device check. CapSkip
722
+ passes that challenge and returns the `datadome` cookie; requests that carry it
723
+ are let through. Public docs: <https://capskip.com/api-docs/#datadome>.
724
+
725
+ The arguments follow 2Captcha's `datadome(captcha_url, pageurl, userAgent,
726
+ proxy)`, so code written against it ports directly; `userAgent` and `proxy` go
727
+ in the options object and are optional here.
728
+
729
+ ### POST `/in.php`
730
+
731
+ | Parameter | Type | Required | Description |
732
+ |---|---|---|---|
733
+ | `key` | string | Yes | CapSkip API key |
734
+ | `method` | string | Yes | `datadome` |
735
+ | `captcha_url` | string | Yes | Address of the challenge iframe on `geo.captcha-delivery.com` |
736
+ | `pageurl` | string | Yes | Full http or https URL of the page that returned the block page |
737
+ | `userAgent` | string | No | The User-Agent you will send with the cookie: Chrome for Windows, same major version as CapSkip's browser. Sent as `useragent` |
738
+ | `json` | int | No | `0` plain text, `1` JSON |
739
+ | `proxy` | string | No | The proxy your later requests go through |
740
+ | `proxytype` | string | No | `HTTP`, `HTTPS`, `SOCKS5` or `SOCKS5H` |
741
+
742
+ The SDK also accepts `captchaUrl` / `captchaURL` for `captcha_url`. An optional
743
+ left `undefined`, `null` or `''` is not sent — the server reads an empty value
744
+ as absent. The `captcha_url` is sent exactly as given.
745
+
746
+ Refused by the SDK before the request is made, as the server would refuse them
747
+ (`ValidationException`):
748
+
749
+ - a missing `captcha_url` or `pageurl`;
750
+ - a `captcha_url` that is not https on captcha-delivery.com (or a subdomain),
751
+ under `/captcha/` or `/interstitial/`, carrying `initialCid`, `hash` and
752
+ `cid` (server: `ERROR_BAD_PARAMETERS`);
753
+ - a `captcha_url` with `t=bv`: the block page bans the address that fetched it,
754
+ so nothing sent from there can pass (server: `ERROR_PROXY_BANNED`);
755
+ - a `pageurl` that is not http(s), or that points at captcha-delivery.com
756
+ (server: `ERROR_PAGEURL`);
757
+ - a `userAgent` that is not stock Chrome for Windows: Firefox, Safari, Edge,
758
+ Opera, macOS, Linux, Android and iOS all fail (server:
759
+ `ERROR_WRONG_USERAGENT`).
760
+
761
+ The Chrome **major version** is checked by CapSkip itself, against the browser
762
+ it solves with, and a mismatch comes back as `ApiException('ERROR_WRONG_USERAGENT')`.
763
+
764
+ ### Building the `captchaUrl`
765
+
766
+ If the block page already contains the challenge iframe, take its `src` as it
767
+ is (an HTML-escaped `&amp;` is fine). Most block pages build it with a script
768
+ from a `var dd={...}` object instead:
769
+
770
+ ```js
771
+ const dd = JSON.parse(html.match(/var dd=(\{[^}]*\})/)[1].replace(/'/g, '"'));
772
+ const path = dd.rt === 'i' ? 'interstitial' : 'captcha';
773
+ const q = new URLSearchParams({ initialCid: dd.cid, hash: dd.hsh, cid: dd.cookie });
774
+ if (dd.t) q.set('t', dd.t);
775
+ q.set('referer', pageUrl);
776
+ q.set('s', String(dd.s));
777
+ for (const k of ['e', 'b']) {
778
+ if (dd[k]) q.set(k, String(dd[k]));
779
+ }
780
+ q.set('dm', 'cd');
781
+ const captchaUrl = `https://${dd.host || 'geo.captcha-delivery.com'}/${path}/?${q}`;
782
+ ```
783
+
784
+ Each block page is a fresh challenge: submit it soon, and request the page again
785
+ for the next task rather than reusing an old `captchaUrl`.
786
+
787
+ ### The cookie belongs to one IP and one User-Agent
788
+
789
+ DataDome refuses the cookie from any other address, or with any other
790
+ User-Agent:
791
+
792
+ - **Solve from the address you will use the cookie from.** Pass the proxy your
793
+ requests go through; pass none if they come from the machine running CapSkip.
794
+ - **Send the User-Agent you will use, or none.** With none, CapSkip uses its
795
+ browser's own, and the result names it.
796
+ - **CapSkip's own DataDome proxy list.** The app can hold a list of proxies for
797
+ DataDome (Settings, Captcha types, DataDome, Configure Proxies). A task sent
798
+ without a proxy is solved through that list, and the result then carries
799
+ `proxy`: that proxy as `host:port`, without its login. The cookie only works
800
+ through it. A proxy sent with the task always takes priority, and then no
801
+ `proxy` comes back.
802
+
803
+ ### GET `/res.php`
804
+
805
+ The SDK polls with `json=1`, where CapSkip returns the cookie in `request` with
806
+ `userAgent` and, when its list supplied the proxy, `proxy` next to it:
807
+
808
+ ```json
809
+ {
810
+ "status": 1,
811
+ "request": "datadome=FAPPpli4...; Max-Age=31536000; Domain=.example.com; Path=/; Secure; SameSite=Lax",
812
+ "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36",
813
+ "proxy": "203.0.113.10:8080"
814
+ }
815
+ ```
816
+
817
+ (In plain-text mode the same two values arrive in the `X-DataDome-User-Agent`
818
+ and `X-DataDome-Proxy` response headers.) See [Return value](#return-value).
819
+
820
+ ### SDK usage
821
+
822
+ ```js
823
+ // 2Captcha's argument order, with userAgent and proxy as options
824
+ const result = await solver.dataDome(captchaUrl, 'https://www.example.com/', {
825
+ userAgent,
826
+ proxy: { type: 'HTTP', uri: 'login:password@203.0.113.10:8080' },
827
+ });
828
+
829
+ // No proxy, no User-Agent: CapSkip's browser, and its proxy list if it has one
830
+ const result = await solver.dataDome(captchaUrl, 'https://www.example.com/');
831
+
832
+ const headers = {
833
+ 'User-Agent': result.userAgent,
834
+ Cookie: result.cookie, // "datadome=..."
835
+ };
836
+ if (result.proxy) {
837
+ // send this request through result.proxy
838
+ }
839
+ ```
840
+
841
+ DataDome may send a replacement `datadome` cookie on later responses; keep the
842
+ newest one, as a browser would. When the block page comes back anyway, build a
843
+ new `captchaUrl` from it and submit a new task.
844
+
845
+ Uses `recaptchaTimeout`: the challenge is passed in a real browser.
846
+
847
+ ---
848
+
615
849
  ## Return value
616
850
 
617
851
  Every solve method resolves to:
@@ -663,6 +897,34 @@ target form takes (`code` keeps the raw answer):
663
897
  }
664
898
  ```
665
899
 
900
+ AWS WAF returns an object too, expanded into the voucher pair the site's
901
+ `voucher` endpoint trades for the `aws-waf-token` cookie (`code` keeps the raw
902
+ answer):
903
+
904
+ ```js
905
+ {
906
+ captchaId: '12345',
907
+ code: { captcha_voucher: '…', existing_token: '…' },
908
+ captcha_voucher: 'eyJ2b3VjaGVyIjoiRVhBTVBMRSJ9.9QwZcEXAMPLEvoucherJk4LmN0pQ',
909
+ existing_token: '7d4c2a91-EXAMPLE-4f0b-9e6d-3a1b5c8e2f70:EQoAeXAMPLEtokenAAAA:Wm9wc2E=',
910
+ }
911
+ ```
912
+
913
+ DataDome returns a cookie. `code` keeps it exactly as DataDome set it, for a
914
+ cookie jar; `cookie` is the `datadome=...` pair alone, for a Cookie header.
915
+ `userAgent` is the User-Agent the cookie was earned with, and `proxy` appears
916
+ only when the task sent no proxy and CapSkip used its own DataDome proxy list:
917
+
918
+ ```js
919
+ {
920
+ captchaId: '12345',
921
+ code: 'datadome=FAPPpli4...; Max-Age=31536000; Domain=.example.com; Path=/; Secure; SameSite=Lax',
922
+ cookie: 'datadome=FAPPpli4...',
923
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36',
924
+ proxy: '203.0.113.10:8080',
925
+ }
926
+ ```
927
+
666
928
  GeeTest additionally expands its answer into `challenge`, `validate`, and
667
929
  `seccode` (`code` keeps the raw JSON string):
668
930
 
@@ -697,6 +959,10 @@ Convenience aliases mapped before sending to CapSkip:
697
959
  | `captchaKey` | `captchakey` |
698
960
  | `moduleScript` | `module_script` |
699
961
  | `nomoduleScript` / `noModuleScript` | `nomodule_script` |
962
+ | `challengeScript` | `challenge_script` |
963
+ | `captchaScript` | `captcha_script` |
964
+ | `jsapi_script` | `jsapiScript` |
965
+ | `captchaUrl` / `captchaURL` | `captcha_url` |
700
966
  | `proxy` object | `proxy` + `proxytype` strings |
701
967
 
702
968
  ```js
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Solve an AWS WAF CAPTCHA.
5
+ *
6
+ * AWS WAF shows up in two forms, and the values you need depend on which one
7
+ * the target page uses:
8
+ *
9
+ * * A challenge page -- the HTTP 405 interstitial that sets `window.gokuProps`
10
+ * in an inline script. `sitekey` is `gokuProps.key`; `iv` and `context` are
11
+ * `gokuProps.iv` and `gokuProps.context`. `challenge_script` and
12
+ * `captcha_script` are the full URLs of the page's challenge.js and
13
+ * captcha.js.
14
+ * * A widget page -- an ordinary page that calls
15
+ * `AwsWafCaptcha.renderCaptcha(container, { apiKey, ... })`. `sitekey` is
16
+ * that `apiKey`, and `jsapiScript` is the full URL of jsapi.js.
17
+ *
18
+ * Only `sitekey` and the page URL are required. `iv` and `context` travel
19
+ * together: send both or neither. Left out, CapSkip reads them from the page
20
+ * at solve time, which is the safer choice when your copy of the page is more
21
+ * than a few minutes old.
22
+ *
23
+ * Every script URL must be an https URL on an awswaf.com host. CapSkip posts
24
+ * the solution to the host the script names, so it refuses anything else
25
+ * before the request is made.
26
+ *
27
+ * Unlike reCAPTCHA or Turnstile, the answer is NOT a token you put in a form.
28
+ * It is a voucher pair that AWS trades for the `aws-waf-token` cookie.
29
+ */
30
+
31
+ const { CapSkip } = require('capskip');
32
+
33
+ const solver = new CapSkip({
34
+ apiKey: process.env.CAPSKIP_API_KEY || 'capskip',
35
+ host: process.env.CAPSKIP_HOST || '127.0.0.1',
36
+ port: Number(process.env.CAPSKIP_PORT || 8080),
37
+ });
38
+
39
+ const PAGE_URL = 'https://example.com/protected';
40
+
41
+ // From the challenge page's window.gokuProps.
42
+ const SITEKEY = 'AQIDAHjcYuEXAMPLEghicBgQ/7bFaQZ+m5FKCMDnO+vTbNg96AHDh0IR5vgzHNceHYqZR+GOAAAAfjB8';
43
+ const IV = 'CgAFRDIjfgAAA5yY';
44
+ const CONTEXT = 'Rbp3r8fvbuhmrCqpUe8Mh1TLvs8OSKJZiSs2vCF4dt9rxVZI7xYJa2n5k/9r+XaXe7KUIu2I8nXTDHHkDvIOTDc2';
45
+ const CHALLENGE_JS = 'https://3f2a9c7d41b0.edge.sdk.awswaf.com/3f2a9c7d41b0/8e51d0c7a2f4/challenge.js';
46
+
47
+ async function main() {
48
+ const result = await solver.amazonWaf(SITEKEY, PAGE_URL, {
49
+ iv: IV,
50
+ context: CONTEXT,
51
+ challenge_script: CHALLENGE_JS,
52
+ });
53
+
54
+ console.log('Captcha ID: ', result.captchaId);
55
+ console.log('Voucher: ', `${result.captcha_voucher.slice(0, 32)}...`);
56
+ console.log('Existing token:', `${result.existing_token.slice(0, 32)}...`);
57
+
58
+ // --- A widget page -------------------------------------------------------
59
+ // The widget's apiKey as the sitekey, and the jsapi.js it loads. No iv or
60
+ // context: a widget page has none.
61
+ //
62
+ // await solver.amazonWaf(
63
+ // 'Xr9kEXAMPLE2bWQx1vTq8LmZ0aYc4dHs7nFp3gJt6uKw5eRi9AbCdEfGh=_0_1',
64
+ // PAGE_URL,
65
+ // { jsapiScript: 'https://5b7e0a19c3d8.us-east-1.captcha-sdk.awswaf.com/5b7e0a19c3d8/jsapi.js' },
66
+ // );
67
+ //
68
+ // --- With a per-request proxy --------------------------------------------
69
+ // await solver.amazonWaf(SITEKEY, PAGE_URL, {
70
+ // proxy: { type: 'HTTP', uri: 'login:password@1.2.3.4:8080' },
71
+ // });
72
+
73
+ // Exchange the pair for the aws-waf-token cookie: POST both values as JSON
74
+ // to the `voucher` endpoint that sits beside the page's challenge.js. The
75
+ // response's `token` is the cookie value. Do this immediately -- the voucher
76
+ // is short-lived, so do not store the pair for a later request.
77
+ const voucherUrl = new URL('voucher', CHALLENGE_JS).href;
78
+ console.log('Voucher URL: ', voucherUrl);
79
+ //
80
+ // const response = await fetch(voucherUrl, {
81
+ // method: 'POST',
82
+ // headers: { 'Content-Type': 'application/json' },
83
+ // body: JSON.stringify({
84
+ // captcha_voucher: result.captcha_voucher,
85
+ // existing_token: result.existing_token,
86
+ // }),
87
+ // });
88
+ // const { token } = await response.json();
89
+ //
90
+ // // Then request the protected page with the cookie:
91
+ // await fetch(PAGE_URL, { headers: { Cookie: `aws-waf-token=${token}` } });
92
+ }
93
+
94
+ main().catch((error) => {
95
+ console.error(`${error.name}: ${error.message}`);
96
+ process.exitCode = 1;
97
+ });