@apifuse/provider-sdk 2.2.0-beta.23 → 2.2.0-beta.24

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/bin/apifuse-dev.ts +2 -0
  3. package/bin/apifuse-pack-types.ts +234 -38
  4. package/bin/apifuse-perf.ts +15 -12
  5. package/bin/apifuse-record.ts +2 -0
  6. package/dist/config/loader.d.ts +8 -19
  7. package/dist/config/loader.js +28 -86
  8. package/dist/define.d.ts +4 -1
  9. package/dist/define.js +64 -6
  10. package/dist/index.d.ts +3 -2
  11. package/dist/index.js +1 -0
  12. package/dist/provider.d.ts +1 -1
  13. package/dist/runtime/auth-flow.js +2 -0
  14. package/dist/runtime/browser.js +50 -0
  15. package/dist/runtime/http.js +0 -1
  16. package/dist/runtime/instrumentation.js +26 -1
  17. package/dist/runtime/resolver-vendors/bindings.d.ts +8 -0
  18. package/dist/runtime/resolver-vendors/bindings.js +15 -0
  19. package/dist/runtime/resolver-vendors/browser.d.ts +24 -0
  20. package/dist/runtime/resolver-vendors/browser.js +287 -0
  21. package/dist/runtime/resolver-vendors/types.d.ts +42 -0
  22. package/dist/runtime/resolver-vendors/types.js +57 -0
  23. package/dist/runtime/resolver.d.ts +39 -0
  24. package/dist/runtime/resolver.js +414 -0
  25. package/dist/runtime/state.d.ts +3 -0
  26. package/dist/runtime/state.js +245 -141
  27. package/dist/runtime/stealth.js +3 -6
  28. package/dist/server/serve.d.ts +4 -1
  29. package/dist/server/serve.js +35 -8
  30. package/dist/testing/run.js +7 -0
  31. package/dist/types.d.ts +112 -4
  32. package/package.json +1 -1
  33. package/src/config/loader.ts +35 -111
  34. package/src/define.ts +105 -8
  35. package/src/index.ts +20 -1
  36. package/src/provider.ts +1 -0
  37. package/src/runtime/auth-flow.ts +2 -0
  38. package/src/runtime/browser.ts +69 -0
  39. package/src/runtime/http.ts +0 -1
  40. package/src/runtime/instrumentation.ts +36 -2
  41. package/src/runtime/resolver-vendors/bindings.ts +31 -0
  42. package/src/runtime/resolver-vendors/browser.ts +420 -0
  43. package/src/runtime/resolver-vendors/types.ts +113 -0
  44. package/src/runtime/resolver.ts +668 -0
  45. package/src/runtime/state.ts +323 -166
  46. package/src/runtime/stealth.ts +3 -6
  47. package/src/server/serve.ts +73 -5
  48. package/src/testing/run.ts +8 -0
  49. package/src/types.ts +130 -4
