@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.
- package/AUTHORING.md +7 -6
- package/CHANGELOG.md +9 -1
- package/README.md +3 -3
- package/bin/apifuse-check.ts +62 -3
- package/bin/apifuse-pack-check.ts +8 -2
- package/bin/apifuse-pack-smoke.ts +43 -2
- package/bin/apifuse-pack-types.ts +58 -0
- package/bin/apifuse-submit-check.ts +15 -2
- package/dist/auth.js +29 -0
- package/dist/cli/templates/provider/README.md.tpl +4 -4
- package/dist/contract-serialization.d.ts +20 -1
- package/dist/contract-serialization.js +583 -8
- package/dist/contract.d.ts +2 -0
- package/dist/contract.js +9 -5
- package/dist/declaration-validation.d.ts +23 -0
- package/dist/declaration-validation.js +159 -0
- package/dist/define.d.ts +1 -1
- package/dist/define.js +13 -2
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -3
- package/dist/lint.js +85 -3
- package/dist/provider.d.ts +1 -1
- package/dist/provider.js +1 -1
- package/dist/runtime/cache.d.ts +1 -0
- package/dist/runtime/cache.js +169 -15
- package/dist/runtime/resolver-vendors/bindings.d.ts +42 -2
- package/dist/runtime/resolver-vendors/bindings.js +31 -6
- package/dist/runtime/resolver-vendors/browser.d.ts +3 -7
- package/dist/runtime/resolver-vendors/browser.js +7 -22
- 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 +23 -0
- package/dist/runtime/resolver-vendors/twocaptcha.js +264 -0
- package/dist/runtime/resolver-vendors/types.d.ts +44 -3
- package/dist/runtime/resolver-vendors/types.js +10 -0
- package/dist/runtime/resolver.d.ts +17 -2
- package/dist/runtime/resolver.js +237 -15
- package/dist/runtime/stealth.d.ts +26 -4
- package/dist/runtime/stealth.js +224 -114
- package/dist/schema.d.ts +63 -0
- package/dist/schema.js +808 -8
- package/dist/server/serve.js +8 -0
- package/dist/stealth/profiles.js +16 -7
- package/dist/types.d.ts +37 -4
- package/package.json +2 -2
- package/src/auth.ts +40 -0
- package/src/cli/templates/provider/README.md.tpl +4 -4
- package/src/contract-serialization.ts +857 -8
- package/src/contract.ts +16 -5
- package/src/declaration-validation.ts +202 -0
- package/src/define.ts +23 -2
- package/src/index.ts +13 -0
- package/src/lint.ts +98 -3
- package/src/provider.ts +10 -0
- package/src/runtime/cache.ts +189 -14
- package/src/runtime/resolver-vendors/bindings.ts +40 -15
- package/src/runtime/resolver-vendors/browser.ts +9 -31
- package/src/runtime/resolver-vendors/hosts.ts +38 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +366 -0
- package/src/runtime/resolver-vendors/types.ts +54 -0
- package/src/runtime/resolver.ts +304 -24
- package/src/runtime/stealth.ts +317 -136
- package/src/schema.ts +1060 -9
- package/src/server/serve.ts +8 -0
- package/src/stealth/profiles.ts +17 -7
- package/src/types.ts +39 -6
package/dist/runtime/resolver.js
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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
|
|
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 &&
|
|
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 = () =>
|
|
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
|
-
?
|
|
503
|
+
? unavailableSpanAttributes(error)
|
|
306
504
|
: undefined;
|
|
307
505
|
},
|
|
308
506
|
})
|
|
309
507
|
: await solveAttempt();
|
|
310
|
-
if (options.cache &&
|
|
311
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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,
|
|
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 {
|
|
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
|
|
16
|
-
|
|
17
|
-
|
|
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 {};
|