@apifuse/provider-sdk 2.2.0-beta.3 → 2.2.0-beta.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTHORING.md +493 -5
- package/CHANGELOG.md +131 -1
- package/README.md +52 -6
- package/SUBMISSION.md +1 -1
- package/bin/apifuse-check.ts +106 -62
- package/bin/apifuse-create.ts +1 -1
- package/bin/apifuse-dev.ts +63 -55
- package/bin/apifuse-pack-check.ts +22 -2
- package/bin/apifuse-pack-smoke.ts +78 -82
- package/bin/apifuse-pack-types.ts +583 -0
- package/bin/apifuse-perf.ts +59 -140
- package/bin/apifuse-record.ts +698 -113
- package/bin/apifuse-submit-check.ts +517 -44
- package/bin/apifuse-sync-assets.ts +117 -0
- package/bin/apifuse.ts +1 -1
- package/bin/submit-check-delimited-text.ts +50 -0
- package/bin/submit-check-xml.ts +1 -1
- package/dist/auth-turn/index.d.ts +4 -4
- package/dist/auth-turn/index.js +1 -1
- package/dist/auth.d.ts +16 -2
- package/dist/auth.js +76 -18
- package/dist/ceremonies/index.d.ts +9 -1
- package/dist/ceremonies/index.js +117 -29
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +8 -0
- package/dist/cli/create.d.ts +3 -0
- package/dist/cli/create.js +34 -35
- package/dist/cli/prompt-assets.d.ts +80 -0
- package/dist/cli/prompt-assets.js +743 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/dist/cli/templates/provider/README.md.tpl +4 -4
- package/dist/config/loader.d.ts +176 -17
- package/dist/config/loader.js +434 -161
- package/dist/contract-serialization.d.ts +2 -2
- package/dist/contract-serialization.js +7 -14
- package/dist/contract-types.d.ts +3 -2
- package/dist/contract.d.ts +3 -3
- package/dist/contract.js +6 -6
- package/dist/declaration-validation.d.ts +23 -0
- package/dist/declaration-validation.js +159 -0
- package/dist/define.d.ts +13 -1
- package/dist/define.js +391 -122
- package/dist/dev.d.ts +1 -1
- package/dist/dev.js +1 -1
- package/dist/error-resolution.d.ts +4 -0
- package/dist/error-resolution.js +123 -0
- package/dist/errors.d.ts +19 -1
- package/dist/errors.js +41 -3
- package/dist/fixture-sanitization.d.ts +26 -0
- package/dist/fixture-sanitization.js +216 -0
- package/dist/i18n/catalog.d.ts +2 -2
- package/dist/i18n/catalog.js +4 -10
- package/dist/i18n/index.d.ts +2 -2
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/keys.d.ts +2 -2
- package/dist/index.d.ts +50 -42
- package/dist/index.js +41 -37
- package/dist/lint.d.ts +6 -1
- package/dist/lint.js +370 -18
- package/dist/native-address.d.ts +43 -0
- package/dist/native-address.js +281 -0
- package/dist/native-egress-policy.d.ts +31 -0
- package/dist/native-egress-policy.js +288 -0
- package/dist/observability.d.ts +5 -2
- package/dist/observability.js +48 -1
- package/dist/provider.d.ts +13 -11
- package/dist/provider.js +10 -9
- package/dist/public-schema-field-lint.d.ts +1 -1
- package/dist/recipes/gov-api.js +1 -1
- package/dist/runtime/auth-flow.d.ts +3 -1
- package/dist/runtime/auth-flow.js +8 -3
- package/dist/runtime/browser.d.ts +1 -1
- package/dist/runtime/browser.js +138 -40
- package/dist/runtime/cache.d.ts +2 -1
- package/dist/runtime/cache.js +173 -23
- package/dist/runtime/choice-wordlist.d.ts +9 -0
- package/dist/runtime/choice-wordlist.js +138 -0
- package/dist/runtime/choice.d.ts +14 -1
- package/dist/runtime/choice.js +566 -101
- package/dist/runtime/credential.d.ts +1 -1
- package/dist/runtime/credential.js +1 -1
- package/dist/runtime/env.d.ts +1 -1
- package/dist/runtime/executor.d.ts +1 -1
- package/dist/runtime/executor.js +25 -3
- package/dist/runtime/http.d.ts +3 -2
- package/dist/runtime/http.js +517 -55
- package/dist/runtime/insights.d.ts +1 -1
- package/dist/runtime/insights.js +6 -13
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +371 -23
- package/dist/runtime/keyring.js +1 -1
- package/dist/runtime/namespace.js +1 -1
- package/dist/runtime/native-network-errors.d.ts +33 -0
- package/dist/runtime/native-network-errors.js +69 -0
- package/dist/runtime/native-network.d.ts +96 -0
- package/dist/runtime/native-network.js +1232 -0
- package/dist/runtime/ocr.d.ts +29 -0
- package/dist/runtime/ocr.js +440 -0
- package/dist/runtime/otlp.d.ts +1 -1
- package/dist/runtime/perf.d.ts +1 -1
- package/dist/runtime/provider.d.ts +1 -1
- package/dist/runtime/provider.js +1 -2
- package/dist/runtime/proxy-errors.d.ts +1 -1
- package/dist/runtime/proxy-errors.js +9 -7
- package/dist/runtime/proxy-nodemaven.d.ts +56 -0
- package/dist/runtime/proxy-nodemaven.js +146 -0
- package/dist/runtime/proxy-retry-policy.d.ts +2 -2
- package/dist/runtime/proxy-retry-policy.js +2 -2
- package/dist/runtime/proxy-telemetry.d.ts +2 -1
- package/dist/runtime/proxy-telemetry.js +58 -52
- package/dist/runtime/redirects.d.ts +29 -0
- package/dist/runtime/redirects.js +36 -0
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +5 -5
- package/dist/runtime/request-options.d.ts +68 -1
- package/dist/runtime/request-options.js +548 -0
- package/dist/runtime/resolver-config.d.ts +6 -0
- package/dist/runtime/resolver-config.js +6 -0
- package/dist/runtime/resolver-public.d.ts +1 -0
- package/dist/runtime/resolver-public.js +1 -0
- package/dist/runtime/resolver-shared.d.ts +3 -0
- package/dist/runtime/resolver-shared.js +12 -0
- package/dist/runtime/resolver-vendors/bindings.d.ts +48 -0
- package/dist/runtime/resolver-vendors/bindings.js +40 -0
- package/dist/runtime/resolver-vendors/browser.d.ts +20 -0
- package/dist/runtime/resolver-vendors/browser.js +282 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
- package/dist/runtime/resolver-vendors/hosts.js +33 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
- package/dist/runtime/resolver-vendors/twocaptcha.js +368 -0
- package/dist/runtime/resolver-vendors/types.d.ts +83 -0
- package/dist/runtime/resolver-vendors/types.js +69 -0
- package/dist/runtime/resolver.d.ts +59 -0
- package/dist/runtime/resolver.js +705 -0
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +5 -2
- package/dist/runtime/state.js +280 -74
- package/dist/runtime/stealth-cookies.d.ts +20 -0
- package/dist/runtime/stealth-cookies.js +111 -0
- package/dist/runtime/stealth.d.ts +30 -5
- package/dist/runtime/stealth.js +523 -259
- package/dist/runtime/stt.d.ts +1 -1
- package/dist/runtime/stt.js +12 -27
- package/dist/runtime/timeout.d.ts +5 -0
- package/dist/runtime/timeout.js +12 -0
- package/dist/runtime/trace.d.ts +2 -2
- package/dist/runtime/trace.js +2 -4
- package/dist/runtime/waterfall.d.ts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +7 -15
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/index.d.ts +7 -7
- package/dist/server/index.js +6 -6
- package/dist/server/self-test-input-tokens.d.ts +2 -1
- package/dist/server/self-test-input-tokens.js +18 -14
- package/dist/server/self-test-redaction.d.ts +1 -1
- package/dist/server/self-test-redaction.js +1 -1
- package/dist/server/self-test.d.ts +117 -3
- package/dist/server/self-test.js +787 -151
- package/dist/server/serve-implementation.d.ts +210 -0
- package/dist/server/serve-implementation.js +2078 -0
- package/dist/server/serve.d.ts +1 -70
- package/dist/server/serve.js +1 -1143
- package/dist/server/types.d.ts +34 -9
- package/dist/server/types.js +8 -1
- package/dist/stateful/errors.d.ts +19 -0
- package/dist/stateful/errors.js +24 -0
- package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
- package/dist/stateful/http-provider-event-emitter.js +237 -0
- package/dist/stateful/http-session-owner-registry.d.ts +44 -0
- package/dist/stateful/http-session-owner-registry.js +210 -0
- package/dist/stateful/index.d.ts +18 -0
- package/dist/stateful/index.js +18 -0
- package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
- package/dist/stateful/provider-event-delivery-failures.js +43 -0
- package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
- package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
- package/dist/stateful/provider-event-pipeline.d.ts +50 -0
- package/dist/stateful/provider-event-pipeline.js +1 -0
- package/dist/stateful/provider-events.d.ts +101 -0
- package/dist/stateful/provider-events.js +289 -0
- package/dist/stateful/session-key.d.ts +15 -0
- package/dist/stateful/session-key.js +86 -0
- package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
- package/dist/stateful/stateful-provider-adapter-context.js +42 -0
- package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
- package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
- package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
- package/dist/stateful/stateful-provider-adapter.js +287 -0
- package/dist/stateful/stateful-provider-observability.d.ts +62 -0
- package/dist/stateful/stateful-provider-observability.js +161 -0
- package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +215 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
- package/dist/stateful/stateful-provider-runtime-context.js +60 -0
- package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
- package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
- package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
- package/dist/stateful/stateful-provider-session-routing.js +345 -0
- package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
- package/dist/stateful/stateful-provider-session-runtime.js +245 -0
- package/dist/stateful-signing.d.ts +18 -0
- package/dist/stateful-signing.js +27 -0
- package/dist/stealth/profiles.d.ts +1 -1
- package/dist/stealth/profiles.js +21 -21
- package/dist/stream-evidence.d.ts +74 -0
- package/dist/stream-evidence.js +785 -0
- package/dist/stream.d.ts +1 -1
- package/dist/stream.js +7 -1
- package/dist/testing/index.d.ts +3 -2
- package/dist/testing/index.js +3 -2
- package/dist/testing/run.d.ts +32 -2
- package/dist/testing/run.js +488 -28
- package/dist/types.d.ts +566 -19
- package/dist/types.js +1 -0
- package/dist/user-input.d.ts +30 -0
- package/dist/user-input.js +66 -0
- package/package.json +42 -7
- package/src/auth-turn/index.ts +2 -2
- package/src/auth.ts +146 -86
- package/src/ceremonies/index.ts +167 -92
- package/src/cli/commands.ts +10 -0
- package/src/cli/create.ts +42 -35
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/src/cli/templates/provider/README.md.tpl +4 -4
- package/src/config/loader.ts +667 -289
- package/src/contract-serialization.ts +10 -18
- package/src/contract-types.ts +3 -2
- package/src/contract.ts +14 -28
- package/src/declaration-validation.ts +202 -0
- package/src/define.ts +631 -495
- package/src/dev.ts +4 -9
- package/src/error-resolution.ts +128 -0
- package/src/errors.ts +56 -11
- package/src/fixture-sanitization.ts +247 -0
- package/src/i18n/catalog.ts +10 -32
- package/src/i18n/index.ts +2 -2
- package/src/i18n/keys.ts +5 -11
- package/src/index.ts +158 -44
- package/src/lint.ts +488 -154
- package/src/native-address.ts +340 -0
- package/src/native-egress-policy.ts +358 -0
- package/src/observability.ts +51 -1
- package/src/provider.ts +66 -11
- package/src/public-schema-field-lint.ts +7 -33
- package/src/recipes/gov-api.ts +2 -5
- package/src/runtime/auth-flow.ts +13 -7
- package/src/runtime/browser.ts +252 -207
- package/src/runtime/cache.ts +209 -81
- package/src/runtime/choice-wordlist.ts +145 -0
- package/src/runtime/choice.ts +758 -197
- package/src/runtime/credential.ts +2 -2
- package/src/runtime/env.ts +1 -1
- package/src/runtime/executor.ts +37 -19
- package/src/runtime/http.ts +645 -65
- package/src/runtime/insights.ts +15 -53
- package/src/runtime/instrumentation.ts +530 -67
- package/src/runtime/keyring.ts +7 -19
- package/src/runtime/namespace.ts +2 -7
- package/src/runtime/native-network-errors.ts +99 -0
- package/src/runtime/native-network.ts +1605 -0
- package/src/runtime/ocr.ts +523 -0
- package/src/runtime/otlp.ts +12 -23
- package/src/runtime/perf.ts +1 -1
- package/src/runtime/provider.ts +4 -9
- package/src/runtime/proxy-errors.ts +29 -42
- package/src/runtime/proxy-nodemaven.ts +221 -0
- package/src/runtime/proxy-retry-policy.ts +3 -3
- package/src/runtime/proxy-telemetry.ts +84 -77
- package/src/runtime/redirects.ts +66 -0
- package/src/runtime/redis.ts +10 -13
- package/src/runtime/request-options.ts +679 -9
- package/src/runtime/resolver-config.ts +6 -0
- package/src/runtime/resolver-public.ts +18 -0
- package/src/runtime/resolver-shared.ts +17 -0
- package/src/runtime/resolver-vendors/bindings.ts +56 -0
- package/src/runtime/resolver-vendors/browser.ts +408 -0
- package/src/runtime/resolver-vendors/hosts.ts +38 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +500 -0
- package/src/runtime/resolver-vendors/types.ts +173 -0
- package/src/runtime/resolver.ts +1060 -0
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +399 -161
- package/src/runtime/stealth-cookies.ts +132 -0
- package/src/runtime/stealth.ts +681 -295
- package/src/runtime/stt.ts +39 -113
- package/src/runtime/timeout.ts +18 -0
- package/src/runtime/trace.ts +14 -44
- package/src/runtime/waterfall.ts +5 -18
- package/src/schema.ts +23 -84
- package/src/serve.ts +6 -1
- package/src/server/index.ts +30 -7
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/self-test-redaction.ts +2 -2
- package/src/server/self-test.ts +1030 -180
- package/src/server/serve-implementation.ts +3062 -0
- package/src/server/serve.ts +1 -1781
- package/src/server/types.ts +12 -13
- package/src/stateful/README.md +146 -0
- package/src/stateful/errors.ts +35 -0
- package/src/stateful/http-provider-event-emitter.ts +314 -0
- package/src/stateful/http-session-owner-registry.ts +306 -0
- package/src/stateful/index.ts +18 -0
- package/src/stateful/provider-event-delivery-failures.ts +80 -0
- package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
- package/src/stateful/provider-event-pipeline.ts +61 -0
- package/src/stateful/provider-events.ts +462 -0
- package/src/stateful/session-key.ts +111 -0
- package/src/stateful/stateful-provider-adapter-context.ts +59 -0
- package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
- package/src/stateful/stateful-provider-adapter.ts +562 -0
- package/src/stateful/stateful-provider-observability.ts +261 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
- package/src/stateful/stateful-provider-runtime-context.ts +92 -0
- package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
- package/src/stateful/stateful-provider-session-routing.ts +546 -0
- package/src/stateful/stateful-provider-session-runtime.ts +403 -0
- package/src/stateful-signing.ts +46 -0
- package/src/stealth/profiles.ts +27 -33
- package/src/stream-evidence.ts +988 -0
- package/src/stream.ts +16 -20
- package/src/testing/index.ts +11 -2
- package/src/testing/run.ts +668 -74
- package/src/types.ts +665 -35
- package/src/user-input.ts +118 -0
- package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
- package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
- /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
package/dist/config/loader.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
3
4
|
import path from "node:path";
|
|
4
|
-
import
|
|
5
|
+
import { NODEMAVEN_DEFAULT_PROTOCOL, NODEMAVEN_FILTER_ENV, NODEMAVEN_MAX_POOL_SIZE, NODEMAVEN_PASSWORD_ENV, NODEMAVEN_USERNAME_ENV, hasNodemavenCredentials, nodemavenPoolSize, synthesizeNodemavenProxy, } from "../runtime/proxy-nodemaven.js";
|
|
6
|
+
// "smartproxy" here is api.smartproxy.org — a residential proxy with an IP
|
|
7
|
+
// extraction API (app_key → raw ip:port pool). It is NOT the company formerly
|
|
8
|
+
// named Smartproxy (smartproxy.com), which rebranded to Decodo in 2025 and is
|
|
9
|
+
// modelled separately as the `decodo` gateway vendor. Do not conflate them.
|
|
5
10
|
export const SMARTPROXY_APP_KEY_ENV = "APIFUSE__PROXY__SMARTPROXY_APP_KEY";
|
|
6
11
|
export const SMARTPROXY_MAX_LIFETIME_MINUTES = 2000;
|
|
7
12
|
export const DEFAULT_SMARTPROXY_POOL_SIZE = 20;
|
|
@@ -15,14 +20,21 @@ export const REDIS_URL_ENV = "APIFUSE__REDIS__URL";
|
|
|
15
20
|
export class ProxyResolutionError extends Error {
|
|
16
21
|
code;
|
|
17
22
|
telemetry;
|
|
23
|
+
vendor;
|
|
24
|
+
vendorChain;
|
|
25
|
+
protocol;
|
|
18
26
|
constructor(code, message, options) {
|
|
19
27
|
super(message, options);
|
|
20
28
|
this.name = "ProxyResolutionError";
|
|
21
29
|
this.code = code;
|
|
22
30
|
this.telemetry = options?.telemetry;
|
|
31
|
+
this.vendor = options?.vendor;
|
|
32
|
+
this.vendorChain = options?.vendorChain;
|
|
33
|
+
this.protocol = options?.protocol;
|
|
23
34
|
}
|
|
24
35
|
}
|
|
25
36
|
const proxyCache = new Map();
|
|
37
|
+
const require = createRequire(import.meta.url);
|
|
26
38
|
const proxyInflight = new Map();
|
|
27
39
|
const invalidatedProxyKeys = new Map();
|
|
28
40
|
const redisClients = new Map();
|
|
@@ -73,7 +85,8 @@ function getProxyRedis() {
|
|
|
73
85
|
const existing = redisClients.get(redisUrl);
|
|
74
86
|
if (existing)
|
|
75
87
|
return existing;
|
|
76
|
-
const
|
|
88
|
+
const { Redis: RedisClient } = require("ioredis");
|
|
89
|
+
const redis = new RedisClient(redisUrl, {
|
|
77
90
|
connectTimeout: REDIS_TIMEOUT_MS,
|
|
78
91
|
enableOfflineQueue: false,
|
|
79
92
|
lazyConnect: true,
|
|
@@ -160,9 +173,7 @@ function toProxyDiagnostics(value) {
|
|
|
160
173
|
return undefined;
|
|
161
174
|
const diagnostics = {};
|
|
162
175
|
for (const [key, item] of Object.entries(value)) {
|
|
163
|
-
if (typeof item === "string" ||
|
|
164
|
-
typeof item === "number" ||
|
|
165
|
-
typeof item === "boolean") {
|
|
176
|
+
if (typeof item === "string" || typeof item === "number" || typeof item === "boolean") {
|
|
166
177
|
diagnostics[key] = item;
|
|
167
178
|
}
|
|
168
179
|
}
|
|
@@ -212,63 +223,7 @@ function serializeSmartproxyPool(pool) {
|
|
|
212
223
|
}
|
|
213
224
|
function normalizeProxyUrl(url) {
|
|
214
225
|
const normalized = url?.trim();
|
|
215
|
-
return normalized
|
|
216
|
-
}
|
|
217
|
-
function readPositiveIntegerEnv(name) {
|
|
218
|
-
const raw = process.env[name]?.trim();
|
|
219
|
-
if (!raw)
|
|
220
|
-
return undefined;
|
|
221
|
-
if (!/^[1-9]\d*$/.test(raw)) {
|
|
222
|
-
throw new Error(`${name} must be a positive integer`);
|
|
223
|
-
}
|
|
224
|
-
return raw;
|
|
225
|
-
}
|
|
226
|
-
function applyStickyProxySession(proxyUrl) {
|
|
227
|
-
let parsed;
|
|
228
|
-
try {
|
|
229
|
-
parsed = new URL(proxyUrl);
|
|
230
|
-
}
|
|
231
|
-
catch {
|
|
232
|
-
return proxyUrl;
|
|
233
|
-
}
|
|
234
|
-
if (!parsed.hostname || !parsed.username || !parsed.password) {
|
|
235
|
-
return proxyUrl;
|
|
236
|
-
}
|
|
237
|
-
const host = parsed.hostname.toLowerCase();
|
|
238
|
-
if (!host.includes("smartproxy") && !host.includes("decodo")) {
|
|
239
|
-
return proxyUrl;
|
|
240
|
-
}
|
|
241
|
-
const username = decodeURIComponent(parsed.username);
|
|
242
|
-
const sessionId = process.env.APIFUSE__PROXY__SESSION_ID?.trim() || "apifuse-shared";
|
|
243
|
-
const sessionDuration = readPositiveIntegerEnv("APIFUSE__PROXY__SESSION_DURATION");
|
|
244
|
-
const stickyUsername = host.includes("smartproxy")
|
|
245
|
-
? buildSmartproxyUsername(username, sessionId, sessionDuration)
|
|
246
|
-
: buildDecodoUsername(username, sessionId, sessionDuration ?? "60");
|
|
247
|
-
parsed.username = stickyUsername;
|
|
248
|
-
return parsed.toString();
|
|
249
|
-
}
|
|
250
|
-
function buildSmartproxyUsername(username, sessionId, sessionDuration) {
|
|
251
|
-
const parts = username.split("_");
|
|
252
|
-
const configuredLife = parts
|
|
253
|
-
.find((part) => part.startsWith("life-"))
|
|
254
|
-
?.slice("life-".length);
|
|
255
|
-
const baseUsername = parts
|
|
256
|
-
.filter((part) => !part.startsWith("session-") && !part.startsWith("life-"))
|
|
257
|
-
.join("_");
|
|
258
|
-
return `${baseUsername}_session-${sessionId}_life-${sessionDuration ?? configuredLife ?? "60"}`;
|
|
259
|
-
}
|
|
260
|
-
function buildDecodoUsername(username, sessionId, sessionDuration) {
|
|
261
|
-
const withoutSticky = username.replace(/-session-.+-sessionduration-\d+$/, "");
|
|
262
|
-
const baseUsername = withoutSticky.startsWith("user-")
|
|
263
|
-
? withoutSticky
|
|
264
|
-
: `user-${withoutSticky}`;
|
|
265
|
-
return `${baseUsername}-session-${sessionId}-sessionduration-${sessionDuration}`;
|
|
266
|
-
}
|
|
267
|
-
function syncProxyEnv(config) {
|
|
268
|
-
const configProxyUrl = normalizeProxyUrl(config.proxy?.url);
|
|
269
|
-
if (!process.env.APIFUSE__PROXY__URL && configProxyUrl) {
|
|
270
|
-
process.env.APIFUSE__PROXY__URL = configProxyUrl;
|
|
271
|
-
}
|
|
226
|
+
return normalized || undefined;
|
|
272
227
|
}
|
|
273
228
|
export function resolveProxyConfig(options = {}) {
|
|
274
229
|
const explicitProxyUrl = normalizeProxyUrl(options.proxy);
|
|
@@ -283,14 +238,6 @@ export function resolveProxyConfig(options = {}) {
|
|
|
283
238
|
if (!legacyProxyRequested) {
|
|
284
239
|
return { shouldWarn: false };
|
|
285
240
|
}
|
|
286
|
-
const envProxyUrl = normalizeProxyUrl(process.env.APIFUSE__PROXY__URL);
|
|
287
|
-
if (envProxyUrl) {
|
|
288
|
-
return { shouldWarn: false, url: envProxyUrl };
|
|
289
|
-
}
|
|
290
|
-
const configuredProxyUrl = normalizeProxyUrl(options.apifuseConfig?.proxy?.url);
|
|
291
|
-
if (configuredProxyUrl) {
|
|
292
|
-
return { shouldWarn: false, url: configuredProxyUrl };
|
|
293
|
-
}
|
|
294
241
|
return { shouldWarn: true };
|
|
295
242
|
}
|
|
296
243
|
export async function resolveProxyConfigAsync(options = {}) {
|
|
@@ -305,47 +252,211 @@ export async function resolveProxyConfigAsync(options = {}) {
|
|
|
305
252
|
if (policy.mode === "disabled") {
|
|
306
253
|
return { shouldWarn: false };
|
|
307
254
|
}
|
|
308
|
-
const
|
|
309
|
-
if (
|
|
255
|
+
const chain = resolveVendorChain(policy);
|
|
256
|
+
if (chain.length === 0) {
|
|
257
|
+
const declared = declaredVendorChain(policy);
|
|
258
|
+
const deprecated = declared.filter((vendor) => vendor === "decodo" || vendor === "custom");
|
|
259
|
+
if (policy.mode === "required") {
|
|
260
|
+
const providerIds = declared.length > 0 ? declared.map((vendor) => `"${vendor}"`).join(", ") : "none";
|
|
261
|
+
const deprecatedDetail = deprecated.length > 0
|
|
262
|
+
? ` Deprecated vendor(s): ${deprecated.map((vendor) => `"${vendor}"`).join(", ")}.`
|
|
263
|
+
: "";
|
|
264
|
+
throw new ProxyResolutionError("PROXY_REQUIRED", `Required proxy policy has no SDK-managed adapter for provider id(s): ${providerIds}.${deprecatedDetail} Use "smartproxy" or "nodemaven".`);
|
|
265
|
+
}
|
|
266
|
+
// Deprecated decodo/custom providers have no SDK-managed adapter. Optional
|
|
267
|
+
// policies preserve the warning-only behavior and may continue directly.
|
|
310
268
|
return resolveProxyConfig({
|
|
311
269
|
...options,
|
|
312
270
|
upstream: { proxy: true },
|
|
313
271
|
});
|
|
314
272
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
273
|
+
// Protocol is chosen per vendor (each vendor's benchmarked-best), with an
|
|
274
|
+
// optional explicit override for the harness/tests. Both are tunnelling
|
|
275
|
+
// schemes. transportProtocols is what the calling transport can actually use.
|
|
276
|
+
const transportProtocols = options.transportProtocols ?? ["http", "socks5"];
|
|
277
|
+
const sizes = chain.map((vendor) => vendorPoolSize(vendor, policy));
|
|
278
|
+
const total = sizes.reduce((sum, size) => sum + size, 0);
|
|
279
|
+
const normalizedAttempt = normalizeAttemptIndex(options.proxyAttempt);
|
|
280
|
+
const { vendorIndex: startVendorIndex, poolIndex: startPoolIndex } = mapFlatAttempt(total > 0 ? normalizedAttempt % total : 0, sizes);
|
|
281
|
+
const refreshEpoch = normalizeAttemptIndex(options.proxyRefreshEpoch);
|
|
282
|
+
let lastError;
|
|
283
|
+
let blockedProtocol;
|
|
284
|
+
for (let vendorIndex = startVendorIndex; vendorIndex < chain.length; vendorIndex++) {
|
|
285
|
+
const vendor = chain[vendorIndex];
|
|
286
|
+
const nextVendor = chain[vendorIndex + 1];
|
|
287
|
+
const poolIndex = vendorIndex === startVendorIndex ? startPoolIndex : 0;
|
|
288
|
+
const protocol = options.protocol ?? VENDOR_DEFAULT_PROTOCOL[vendor];
|
|
289
|
+
if (!vendorHasCredentials(vendor)) {
|
|
290
|
+
options.telemetry?.recordProxyVendorFailover?.({
|
|
291
|
+
vendor,
|
|
292
|
+
nextVendor,
|
|
293
|
+
phase: "resolution",
|
|
294
|
+
reason: "no_credentials",
|
|
295
|
+
});
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
// The calling transport must be able to use this vendor's protocol; if not,
|
|
299
|
+
// fail over to the next vendor rather than silently downgrading.
|
|
300
|
+
if (!transportProtocols.includes(protocol)) {
|
|
301
|
+
blockedProtocol = protocol;
|
|
302
|
+
options.telemetry?.recordProxyVendorFailover?.({
|
|
303
|
+
vendor,
|
|
304
|
+
nextVendor,
|
|
305
|
+
phase: "resolution",
|
|
306
|
+
reason: "protocol_unsupported",
|
|
307
|
+
});
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
return await resolveWithVendor(vendor, policy, options, {
|
|
312
|
+
protocol,
|
|
313
|
+
poolIndex,
|
|
314
|
+
refreshEpoch,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
// Config/programming errors (invalid filter, etc.) are not vendor
|
|
319
|
+
// outages — propagate them rather than failing over.
|
|
320
|
+
if (!(error instanceof ProxyResolutionError)) {
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
if (error.telemetry) {
|
|
324
|
+
options.telemetry?.recordProxyResolution(error.telemetry);
|
|
325
|
+
}
|
|
326
|
+
lastError = error;
|
|
327
|
+
options.telemetry?.recordProxyVendorFailover?.({
|
|
328
|
+
vendor,
|
|
329
|
+
nextVendor,
|
|
330
|
+
phase: "resolution",
|
|
331
|
+
reason: "allocation_failed",
|
|
332
|
+
});
|
|
319
333
|
}
|
|
320
|
-
return { shouldWarn: true };
|
|
321
334
|
}
|
|
322
|
-
|
|
335
|
+
if (policy.mode === "required") {
|
|
336
|
+
if (lastError) {
|
|
337
|
+
throw lastError instanceof ProxyResolutionError
|
|
338
|
+
? lastError
|
|
339
|
+
: new ProxyResolutionError("PROXY_ALLOCATION_FAILED", `All proxy vendors [${chain.join(", ")}] failed for required proxy egress.`, { cause: lastError, vendorChain: chain });
|
|
340
|
+
}
|
|
341
|
+
if (blockedProtocol) {
|
|
342
|
+
throw new ProxyResolutionError("PROXY_PROTOCOL_UNSUPPORTED", `No proxy vendor in [${chain.join(", ")}] could serve a protocol supported by this transport (supports: ${transportProtocols.join(", ")}; vendor wanted "${blockedProtocol}"). Route this provider through the stealth transport.`, { protocol: blockedProtocol, vendorChain: chain });
|
|
343
|
+
}
|
|
344
|
+
throw new ProxyResolutionError("PROXY_REQUIRED", `Proxy egress is required but no vendor credentials are configured. Missing: ${chain
|
|
345
|
+
.map((vendor) => `${missingCredentialEnv(vendor)} (${vendor})`)
|
|
346
|
+
.join(", ")}.`, { vendorChain: chain });
|
|
347
|
+
}
|
|
348
|
+
return { shouldWarn: true };
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Resolve the proxy URL for a provider-owned consumer such as a CAPTCHA solver.
|
|
352
|
+
* Vendor allocation and failover remain owned by the SDK.
|
|
353
|
+
*/
|
|
354
|
+
export async function resolveProxy(options = {}) {
|
|
355
|
+
const resolved = await resolveProxyConfigAsync(options);
|
|
356
|
+
const vendor = vendorFromResolvedSource(resolved.source);
|
|
357
|
+
return vendor ? { ...resolved, vendor } : resolved;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Each vendor's default egress protocol, chosen from live KR benchmarks. HTTP
|
|
361
|
+
* CONNECT wins for nodemaven (socks5 adds ~500ms through the gateway) and ties
|
|
362
|
+
* for smartproxy, and is the only protocol ctx.http (Bun native fetch) supports.
|
|
363
|
+
* Override per call via ProxyResolutionOptions.protocol (harness/tests).
|
|
364
|
+
*/
|
|
365
|
+
export const VENDOR_DEFAULT_PROTOCOL = {
|
|
366
|
+
smartproxy: "http",
|
|
367
|
+
nodemaven: NODEMAVEN_DEFAULT_PROTOCOL,
|
|
368
|
+
};
|
|
369
|
+
/**
|
|
370
|
+
* Guard the No-MITM invariant: a resolved proxy URL must use a tunnelling scheme
|
|
371
|
+
* (http CONNECT or socks5) so the client TLS handshake reaches the origin
|
|
372
|
+
* end-to-end. Anything else would intercept TLS and break fingerprinting.
|
|
373
|
+
*/
|
|
374
|
+
export function assertTunnelingScheme(url) {
|
|
375
|
+
let scheme;
|
|
323
376
|
try {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
377
|
+
scheme = new URL(url).protocol.replace(/:$/, "").toLowerCase();
|
|
378
|
+
}
|
|
379
|
+
catch {
|
|
380
|
+
throw new ProxyResolutionError("PROXY_ALLOCATION_FAILED", `Malformed proxy URL: ${url}`);
|
|
381
|
+
}
|
|
382
|
+
if (scheme !== "http" && scheme !== "socks5") {
|
|
383
|
+
throw new ProxyResolutionError("PROXY_ALLOCATION_FAILED", `Resolved proxy scheme "${scheme}" is not a tunnelling scheme (expected http or socks5). Refusing to route TLS through a non-tunnelling proxy.`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
export async function resolveWithVendor(vendor, policy, options, context) {
|
|
387
|
+
if (vendor === "nodemaven") {
|
|
388
|
+
const startedAt = Date.now();
|
|
389
|
+
const username = (context.credentials === undefined
|
|
390
|
+
? process.env[NODEMAVEN_USERNAME_ENV]
|
|
391
|
+
: context.credentials[NODEMAVEN_USERNAME_ENV])?.trim();
|
|
392
|
+
const password = (context.credentials === undefined
|
|
393
|
+
? process.env[NODEMAVEN_PASSWORD_ENV]
|
|
394
|
+
: context.credentials[NODEMAVEN_PASSWORD_ENV])?.trim();
|
|
395
|
+
const filter = context.credentials === undefined
|
|
396
|
+
? process.env[NODEMAVEN_FILTER_ENV]
|
|
397
|
+
: context.credentials[NODEMAVEN_FILTER_ENV];
|
|
398
|
+
if (!username || !password) {
|
|
399
|
+
throw new ProxyResolutionError("PROXY_ALLOCATION_FAILED", `NodeMaven credentials missing: set ${NODEMAVEN_USERNAME_ENV} and ${NODEMAVEN_PASSWORD_ENV}.`, { vendor: "nodemaven" });
|
|
400
|
+
}
|
|
401
|
+
const synthesized = synthesizeNodemavenProxy({
|
|
402
|
+
policy,
|
|
403
|
+
credentials: {
|
|
404
|
+
username,
|
|
405
|
+
password,
|
|
406
|
+
...(filter ? { filter } : {}),
|
|
407
|
+
},
|
|
408
|
+
affinityKey: options.affinityKey,
|
|
409
|
+
protocol: context.protocol,
|
|
410
|
+
poolIndex: context.poolIndex,
|
|
411
|
+
refreshEpoch: context.refreshEpoch,
|
|
412
|
+
country: resolveSmartproxyCountry(policy, context.ambientDefaults !== false),
|
|
413
|
+
});
|
|
414
|
+
options.telemetry?.recordProxyResolution({
|
|
415
|
+
provider: "nodemaven",
|
|
416
|
+
protocol: synthesized.protocol,
|
|
417
|
+
cacheStatus: "disabled",
|
|
418
|
+
cacheHit: false,
|
|
419
|
+
resolutionMs: Math.max(0, Date.now() - startedAt),
|
|
420
|
+
attempts: 1,
|
|
421
|
+
});
|
|
422
|
+
assertTunnelingScheme(synthesized.url);
|
|
327
423
|
return {
|
|
328
424
|
shouldWarn: false,
|
|
329
|
-
url:
|
|
330
|
-
source: "
|
|
425
|
+
url: synthesized.url,
|
|
426
|
+
source: "nodemaven-gateway",
|
|
427
|
+
protocol: synthesized.protocol,
|
|
331
428
|
diagnostics: {
|
|
332
|
-
...
|
|
333
|
-
|
|
334
|
-
poolIndex,
|
|
429
|
+
...synthesized.diagnostics,
|
|
430
|
+
poolIndex: context.poolIndex,
|
|
335
431
|
},
|
|
336
432
|
};
|
|
337
433
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
434
|
+
// smartproxy allocation-style vendor.
|
|
435
|
+
const appKey = (context.credentials === undefined
|
|
436
|
+
? process.env[SMARTPROXY_APP_KEY_ENV]
|
|
437
|
+
: context.credentials[SMARTPROXY_APP_KEY_ENV])?.trim();
|
|
438
|
+
if (!appKey) {
|
|
439
|
+
// Guarded by vendorHasCredentials; treated as a vendor-internal failure.
|
|
440
|
+
throw new ProxyResolutionError("PROXY_ALLOCATION_FAILED", `${SMARTPROXY_APP_KEY_ENV} is not configured.`, { vendor: "smartproxy" });
|
|
441
|
+
}
|
|
442
|
+
const lifetimeMinutes = resolveSmartproxyLifetime(policy, context.ambientDefaults !== false);
|
|
443
|
+
const allocated = await allocateSmartproxy(policy, appKey, lifetimeMinutes, options.affinityKey, context.protocol, context.ambientDefaults !== false, context.sharedCache !== false);
|
|
444
|
+
options.telemetry?.recordProxyResolution({ ...allocated.telemetry, protocol: context.protocol });
|
|
445
|
+
const poolIndex = selectProxyPoolIndex(allocated.pool.urls.length, context.poolIndex);
|
|
446
|
+
const url = allocated.pool.urls[poolIndex];
|
|
447
|
+
if (url)
|
|
448
|
+
assertTunnelingScheme(url);
|
|
449
|
+
return {
|
|
450
|
+
shouldWarn: false,
|
|
451
|
+
url,
|
|
452
|
+
source: "smartproxy-allocator",
|
|
453
|
+
protocol: context.protocol,
|
|
454
|
+
diagnostics: {
|
|
455
|
+
...allocated.pool.diagnostics,
|
|
456
|
+
poolSize: allocated.pool.urls.length,
|
|
457
|
+
poolIndex,
|
|
458
|
+
},
|
|
459
|
+
};
|
|
349
460
|
}
|
|
350
461
|
function resolvePolicy(options) {
|
|
351
462
|
if (options.proxyPolicy) {
|
|
@@ -357,19 +468,179 @@ function resolvePolicy(options) {
|
|
|
357
468
|
}
|
|
358
469
|
return undefined;
|
|
359
470
|
}
|
|
360
|
-
function
|
|
361
|
-
return
|
|
362
|
-
|
|
363
|
-
|
|
471
|
+
function isRegistryVendor(name) {
|
|
472
|
+
return name === "smartproxy" || name === "nodemaven";
|
|
473
|
+
}
|
|
474
|
+
function declaredVendorChain(policy) {
|
|
475
|
+
const declared = policy.providers?.length
|
|
476
|
+
? policy.providers
|
|
477
|
+
: [policy.provider ?? envDefaultProvider()];
|
|
478
|
+
return declared.filter((vendor) => vendor !== undefined);
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Ordered list of SDK-native proxy vendors declared by the policy. `providers`
|
|
482
|
+
* takes precedence over the legacy singular `provider`; the platform default
|
|
483
|
+
* env is the final fallback. Non-registry names (decodo/custom) are dropped so
|
|
484
|
+
* an all-deprecated chain has no managed adapter.
|
|
485
|
+
*/
|
|
486
|
+
export function resolveVendorChain(policy) {
|
|
487
|
+
const chain = [];
|
|
488
|
+
for (const name of declaredVendorChain(policy)) {
|
|
489
|
+
if (isRegistryVendor(name) && !chain.includes(name)) {
|
|
490
|
+
chain.push(name);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return chain;
|
|
494
|
+
}
|
|
495
|
+
function envDefaultProvider() {
|
|
496
|
+
const raw = process.env[DEFAULT_PROXY_PROVIDER_ENV]?.trim().toLowerCase();
|
|
497
|
+
return raw ?? undefined;
|
|
498
|
+
}
|
|
499
|
+
function vendorHasCredentials(vendor) {
|
|
500
|
+
if (vendor === "nodemaven")
|
|
501
|
+
return hasNodemavenCredentials();
|
|
502
|
+
return Boolean(process.env[SMARTPROXY_APP_KEY_ENV]?.trim());
|
|
503
|
+
}
|
|
504
|
+
function missingCredentialEnv(vendor) {
|
|
505
|
+
return vendor === "nodemaven" ? "APIFUSE__PROXY__NODEMAVEN_USERNAME" : SMARTPROXY_APP_KEY_ENV;
|
|
506
|
+
}
|
|
507
|
+
function vendorPoolSize(vendor, policy) {
|
|
508
|
+
return vendor === "nodemaven" ? nodemavenPoolSize(policy) : resolveSmartproxyPoolSize(policy);
|
|
364
509
|
}
|
|
365
|
-
|
|
510
|
+
/**
|
|
511
|
+
* Total attempt span across a policy's vendor chain — the sum of each vendor's
|
|
512
|
+
* pool size. Transports use this so successive attempts rotate a vendor's pool
|
|
513
|
+
* and then fail over to the next vendor via the flat attempt index. With one
|
|
514
|
+
* vendor this equals that vendor's pool size (today's behaviour).
|
|
515
|
+
*/
|
|
516
|
+
export function resolvePolicyProxyPoolSpan(policy) {
|
|
517
|
+
const chain = resolveVendorChain(policy);
|
|
518
|
+
if (chain.length === 0)
|
|
519
|
+
return resolveSmartproxyPoolSize(policy);
|
|
520
|
+
return chain.reduce((sum, vendor) => sum + vendorPoolSize(vendor, policy), 0);
|
|
521
|
+
}
|
|
522
|
+
function vendorMaxPoolSize(vendor) {
|
|
523
|
+
return vendor === "nodemaven" ? NODEMAVEN_MAX_POOL_SIZE : SMARTPROXY_MAX_POOL_SIZE;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Absolute upper bound on a chain's attempt span — the sum of each vendor's
|
|
527
|
+
* *maximum* pool size. Unlike `resolvePolicyProxyPoolSpan` (the configured
|
|
528
|
+
* span), this backstop is independent of `session.poolSize`, so it never
|
|
529
|
+
* truncates a legitimately large pool below the point where the flat attempt
|
|
530
|
+
* index would cross into the next vendor (e.g. a 50-slot NodeMaven pool).
|
|
531
|
+
*/
|
|
532
|
+
export function maxPolicyProxyPoolSpan(policy) {
|
|
533
|
+
const chain = resolveVendorChain(policy);
|
|
534
|
+
if (chain.length === 0)
|
|
535
|
+
return SMARTPROXY_MAX_POOL_SIZE;
|
|
536
|
+
return chain.reduce((sum, vendor) => sum + vendorMaxPoolSize(vendor), 0);
|
|
537
|
+
}
|
|
538
|
+
const UNSAFE_TRANSPORT_RETRY_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE", "TRACE"]);
|
|
539
|
+
/**
|
|
540
|
+
* Transport-retry attempt cap for a policy-managed request. A transport failure
|
|
541
|
+
* rotates the flat attempt index onto the *next* endpoint (and, once the index
|
|
542
|
+
* passes the primary vendor's pool span, the *next vendor*), so the cap must be
|
|
543
|
+
* the chain's full pool span for failover to reach the fallback vendor — the
|
|
544
|
+
* per-endpoint retry budget (default 3) never gets there.
|
|
545
|
+
*
|
|
546
|
+
* The span only widens beyond the caller's retry budget when ALL hold:
|
|
547
|
+
* - the request is policy-allocator managed (not a caller-supplied proxy URL);
|
|
548
|
+
* - the caller did NOT pin an explicit retry policy — `HttpRetryOptions.attempts`
|
|
549
|
+
* is the documented total-attempt ceiling and must be honoured verbatim;
|
|
550
|
+
* - the method is safe/idempotent — an unsafe request must never be duplicated
|
|
551
|
+
* across the pool even if some framework default would allow it;
|
|
552
|
+
* - the policy resolves a non-empty *registry* vendor chain (smartproxy /
|
|
553
|
+
* nodemaven). Deprecated vendors (custom / decodo) resolve no managed pool,
|
|
554
|
+
* so there is no possible endpoint crossover — they keep the retry budget.
|
|
555
|
+
*
|
|
556
|
+
* The widened cap is bounded by the chain's true maximum span (sum of each
|
|
557
|
+
* vendor's max pool size), so a large NodeMaven pool (≤50) stays reachable and
|
|
558
|
+
* a pathological chain can never spin unbounded.
|
|
559
|
+
*/
|
|
560
|
+
/**
|
|
561
|
+
* True when a policy request is in *implicit chain-rotation* mode: successive
|
|
562
|
+
* transport attempts rotate the flat index across the concatenated vendor pool
|
|
563
|
+
* spans (and, past the primary vendor's span, into the fallback vendor). This is
|
|
564
|
+
* the ONLY mode in which the transport loop widens its attempt cap AND
|
|
565
|
+
* de-duplicates repeated endpoints — the two behaviours must share one predicate
|
|
566
|
+
* so they never diverge. It holds when ALL of the widening conditions hold:
|
|
567
|
+
* - the request is policy-allocator managed (not a caller-supplied proxy URL);
|
|
568
|
+
* - the caller did NOT pin an explicit retry policy — its `attempts` ceiling is
|
|
569
|
+
* the documented contract and must be honoured verbatim against whatever
|
|
570
|
+
* endpoint each attempt resolves (even a repeated one), so no de-duplication;
|
|
571
|
+
* - the method is safe/idempotent — an unsafe request is never duplicated;
|
|
572
|
+
* - the policy resolves a non-empty registry vendor chain (smartproxy /
|
|
573
|
+
* nodemaven). Deprecated vendors (custom / decodo) resolve no managed
|
|
574
|
+
* endpoint, so there is nothing to rotate or de-duplicate.
|
|
575
|
+
*/
|
|
576
|
+
export function policyRotatesTransportVendorChain(input) {
|
|
577
|
+
if (!input.usesPolicyAllocator || !input.policy || input.explicitRetry) {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
if (UNSAFE_TRANSPORT_RETRY_METHODS.has(input.method.toUpperCase())) {
|
|
581
|
+
return false;
|
|
582
|
+
}
|
|
583
|
+
return resolveVendorChain(input.policy).length > 0;
|
|
584
|
+
}
|
|
585
|
+
export function resolvePolicyTransportAttemptCap(input) {
|
|
586
|
+
const budget = Math.max(1, Math.floor(input.retryAttempts));
|
|
587
|
+
if (!policyRotatesTransportVendorChain({
|
|
588
|
+
policy: input.policy,
|
|
589
|
+
usesPolicyAllocator: input.usesPolicyAllocator,
|
|
590
|
+
explicitRetry: input.explicitRetry,
|
|
591
|
+
method: input.method,
|
|
592
|
+
})) {
|
|
593
|
+
return budget;
|
|
594
|
+
}
|
|
595
|
+
const span = Math.min(maxPolicyProxyPoolSpan(input.policy), resolvePolicyProxyPoolSpan(input.policy));
|
|
596
|
+
return Math.max(budget, span);
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* A registry vendor chain (smartproxy/nodemaven) resolves a potentially
|
|
600
|
+
* *different* endpoint per flat attempt index, so a transport retry should
|
|
601
|
+
* advance across endpoints and de-duplicate once the chain stops yielding new
|
|
602
|
+
* ones. Deprecated custom/decodo policies have an empty registry chain and no
|
|
603
|
+
* managed endpoint, so the transport loop has nothing to rotate or de-duplicate.
|
|
604
|
+
*/
|
|
605
|
+
export function policyResolvesRegistryVendorChain(policy) {
|
|
606
|
+
return Boolean(policy) && resolveVendorChain(policy).length > 0;
|
|
607
|
+
}
|
|
608
|
+
/** Map a resolved proxy source label to the vendor that served it. */
|
|
609
|
+
export function vendorFromResolvedSource(source) {
|
|
610
|
+
if (source === "nodemaven-gateway")
|
|
611
|
+
return "nodemaven";
|
|
612
|
+
if (source === "smartproxy-allocator")
|
|
613
|
+
return "smartproxy";
|
|
614
|
+
return undefined;
|
|
615
|
+
}
|
|
616
|
+
function normalizeAttemptIndex(attempt) {
|
|
617
|
+
return Number.isFinite(attempt) ? Math.max(0, Math.floor(attempt)) : 0;
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Map a flat attempt index into (vendorIndex, poolIndex) by concatenating each
|
|
621
|
+
* vendor's pool space in chain order. With a single vendor this reduces to
|
|
622
|
+
* `attempt % poolSize`, preserving today's behaviour exactly.
|
|
623
|
+
*/
|
|
624
|
+
export function mapFlatAttempt(flat, sizes) {
|
|
625
|
+
let cursor = flat;
|
|
626
|
+
for (let vendorIndex = 0; vendorIndex < sizes.length; vendorIndex++) {
|
|
627
|
+
const size = Math.max(1, sizes[vendorIndex] ?? 1);
|
|
628
|
+
if (cursor < size) {
|
|
629
|
+
return { vendorIndex, poolIndex: cursor };
|
|
630
|
+
}
|
|
631
|
+
cursor -= size;
|
|
632
|
+
}
|
|
633
|
+
return { vendorIndex: 0, poolIndex: 0 };
|
|
634
|
+
}
|
|
635
|
+
function resolveSmartproxyCountry(policy, ambientDefaults = true) {
|
|
366
636
|
return (policy.geo?.country ??
|
|
367
|
-
|
|
368
|
-
|
|
637
|
+
(ambientDefaults
|
|
638
|
+
? process.env[DEFAULT_PROXY_COUNTRY_ENV]?.trim().toUpperCase() || undefined
|
|
639
|
+
: undefined));
|
|
369
640
|
}
|
|
370
|
-
function resolveSmartproxyLifetime(policy) {
|
|
641
|
+
function resolveSmartproxyLifetime(policy, ambientDefaults = true) {
|
|
371
642
|
const configuredLifetime = policy.session?.lifetimeMinutes ??
|
|
372
|
-
readPositiveNumberEnv(DEFAULT_PROXY_LIFETIME_ENV, 30);
|
|
643
|
+
(ambientDefaults ? readPositiveNumberEnv(DEFAULT_PROXY_LIFETIME_ENV, 30) : 30);
|
|
373
644
|
return Math.min(SMARTPROXY_MAX_LIFETIME_MINUTES, Math.max(1, Math.floor(configuredLifetime)));
|
|
374
645
|
}
|
|
375
646
|
function readPositiveNumberEnv(name, fallback) {
|
|
@@ -389,26 +660,27 @@ function selectProxyPoolIndex(poolSize, attempt = 0) {
|
|
|
389
660
|
if (poolSize <= 1) {
|
|
390
661
|
return 0;
|
|
391
662
|
}
|
|
392
|
-
const normalizedAttempt = Number.isFinite(attempt)
|
|
393
|
-
? Math.max(0, Math.floor(attempt))
|
|
394
|
-
: 0;
|
|
663
|
+
const normalizedAttempt = Number.isFinite(attempt) ? Math.max(0, Math.floor(attempt)) : 0;
|
|
395
664
|
return normalizedAttempt % poolSize;
|
|
396
665
|
}
|
|
397
|
-
function buildSmartproxyCacheKey(policy, affinityKey, lifetimeMinutes) {
|
|
666
|
+
function buildSmartproxyCacheKey(policy, appKey, affinityKey, lifetimeMinutes, protocol, ambientDefaults = true) {
|
|
398
667
|
const poolSize = resolveSmartproxyPoolSize(policy);
|
|
399
668
|
return JSON.stringify({
|
|
400
669
|
provider: "smartproxy",
|
|
401
|
-
|
|
670
|
+
credentialHash: createHash("sha256")
|
|
671
|
+
.update("apifuse-smartproxy-credential:v1\0")
|
|
672
|
+
.update(appKey)
|
|
673
|
+
.digest("hex"),
|
|
674
|
+
protocol,
|
|
675
|
+
country: resolveSmartproxyCountry(policy, ambientDefaults),
|
|
402
676
|
affinity: policy.session?.affinity ?? "request",
|
|
403
|
-
affinityKey: (policy.session?.affinity ?? "request") === "request"
|
|
404
|
-
? undefined
|
|
405
|
-
: affinityKey,
|
|
677
|
+
affinityKey: (policy.session?.affinity ?? "request") === "request" ? undefined : affinityKey,
|
|
406
678
|
lifetimeMinutes,
|
|
407
679
|
poolSize,
|
|
408
680
|
});
|
|
409
681
|
}
|
|
410
|
-
async function allocateSmartproxy(policy, appKey, lifetimeMinutes, affinityKey) {
|
|
411
|
-
const cacheKey = buildSmartproxyCacheKey(policy, affinityKey, lifetimeMinutes);
|
|
682
|
+
async function allocateSmartproxy(policy, appKey, lifetimeMinutes, affinityKey, protocol, ambientDefaults = true, sharedCache = true) {
|
|
683
|
+
const cacheKey = buildSmartproxyCacheKey(policy, appKey, affinityKey, lifetimeMinutes, protocol, ambientDefaults);
|
|
412
684
|
const startedAt = Date.now();
|
|
413
685
|
const now = startedAt;
|
|
414
686
|
const invalidatedUntil = invalidatedProxyKeys.get(cacheKey) ?? 0;
|
|
@@ -416,7 +688,7 @@ async function allocateSmartproxy(policy, appKey, lifetimeMinutes, affinityKey)
|
|
|
416
688
|
const cached = proxyCache.get(cacheKey);
|
|
417
689
|
if (!skipCached && cached && isFresh(cached, now)) {
|
|
418
690
|
if (shouldSoftRefresh(cached, now)) {
|
|
419
|
-
void refreshSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes);
|
|
691
|
+
void refreshSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes, protocol, ambientDefaults, sharedCache);
|
|
420
692
|
return {
|
|
421
693
|
pool: cached,
|
|
422
694
|
telemetry: telemetryForPool(cached, "soft_stale_refresh", startedAt, {
|
|
@@ -429,7 +701,7 @@ async function allocateSmartproxy(policy, appKey, lifetimeMinutes, affinityKey)
|
|
|
429
701
|
telemetry: telemetryForPool(cached, "memory_hit", startedAt),
|
|
430
702
|
};
|
|
431
703
|
}
|
|
432
|
-
if (!skipCached) {
|
|
704
|
+
if (!skipCached && sharedCache) {
|
|
433
705
|
const redisResult = await readSmartproxyRedisPool(cacheKey, startedAt);
|
|
434
706
|
if (redisResult)
|
|
435
707
|
return redisResult;
|
|
@@ -444,7 +716,7 @@ async function allocateSmartproxy(policy, appKey, lifetimeMinutes, affinityKey)
|
|
|
444
716
|
}),
|
|
445
717
|
};
|
|
446
718
|
}
|
|
447
|
-
const promise = allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinutes, startedAt).finally(() => {
|
|
719
|
+
const promise = allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinutes, startedAt, protocol, ambientDefaults, sharedCache).finally(() => {
|
|
448
720
|
proxyInflight.delete(cacheKey);
|
|
449
721
|
});
|
|
450
722
|
proxyInflight.set(cacheKey, promise);
|
|
@@ -475,9 +747,9 @@ async function readSmartproxyRedisPool(cacheKey, startedAt) {
|
|
|
475
747
|
telemetry: telemetryForPool(pool, "redis_hit", startedAt, { redisReadMs }),
|
|
476
748
|
};
|
|
477
749
|
}
|
|
478
|
-
async function refreshSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes) {
|
|
750
|
+
async function refreshSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes, protocol, ambientDefaults, sharedCache) {
|
|
479
751
|
try {
|
|
480
|
-
await allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinutes, Date.now(), {
|
|
752
|
+
await allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinutes, Date.now(), protocol, ambientDefaults, sharedCache, {
|
|
481
753
|
background: true,
|
|
482
754
|
});
|
|
483
755
|
}
|
|
@@ -485,10 +757,10 @@ async function refreshSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes)
|
|
|
485
757
|
// Soft refresh is opportunistic; current fresh pool remains usable.
|
|
486
758
|
}
|
|
487
759
|
}
|
|
488
|
-
async function allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinutes, startedAt, options = {}) {
|
|
489
|
-
const redis = getProxyRedis();
|
|
760
|
+
async function allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinutes, startedAt, protocol, ambientDefaults, sharedCache, options = {}) {
|
|
761
|
+
const redis = sharedCache ? getProxyRedis() : undefined;
|
|
490
762
|
if (!redis || !(await ensureRedisReady(redis))) {
|
|
491
|
-
return await allocateAndStoreSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes, startedAt, { cacheStatus: "allocator" });
|
|
763
|
+
return await allocateAndStoreSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes, startedAt, { cacheStatus: "allocator", protocol, ambientDefaults });
|
|
492
764
|
}
|
|
493
765
|
const poolKey = smartproxyRedisPoolKey(cacheKey);
|
|
494
766
|
const lockKey = smartproxyRedisLockKey(cacheKey);
|
|
@@ -499,11 +771,11 @@ async function allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinute
|
|
|
499
771
|
if (acquired === "OK") {
|
|
500
772
|
try {
|
|
501
773
|
return await allocateAndStoreSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes, startedAt, {
|
|
502
|
-
cacheStatus: options.background
|
|
503
|
-
? "soft_stale_refresh"
|
|
504
|
-
: "allocator",
|
|
774
|
+
cacheStatus: options.background ? "soft_stale_refresh" : "allocator",
|
|
505
775
|
redis,
|
|
506
776
|
poolKey,
|
|
777
|
+
protocol,
|
|
778
|
+
ambientDefaults,
|
|
507
779
|
});
|
|
508
780
|
}
|
|
509
781
|
finally {
|
|
@@ -526,8 +798,7 @@ async function allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinute
|
|
|
526
798
|
if (typeof pttl === "number" && pttl <= 0) {
|
|
527
799
|
continue;
|
|
528
800
|
}
|
|
529
|
-
if (Date.now() - startedAt >
|
|
530
|
-
SMARTPROXY_LOCK_POLL_MAX_MS - SMARTPROXY_DEADLINE_MARGIN_MS) {
|
|
801
|
+
if (Date.now() - startedAt > SMARTPROXY_LOCK_POLL_MAX_MS - SMARTPROXY_DEADLINE_MARGIN_MS) {
|
|
531
802
|
break;
|
|
532
803
|
}
|
|
533
804
|
await sleep(Math.min(500, Math.max(50, typeof pttl === "number" ? pttl : 100)));
|
|
@@ -559,7 +830,7 @@ function sleep(ms, signal) {
|
|
|
559
830
|
});
|
|
560
831
|
}
|
|
561
832
|
function smartproxyAllocatorDeadlineMs() {
|
|
562
|
-
return
|
|
833
|
+
return smartproxyAllocatorDeadlineMsForTests ?? SMARTPROXY_ALLOCATOR_DEADLINE_MS;
|
|
563
834
|
}
|
|
564
835
|
function createDeadlineAbortController(deadlineAt) {
|
|
565
836
|
const controller = new AbortController();
|
|
@@ -617,7 +888,7 @@ async function readSmartproxyAllocatorBodyWithDeadline(response, signal) {
|
|
|
617
888
|
}
|
|
618
889
|
async function allocateAndStoreSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes, startedAt, options) {
|
|
619
890
|
const poolSize = resolveSmartproxyPoolSize(policy);
|
|
620
|
-
const allocatorUrl = buildSmartproxyAllocatorUrl(policy, appKey, lifetimeMinutes, poolSize);
|
|
891
|
+
const allocatorUrl = buildSmartproxyAllocatorUrl(policy, appKey, lifetimeMinutes, poolSize, options.protocol, options.ambientDefaults);
|
|
621
892
|
const allocatorStartedAt = Date.now();
|
|
622
893
|
const allocatorDeadlineAt = allocatorStartedAt + smartproxyAllocatorDeadlineMs();
|
|
623
894
|
let allocation;
|
|
@@ -627,7 +898,7 @@ async function allocateAndStoreSmartproxyPool(cacheKey, policy, appKey, lifetime
|
|
|
627
898
|
lastFailure = smartproxyAllocatorDeadlineFailure(attempt);
|
|
628
899
|
break;
|
|
629
900
|
}
|
|
630
|
-
const attemptResult = await fetchSmartproxyAllocatorAttempt(allocatorUrl, attempt, allocatorDeadlineAt);
|
|
901
|
+
const attemptResult = await fetchSmartproxyAllocatorAttempt(allocatorUrl, attempt, allocatorDeadlineAt, options.protocol);
|
|
631
902
|
if (attemptResult.ok) {
|
|
632
903
|
allocation = attemptResult;
|
|
633
904
|
break;
|
|
@@ -649,9 +920,7 @@ async function allocateAndStoreSmartproxyPool(cacheKey, policy, appKey, lifetime
|
|
|
649
920
|
}
|
|
650
921
|
}
|
|
651
922
|
const allocatorMs = Math.max(0, Date.now() - allocatorStartedAt);
|
|
652
|
-
const allocatorAttempts = allocation?.attempt ??
|
|
653
|
-
lastFailure?.attempt ??
|
|
654
|
-
SMARTPROXY_ALLOCATOR_MAX_ATTEMPTS;
|
|
923
|
+
const allocatorAttempts = allocation?.attempt ?? lastFailure?.attempt ?? SMARTPROXY_ALLOCATOR_MAX_ATTEMPTS;
|
|
655
924
|
const allocatorBodyClass = lastFailure?.bodyClass ?? allocation?.bodyClass ?? "usable_proxy_endpoints";
|
|
656
925
|
const allocatorStatus = lastFailure ? lastFailure.status : allocation?.status;
|
|
657
926
|
if (!allocation) {
|
|
@@ -677,7 +946,7 @@ async function allocateAndStoreSmartproxyPool(cacheKey, policy, appKey, lifetime
|
|
|
677
946
|
expiresAt: allocatedAt + ttlMs,
|
|
678
947
|
diagnostics: {
|
|
679
948
|
provider: "smartproxy",
|
|
680
|
-
country: resolveSmartproxyCountry(policy) ?? "default",
|
|
949
|
+
country: resolveSmartproxyCountry(policy, options.ambientDefaults) ?? "default",
|
|
681
950
|
lifetimeMinutes,
|
|
682
951
|
affinity: policy.session?.affinity ?? "request",
|
|
683
952
|
rawConnect: true,
|
|
@@ -712,7 +981,7 @@ async function allocateAndStoreSmartproxyPool(cacheKey, policy, appKey, lifetime
|
|
|
712
981
|
}),
|
|
713
982
|
};
|
|
714
983
|
}
|
|
715
|
-
async function fetchSmartproxyAllocatorAttempt(allocatorUrl, attempt, deadlineAt) {
|
|
984
|
+
async function fetchSmartproxyAllocatorAttempt(allocatorUrl, attempt, deadlineAt, protocol) {
|
|
716
985
|
const { controller, dispose } = createDeadlineAbortController(deadlineAt);
|
|
717
986
|
let response;
|
|
718
987
|
try {
|
|
@@ -755,7 +1024,7 @@ async function fetchSmartproxyAllocatorAttempt(allocatorUrl, attempt, deadlineAt
|
|
|
755
1024
|
bodyClass: "http_error",
|
|
756
1025
|
};
|
|
757
1026
|
}
|
|
758
|
-
const urls = parseSmartproxyAllocatorProxies(body);
|
|
1027
|
+
const urls = parseSmartproxyAllocatorProxies(body, protocol);
|
|
759
1028
|
const bodyClass = classifySmartproxyAllocatorBody(body, urls);
|
|
760
1029
|
if (urls.length === 0) {
|
|
761
1030
|
return {
|
|
@@ -790,35 +1059,41 @@ function smartproxyAllocatorFailureMessage(failure) {
|
|
|
790
1059
|
}
|
|
791
1060
|
return "Smartproxy allocator response did not contain a usable proxy endpoint.";
|
|
792
1061
|
}
|
|
793
|
-
|
|
1062
|
+
// Smartproxy get-ip-v3 `protocol` param: 1 = HTTP. The SOCKS5 value ("2") is a
|
|
1063
|
+
// best-effort mapping pending live vendor confirmation; http is the default and
|
|
1064
|
+
// the only value exercised in production today.
|
|
1065
|
+
const SMARTPROXY_PROTOCOL_PARAM = {
|
|
1066
|
+
http: "1",
|
|
1067
|
+
socks5: "2",
|
|
1068
|
+
};
|
|
1069
|
+
function buildSmartproxyAllocatorUrl(policy, appKey, lifetimeMinutes, poolSize, protocol, ambientDefaults = true) {
|
|
794
1070
|
const params = new URLSearchParams({
|
|
795
1071
|
app_key: appKey,
|
|
796
1072
|
pt: "9",
|
|
797
1073
|
num: String(poolSize),
|
|
798
1074
|
life: String(lifetimeMinutes),
|
|
799
|
-
protocol:
|
|
1075
|
+
protocol: SMARTPROXY_PROTOCOL_PARAM[protocol],
|
|
800
1076
|
format: "txt",
|
|
801
1077
|
lb: "\\n",
|
|
802
1078
|
});
|
|
803
|
-
const country = resolveSmartproxyCountry(policy);
|
|
1079
|
+
const country = resolveSmartproxyCountry(policy, ambientDefaults);
|
|
804
1080
|
if (country) {
|
|
805
1081
|
params.set("cc", country);
|
|
806
1082
|
}
|
|
807
|
-
|
|
1083
|
+
// www.smartproxy.org stopped serving the extraction API on 2026-07-21 (the
|
|
1084
|
+
// old path 404s into the marketing site); the API lives on the api host.
|
|
1085
|
+
return `https://api.smartproxy.org/web_v1/ip/get-ip-v3?${params.toString()}`;
|
|
808
1086
|
}
|
|
809
|
-
function parseSmartproxyAllocatorProxies(body) {
|
|
1087
|
+
function parseSmartproxyAllocatorProxies(body, protocol) {
|
|
1088
|
+
const scheme = protocol === "socks5" ? "socks5" : "http";
|
|
810
1089
|
const trimmed = body.trim();
|
|
811
1090
|
if (!trimmed) {
|
|
812
1091
|
return [];
|
|
813
1092
|
}
|
|
814
1093
|
try {
|
|
815
1094
|
const parsed = JSON.parse(trimmed);
|
|
816
|
-
const data = parsed && typeof parsed === "object" && "data" in parsed
|
|
817
|
-
|
|
818
|
-
: undefined;
|
|
819
|
-
const list = data && typeof data === "object" && "list" in data
|
|
820
|
-
? data.list
|
|
821
|
-
: undefined;
|
|
1095
|
+
const data = parsed && typeof parsed === "object" && "data" in parsed ? parsed.data : undefined;
|
|
1096
|
+
const list = data && typeof data === "object" && "list" in data ? data.list : undefined;
|
|
822
1097
|
if (Array.isArray(list)) {
|
|
823
1098
|
return list
|
|
824
1099
|
.map((item) => {
|
|
@@ -826,11 +1101,10 @@ function parseSmartproxyAllocatorProxies(body) {
|
|
|
826
1101
|
return null;
|
|
827
1102
|
}
|
|
828
1103
|
const ip = "ip" in item && typeof item.ip === "string" ? item.ip : "";
|
|
829
|
-
const port = "port" in item &&
|
|
830
|
-
(typeof item.port === "string" || typeof item.port === "number")
|
|
1104
|
+
const port = "port" in item && (typeof item.port === "string" || typeof item.port === "number")
|
|
831
1105
|
? item.port
|
|
832
1106
|
: "";
|
|
833
|
-
return ip && port ?
|
|
1107
|
+
return ip && port ? `${scheme}://${ip}:${port}` : null;
|
|
834
1108
|
})
|
|
835
1109
|
.filter((url) => url !== null);
|
|
836
1110
|
}
|
|
@@ -842,7 +1116,7 @@ function parseSmartproxyAllocatorProxies(body) {
|
|
|
842
1116
|
.split(/\r?\n/)
|
|
843
1117
|
.map((item) => item.trim())
|
|
844
1118
|
.filter((item) => /^\d{1,3}(?:\.\d{1,3}){3}:\d{2,5}$/.test(item))
|
|
845
|
-
.map((line) =>
|
|
1119
|
+
.map((line) => `${scheme}://${line}`);
|
|
846
1120
|
}
|
|
847
1121
|
function classifySmartproxyAllocatorBody(body, urls) {
|
|
848
1122
|
if (urls.length > 0) {
|
|
@@ -870,11 +1144,14 @@ function markSmartproxyCacheInvalidated(options = {}) {
|
|
|
870
1144
|
if (!policy || policy.mode === "disabled") {
|
|
871
1145
|
return undefined;
|
|
872
1146
|
}
|
|
873
|
-
if (
|
|
1147
|
+
if (!resolveVendorChain(policy).includes("smartproxy")) {
|
|
874
1148
|
return undefined;
|
|
875
1149
|
}
|
|
876
1150
|
const lifetimeMinutes = resolveSmartproxyLifetime(policy);
|
|
877
|
-
const
|
|
1151
|
+
const appKey = process.env[SMARTPROXY_APP_KEY_ENV]?.trim();
|
|
1152
|
+
if (!appKey)
|
|
1153
|
+
return undefined;
|
|
1154
|
+
const cacheKey = buildSmartproxyCacheKey(policy, appKey, options.affinityKey, lifetimeMinutes, options.protocol ?? VENDOR_DEFAULT_PROTOCOL.smartproxy);
|
|
878
1155
|
invalidatedProxyKeys.set(cacheKey, Date.now() + SMARTPROXY_INVALIDATION_SKIP_REDIS_MS);
|
|
879
1156
|
proxyCache.delete(cacheKey);
|
|
880
1157
|
proxyInflight.delete(cacheKey);
|
|
@@ -920,16 +1197,12 @@ export async function loadApiFuseConfig(dir = process.cwd()) {
|
|
|
920
1197
|
const tsPath = path.resolve(dir, "apifuse.config.ts");
|
|
921
1198
|
if (existsSync(tsPath)) {
|
|
922
1199
|
const config = await importConfig(tsPath);
|
|
923
|
-
|
|
924
|
-
syncProxyEnv(resolvedConfig);
|
|
925
|
-
return resolvedConfig;
|
|
1200
|
+
return config ?? {};
|
|
926
1201
|
}
|
|
927
1202
|
const jsPath = path.resolve(dir, "apifuse.config.js");
|
|
928
1203
|
if (existsSync(jsPath)) {
|
|
929
1204
|
const config = await importConfig(jsPath);
|
|
930
|
-
|
|
931
|
-
syncProxyEnv(resolvedConfig);
|
|
932
|
-
return resolvedConfig;
|
|
1205
|
+
return config ?? {};
|
|
933
1206
|
}
|
|
934
1207
|
return {};
|
|
935
1208
|
}
|