package/src/define.ts CHANGED
@@ -20,21 +20,24 @@ import type {
20
20
  HealthJourneySchedule,
21
21
  HealthScheduleRandomization,
22
22
  InferSchemaOutput,
23
+ NativeProviderConfig,
23
24
  OperationDefinition,
24
25
  OperationHandlerResult,
25
26
  OperationHttpStreamTransport,
26
27
  OperationSseTransport,
27
28
  OperationTransport,
28
29
  OperationWebSocketTransport,
29
- NativeProviderConfig,
30
- ProviderOcrConfig,
31
30
  ProviderAccessConfig,
31
+ ProviderChallengeKind,
32
32
  ProviderDefinition,
33
+ ProviderOcrConfig,
33
34
  ProviderDeploymentOverrides,
34
35
  ProviderHealthMonitorConfig,
35
36
  ProviderProxyConfig,
36
37
  ProviderProxyProvider,
37
38
  ProviderPublicProfile,
39
+ ProviderResolverConfig,
40
+ ProviderResolverVendor,
38
41
  ProviderReviewed,
39
42
  ProviderSecretDeclaration,
40
43
  ProviderStreamEvent,
@@ -126,6 +129,30 @@ const VALID_PROVIDER_PROXY_AFFINITIES = [
126
129
  ] as const;
127
130
  const VALID_PROVIDER_OCR_MODES = ["optional", "required"] as const;
128
131
  const VALID_PROVIDER_STT_MODES = ["optional", "required"] as const;
132
+ function exhaustiveLiteralArray<TUnion extends string>() {
133
+ return <const TValues extends readonly TUnion[]>(
134
+ values: TValues,
135
+ ..._missing: Exclude<TUnion, TValues[number]> extends never
136
+ ? []
137
+ : ["Missing runtime values", Exclude<TUnion, TValues[number]>]
138
+ ): TValues => values;
139
+ }
140
+
141
+ export const VALID_PROVIDER_RESOLVER_VENDORS = exhaustiveLiteralArray<ProviderResolverVendor>()([
142
+ "browser",
143
+ "capsolver",
144
+ "capmonster",
145
+ "2captcha",
146
+ "custom",
147
+ ] as const);
148
+ export const VALID_PROVIDER_CHALLENGE_KINDS = exhaustiveLiteralArray<ProviderChallengeKind>()([
149
+ "turnstile",
150
+ "recaptcha_v2",
151
+ "recaptcha_v3",
152
+ "hcaptcha",
153
+ "cloudflare_interstitial",
154
+ "aws_waf",
155
+ ] as const);
129
156
  const SMARTPROXY_APP_KEY_SECRET = "APIFUSE__PROXY__SMARTPROXY_APP_KEY";
130
157
  const NODEMAVEN_USERNAME_SECRET = "APIFUSE__PROXY__NODEMAVEN_USERNAME";
131
158
  const NODEMAVEN_PASSWORD_SECRET = "APIFUSE__PROXY__NODEMAVEN_PASSWORD";
@@ -134,9 +161,8 @@ const NODEMAVEN_PASSWORD_SECRET = "APIFUSE__PROXY__NODEMAVEN_PASSWORD";
134
161
  // credential fails at build/validation time rather than during a live outage: a
135
162
  // declared-but-uncredentialed fallback leg is a silently dead SPOF, which is
136
163
  // exactly the failure class the multi-vendor chain exists to remove. Vendors
137
- // absent from this map (e.g. `custom`/`decodo`, whose credentials come from the
138
- // `APIFUSE__PROXY__URL` bring-your-own escape hatch, not provider secrets) impose
139
- // no declaration requirement.
164
+ // absent from this map (the deprecated `custom`/`decodo` values have no managed
165
+ // adapter) impose no declaration requirement.
140
166
  const VENDOR_REQUIRED_SECRETS: Partial<Record<ProviderProxyProvider, readonly string[]>> = {
141
167
  smartproxy: [SMARTPROXY_APP_KEY_SECRET],
142
168
  nodemaven: [NODEMAVEN_USERNAME_SECRET, NODEMAVEN_PASSWORD_SECRET],
@@ -252,6 +278,7 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
252
278
  proxy?: ProviderProxyConfig;
253
279
  ocr?: ProviderOcrConfig;
254
280
  stt?: ProviderSttConfig;
281
+ resolver?: ProviderResolverConfig;
255
282
  browser?: { engine: BrowserEngine };
256
283
  auth?: AuthConfig;
257
284
  reviewed?: ProviderReviewed;
@@ -681,9 +708,21 @@ function validateProviderProxy(config: {
681
708
  const deprecatedVendors = vendorChain.filter(
682
709
  (vendor) => vendor === "decodo" || vendor === "custom",
683
710
  );
711
+ if (
712
+ proxy.mode === "required" &&
713
+ vendorChain.length > 0 &&
714
+ deprecatedVendors.length === vendorChain.length
715
+ ) {
716
+ throw new ValidationError(
717
+ `Provider "${config.id}" requires proxy egress but declares only deprecated proxy vendor(s): ${deprecatedVendors.join(", ")}.`,
718
+ {
719
+ fix: `Use proxy.provider or proxy.providers with "smartproxy" and/or "nodemaven".`,
720
+ },
721
+ );
722
+ }
684
723
  if (deprecatedVendors.length > 0) {
685
724
  console.warn(
686
- `[provider-sdk] Provider "${config.id}" uses deprecated proxy vendor(s): ${deprecatedVendors.join(", ")}. Use "smartproxy"/"nodemaven", or the APIFUSE__PROXY__URL bring-your-own escape hatch.`,
725
+ `[provider-sdk] Provider "${config.id}" uses deprecated proxy vendor(s): ${deprecatedVendors.join(", ")}. Use "smartproxy"/"nodemaven".`,
687
726
  );
688
727
  }
689
728
  }
@@ -712,6 +751,53 @@ function validateProviderOcr(config: { id: string; ocr?: ProviderOcrConfig }): v
712
751
  assertLiteralField(ocr.mode, "ocr.mode", VALID_PROVIDER_OCR_MODES, config.id);
713
752
  }
714
753
 
754
+ function validateProviderResolver(config: { id: string; resolver?: ProviderResolverConfig }): void {
755
+ const resolver = config.resolver;
756
+ if (resolver === undefined) return;
757
+ if (!resolver || typeof resolver !== "object" || Array.isArray(resolver)) {
758
+ throw new ValidationError(`Provider "${config.id}" has invalid resolver: must be an object.`, {
759
+ fix: `Set resolver for provider "${config.id}" to { vendors: ["2captcha"], kinds: ["turnstile"] }.`,
760
+ });
761
+ }
762
+ rejectUnknownFields(resolver, new Set(["vendors", "kinds"]), "resolver", config.id);
763
+ validateResolverLiteralArray(
764
+ resolver.vendors,
765
+ "resolver.vendors",
766
+ VALID_PROVIDER_RESOLVER_VENDORS,
767
+ config.id,
768
+ );
769
+ validateResolverLiteralArray(
770
+ resolver.kinds,
771
+ "resolver.kinds",
772
+ VALID_PROVIDER_CHALLENGE_KINDS,
773
+ config.id,
774
+ );
775
+ }
776
+
777
+ function validateResolverLiteralArray<TValue extends string>(
778
+ value: readonly TValue[],
779
+ field: string,
780
+ validValues: readonly TValue[],
781
+ providerId: string,
782
+ ): void {
783
+ if (!Array.isArray(value)) {
784
+ throw new ValidationError(`Provider "${providerId}" has invalid ${field}: must be an array.`, {
785
+ fix: `Set ${field} for provider "${providerId}" to an array containing only: ${validValues.join(", ")}.`,
786
+ });
787
+ }
788
+ for (const [index, item] of value.entries()) {
789
+ if (typeof item === "string" && validValues.some((validValue) => validValue === item)) {
790
+ continue;
791
+ }
792
+ throw new ValidationError(
793
+ `Provider "${providerId}" has invalid ${field}[${index}]: ${JSON.stringify(item)}. Expected one of: ${validValues.join(", ")}`,
794
+ {
795
+ fix: `Set ${field}[${index}] for provider "${providerId}" to one of ${validValues.map((validValue) => `"${validValue}"`).join(", ")}.`,
796
+ },
797
+ );
798
+ }
799
+ }
800
+
715
801
  function validateOperationIds(
716
802
  providerId: string,
717
803
  operations: Record<string, ProviderOperation>,
@@ -1309,7 +1395,12 @@ function suggestField(unknown: string, candidates: ReadonlySet<string>): string
1309
1395
  return best;
1310
1396
  }
1311
1397
 
1312
- function rejectUnknownFields(value: object, allowed: ReadonlySet<string>, fieldPath: string): void {
1398
+ function rejectUnknownFields(
1399
+ value: object,
1400
+ allowed: ReadonlySet<string>,
1401
+ fieldPath: string,
1402
+ providerId?: string,
1403
+ ): void {
1313
1404
  for (const key of Object.keys(value)) {
1314
1405
  if (allowed.has(key)) continue;
1315
1406
  const hint = suggestField(key, allowed);
@@ -1317,7 +1408,11 @@ function rejectUnknownFields(value: object, allowed: ReadonlySet<string>, fieldP
1317
1408
  hint
1318
1409
  ? `Unknown field "${key}" on ${fieldPath}. Did you mean "${hint}"?`
1319
1410
  : `Unknown field "${key}" on ${fieldPath}.`,
1320
- { fix: `Remove ${fieldPath}.${key} or rename it.` },
1411
+ {
1412
+ fix: providerId
1413
+ ? `Remove ${fieldPath}.${key} from provider "${providerId}" or rename it.`
1414
+ : `Remove ${fieldPath}.${key} or rename it.`,
1415
+ },
1321
1416
  );
1322
1417
  }
1323
1418
  }
@@ -2403,6 +2498,7 @@ export function defineProvider<
2403
2498
  validateProviderProxy(config);
2404
2499
  validateProviderOcr(config);
2405
2500
  validateProviderStt(config);
2501
+ validateProviderResolver(config);
2406
2502
  if (config.runtime === "browser" && !config.browser)
2407
2503
  throw new ProviderError(
2408
2504
  `Provider "${config.id}" must define browser.engine when runtime is "browser"`,
@@ -2428,6 +2524,7 @@ export function defineProvider<
2428
2524
  proxy: config.proxy,
2429
2525
  ocr: config.ocr,
2430
2526
  stt: config.stt,
2527
+ resolver: config.resolver,
2431
2528
  browser: config.browser,
2432
2529
  auth: config.auth,
2433
2530
  reviewed: config.reviewed,
package/src/index.ts CHANGED
@@ -6,7 +6,6 @@ export * from "./choice-token.js";
6
6
  export type {
7
7
  ApiFuseConfig,
8
8
  BrowserConfig,
9
- ProxyConfig,
10
9
  ProxyProtocol,
11
10
  ProxyResolutionOptions,
12
11
  ProxyResolutionSource,
@@ -101,6 +100,18 @@ export {
101
100
  } from "./runtime/instrumentation.js";
102
101
  export { type PrevalidateResult, prevalidate } from "./runtime/prevalidate.js";
103
102
  export { getProviderBaseUrl } from "./runtime/provider.js";
103
+ export {
104
+ APIFUSE__CDP_POOL__URL,
105
+ APIFUSE__RESOLVER__2CAPTCHA__API_KEY,
106
+ APIFUSE__RESOLVER__CAPMONSTER__API_KEY,
107
+ APIFUSE__RESOLVER__CAPSOLVER__API_KEY,
108
+ APIFUSE__RESOLVER__TIMEOUT_MS,
109
+ createResolverClientFromEnv,
110
+ createUnsupportedResolverClient,
111
+ DEFAULT_RESOLVER_TIMEOUT_MS,
112
+ invalidateResolverSolution,
113
+ type ResolverRuntimeOptions,
114
+ } from "./runtime/resolver.js";
104
115
  export {
105
116
  assertRequiredSecretsPresent,
106
117
  listMissingRequiredSecrets,
@@ -177,6 +188,7 @@ export type {
177
188
  AuthMode,
178
189
  AuthTurn,
179
190
  Bcp47Locale,
191
+ BrowserCookie,
180
192
  BrowserEngine,
181
193
  BrowserOptions,
182
194
  BrowserResourceBody,
@@ -185,6 +197,7 @@ export type {
185
197
  BrowserResourcePolicy,
186
198
  BrowserResourceRequest,
187
199
  BrowserResourceRoute,
200
+ ChallengeSolution,
188
201
  ConnectionMode,
189
202
  ContextDeclaration,
190
203
  CookieJar,
@@ -285,6 +298,8 @@ export type {
285
298
  ProviderChoiceContext,
286
299
  ProviderChoiceIssueOptions,
287
300
  ProviderChoiceParseOptions,
301
+ ProviderChallenge,
302
+ ProviderChallengeKind,
288
303
  ProviderContext,
289
304
  ProviderDefinition,
290
305
  ProviderDeploymentOverrides,
@@ -309,6 +324,8 @@ export type {
309
324
  ProviderPublicProfile,
310
325
  ProviderReviewed,
311
326
  ProviderResolvedFile,
327
+ ProviderResolverConfig,
328
+ ProviderResolverVendor,
312
329
  ProviderRuntimeState,
313
330
  ProviderSecretDeclaration,
314
331
  ProviderStateDurationString,
@@ -319,6 +336,7 @@ export type {
319
336
  ProviderSupportLevel,
320
337
  RequestOptions,
321
338
  RedirectRunReason,
339
+ ResolverContext,
322
340
  Rfc3339Instant,
323
341
  SchemaLike,
324
342
  SmsOrigin,
@@ -328,6 +346,7 @@ export type {
328
346
  StandardSchemaV1,
329
347
  StateCasResult,
330
348
  StateNamespaceOptions,
349
+ StateNamespaceScope,
331
350
  StateValue,
332
351
  StateWriteOptions,
333
352
  StealthClient,
package/src/provider.ts CHANGED
@@ -162,6 +162,7 @@ export type {
162
162
  StandardSchemaV1,
163
163
  StateCasResult,
164
164
  StateNamespaceOptions,
165
+ StateNamespaceScope,
165
166
  StateValue,
166
167
  StateWriteOptions,
167
168
  } from "./types.js";
@@ -10,6 +10,7 @@ import type {
10
10
  SttContext,
11
11
  } from "../types.js";
12
12
  import { createUnsupportedOcrClient } from "./ocr.js";
13
+ import { createUnsupportedResolverClient } from "./resolver.js";
13
14
  import { createUnsupportedSttClient } from "./stt.js";
14
15
 
15
16
  function normalizeAllowedKeys(allowedKeys: string[]): Set<string> {
@@ -75,6 +76,7 @@ export function createFlowContext(options: {
75
76
  context: createScratchpad(options.allowedKeys, options.initialContext),
76
77
  ocr: options.ocr ?? createUnsupportedOcrClient(),
77
78
  stt: options.stt ?? createUnsupportedSttClient(),
79
+ resolver: createUnsupportedResolverClient("Resolver is not available in auth flow context"),
78
80
  auth: createAuthFlowHelpers(),
79
81
  };
80
82
  }
@@ -6,6 +6,7 @@ import type {
6
6
  BrowserChallengeRequest,
7
7
  BrowserChallengeResult,
8
8
  BrowserClient as BrowserClientContract,
9
+ BrowserCookie,
9
10
  BrowserEngine,
10
11
  BrowserFrame,
11
12
  BrowserLocator,
@@ -545,6 +546,10 @@ class PlaywrightBrowserPage implements BrowserPageContract {
545
546
  await this.page.close();
546
547
  }
547
548
 
549
+ async cookies(): Promise<readonly BrowserCookie[]> {
550
+ return (await this.page.context().cookies()).map(toBrowserCookie);
551
+ }
552
+
548
553
  async withResourcePolicy<T>(policy: BrowserResourcePolicy, run: () => Promise<T>): Promise<T> {
549
554
  const allowedMethods = new Set(policy.allowedMethods ?? DEFAULT_RESOURCE_METHODS);
550
555
  const handler = async (route: Route): Promise<void> => {
@@ -845,6 +850,64 @@ function isRecord(value: unknown): value is Record<string, unknown> {
845
850
  return value !== null && typeof value === "object" && !Array.isArray(value);
846
851
  }
847
852
 
853
+ function isBrowserCookieSameSite(value: unknown): value is BrowserCookie["sameSite"] {
854
+ return value === "Strict" || value === "Lax" || value === "None";
855
+ }
856
+
857
+ function toBrowserCookie(cookie: {
858
+ readonly name: string;
859
+ readonly value: string;
860
+ readonly domain: string;
861
+ readonly path: string;
862
+ readonly expires?: number;
863
+ readonly httpOnly: boolean;
864
+ readonly secure: boolean;
865
+ readonly sameSite?: unknown;
866
+ }): BrowserCookie {
867
+ return {
868
+ name: cookie.name,
869
+ value: cookie.value,
870
+ domain: cookie.domain,
871
+ path: cookie.path,
872
+ ...(cookie.expires !== undefined && cookie.expires > 0 ? { expires: cookie.expires } : {}),
873
+ httpOnly: cookie.httpOnly,
874
+ secure: cookie.secure,
875
+ ...(isBrowserCookieSameSite(cookie.sameSite) ? { sameSite: cookie.sameSite } : {}),
876
+ };
877
+ }
878
+
879
+ function parseCdpCookies(value: unknown): readonly BrowserCookie[] {
880
+ if (!Array.isArray(value)) {
881
+ throw new Error("CDP Network.getCookies returned an invalid cookie list");
882
+ }
883
+
884
+ return value.map((cookie) => {
885
+ if (
886
+ !isRecord(cookie) ||
887
+ typeof cookie.name !== "string" ||
888
+ typeof cookie.value !== "string" ||
889
+ typeof cookie.domain !== "string" ||
890
+ typeof cookie.path !== "string" ||
891
+ typeof cookie.expires !== "number" ||
892
+ typeof cookie.httpOnly !== "boolean" ||
893
+ typeof cookie.secure !== "boolean"
894
+ ) {
895
+ throw new Error("CDP Network.getCookies returned an invalid cookie");
896
+ }
897
+
898
+ return toBrowserCookie({
899
+ name: cookie.name,
900
+ value: cookie.value,
901
+ domain: cookie.domain,
902
+ path: cookie.path,
903
+ expires: cookie.expires,
904
+ httpOnly: cookie.httpOnly,
905
+ secure: cookie.secure,
906
+ sameSite: cookie.sameSite,
907
+ });
908
+ });
909
+ }
910
+
848
911
  function parsePoolAcquireResponse(value: unknown): PoolAcquireResponse {
849
912
  if (
850
913
  !isRecord(value) ||
@@ -1200,6 +1263,12 @@ class CdpPoolBrowserPage implements BrowserPageContract {
1200
1263
  return Buffer.from(String(result.data ?? ""), "base64");
1201
1264
  }
1202
1265
 
1266
+ async cookies(): Promise<readonly BrowserCookie[]> {
1267
+ await this.initialize();
1268
+ const result = await this.pageClient.send("Network.getCookies");
1269
+ return parseCdpCookies(result.cookies);
1270
+ }
1271
+
1203
1272
  async close(): Promise<void> {
1204
1273
  if (this.closed) {
1205
1274
  return;
@@ -517,7 +517,6 @@ async function resolveNativeProxy(
517
517
  const resolvedProxy = await resolveProxyConfigAsync({
518
518
  proxy: options.proxy ?? clientOptions.proxy,
519
519
  upstream: clientOptions.upstream,
520
- apifuseConfig: clientOptions.apifuseConfig,
521
520
  proxyPolicy: clientOptions.proxyPolicy,
522
521
  affinityKey: clientOptions.affinityKey,
523
522
  proxyAttempt: computeProxyAttemptIndex({
@@ -23,6 +23,7 @@ import {
23
23
  getTraceRecorder,
24
24
  type TraceContext,
25
25
  } from "./trace.js";
26
+ import { RESOLVER_INSTRUMENTATION_METADATA } from "./resolver.js";
26
27
 
27
28
  export interface InstrumentationOptions extends CreateTraceContextOptions {}
28
29
 
@@ -30,7 +31,7 @@ export type InstrumentedProviderContext<T extends ProviderContext> = Omit<T, "tr
30
31
  trace: TraceContext;
31
32
  };
32
33
 
33
- type InstrumentedNamespace = "http" | "stealth" | "browser" | "session" | "state";
34
+ type InstrumentedNamespace = "http" | "stealth" | "browser" | "session" | "state" | "resolver";
34
35
 
35
36
  const BROWSER_PAGE_METHODS = new Set(["goto", "fill", "click", "type", "waitForSelector"]);
36
37
  const DIAGNOSTIC_BASE_URL = "http://apifuse-instrumentation.invalid";
@@ -621,15 +622,47 @@ function wrapNamespace<T extends object>(
621
622
  }
622
623
 
623
624
  const wrappedMethods = new Map<PropertyKey, unknown>();
625
+ const resolverMetadata =
626
+ namespace === "resolver" ? { target, traceRecorder: recorder } : undefined;
624
627
 
625
628
  return new Proxy(target, {
626
629
  get(namespaceTarget, property, receiver) {
630
+ if (property === RESOLVER_INSTRUMENTATION_METADATA && resolverMetadata) {
631
+ return resolverMetadata;
632
+ }
627
633
  const value = Reflect.get(namespaceTarget, property, receiver);
628
634
 
629
635
  if (typeof value !== "function" || property === "constructor") {
630
636
  return value;
631
637
  }
632
638
 
639
+ if (namespace === "resolver" && property === "solve") {
640
+ if (wrappedMethods.has(property)) {
641
+ return wrappedMethods.get(property);
642
+ }
643
+
644
+ const wrapped = (...args: unknown[]) => {
645
+ const challenge = args[0];
646
+ const challengeKind =
647
+ typeof challenge === "object" &&
648
+ challenge !== null &&
649
+ "kind" in challenge &&
650
+ typeof challenge.kind === "string"
651
+ ? challenge.kind
652
+ : undefined;
653
+ return recorder.runSpan(
654
+ "resolver.solve",
655
+ () => Reflect.apply(value, namespaceTarget, [args[0], args[1], recorder]),
656
+ {
657
+ attributes: challengeKind ? { challenge_kind: challengeKind } : undefined,
658
+ },
659
+ );
660
+ };
661
+
662
+ wrappedMethods.set(property, wrapped);
663
+ return wrapped;
664
+ }
665
+
633
666
  if (namespace === "browser" && property === "newPage") {
634
667
  if (wrappedMethods.has(property)) {
635
668
  return wrappedMethods.get(property);
@@ -838,7 +871,8 @@ export function wrapWithInstrumentation<T extends ProviderContext>(
838
871
  property === "stealth" ||
839
872
  property === "browser" ||
840
873
  property === "session" ||
841
- property === "state"
874
+ property === "state" ||
875
+ property === "resolver"
842
876
  ) {
843
877
  const namespace = property;
844
878
  if (wrappedTargets.has(namespace)) {
@@ -0,0 +1,31 @@
1
+ import type { ProviderChallenge, ProviderChallengeKind } from "../../types.js";
2
+ import type { ResolverIssuingIdentity } from "./types.js";
3
+
4
+ export const RESOLVER_CHALLENGE_BINDINGS = {
5
+ aws_waf: "portable",
6
+ cloudflare_interstitial: "identity_scoped",
7
+ } as const satisfies Partial<
8
+ Readonly<Record<ProviderChallengeKind, "identity_scoped" | "portable">>
9
+ >;
10
+
11
+ export function resolverChallengeIsIdentityScoped(challenge: ProviderChallenge): boolean {
12
+ return (
13
+ RESOLVER_CHALLENGE_BINDINGS[challenge.kind as keyof typeof RESOLVER_CHALLENGE_BINDINGS] ===
14
+ "identity_scoped"
15
+ );
16
+ }
17
+
18
+ export function resolverChallengeIssuingIdentity(
19
+ challenge: ProviderChallenge,
20
+ identity: ResolverIssuingIdentity,
21
+ ): ResolverIssuingIdentity {
22
+ const binding = (
23
+ RESOLVER_CHALLENGE_BINDINGS as Partial<
24
+ Readonly<Record<ProviderChallengeKind, "identity_scoped" | "portable">>
25
+ >
26
+ )[challenge.kind];
27
+ if (binding === "portable") {
28
+ return { userAgent: identity.userAgent };
29
+ }
30
+ return identity;
31
+ }