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

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 (66) hide show
  1. package/AUTHORING.md +7 -6
  2. package/CHANGELOG.md +9 -1
  3. package/README.md +3 -3
  4. package/bin/apifuse-check.ts +62 -3
  5. package/bin/apifuse-pack-check.ts +8 -2
  6. package/bin/apifuse-pack-smoke.ts +43 -2
  7. package/bin/apifuse-pack-types.ts +58 -0
  8. package/bin/apifuse-submit-check.ts +15 -2
  9. package/dist/auth.js +29 -0
  10. package/dist/cli/templates/provider/README.md.tpl +4 -4
  11. package/dist/contract-serialization.d.ts +20 -1
  12. package/dist/contract-serialization.js +583 -8
  13. package/dist/contract.d.ts +2 -0
  14. package/dist/contract.js +9 -5
  15. package/dist/declaration-validation.d.ts +23 -0
  16. package/dist/declaration-validation.js +159 -0
  17. package/dist/define.d.ts +1 -1
  18. package/dist/define.js +13 -2
  19. package/dist/index.d.ts +4 -3
  20. package/dist/index.js +3 -3
  21. package/dist/lint.js +85 -3
  22. package/dist/provider.d.ts +1 -1
  23. package/dist/provider.js +1 -1
  24. package/dist/runtime/cache.d.ts +1 -0
  25. package/dist/runtime/cache.js +169 -15
  26. package/dist/runtime/resolver-vendors/bindings.d.ts +42 -2
  27. package/dist/runtime/resolver-vendors/bindings.js +31 -6
  28. package/dist/runtime/resolver-vendors/browser.d.ts +3 -7
  29. package/dist/runtime/resolver-vendors/browser.js +7 -22
  30. package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
  31. package/dist/runtime/resolver-vendors/hosts.js +33 -0
  32. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +23 -0
  33. package/dist/runtime/resolver-vendors/twocaptcha.js +264 -0
  34. package/dist/runtime/resolver-vendors/types.d.ts +44 -3
  35. package/dist/runtime/resolver-vendors/types.js +10 -0
  36. package/dist/runtime/resolver.d.ts +17 -2
  37. package/dist/runtime/resolver.js +237 -15
  38. package/dist/runtime/stealth.d.ts +26 -4
  39. package/dist/runtime/stealth.js +224 -114
  40. package/dist/schema.d.ts +63 -0
  41. package/dist/schema.js +808 -8
  42. package/dist/server/serve.js +8 -0
  43. package/dist/stealth/profiles.js +16 -7
  44. package/dist/types.d.ts +37 -4
  45. package/package.json +2 -2
  46. package/src/auth.ts +40 -0
  47. package/src/cli/templates/provider/README.md.tpl +4 -4
  48. package/src/contract-serialization.ts +857 -8
  49. package/src/contract.ts +16 -5
  50. package/src/declaration-validation.ts +202 -0
  51. package/src/define.ts +23 -2
  52. package/src/index.ts +13 -0
  53. package/src/lint.ts +98 -3
  54. package/src/provider.ts +10 -0
  55. package/src/runtime/cache.ts +189 -14
  56. package/src/runtime/resolver-vendors/bindings.ts +40 -15
  57. package/src/runtime/resolver-vendors/browser.ts +9 -31
  58. package/src/runtime/resolver-vendors/hosts.ts +38 -0
  59. package/src/runtime/resolver-vendors/twocaptcha.ts +366 -0
  60. package/src/runtime/resolver-vendors/types.ts +54 -0
  61. package/src/runtime/resolver.ts +304 -24
  62. package/src/runtime/stealth.ts +317 -136
  63. package/src/schema.ts +1060 -9
  64. package/src/server/serve.ts +8 -0
  65. package/src/stealth/profiles.ts +17 -7
  66. package/src/types.ts +39 -6
@@ -1,7 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { ProviderError } from "../errors.js";
3
- import { resolverChallengeIsIdentityScoped, resolverChallengeIssuingIdentity, } from "./resolver-vendors/bindings.js";
3
+ import { resolverChallengeAllowsDirectCache, resolverChallengeIsCacheable, resolverChallengeIsIdentityScoped, resolverChallengeIssuingIdentity, } from "./resolver-vendors/bindings.js";
4
4
  import { createBrowserResolverVendorAdapter } from "./resolver-vendors/browser.js";
