From 03b8399c4ae3253017c0f3287620aa974ac64fd3 Mon Sep 17 00:00:00 2001 From: dariyaanis Date: Tue, 29 Sep 2026 15:11:55 +0300 Subject: [PATCH] fix: pass user agent in Turnstile tasks --- README.md | 11 +++++------ src/tasks.ts | 11 ++++++----- tests/unit/sync/turnstile.test.js | 8 +++++--- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 1128a15..09f53c1 100644 --- a/README.md +++ b/README.md @@ -317,20 +317,19 @@ that the target page expects in `cf-turnstile-response`. | `action` | no | Value of the widget's `data-action` attribute, if set. | | `data` | no | Custom payload from the widget's `data-cdata` attribute, if set. | | `pagedata` | no | Value of the `chlPageData` parameter, needed for some Cloudflare challenge pages beyond the basic widget. Note the lowercase, single-word spelling -- unlike every other field on this page, the real API does not accept `pageData`. | - -There's no `userAgent` input for this type -- the worker picks its own while solving -and returns it in the response instead (see below). +| `userAgent` | no | User-Agent used by the browser that will submit the token. Required for Cloudflare Challenge pages. | **Response:** `token` -- submit as `cf-turnstile-response` -- and `userAgent`, the User-Agent the -worker actually solved with. The token is tied to that fingerprint, so submit it with that exact -User-Agent, not one you chose yourself. +worker solved with. The token is tied to that fingerprint. For Cloudflare Challenge pages, pass +the browser's User-Agent in the task and use the same value when submitting the token. ```javascript import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; const captchaSolver = new CaptchaClient({ clientKey: 'your_api_key' }); const task = new Tasks.TurnstileProxyless({ websiteURL: 'https://example.com/login', - websiteKey: 'YOUR_WEBSITE_KEY' + websiteKey: 'YOUR_WEBSITE_KEY', + userAgent: 'Mozilla/5.0 ...' }); const result = await captchaSolver.solve(task); console.log(result.token); diff --git a/src/tasks.ts b/src/tasks.ts index eeb850e..670403d 100644 --- a/src/tasks.ts +++ b/src/tasks.ts @@ -340,6 +340,8 @@ export interface TurnstileProxylessParams { * the API does not accept `pageData`. */ pagedata?: string | null; + /** User-Agent used by the browser that will submit the token. */ + userAgent?: string | null; } /** Parameters of {@link Turnstile}. */ @@ -347,14 +349,13 @@ export interface TurnstileParams extends TurnstileProxylessParams, ProxyParams { /** * Cloudflare Turnstile without a proxy (`TurnstileTaskProxyless`). - * There is no `userAgent` input: the worker picks one and returns it in the solution. * Solution: {@link TurnstileSolution}. * * @see https://captcha-solver.com/en/docs/captcha-types#cloudflare-turnstile */ export class TurnstileProxyless extends BaseTask { - constructor({ websiteURL, websiteKey, action = null, data = null, pagedata = null }: TurnstileProxylessParams) { - super({ type: 'TurnstileTaskProxyless', websiteURL, websiteKey, action, data, pagedata }); + constructor({ websiteURL, websiteKey, action = null, data = null, pagedata = null, userAgent = null }: TurnstileProxylessParams) { + super({ type: 'TurnstileTaskProxyless', websiteURL, websiteKey, action, data, pagedata, userAgent }); } } @@ -365,8 +366,8 @@ export class TurnstileProxyless extends BaseTask { * @see https://captcha-solver.com/en/docs/captcha-types#cloudflare-turnstile */ export class Turnstile extends BaseTask { - constructor({ websiteURL, websiteKey, action = null, data = null, pagedata = null, proxyType, proxyAddress, proxyPort, proxyLogin = null, proxyPassword = null }: TurnstileParams) { - super({ type: 'TurnstileTask', websiteURL, websiteKey, action, data, pagedata, proxyType, proxyAddress, proxyPort, proxyLogin, proxyPassword }); + constructor({ websiteURL, websiteKey, action = null, data = null, pagedata = null, userAgent = null, proxyType, proxyAddress, proxyPort, proxyLogin = null, proxyPassword = null }: TurnstileParams) { + super({ type: 'TurnstileTask', websiteURL, websiteKey, action, data, pagedata, userAgent, proxyType, proxyAddress, proxyPort, proxyLogin, proxyPassword }); } } diff --git a/tests/unit/sync/turnstile.test.js b/tests/unit/sync/turnstile.test.js index 72948ec..369496c 100644 --- a/tests/unit/sync/turnstile.test.js +++ b/tests/unit/sync/turnstile.test.js @@ -36,7 +36,7 @@ describe('TurnstileProxyless', () => { expect(result.pageData).toBeUndefined(); }); - test('does not accept userAgent as input -- the API only returns it in the solution', () => { + test('includes the browser userAgent for Cloudflare Challenge pages', () => { const task = new Tasks.TurnstileProxyless({ websiteURL: 'https://example.com', websiteKey: 'test_key', @@ -44,7 +44,7 @@ describe('TurnstileProxyless', () => { }); const result = task.toDict(); - expect(result.userAgent).toBeUndefined(); + expect(result.userAgent).toBe('Mozilla/5.0'); }); }); @@ -55,13 +55,15 @@ describe('Turnstile with proxy', () => { websiteKey: 'test_key', proxyType: 'http', proxyAddress: '1.2.3.4', - proxyPort: 8080 + proxyPort: 8080, + userAgent: 'Mozilla/5.0' }); const result = task.toDict(); expect(result.type).toBe('TurnstileTask'); expect(result.proxyType).toBe('http'); expect(result.proxyAddress).toBe('1.2.3.4'); + expect(result.userAgent).toBe('Mozilla/5.0'); }); });