@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,771 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { resolveProxyConfigAsync, } from "../config/loader.js";
|
|
3
|
+
import { ProviderError } from "../errors.js";
|
|
4
|
+
import { getStealthProfile } from "../stealth/profiles.js";
|
|
5
|
+
import { resolverChallengeAllowsDirectCache, resolverChallengeIsCacheable, resolverChallengeIsIdentityScoped, resolverChallengeIssuingIdentity, } from "./resolver-vendors/bindings.js";
|
|
6
|
+
import { createBrowserResolverVendorAdapter } from "./resolver-vendors/browser.js";
|
|
7
|
+
import { createCapsolverResolverVendorAdapter } from "./resolver-vendors/capsolver.js";
|
|
8
|
+
import { assertResolverHostAllowed } from "./resolver-vendors/hosts.js";
|
|
9
|
+
import { createTwoCaptchaResolverVendorAdapter } from "./resolver-vendors/twocaptcha.js";
|
|
10
|
+
import { RESOLVER_VENDOR_CAPABILITIES, ResolverVendorUnavailableError, resolveProviderResolverVendors, resolverVendorSupports, } from "./resolver-vendors/types.js";
|
|
11
|
+
import { createUnsupportedResolverClient, RESOLVER_INSTRUMENTATION_METADATA, } from "./resolver-shared.js";
|
|
12
|
+
import { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, DEFAULT_RESOLVER_TIMEOUT_MS, } from "./resolver-config.js";
|
|
13
|
+
import { DEFAULT_STEALTH_PROFILE } from "./stealth.js";
|
|
14
|
+
export { createUnsupportedResolverClient, RESOLVER_INSTRUMENTATION_METADATA, } from "./resolver-shared.js";
|
|
15
|
+
export { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, DEFAULT_RESOLVER_TIMEOUT_MS, } from "./resolver-config.js";
|
|
16
|
+
export { DEFAULT_RESOLVER_VENDOR_PREFERENCE, resolveProviderResolverVendors, } from "./resolver-vendors/types.js";
|
|
17
|
+
const RESOLVER_SOLUTION_CACHE_NAMESPACE = "resolver-solution";
|
|
18
|
+
const RESOLVER_SOLUTION_INDEX_CACHE_NAMESPACE = "resolver-solution-index";
|
|
19
|
+
const MIN_RESOLVER_CACHE_TTL_MS = 1_000;
|
|
20
|
+
const resolverCaches = new WeakMap();
|
|
21
|
+
const solutionIssuerDigests = new WeakMap();
|
|
22
|
+
const resolverSolutionSources = new WeakMap();
|
|
23
|
+
const SAFE_CAUSE_MESSAGE_WORDS = new Set([
|
|
24
|
+
"abort",
|
|
25
|
+
"aborted",
|
|
26
|
+
"at",
|
|
27
|
+
"closed",
|
|
28
|
+
"connect",
|
|
29
|
+
"connection",
|
|
30
|
+
"dns",
|
|
31
|
+
"during",
|
|
32
|
+
"econnrefused",
|
|
33
|
+
"econnreset",
|
|
34
|
+
"error",
|
|
35
|
+
"etimedout",
|
|
36
|
+
"failed",
|
|
37
|
+
"failure",
|
|
38
|
+
"fetch",
|
|
39
|
+
"from",
|
|
40
|
+
"get",
|
|
41
|
+
"lookup",
|
|
42
|
+
"network",
|
|
43
|
+
"post",
|
|
44
|
+
"reading",
|
|
45
|
+
"refused",
|
|
46
|
+
"request",
|
|
47
|
+
"reset",
|
|
48
|
+
"response",
|
|
49
|
+
"socket",
|
|
50
|
+
"timed",
|
|
51
|
+
"timeout",
|
|
52
|
+
"tls",
|
|
53
|
+
"to",
|
|
54
|
+
"unavailable",
|
|
55
|
+
"upstream",
|
|
56
|
+
"while",
|
|
57
|
+
"writing",
|
|
58
|
+
]);
|
|
59
|
+
const resolverAdapterRegistry = {
|
|
60
|
+
"2captcha"(configuration, timeoutMs, allowedHosts) {
|
|
61
|
+
if (configuration === undefined) {
|
|
62
|
+
throw new Error("2captcha resolver adapter factory requires an API key");
|
|
63
|
+
}
|
|
64
|
+
return createTwoCaptchaResolverVendorAdapter({
|
|
65
|
+
allowedHosts,
|
|
66
|
+
apiKey: configuration,
|
|
67
|
+
timeoutMs,
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
capsolver(configuration, timeoutMs, allowedHosts) {
|
|
71
|
+
return createCapsolverResolverVendorAdapter({
|
|
72
|
+
allowedHosts,
|
|
73
|
+
apiKey: configuration,
|
|
74
|
+
timeoutMs,
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
browser(configuration, timeoutMs, allowedHosts) {
|
|
78
|
+
return createBrowserResolverVendorAdapter({
|
|
79
|
+
allowedHosts,
|
|
80
|
+
cdpUrl: configuration,
|
|
81
|
+
timeoutMs,
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
export const RESOLVER_ADAPTER_REGISTRY = resolverAdapterRegistry;
|
|
86
|
+
export function swapResolverAdapterFactoryForTests(vendor, factory) {
|
|
87
|
+
const original = resolverAdapterRegistry[vendor];
|
|
88
|
+
if (factory === undefined)
|
|
89
|
+
delete resolverAdapterRegistry[vendor];
|
|
90
|
+
else
|
|
91
|
+
resolverAdapterRegistry[vendor] = factory;
|
|
92
|
+
let restored = false;
|
|
93
|
+
return () => {
|
|
94
|
+
if (restored)
|
|
95
|
+
return;
|
|
96
|
+
restored = true;
|
|
97
|
+
if (original === undefined)
|
|
98
|
+
delete resolverAdapterRegistry[vendor];
|
|
99
|
+
else
|
|
100
|
+
resolverAdapterRegistry[vendor] = original;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
let resolveDefaultResolverUserAgent = () => getStealthProfile(DEFAULT_STEALTH_PROFILE).userAgent;
|
|
104
|
+
/** Internal test seam; deliberately not re-exported from the package root. */
|
|
105
|
+
export function swapResolverDefaultUserAgentForTests(resolver) {
|
|
106
|
+
const original = resolveDefaultResolverUserAgent;
|
|
107
|
+
resolveDefaultResolverUserAgent =
|
|
108
|
+
resolver ?? (() => getStealthProfile(DEFAULT_STEALTH_PROFILE).userAgent);
|
|
109
|
+
let restored = false;
|
|
110
|
+
return () => {
|
|
111
|
+
if (restored)
|
|
112
|
+
return;
|
|
113
|
+
restored = true;
|
|
114
|
+
resolveDefaultResolverUserAgent = original;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// This is the sole allowlist for declared vendors whose registry entry may be absent.
|
|
118
|
+
// Remove a vendor here when its adapter is registered.
|
|
119
|
+
const KNOWN_UNIMPLEMENTED_RESOLVER_VENDORS = new Set([
|
|
120
|
+
"capmonster",
|
|
121
|
+
"custom",
|
|
122
|
+
]);
|
|
123
|
+
function normalizedEnvValue(env, key) {
|
|
124
|
+
const value = env[key]?.trim();
|
|
125
|
+
return value ? value : undefined;
|
|
126
|
+
}
|
|
127
|
+
function readPositiveIntegerEnv(env, name) {
|
|
128
|
+
const raw = env[name]?.trim();
|
|
129
|
+
if (!raw)
|
|
130
|
+
return undefined;
|
|
131
|
+
if (!/^[1-9]\d*$/.test(raw)) {
|
|
132
|
+
throw new Error(`${name} must be a positive integer`);
|
|
133
|
+
}
|
|
134
|
+
return raw;
|
|
135
|
+
}
|
|
136
|
+
function assertDeclaredKind(requestedKind, declaredKinds) {
|
|
137
|
+
if (declaredKinds.includes(requestedKind))
|
|
138
|
+
return;
|
|
139
|
+
const declared = declaredKinds.length > 0 ? declaredKinds.join(", ") : "none";
|
|
140
|
+
throw new ProviderError(`Resolver kind "${requestedKind}" is not declared; declared kinds: ${declared}`, {
|
|
141
|
+
code: "RESOLVER_KIND_NOT_DECLARED",
|
|
142
|
+
fix: `Add "${requestedKind}" to the provider's resolver.kinds declaration.`,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function createUnavailableAdapter(vendor, reason) {
|
|
146
|
+
return {
|
|
147
|
+
id: vendor,
|
|
148
|
+
supports(kind) {
|
|
149
|
+
return resolverVendorSupports(vendor, kind);
|
|
150
|
+
},
|
|
151
|
+
async solve() {
|
|
152
|
+
throw new ResolverVendorUnavailableError(vendor, reason);
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function createAdapter(vendor, timeoutMs, allowedHosts, adapterFactories) {
|
|
157
|
+
const factory = adapterFactories[vendor.vendor];
|
|
158
|
+
if (!factory && !KNOWN_UNIMPLEMENTED_RESOLVER_VENDORS.has(vendor.vendor)) {
|
|
159
|
+
throw new Error(`Resolver adapter factory is missing for implemented vendor "${vendor.vendor}"`);
|
|
160
|
+
}
|
|
161
|
+
if (!vendor.available) {
|
|
162
|
+
return createUnavailableAdapter(vendor.vendor, vendor.reason);
|
|
163
|
+
}
|
|
164
|
+
if (factory) {
|
|
165
|
+
return factory(vendor.configuration, timeoutMs, allowedHosts);
|
|
166
|
+
}
|
|
167
|
+
return createUnavailableAdapter(vendor.vendor, "not_implemented");
|
|
168
|
+
}
|
|
169
|
+
function assertKnownResolverVendor(vendor) {
|
|
170
|
+
if (Object.hasOwn(RESOLVER_VENDOR_CAPABILITIES, vendor))
|
|
171
|
+
return;
|
|
172
|
+
throw new Error(`Unknown resolver vendor "${vendor}" in resolver configuration`);
|
|
173
|
+
}
|
|
174
|
+
function throwUnsupportedKind(kind, usingDefaultVendors) {
|
|
175
|
+
throw new ProviderError(`Resolver vendor chain does not support kind "${kind}"`, {
|
|
176
|
+
code: "RESOLVER_KIND_UNSUPPORTED_BY_CHAIN",
|
|
177
|
+
fix: usingDefaultVendors
|
|
178
|
+
? `No SDK default resolver vendor supports "${kind}". Declare an explicit resolver.vendors override with a supporting vendor.`
|
|
179
|
+
: `Add a resolver vendor that supports "${kind}" to the provider's resolver.vendors declaration.`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function throwExhausted(attempts) {
|
|
183
|
+
const hasMissingChallengeInput = attempts.some((attempt) => attempt.reason === "missing_challenge_input");
|
|
184
|
+
const summary = attempts
|
|
185
|
+
.map(({ vendor, reason, missingFields }) => missingFields === undefined || missingFields.length === 0
|
|
186
|
+
? `${vendor}: ${reason}`
|
|
187
|
+
: `${vendor}: ${reason} (missing fields: ${missingFields.join(", ")})`)
|
|
188
|
+
.join(", ");
|
|
189
|
+
throw new ProviderError(`Resolver vendor chain exhausted: ${summary}`, {
|
|
190
|
+
code: "RESOLVER_CHAIN_EXHAUSTED",
|
|
191
|
+
fix: hasMissingChallengeInput
|
|
192
|
+
? "Capture the named challenge fields or configure another supporting resolver vendor."
|
|
193
|
+
: "Configure another supporting resolver vendor or restore an unavailable vendor.",
|
|
194
|
+
details: attempts,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
function assertClientProfileTransportContract(clientProfile, transport) {
|
|
198
|
+
if (clientProfile === undefined || transport === undefined)
|
|
199
|
+
return;
|
|
200
|
+
throw new ProviderError(`Resolver client profile "${clientProfile}" cannot be applied to a pre-bound transport`, {
|
|
201
|
+
code: "RESOLVER_CLIENT_PROFILE_TRANSPORT_CONFLICT",
|
|
202
|
+
fix: "Remove the pre-bound transport and provide createTransport({ clientProfile, identityScope }) so the SDK can apply the provider-declared profile.",
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
function adapterRequiresTransport(adapter, kind) {
|
|
206
|
+
return typeof adapter.requiresTransport === "function"
|
|
207
|
+
? adapter.requiresTransport(kind)
|
|
208
|
+
: adapter.requiresTransport === true;
|
|
209
|
+
}
|
|
210
|
+
function sanitizeDiagnosticUrl(rawUrl) {
|
|
211
|
+
try {
|
|
212
|
+
const parsed = new URL(rawUrl);
|
|
213
|
+
if (parsed.username || parsed.password)
|
|
214
|
+
return "[REDACTED_PROXY_URL]";
|
|
215
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return "[REDACTED_URL]";
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function sanitizeCauseMessage(message) {
|
|
222
|
+
const withoutUrls = message.replace(/\b[a-z][a-z\d+.-]*:\/\/[^\s"'<>]+/gi, sanitizeDiagnosticUrl);
|
|
223
|
+
const withoutAssignments = withoutUrls.replace(/(?:^|\s)[^\s=]+=[^\s]*/g, " [REDACTED]");
|
|
224
|
+
const safeTokens = withoutAssignments
|
|
225
|
+
.replaceAll("\r", " ")
|
|
226
|
+
.replaceAll("\n", " ")
|
|
227
|
+
.split(/\s+/)
|
|
228
|
+
.filter(Boolean)
|
|
229
|
+
.map((token) => {
|
|
230
|
+
if (token === "[REDACTED]" ||
|
|
231
|
+
token === "[REDACTED_PROXY_URL]" ||
|
|
232
|
+
/^[a-z][a-z\d+.-]*:\/\/[^\s]+$/i.test(token))
|
|
233
|
+
return token;
|
|
234
|
+
const word = token.replace(/^[^a-z\d]+|[^a-z\d]+$/gi, "");
|
|
235
|
+
return word.length > 0 &&
|
|
236
|
+
word.length <= 32 &&
|
|
237
|
+
SAFE_CAUSE_MESSAGE_WORDS.has(word.toLowerCase())
|
|
238
|
+
? token
|
|
239
|
+
: "[REDACTED]";
|
|
240
|
+
});
|
|
241
|
+
return safeTokens
|
|
242
|
+
.filter((token, index) => token !== "[REDACTED]" || safeTokens[index - 1] !== token)
|
|
243
|
+
.join(" ")
|
|
244
|
+
.slice(0, 512);
|
|
245
|
+
}
|
|
246
|
+
function restrictResolverTransport(transport, allowedHosts) {
|
|
247
|
+
return {
|
|
248
|
+
async fetch(url, init) {
|
|
249
|
+
// Empty declarations remain deny-by-default, matching the adapter-factory/browser path.
|
|
250
|
+
assertResolverHostAllowed(url, allowedHosts);
|
|
251
|
+
const response = await transport.fetch(url, { ...init, redirect: "manual" });
|
|
252
|
+
const hasLocationHeader = Object.keys(response.headers).some((name) => name.toLowerCase() === "location");
|
|
253
|
+
if (response.status >= 300 && response.status < 400 && hasLocationHeader) {
|
|
254
|
+
throw new ProviderError("Resolver transport refused a redirect response", {
|
|
255
|
+
code: "RESOLVER_HOST_NOT_ALLOWED",
|
|
256
|
+
fix: "Use a non-redirecting http or https URL on a host declared in allowedHosts.",
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return response;
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function safeCause(error) {
|
|
264
|
+
if (error.cause === undefined)
|
|
265
|
+
return undefined;
|
|
266
|
+
const cause = error.cause;
|
|
267
|
+
const rawName = cause instanceof Error ? cause.name : "Error";
|
|
268
|
+
return {
|
|
269
|
+
name: /^[a-z\d_.:-]{1,64}$/i.test(rawName) ? rawName : "Error",
|
|
270
|
+
message: sanitizeCauseMessage(cause instanceof Error ? cause.message : String(cause)),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function safeUpstreamHost(host) {
|
|
274
|
+
if (host === undefined)
|
|
275
|
+
return undefined;
|
|
276
|
+
const trimmed = host.trim();
|
|
277
|
+
if (/^[a-z\d.-]+$/i.test(trimmed))
|
|
278
|
+
return trimmed.toLowerCase();
|
|
279
|
+
try {
|
|
280
|
+
return new URL(trimmed).hostname.toLowerCase();
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function safePhase(phase) {
|
|
287
|
+
return phase !== undefined && /^[a-z\d_.:-]{1,64}$/i.test(phase) ? phase : undefined;
|
|
288
|
+
}
|
|
289
|
+
function unavailableAttempt(error) {
|
|
290
|
+
const cause = safeCause(error);
|
|
291
|
+
const upstreamHost = safeUpstreamHost(error.upstreamHost);
|
|
292
|
+
const phase = safePhase(error.phase);
|
|
293
|
+
const round = Number.isSafeInteger(error.round) && error.round > 0 ? error.round : undefined;
|
|
294
|
+
return {
|
|
295
|
+
vendor: error.vendor,
|
|
296
|
+
reason: error.reason,
|
|
297
|
+
...(error.missingFields ? { missingFields: [...error.missingFields] } : {}),
|
|
298
|
+
...(cause ? { cause } : {}),
|
|
299
|
+
...(upstreamHost ? { upstreamHost } : {}),
|
|
300
|
+
...(phase ? { phase } : {}),
|
|
301
|
+
...(round !== undefined ? { round } : {}),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function unavailableSpanAttributes(error) {
|
|
305
|
+
const attempt = unavailableAttempt(error);
|
|
306
|
+
return {
|
|
307
|
+
unavailability_reason: error.reason,
|
|
308
|
+
missing_fields: error.missingFields,
|
|
309
|
+
cause_name: attempt.cause?.name,
|
|
310
|
+
cause_message: attempt.cause?.message,
|
|
311
|
+
upstream_host: attempt.upstreamHost,
|
|
312
|
+
transport_phase: attempt.phase,
|
|
313
|
+
transport_round: attempt.round,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
function challengeOrigin(challenge) {
|
|
317
|
+
return new URL(challenge.pageUrl).origin;
|
|
318
|
+
}
|
|
319
|
+
function resolverIdentityDigest(identity) {
|
|
320
|
+
return createHash("sha256")
|
|
321
|
+
.update(JSON.stringify({
|
|
322
|
+
proxyUrl: identity.proxyUrl ?? null,
|
|
323
|
+
userAgent: identity.userAgent,
|
|
324
|
+
}))
|
|
325
|
+
.digest("hex");
|
|
326
|
+
}
|
|
327
|
+
function resolverIdentityScopeDigest(identityScope) {
|
|
328
|
+
return createHash("sha256").update(JSON.stringify({ identityScope })).digest("hex");
|
|
329
|
+
}
|
|
330
|
+
function resolverSolutionCacheKey(cache, challenge, issuerDigest) {
|
|
331
|
+
return cache.key(RESOLVER_SOLUTION_CACHE_NAMESPACE, {
|
|
332
|
+
kind: challenge.kind,
|
|
333
|
+
origin: challengeOrigin(challenge),
|
|
334
|
+
issuerDigest,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
function resolverSolutionIndexCacheKey(cache, challenge) {
|
|
338
|
+
return cache.key(RESOLVER_SOLUTION_INDEX_CACHE_NAMESPACE, {
|
|
339
|
+
kind: challenge.kind,
|
|
340
|
+
origin: challengeOrigin(challenge),
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
function isCachedResolverSolution(value) {
|
|
344
|
+
try {
|
|
345
|
+
if (value === null || typeof value !== "object")
|
|
346
|
+
return false;
|
|
347
|
+
const candidate = value;
|
|
348
|
+
if (typeof candidate.expiresAtMs !== "number" ||
|
|
349
|
+
typeof candidate.issuerDigest !== "string" ||
|
|
350
|
+
candidate.solution === null ||
|
|
351
|
+
typeof candidate.solution !== "object" ||
|
|
352
|
+
candidate.solution.form !== "cookies" ||
|
|
353
|
+
typeof candidate.solution.userAgent !== "string") {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
const cookies = candidate.solution.cookies;
|
|
357
|
+
return (cookies !== null &&
|
|
358
|
+
typeof cookies === "object" &&
|
|
359
|
+
!Array.isArray(cookies) &&
|
|
360
|
+
Object.values(cookies).every((cookie) => typeof cookie === "string"));
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function isResolverCacheIndex(value) {
|
|
367
|
+
if (value === null || typeof value !== "object")
|
|
368
|
+
return false;
|
|
369
|
+
const entries = value.entries;
|
|
370
|
+
return (Array.isArray(entries) &&
|
|
371
|
+
entries.every((entry) => entry !== null &&
|
|
372
|
+
typeof entry === "object" &&
|
|
373
|
+
entry.direct === true &&
|
|
374
|
+
typeof entry.expiresAtMs === "number" &&
|
|
375
|
+
typeof entry.issuerDigest === "string"));
|
|
376
|
+
}
|
|
377
|
+
function solutionExpiryMs(solution) {
|
|
378
|
+
if (solution.form !== "cookies")
|
|
379
|
+
return undefined;
|
|
380
|
+
const expires = solution.expires ?? solution.sdkEstimatedExpires;
|
|
381
|
+
if (typeof expires !== "number" || !Number.isFinite(expires))
|
|
382
|
+
return undefined;
|
|
383
|
+
return expires * 1_000;
|
|
384
|
+
}
|
|
385
|
+
function rememberSolutionIssuer(solution, issuerDigest) {
|
|
386
|
+
if (typeof solution === "object" && solution !== null) {
|
|
387
|
+
solutionIssuerDigests.set(solution, issuerDigest);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function rememberSolutionSource(solution, source) {
|
|
391
|
+
if (typeof solution === "object" && solution !== null) {
|
|
392
|
+
resolverSolutionSources.set(solution, source);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Identify whether this exact SDK-returned solution object came from the cache
|
|
397
|
+
* or a vendor solve. Returns undefined for copied or caller-created objects.
|
|
398
|
+
*/
|
|
399
|
+
export function getResolverSolutionSource(solution) {
|
|
400
|
+
return resolverSolutionSources.get(solution);
|
|
401
|
+
}
|
|
402
|
+
async function readCachedSolution(cache, challenge, issuerDigest, now) {
|
|
403
|
+
const cached = await cache.get(resolverSolutionCacheKey(cache, challenge, issuerDigest));
|
|
404
|
+
if (!cached || !isCachedResolverSolution(cached.value))
|
|
405
|
+
return undefined;
|
|
406
|
+
if (cached.value.issuerDigest !== issuerDigest || cached.value.expiresAtMs <= now)
|
|
407
|
+
return undefined;
|
|
408
|
+
if (cached.value.solution.form !== "cookies")
|
|
409
|
+
return undefined;
|
|
410
|
+
const solution = {
|
|
411
|
+
...cached.value.solution,
|
|
412
|
+
cookies: { ...cached.value.solution.cookies },
|
|
413
|
+
};
|
|
414
|
+
rememberSolutionIssuer(solution, issuerDigest);
|
|
415
|
+
rememberSolutionSource(solution, "cache");
|
|
416
|
+
return solution;
|
|
417
|
+
}
|
|
418
|
+
async function findCachedSolution(cache, challenge, identity, identityScope) {
|
|
419
|
+
const now = Date.now();
|
|
420
|
+
if (identityScope !== undefined && resolverChallengeIsIdentityScoped(challenge)) {
|
|
421
|
+
return await readCachedSolution(cache, challenge, resolverIdentityScopeDigest(identityScope), now);
|
|
422
|
+
}
|
|
423
|
+
if (identity) {
|
|
424
|
+
const lookupIdentity = resolverChallengeIssuingIdentity(challenge, identity);
|
|
425
|
+
return await readCachedSolution(cache, challenge, resolverIdentityDigest(lookupIdentity), now);
|
|
426
|
+
}
|
|
427
|
+
const index = await cache.get(resolverSolutionIndexCacheKey(cache, challenge));
|
|
428
|
+
if (!index || !isResolverCacheIndex(index.value))
|
|
429
|
+
return undefined;
|
|
430
|
+
for (const entry of index.value.entries) {
|
|
431
|
+
if (entry.expiresAtMs <= now)
|
|
432
|
+
continue;
|
|
433
|
+
const solution = await readCachedSolution(cache, challenge, entry.issuerDigest, now);
|
|
434
|
+
if (solution)
|
|
435
|
+
return solution;
|
|
436
|
+
}
|
|
437
|
+
return undefined;
|
|
438
|
+
}
|
|
439
|
+
async function writeResolverCacheIndex(cache, challenge, entries, now) {
|
|
440
|
+
const indexKey = resolverSolutionIndexCacheKey(cache, challenge);
|
|
441
|
+
const liveEntries = entries.filter((entry) => entry.expiresAtMs > now);
|
|
442
|
+
if (liveEntries.length === 0) {
|
|
443
|
+
await cache.delete(indexKey);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
const ttlMs = Math.max(1, Math.floor(Math.max(...liveEntries.map((entry) => entry.expiresAtMs)) - now));
|
|
447
|
+
await cache.set(indexKey, { entries: liveEntries }, { ttlMs });
|
|
448
|
+
}
|
|
449
|
+
async function cacheResolverSolution(cache, challenge, solution, identity, identityScope) {
|
|
450
|
+
if (!resolverChallengeIsCacheable(challenge))
|
|
451
|
+
return;
|
|
452
|
+
const expiresAtMs = solutionExpiryMs(solution);
|
|
453
|
+
const now = Date.now();
|
|
454
|
+
if (expiresAtMs === undefined)
|
|
455
|
+
return;
|
|
456
|
+
const ttlMs = Math.floor(expiresAtMs - now);
|
|
457
|
+
if (ttlMs <= MIN_RESOLVER_CACHE_TTL_MS)
|
|
458
|
+
return;
|
|
459
|
+
const scopedDigest = identityScope !== undefined && resolverChallengeIsIdentityScoped(challenge)
|
|
460
|
+
? resolverIdentityScopeDigest(identityScope)
|
|
461
|
+
: undefined;
|
|
462
|
+
if (!resolverChallengeAllowsDirectCache(challenge) &&
|
|
463
|
+
scopedDigest === undefined &&
|
|
464
|
+
identity.proxyUrl === undefined) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
const issuerDigest = scopedDigest ?? resolverIdentityDigest(identity);
|
|
468
|
+
await cache.set(resolverSolutionCacheKey(cache, challenge, issuerDigest), { expiresAtMs, issuerDigest, solution }, { ttlMs });
|
|
469
|
+
rememberSolutionIssuer(solution, issuerDigest);
|
|
470
|
+
if (scopedDigest !== undefined || identity.proxyUrl !== undefined)
|
|
471
|
+
return;
|
|
472
|
+
const indexKey = resolverSolutionIndexCacheKey(cache, challenge);
|
|
473
|
+
const current = await cache.get(indexKey);
|
|
474
|
+
const currentEntries = isResolverCacheIndex(current?.value) ? current.value.entries : [];
|
|
475
|
+
await writeResolverCacheIndex(cache, challenge, [
|
|
476
|
+
...currentEntries.filter((entry) => entry.issuerDigest !== issuerDigest),
|
|
477
|
+
{ direct: true, expiresAtMs, issuerDigest },
|
|
478
|
+
], now);
|
|
479
|
+
}
|
|
480
|
+
async function invalidateResolverSolutionWithOutcome(resolver, challenge, solution) {
|
|
481
|
+
const metadata = resolver[RESOLVER_INSTRUMENTATION_METADATA];
|
|
482
|
+
const cacheOwner = metadata?.target ?? resolver;
|
|
483
|
+
const invalidate = async () => {
|
|
484
|
+
if (solution.form !== "cookies")
|
|
485
|
+
return "not_cookie_solution";
|
|
486
|
+
if (!resolverCaches.has(cacheOwner)) {
|
|
487
|
+
throw new Error("Resolver cache registration lookup failed during solution invalidation");
|
|
488
|
+
}
|
|
489
|
+
const cache = resolverCaches.get(cacheOwner);
|
|
490
|
+
if (cache === null || cache === undefined)
|
|
491
|
+
return "cache_disabled";
|
|
492
|
+
const issuerDigest = solutionIssuerDigests.get(solution);
|
|
493
|
+
if (!issuerDigest)
|
|
494
|
+
return "solution_not_cached";
|
|
495
|
+
await cache.delete(resolverSolutionCacheKey(cache, challenge, issuerDigest));
|
|
496
|
+
const index = await cache.get(resolverSolutionIndexCacheKey(cache, challenge));
|
|
497
|
+
if (!index)
|
|
498
|
+
return "entry_deleted";
|
|
499
|
+
if (!isResolverCacheIndex(index.value)) {
|
|
500
|
+
throw new Error("Resolver solution cache index is malformed during invalidation");
|
|
501
|
+
}
|
|
502
|
+
await writeResolverCacheIndex(cache, challenge, index.value.entries.filter((entry) => entry.issuerDigest !== issuerDigest), Date.now());
|
|
503
|
+
return "index_entry_deleted";
|
|
504
|
+
};
|
|
505
|
+
if (!metadata) {
|
|
506
|
+
return await invalidate();
|
|
507
|
+
}
|
|
508
|
+
return await metadata.traceRecorder.runSpan("resolver.cache.invalidate", invalidate, {
|
|
509
|
+
attributes: { challenge_kind: challenge.kind },
|
|
510
|
+
onSuccess: (outcome) => ({ outcome }),
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
/** Remove the cached entry for the exact solution object returned by this resolver. */
|
|
514
|
+
export async function invalidateResolverSolution(resolver, challenge, solution) {
|
|
515
|
+
await invalidateResolverSolutionWithOutcome(resolver, challenge, solution);
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Remove a solution only when it was returned from the resolver cache. Providers
|
|
519
|
+
* should call this when an upstream serves the same challenge after a cached
|
|
520
|
+
* solution was applied, so the next `solve()` mints a fresh solution.
|
|
521
|
+
*/
|
|
522
|
+
export async function invalidateCachedResolverSolution(resolver, challenge, solution) {
|
|
523
|
+
if (getResolverSolutionSource(solution) !== "cache")
|
|
524
|
+
return false;
|
|
525
|
+
const outcome = await invalidateResolverSolutionWithOutcome(resolver, challenge, solution);
|
|
526
|
+
return outcome === "entry_deleted" || outcome === "index_entry_deleted";
|
|
527
|
+
}
|
|
528
|
+
async function resolveResolverIdentity(proxyIntent) {
|
|
529
|
+
const userAgentSource = proxyIntent.userAgent ? "declared" : "defaulted";
|
|
530
|
+
let proxyUrl;
|
|
531
|
+
try {
|
|
532
|
+
const resolved = await resolveProxyConfigAsync({
|
|
533
|
+
upstream: proxyIntent.upstream,
|
|
534
|
+
affinityKey: proxyIntent.affinityKey,
|
|
535
|
+
telemetry: proxyIntent.telemetry
|
|
536
|
+
? {
|
|
537
|
+
...proxyIntent.telemetry,
|
|
538
|
+
recordProxyResolution(event) {
|
|
539
|
+
proxyIntent.telemetry?.recordProxyResolution({
|
|
540
|
+
...event,
|
|
541
|
+
userAgentSource,
|
|
542
|
+
});
|
|
543
|
+
},
|
|
544
|
+
}
|
|
545
|
+
: undefined,
|
|
546
|
+
});
|
|
547
|
+
proxyUrl = resolved.url;
|
|
548
|
+
if (!proxyUrl)
|
|
549
|
+
return { unavailableReason: "missing_proxy_identity", userAgentSource };
|
|
550
|
+
}
|
|
551
|
+
catch {
|
|
552
|
+
// Lease failures contain infrastructure detail that must not cross the resolver
|
|
553
|
+
// boundary. A required policy is classified by the existing fail-closed guard.
|
|
554
|
+
return { unavailableReason: "missing_proxy_identity", userAgentSource };
|
|
555
|
+
}
|
|
556
|
+
try {
|
|
557
|
+
const userAgent = proxyIntent.userAgent || resolveDefaultResolverUserAgent();
|
|
558
|
+
if (!userAgent)
|
|
559
|
+
return { unavailableReason: "missing_client_profile", userAgentSource };
|
|
560
|
+
return {
|
|
561
|
+
identity: { proxyUrl, userAgent },
|
|
562
|
+
userAgentSource,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
return { unavailableReason: "missing_client_profile", userAgentSource };
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
function createResolverChainClient(options) {
|
|
570
|
+
assertClientProfileTransportContract(options.clientProfile, options.transport);
|
|
571
|
+
const client = {
|
|
572
|
+
async solve(challenge, signal = new AbortController().signal, traceRecorder) {
|
|
573
|
+
assertDeclaredKind(challenge.kind, options.kinds);
|
|
574
|
+
if (options.unavailableReason) {
|
|
575
|
+
throw new ProviderError(options.unavailableReason, {
|
|
576
|
+
code: "RESOLVER_UNAVAILABLE",
|
|
577
|
+
fix: "Configure at least one declared resolver vendor or provide a test ResolverContext override.",
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
const supportingEntries = options.entries.filter((entry) => entry.supports(challenge.kind));
|
|
581
|
+
if (supportingEntries.length === 0) {
|
|
582
|
+
throwUnsupportedKind(challenge.kind, options.usingDefaultVendors ?? false);
|
|
583
|
+
}
|
|
584
|
+
signal.throwIfAborted();
|
|
585
|
+
const identityResolution = options.proxyIntent
|
|
586
|
+
? await resolveResolverIdentity(options.proxyIntent)
|
|
587
|
+
: { identity: options.identity };
|
|
588
|
+
const identity = identityResolution.identity;
|
|
589
|
+
signal.throwIfAborted();
|
|
590
|
+
// Resolve a required proxy lease before consulting the cache. Solutions minted
|
|
591
|
+
// under a previous release are shared and long-lived, but a portable cached token
|
|
592
|
+
// must not bypass the upstream admission policy when no lease can be resolved.
|
|
593
|
+
const requiredProxyIdentityMissing = options.proxyIntent?.mode === "required" && identity === undefined;
|
|
594
|
+
if (requiredProxyIdentityMissing) {
|
|
595
|
+
throwExhausted(supportingEntries.map((entry) => ({
|
|
596
|
+
vendor: entry.id,
|
|
597
|
+
reason: identityResolution.unavailableReason ?? "missing_proxy_identity",
|
|
598
|
+
})));
|
|
599
|
+
}
|
|
600
|
+
if (options.cache && resolverChallengeIsCacheable(challenge)) {
|
|
601
|
+
const cached = await findCachedSolution(options.cache, challenge, identity, options.identityScope);
|
|
602
|
+
if (cached)
|
|
603
|
+
return cached;
|
|
604
|
+
}
|
|
605
|
+
const attempts = [];
|
|
606
|
+
for (const entry of supportingEntries) {
|
|
607
|
+
const adapter = entry.createAdapter();
|
|
608
|
+
try {
|
|
609
|
+
const solveAttempt = () => {
|
|
610
|
+
const requiresTransport = adapterRequiresTransport(adapter, challenge.kind);
|
|
611
|
+
const unrestrictedTransport = options.transport ??
|
|
612
|
+
(requiresTransport
|
|
613
|
+
? options.createTransport?.({
|
|
614
|
+
clientProfile: options.clientProfile,
|
|
615
|
+
identityScope: options.identityScope,
|
|
616
|
+
})
|
|
617
|
+
: undefined);
|
|
618
|
+
if (requiresTransport && unrestrictedTransport === undefined) {
|
|
619
|
+
throw new ResolverVendorUnavailableError(adapter.id, "missing_transport");
|
|
620
|
+
}
|
|
621
|
+
const transport = unrestrictedTransport
|
|
622
|
+
? restrictResolverTransport(unrestrictedTransport, options.allowedHosts ?? [])
|
|
623
|
+
: undefined;
|
|
624
|
+
return adapter.solve(challenge, identity, signal, traceRecorder, transport);
|
|
625
|
+
};
|
|
626
|
+
const solution = traceRecorder
|
|
627
|
+
? await traceRecorder.runSpan("resolver.vendor.attempt", solveAttempt, {
|
|
628
|
+
attributes: {
|
|
629
|
+
vendor: adapter.id,
|
|
630
|
+
challenge_kind: challenge.kind,
|
|
631
|
+
client_profile: options.clientProfile,
|
|
632
|
+
resolver_identity_source: identityResolution.userAgentSource,
|
|
633
|
+
},
|
|
634
|
+
onError(error) {
|
|
635
|
+
return error instanceof ResolverVendorUnavailableError
|
|
636
|
+
? unavailableSpanAttributes(error)
|
|
637
|
+
: undefined;
|
|
638
|
+
},
|
|
639
|
+
})
|
|
640
|
+
: await solveAttempt();
|
|
641
|
+
if (options.cache &&
|
|
642
|
+
resolverChallengeIsCacheable(challenge) &&
|
|
643
|
+
solution.form === "cookies" &&
|
|
644
|
+
solutionExpiryMs(solution) !== undefined) {
|
|
645
|
+
const issuingIdentity = adapter.getIssuingIdentity?.(solution, identity, challenge) ??
|
|
646
|
+
resolverChallengeIssuingIdentity(challenge, {
|
|
647
|
+
...(identity ? { proxyUrl: identity.proxyUrl } : {}),
|
|
648
|
+
userAgent: solution.userAgent,
|
|
649
|
+
});
|
|
650
|
+
if (issuingIdentity) {
|
|
651
|
+
await cacheResolverSolution(options.cache, challenge, solution, issuingIdentity, options.identityScope);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
rememberSolutionSource(solution, "vendor");
|
|
655
|
+
return solution;
|
|
656
|
+
}
|
|
657
|
+
catch (error) {
|
|
658
|
+
signal.throwIfAborted();
|
|
659
|
+
if (!(error instanceof ResolverVendorUnavailableError))
|
|
660
|
+
throw error;
|
|
661
|
+
// Every vendor-unavailable result, including missing_challenge_input, falls
|
|
662
|
+
// through so another adapter can solve with a different input contract.
|
|
663
|
+
attempts.push(unavailableAttempt(error));
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
throwExhausted(attempts);
|
|
667
|
+
},
|
|
668
|
+
};
|
|
669
|
+
resolverCaches.set(client, options.cache ?? null);
|
|
670
|
+
return client;
|
|
671
|
+
}
|
|
672
|
+
export function createResolverClient(options) {
|
|
673
|
+
return createResolverChainClient({
|
|
674
|
+
kinds: options.kinds,
|
|
675
|
+
entries: options.adapters.map((adapter) => ({
|
|
676
|
+
id: adapter.id,
|
|
677
|
+
supports: (kind) => adapter.supports(kind),
|
|
678
|
+
createAdapter: () => adapter,
|
|
679
|
+
})),
|
|
680
|
+
unavailableReason: options.unavailableReason,
|
|
681
|
+
cache: options.cache,
|
|
682
|
+
identity: options.identity,
|
|
683
|
+
proxyIntent: options.proxyIntent,
|
|
684
|
+
transport: options.transport,
|
|
685
|
+
createTransport: options.createTransport,
|
|
686
|
+
clientProfile: options.clientProfile,
|
|
687
|
+
allowedHosts: options.allowedHosts,
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
function resolveVendorAvailability(vendor, env) {
|
|
691
|
+
if (vendor === "custom") {
|
|
692
|
+
return {
|
|
693
|
+
vendor,
|
|
694
|
+
available: false,
|
|
695
|
+
reason: "missing_transport",
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
if (vendor === "browser") {
|
|
699
|
+
return {
|
|
700
|
+
vendor,
|
|
701
|
+
available: true,
|
|
702
|
+
configuration: normalizedEnvValue(env, APIFUSE__CDP_POOL__URL),
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
const envKey = vendor === "2captcha"
|
|
706
|
+
? APIFUSE__RESOLVER__2CAPTCHA__API_KEY
|
|
707
|
+
: vendor === "capsolver"
|
|
708
|
+
? APIFUSE__RESOLVER__CAPSOLVER__API_KEY
|
|
709
|
+
: APIFUSE__RESOLVER__CAPMONSTER__API_KEY;
|
|
710
|
+
const configuration = normalizedEnvValue(env, envKey);
|
|
711
|
+
return configuration
|
|
712
|
+
? { vendor, available: true, configuration }
|
|
713
|
+
: { vendor, available: false, reason: "missing_credentials" };
|
|
714
|
+
}
|
|
715
|
+
export function bindResolverSignal(resolver, defaultSignal) {
|
|
716
|
+
if (!defaultSignal)
|
|
717
|
+
return resolver;
|
|
718
|
+
const boundResolver = {
|
|
719
|
+
solve(challenge, signal = defaultSignal, traceRecorder) {
|
|
720
|
+
return resolver.solve(challenge, signal, traceRecorder);
|
|
721
|
+
},
|
|
722
|
+
};
|
|
723
|
+
if (resolverCaches.has(resolver)) {
|
|
724
|
+
resolverCaches.set(boundResolver, resolverCaches.get(resolver) ?? null);
|
|
725
|
+
}
|
|
726
|
+
return boundResolver;
|
|
727
|
+
}
|
|
728
|
+
function createResolverClientFromEnvInternal(config, env, options, adapterFactories) {
|
|
729
|
+
if (!config) {
|
|
730
|
+
return createUnsupportedResolverClient("Provider does not declare resolver capability");
|
|
731
|
+
}
|
|
732
|
+
assertClientProfileTransportContract(config.clientProfile, options.transport);
|
|
733
|
+
const vendors = resolveProviderResolverVendors(config);
|
|
734
|
+
if (config.vendors !== undefined && config.vendors.length === 0) {
|
|
735
|
+
return createResolverChainClient({
|
|
736
|
+
kinds: config.kinds,
|
|
737
|
+
entries: [],
|
|
738
|
+
unavailableReason: "Provider resolver vendor chain is empty",
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
const timeoutValue = readPositiveIntegerEnv(env, APIFUSE__RESOLVER__TIMEOUT_MS);
|
|
742
|
+
const timeoutMs = timeoutValue === undefined ? DEFAULT_RESOLVER_TIMEOUT_MS : Number(timeoutValue);
|
|
743
|
+
const allowedHosts = [...(options.allowedHosts ?? [])];
|
|
744
|
+
return createResolverChainClient({
|
|
745
|
+
kinds: config.kinds,
|
|
746
|
+
entries: vendors.map((configuredVendor) => {
|
|
747
|
+
assertKnownResolverVendor(configuredVendor);
|
|
748
|
+
const vendor = configuredVendor;
|
|
749
|
+
return {
|
|
750
|
+
id: vendor,
|
|
751
|
+
supports: (kind) => resolverVendorSupports(vendor, kind),
|
|
752
|
+
createAdapter: () => createAdapter(resolveVendorAvailability(vendor, env), timeoutMs, allowedHosts, adapterFactories),
|
|
753
|
+
};
|
|
754
|
+
}),
|
|
755
|
+
usingDefaultVendors: config.vendors === undefined,
|
|
756
|
+
cache: options.cache,
|
|
757
|
+
proxyIntent: options.proxyIntent,
|
|
758
|
+
identityScope: options.identityScope,
|
|
759
|
+
transport: options.transport,
|
|
760
|
+
createTransport: options.createTransport,
|
|
761
|
+
clientProfile: config.clientProfile,
|
|
762
|
+
allowedHosts,
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
export function createResolverClientFromEnv(config, env = process.env, options = {}) {
|
|
766
|
+
return createResolverClientFromEnvInternal(config, env, options, RESOLVER_ADAPTER_REGISTRY);
|
|
767
|
+
}
|
|
768
|
+
/** Internal test seam; deliberately not re-exported from the package root. */
|
|
769
|
+
export function createResolverClientFromEnvForTests(config, env, options, adapterFactories) {
|
|
770
|
+
return createResolverClientFromEnvInternal(config, env, options, adapterFactories);
|
|
771
|
+
}
|