5
+ import { assertResolverHostAllowed } from "./resolver-vendors/hosts.js";
6
+ import { createTwoCaptchaResolverVendorAdapter } from "./resolver-vendors/twocaptcha.js";
5
7
  import { RESOLVER_VENDOR_CAPABILITIES, ResolverVendorUnavailableError, resolverVendorSupports, } from "./resolver-vendors/types.js";
6
8
  export const APIFUSE__RESOLVER__2CAPTCHA__API_KEY = "APIFUSE__RESOLVER__2CAPTCHA__API_KEY";
7
9
  export const APIFUSE__RESOLVER__CAPSOLVER__API_KEY = "APIFUSE__RESOLVER__CAPSOLVER__API_KEY";
@@ -14,8 +16,51 @@ const RESOLVER_SOLUTION_INDEX_CACHE_NAMESPACE = "resolver-solution-index";
14
16
  const MIN_RESOLVER_CACHE_TTL_MS = 1_000;
15
17
  const resolverCaches = new WeakMap();
16
18
  const solutionIssuerDigests = new WeakMap();
19
+ const SAFE_CAUSE_MESSAGE_WORDS = new Set([
20
+ "abort",
21
+ "aborted",
22
+ "at",
23
+ "closed",
24
+ "connect",
25
+ "connection",
26
+ "dns",
27
+ "during",
28
+ "econnrefused",
29
+ "econnreset",
30
+ "error",
31
+ "etimedout",
32
+ "failed",
33
+ "failure",
34
+ "fetch",
35
+ "from",
36
+ "get",
37
+ "lookup",
38
+ "network",
39
+ "post",
40
+ "reading",
41
+ "refused",
42
+ "request",
43
+ "reset",
44
+ "response",
45
+ "socket",
46
+ "timed",
47
+ "timeout",
48
+ "tls",
49
+ "to",
50
+ "unavailable",
51
+ "upstream",
52
+ "while",
53
+ "writing",
54
+ ]);
17
55
  export const RESOLVER_INSTRUMENTATION_METADATA = Symbol.for("@apifuse/provider-sdk/runtime/resolver-instrumentation-metadata");
