@apifuse/provider-sdk 2.2.0-beta.5 → 2.2.0-beta.51
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/AUTHORING.md +537 -33
- package/CHANGELOG.md +205 -1
- package/README.md +83 -22
- package/SUBMISSION.md +2 -2
- package/bin/apifuse-check.ts +165 -11
- package/bin/apifuse-dev.ts +57 -13
- package/bin/apifuse-migrate-operation-declaration.ts +55 -0
- package/bin/apifuse-migrate-shape.ts +202 -0
- package/bin/apifuse-pack-check.ts +22 -2
- package/bin/apifuse-pack-smoke.ts +57 -2
- package/bin/apifuse-pack-types.ts +357 -38
- package/bin/apifuse-perf.ts +14 -13
- package/bin/apifuse-record.ts +709 -72
- package/bin/apifuse-submit-check.ts +2344 -324
- package/bin/apifuse-sync-assets.ts +117 -0
- package/bin/submit-check-delimited-text.ts +50 -0
- package/dist/auth-turn/index.d.ts +3 -3
- package/dist/auth-turn/index.js +1 -1
- package/dist/auth.d.ts +14 -0
- package/dist/auth.js +67 -0
- package/dist/ceremonies/index.d.ts +16 -0
- package/dist/ceremonies/index.js +141 -36
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +27 -0
- package/dist/cli/create.d.ts +4 -1
- package/dist/cli/create.js +42 -37
- package/dist/cli/migrate-operation-declaration.d.ts +59 -0
- package/dist/cli/migrate-operation-declaration.js +1249 -0
- package/dist/cli/migrate-operation-shape.d.ts +44 -0
- package/dist/cli/migrate-operation-shape.js +113 -0
- package/dist/cli/migrate-provider-shape.d.ts +52 -0
- package/dist/cli/migrate-provider-shape.js +578 -0
- package/dist/cli/prompt-assets.d.ts +80 -0
- package/dist/cli/prompt-assets.js +743 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
- package/dist/cli/templates/provider/README.md.tpl +5 -5
- package/dist/cli/templates/provider/index.ts.tpl +6 -3
- package/dist/cli/templates/provider/operations/ping.ts.tpl +4 -1
- package/dist/cli/templates/provider/provider.json.tpl +6 -0
- package/dist/config/loader.d.ts +179 -16
- package/dist/config/loader.js +435 -127
- package/dist/contract-serialization.js +4 -8
- package/dist/contract-types.d.ts +12 -5
- package/dist/contract.js +24 -10
- package/dist/declaration-validation.d.ts +33 -0
- package/dist/declaration-validation.js +266 -0
- package/dist/define.d.ts +66 -29
- package/dist/define.js +778 -69
- package/dist/dev.d.ts +3 -0
- package/dist/dev.js +1 -1
- package/dist/engine.d.ts +78 -0
- package/dist/engine.js +133 -0
- package/dist/error-observability.d.ts +7 -0
- package/dist/error-observability.js +61 -0
- package/dist/error-resolution.d.ts +4 -0
- package/dist/error-resolution.js +122 -0
- package/dist/errors.d.ts +33 -0
- package/dist/errors.js +40 -0
- package/dist/fixture-sanitization.d.ts +28 -0
- package/dist/fixture-sanitization.js +227 -0
- package/dist/health-scenario.d.ts +1842 -0
- package/dist/health-scenario.js +624 -0
- package/dist/index.d.ts +21 -10
- package/dist/index.js +12 -7
- package/dist/lint.d.ts +23 -11
- package/dist/lint.js +572 -69
- package/dist/native-address.d.ts +43 -0
- package/dist/native-address.js +281 -0
- package/dist/native-egress-policy.d.ts +31 -0
- package/dist/native-egress-policy.js +288 -0
- package/dist/observability.d.ts +5 -2
- package/dist/observability.js +48 -1
- package/dist/provider.d.ts +10 -2
- package/dist/provider.js +4 -1
- package/dist/runtime/auth-flow.d.ts +5 -1
- package/dist/runtime/auth-flow.js +6 -0
- package/dist/runtime/browser.d.ts +1 -0
- package/dist/runtime/browser.js +492 -49
- package/dist/runtime/cache.d.ts +1 -0
- package/dist/runtime/cache.js +169 -15
- package/dist/runtime/choice-wordlist.d.ts +9 -0
- package/dist/runtime/choice-wordlist.js +138 -0
- package/dist/runtime/choice.d.ts +13 -1
- package/dist/runtime/choice.js +490 -102
- package/dist/runtime/chrome149-header-order.d.ts +58 -0
- package/dist/runtime/chrome149-header-order.js +289 -0
- package/dist/runtime/env.js +12 -0
- package/dist/runtime/executor.d.ts +3 -2
- package/dist/runtime/executor.js +26 -25
- package/dist/runtime/http.d.ts +1 -0
- package/dist/runtime/http.js +515 -53
- package/dist/runtime/insights.js +2 -2
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +366 -8
- package/dist/runtime/native-network-errors.d.ts +33 -0
- package/dist/runtime/native-network-errors.js +69 -0
- package/dist/runtime/native-network.d.ts +96 -0
- package/dist/runtime/native-network.js +1232 -0
- package/dist/runtime/ocr.d.ts +29 -0
- package/dist/runtime/ocr.js +440 -0
- package/dist/runtime/otlp.d.ts +71 -2
- package/dist/runtime/otlp.js +397 -16
- package/dist/runtime/proxy-errors.js +6 -2
- package/dist/runtime/proxy-nodemaven.d.ts +56 -0
- package/dist/runtime/proxy-nodemaven.js +146 -0
- package/dist/runtime/proxy-telemetry.d.ts +80 -1
- package/dist/runtime/proxy-telemetry.js +154 -47
- package/dist/runtime/redirects.d.ts +29 -0
- package/dist/runtime/redirects.js +36 -0
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +4 -2
- package/dist/runtime/request-options.d.ts +68 -1
- package/dist/runtime/request-options.js +548 -0
- package/dist/runtime/resolver-config.d.ts +6 -0
- package/dist/runtime/resolver-config.js +6 -0
- package/dist/runtime/resolver-public.d.ts +1 -0
- package/dist/runtime/resolver-public.js +1 -0
- package/dist/runtime/resolver-shared.d.ts +14 -0
- package/dist/runtime/resolver-shared.js +12 -0
- package/dist/runtime/resolver-vendors/bindings.d.ts +48 -0
- package/dist/runtime/resolver-vendors/bindings.js +40 -0
- package/dist/runtime/resolver-vendors/browser.d.ts +22 -0
- package/dist/runtime/resolver-vendors/browser.js +377 -0
- package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
- package/dist/runtime/resolver-vendors/capsolver.js +531 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
- package/dist/runtime/resolver-vendors/hosts.js +33 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
- package/dist/runtime/resolver-vendors/twocaptcha.js +408 -0
- package/dist/runtime/resolver-vendors/types.d.ts +94 -0
- package/dist/runtime/resolver-vendors/types.js +96 -0
- package/dist/runtime/resolver.d.ts +72 -0
- package/dist/runtime/resolver.js +771 -0
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +3 -0
- package/dist/runtime/state.js +277 -71
- package/dist/runtime/stealth-cookies.d.ts +20 -0
- package/dist/runtime/stealth-cookies.js +111 -0
- package/dist/runtime/stealth.d.ts +41 -5
- package/dist/runtime/stealth.js +997 -291
- package/dist/runtime/stt.js +1 -12
- package/dist/runtime/timeout.d.ts +5 -0
- package/dist/runtime/timeout.js +12 -0
- package/dist/runtime/trace-config.d.ts +12 -0
- package/dist/runtime/trace-config.js +62 -0
- package/dist/runtime/trace.d.ts +5 -0
- package/dist/runtime/trace.js +43 -10
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/error-observability.d.ts +1 -0
- package/dist/server/error-observability.js +1 -0
- package/dist/server/index.d.ts +5 -2
- package/dist/server/index.js +2 -2
- package/dist/server/self-test-input-tokens.d.ts +2 -1
- package/dist/server/self-test-input-tokens.js +18 -14
- package/dist/server/self-test.d.ts +14 -3
- package/dist/server/self-test.js +129 -58
- package/dist/server/serve-implementation.d.ts +230 -0
- package/dist/server/serve-implementation.js +2265 -0
- package/dist/server/serve.d.ts +1 -70
- package/dist/server/serve.js +1 -1131
- package/dist/server/trace-output.d.ts +6 -0
- package/dist/server/trace-output.js +79 -0
- package/dist/server/types.d.ts +30 -5
- package/dist/server/types.js +13 -1
- package/dist/stateful/errors.d.ts +19 -0
- package/dist/stateful/errors.js +24 -0
- package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
- package/dist/stateful/http-provider-event-emitter.js +237 -0
- package/dist/stateful/http-session-owner-registry.d.ts +44 -0
- package/dist/stateful/http-session-owner-registry.js +210 -0
- package/dist/stateful/index.d.ts +18 -0
- package/dist/stateful/index.js +18 -0
- package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
- package/dist/stateful/provider-event-delivery-failures.js +43 -0
- package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
- package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
- package/dist/stateful/provider-event-pipeline.d.ts +50 -0
- package/dist/stateful/provider-event-pipeline.js +1 -0
- package/dist/stateful/provider-events.d.ts +101 -0
- package/dist/stateful/provider-events.js +289 -0
- package/dist/stateful/session-key.d.ts +15 -0
- package/dist/stateful/session-key.js +86 -0
- package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
- package/dist/stateful/stateful-provider-adapter-context.js +42 -0
- package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
- package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
- package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
- package/dist/stateful/stateful-provider-adapter.js +287 -0
- package/dist/stateful/stateful-provider-observability.d.ts +62 -0
- package/dist/stateful/stateful-provider-observability.js +161 -0
- package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +215 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
- package/dist/stateful/stateful-provider-runtime-context.js +60 -0
- package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
- package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
- package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
- package/dist/stateful/stateful-provider-session-routing.js +345 -0
- package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
- package/dist/stateful/stateful-provider-session-runtime.js +245 -0
- package/dist/stateful-signing.d.ts +18 -0
- package/dist/stateful-signing.js +27 -0
- package/dist/stealth/profiles.d.ts +9 -4
- package/dist/stealth/profiles.js +160 -211
- package/dist/stream-evidence.d.ts +74 -0
- package/dist/stream-evidence.js +785 -0
- package/dist/stream.js +7 -1
- package/dist/testing/index.d.ts +2 -1
- package/dist/testing/index.js +2 -1
- package/dist/testing/run.d.ts +32 -2
- package/dist/testing/run.js +489 -21
- package/dist/trace-sanitization.d.ts +5 -0
- package/dist/trace-sanitization.js +45 -0
- package/dist/types.d.ts +673 -131
- package/dist/types.js +1 -0
- package/package.json +45 -5
- package/src/auth-turn/index.ts +1 -1
- package/src/auth.ts +118 -0
- package/src/ceremonies/index.ts +189 -46
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +3 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +10 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +8 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-hoisted-array-spread.ts.txt +15 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-imported-array-spread.ts.txt +9 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +5 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-mixed-array-spread.ts.txt +24 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +16 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +3 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +31 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +21 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +14 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +15 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +4 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +12 -0
- package/src/cli/commands.ts +33 -0
- package/src/cli/create.ts +50 -37
- package/src/cli/migrate-operation-declaration.ts +1751 -0
- package/src/cli/migrate-operation-shape.ts +184 -0
- package/src/cli/migrate-provider-shape.ts +772 -0
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/src/cli/templates/provider/Dockerfile.tpl +1 -1
- package/src/cli/templates/provider/README.md.tpl +5 -5
- package/src/cli/templates/provider/index.ts.tpl +6 -3
- package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
- package/src/cli/templates/provider/provider.json.tpl +6 -0
- package/src/config/loader.ts +690 -163
- package/src/contract-serialization.ts +5 -7
- package/src/contract-types.ts +12 -5
- package/src/contract.ts +24 -10
- package/src/declaration-validation.ts +330 -0
- package/src/define.ts +1048 -131
- package/src/dev.ts +4 -1
- package/src/engine.ts +279 -0
- package/src/error-observability.ts +64 -0
- package/src/error-resolution.ts +127 -0
- package/src/errors.ts +68 -0
- package/src/fixture-sanitization.ts +264 -0
- package/src/health-scenario.ts +875 -0
- package/src/index.ts +220 -13
- package/src/lint.ts +698 -90
- package/src/native-address.ts +340 -0
- package/src/native-egress-policy.ts +358 -0
- package/src/observability.ts +51 -1
- package/src/provider.ts +161 -3
- package/src/runtime/auth-flow.ts +12 -0
- package/src/runtime/browser.ts +661 -63
- package/src/runtime/cache.ts +189 -14
- package/src/runtime/choice-wordlist.ts +145 -0
- package/src/runtime/choice.ts +631 -120
- package/src/runtime/chrome149-header-order.ts +330 -0
- package/src/runtime/env.ts +13 -0
- package/src/runtime/executor.ts +43 -31
- package/src/runtime/http.ts +641 -61
- package/src/runtime/insights.ts +2 -2
- package/src/runtime/instrumentation.ts +520 -15
- package/src/runtime/native-network-errors.ts +99 -0
- package/src/runtime/native-network.ts +1605 -0
- package/src/runtime/ocr.ts +523 -0
- package/src/runtime/otlp.ts +467 -21
- package/src/runtime/proxy-errors.ts +12 -4
- package/src/runtime/proxy-nodemaven.ts +221 -0
- package/src/runtime/proxy-telemetry.ts +244 -75
- package/src/runtime/redirects.ts +66 -0
- package/src/runtime/redis.ts +7 -2
- package/src/runtime/request-options.ts +680 -1
- package/src/runtime/resolver-config.ts +6 -0
- package/src/runtime/resolver-public.ts +23 -0
- package/src/runtime/resolver-shared.ts +33 -0
- package/src/runtime/resolver-vendors/bindings.ts +56 -0
- package/src/runtime/resolver-vendors/browser.ts +533 -0
- package/src/runtime/resolver-vendors/capsolver.ts +707 -0
- package/src/runtime/resolver-vendors/hosts.ts +38 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +540 -0
- package/src/runtime/resolver-vendors/types.ts +212 -0
- package/src/runtime/resolver.ts +1157 -0
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +394 -77
- package/src/runtime/stealth-cookies.ts +132 -0
- package/src/runtime/stealth.ts +1280 -336
- package/src/runtime/stt.ts +1 -19
- package/src/runtime/timeout.ts +18 -0
- package/src/runtime/trace-config.ts +78 -0
- package/src/runtime/trace.ts +57 -17
- package/src/serve.ts +6 -1
- package/src/server/error-observability.ts +1 -0
- package/src/server/index.ts +34 -2
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/self-test.ts +191 -68
- package/src/server/serve-implementation.ts +3378 -0
- package/src/server/serve.ts +1 -1632
- package/src/server/trace-output.ts +129 -0
- package/src/server/types.ts +13 -1
- package/src/stateful/README.md +146 -0
- package/src/stateful/errors.ts +35 -0
- package/src/stateful/http-provider-event-emitter.ts +314 -0
- package/src/stateful/http-session-owner-registry.ts +306 -0
- package/src/stateful/index.ts +18 -0
- package/src/stateful/provider-event-delivery-failures.ts +80 -0
- package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
- package/src/stateful/provider-event-pipeline.ts +61 -0
- package/src/stateful/provider-events.ts +462 -0
- package/src/stateful/session-key.ts +111 -0
- package/src/stateful/stateful-provider-adapter-context.ts +59 -0
- package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
- package/src/stateful/stateful-provider-adapter.ts +562 -0
- package/src/stateful/stateful-provider-observability.ts +261 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
- package/src/stateful/stateful-provider-runtime-context.ts +92 -0
- package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
- package/src/stateful/stateful-provider-session-routing.ts +546 -0
- package/src/stateful/stateful-provider-session-runtime.ts +403 -0
- package/src/stateful-signing.ts +46 -0
- package/src/stealth/profiles.ts +202 -231
- package/src/stream-evidence.ts +988 -0
- package/src/stream.ts +8 -1
- package/src/testing/index.ts +10 -1
- package/src/testing/run.ts +658 -15
- package/src/trace-sanitization.ts +63 -0
- package/src/types.ts +788 -174
- package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
- package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
- /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ProviderError } from "../../errors.js";
|
|
2
|
+
|
|
3
|
+
export function normalizedResolverHostname(hostname: string): string {
|
|
4
|
+
return hostname.trim().toLowerCase().replace(/\.$/, "");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function assertResolverHostAllowed(
|
|
8
|
+
targetUrl: string,
|
|
9
|
+
allowedHosts: readonly string[],
|
|
10
|
+
): void {
|
|
11
|
+
let targetUrlObject: URL;
|
|
12
|
+
try {
|
|
13
|
+
targetUrlObject = new URL(targetUrl);
|
|
14
|
+
} catch {
|
|
15
|
+
throw new ProviderError("Resolver target URL is invalid", {
|
|
16
|
+
code: "RESOLVER_HOST_NOT_ALLOWED",
|
|
17
|
+
fix: "Use a valid URL whose exact hostname appears in the provider's allowedHosts declaration.",
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
if (targetUrlObject.protocol !== "http:" && targetUrlObject.protocol !== "https:") {
|
|
21
|
+
throw new ProviderError(`Resolver target URL scheme "${targetUrlObject.protocol}" is not allowed`, {
|
|
22
|
+
code: "RESOLVER_HOST_NOT_ALLOWED",
|
|
23
|
+
fix: "Use an http or https URL whose exact hostname appears in the provider's allowedHosts declaration.",
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const targetHost = normalizedResolverHostname(targetUrlObject.hostname);
|
|
27
|
+
|
|
28
|
+
const isAllowed = allowedHosts.some((host) => {
|
|
29
|
+
const declaredHost = normalizedResolverHostname(host);
|
|
30
|
+
return declaredHost.length > 0 && !declaredHost.includes("*") && declaredHost === targetHost;
|
|
31
|
+
});
|
|
32
|
+
if (isAllowed) return;
|
|
33
|
+
|
|
34
|
+
throw new ProviderError(`Resolver target host "${targetHost}" is not declared`, {
|
|
35
|
+
code: "RESOLVER_HOST_NOT_ALLOWED",
|
|
36
|
+
fix: "Add the exact target hostname to the provider's allowedHosts declaration.",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import type { ChallengeSolution, ProviderChallenge } from "../../types.js";
|
|
2
|
+
import type { TraceRecorder } from "../trace.js";
|
|
3
|
+
import { assertResolverHostAllowed } from "./hosts.js";
|
|
4
|
+
import {
|
|
5
|
+
type ResolverIdentity,
|
|
6
|
+
type ResolverVendorAdapter,
|
|
7
|
+
ResolverChallengeVerdictError,
|
|
8
|
+
ResolverVendorUnavailableError,
|
|
9
|
+
resolverVendorSupports,
|
|
10
|
+
} from "./types.js";
|
|
11
|
+
|
|
12
|
+
const TWOCAPTCHA_VENDOR_ID = "2captcha" as const;
|
|
13
|
+
const DEFAULT_TWOCAPTCHA_BASE_URL = "https://api.2captcha.com";
|
|
14
|
+
const DEFAULT_POLL_INTERVAL_MS = 3_000;
|
|
15
|
+
const DEFAULT_TIMEOUT_MS = 180_000;
|
|
16
|
+
|
|
17
|
+
type Delay = (ms: number, signal: AbortSignal) => Promise<void>;
|
|
18
|
+
type TwoCaptchaOperationPhase = "create_task" | "poll_result";
|
|
19
|
+
|
|
20
|
+
export interface TwoCaptchaResolverVendorOptions {
|
|
21
|
+
readonly apiKey?: string;
|
|
22
|
+
readonly timeoutMs?: number;
|
|
23
|
+
readonly pollIntervalMs?: number;
|
|
24
|
+
readonly allowedHosts: readonly string[];
|
|
25
|
+
readonly fetchImpl?: typeof fetch;
|
|
26
|
+
readonly baseUrl?: string;
|
|
27
|
+
/** Test-only clock override; supplying it disables the real-time deadline timer. */
|
|
28
|
+
readonly now?: () => number;
|
|
29
|
+
/** Test-only delay override used with `now` to exercise polling without sleeping. */
|
|
30
|
+
readonly delay?: Delay;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface TwoCaptchaResolverVendorAdapter extends ResolverVendorAdapter {
|
|
34
|
+
readonly id: "2captcha";
|
|
35
|
+
solve(
|
|
36
|
+
challenge: ProviderChallenge,
|
|
37
|
+
identity: ResolverIdentity | undefined,
|
|
38
|
+
signal: AbortSignal,
|
|
39
|
+
traceRecorder?: TraceRecorder,
|
|
40
|
+
): Promise<Extract<ChallengeSolution, { readonly form: "token" }>>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
class TwoCaptchaSolveTimeoutError extends Error {
|
|
44
|
+
constructor() {
|
|
45
|
+
super("2captcha resolver solve budget elapsed");
|
|
46
|
+
this.name = "TwoCaptchaSolveTimeoutError";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type JsonRecord = Record<string, unknown>;
|
|
51
|
+
|
|
52
|
+
type ProxyConfiguration = {
|
|
53
|
+
readonly proxyType: "http" | "socks4" | "socks5";
|
|
54
|
+
readonly proxyAddress: string;
|
|
55
|
+
readonly proxyPort: number;
|
|
56
|
+
readonly proxyLogin?: string;
|
|
57
|
+
readonly proxyPassword?: string;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function isJsonRecord(value: unknown): value is JsonRecord {
|
|
61
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function abortReason(signal: AbortSignal): unknown {
|
|
65
|
+
return signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function containsSensitiveValue(value: unknown, sensitiveValues: readonly string[]): boolean {
|
|
69
|
+
const secrets = sensitiveValues.filter((secret) => secret.length > 0);
|
|
70
|
+
if (secrets.length === 0) return false;
|
|
71
|
+
const seen = new Set<object>();
|
|
72
|
+
|
|
73
|
+
const inspect = (candidate: unknown): boolean => {
|
|
74
|
+
if (typeof candidate === "string") {
|
|
75
|
+
return secrets.some((secret) => candidate.includes(secret));
|
|
76
|
+
}
|
|
77
|
+
if (
|
|
78
|
+
candidate === null ||
|
|
79
|
+
(typeof candidate !== "object" && typeof candidate !== "function")
|
|
80
|
+
) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
if (seen.has(candidate)) return false;
|
|
84
|
+
seen.add(candidate);
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
for (const property of Reflect.ownKeys(candidate)) {
|
|
88
|
+
if (typeof property === "string" && inspect(property)) return true;
|
|
89
|
+
const descriptor = Object.getOwnPropertyDescriptor(candidate, property);
|
|
90
|
+
if (!descriptor) return true;
|
|
91
|
+
if ("value" in descriptor && inspect(descriptor.value)) return true;
|
|
92
|
+
if (descriptor.get !== undefined || descriptor.set !== undefined) return true;
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return inspect(value);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function safeCauseOptions(
|
|
104
|
+
error: unknown,
|
|
105
|
+
sensitiveValues: readonly string[],
|
|
106
|
+
): { readonly cause?: unknown } {
|
|
107
|
+
return containsSensitiveValue(error, sensitiveValues) ? {} : { cause: error };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function raceWithAbort<T>(
|
|
111
|
+
operation: () => Promise<T>,
|
|
112
|
+
signal: AbortSignal,
|
|
113
|
+
phase?: TwoCaptchaOperationPhase,
|
|
114
|
+
sensitiveValues: readonly string[] = [],
|
|
115
|
+
): Promise<T> {
|
|
116
|
+
if (signal.aborted) return Promise.reject(abortReason(signal));
|
|
117
|
+
|
|
118
|
+
return new Promise<T>((resolve, reject) => {
|
|
119
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
120
|
+
const onAbort = () => {
|
|
121
|
+
cleanup();
|
|
122
|
+
reject(abortReason(signal));
|
|
123
|
+
};
|
|
124
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
125
|
+
operation().then(
|
|
126
|
+
(value) => {
|
|
127
|
+
cleanup();
|
|
128
|
+
resolve(value);
|
|
129
|
+
},
|
|
130
|
+
(error: unknown) => {
|
|
131
|
+
cleanup();
|
|
132
|
+
if (phase === undefined) {
|
|
133
|
+
reject(error);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
reject(
|
|
137
|
+
new ResolverVendorUnavailableError(TWOCAPTCHA_VENDOR_ID, "transport_failure", {
|
|
138
|
+
...safeCauseOptions(error, sensitiveValues),
|
|
139
|
+
phase,
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function abortableDelay(ms: number, signal: AbortSignal): Promise<void> {
|
|
148
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
149
|
+
try {
|
|
150
|
+
await raceWithAbort(
|
|
151
|
+
() =>
|
|
152
|
+
new Promise<void>((resolve) => {
|
|
153
|
+
timer = setTimeout(resolve, ms);
|
|
154
|
+
}),
|
|
155
|
+
signal,
|
|
156
|
+
);
|
|
157
|
+
} finally {
|
|
158
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function parseProxyConfiguration(proxyUrl: string): ProxyConfiguration | undefined {
|
|
163
|
+
try {
|
|
164
|
+
const url = new URL(proxyUrl);
|
|
165
|
+
const protocol = url.protocol.slice(0, -1).toLowerCase();
|
|
166
|
+
const proxyType =
|
|
167
|
+
protocol === "socks4" || protocol === "socks5"
|
|
168
|
+
? protocol
|
|
169
|
+
: protocol === "http" || protocol === "https"
|
|
170
|
+
? "http"
|
|
171
|
+
: undefined;
|
|
172
|
+
const defaultPort = proxyType === "http" ? 80 : 1080;
|
|
173
|
+
const proxyPort = Number(url.port || defaultPort);
|
|
174
|
+
if (!proxyType || !url.hostname || !Number.isInteger(proxyPort) || proxyPort <= 0) {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
const proxyLogin = url.username ? decodeURIComponent(url.username) : undefined;
|
|
178
|
+
const proxyPassword = url.password ? decodeURIComponent(url.password) : undefined;
|
|
179
|
+
return {
|
|
180
|
+
proxyType,
|
|
181
|
+
proxyAddress: url.hostname,
|
|
182
|
+
proxyPort,
|
|
183
|
+
...(proxyLogin ? { proxyLogin } : {}),
|
|
184
|
+
...(proxyPassword ? { proxyPassword } : {}),
|
|
185
|
+
};
|
|
186
|
+
} catch {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function errorText(payload: JsonRecord, key: "errorCode" | "errorDescription"): string {
|
|
192
|
+
const value = payload[key];
|
|
193
|
+
return typeof value === "string" ? value : "";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isAllocationExhausted(payload: JsonRecord): boolean {
|
|
197
|
+
const code = errorText(payload, "errorCode").toLowerCase();
|
|
198
|
+
const description = errorText(payload, "errorDescription").toLowerCase();
|
|
199
|
+
return (
|
|
200
|
+
code === "error_zero_balance" ||
|
|
201
|
+
/(?:insufficient|zero|no|not enough)\s+(?:balance|funds|credit)/u.test(`${code} ${description}`)
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function isNegativeVerdict(payload: JsonRecord): boolean {
|
|
206
|
+
return errorText(payload, "errorCode").toLowerCase() === "error_captcha_unsolvable";
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function unavailableForPayload(
|
|
210
|
+
payload: JsonRecord,
|
|
211
|
+
phase: TwoCaptchaOperationPhase,
|
|
212
|
+
): ResolverVendorUnavailableError | ResolverChallengeVerdictError {
|
|
213
|
+
if (isNegativeVerdict(payload)) {
|
|
214
|
+
return new ResolverChallengeVerdictError(TWOCAPTCHA_VENDOR_ID, "solve_failed");
|
|
215
|
+
}
|
|
216
|
+
return new ResolverVendorUnavailableError(
|
|
217
|
+
TWOCAPTCHA_VENDOR_ID,
|
|
218
|
+
isAllocationExhausted(payload) ? "allocation_exhausted" : "transport_failure",
|
|
219
|
+
{ phase },
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function postJson(
|
|
224
|
+
fetchImpl: typeof fetch,
|
|
225
|
+
url: string,
|
|
226
|
+
body: JsonRecord,
|
|
227
|
+
signal: AbortSignal,
|
|
228
|
+
phase: TwoCaptchaOperationPhase,
|
|
229
|
+
sensitiveValues: readonly string[],
|
|
230
|
+
): Promise<{ readonly ok: boolean; readonly payload: JsonRecord }> {
|
|
231
|
+
const response = await raceWithAbort(
|
|
232
|
+
() =>
|
|
233
|
+
fetchImpl(url, {
|
|
234
|
+
method: "POST",
|
|
235
|
+
headers: { "content-type": "application/json" },
|
|
236
|
+
body: JSON.stringify(body),
|
|
237
|
+
signal,
|
|
238
|
+
redirect: "error",
|
|
239
|
+
}),
|
|
240
|
+
signal,
|
|
241
|
+
phase,
|
|
242
|
+
sensitiveValues,
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
let responseText: string;
|
|
246
|
+
try {
|
|
247
|
+
responseText = await raceWithAbort(() => response.text(), signal, phase);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
if (error instanceof ResolverVendorUnavailableError) throw error;
|
|
250
|
+
throw new ResolverVendorUnavailableError(TWOCAPTCHA_VENDOR_ID, "transport_failure", {
|
|
251
|
+
cause: error,
|
|
252
|
+
phase,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let payload: unknown;
|
|
257
|
+
try {
|
|
258
|
+
payload = JSON.parse(responseText);
|
|
259
|
+
} catch {
|
|
260
|
+
// JSON parse errors may contain response-body excerpts, so do not retain them as causes.
|
|
261
|
+
throw new ResolverVendorUnavailableError(TWOCAPTCHA_VENDOR_ID, "transport_failure", {
|
|
262
|
+
phase,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
if (!isJsonRecord(payload)) {
|
|
266
|
+
throw new ResolverVendorUnavailableError(TWOCAPTCHA_VENDOR_ID, "transport_failure", {
|
|
267
|
+
phase,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
return { ok: response.ok, payload };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function taskIdFrom(payload: JsonRecord): string | number | undefined {
|
|
274
|
+
const taskId = payload.taskId;
|
|
275
|
+
return typeof taskId === "string" || typeof taskId === "number" ? taskId : undefined;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function tokenFrom(payload: JsonRecord, challenge: ProviderChallenge): string | undefined {
|
|
279
|
+
const solution = payload.solution;
|
|
280
|
+
if (!isJsonRecord(solution)) return undefined;
|
|
281
|
+
if (challenge.kind === "aws_waf") {
|
|
282
|
+
return typeof solution.existing_token === "string" ? solution.existing_token : undefined;
|
|
283
|
+
}
|
|
284
|
+
if (typeof solution.gRecaptchaResponse === "string") return solution.gRecaptchaResponse;
|
|
285
|
+
return typeof solution.token === "string" ? solution.token : undefined;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function endpoint(baseUrl: string, path: string): string {
|
|
289
|
+
return `${baseUrl.replace(/\/+$/u, "")}/${path}`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function spanErrorAttributes(
|
|
293
|
+
error: unknown,
|
|
294
|
+
phase: TwoCaptchaOperationPhase,
|
|
295
|
+
): Record<string, unknown> | undefined {
|
|
296
|
+
if (error instanceof ResolverVendorUnavailableError) {
|
|
297
|
+
return {
|
|
298
|
+
unavailability_reason: error.reason,
|
|
299
|
+
transport_phase: error.phase,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
// The solve budget raises TwoCaptchaSolveTimeoutError inside the spanned closure and
|
|
303
|
+
// the outer catch converts it only after the span has been finalized, so the span has
|
|
304
|
+
// to recognize it here or slow solves lose exactly the attribution these spans add.
|
|
305
|
+
if (error instanceof TwoCaptchaSolveTimeoutError) {
|
|
306
|
+
return {
|
|
307
|
+
unavailability_reason: "timeout",
|
|
308
|
+
transport_phase: phase,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
return undefined;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function createTwoCaptchaResolverVendorAdapter(
|
|
315
|
+
options: TwoCaptchaResolverVendorOptions,
|
|
316
|
+
): TwoCaptchaResolverVendorAdapter {
|
|
317
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
318
|
+
const baseUrl = options.baseUrl ?? DEFAULT_TWOCAPTCHA_BASE_URL;
|
|
319
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
320
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
321
|
+
const now = options.now ?? Date.now;
|
|
322
|
+
const delay = options.delay ?? abortableDelay;
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
id: TWOCAPTCHA_VENDOR_ID,
|
|
326
|
+
|
|
327
|
+
supports(kind) {
|
|
328
|
+
return resolverVendorSupports(TWOCAPTCHA_VENDOR_ID, kind);
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
async solve(challenge, identity, callerSignal, traceRecorder) {
|
|
332
|
+
const apiKey = options.apiKey?.trim();
|
|
333
|
+
if (!apiKey) {
|
|
334
|
+
throw new ResolverVendorUnavailableError(TWOCAPTCHA_VENDOR_ID, "missing_credentials", {
|
|
335
|
+
phase: "create_task",
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
if (!resolverVendorSupports(TWOCAPTCHA_VENDOR_ID, challenge.kind)) {
|
|
339
|
+
throw new TypeError(`2captcha resolver does not support ${challenge.kind}`);
|
|
340
|
+
}
|
|
341
|
+
if (challenge.kind === "aws_waf") {
|
|
342
|
+
const missingFields = [
|
|
343
|
+
...(challenge.siteKey?.trim() ? [] : ["siteKey"]),
|
|
344
|
+
...(challenge.captchaScript?.trim() ? [] : ["captchaScript"]),
|
|
345
|
+
...(challenge.context?.trim() ? [] : ["context"]),
|
|
346
|
+
...(challenge.iv?.trim() ? [] : ["iv"]),
|
|
347
|
+
];
|
|
348
|
+
if (missingFields.length > 0) {
|
|
349
|
+
throw new ResolverVendorUnavailableError(TWOCAPTCHA_VENDOR_ID, "missing_challenge_input", {
|
|
350
|
+
missingFields,
|
|
351
|
+
phase: "create_task",
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (challenge.kind === "recaptcha_v3" && challenge.minScore === undefined) {
|
|
356
|
+
throw new ResolverVendorUnavailableError(TWOCAPTCHA_VENDOR_ID, "missing_challenge_input", {
|
|
357
|
+
missingFields: ["minScore"],
|
|
358
|
+
phase: "create_task",
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
assertResolverHostAllowed(challenge.pageUrl, options.allowedHosts);
|
|
363
|
+
callerSignal.throwIfAborted();
|
|
364
|
+
|
|
365
|
+
const proxy = identity ? parseProxyConfiguration(identity.proxyUrl) : undefined;
|
|
366
|
+
if (identity && !proxy) {
|
|
367
|
+
throw new ResolverVendorUnavailableError(TWOCAPTCHA_VENDOR_ID, "transport_failure", {
|
|
368
|
+
phase: "create_task",
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const solveController = new AbortController();
|
|
373
|
+
const onCallerAbort = () => solveController.abort(abortReason(callerSignal));
|
|
374
|
+
callerSignal.addEventListener("abort", onCallerAbort, { once: true });
|
|
375
|
+
const timeout = options.now
|
|
376
|
+
? undefined
|
|
377
|
+
: setTimeout(() => solveController.abort(new TwoCaptchaSolveTimeoutError()), timeoutMs);
|
|
378
|
+
const startedAt = now();
|
|
379
|
+
let phase: TwoCaptchaOperationPhase = "create_task";
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
const createTask = async () => {
|
|
383
|
+
const task =
|
|
384
|
+
challenge.kind === "aws_waf"
|
|
385
|
+
? {
|
|
386
|
+
type: proxy ? "AmazonTask" : "AmazonTaskProxyless",
|
|
387
|
+
websiteURL: challenge.pageUrl,
|
|
388
|
+
websiteKey: challenge.siteKey,
|
|
389
|
+
captchaScript: challenge.captchaScript,
|
|
390
|
+
context: challenge.context,
|
|
391
|
+
iv: challenge.iv,
|
|
392
|
+
...(identity ? { userAgent: identity.userAgent } : {}),
|
|
393
|
+
...(proxy ?? {}),
|
|
394
|
+
}
|
|
395
|
+
: challenge.kind === "recaptcha_v2"
|
|
396
|
+
? {
|
|
397
|
+
type: proxy ? "RecaptchaV2Task" : "RecaptchaV2TaskProxyless",
|
|
398
|
+
websiteURL: challenge.pageUrl,
|
|
399
|
+
websiteKey: challenge.siteKey,
|
|
400
|
+
isInvisible: false,
|
|
401
|
+
...(identity ? { userAgent: identity.userAgent } : {}),
|
|
402
|
+
...(proxy ?? {}),
|
|
403
|
+
}
|
|
404
|
+
: challenge.kind === "recaptcha_v3"
|
|
405
|
+
? {
|
|
406
|
+
type: "RecaptchaV3TaskProxyless",
|
|
407
|
+
websiteURL: challenge.pageUrl,
|
|
408
|
+
websiteKey: challenge.siteKey,
|
|
409
|
+
minScore: challenge.minScore,
|
|
410
|
+
pageAction: challenge.action,
|
|
411
|
+
...(identity ? { userAgent: identity.userAgent } : {}),
|
|
412
|
+
}
|
|
413
|
+
: challenge.kind === "hcaptcha"
|
|
414
|
+
? {
|
|
415
|
+
type: proxy ? "HCaptchaTask" : "HCaptchaTaskProxyless",
|
|
416
|
+
websiteURL: challenge.pageUrl,
|
|
417
|
+
websiteKey: challenge.siteKey,
|
|
418
|
+
...(identity ? { userAgent: identity.userAgent } : {}),
|
|
419
|
+
...(proxy ?? {}),
|
|
420
|
+
}
|
|
421
|
+
: challenge.kind === "turnstile"
|
|
422
|
+
? {
|
|
423
|
+
type: proxy ? "TurnstileTask" : "TurnstileTaskProxyless",
|
|
424
|
+
websiteURL: challenge.pageUrl,
|
|
425
|
+
websiteKey: challenge.siteKey,
|
|
426
|
+
...(challenge.action !== undefined ? { action: challenge.action } : {}),
|
|
427
|
+
...(challenge.cdata !== undefined ? { data: challenge.cdata } : {}),
|
|
428
|
+
...(identity ? { userAgent: identity.userAgent } : {}),
|
|
429
|
+
...(proxy ?? {}),
|
|
430
|
+
}
|
|
431
|
+
: // `resolverVendorSupports` above already rejected every kind this
|
|
432
|
+
// adapter does not build a task for, so this branch is unreachable.
|
|
433
|
+
(() => {
|
|
434
|
+
throw new TypeError(
|
|
435
|
+
`2captcha resolver does not support ${challenge.kind}`,
|
|
436
|
+
);
|
|
437
|
+
})();
|
|
438
|
+
const createResult = await postJson(
|
|
439
|
+
fetchImpl,
|
|
440
|
+
endpoint(baseUrl, "createTask"),
|
|
441
|
+
{ clientKey: apiKey, task },
|
|
442
|
+
solveController.signal,
|
|
443
|
+
phase,
|
|
444
|
+
[apiKey],
|
|
445
|
+
);
|
|
446
|
+
const taskId = taskIdFrom(createResult.payload);
|
|
447
|
+
if (!createResult.ok || createResult.payload.errorId !== 0 || taskId === undefined) {
|
|
448
|
+
throw unavailableForPayload(createResult.payload, phase);
|
|
449
|
+
}
|
|
450
|
+
return taskId;
|
|
451
|
+
};
|
|
452
|
+
const taskId = traceRecorder
|
|
453
|
+
? await traceRecorder.runSpan("resolver.vendor.create_task", createTask, {
|
|
454
|
+
attributes: {
|
|
455
|
+
vendor: TWOCAPTCHA_VENDOR_ID,
|
|
456
|
+
challenge_kind: challenge.kind,
|
|
457
|
+
},
|
|
458
|
+
onError: (error) => spanErrorAttributes(error, "create_task"),
|
|
459
|
+
})
|
|
460
|
+
: await createTask();
|
|
461
|
+
|
|
462
|
+
phase = "poll_result";
|
|
463
|
+
const pollResult = async () => {
|
|
464
|
+
while (true) {
|
|
465
|
+
callerSignal.throwIfAborted();
|
|
466
|
+
const remainingMs = timeoutMs - (now() - startedAt);
|
|
467
|
+
if (remainingMs <= 0) throw new TwoCaptchaSolveTimeoutError();
|
|
468
|
+
await delay(Math.min(pollIntervalMs, remainingMs), solveController.signal);
|
|
469
|
+
callerSignal.throwIfAborted();
|
|
470
|
+
if (now() - startedAt >= timeoutMs) throw new TwoCaptchaSolveTimeoutError();
|
|
471
|
+
|
|
472
|
+
const pollResult = await postJson(
|
|
473
|
+
fetchImpl,
|
|
474
|
+
endpoint(baseUrl, "getTaskResult"),
|
|
475
|
+
{ clientKey: apiKey, taskId },
|
|
476
|
+
solveController.signal,
|
|
477
|
+
phase,
|
|
478
|
+
[apiKey],
|
|
479
|
+
);
|
|
480
|
+
if (!pollResult.ok || pollResult.payload.errorId !== 0) {
|
|
481
|
+
throw unavailableForPayload(pollResult.payload, phase);
|
|
482
|
+
}
|
|
483
|
+
if (pollResult.payload.status === "processing") continue;
|
|
484
|
+
if (pollResult.payload.status !== "ready") {
|
|
485
|
+
throw new ResolverVendorUnavailableError(
|
|
486
|
+
TWOCAPTCHA_VENDOR_ID,
|
|
487
|
+
"transport_failure",
|
|
488
|
+
{ phase },
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const token = tokenFrom(pollResult.payload, challenge);
|
|
493
|
+
if (!token?.trim()) {
|
|
494
|
+
throw new ResolverVendorUnavailableError(
|
|
495
|
+
TWOCAPTCHA_VENDOR_ID,
|
|
496
|
+
"transport_failure",
|
|
497
|
+
{ phase },
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
// AWS WAF remains a token solution here, so resolver cookie caching does not apply.
|
|
501
|
+
return { form: "token" as const, token };
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
return traceRecorder
|
|
505
|
+
? await traceRecorder.runSpan("resolver.vendor.poll_result", pollResult, {
|
|
506
|
+
attributes: {
|
|
507
|
+
vendor: TWOCAPTCHA_VENDOR_ID,
|
|
508
|
+
challenge_kind: challenge.kind,
|
|
509
|
+
},
|
|
510
|
+
onError: (error) => spanErrorAttributes(error, "poll_result"),
|
|
511
|
+
})
|
|
512
|
+
: await pollResult();
|
|
513
|
+
} catch (error) {
|
|
514
|
+
if (callerSignal.aborted) throw abortReason(callerSignal);
|
|
515
|
+
if (
|
|
516
|
+
error instanceof TwoCaptchaSolveTimeoutError ||
|
|
517
|
+
solveController.signal.reason instanceof TwoCaptchaSolveTimeoutError
|
|
518
|
+
) {
|
|
519
|
+
throw new ResolverVendorUnavailableError(TWOCAPTCHA_VENDOR_ID, "timeout", {
|
|
520
|
+
cause: error,
|
|
521
|
+
phase,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
if (
|
|
525
|
+
error instanceof ResolverVendorUnavailableError ||
|
|
526
|
+
error instanceof ResolverChallengeVerdictError
|
|
527
|
+
) {
|
|
528
|
+
throw error;
|
|
529
|
+
}
|
|
530
|
+
throw new ResolverVendorUnavailableError(TWOCAPTCHA_VENDOR_ID, "transport_failure", {
|
|
531
|
+
...safeCauseOptions(error, [apiKey]),
|
|
532
|
+
phase,
|
|
533
|
+
});
|
|
534
|
+
} finally {
|
|
535
|
+
if (timeout !== undefined) clearTimeout(timeout);
|
|
536
|
+
callerSignal.removeEventListener("abort", onCallerAbort);
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
};
|
|
540
|
+
}
|