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 +77 -0
- package/README.md +118 -7
- package/docs/API_REFERENCE.md +267 -1
- package/examples/amazon_waf.js +97 -0
- package/examples/datadome.js +142 -0
- package/package.json +6 -2
- package/src/apiParams.js +314 -0
- package/src/index.js +1 -1
- package/src/solver.js +160 -4
- package/types/index.d.ts +122 -6
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)
|
|
6
6
|
[](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.
|
|
415
|
-
|
|
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
|
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -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
|
|
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 `&` 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
|
+
});
|