18
- export const RESOLVER_ADAPTER_REGISTRY = {
56
+ const resolverAdapterRegistry = {
57
+ "2captcha"(configuration, timeoutMs, allowedHosts) {
58
+ return createTwoCaptchaResolverVendorAdapter({
59
+ allowedHosts,
60
+ apiKey: configuration,
61
+ timeoutMs,
62
+ });
63
+ },
19
64
  browser(configuration, timeoutMs, allowedHosts) {
20
65
  return createBrowserResolverVendorAdapter({
21
66
  allowedHosts,
@@ -24,10 +69,27 @@ export const RESOLVER_ADAPTER_REGISTRY = {
24
69
  });
25
70
  },
26
71
  };
72
+ export const RESOLVER_ADAPTER_REGISTRY = resolverAdapterRegistry;
73
+ export function swapResolverAdapterFactoryForTests(vendor, factory) {
74
+ const original = resolverAdapterRegistry[vendor];
75
+ if (factory === undefined)
76
+ delete resolverAdapterRegistry[vendor];
77
+ else
78
+ resolverAdapterRegistry[vendor] = factory;
79
+ let restored = false;
80
+ return () => {
81
+ if (restored)
82
+ return;
83
+ restored = true;
84
+ if (original === undefined)
85
+ delete resolverAdapterRegistry[vendor];
86
+ else
87
+ resolverAdapterRegistry[vendor] = original;
88
+ };
89
+ }
27
90
  // This is the sole allowlist for declared vendors whose registry entry may be absent.
28
91
  // Remove a vendor here when its adapter is registered.
29
92
  const KNOWN_UNIMPLEMENTED_RESOLVER_VENDORS = new Set([
30
- "2captcha",
31
93
  "capsolver",
32
94
  "capmonster",
33
95
  "custom",
@@ -65,8 +127,8 @@ function createUnavailableAdapter(vendor, reason) {
65
127
  },
66
128
  };
67
129
  }
68
- function createAdapter(vendor, timeoutMs, allowedHosts) {
69
- const factory = RESOLVER_ADAPTER_REGISTRY[vendor.vendor];
130
+ function createAdapter(vendor, timeoutMs, allowedHosts, adapterFactories) {
131
+ const factory = adapterFactories[vendor.vendor];
70
132
  if (!factory && !KNOWN_UNIMPLEMENTED_RESOLVER_VENDORS.has(vendor.vendor)) {
71
133
  throw new Error(`Resolver adapter factory is missing for implemented vendor "${vendor.vendor}"`);
72
134
  }
@@ -97,6 +159,117 @@ function throwExhausted(attempts) {
97
159
  details: attempts,
98
160
  });
99
161
  }
162
+ function assertClientProfileTransportContract(clientProfile, transport) {
163
+ if (clientProfile === undefined || transport === undefined)
164
+ return;
165
+ throw new ProviderError(`Resolver client profile "${clientProfile}" cannot be applied to a pre-bound transport`, {
166
+ code: "RESOLVER_CLIENT_PROFILE_TRANSPORT_CONFLICT",
167
+ fix: "Remove the pre-bound transport and provide createTransport({ clientProfile, identityScope }) so the SDK can apply the provider-declared profile.",
168
+ });
169
+ }
170
+ function adapterRequiresTransport(adapter, kind) {
171
+ return typeof adapter.requiresTransport === "function"
172
+ ? adapter.requiresTransport(kind)
173
+ : adapter.requiresTransport === true;
174
+ }
175
+ function sanitizeDiagnosticUrl(rawUrl) {
176
+ try {
177
+ const parsed = new URL(rawUrl);
178
+ return `${parsed.protocol}//${parsed.host}`;
179
+ }
180
+ catch {
181
+ return "[REDACTED_URL]";
182
+ }
183
+ }
184
+ function sanitizeCauseMessage(message) {
185
+ const withoutUrls = message.replace(/\b[a-z][a-z\d+.-]*:\/\/[^\s"'<>]+/gi, sanitizeDiagnosticUrl);
186
+ const withoutAssignments = withoutUrls.replace(/(?:^|\s)[^\s=]+=[^\s]*/g, " [REDACTED]");
187
+ const safeTokens = withoutAssignments
188
+ .replaceAll("\r", " ")
189
+ .replaceAll("\n", " ")
190
+ .split(/\s+/)
191
+ .filter(Boolean)
192
+ .map((token) => {
193
+ if (token === "[REDACTED]" || /^[a-z][a-z\d+.-]*:\/\/[^\s]+$/i.test(token))
194
+ return token;
195
+ const word = token.replace(/^[^a-z\d]+|[^a-z\d]+$/gi, "");
196
+ return word.length > 0 && word.length <= 32 && SAFE_CAUSE_MESSAGE_WORDS.has(word.toLowerCase())
197
+ ? token
198
+ : "[REDACTED]";
199
+ });
200
+ return safeTokens
201
+ .filter((token, index) => token !== "[REDACTED]" || safeTokens[index - 1] !== token)
202
+ .join(" ")
203
+ .slice(0, 512);
204
+ }
205
+ function restrictResolverTransport(transport, allowedHosts) {
206
+ return {
207
+ async fetch(url, init) {
208
+ // Empty declarations remain deny-by-default, matching the adapter-factory/browser path.
209
+ assertResolverHostAllowed(url, allowedHosts);
210
+ const response = await transport.fetch(url, { ...init, redirect: "manual" });
211
+ const hasLocationHeader = Object.keys(response.headers).some((name) => name.toLowerCase() === "location");
212
+ if (response.status >= 300 && response.status < 400 && hasLocationHeader) {
213
+ throw new ProviderError("Resolver transport refused a redirect response", {
214
+ code: "RESOLVER_HOST_NOT_ALLOWED",
215
+ fix: "Use a non-redirecting http or https URL on a host declared in allowedHosts.",
216
+ });
217
+ }
218
+ return response;
219
+ },
220
+ };
221
+ }
222
+ function safeCause(error) {
223
+ if (error.cause === undefined)
224
+ return undefined;
225
+ const cause = error.cause;
226
+ const rawName = cause instanceof Error ? cause.name : "Error";
227
+ return {
228
+ name: /^[a-z\d_.:-]{1,64}$/i.test(rawName) ? rawName : "Error",
229
+ message: sanitizeCauseMessage(cause instanceof Error ? cause.message : String(cause)),
230
+ };
231
+ }
232
+ function safeUpstreamHost(host) {
233
+ if (host === undefined)
234
+ return undefined;
235
+ const trimmed = host.trim();
236
+ if (/^[a-z\d.-]+$/i.test(trimmed))
237
+ return trimmed.toLowerCase();
238
+ try {
239
+ return new URL(trimmed).hostname.toLowerCase();
240
+ }
241
+ catch {
242
+ return undefined;
243
+ }
244
+ }
245
+ function safePhase(phase) {
246
+ return phase !== undefined && /^[a-z\d_.:-]{1,64}$/i.test(phase) ? phase : undefined;
247
+ }
248
+ function unavailableAttempt(error) {
249
+ const cause = safeCause(error);
250
+ const upstreamHost = safeUpstreamHost(error.upstreamHost);
251
+ const phase = safePhase(error.phase);
252
+ const round = Number.isSafeInteger(error.round) && error.round > 0 ? error.round : undefined;
253
+ return {
254
+ vendor: error.vendor,
255
+ reason: error.reason,
256
+ ...(cause ? { cause } : {}),
257
+ ...(upstreamHost ? { upstreamHost } : {}),
258
+ ...(phase ? { phase } : {}),
259
+ ...(round !== undefined ? { round } : {}),
260
+ };
261
+ }
262
+ function unavailableSpanAttributes(error) {
263
+ const attempt = unavailableAttempt(error);
264
+ return {
265
+ unavailability_reason: error.reason,
266
+ cause_name: attempt.cause?.name,
267
+ cause_message: attempt.cause?.message,
268
+ upstream_host: attempt.upstreamHost,
269
+ transport_phase: attempt.phase,
270
+ transport_round: attempt.round,
271
+ };
272
+ }
100
273
  function challengeOrigin(challenge) {
101
274
  return new URL(challenge.pageUrl).origin;
102
275
  }
@@ -211,7 +384,9 @@ async function writeResolverCacheIndex(cache, challenge, entries, now) {
211
384
  const ttlMs = Math.max(1, Math.floor(Math.max(...liveEntries.map((entry) => entry.expiresAtMs)) - now));
212
385
  await cache.set(indexKey, { entries: liveEntries }, { ttlMs });
213
386
  }
214
- async function cacheBrowserSolution(cache, challenge, solution, identity, identityScope) {
387
+ async function cacheResolverSolution(cache, challenge, solution, identity, identityScope) {
388
+ if (!resolverChallengeIsCacheable(challenge))
389
+ return;
215
390
  const expiresAtMs = solutionExpiryMs(solution);
216
391
  const now = Date.now();
217
392
  if (expiresAtMs === undefined)
@@ -222,6 +397,11 @@ async function cacheBrowserSolution(cache, challenge, solution, identity, identi
222
397
  const scopedDigest = identityScope !== undefined && resolverChallengeIsIdentityScoped(challenge)
223
398
  ? resolverIdentityScopeDigest(identityScope)
224
399
  : undefined;
400
+ if (!resolverChallengeAllowsDirectCache(challenge) &&
401
+ scopedDigest === undefined &&
402
+ identity.proxyUrl === undefined) {
403
+ return;
404
+ }
225
405
  const issuerDigest = scopedDigest ?? resolverIdentityDigest(identity);
226
406
  await cache.set(resolverSolutionCacheKey(cache, challenge, issuerDigest), { expiresAtMs, issuerDigest, solution }, { ttlMs });
227
407
  rememberSolutionIssuer(solution, issuerDigest);
@@ -271,6 +451,7 @@ export async function invalidateResolverSolution(resolver, challenge, solution)
271
451
  });
272
452
  }
273
453
  function createResolverChainClient(options) {
454
+ assertClientProfileTransportContract(options.clientProfile, options.transport);
274
455
  const client = {
275
456
  async solve(challenge, signal = new AbortController().signal, traceRecorder) {
276
457
  assertDeclaredKind(challenge.kind, options.kinds);
@@ -284,7 +465,7 @@ function createResolverChainClient(options) {
284
465
  if (supportingEntries.length === 0)
285
466
  throwUnsupportedKind(challenge.kind);
286
467
  signal.throwIfAborted();
287
- if (options.cache && supportingEntries.some((entry) => entry.id === "browser")) {
468
+ if (options.cache && resolverChallengeIsCacheable(challenge)) {
288
469
  const cached = await findCachedSolution(options.cache, challenge, options.identity, options.identityScope);
289
470
  if (cached)
290
471
  return cached;
@@ -293,24 +474,48 @@ function createResolverChainClient(options) {
293
474
  for (const entry of supportingEntries) {
294
475
  const adapter = entry.createAdapter();
295
476
  try {
296
- const solveAttempt = () => adapter.solve(challenge, options.identity, signal, traceRecorder);
477
+ const solveAttempt = () => {
478
+ const requiresTransport = adapterRequiresTransport(adapter, challenge.kind);
479
+ const unrestrictedTransport = options.transport ??
480
+ (requiresTransport
481
+ ? options.createTransport?.({
482
+ clientProfile: options.clientProfile,
483
+ identityScope: options.identityScope,
484
+ })
485
+ : undefined);
486
+ if (requiresTransport && unrestrictedTransport === undefined) {
487
+ throw new ResolverVendorUnavailableError(adapter.id, "missing_transport");
488
+ }
489
+ const transport = unrestrictedTransport
490
+ ? restrictResolverTransport(unrestrictedTransport, options.allowedHosts ?? [])
491
+ : undefined;
492
+ return adapter.solve(challenge, options.identity, signal, traceRecorder, transport);
493
+ };
297
494
  const solution = traceRecorder
298
495
  ? await traceRecorder.runSpan("resolver.vendor.attempt", solveAttempt, {
299
496
  attributes: {
300
497
  vendor: adapter.id,
301
498
  challenge_kind: challenge.kind,
499
+ client_profile: options.clientProfile,
302
500
  },
303
501
  onError(error) {
304
502
  return error instanceof ResolverVendorUnavailableError
305
- ? { unavailability_reason: error.reason }
503
+ ? unavailableSpanAttributes(error)
306
504
  : undefined;
307
505
  },
308
506
  })
309
507
  : await solveAttempt();
310
- if (options.cache && entry.id === "browser" && solution.form === "cookies") {
311
- const issuingIdentity = adapter.getIssuingIdentity?.(solution, options.identity, challenge);
508
+ if (options.cache &&
509
+ resolverChallengeIsCacheable(challenge) &&
510
+ solution.form === "cookies" &&
511
+ solutionExpiryMs(solution) !== undefined) {
512
+ const issuingIdentity = adapter.getIssuingIdentity?.(solution, options.identity, challenge) ??
513
+ resolverChallengeIssuingIdentity(challenge, {
514
+ ...(options.identity ? { proxyUrl: options.identity.proxyUrl } : {}),
515
+ userAgent: solution.userAgent,
516
+ });
312
517
  if (issuingIdentity) {
313
- await cacheBrowserSolution(options.cache, challenge, solution, issuingIdentity, options.identityScope);
518
+ await cacheResolverSolution(options.cache, challenge, solution, issuingIdentity, options.identityScope);
314
519
  }
315
520
  }
316
521
  return solution;
@@ -319,7 +524,7 @@ function createResolverChainClient(options) {
319
524
  signal.throwIfAborted();
320
525
  if (!(error instanceof ResolverVendorUnavailableError))
321
526
  throw error;
322
- attempts.push({ vendor: adapter.id, reason: error.reason });
527
+ attempts.push(unavailableAttempt(error));
323
528
  }
324
529
  }
325
530
  throwExhausted(attempts);
@@ -339,6 +544,10 @@ export function createResolverClient(options) {
339
544
  unavailableReason: options.unavailableReason,
340
545
  cache: options.cache,
341
546
  identity: options.identity,
547
+ transport: options.transport,
548
+ createTransport: options.createTransport,
549
+ clientProfile: options.clientProfile,
550
+ allowedHosts: options.allowedHosts,
342
551
  });
343
552
  }
344
553
  function resolveVendorAvailability(vendor, env) {
@@ -384,10 +593,11 @@ export function bindResolverSignal(resolver, defaultSignal) {
384
593
  }
385
594
  return boundResolver;
386
595
  }
387
- export function createResolverClientFromEnv(config, env = process.env, options = {}) {
596
+ function createResolverClientFromEnvInternal(config, env, options, adapterFactories) {
388
597
  if (!config) {
389
598
  return createUnsupportedResolverClient("Provider does not declare resolver capability");
390
599
  }
600
+ assertClientProfileTransportContract(config.clientProfile, options.transport);
391
601
  if (config.vendors.length === 0) {
392
602
  return createResolverChainClient({
393
603
  kinds: config.kinds,
@@ -397,6 +607,7 @@ export function createResolverClientFromEnv(config, env = process.env, options =
397
607
  }
398
608
  const timeoutValue = readPositiveIntegerEnv(env, APIFUSE__RESOLVER__TIMEOUT_MS);
399
609
  const timeoutMs = timeoutValue === undefined ? DEFAULT_RESOLVER_TIMEOUT_MS : Number(timeoutValue);
610
+ const allowedHosts = [...(options.allowedHosts ?? [])];
400
611
  return createResolverChainClient({
401
612
  kinds: config.kinds,
402
613
  entries: config.vendors.map((configuredVendor) => {
@@ -405,10 +616,21 @@ export function createResolverClientFromEnv(config, env = process.env, options =
405
616
  return {
406
617
  id: vendor,
407
618
  supports: (kind) => resolverVendorSupports(vendor, kind),
408
- createAdapter: () => createAdapter(resolveVendorAvailability(vendor, env), timeoutMs, options.allowedHosts ?? []),
619
+ createAdapter: () => createAdapter(resolveVendorAvailability(vendor, env), timeoutMs, allowedHosts, adapterFactories),
409
620
  };
410
621
  }),
411
622
  cache: options.cache,
412
623
  identityScope: options.identityScope,
624
+ transport: options.transport,
625
+ createTransport: options.createTransport,
626
+ clientProfile: config.clientProfile,
627
+ allowedHosts,
413
628
  });
414
629
  }
630
+ export function createResolverClientFromEnv(config, env = process.env, options = {}) {
631
+ return createResolverClientFromEnvInternal(config, env, options, RESOLVER_ADAPTER_REGISTRY);
632
+ }
633
+ /** Internal test seam; deliberately not re-exported from the package root. */
634
+ export function createResolverClientFromEnvForTests(config, env, options, adapterFactories) {
635
+ return createResolverClientFromEnvInternal(config, env, options, adapterFactories);
636
+ }
@@ -1,4 +1,4 @@
1
- import type { ImpitResponse } from "impit";
1
+ import type { BrowserProfile, EmulationOS } from "wreq-js";
2
2
  import type { ProxyResolutionOptions } from "../config/loader.js";
3
3
  import type { StealthClient, StealthResponse } from "../types.js";
4
4
  export type StealthClientOptions = ProxyResolutionOptions & {
@@ -12,12 +12,34 @@ export type StealthClientOptions = ProxyResolutionOptions & {
12
12
  insecureSkipVerify?: boolean;
13
13
  };
14
14
  };
15
- type StealthTransportResponse = Pick<ImpitResponse, "arrayBuffer" | "headers" | "json" | "ok" | "status" | "text"> & {
16
- body?: ReadableStream<Uint8Array>;
17
- abort?: () => void;
15
+ type StealthTransportHeaders = {
16
+ entries(): IterableIterator<[string, string]>;
17
+ get(name: string): string | null;
18
+ getSetCookie?: () => string[];
19
+ };
20
+ type StealthTransportBody = {
21
+ cancel(): Promise<void>;
22
+ getReader(): {
23
+ read(): Promise<{
24
+ done: boolean;
25
+ value?: Uint8Array;
26
+ }>;
27
+ cancel(): Promise<void>;
28
+ releaseLock(): void;
29
+ };
30
+ };
31
+ type StealthTransportResponse = {
32
+ arrayBuffer(): Promise<ArrayBuffer>;
33
+ headers: StealthTransportHeaders;
34
+ status: number;
35
+ body?: StealthTransportBody | null;
18
36
  url?: string;
19
37
  redirected?: boolean;
20
38
  };
39
+ export declare function resolveWreqProfile(profileName: string, wreqProfiles: readonly BrowserProfile[]): {
40
+ browser: BrowserProfile;
41
+ os: EmulationOS;
42
+ };
21
43
  export declare function normalizeResponse(response: StealthTransportResponse, requestUrl?: string, maxBodyBytes?: number): Promise<StealthResponse>;
22
44
  export declare function createStealthClient(baseUrl: string, defaultProfileOrOptions?: string | StealthClientOptions, clientOptions?: StealthClientOptions): StealthClient;
23
45
  export {};