fiptcha 0.1.0 → 0.1.1

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
@@ -1,139 +1,233 @@
1
1
  # Fiptcha
2
2
 
3
- Local, self-hosted CAPTCHA solving primitives for browser automation. Built for Figranium, designed to be usable by other Node.js browser automation projects.
3
+ [![npm version](https://img.shields.io/npm/v/fiptcha.svg)](https://www.npmjs.com/package/fiptcha)
4
4
 
5
- Fiptcha is the standalone CAPTCHA runtime extracted from Figranium. It contains the local model/runtime layer that Figranium uses to detect resources, manage vision models, solve image grids, interact with browser challenges, and optionally use an Apple Silicon MLX companion.
5
+ Local CAPTCHA solving for browser automation.
6
6
 
7
- Fiptcha does **not** require the Figranium application or task format. The package exposes its solver modules directly, so another application can integrate the pieces it needs around its own browser lifecycle.
7
+ Fiptcha is the lightweight CAPTCHA runtime used by Figranium, packaged as a standalone Apache-2.0 Node.js library so it can be embedded in other browser automation projects.
8
8
 
9
- ## Why Fiptcha?
9
+ ## Install
10
10
 
11
- CAPTCHA solving inside a browser automation project tends to grow into its own subsystem: model selection, model downloads, resource checks, image-grid interpretation, challenge interception, browser interaction, platform-specific acceleration, and lifecycle management all need to work together. Fiptcha keeps that subsystem separate from the automation framework using it.
11
+ ```bash
12
+ npm install fiptcha
13
+ ```
14
+
15
+ CommonJS:
16
+
17
+ ```js
18
+ const { solveLocalCaptcha } = require('fiptcha');
19
+ ```
20
+
21
+ Fiptcha does not launch a browser for you. Your application owns the browser lifecycle and passes an active Playwright-compatible `page` into the solver.
22
+
23
+ ## Quick start
24
+
25
+ ```js
26
+ const { chromium } = require('playwright');
27
+ const { solveLocalCaptcha } = require('fiptcha');
28
+
29
+ const browser = await chromium.launch({ headless: false });
30
+ const page = await browser.newPage();
12
31
 
13
- The goals are:
32
+ await page.goto('https://example.com');
14
33
 
15
- - keep local CAPTCHA solving self-hostable;
16
- - avoid coupling the solver to Figranium's UI, API, task schema, or storage layer;
17
- - expose reusable Node.js modules for other browser automation projects;
18
- - download model assets only when they are needed rather than bundling large weights;
19
- - select an appropriate local backend based on available resources;
20
- - support optional companion runtimes without making them mandatory for every installation.
34
+ const logs = [];
35
+ const result = await solveLocalCaptcha(page, {
36
+ captchaType: 'recaptcha_v2',
37
+ timeout: 60_000,
38
+ logs
39
+ });
21
40
 
22
- ## Supported challenge families
41
+ console.log(result.token);
42
+ console.log(result.provider); // "local"
43
+ console.log(result.model); // present when a vision model was used
44
+ console.log(result.device); // "browser", "cpu", or the active inference device
23
45
 
24
- Fiptcha contains the local runtime used by Figranium for:
46
+ await browser.close();
47
+ ```
25
48
 
26
- - reCAPTCHA v2 image challenges;
27
- - hCaptcha image challenges;
28
- - Cloudflare Turnstile/browser challenge helpers.
49
+ ## Supported challenge types
29
50
 
30
- Support is intentionally described as challenge/runtime support rather than a guarantee that every CAPTCHA can be solved. Providers change their challenge implementations and may use additional risk signals beyond the visible challenge.
51
+ Pass one of these values as `captchaType`:
31
52
 
32
- ## Features
53
+ | Value | Behavior |
54
+ | --- | --- |
55
+ | `recaptcha_v2` | Interacts with the checkbox and solves supported image-grid challenges locally when required. |
56
+ | `hcaptcha` | Interacts with the widget and solves supported image-grid challenges locally when required. |
57
+ | `turnstile` | Performs active-browser interaction and waits for a token. It does not use the image-grid vision path. |
33
58
 
34
- - Local CAPTCHA solving from a browser automation session.
35
- - Resource-aware model selection.
36
- - Verified, on-demand model downloads and model lifecycle management.
37
- - Image-grid classification and challenge handling.
38
- - Browser challenge interception helpers.
39
- - Companion-process client and runtime support.
40
- - Optional MLX acceleration for compatible Apple Silicon environments.
41
- - Container/resource probing utilities.
42
- - CAPTCHA benchmarking helpers.
43
- - Environment-variable configuration through the `CAPTCHA_*` settings used by the runtime.
44
- - Apache-2.0 licensing for reuse in other applications.
59
+ Fiptcha cannot guarantee that every challenge will be solvable. CAPTCHA providers can change their UI, challenge format, and risk checks independently of the visible widget.
45
60
 
46
- ## Requirements
61
+ ## `solveLocalCaptcha(page, options)`
47
62
 
48
- Fiptcha is a Node.js package and expects the host application to provide the browser/page object used for challenge interaction. It does not launch or manage your entire automation application for you.
63
+ This is the main integration API.
49
64
 
50
- Model inference can require substantial memory. Fiptcha performs resource detection before selecting a local model. A provider-advertised 2 GB host is treated using decimal gigabyte semantics rather than incorrectly requiring a full 2 GiB/2048 MiB allocation.
65
+ ```js
66
+ const result = await solveLocalCaptcha(page, {
67
+ captchaType: 'hcaptcha',
68
+ timeout: 60_000,
69
+ logs: []
70
+ });
71
+ ```
51
72
 
52
- Large model files are not intended to be embedded into the npm package. They are fetched by the model-management layer when required.
73
+ ### Arguments
53
74
 
54
- ## Installation
75
+ #### `page`
55
76
 
56
- ```bash
57
- npm install fiptcha
77
+ An active Playwright-compatible page. Fiptcha uses page/frame APIs, locators, mouse interaction, evaluation, and timeouts against this object.
78
+
79
+ #### `options.captchaType`
80
+
81
+ Required. One of:
82
+
83
+ ```text
84
+ recaptcha_v2
85
+ hcaptcha
86
+ turnstile
58
87
  ```
59
88
 
60
- The package entry point is CommonJS, so it can be loaded with `require()` directly.
89
+ #### `options.timeout`
90
+
91
+ Optional. Maximum solve time in milliseconds.
92
+
93
+ Default:
61
94
 
62
95
  ```js
63
- const fiptcha = require('fiptcha');
96
+ 60_000
64
97
  ```
65
98
 
66
- ## Basic usage
99
+ #### `options.logs`
67
100
 
68
- The highest-level local solver can be used with a compatible browser page:
101
+ Optional array. Fiptcha appends diagnostic messages to it while solving.
69
102
 
70
103
  ```js
71
- const { solveLocalCaptcha } = require('fiptcha');
104
+ const logs = [];
72
105
 
73
- const result = await solveLocalCaptcha(page, {
106
+ await solveLocalCaptcha(page, {
74
107
  captchaType: 'recaptcha_v2',
75
- timeout: 60_000
108
+ logs
76
109
  });
77
110
 
78
- console.log(result);
111
+ console.log(logs);
79
112
  ```
80
113
 
81
- Your application remains responsible for creating the browser, navigating to the target page, deciding when a solve should occur, and handling the solve result in its own workflow.
114
+ ### Return value
115
+
116
+ A successful solve returns an object similar to:
82
117
 
83
- ## Integration model
118
+ ```js
119
+ {
120
+ token: '...',
121
+ provider: 'local',
122
+ model: 'owlvit', // only when a vision backend was used
123
+ device: 'cpu'
124
+ }
125
+ ```
84
126
 
85
- Fiptcha is designed as a runtime library rather than a complete automation framework. A typical integration looks like this:
127
+ `model` is omitted when no vision model was needed.
86
128
 
87
- ```text
88
- Your application
89
- |
90
- +-- browser lifecycle (Playwright/compatible host)
91
- |
92
- +-- challenge detection / solve trigger
93
- | |
94
- | +-- Fiptcha
95
- | +-- resource detection
96
- | +-- model manager
97
- | +-- model downloader
98
- | +-- local solver
99
- | +-- grid solver
100
- | +-- optional companion / MLX runtime
101
- |
102
- +-- application-specific result handling
103
- ```
104
-
105
- This separation is deliberate. Fiptcha does not know about Figranium Tasks, blocks, Cabinets, executions, authentication, scheduling, or its UI.
106
-
107
- ## Public API
108
-
109
- The root package currently re-exports the public functions from the following modules:
110
-
111
- - `captcha-local-solver` — high-level local solving logic;
112
- - `captcha-grid-solver` — image-grid solving primitives;
113
- - `captcha-interceptor` — browser/challenge interception helpers;
114
- - `captcha-resources` — host/container resource detection;
115
- - `captcha-model-manager` — model selection and lifecycle management;
116
- - `captcha-model-downloader` — model acquisition and verification;
117
- - `captcha-model-manifest` — model metadata;
118
- - `captcha-companion-client` — communication with the optional companion process;
119
- - `captcha-mlx-runtime` — MLX runtime integration;
120
- - `captcha-benchmark` — benchmark helpers.
121
-
122
- For example:
129
+ ### Errors
130
+
131
+ `solveLocalCaptcha` throws when:
132
+
133
+ - the requested CAPTCHA type is unsupported;
134
+ - a challenge never exposes a usable token or image challenge;
135
+ - a required vision backend cannot be prepared;
136
+ - the challenge ends without producing a token;
137
+ - Turnstile does not issue a token after browser interaction.
138
+
139
+ Wrap solves in your application's normal error handling:
140
+
141
+ ```js
142
+ try {
143
+ const result = await solveLocalCaptcha(page, {
144
+ captchaType: 'recaptcha_v2',
145
+ timeout: 60_000
146
+ });
147
+
148
+ console.log(result.token);
149
+ } catch (error) {
150
+ console.error('CAPTCHA solve failed:', error.message);
151
+ }
152
+ ```
153
+
154
+ ## Browser integration pattern
155
+
156
+ A typical integration should keep orchestration outside Fiptcha:
157
+
158
+ ```js
159
+ const { solveLocalCaptcha } = require('fiptcha');
160
+
161
+ async function handleCaptcha(page, captchaType) {
162
+ const logs = [];
163
+
164
+ const result = await solveLocalCaptcha(page, {
165
+ captchaType,
166
+ timeout: 60_000,
167
+ logs
168
+ });
169
+
170
+ return {
171
+ token: result.token,
172
+ diagnostics: logs
173
+ };
174
+ }
175
+ ```
176
+
177
+ Your application should remain responsible for:
178
+
179
+ - launching and closing browsers;
180
+ - choosing browser contexts and sessions;
181
+ - navigation;
182
+ - deciding when CAPTCHA handling should run;
183
+ - retries at the workflow level;
184
+ - proxies and network policy;
185
+ - persistence;
186
+ - translating Fiptcha errors/results into your own execution model.
187
+
188
+ Fiptcha is responsible for the CAPTCHA-specific runtime: token detection, widget interaction, grid solving, model selection, model downloads, resource checks, and optional companion runtimes.
189
+
190
+ ## Exported helpers
191
+
192
+ The package root re-exports the public exports from these modules:
193
+
194
+ | Module | Purpose |
195
+ | --- | --- |
196
+ | `captcha-local-solver` | High-level widget/token handling and `solveLocalCaptcha`. |
197
+ | `captcha-grid-solver` | Image-grid solving and provider adapters. |
198
+ | `captcha-interceptor` | Challenge interception helpers. |
199
+ | `captcha-resources` | Host/container memory and resource detection. |
200
+ | `captcha-model-manager` | Model selection, readiness, and lifecycle state. |
201
+ | `captcha-model-downloader` | Model download and verification. |
202
+ | `captcha-model-manifest` | Model metadata and requirements. |
203
+ | `captcha-companion-client` | Client for the optional companion process. |
204
+ | `captcha-mlx-runtime` | Apple Silicon MLX runtime support. |
205
+ | `captcha-benchmark` | Benchmarking helpers. |
206
+
207
+ For example, lower-level token helpers are also available:
123
208
 
124
209
  ```js
125
210
  const {
126
- solveLocalCaptcha,
127
- // Other model, resource, interception, and grid helpers are
128
- // exported from the same package entry point.
211
+ readToken,
212
+ waitForToken,
213
+ clickCheckbox,
214
+ solveLocalCaptcha
129
215
  } = require('fiptcha');
130
216
  ```
131
217
 
132
- Fiptcha is still young, so applications that depend on lower-level helpers should pin the package version while the standalone API is being stabilized.
218
+ The high-level solver is the recommended integration boundary. Lower-level exports are useful for custom integrations, but their API should be treated as pre-stable until Fiptcha reaches a stable standalone API release.
219
+
220
+ ## Models and memory requirements
221
+
222
+ Fiptcha does not bundle large model weights into the npm tarball. Required assets are acquired through the model-management layer when needed.
223
+
224
+ The runtime chooses an appropriate backend based on available resources. In particular, a provider-advertised **2 GB** machine is interpreted using decimal sizing (2,000,000,000 bytes, about 1907 MiB) rather than incorrectly requiring 2048 MiB.
225
+
226
+ If your application runs in an ephemeral container, persist the model/cache directory used by your deployment so models do not need to be downloaded again on every container recreation.
133
227
 
134
228
  ## Companion runtime
135
229
 
136
- Fiptcha includes the companion tooling that was previously embedded in Figranium.
230
+ Fiptcha includes an optional companion process for environments that need it.
137
231
 
138
232
  Install companion requirements:
139
233
 
@@ -141,103 +235,150 @@ Install companion requirements:
141
235
  npm run companion:install
142
236
  ```
143
237
 
144
- Start the companion:
238
+ Start it:
145
239
 
146
240
  ```bash
147
241
  npm run companion:start
148
242
  ```
149
243
 
150
- For the Docker-oriented path:
244
+ Start the Docker-oriented path:
151
245
 
152
246
  ```bash
153
247
  npm run companion:start:docker
154
248
  ```
155
249
 
156
- The companion is optional. Applications that do not need that backend can use the ordinary Node.js runtime without making the companion part of their own product architecture.
250
+ The companion is optional. Ordinary Node.js integrations do not need to make it part of their architecture unless they use that runtime path.
251
+
252
+ ## Apple Silicon / MLX
157
253
 
158
- ## Resource probing
254
+ Fiptcha contains the MLX worker and runtime integration used for compatible Apple Silicon environments.
159
255
 
160
- The package includes a probe command:
256
+ The MLX path is optional. Consumers that do not use MLX can ignore the bundled worker and companion requirements.
257
+
258
+ ## Inspect the runtime environment
259
+
260
+ Run the included resource probe:
161
261
 
162
262
  ```bash
163
263
  npm run probe
164
264
  ```
165
265
 
166
- This is useful when validating the environment Fiptcha will run in, especially inside containers where reported memory limits may differ from the physical host.
167
-
168
- Resource detection is kept inside Fiptcha so applications embedding it do not need to reproduce Figranium-specific hardware checks.
266
+ This is useful in containers and VMs where cgroup/container limits may differ from the physical host's reported memory.
169
267
 
170
- ## Model management
268
+ ## Test Fiptcha
171
269
 
172
- Fiptcha separates model metadata, downloading, resource selection, and inference/runtime behavior. This allows the package to choose a suitable backend without forcing every application to ship every supported model.
270
+ ```bash
271
+ npm test
272
+ ```
173
273
 
174
- The model downloader verifies model assets against the package's model manifest. Applications embedding Fiptcha should persist the configured model/cache directory if they do not want model assets to be downloaded again when an ephemeral container is recreated.
274
+ The smoke suite checks the extracted runtime and includes regression coverage for resource selection, including the 2 GB memory threshold.
175
275
 
176
- ## Configuration
276
+ ## Check what npm will publish
177
277
 
178
- Fiptcha uses the `CAPTCHA_*` environment-variable configuration inherited from the standalone runtime extracted from Figranium. Configuration belongs to the process embedding Fiptcha; the package does not require a Figranium settings database.
278
+ Before publishing a release:
179
279
 
180
- When integrating Fiptcha into another application, keep CAPTCHA/model configuration at the application boundary and pass browser state through the solver APIs rather than importing Figranium-specific configuration code.
280
+ ```bash
281
+ npm pack --dry-run
282
+ ```
181
283
 
182
- ## Using Fiptcha outside Figranium
284
+ The package intentionally publishes the runtime source, entry point, companion scripts, README, and license—not downloaded model weights.
183
285
 
184
- Fiptcha is intentionally licensed and packaged for this use case. A third-party automation project can install `fiptcha`, provide its own browser page, call the high-level solver or lower-level exported helpers, and keep its own orchestration around the package.
286
+ ## Release process
185
287
 
186
- A good integration should generally:
288
+ Fiptcha publishes to npm as:
187
289
 
188
- 1. let the host application own browser creation and navigation;
189
- 2. detect or decide when a CAPTCHA needs to be handled;
190
- 3. invoke Fiptcha with the active page/challenge context;
191
- 4. allow Fiptcha to manage its local model/runtime concerns;
192
- 5. translate the returned result into the host application's own success/error model.
290
+ ```text
291
+ fiptcha
292
+ ```
193
293
 
194
- Avoid copying Fiptcha source into the consuming project. Depending on the package keeps fixes to model handling, resource detection, and challenge logic independently upgradeable.
294
+ The repository uses npm Trusted Publishing through GitHub Actions. Releases are published without a long-lived npm token.
195
295
 
196
- ## Figranium integration
296
+ To release a new version:
197
297
 
198
- Figranium consumes Fiptcha as a dependency. Compatibility shims in Figranium preserve its previous internal CAPTCHA import paths while the implementation itself lives here.
298
+ 1. Update `version` in `package.json`.
299
+ 2. Merge the change to `main`.
300
+ 3. Create and publish a GitHub Release whose tag matches the package version, for example `v0.2.0`.
301
+ 4. The release workflow runs the tests and publishes the package to npm through OIDC Trusted Publishing.
199
302
 
200
- That means Fiptcha is not merely a separately published copy of the solver: this repository is intended to be the canonical implementation of the local CAPTCHA runtime used by Figranium.
303
+ Do not manually add an `NPM_TOKEN` secret for the normal release path.
201
304
 
202
- ## Testing
305
+ ## Using Fiptcha from another project
203
306
 
204
- Run the package smoke tests with:
307
+ Install it normally:
205
308
 
206
309
  ```bash
207
- npm test
310
+ npm install fiptcha
208
311
  ```
209
312
 
210
- The test suite includes regression coverage for resource detection, including the 2 GB host threshold that previously rejected machines advertised as having 2 GB RAM because the check effectively required 2 GiB.
313
+ Then keep the integration at your browser/runtime boundary:
314
+
315
+ ```js
316
+ const { solveLocalCaptcha } = require('fiptcha');
211
317
 
212
- ## Current maturity
318
+ async function solve(page) {
319
+ return solveLocalCaptcha(page, {
320
+ captchaType: 'recaptcha_v2',
321
+ timeout: 45_000
322
+ });
323
+ }
324
+ ```
213
325
 
214
- Fiptcha began as an extraction of a production subsystem rather than a greenfield SDK. The underlying solver has already been exercised through Figranium, but the standalone package API is new.
326
+ No Figranium task schema, server, UI, database, scheduler, authentication system, or storage layer is required.
215
327
 
216
- The high-level integration boundary is usable today. The main area that should be considered pre-stable is the exact shape and naming of lower-level exported helpers. Until a stable standalone API is declared, pinning an exact Fiptcha version is recommended for third-party applications.
328
+ ## Using Fiptcha in Figranium
217
329
 
218
- ## Security and responsible use
330
+ Figranium consumes the published `fiptcha` package and keeps only Figranium-specific integration code in the main application.
219
331
 
220
- CAPTCHA systems are one part of a site's abuse-prevention and access-control strategy. Integrators are responsible for ensuring their automation is authorized and complies with applicable terms, policies, and law.
332
+ The standalone Fiptcha repository is the canonical implementation of the local CAPTCHA runtime. Fixes to model handling, resource detection, challenge interaction, or companion behavior should be made here rather than copied back into Figranium.
221
333
 
222
- Do not treat successful CAPTCHA handling as permission to access data or perform actions that the application is otherwise not authorized to perform.
334
+ ## Troubleshooting
223
335
 
224
- ## Relationship to Figranium
336
+ ### `Local solver does not support ...`
225
337
 
226
- Fiptcha is part of the Figranium ecosystem but is independently reusable. Figranium provides the visual browser-automation product and orchestration layer; Fiptcha focuses on the CAPTCHA-solving runtime.
338
+ Check that `captchaType` is exactly one of:
227
339
 
228
- ## Contributing
340
+ ```text
341
+ recaptcha_v2
342
+ hcaptcha
343
+ turnstile
344
+ ```
229
345
 
230
- Bug reports and focused improvements are welcome. For solver bugs, include enough environment information to reproduce the issue, particularly:
346
+ ### `vision backend unavailable`
231
347
 
232
- - operating system and architecture;
233
- - Node.js version;
234
- - container/runtime details if applicable;
235
- - available memory;
236
- - CAPTCHA provider/challenge family;
237
- - whether the ordinary runtime or companion/MLX path was used.
348
+ The challenge requires the image-solving path, but Fiptcha could not prepare a supported vision backend. Check available memory, model availability/downloads, and the output of:
238
349
 
239
- Please avoid attaching sensitive cookies, credentials, or private browsing data to public issues.
350
+ ```bash
351
+ npm run probe
352
+ ```
353
+
354
+ ### Challenge appears but no token is returned
355
+
356
+ Increase the solve timeout and collect the `logs` array. Also verify that the challenge family still matches the provider structure expected by the current Fiptcha version.
357
+
358
+ ### Models download repeatedly in containers
359
+
360
+ Persist the model/cache storage used by the runtime instead of recreating it with every ephemeral container.
361
+
362
+ ### A lower-level helper changed between releases
363
+
364
+ Prefer `solveLocalCaptcha` for application integrations. Until the low-level API is declared stable, pin an exact Fiptcha version if you depend directly on internal/model/grid helpers.
365
+
366
+ ## Package contents
367
+
368
+ The npm package contains:
369
+
370
+ ```text
371
+ index.js
372
+ src/
373
+ scripts/captcha-mlx-worker.py
374
+ scripts/captcha-companion-requirements.txt
375
+ scripts/captcha-container-probe.js
376
+ scripts/install-captcha-companion.js
377
+ scripts/captcha-companion.js
378
+ README.md
379
+ LICENSE
380
+ ```
240
381
 
241
382
  ## License
242
383
 
243
- Fiptcha is licensed under the Apache License 2.0. See `LICENSE` for the full terms.
384
+ Apache-2.0.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fiptcha",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Local CAPTCHA solving for browser automation. Built for Figranium, usable anywhere.",
5
5
  "main": "index.js",
6
6
  "license": "Apache-2.0",
@@ -7,6 +7,21 @@ const TOKEN_SELECTORS = Object.freeze({
7
7
  turnstile: ['input[name="cf-turnstile-response"]', 'textarea[name="cf-turnstile-response"]']
8
8
  });
9
9
 
10
+ const WIDGET_INTERACTIONS = Object.freeze({
11
+ recaptcha_v2: Object.freeze({
12
+ framePatterns: ['/recaptcha/api2/anchor', '/recaptcha/enterprise/anchor'],
13
+ selectors: ['#recaptcha-anchor', '[role="checkbox"]']
14
+ }),
15
+ hcaptcha: Object.freeze({
16
+ framePatterns: ['hcaptcha.com/captcha', 'newassets.hcaptcha.com/captcha'],
17
+ selectors: ['#checkbox', '[role="checkbox"]', 'input[type="checkbox"]', 'label']
18
+ }),
19
+ turnstile: Object.freeze({
20
+ framePatterns: ['challenges.cloudflare.com'],
21
+ selectors: ['[role="checkbox"]', 'input[type="checkbox"]', 'label', 'button']
22
+ })
23
+ });
24
+
10
25
  async function readToken(page, captchaType) {
11
26
  return page.evaluate(({ type, selectors }) => {
12
27
  for (const selector of selectors[type] || []) {
@@ -57,43 +72,40 @@ async function waitForTokenOrChallenge(page, captchaType, timeout) {
57
72
  return null;
58
73
  }
59
74
 
60
- async function clickCheckbox(page, captchaType) {
61
- const patterns = captchaType === 'hcaptcha'
62
- ? ['hcaptcha.com/captcha', 'newassets.hcaptcha.com/captcha']
63
- : captchaType === 'turnstile'
64
- ? ['challenges.cloudflare.com']
65
- : ['/recaptcha/api2/anchor', '/recaptcha/enterprise/anchor'];
66
- const selector = captchaType === 'hcaptcha'
67
- ? '#checkbox, [role="checkbox"], input[type="checkbox"], label'
68
- : captchaType === 'turnstile'
69
- ? 'input[type="checkbox"], [role="checkbox"], button, label'
70
- : '#recaptcha-anchor, [role="checkbox"]';
71
- for (const frame of page.frames?.() || []) {
72
- if (!patterns.some((pattern) => frame.url().includes(pattern))) continue;
73
- const checkbox = frame.locator(selector).first();
74
- if (!await checkbox.isVisible({ timeout: 1000 }).catch(() => false)) continue;
75
- if (!await checkbox.isEnabled({ timeout: 250 }).catch(() => true)) continue;
76
- const pointerReady = await checkbox.evaluate((element) => {
77
- const style = getComputedStyle(element);
78
- return style.pointerEvents !== 'none' && element.getAttribute('aria-disabled') !== 'true';
79
- }).catch(() => true);
80
- if (!pointerReady) continue;
81
- const firstBox = await checkbox.boundingBox().catch(() => null);
82
- await page.waitForTimeout(75);
83
- const box = await checkbox.boundingBox().catch(() => null);
84
- if (firstBox && box && (Math.abs(firstBox.x - box.x) >= 1 || Math.abs(firstBox.y - box.y) >= 1)) continue;
85
- if (box && page.mouse) {
86
- const x = box.x + box.width / 2;
87
- const y = box.y + box.height / 2;
88
- await page.mouse.move(x, y, { steps: 8 });
89
- await page.mouse.down();
90
- await page.waitForTimeout(45);
91
- await page.mouse.up();
92
- } else {
93
- await checkbox.click({ timeout: 2000 });
75
+ async function clickCheckbox(page, captchaType, timeout = 10_000) {
76
+ const interaction = WIDGET_INTERACTIONS[captchaType];
77
+ if (!interaction) return false;
78
+ const deadline = Date.now() + Math.max(0, timeout);
79
+
80
+ do {
81
+ for (const frame of page.frames?.() || []) {
82
+ const frameUrl = String(frame.url?.() || '');
83
+ if (!interaction.framePatterns.some((pattern) => frameUrl.includes(pattern))) continue;
84
+
85
+ for (const selector of interaction.selectors) {
86
+ const matches = frame.locator(selector);
87
+ const count = await matches.count().catch(() => 0);
88
+ for (let index = 0; index < count; index += 1) {
89
+ const checkbox = matches.nth(index);
90
+ if (!await checkbox.isVisible({ timeout: 100 }).catch(() => false)) continue;
91
+ if (!await checkbox.isEnabled({ timeout: 100 }).catch(() => true)) continue;
92
+ const pointerReady = await checkbox.evaluate((element) => {
93
+ const style = getComputedStyle(element);
94
+ return style.pointerEvents !== 'none' && element.getAttribute('aria-disabled') !== 'true';
95
+ }).catch(() => true);
96
+ if (!pointerReady) continue;
97
+
98
+ const clickTimeout = Math.max(1, Math.min(2000, deadline - Date.now()));
99
+ const clicked = await checkbox.click({ timeout: clickTimeout }).then(() => true, () => false);
100
+ if (clicked) return true;
101
+ }
102
+ }
94
103
  }
95
- return true;
96
- }
104
+
105
+ if (Date.now() >= deadline) break;
106
+ await page.waitForTimeout(Math.min(100, Math.max(1, deadline - Date.now())));
107
+ } while (Date.now() <= deadline);
108
+
97
109
  return false;
98
110
  }
99
111
 
@@ -121,7 +133,15 @@ async function solveLocalCaptcha(page, { captchaType, timeout = 60_000, logs = [
121
133
  : null;
122
134
  let token = await readToken(page, captchaType);
123
135
  if (!token) {
124
- await clickCheckbox(page, captchaType).catch((error) => logs.push(`Local ${captchaType} checkbox click failed: ${error.message}`));
136
+ const clickTimeout = Math.min(10_000, Math.max(0, deadline - Date.now()));
137
+ const clicked = await clickCheckbox(page, captchaType, clickTimeout)
138
+ .catch((error) => {
139
+ logs.push(`Local ${captchaType} checkbox click failed: ${error.message}`);
140
+ return false;
141
+ });
142
+ logs.push(clicked
143
+ ? `Local ${captchaType} checkbox clicked`
144
+ : `Local ${captchaType} checkbox was not found before the interaction timeout`);
125
145
  const initialWait = captchaType === 'hcaptcha' ? 1500 : 7000;
126
146
  token = await waitForTokenOrChallenge(page, captchaType, Math.min(initialWait, Math.max(0, deadline - Date.now())));
127
147
  if (!token && captchaType === 'hcaptcha' && Date.now() < deadline) {
@@ -165,4 +185,4 @@ async function solveLocalCaptcha(page, { captchaType, timeout = 60_000, logs = [
165
185
  };
166
186
  }
167
187
 
168
- module.exports = { TOKEN_SELECTORS, readToken, waitForToken, waitForTokenOrChallenge, clickCheckbox, executeHcaptchaWidget, solveLocalCaptcha };
188
+ module.exports = { TOKEN_SELECTORS, WIDGET_INTERACTIONS, readToken, waitForToken, waitForTokenOrChallenge, clickCheckbox, executeHcaptchaWidget, solveLocalCaptcha };