@apifuse/provider-sdk 2.2.0-beta.3 → 2.2.0-beta.30
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 +493 -5
- package/CHANGELOG.md +127 -1
- package/README.md +52 -6
- package/SUBMISSION.md +1 -1
- package/bin/apifuse-check.ts +106 -62
- package/bin/apifuse-create.ts +1 -1
- package/bin/apifuse-dev.ts +63 -55
- package/bin/apifuse-pack-check.ts +22 -2
- package/bin/apifuse-pack-smoke.ts +78 -82
- package/bin/apifuse-pack-types.ts +583 -0
- package/bin/apifuse-perf.ts +59 -140
- package/bin/apifuse-record.ts +698 -113
- package/bin/apifuse-submit-check.ts +517 -44
- package/bin/apifuse-sync-assets.ts +117 -0
- package/bin/apifuse.ts +1 -1
- package/bin/submit-check-delimited-text.ts +50 -0
- package/bin/submit-check-xml.ts +1 -1
- package/dist/auth-turn/index.d.ts +4 -4
- package/dist/auth-turn/index.js +1 -1
- package/dist/auth.d.ts +16 -2
- package/dist/auth.js +76 -18
- package/dist/ceremonies/index.d.ts +9 -1
- package/dist/ceremonies/index.js +117 -29
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +8 -0
- package/dist/cli/create.d.ts +3 -0
- package/dist/cli/create.js +34 -35
- 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/README.md.tpl +4 -4
- package/dist/config/loader.d.ts +176 -17
- package/dist/config/loader.js +434 -161
- package/dist/contract-serialization.d.ts +2 -2
- package/dist/contract-serialization.js +7 -14
- package/dist/contract-types.d.ts +3 -2
- package/dist/contract.d.ts +3 -3
- package/dist/contract.js +6 -6
- package/dist/declaration-validation.d.ts +23 -0
- package/dist/declaration-validation.js +159 -0
- package/dist/define.d.ts +13 -1
- package/dist/define.js +391 -122
- package/dist/dev.d.ts +1 -1
- package/dist/dev.js +1 -1
- package/dist/error-resolution.d.ts +4 -0
- package/dist/error-resolution.js +122 -0
- package/dist/errors.d.ts +19 -1
- package/dist/errors.js +41 -3
- package/dist/fixture-sanitization.d.ts +26 -0
- package/dist/fixture-sanitization.js +216 -0
- package/dist/i18n/catalog.d.ts +2 -2
- package/dist/i18n/catalog.js +4 -10
- package/dist/i18n/index.d.ts +2 -2
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/keys.d.ts +2 -2
- package/dist/index.d.ts +50 -42
- package/dist/index.js +41 -37
- package/dist/lint.d.ts +6 -1
- package/dist/lint.js +370 -18
- 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 +13 -11
- package/dist/provider.js +10 -9
- package/dist/public-schema-field-lint.d.ts +1 -1
- package/dist/recipes/gov-api.js +1 -1
- package/dist/runtime/auth-flow.d.ts +3 -1
- package/dist/runtime/auth-flow.js +8 -3
- package/dist/runtime/browser.d.ts +1 -1
- package/dist/runtime/browser.js +138 -40
- package/dist/runtime/cache.d.ts +2 -1
- package/dist/runtime/cache.js +173 -23
- 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 +514 -124
- package/dist/runtime/credential.d.ts +1 -1
- package/dist/runtime/credential.js +1 -1
- package/dist/runtime/env.d.ts +1 -1
- package/dist/runtime/executor.d.ts +1 -1
- package/dist/runtime/executor.js +25 -3
- package/dist/runtime/http.d.ts +3 -2
- package/dist/runtime/http.js +517 -55
- package/dist/runtime/insights.d.ts +1 -1
- package/dist/runtime/insights.js +6 -13
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +371 -23
- package/dist/runtime/keyring.js +1 -1
- package/dist/runtime/namespace.js +1 -1
- 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 +1 -1
- package/dist/runtime/perf.d.ts +1 -1
- package/dist/runtime/provider.d.ts +1 -1
- package/dist/runtime/provider.js +1 -2
- package/dist/runtime/proxy-errors.d.ts +1 -1
- package/dist/runtime/proxy-errors.js +9 -7
- package/dist/runtime/proxy-nodemaven.d.ts +56 -0
- package/dist/runtime/proxy-nodemaven.js +146 -0
- package/dist/runtime/proxy-retry-policy.d.ts +2 -2
- package/dist/runtime/proxy-retry-policy.js +2 -2
- package/dist/runtime/proxy-telemetry.d.ts +2 -1
- package/dist/runtime/proxy-telemetry.js +58 -52
- 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 +5 -5
- 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 +3 -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 +20 -0
- package/dist/runtime/resolver-vendors/browser.js +282 -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 +368 -0
- package/dist/runtime/resolver-vendors/types.d.ts +83 -0
- package/dist/runtime/resolver-vendors/types.js +69 -0
- package/dist/runtime/resolver.d.ts +59 -0
- package/dist/runtime/resolver.js +705 -0
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +5 -2
- package/dist/runtime/state.js +280 -74
- package/dist/runtime/stealth-cookies.d.ts +20 -0
- package/dist/runtime/stealth-cookies.js +111 -0
- package/dist/runtime/stealth.d.ts +30 -5
- package/dist/runtime/stealth.js +523 -259
- package/dist/runtime/stt.d.ts +1 -1
- package/dist/runtime/stt.js +12 -27
- package/dist/runtime/timeout.d.ts +5 -0
- package/dist/runtime/timeout.js +12 -0
- package/dist/runtime/trace.d.ts +2 -2
- package/dist/runtime/trace.js +2 -4
- package/dist/runtime/waterfall.d.ts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +7 -15
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/index.d.ts +7 -7
- package/dist/server/index.js +6 -6
- 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-redaction.d.ts +1 -1
- package/dist/server/self-test-redaction.js +1 -1
- package/dist/server/self-test.d.ts +117 -3
- package/dist/server/self-test.js +787 -151
- package/dist/server/serve-implementation.d.ts +210 -0
- package/dist/server/serve-implementation.js +2077 -0
- package/dist/server/serve.d.ts +1 -70
- package/dist/server/serve.js +1 -1143
- package/dist/server/types.d.ts +34 -9
- package/dist/server/types.js +8 -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 +1 -1
- package/dist/stealth/profiles.js +21 -21
- package/dist/stream-evidence.d.ts +74 -0
- package/dist/stream-evidence.js +785 -0
- package/dist/stream.d.ts +1 -1
- package/dist/stream.js +7 -1
- package/dist/testing/index.d.ts +3 -2
- package/dist/testing/index.js +3 -2
- package/dist/testing/run.d.ts +32 -2
- package/dist/testing/run.js +488 -28
- package/dist/types.d.ts +566 -19
- package/dist/types.js +1 -0
- package/dist/user-input.d.ts +30 -0
- package/dist/user-input.js +66 -0
- package/package.json +42 -7
- package/src/auth-turn/index.ts +2 -2
- package/src/auth.ts +146 -86
- package/src/ceremonies/index.ts +167 -92
- package/src/cli/commands.ts +10 -0
- package/src/cli/create.ts +42 -35
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/src/cli/templates/provider/README.md.tpl +4 -4
- package/src/config/loader.ts +667 -289
- package/src/contract-serialization.ts +10 -18
- package/src/contract-types.ts +3 -2
- package/src/contract.ts +14 -28
- package/src/declaration-validation.ts +202 -0
- package/src/define.ts +631 -495
- package/src/dev.ts +4 -9
- package/src/error-resolution.ts +127 -0
- package/src/errors.ts +56 -11
- package/src/fixture-sanitization.ts +247 -0
- package/src/i18n/catalog.ts +10 -32
- package/src/i18n/index.ts +2 -2
- package/src/i18n/keys.ts +5 -11
- package/src/index.ts +157 -44
- package/src/lint.ts +488 -154
- 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 +65 -11
- package/src/public-schema-field-lint.ts +7 -33
- package/src/recipes/gov-api.ts +2 -5
- package/src/runtime/auth-flow.ts +13 -7
- package/src/runtime/browser.ts +252 -207
- package/src/runtime/cache.ts +209 -81
- package/src/runtime/choice-wordlist.ts +145 -0
- package/src/runtime/choice.ts +685 -216
- package/src/runtime/credential.ts +2 -2
- package/src/runtime/env.ts +1 -1
- package/src/runtime/executor.ts +37 -19
- package/src/runtime/http.ts +645 -65
- package/src/runtime/insights.ts +15 -53
- package/src/runtime/instrumentation.ts +530 -67
- package/src/runtime/keyring.ts +7 -19
- package/src/runtime/namespace.ts +2 -7
- 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 +12 -23
- package/src/runtime/perf.ts +1 -1
- package/src/runtime/provider.ts +4 -9
- package/src/runtime/proxy-errors.ts +29 -42
- package/src/runtime/proxy-nodemaven.ts +221 -0
- package/src/runtime/proxy-retry-policy.ts +3 -3
- package/src/runtime/proxy-telemetry.ts +84 -77
- package/src/runtime/redirects.ts +66 -0
- package/src/runtime/redis.ts +10 -13
- package/src/runtime/request-options.ts +679 -9
- package/src/runtime/resolver-config.ts +6 -0
- package/src/runtime/resolver-public.ts +18 -0
- package/src/runtime/resolver-shared.ts +17 -0
- package/src/runtime/resolver-vendors/bindings.ts +56 -0
- package/src/runtime/resolver-vendors/browser.ts +408 -0
- package/src/runtime/resolver-vendors/hosts.ts +38 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +500 -0
- package/src/runtime/resolver-vendors/types.ts +173 -0
- package/src/runtime/resolver.ts +1060 -0
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +399 -161
- package/src/runtime/stealth-cookies.ts +132 -0
- package/src/runtime/stealth.ts +681 -295
- package/src/runtime/stt.ts +39 -113
- package/src/runtime/timeout.ts +18 -0
- package/src/runtime/trace.ts +14 -44
- package/src/runtime/waterfall.ts +5 -18
- package/src/schema.ts +23 -84
- package/src/serve.ts +6 -1
- package/src/server/index.ts +30 -7
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/self-test-redaction.ts +2 -2
- package/src/server/self-test.ts +1030 -180
- package/src/server/serve-implementation.ts +3060 -0
- package/src/server/serve.ts +1 -1781
- package/src/server/types.ts +12 -13
- 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 +27 -33
- package/src/stream-evidence.ts +988 -0
- package/src/stream.ts +16 -20
- package/src/testing/index.ts +11 -2
- package/src/testing/run.ts +668 -74
- package/src/types.ts +665 -35
- package/src/user-input.ts +118 -0
- 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,40 @@
|
|
|
1
|
+
// An IP-bound artifact minted without any recorded egress identity is unsafe to
|
|
2
|
+
// share. The Akamai kinds therefore reject direct caching, while Cloudflare
|
|
3
|
+
// keeps its pre-existing direct-cache behavior pending measurement.
|
|
4
|
+
export const RESOLVER_CHALLENGE_BINDINGS = {
|
|
5
|
+
turnstile: { cacheable: false, identityBinding: "none", directCacheable: false },
|
|
6
|
+
recaptcha_v2: { cacheable: false, identityBinding: "none", directCacheable: false },
|
|
7
|
+
recaptcha_v3: { cacheable: false, identityBinding: "none", directCacheable: false },
|
|
8
|
+
hcaptcha: { cacheable: false, identityBinding: "none", directCacheable: false },
|
|
9
|
+
cloudflare_interstitial: {
|
|
10
|
+
cacheable: true,
|
|
11
|
+
identityBinding: "identity_scoped",
|
|
12
|
+
directCacheable: true,
|
|
13
|
+
},
|
|
14
|
+
aws_waf: { cacheable: true, identityBinding: "portable", directCacheable: true },
|
|
15
|
+
akamai_sec_cpt: {
|
|
16
|
+
cacheable: true,
|
|
17
|
+
identityBinding: "identity_scoped",
|
|
18
|
+
directCacheable: false,
|
|
19
|
+
},
|
|
20
|
+
akamai_sensor: {
|
|
21
|
+
cacheable: true,
|
|
22
|
+
identityBinding: "identity_scoped",
|
|
23
|
+
directCacheable: false,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
export function resolverChallengeIsCacheable(challenge) {
|
|
27
|
+
return RESOLVER_CHALLENGE_BINDINGS[challenge.kind].cacheable;
|
|
28
|
+
}
|
|
29
|
+
export function resolverChallengeAllowsDirectCache(challenge) {
|
|
30
|
+
return RESOLVER_CHALLENGE_BINDINGS[challenge.kind].directCacheable;
|
|
31
|
+
}
|
|
32
|
+
export function resolverChallengeIsIdentityScoped(challenge) {
|
|
33
|
+
return RESOLVER_CHALLENGE_BINDINGS[challenge.kind].identityBinding === "identity_scoped";
|
|
34
|
+
}
|
|
35
|
+
export function resolverChallengeIssuingIdentity(challenge, identity) {
|
|
36
|
+
if (RESOLVER_CHALLENGE_BINDINGS[challenge.kind].identityBinding === "portable") {
|
|
37
|
+
return { userAgent: identity.userAgent };
|
|
38
|
+
}
|
|
39
|
+
return identity;
|
|
40
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { BrowserClient, ChallengeSolution, ProviderChallenge } from "../../types.js";
|
|
2
|
+
import { type BrowserClientOptions } from "../browser.js";
|
|
3
|
+
import type { TraceRecorder } from "../trace.js";
|
|
4
|
+
import { type ResolverIdentity, type ResolverVendorAdapter } from "./types.js";
|
|
5
|
+
type BrowserClientFactory = (options: BrowserClientOptions) => BrowserClient;
|
|
6
|
+
export interface BrowserResolverVendorOptions {
|
|
7
|
+
readonly cdpUrl?: string;
|
|
8
|
+
readonly timeoutMs: number;
|
|
9
|
+
readonly pollIntervalMs?: number;
|
|
10
|
+
readonly allowedHosts: readonly string[];
|
|
11
|
+
readonly createClient?: BrowserClientFactory;
|
|
12
|
+
}
|
|
13
|
+
export interface BrowserResolverVendorAdapter extends ResolverVendorAdapter {
|
|
14
|
+
readonly id: "browser";
|
|
15
|
+
solve(challenge: ProviderChallenge, identity: ResolverIdentity | undefined, signal: AbortSignal, traceRecorder?: TraceRecorder): Promise<Extract<ChallengeSolution, {
|
|
16
|
+
readonly form: "cookies";
|
|
17
|
+
}>>;
|
|
18
|
+
}
|
|
19
|
+
export declare function createBrowserResolverVendorAdapter(options: BrowserResolverVendorOptions): BrowserResolverVendorAdapter;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { isProviderError } from "../../errors.js";
|
|
2
|
+
import { createBrowserClient } from "../browser.js";
|
|
3
|
+
import { resolverChallengeIssuingIdentity } from "./bindings.js";
|
|
4
|
+
import { assertResolverHostAllowed, normalizedResolverHostname } from "./hosts.js";
|
|
5
|
+
import { ResolverVendorUnavailableError, } from "./types.js";
|
|
6
|
+
const BROWSER_VENDOR_ID = "browser";
|
|
7
|
+
const DEFAULT_COOKIE_POLL_INTERVAL_MS = 100;
|
|
8
|
+
const SUCCESS_COOKIE_NAMES = {
|
|
9
|
+
aws_waf: "aws-waf-token",
|
|
10
|
+
cloudflare_interstitial: "cf_clearance",
|
|
11
|
+
};
|
|
12
|
+
class BrowserSolveTimeoutError extends Error {
|
|
13
|
+
constructor() {
|
|
14
|
+
super("Browser resolver solve budget elapsed");
|
|
15
|
+
this.name = "BrowserSolveTimeoutError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
class BrowserCleanupTimeoutError extends Error {
|
|
19
|
+
constructor(timeoutMs) {
|
|
20
|
+
super(`Browser resolver cleanup exceeded ${timeoutMs}ms`);
|
|
21
|
+
this.name = "BrowserCleanupTimeoutError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function abortReason(signal) {
|
|
25
|
+
return signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
26
|
+
}
|
|
27
|
+
function raceWithAbort(operation, signal) {
|
|
28
|
+
if (signal.aborted) {
|
|
29
|
+
return Promise.reject(abortReason(signal));
|
|
30
|
+
}
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
33
|
+
const onAbort = () => {
|
|
34
|
+
cleanup();
|
|
35
|
+
reject(abortReason(signal));
|
|
36
|
+
};
|
|
37
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
38
|
+
operation().then((value) => {
|
|
39
|
+
cleanup();
|
|
40
|
+
resolve(value);
|
|
41
|
+
}, (error) => {
|
|
42
|
+
cleanup();
|
|
43
|
+
reject(error);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async function runBoundedCleanup(cleanup, timeoutMs, challengeKind, operation, traceRecorder) {
|
|
48
|
+
let timeout;
|
|
49
|
+
const boundedCleanup = async () => {
|
|
50
|
+
try {
|
|
51
|
+
await Promise.race([
|
|
52
|
+
cleanup(),
|
|
53
|
+
new Promise((_resolve, reject) => {
|
|
54
|
+
timeout = setTimeout(() => reject(new BrowserCleanupTimeoutError(timeoutMs)), timeoutMs);
|
|
55
|
+
}),
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
if (timeout !== undefined)
|
|
60
|
+
clearTimeout(timeout);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
if (!traceRecorder) {
|
|
64
|
+
await boundedCleanup().catch(() => undefined);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
await traceRecorder
|
|
68
|
+
.runSpan("resolver.vendor.cleanup", boundedCleanup, {
|
|
69
|
+
attributes: {
|
|
70
|
+
vendor: BROWSER_VENDOR_ID,
|
|
71
|
+
challenge_kind: challengeKind,
|
|
72
|
+
operation,
|
|
73
|
+
},
|
|
74
|
+
onError(error) {
|
|
75
|
+
return {
|
|
76
|
+
error_message: error instanceof Error ? error.message : String(error),
|
|
77
|
+
...(error instanceof Error && error.stack ? { error_stack: error.stack } : {}),
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
.catch(() => undefined);
|
|
82
|
+
}
|
|
83
|
+
async function abortableDelay(ms, signal) {
|
|
84
|
+
let timer;
|
|
85
|
+
try {
|
|
86
|
+
await raceWithAbort(() => new Promise((resolve) => {
|
|
87
|
+
timer = setTimeout(resolve, ms);
|
|
88
|
+
}), signal);
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
if (timer !== undefined)
|
|
92
|
+
clearTimeout(timer);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function isSupportedKind(kind) {
|
|
96
|
+
return Object.hasOwn(SUCCESS_COOKIE_NAMES, kind);
|
|
97
|
+
}
|
|
98
|
+
function cookieDomainSpecificity(cookie) {
|
|
99
|
+
return normalizedResolverHostname(cookie.domain.replace(/^\./, "")).length;
|
|
100
|
+
}
|
|
101
|
+
function isHostOnlyCookieFor(cookie, hostname) {
|
|
102
|
+
return (!cookie.domain.startsWith(".") &&
|
|
103
|
+
normalizedResolverHostname(cookie.domain) === normalizedResolverHostname(hostname));
|
|
104
|
+
}
|
|
105
|
+
function cookieAppliesToUrl(cookie, url) {
|
|
106
|
+
const cookieDomain = normalizedResolverHostname(cookie.domain.replace(/^\./, ""));
|
|
107
|
+
const requestHostname = normalizedResolverHostname(url.hostname);
|
|
108
|
+
const domainMatches = cookieDomain.length > 0 &&
|
|
109
|
+
(requestHostname === cookieDomain ||
|
|
110
|
+
(cookie.domain.startsWith(".") && requestHostname.endsWith(`.${cookieDomain}`)));
|
|
111
|
+
if (!domainMatches || (cookie.secure && url.protocol !== "https:"))
|
|
112
|
+
return false;
|
|
113
|
+
const requestPath = url.pathname || "/";
|
|
114
|
+
const cookiePath = cookie.path;
|
|
115
|
+
return (cookiePath.startsWith("/") &&
|
|
116
|
+
(requestPath === cookiePath ||
|
|
117
|
+
(requestPath.startsWith(cookiePath) &&
|
|
118
|
+
(cookiePath.endsWith("/") || requestPath[cookiePath.length] === "/"))));
|
|
119
|
+
}
|
|
120
|
+
function selectSuccessCookie(cookies, successCookieName, pageUrl) {
|
|
121
|
+
const url = new URL(pageUrl);
|
|
122
|
+
return cookies
|
|
123
|
+
.filter((cookie) => cookie.name === successCookieName && cookieAppliesToUrl(cookie, url))
|
|
124
|
+
.sort((left, right) => Number(isHostOnlyCookieFor(right, url.hostname)) -
|
|
125
|
+
Number(isHostOnlyCookieFor(left, url.hostname)) ||
|
|
126
|
+
cookieDomainSpecificity(right) - cookieDomainSpecificity(left) ||
|
|
127
|
+
right.path.length - left.path.length)[0];
|
|
128
|
+
}
|
|
129
|
+
async function solveInPage(page, pageUrl, successCookieName, pollIntervalMs, signal) {
|
|
130
|
+
await raceWithAbort(() => page.goto(pageUrl), signal);
|
|
131
|
+
while (true) {
|
|
132
|
+
const cookies = await raceWithAbort(() => page.cookies(), signal);
|
|
133
|
+
const successCookie = selectSuccessCookie(cookies, successCookieName, pageUrl);
|
|
134
|
+
if (successCookie) {
|
|
135
|
+
const userAgent = await raceWithAbort(() => page.evaluate("navigator.userAgent"), signal);
|
|
136
|
+
return {
|
|
137
|
+
form: "cookies",
|
|
138
|
+
cookies: { [successCookieName]: successCookie.value },
|
|
139
|
+
userAgent,
|
|
140
|
+
...(successCookie.expires === undefined ? {} : { expires: successCookie.expires }),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
await abortableDelay(pollIntervalMs, signal);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const POOL_ALLOCATION_EXHAUSTED_CODES = new Set([
|
|
147
|
+
-32_001, // queue full
|
|
148
|
+
-32_002, // acquire timed out
|
|
149
|
+
-32_003, // shutting down
|
|
150
|
+
]);
|
|
151
|
+
function poolErrorCode(error) {
|
|
152
|
+
const code = error.code;
|
|
153
|
+
return typeof code === "number" ? code : undefined;
|
|
154
|
+
}
|
|
155
|
+
function knownUnavailableReason(error) {
|
|
156
|
+
// Source-grounded mappings:
|
|
157
|
+
// - apps/cdp-pool/src/index.ts: the JSON-RPC codes and messages below.
|
|
158
|
+
// - src/runtime/browser.ts: BROWSER_CDP_POOL_REQUIRED and the two WebSocket messages.
|
|
159
|
+
// The pool's numeric JSON-RPC code is authoritative and is preferred whenever present.
|
|
160
|
+
// The message substrings remain as a fallback for pool builds predating code
|
|
161
|
+
// propagation; they are exact strings verified against the pool source. -32004 (unknown
|
|
162
|
+
// lease) and -32006 (missing allowedHosts) are deliberately unmapped: both are caller
|
|
163
|
+
// bugs, and the next vendor would fail identically, so they propagate unchanged.
|
|
164
|
+
if (isProviderError(error)) {
|
|
165
|
+
return error.code === "BROWSER_CDP_POOL_REQUIRED" ? "missing_credentials" : undefined;
|
|
166
|
+
}
|
|
167
|
+
if (!(error instanceof Error))
|
|
168
|
+
return undefined;
|
|
169
|
+
const code = poolErrorCode(error);
|
|
170
|
+
if (code !== undefined) {
|
|
171
|
+
return POOL_ALLOCATION_EXHAUSTED_CODES.has(code) ? "allocation_exhausted" : undefined;
|
|
172
|
+
}
|
|
173
|
+
if (error.message.includes("CDP pool acquire queue is full") ||
|
|
174
|
+
error.message.includes("CDP pool acquire timed out") ||
|
|
175
|
+
error.message.includes("CDP pool is shutting down")) {
|
|
176
|
+
return "allocation_exhausted";
|
|
177
|
+
}
|
|
178
|
+
if (error.message.includes("Unable to connect to WebSocket endpoint") ||
|
|
179
|
+
error.message.includes("WebSocket closed")) {
|
|
180
|
+
return "transport_failure";
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
async function closeBrowserClient(client, timeoutMs, challengeKind, traceRecorder) {
|
|
185
|
+
const close = client?.close;
|
|
186
|
+
if (!close)
|
|
187
|
+
return;
|
|
188
|
+
await runBoundedCleanup(() => close.call(client), timeoutMs, challengeKind, "client.close", traceRecorder);
|
|
189
|
+
}
|
|
190
|
+
export function createBrowserResolverVendorAdapter(options) {
|
|
191
|
+
const createClient = options.createClient ?? createBrowserClient;
|
|
192
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_COOKIE_POLL_INTERVAL_MS;
|
|
193
|
+
return {
|
|
194
|
+
id: BROWSER_VENDOR_ID,
|
|
195
|
+
supports(kind) {
|
|
196
|
+
return isSupportedKind(kind);
|
|
197
|
+
},
|
|
198
|
+
getIssuingIdentity(solution, requestedIdentity, challenge) {
|
|
199
|
+
if (solution.form !== "cookies" || !isSupportedKind(challenge.kind))
|
|
200
|
+
return undefined;
|
|
201
|
+
return resolverChallengeIssuingIdentity(challenge, {
|
|
202
|
+
...(requestedIdentity ? { proxyUrl: requestedIdentity.proxyUrl } : {}),
|
|
203
|
+
userAgent: solution.userAgent,
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
async solve(challenge, identity, callerSignal, traceRecorder) {
|
|
207
|
+
const cdpUrl = options.cdpUrl?.trim();
|
|
208
|
+
const proxyUrl = identity?.proxyUrl;
|
|
209
|
+
if (!cdpUrl && proxyUrl === undefined) {
|
|
210
|
+
throw new ResolverVendorUnavailableError(BROWSER_VENDOR_ID, "missing_credentials");
|
|
211
|
+
}
|
|
212
|
+
if (!isSupportedKind(challenge.kind)) {
|
|
213
|
+
throw new TypeError(`Browser resolver does not support ${challenge.kind}`);
|
|
214
|
+
}
|
|
215
|
+
assertResolverHostAllowed(challenge.pageUrl, options.allowedHosts);
|
|
216
|
+
const challengeKind = challenge.kind;
|
|
217
|
+
callerSignal.throwIfAborted();
|
|
218
|
+
if (proxyUrl !== undefined && proxyUrl.length === 0) {
|
|
219
|
+
throw new ResolverVendorUnavailableError(BROWSER_VENDOR_ID, "missing_proxy_identity");
|
|
220
|
+
}
|
|
221
|
+
if (cdpUrl && proxyUrl !== undefined) {
|
|
222
|
+
// The current pool acquire protocol cannot bind a proxy to its browser context.
|
|
223
|
+
// Refuse so the chain can choose a vendor that can honor the resolved identity.
|
|
224
|
+
throw new ResolverVendorUnavailableError(BROWSER_VENDOR_ID, "not_implemented");
|
|
225
|
+
}
|
|
226
|
+
const solveController = new AbortController();
|
|
227
|
+
const onCallerAbort = () => solveController.abort(abortReason(callerSignal));
|
|
228
|
+
callerSignal.addEventListener("abort", onCallerAbort, { once: true });
|
|
229
|
+
const timeout = setTimeout(() => solveController.abort(new BrowserSolveTimeoutError()), options.timeoutMs);
|
|
230
|
+
let client;
|
|
231
|
+
let handlerEntered = false;
|
|
232
|
+
try {
|
|
233
|
+
client = createClient({
|
|
234
|
+
allowedHosts: [...options.allowedHosts],
|
|
235
|
+
cdpUrl: cdpUrl ?? "",
|
|
236
|
+
...(proxyUrl === undefined ? {} : { proxy: proxyUrl }),
|
|
237
|
+
requireCdpPool: cdpUrl !== undefined,
|
|
238
|
+
});
|
|
239
|
+
const contextOperation = client.withIsolatedContext(async (page) => {
|
|
240
|
+
handlerEntered = true;
|
|
241
|
+
return await solveInPage(page, challenge.pageUrl, SUCCESS_COOKIE_NAMES[challengeKind], pollIntervalMs, solveController.signal);
|
|
242
|
+
});
|
|
243
|
+
try {
|
|
244
|
+
return await raceWithAbort(() => contextOperation, solveController.signal);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
if (solveController.signal.aborted) {
|
|
248
|
+
if (handlerEntered) {
|
|
249
|
+
await runBoundedCleanup(() => contextOperation, options.timeoutMs, challengeKind, "context.close", traceRecorder);
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
await closeBrowserClient(client, options.timeoutMs, challengeKind, traceRecorder);
|
|
253
|
+
void contextOperation.catch(() => undefined);
|
|
254
|
+
}
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
if (callerSignal.aborted)
|
|
260
|
+
throw abortReason(callerSignal);
|
|
261
|
+
if (error instanceof BrowserSolveTimeoutError) {
|
|
262
|
+
throw new ResolverVendorUnavailableError(BROWSER_VENDOR_ID, "timeout", {
|
|
263
|
+
cause: error,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
if (error instanceof ResolverVendorUnavailableError) {
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
const reason = knownUnavailableReason(error);
|
|
270
|
+
if (reason) {
|
|
271
|
+
throw new ResolverVendorUnavailableError(BROWSER_VENDOR_ID, reason, { cause: error });
|
|
272
|
+
}
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
275
|
+
finally {
|
|
276
|
+
clearTimeout(timeout);
|
|
277
|
+
callerSignal.removeEventListener("abort", onCallerAbort);
|
|
278
|
+
await closeBrowserClient(client, options.timeoutMs, challengeKind, traceRecorder);
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ProviderError } from "../../errors.js";
|
|
2
|
+
export function normalizedResolverHostname(hostname) {
|
|
3
|
+
return hostname.trim().toLowerCase().replace(/\.$/, "");
|
|
4
|
+
}
|
|
5
|
+
export function assertResolverHostAllowed(targetUrl, allowedHosts) {
|
|
6
|
+
let targetUrlObject;
|
|
7
|
+
try {
|
|
8
|
+
targetUrlObject = new URL(targetUrl);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
throw new ProviderError("Resolver target URL is invalid", {
|
|
12
|
+
code: "RESOLVER_HOST_NOT_ALLOWED",
|
|
13
|
+
fix: "Use a valid URL whose exact hostname appears in the provider's allowedHosts declaration.",
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
if (targetUrlObject.protocol !== "http:" && targetUrlObject.protocol !== "https:") {
|
|
17
|
+
throw new ProviderError(`Resolver target URL scheme "${targetUrlObject.protocol}" is not allowed`, {
|
|
18
|
+
code: "RESOLVER_HOST_NOT_ALLOWED",
|
|
19
|
+
fix: "Use an http or https URL whose exact hostname appears in the provider's allowedHosts declaration.",
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const targetHost = normalizedResolverHostname(targetUrlObject.hostname);
|
|
23
|
+
const isAllowed = allowedHosts.some((host) => {
|
|
24
|
+
const declaredHost = normalizedResolverHostname(host);
|
|
25
|
+
return declaredHost.length > 0 && !declaredHost.includes("*") && declaredHost === targetHost;
|
|
26
|
+
});
|
|
27
|
+
if (isAllowed)
|
|
28
|
+
return;
|
|
29
|
+
throw new ProviderError(`Resolver target host "${targetHost}" is not declared`, {
|
|
30
|
+
code: "RESOLVER_HOST_NOT_ALLOWED",
|
|
31
|
+
fix: "Add the exact target hostname to the provider's allowedHosts declaration.",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ChallengeSolution, ProviderChallenge } from "../../types.js";
|
|
2
|
+
import type { TraceRecorder } from "../trace.js";
|
|
3
|
+
import { type ResolverIdentity, type ResolverVendorAdapter } from "./types.js";
|
|
4
|
+
type Delay = (ms: number, signal: AbortSignal) => Promise<void>;
|
|
5
|
+
export interface TwoCaptchaResolverVendorOptions {
|
|
6
|
+
readonly apiKey?: string;
|
|
7
|
+
readonly timeoutMs?: number;
|
|
8
|
+
readonly pollIntervalMs?: number;
|
|
9
|
+
readonly allowedHosts: readonly string[];
|
|
10
|
+
readonly fetchImpl?: typeof fetch;
|
|
11
|
+
readonly baseUrl?: string;
|
|
12
|
+
/** Test-only clock override; supplying it disables the real-time deadline timer. */
|
|
13
|
+
readonly now?: () => number;
|
|
14
|
+
/** Test-only delay override used with `now` to exercise polling without sleeping. */
|
|
15
|
+
readonly delay?: Delay;
|
|
16
|
+
}
|
|
17
|
+
export interface TwoCaptchaResolverVendorAdapter extends ResolverVendorAdapter {
|
|
18
|
+
readonly id: "2captcha";
|
|
19
|
+
solve(challenge: ProviderChallenge, identity: ResolverIdentity | undefined, signal: AbortSignal, traceRecorder?: TraceRecorder): Promise<Extract<ChallengeSolution, {
|
|
20
|
+
readonly form: "token";
|
|
21
|
+
}>>;
|
|
22
|
+
}
|
|
23
|
+
export declare function createTwoCaptchaResolverVendorAdapter(options: TwoCaptchaResolverVendorOptions): TwoCaptchaResolverVendorAdapter;
|
|
24
|
+
export {};
|