@apifuse/provider-sdk 2.1.0-beta.9 → 2.2.0-beta.10
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 +240 -0
- package/CHANGELOG.md +93 -0
- package/README.md +26 -10
- package/SUBMISSION.md +11 -12
- package/bin/apifuse-check.ts +44 -59
- package/bin/apifuse-create.ts +1 -1
- package/bin/apifuse-dev.ts +27 -52
- package/bin/apifuse-pack-check.ts +36 -0
- package/bin/apifuse-pack-smoke.ts +22 -81
- package/bin/apifuse-pack-types.ts +266 -0
- package/bin/apifuse-perf.ts +45 -127
- package/bin/apifuse-record.ts +53 -70
- package/bin/apifuse-submit-check.ts +2177 -353
- 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-semantics.ts +204 -0
- package/bin/submit-check-xml.ts +134 -0
- package/dist/auth-turn/auth-turn.v1.schema.json +89 -0
- package/dist/auth-turn/fixtures/invalid/empty-kind.json +4 -0
- package/dist/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
- package/dist/auth-turn/fixtures/invalid/missing-kind.json +3 -0
- package/dist/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
- package/dist/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
- package/dist/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
- package/dist/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
- package/dist/auth-turn/fixtures/valid/abort.json +8 -0
- package/dist/auth-turn/fixtures/valid/challenge.json +17 -0
- package/dist/auth-turn/fixtures/valid/complete.json +13 -0
- package/dist/auth-turn/fixtures/valid/form.json +14 -0
- package/dist/auth-turn/fixtures/valid/message.json +13 -0
- package/dist/auth-turn/fixtures/valid/multi_choice.json +15 -0
- package/dist/auth-turn/fixtures/valid/pending.json +5 -0
- package/dist/auth-turn/fixtures/valid/poll.json +9 -0
- package/dist/auth-turn/fixtures/valid/redirect.json +16 -0
- package/dist/auth-turn/fixtures/valid/retry.json +8 -0
- package/dist/auth-turn/fixtures/valid/unknown-kind.json +7 -0
- package/dist/auth-turn/index.d.ts +195 -0
- package/dist/auth-turn/index.js +133 -0
- package/dist/auth.d.ts +76 -0
- package/dist/auth.js +427 -0
- package/dist/ceremonies/index.d.ts +1 -1
- package/dist/ceremonies/index.js +14 -48
- 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 +47 -33
- package/dist/cli/prompt-assets.d.ts +80 -0
- package/dist/cli/prompt-assets.js +743 -0
- package/dist/cli/templates/provider/.agents/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
- package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
- package/dist/cli/templates/provider/.agents/skills/normalization-standards/SKILL.md.tpl +57 -0
- package/dist/cli/templates/provider/.agents/skills/pagination-and-counts/SKILL.md.tpl +52 -0
- package/dist/cli/templates/provider/.agents/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
- package/dist/cli/templates/provider/.agents/skills/upstream-notes/README.md.tpl +13 -0
- package/dist/cli/templates/provider/.dockerignore.tpl +22 -0
- package/dist/cli/templates/provider/.gitignore.tpl +22 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +96 -0
- package/dist/cli/templates/provider/Dockerfile.tpl +7 -0
- package/dist/cli/templates/provider/README.md.tpl +163 -0
- package/dist/cli/templates/provider/dev.ts.tpl +5 -0
- package/dist/cli/templates/provider/domain/README.md.tpl +3 -0
- package/dist/cli/templates/provider/index.test.ts.tpl +13 -0
- package/dist/cli/templates/provider/index.ts.tpl +15 -0
- package/dist/cli/templates/provider/mappers/README.md.tpl +3 -0
- package/dist/cli/templates/provider/meta.ts.tpl +7 -0
- package/dist/cli/templates/provider/operations/index.ts.tpl +5 -0
- package/dist/cli/templates/provider/operations/ping.ts.tpl +24 -0
- package/dist/cli/templates/provider/schemas/ping.ts.tpl +24 -0
- package/dist/cli/templates/provider/start.ts.tpl +5 -0
- package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
- package/dist/config/loader.d.ts +149 -8
- package/dist/config/loader.js +378 -89
- package/dist/contract-serialization.d.ts +2 -2
- package/dist/contract-serialization.js +3 -6
- package/dist/contract-types.d.ts +2 -2
- package/dist/contract.d.ts +3 -3
- package/dist/contract.js +5 -6
- package/dist/define.d.ts +13 -1
- package/dist/define.js +245 -178
- package/dist/dev.d.ts +1 -1
- package/dist/dev.js +1 -1
- package/dist/errors.d.ts +4 -1
- package/dist/errors.js +48 -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 +44 -41
- package/dist/index.js +39 -36
- package/dist/lint.d.ts +1 -0
- package/dist/lint.js +35 -15
- package/dist/provider.d.ts +11 -9
- package/dist/provider.js +9 -8
- 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 +1 -1
- package/dist/runtime/auth-flow.js +4 -2
- package/dist/runtime/browser.d.ts +1 -1
- package/dist/runtime/browser.js +214 -25
- package/dist/runtime/cache.d.ts +1 -1
- package/dist/runtime/cache.js +4 -8
- package/dist/runtime/choice.d.ts +1 -1
- package/dist/runtime/choice.js +31 -35
- 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 +15 -3
- package/dist/runtime/http.d.ts +2 -2
- package/dist/runtime/http.js +160 -344
- 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 +56 -19
- package/dist/runtime/keyring.js +1 -1
- package/dist/runtime/namespace.js +1 -1
- 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 +35 -0
- package/dist/runtime/proxy-nodemaven.js +128 -0
- package/dist/runtime/proxy-retry-policy.d.ts +40 -0
- package/dist/runtime/proxy-retry-policy.js +326 -0
- package/dist/runtime/proxy-telemetry.d.ts +2 -1
- package/dist/runtime/proxy-telemetry.js +55 -52
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +2 -4
- package/dist/runtime/request-options.d.ts +1 -1
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +2 -2
- package/dist/runtime/state.js +15 -4
- package/dist/runtime/stealth.d.ts +7 -4
- package/dist/runtime/stealth.js +257 -215
- package/dist/runtime/stt.d.ts +1 -1
- package/dist/runtime/stt.js +11 -15
- 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 -3
- package/dist/server/index.js +6 -2
- package/dist/server/self-test-input-tokens.d.ts +1 -0
- package/dist/server/self-test-input-tokens.js +37 -0
- package/dist/server/self-test-redaction.d.ts +20 -0
- package/dist/server/self-test-redaction.js +70 -0
- package/dist/server/self-test-token.d.ts +30 -0
- package/dist/server/self-test-token.js +50 -0
- package/dist/server/self-test.d.ts +199 -0
- package/dist/server/self-test.js +1113 -0
- package/dist/server/serve.d.ts +14 -3
- package/dist/server/serve.js +135 -64
- package/dist/server/types.d.ts +10 -9
- package/dist/server/types.js +3 -7
- package/dist/stealth/profiles.d.ts +1 -1
- package/dist/stealth/profiles.js +5 -14
- package/dist/stream.d.ts +1 -1
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/dist/testing/run.d.ts +1 -1
- package/dist/testing/run.js +12 -15
- package/dist/types.d.ts +237 -1
- package/dist/user-input.d.ts +30 -0
- package/dist/user-input.js +66 -0
- package/package.json +16 -5
- package/src/auth-turn/auth-turn.v1.schema.json +89 -0
- package/src/auth-turn/fixtures/invalid/empty-kind.json +4 -0
- package/src/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
- package/src/auth-turn/fixtures/invalid/missing-kind.json +3 -0
- package/src/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
- package/src/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
- package/src/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
- package/src/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
- package/src/auth-turn/fixtures/valid/abort.json +8 -0
- package/src/auth-turn/fixtures/valid/challenge.json +17 -0
- package/src/auth-turn/fixtures/valid/complete.json +13 -0
- package/src/auth-turn/fixtures/valid/form.json +14 -0
- package/src/auth-turn/fixtures/valid/message.json +13 -0
- package/src/auth-turn/fixtures/valid/multi_choice.json +15 -0
- package/src/auth-turn/fixtures/valid/pending.json +5 -0
- package/src/auth-turn/fixtures/valid/poll.json +9 -0
- package/src/auth-turn/fixtures/valid/redirect.json +16 -0
- package/src/auth-turn/fixtures/valid/retry.json +8 -0
- package/src/auth-turn/fixtures/valid/unknown-kind.json +7 -0
- package/src/auth-turn/index.ts +177 -0
- package/src/auth.ts +728 -0
- package/src/ceremonies/index.ts +33 -121
- package/src/cli/commands.ts +10 -0
- package/src/cli/create.ts +69 -99
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/.agents/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
- package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
- package/src/cli/templates/provider/.agents/skills/normalization-standards/SKILL.md.tpl +57 -0
- package/src/cli/templates/provider/.agents/skills/pagination-and-counts/SKILL.md.tpl +52 -0
- package/src/cli/templates/provider/.agents/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
- package/src/cli/templates/provider/.agents/skills/upstream-notes/README.md.tpl +13 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +96 -0
- package/src/cli/templates/provider/README.md.tpl +7 -4
- package/src/config/loader.ts +543 -208
- package/src/contract-serialization.ts +5 -11
- package/src/contract-types.ts +2 -2
- package/src/contract.ts +13 -28
- package/src/define.ts +397 -528
- package/src/dev.ts +4 -9
- package/src/errors.ts +58 -5
- 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 +64 -41
- package/src/lint.ts +122 -159
- package/src/provider.ts +40 -9
- package/src/public-schema-field-lint.ts +7 -33
- package/src/recipes/gov-api.ts +2 -5
- package/src/runtime/auth-flow.ts +6 -6
- package/src/runtime/browser.ts +320 -151
- package/src/runtime/cache.ts +20 -67
- package/src/runtime/choice.ts +79 -132
- package/src/runtime/credential.ts +2 -2
- package/src/runtime/env.ts +1 -1
- package/src/runtime/executor.ts +23 -20
- package/src/runtime/http.ts +216 -539
- package/src/runtime/insights.ts +15 -53
- package/src/runtime/instrumentation.ts +78 -65
- package/src/runtime/keyring.ts +7 -19
- package/src/runtime/namespace.ts +2 -7
- 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 +178 -0
- package/src/runtime/proxy-retry-policy.ts +469 -0
- package/src/runtime/proxy-telemetry.ts +79 -77
- package/src/runtime/redis.ts +4 -12
- package/src/runtime/request-options.ts +4 -13
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +41 -110
- package/src/runtime/stealth.ts +331 -369
- package/src/runtime/stt.ts +38 -94
- package/src/runtime/trace.ts +14 -44
- package/src/runtime/waterfall.ts +5 -18
- package/src/schema.ts +23 -84
- package/src/serve.ts +1 -1
- package/src/server/index.ts +44 -3
- package/src/server/self-test-input-tokens.ts +46 -0
- package/src/server/self-test-redaction.ts +97 -0
- package/src/server/self-test-token.ts +70 -0
- package/src/server/self-test.ts +1450 -0
- package/src/server/serve.ts +206 -216
- package/src/server/types.ts +7 -19
- package/src/stealth/profiles.ts +10 -26
- package/src/stream.ts +8 -19
- package/src/testing/index.ts +2 -2
- package/src/testing/run.ts +24 -64
- package/src/types.ts +274 -1
- package/src/user-input.ts +118 -0
package/dist/runtime/stealth.js
CHANGED
|
@@ -1,44 +1,19 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { Impit } from "impit";
|
|
3
|
-
import { DEFAULT_SMARTPROXY_POOL_SIZE, invalidateProxyResolutionCacheAsync, ProxyResolutionError, resolveProxyConfigAsync,
|
|
4
|
-
import {
|
|
5
|
-
import { getStealthProfile } from "../stealth/profiles";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { appendQueryParams } from "./request-options";
|
|
3
|
+
import { DEFAULT_SMARTPROXY_POOL_SIZE, invalidateProxyResolutionCacheAsync, policyResolvesRegistryVendorChain, ProxyResolutionError, resolvePolicyProxyPoolSpan, resolvePolicyTransportAttemptCap, resolveProxyConfigAsync, vendorFromResolvedSource, } from "../config/loader.js";
|
|
4
|
+
import { SDKError, TransportError } from "../errors.js";
|
|
5
|
+
import { getStealthProfile } from "../stealth/profiles.js";
|
|
6
|
+
import { createProxyAuthIpDeniedError, createProxyEdgeAuthRejectedError, createProxyEdgeTlsRejectedError, createProxyPoolExhaustedError, createProxyPoolStaleError, isProxyAuthIpDeniedMessage, isProxyEdgeAuthRejectedMessage, isProxyEdgeTlsRejectedResponse, isProxyPoolRefreshableError, isProxyPoolStaleMessage, isProxyPoolStaleStatus, PROXY_EDGE_AUTH_REJECTED_CODE, PROXY_POOL_STALE_CODE, } from "./proxy-errors.js";
|
|
7
|
+
import { computeProxyAttemptIndex, computeProxyTransportRetryDelayMs, createDefaultProxyTransportRetryOptions, normalizeProxyTransportRetryOptions, shouldRetryProxyTransportAttempt, validateUnsafeProxyTransportRetryMethods, } from "./proxy-retry-policy.js";
|
|
8
|
+
import { appendQueryParams } from "./request-options.js";
|
|
9
9
|
const DEFAULT_PROFILE = "chrome-146";
|
|
10
10
|
const MISSING_PROXY_WARNING = "[provider-sdk] Provider requested proxy routing, but no proxy URL was configured. Continuing without proxy.";
|
|
11
|
-
const MAX_POLICY_PROXY_RETRY_ATTEMPTS = SMARTPROXY_MAX_POOL_SIZE;
|
|
12
11
|
const MAX_POLICY_PROXY_POOL_REFRESHES = 1;
|
|
13
12
|
const PROXY_CONNECT_FAILURE_CODE = "proxy_connect_failed";
|
|
14
13
|
const PROXY_CONNECT_FAILURE_BODY_PATTERN = /\bproxy\b.*\b(non[\s-]?200|connect|tunnel)|\bconnect\b.*\bproxy\b|\btunnel\b/i;
|
|
15
14
|
const PROXY_AUTH_DIAGNOSTIC_URL = "http://example.com/";
|
|
16
15
|
const PROXY_AUTH_DIAGNOSTIC_TIMEOUT_MS = 5_000;
|
|
17
|
-
const
|
|
18
|
-
const DEFAULT_STEALTH_RETRY_ERROR_CODES = [
|
|
19
|
-
PROXY_CONNECT_FAILURE_CODE,
|
|
20
|
-
"transport_network_error",
|
|
21
|
-
"transport_timeout",
|
|
22
|
-
];
|
|
23
|
-
const RATE_LIMIT_STEALTH_RETRY_ERROR_CODES = ["transport_timeout"];
|
|
24
|
-
const KNOWN_STEALTH_RETRY_METHODS = new Set([
|
|
25
|
-
"GET",
|
|
26
|
-
"HEAD",
|
|
27
|
-
"POST",
|
|
28
|
-
"PUT",
|
|
29
|
-
"DELETE",
|
|
30
|
-
"OPTIONS",
|
|
31
|
-
"TRACE",
|
|
32
|
-
"PATCH",
|
|
33
|
-
]);
|
|
34
|
-
const UNSAFE_STEALTH_RETRY_METHODS = new Set([
|
|
35
|
-
"POST",
|
|
36
|
-
"PUT",
|
|
37
|
-
"PATCH",
|
|
38
|
-
"DELETE",
|
|
39
|
-
"TRACE",
|
|
40
|
-
]);
|
|
41
|
-
const MAX_STEALTH_RETRY_ATTEMPTS = 8;
|
|
16
|
+
const STEALTH_PROXY_TRANSPORT_RETRY_ERROR_CODES = [PROXY_CONNECT_FAILURE_CODE];
|
|
42
17
|
const REMOVED_CHROME_PROFILE_NAMES = new Set([
|
|
43
18
|
"chrome-120",
|
|
44
19
|
"chrome-124",
|
|
@@ -102,11 +77,32 @@ class CookieJarImpl {
|
|
|
102
77
|
getAll() {
|
|
103
78
|
return { ...this.cookies };
|
|
104
79
|
}
|
|
80
|
+
has(name) {
|
|
81
|
+
return Object.hasOwn(this.cookies, name);
|
|
82
|
+
}
|
|
105
83
|
toString() {
|
|
106
84
|
return Object.entries(this.cookies)
|
|
107
85
|
.map(([name, value]) => `${name}=${value}`)
|
|
108
86
|
.join("; ");
|
|
109
87
|
}
|
|
88
|
+
toHeader() {
|
|
89
|
+
return this.toString();
|
|
90
|
+
}
|
|
91
|
+
snapshot() {
|
|
92
|
+
return this.getAll();
|
|
93
|
+
}
|
|
94
|
+
restore(cookies) {
|
|
95
|
+
this.clear();
|
|
96
|
+
for (const [name, value] of Object.entries(cookies)) {
|
|
97
|
+
if (name)
|
|
98
|
+
this.cookies[name] = value;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
clear() {
|
|
102
|
+
for (const name of Object.keys(this.cookies)) {
|
|
103
|
+
delete this.cookies[name];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
110
106
|
find(predicate) {
|
|
111
107
|
for (const [name, value] of Object.entries(this.cookies)) {
|
|
112
108
|
const cookie = `${name}=${value}`;
|
|
@@ -237,7 +233,7 @@ function splitCombinedSetCookieHeader(headerValue) {
|
|
|
237
233
|
cookieStrings.push(finalCookie);
|
|
238
234
|
return cookieStrings;
|
|
239
235
|
}
|
|
240
|
-
export async function normalizeResponse(response) {
|
|
236
|
+
export async function normalizeResponse(response, requestUrl) {
|
|
241
237
|
const headers = Object.fromEntries(response.headers.entries());
|
|
242
238
|
const cookies = new CookieJarImpl(setCookieHeadersFromResponse(response.headers));
|
|
243
239
|
const bodyBytes = await response.arrayBuffer();
|
|
@@ -245,6 +241,12 @@ export async function normalizeResponse(response) {
|
|
|
245
241
|
return {
|
|
246
242
|
status: response.status,
|
|
247
243
|
ok: response.status >= 200 && response.status < 300,
|
|
244
|
+
...(response.url ? { url: response.url } : {}),
|
|
245
|
+
...(response.redirected !== undefined
|
|
246
|
+
? { redirected: response.redirected }
|
|
247
|
+
: requestUrl && response.url
|
|
248
|
+
? { redirected: response.url !== requestUrl }
|
|
249
|
+
: {}),
|
|
248
250
|
headers,
|
|
249
251
|
rawHeaders: headerEntriesFromHeaders(response.headers),
|
|
250
252
|
body,
|
|
@@ -276,130 +278,8 @@ function isPolicyManagedProxy(options) {
|
|
|
276
278
|
const policy = options.proxyPolicy ?? options.upstream?.proxy;
|
|
277
279
|
return Boolean(policy && typeof policy === "object");
|
|
278
280
|
}
|
|
279
|
-
function isRetrySafeStealthMethod(method) {
|
|
280
|
-
return method === "GET" || method === "HEAD" || method === "OPTIONS";
|
|
281
|
-
}
|
|
282
|
-
function createStealthRetryOptions(preset) {
|
|
283
|
-
switch (preset) {
|
|
284
|
-
case HttpRetryPreset.Off:
|
|
285
|
-
return {
|
|
286
|
-
attempts: 1,
|
|
287
|
-
methods: DEFAULT_STEALTH_RETRY_METHODS,
|
|
288
|
-
errorCodes: DEFAULT_STEALTH_RETRY_ERROR_CODES,
|
|
289
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
290
|
-
};
|
|
291
|
-
case HttpRetryPreset.AggressiveRead:
|
|
292
|
-
return {
|
|
293
|
-
attempts: 4,
|
|
294
|
-
methods: DEFAULT_STEALTH_RETRY_METHODS,
|
|
295
|
-
errorCodes: DEFAULT_STEALTH_RETRY_ERROR_CODES,
|
|
296
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
297
|
-
};
|
|
298
|
-
case HttpRetryPreset.RateLimitAware:
|
|
299
|
-
return {
|
|
300
|
-
attempts: 3,
|
|
301
|
-
methods: DEFAULT_STEALTH_RETRY_METHODS,
|
|
302
|
-
errorCodes: RATE_LIMIT_STEALTH_RETRY_ERROR_CODES,
|
|
303
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
304
|
-
};
|
|
305
|
-
case HttpRetryPreset.SafeRead:
|
|
306
|
-
case HttpRetryPreset.TransportTransient:
|
|
307
|
-
return {
|
|
308
|
-
attempts: 3,
|
|
309
|
-
methods: DEFAULT_STEALTH_RETRY_METHODS,
|
|
310
|
-
errorCodes: DEFAULT_STEALTH_RETRY_ERROR_CODES,
|
|
311
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
throw new ProviderError(`Unknown stealth retry preset: ${preset}`, {
|
|
315
|
-
code: "retry_invalid_policy",
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
function normalizeStealthRetryOptions(retry) {
|
|
319
|
-
if (retry === undefined)
|
|
320
|
-
return undefined;
|
|
321
|
-
if (retry === false)
|
|
322
|
-
return createStealthRetryOptions(HttpRetryPreset.Off);
|
|
323
|
-
if (retry === true)
|
|
324
|
-
return createStealthRetryOptions(HttpRetryPreset.TransportTransient);
|
|
325
|
-
if (typeof retry === "string") {
|
|
326
|
-
if (!Object.values(HttpRetryPreset).includes(retry)) {
|
|
327
|
-
throw new ProviderError(`Unknown stealth retry preset: ${retry}`, {
|
|
328
|
-
code: "retry_invalid_policy",
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
return createStealthRetryOptions(retry);
|
|
332
|
-
}
|
|
333
|
-
if (typeof retry !== "object" || retry === null || Array.isArray(retry)) {
|
|
334
|
-
throw new ProviderError("Stealth retry policy must be a plain object", {
|
|
335
|
-
code: "retry_invalid_policy",
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
if (retry.unsafeMethodPolicy !== undefined &&
|
|
339
|
-
!Object.values(HttpRetryUnsafeMethodPolicy).includes(retry.unsafeMethodPolicy)) {
|
|
340
|
-
throw new ProviderError(`Unknown stealth retry unsafe method policy: ${String(retry.unsafeMethodPolicy)}`, { code: "retry_invalid_policy" });
|
|
341
|
-
}
|
|
342
|
-
if (retry.methods !== undefined) {
|
|
343
|
-
if (!Array.isArray(retry.methods)) {
|
|
344
|
-
throw new ProviderError("Stealth retry methods must be an array", {
|
|
345
|
-
code: "retry_invalid_policy",
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
const unknownMethods = retry.methods
|
|
349
|
-
.map((method) => (typeof method === "string" ? method.toUpperCase() : ""))
|
|
350
|
-
.filter((method) => !KNOWN_STEALTH_RETRY_METHODS.has(method));
|
|
351
|
-
if (unknownMethods.length > 0) {
|
|
352
|
-
throw new ProviderError(`Unknown stealth retry method(s): ${unknownMethods.join(", ")}`, { code: "retry_invalid_policy" });
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
if (retry.errorCodes !== undefined) {
|
|
356
|
-
if (!Array.isArray(retry.errorCodes) ||
|
|
357
|
-
retry.errorCodes.some((errorCode) => typeof errorCode !== "string")) {
|
|
358
|
-
throw new ProviderError("Stealth retry errorCodes must contain only strings", { code: "retry_invalid_policy" });
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
const base = createStealthRetryOptions(retry.preset ?? HttpRetryPreset.TransportTransient);
|
|
362
|
-
const attempts = retry.attempts === undefined || !Number.isFinite(retry.attempts)
|
|
363
|
-
? base.attempts
|
|
364
|
-
: Math.max(1, Math.min(MAX_STEALTH_RETRY_ATTEMPTS, Math.floor(retry.attempts)));
|
|
365
|
-
const normalized = {
|
|
366
|
-
attempts,
|
|
367
|
-
methods: retry.methods?.map((method) => method.toUpperCase()) ?? base.methods,
|
|
368
|
-
errorCodes: retry.errorCodes ?? base.errorCodes,
|
|
369
|
-
unsafeMethodPolicy: retry.unsafeMethodPolicy ?? base.unsafeMethodPolicy,
|
|
370
|
-
};
|
|
371
|
-
if (normalized.unsafeMethodPolicy !==
|
|
372
|
-
HttpRetryUnsafeMethodPolicy.AllowExplicitUnsafe) {
|
|
373
|
-
const unsafeMethods = normalized.methods.filter((method) => UNSAFE_STEALTH_RETRY_METHODS.has(method.toUpperCase()));
|
|
374
|
-
if (unsafeMethods.length > 0) {
|
|
375
|
-
throw new ProviderError(`Stealth retry methods include unsafe method(s): ${unsafeMethods.join(", ")}`, { code: "retry_unsafe_method" });
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
return normalized;
|
|
379
|
-
}
|
|
380
|
-
function isExplicitStealthRetryAllowed(method, error, retryOptions) {
|
|
381
|
-
if (!retryOptions || retryOptions.attempts <= 1)
|
|
382
|
-
return false;
|
|
383
|
-
return (retryOptions.methods.includes(method.toUpperCase()) &&
|
|
384
|
-
retryOptions.errorCodes.includes(proxyAttemptErrorCode(error)));
|
|
385
|
-
}
|
|
386
|
-
function isRetryableProxyTransportError(error) {
|
|
387
|
-
if (error instanceof TransportError) {
|
|
388
|
-
if (error.code === PROXY_AUTH_IP_DENIED_CODE) {
|
|
389
|
-
return false;
|
|
390
|
-
}
|
|
391
|
-
return (error.code === PROXY_CONNECT_FAILURE_CODE ||
|
|
392
|
-
error.code === "transport_network_error" ||
|
|
393
|
-
error.code === "transport_timeout");
|
|
394
|
-
}
|
|
395
|
-
if (error instanceof SDKError) {
|
|
396
|
-
return false;
|
|
397
|
-
}
|
|
398
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
399
|
-
return /\bproxy\b|\bnon[\s-]?200\b|\bconnect\b|\btunnel\b/i.test(message);
|
|
400
|
-
}
|
|
401
281
|
function isProxyConnectFailureResponse(response, body) {
|
|
402
|
-
return
|
|
282
|
+
return response.status === 0 && PROXY_CONNECT_FAILURE_BODY_PATTERN.test(body ?? "");
|
|
403
283
|
}
|
|
404
284
|
function createProxyConnectFailureError(body, cause) {
|
|
405
285
|
const bodyExcerpt = (body ?? "").trim().slice(0, 1_000);
|
|
@@ -502,8 +382,7 @@ function normalizeStealthTransportError(error) {
|
|
|
502
382
|
return createProxyEdgeAuthRejectedError(error instanceof Error ? error : undefined);
|
|
503
383
|
}
|
|
504
384
|
const proxyTunnelStatus = getProxyTunnelStatus(error);
|
|
505
|
-
if (proxyTunnelStatus !== undefined &&
|
|
506
|
-
isProxyPoolStaleStatus(proxyTunnelStatus)) {
|
|
385
|
+
if (proxyTunnelStatus !== undefined && isProxyPoolStaleStatus(proxyTunnelStatus)) {
|
|
507
386
|
return createProxyPoolStaleError(proxyTunnelStatus, error instanceof Error ? error : undefined);
|
|
508
387
|
}
|
|
509
388
|
if (PROXY_CONNECT_FAILURE_BODY_PATTERN.test(message)) {
|
|
@@ -515,6 +394,9 @@ function normalizeStealthTransportError(error) {
|
|
|
515
394
|
cause: error instanceof Error ? error : undefined,
|
|
516
395
|
});
|
|
517
396
|
}
|
|
397
|
+
function sleep(ms) {
|
|
398
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
399
|
+
}
|
|
518
400
|
function normalizeMethod(method) {
|
|
519
401
|
switch (method.toUpperCase()) {
|
|
520
402
|
case "HEAD":
|
|
@@ -537,6 +419,23 @@ function normalizeMethod(method) {
|
|
|
537
419
|
throw new SDKError(`Unsupported stealth method: ${method}`);
|
|
538
420
|
}
|
|
539
421
|
}
|
|
422
|
+
function isRedirectStatus(status) {
|
|
423
|
+
return [301, 302, 303, 307, 308].includes(status);
|
|
424
|
+
}
|
|
425
|
+
function nextRedirectMethod(status, method) {
|
|
426
|
+
if (status === 303 && method !== "HEAD")
|
|
427
|
+
return "GET";
|
|
428
|
+
if ((status === 301 || status === 302) && method === "POST")
|
|
429
|
+
return "GET";
|
|
430
|
+
return method;
|
|
431
|
+
}
|
|
432
|
+
function locationHeader(headers) {
|
|
433
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
434
|
+
if (name.toLowerCase() === "location")
|
|
435
|
+
return value;
|
|
436
|
+
}
|
|
437
|
+
return undefined;
|
|
438
|
+
}
|
|
540
439
|
function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
541
440
|
const clients = new Map();
|
|
542
441
|
let closed = false;
|
|
@@ -563,19 +462,21 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
563
462
|
}
|
|
564
463
|
return client;
|
|
565
464
|
}
|
|
566
|
-
async function resolveRequestProxy(options, proxyAttempt) {
|
|
567
|
-
const rawProxyAttemptOffset = options?.proxyAttemptOffset ?? 0;
|
|
568
|
-
const proxyAttemptOffset = Number.isFinite(rawProxyAttemptOffset)
|
|
569
|
-
? Math.max(0, Math.floor(rawProxyAttemptOffset))
|
|
570
|
-
: 0;
|
|
465
|
+
async function resolveRequestProxy(options, proxyAttempt, refreshEpoch) {
|
|
571
466
|
const resolvedProxy = await resolveProxyConfigAsync({
|
|
572
467
|
proxy: options?.proxy ?? clientOptions.proxy,
|
|
573
468
|
upstream: clientOptions.upstream,
|
|
574
469
|
apifuseConfig: clientOptions.apifuseConfig,
|
|
575
470
|
affinityKey: clientOptions.affinityKey,
|
|
576
|
-
proxyAttempt:
|
|
577
|
-
|
|
578
|
-
: proxyAttemptOffset
|
|
471
|
+
proxyAttempt: computeProxyAttemptIndex({
|
|
472
|
+
baseProxyAttempt: clientOptions.proxyAttempt,
|
|
473
|
+
proxyAttemptOffset: options?.proxyAttemptOffset,
|
|
474
|
+
retryAttemptOffset: proxyAttempt,
|
|
475
|
+
}),
|
|
476
|
+
// The impit stealth transport tunnels both HTTP CONNECT and SOCKS5,
|
|
477
|
+
// preserving the client TLS fingerprint end-to-end.
|
|
478
|
+
transportProtocols: ["http", "socks5"],
|
|
479
|
+
...(refreshEpoch === undefined ? {} : { proxyRefreshEpoch: refreshEpoch }),
|
|
579
480
|
telemetry: clientOptions.telemetry,
|
|
580
481
|
});
|
|
581
482
|
if (resolvedProxy.shouldWarn && !hasWarnedMissingProxy) {
|
|
@@ -586,22 +487,51 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
586
487
|
url: resolvedProxy.url,
|
|
587
488
|
poolIndex: proxyPoolIndexFromDiagnostics(resolvedProxy.diagnostics),
|
|
588
489
|
proxyHash: proxyEndpointHash(resolvedProxy.url),
|
|
490
|
+
vendor: vendorFromResolvedSource(resolvedProxy.source),
|
|
589
491
|
};
|
|
590
492
|
}
|
|
591
|
-
|
|
493
|
+
const session = {
|
|
592
494
|
async fetch(url, options = {}) {
|
|
593
495
|
const method = normalizeMethod(options.method ?? "GET");
|
|
594
496
|
const hasExplicitRetryPolicy = options.retry !== undefined;
|
|
595
|
-
const stealthRetryOptions =
|
|
497
|
+
const stealthRetryOptions = normalizeProxyTransportRetryOptions(options.retry, {
|
|
498
|
+
extraErrorCodes: STEALTH_PROXY_TRANSPORT_RETRY_ERROR_CODES,
|
|
499
|
+
label: "Stealth",
|
|
500
|
+
}) ??
|
|
501
|
+
(hasExplicitRetryPolicy
|
|
502
|
+
? undefined
|
|
503
|
+
: createDefaultProxyTransportRetryOptions({
|
|
504
|
+
extraErrorCodes: STEALTH_PROXY_TRANSPORT_RETRY_ERROR_CODES,
|
|
505
|
+
label: "Stealth",
|
|
506
|
+
}));
|
|
507
|
+
if (stealthRetryOptions) {
|
|
508
|
+
validateUnsafeProxyTransportRetryMethods(stealthRetryOptions, "Stealth");
|
|
509
|
+
}
|
|
596
510
|
const hasPolicyProxy = isPolicyManagedProxy(clientOptions);
|
|
597
511
|
const usesPolicyAllocator = hasPolicyProxy && !options.proxy && !clientOptions.proxy;
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
512
|
+
const retryAttemptCap = Math.max(1, stealthRetryOptions?.attempts ?? 1);
|
|
513
|
+
// Span the whole vendor chain: successive attempts rotate one vendor's
|
|
514
|
+
// pool, then fail over to the next vendor via the flat attempt index.
|
|
515
|
+
const policyProxy = clientOptions.proxyPolicy ??
|
|
516
|
+
(typeof clientOptions.upstream?.proxy === "object"
|
|
517
|
+
? clientOptions.upstream.proxy
|
|
518
|
+
: undefined);
|
|
519
|
+
// The pool span is already bounded by each vendor's max pool size
|
|
520
|
+
// (smartproxy ≤20, nodemaven ≤50), so the configured span never exceeds
|
|
521
|
+
// the chain's true maximum — a large NodeMaven pool stays fully
|
|
522
|
+
// reachable rather than being truncated at an arbitrary ceiling.
|
|
523
|
+
const policyProxyAttemptCap = Math.max(1, policyProxy ? resolvePolicyProxyPoolSpan(policyProxy) : DEFAULT_SMARTPROXY_POOL_SIZE);
|
|
524
|
+
// A registry vendor chain (smartproxy/nodemaven) is the only policy whose
|
|
525
|
+
// successive attempts resolve a *different* endpoint, so it is the only one
|
|
526
|
+
// that may widen the attempt cap to the pool span, de-duplicate endpoints,
|
|
527
|
+
// and drive allocator stale-pool refresh. A static custom/decodo policy
|
|
528
|
+
// resolves the same URL every attempt: widening/refreshing it would resend
|
|
529
|
+
// the request dozens of times (up to maxAttempts × refreshes) and bypass
|
|
530
|
+
// retry:false and unsafe-method controls. Static policies therefore follow
|
|
531
|
+
// the ordinary transport-retry budget instead.
|
|
532
|
+
const rotatesRegistryChain = usesPolicyAllocator && policyResolvesRegistryVendorChain(policyProxy);
|
|
533
|
+
const maxAttempts = rotatesRegistryChain ? policyProxyAttemptCap : retryAttemptCap;
|
|
534
|
+
const dedupeAllocatorEndpoints = rotatesRegistryChain;
|
|
605
535
|
let lastError;
|
|
606
536
|
for (let refreshAttempt = 0; refreshAttempt <= MAX_POLICY_PROXY_POOL_REFRESHES; refreshAttempt += 1) {
|
|
607
537
|
let stalePoolError;
|
|
@@ -617,14 +547,12 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
617
547
|
return;
|
|
618
548
|
attemptRecorded = true;
|
|
619
549
|
clientOptions.telemetry?.recordProxyAttempt?.({
|
|
620
|
-
provider: "smartproxy",
|
|
550
|
+
provider: attemptProxy?.vendor ?? "smartproxy",
|
|
621
551
|
attempt: attempt + 1,
|
|
622
552
|
...(attemptProxy?.poolIndex === undefined
|
|
623
553
|
? {}
|
|
624
554
|
: { poolIndex: attemptProxy.poolIndex }),
|
|
625
|
-
...(attemptProxy?.proxyHash
|
|
626
|
-
? { proxyHash: attemptProxy.proxyHash }
|
|
627
|
-
: {}),
|
|
555
|
+
...(attemptProxy?.proxyHash ? { proxyHash: attemptProxy.proxyHash } : {}),
|
|
628
556
|
outcome,
|
|
629
557
|
...(errorCode ? { errorCode } : {}),
|
|
630
558
|
...(status === undefined ? {} : { status }),
|
|
@@ -633,18 +561,21 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
633
561
|
};
|
|
634
562
|
try {
|
|
635
563
|
assertNoUnsupportedFingerprintOverrides(options);
|
|
636
|
-
attemptProxy = await resolveRequestProxy(options, attempt);
|
|
564
|
+
attemptProxy = await resolveRequestProxy(options, attempt, refreshAttempt);
|
|
637
565
|
proxy = attemptProxy.url;
|
|
638
|
-
if (proxy) {
|
|
566
|
+
if (proxy && dedupeAllocatorEndpoints) {
|
|
567
|
+
// An under-filled allocation repeats endpoints (via the modulo
|
|
568
|
+
// pool mapping) before the flat offset crosses into the next
|
|
569
|
+
// vendor. Skip an already-tried endpoint and advance the offset
|
|
570
|
+
// rather than breaking — breaking here would strand the request on
|
|
571
|
+
// the primary vendor and never reach the fallback leg.
|
|
639
572
|
if (attemptedProxies.has(proxy)) {
|
|
640
|
-
|
|
573
|
+
continue;
|
|
641
574
|
}
|
|
642
575
|
attemptedProxies.add(proxy);
|
|
643
576
|
}
|
|
644
577
|
const ignoreTlsErrors = Boolean(options.stealth?.insecureSkipVerify ??
|
|
645
|
-
(!hasPolicyProxy &&
|
|
646
|
-
proxy &&
|
|
647
|
-
clientOptions.proxyStealth?.insecureSkipVerify));
|
|
578
|
+
(!hasPolicyProxy && proxy && clientOptions.proxyStealth?.insecureSkipVerify));
|
|
648
579
|
const profileName = options.profile ?? defaultProfile;
|
|
649
580
|
const requestUrl = appendQueryParams(resolveUrl(baseUrl, url), options.params);
|
|
650
581
|
const headers = { ...(options.headers ?? {}) };
|
|
@@ -656,25 +587,22 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
656
587
|
const requestInit = {
|
|
657
588
|
headers: normalizeHeaders(headers),
|
|
658
589
|
method,
|
|
590
|
+
...(options.redirect ? { redirect: options.redirect } : {}),
|
|
659
591
|
...(options.timeout ? { timeout: options.timeout } : {}),
|
|
660
592
|
};
|
|
661
593
|
if (options.body !== undefined) {
|
|
662
594
|
requestInit.body = normalizeBody(options.body);
|
|
663
595
|
}
|
|
664
596
|
const response = await getClient(profileName, proxy, ignoreTlsErrors).fetch(requestUrl, requestInit);
|
|
665
|
-
const normalized = await normalizeResponse(response);
|
|
597
|
+
const normalized = await normalizeResponse(response, requestUrl);
|
|
666
598
|
cookieJar.setFromCookieStrings(setCookieHeadersFromResponse(response.headers));
|
|
667
|
-
if (proxy &&
|
|
668
|
-
isProxyConnectFailureResponse(response, normalized.body)) {
|
|
599
|
+
if (proxy && isProxyConnectFailureResponse(response, normalized.body)) {
|
|
669
600
|
throw createProxyConnectFailureError(normalized.body);
|
|
670
601
|
}
|
|
671
602
|
if (response.status >= 400) {
|
|
672
603
|
if (proxy &&
|
|
673
604
|
usesPolicyAllocator &&
|
|
674
|
-
isProxyEdgeTlsRejectedResponse(response.status, [
|
|
675
|
-
JSON.stringify(responseHeadersToRecord(response.headers)),
|
|
676
|
-
normalized.body,
|
|
677
|
-
].join("\n"))) {
|
|
605
|
+
isProxyEdgeTlsRejectedResponse(response.status, [JSON.stringify(responseHeadersToRecord(response.headers)), normalized.body].join("\n"))) {
|
|
678
606
|
throw createProxyEdgeTlsRejectedError(response.status);
|
|
679
607
|
}
|
|
680
608
|
if (proxy && isProxyAuthIpDeniedMessage(normalized.body)) {
|
|
@@ -702,9 +630,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
702
630
|
const normalizedError = normalizeStealthTransportError(error);
|
|
703
631
|
recordProxyAttempt("error", proxyAttemptErrorCode(normalizedError), proxyAttemptStatus(normalizedError));
|
|
704
632
|
lastError = normalizedError;
|
|
705
|
-
if (proxy &&
|
|
706
|
-
usesPolicyAllocator &&
|
|
707
|
-
isProxyPoolRefreshableError(normalizedError)) {
|
|
633
|
+
if (proxy && rotatesRegistryChain && isProxyPoolRefreshableError(normalizedError)) {
|
|
708
634
|
stalePoolError = normalizedError;
|
|
709
635
|
if (shouldRunProxyAuthDiagnostic(normalizedError)) {
|
|
710
636
|
stalePoolDiagnosticProxy = proxy;
|
|
@@ -714,21 +640,43 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
714
640
|
}
|
|
715
641
|
break;
|
|
716
642
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
643
|
+
// Cap the number of transport retries. For a policy-allocator chain,
|
|
644
|
+
// every attempt resolves a *different* endpoint/vendor (poolIndex
|
|
645
|
+
// rotates across the concatenated vendor pool spans), so a transport
|
|
646
|
+
// failure is a signal to advance to the next endpoint — potentially
|
|
647
|
+
// crossing into the fallback vendor — not to retry the same endpoint.
|
|
648
|
+
// Truncating that rotation at the per-endpoint retry budget would
|
|
649
|
+
// strand the request on the primary vendor and never reach the
|
|
650
|
+
// fallback, since the crossover only happens once the flat attempt
|
|
651
|
+
// index exceeds the primary vendor's pool size (~10-20).
|
|
652
|
+
// resolvePolicyTransportAttemptCap widens to the full chain span only
|
|
653
|
+
// for implicit, safe-method allocator requests; explicit retry
|
|
654
|
+
// policies (their documented `attempts` ceiling), unsafe methods, and
|
|
655
|
+
// static/non-registry vendors keep the per-endpoint retry budget.
|
|
656
|
+
const transportRetryCap = resolvePolicyTransportAttemptCap({
|
|
657
|
+
policy: policyProxy,
|
|
658
|
+
usesPolicyAllocator,
|
|
659
|
+
retryAttempts: stealthRetryOptions?.attempts ?? 1,
|
|
660
|
+
explicitRetry: hasExplicitRetryPolicy,
|
|
661
|
+
method,
|
|
662
|
+
});
|
|
663
|
+
if (attempt + 1 < transportRetryCap &&
|
|
664
|
+
shouldRetryProxyTransportAttempt({
|
|
665
|
+
error: normalizedError,
|
|
666
|
+
explicitRetry: hasExplicitRetryPolicy,
|
|
667
|
+
method,
|
|
668
|
+
options: stealthRetryOptions,
|
|
669
|
+
proxyUsed: Boolean(proxy),
|
|
670
|
+
})) {
|
|
671
|
+
if (stealthRetryOptions) {
|
|
672
|
+
await sleep(computeProxyTransportRetryDelayMs(stealthRetryOptions, attempt + 1));
|
|
673
|
+
}
|
|
726
674
|
continue;
|
|
727
675
|
}
|
|
728
676
|
throw normalizedError;
|
|
729
677
|
}
|
|
730
678
|
}
|
|
731
|
-
if (
|
|
679
|
+
if (rotatesRegistryChain &&
|
|
732
680
|
stalePoolError &&
|
|
733
681
|
refreshAttempt < MAX_POLICY_PROXY_POOL_REFRESHES) {
|
|
734
682
|
await invalidateProxyResolutionCacheAsync({
|
|
@@ -758,11 +706,112 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
758
706
|
}
|
|
759
707
|
throw normalizeStealthTransportError(lastError);
|
|
760
708
|
},
|
|
709
|
+
cookies: cookieJar,
|
|
710
|
+
redirects: {
|
|
711
|
+
async run(options) {
|
|
712
|
+
const maxHops = options.maxHops === undefined || !Number.isFinite(options.maxHops)
|
|
713
|
+
? 10
|
|
714
|
+
: Math.max(0, Math.floor(options.maxHops));
|
|
715
|
+
const hops = [];
|
|
716
|
+
let currentUrl = resolveUrl(baseUrl, options.url);
|
|
717
|
+
let method = normalizeMethod(options.method ?? "GET");
|
|
718
|
+
let body = options.body;
|
|
719
|
+
let response;
|
|
720
|
+
const visitedRequests = new Set();
|
|
721
|
+
const { url: _url, maxHops: _maxHops, stopWhen, params, ...fetchOptions } = options;
|
|
722
|
+
for (let hopIndex = 0; hopIndex <= maxHops; hopIndex += 1) {
|
|
723
|
+
visitedRequests.add(`${method} ${currentUrl}`);
|
|
724
|
+
response = await session.fetch(currentUrl, {
|
|
725
|
+
...fetchOptions,
|
|
726
|
+
body,
|
|
727
|
+
method,
|
|
728
|
+
...(hopIndex === 0 && params ? { params } : {}),
|
|
729
|
+
redirect: "manual",
|
|
730
|
+
throwOnHttpError: false,
|
|
731
|
+
});
|
|
732
|
+
if (!isRedirectStatus(response.status)) {
|
|
733
|
+
return {
|
|
734
|
+
final: response,
|
|
735
|
+
hops,
|
|
736
|
+
reason: "completed",
|
|
737
|
+
cookies: cookieJar.snapshot(),
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
const location = locationHeader(response.headers);
|
|
741
|
+
const nextUrl = location
|
|
742
|
+
? new URL(location, response.url ?? currentUrl).toString()
|
|
743
|
+
: undefined;
|
|
744
|
+
const hop = {
|
|
745
|
+
url: response.url ?? currentUrl,
|
|
746
|
+
status: response.status,
|
|
747
|
+
method,
|
|
748
|
+
...(location ? { location } : {}),
|
|
749
|
+
...(nextUrl ? { nextUrl } : {}),
|
|
750
|
+
};
|
|
751
|
+
hops.push(hop);
|
|
752
|
+
if (stopWhen && (await stopWhen(hop))) {
|
|
753
|
+
return {
|
|
754
|
+
final: response,
|
|
755
|
+
hops,
|
|
756
|
+
reason: "stopped",
|
|
757
|
+
cookies: cookieJar.snapshot(),
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
if (!nextUrl) {
|
|
761
|
+
return {
|
|
762
|
+
final: response,
|
|
763
|
+
hops,
|
|
764
|
+
reason: "missing_location",
|
|
765
|
+
cookies: cookieJar.snapshot(),
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
if (hops.length > maxHops) {
|
|
769
|
+
return {
|
|
770
|
+
final: response,
|
|
771
|
+
hops,
|
|
772
|
+
reason: "max_hops",
|
|
773
|
+
cookies: cookieJar.snapshot(),
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
const nextMethod = nextRedirectMethod(response.status, method);
|
|
777
|
+
if (nextMethod !== method) {
|
|
778
|
+
body = undefined;
|
|
779
|
+
}
|
|
780
|
+
if (visitedRequests.has(`${nextMethod} ${nextUrl}`)) {
|
|
781
|
+
return {
|
|
782
|
+
final: response,
|
|
783
|
+
hops,
|
|
784
|
+
reason: "loop",
|
|
785
|
+
cookies: cookieJar.snapshot(),
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
method = nextMethod;
|
|
789
|
+
currentUrl = nextUrl;
|
|
790
|
+
}
|
|
791
|
+
if (!response) {
|
|
792
|
+
response = await session.fetch(currentUrl, {
|
|
793
|
+
...fetchOptions,
|
|
794
|
+
body,
|
|
795
|
+
method,
|
|
796
|
+
...(params ? { params } : {}),
|
|
797
|
+
redirect: "manual",
|
|
798
|
+
throwOnHttpError: false,
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
return {
|
|
802
|
+
final: response,
|
|
803
|
+
hops,
|
|
804
|
+
reason: "max_hops",
|
|
805
|
+
cookies: cookieJar.snapshot(),
|
|
806
|
+
};
|
|
807
|
+
},
|
|
808
|
+
},
|
|
761
809
|
close() {
|
|
762
810
|
closed = true;
|
|
763
811
|
clients.clear();
|
|
764
812
|
},
|
|
765
813
|
};
|
|
814
|
+
return session;
|
|
766
815
|
async function classifyProxyAuthDiagnostic(profileName, proxy) {
|
|
767
816
|
try {
|
|
768
817
|
const response = await getClient(profileName, proxy, false).fetch(PROXY_AUTH_DIAGNOSTIC_URL, {
|
|
@@ -774,10 +823,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
774
823
|
}
|
|
775
824
|
catch (error) {
|
|
776
825
|
const message = error instanceof Error
|
|
777
|
-
? [
|
|
778
|
-
error.message,
|
|
779
|
-
error.cause instanceof Error ? error.cause.message : "",
|
|
780
|
-
]
|
|
826
|
+
? [error.message, error.cause instanceof Error ? error.cause.message : ""]
|
|
781
827
|
.filter(Boolean)
|
|
782
828
|
.join(" ")
|
|
783
829
|
: String(error);
|
|
@@ -805,12 +851,8 @@ function hasHeader(headers, name) {
|
|
|
805
851
|
return Object.keys(headers).some((key) => key.toLowerCase() === needle);
|
|
806
852
|
}
|
|
807
853
|
export function createStealthClient(baseUrl, defaultProfileOrOptions = DEFAULT_PROFILE, clientOptions = {}) {
|
|
808
|
-
const defaultProfile = typeof defaultProfileOrOptions === "string"
|
|
809
|
-
|
|
810
|
-
: DEFAULT_PROFILE;
|
|
811
|
-
const resolvedClientOptions = typeof defaultProfileOrOptions === "string"
|
|
812
|
-
? clientOptions
|
|
813
|
-
: defaultProfileOrOptions;
|
|
854
|
+
const defaultProfile = typeof defaultProfileOrOptions === "string" ? defaultProfileOrOptions : DEFAULT_PROFILE;
|
|
855
|
+
const resolvedClientOptions = typeof defaultProfileOrOptions === "string" ? clientOptions : defaultProfileOrOptions;
|
|
814
856
|
let sharedSession = null;
|
|
815
857
|
function getSharedSession() {
|
|
816
858
|
if (!sharedSession) {
|
package/dist/runtime/stt.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ProviderSttConfig, SttContext, SttTranscribeRequest, SttVerificationCodeOptions, VerificationCodeExtractionResult } from "../types";
|
|
1
|
+
import type { ProviderSttConfig, SttContext, SttTranscribeRequest, SttVerificationCodeOptions, VerificationCodeExtractionResult } from "../types.js";
|
|
2
2
|
export declare const APIFUSE__STT__BACKEND_ENV = "APIFUSE__STT__BACKEND";
|
|
3
3
|
export declare const APIFUSE__STT__MODEL_ENV = "APIFUSE__STT__MODEL";
|
|
4
4
|
export declare const CLOUDFLARE_ACCOUNT_ID_ENV = "APIFUSE__CLOUDFLARE__ACCOUNT_ID";
|