@apifuse/provider-sdk 2.2.0-beta.5 → 2.2.0-beta.50
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 +537 -33
- package/CHANGELOG.md +201 -1
- package/README.md +83 -22
- package/SUBMISSION.md +2 -2
- package/bin/apifuse-check.ts +165 -11
- package/bin/apifuse-dev.ts +57 -13
- package/bin/apifuse-migrate-operation-declaration.ts +55 -0
- package/bin/apifuse-migrate-shape.ts +202 -0
- package/bin/apifuse-pack-check.ts +22 -2
- package/bin/apifuse-pack-smoke.ts +57 -2
- package/bin/apifuse-pack-types.ts +357 -38
- package/bin/apifuse-perf.ts +14 -13
- package/bin/apifuse-record.ts +709 -72
- package/bin/apifuse-submit-check.ts +2344 -324
- package/bin/apifuse-sync-assets.ts +117 -0
- package/bin/submit-check-delimited-text.ts +50 -0
- package/dist/auth-turn/index.d.ts +3 -3
- package/dist/auth-turn/index.js +1 -1
- package/dist/auth.d.ts +14 -0
- package/dist/auth.js +67 -0
- package/dist/ceremonies/index.d.ts +16 -0
- package/dist/ceremonies/index.js +141 -36
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +27 -0
- package/dist/cli/create.d.ts +4 -1
- package/dist/cli/create.js +42 -37
- package/dist/cli/migrate-operation-declaration.d.ts +59 -0
- package/dist/cli/migrate-operation-declaration.js +1178 -0
- package/dist/cli/migrate-operation-shape.d.ts +44 -0
- package/dist/cli/migrate-operation-shape.js +113 -0
- package/dist/cli/migrate-provider-shape.d.ts +52 -0
- package/dist/cli/migrate-provider-shape.js +578 -0
- 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/Dockerfile.tpl +1 -1
- package/dist/cli/templates/provider/README.md.tpl +5 -5
- package/dist/cli/templates/provider/index.ts.tpl +6 -3
- package/dist/cli/templates/provider/operations/ping.ts.tpl +4 -1
- package/dist/cli/templates/provider/provider.json.tpl +6 -0
- package/dist/config/loader.d.ts +179 -16
- package/dist/config/loader.js +435 -127
- package/dist/contract-serialization.js +4 -8
- package/dist/contract-types.d.ts +12 -5
- package/dist/contract.js +24 -10
- package/dist/declaration-validation.d.ts +33 -0
- package/dist/declaration-validation.js +266 -0
- package/dist/define.d.ts +66 -29
- package/dist/define.js +778 -69
- package/dist/dev.d.ts +3 -0
- package/dist/dev.js +1 -1
- package/dist/engine.d.ts +78 -0
- package/dist/engine.js +133 -0
- package/dist/error-observability.d.ts +7 -0
- package/dist/error-observability.js +61 -0
- package/dist/error-resolution.d.ts +4 -0
- package/dist/error-resolution.js +122 -0
- package/dist/errors.d.ts +33 -0
- package/dist/errors.js +40 -0
- package/dist/fixture-sanitization.d.ts +28 -0
- package/dist/fixture-sanitization.js +227 -0
- package/dist/health-scenario.d.ts +1842 -0
- package/dist/health-scenario.js +624 -0
- package/dist/index.d.ts +21 -10
- package/dist/index.js +12 -7
- package/dist/lint.d.ts +23 -11
- package/dist/lint.js +572 -69
- 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 +10 -2
- package/dist/provider.js +4 -1
- package/dist/runtime/auth-flow.d.ts +5 -1
- package/dist/runtime/auth-flow.js +6 -0
- package/dist/runtime/browser.d.ts +1 -0
- package/dist/runtime/browser.js +492 -49
- package/dist/runtime/cache.d.ts +1 -0
- package/dist/runtime/cache.js +169 -15
- package/dist/runtime/choice-wordlist.d.ts +9 -0
- package/dist/runtime/choice-wordlist.js +138 -0
- package/dist/runtime/choice.d.ts +13 -1
- package/dist/runtime/choice.js +490 -102
- package/dist/runtime/chrome149-header-order.d.ts +58 -0
- package/dist/runtime/chrome149-header-order.js +289 -0
- package/dist/runtime/env.js +12 -0
- package/dist/runtime/executor.d.ts +3 -2
- package/dist/runtime/executor.js +26 -25
- package/dist/runtime/http.d.ts +1 -0
- package/dist/runtime/http.js +515 -53
- package/dist/runtime/insights.js +2 -2
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +366 -8
- 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 +71 -2
- package/dist/runtime/otlp.js +397 -16
- package/dist/runtime/proxy-errors.js +6 -2
- package/dist/runtime/proxy-nodemaven.d.ts +56 -0
- package/dist/runtime/proxy-nodemaven.js +146 -0
- package/dist/runtime/proxy-telemetry.d.ts +80 -1
- package/dist/runtime/proxy-telemetry.js +154 -47
- 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 +4 -2
- 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 +14 -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 +22 -0
- package/dist/runtime/resolver-vendors/browser.js +377 -0
- package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
- package/dist/runtime/resolver-vendors/capsolver.js +531 -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 +408 -0
- package/dist/runtime/resolver-vendors/types.d.ts +94 -0
- package/dist/runtime/resolver-vendors/types.js +96 -0
- package/dist/runtime/resolver.d.ts +72 -0
- package/dist/runtime/resolver.js +771 -0
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +3 -0
- package/dist/runtime/state.js +277 -71
- package/dist/runtime/stealth-cookies.d.ts +20 -0
- package/dist/runtime/stealth-cookies.js +111 -0
- package/dist/runtime/stealth.d.ts +41 -5
- package/dist/runtime/stealth.js +997 -291
- package/dist/runtime/stt.js +1 -12
- package/dist/runtime/timeout.d.ts +5 -0
- package/dist/runtime/timeout.js +12 -0
- package/dist/runtime/trace-config.d.ts +12 -0
- package/dist/runtime/trace-config.js +62 -0
- package/dist/runtime/trace.d.ts +5 -0
- package/dist/runtime/trace.js +43 -10
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/error-observability.d.ts +1 -0
- package/dist/server/error-observability.js +1 -0
- package/dist/server/index.d.ts +5 -2
- package/dist/server/index.js +2 -2
- 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.d.ts +14 -3
- package/dist/server/self-test.js +129 -58
- package/dist/server/serve-implementation.d.ts +230 -0
- package/dist/server/serve-implementation.js +2265 -0
- package/dist/server/serve.d.ts +1 -70
- package/dist/server/serve.js +1 -1131
- package/dist/server/trace-output.d.ts +6 -0
- package/dist/server/trace-output.js +79 -0
- package/dist/server/types.d.ts +30 -5
- package/dist/server/types.js +13 -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 +9 -4
- package/dist/stealth/profiles.js +160 -211
- package/dist/stream-evidence.d.ts +74 -0
- package/dist/stream-evidence.js +785 -0
- package/dist/stream.js +7 -1
- package/dist/testing/index.d.ts +2 -1
- package/dist/testing/index.js +2 -1
- package/dist/testing/run.d.ts +32 -2
- package/dist/testing/run.js +489 -21
- package/dist/trace-sanitization.d.ts +5 -0
- package/dist/trace-sanitization.js +45 -0
- package/dist/types.d.ts +673 -131
- package/dist/types.js +1 -0
- package/package.json +45 -5
- package/src/auth-turn/index.ts +1 -1
- package/src/auth.ts +118 -0
- package/src/ceremonies/index.ts +189 -46
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +3 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +10 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +8 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +5 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +16 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +3 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +31 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +21 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +14 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +15 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +4 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +12 -0
- package/src/cli/commands.ts +33 -0
- package/src/cli/create.ts +50 -37
- package/src/cli/migrate-operation-declaration.ts +1654 -0
- package/src/cli/migrate-operation-shape.ts +184 -0
- package/src/cli/migrate-provider-shape.ts +772 -0
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/src/cli/templates/provider/Dockerfile.tpl +1 -1
- package/src/cli/templates/provider/README.md.tpl +5 -5
- package/src/cli/templates/provider/index.ts.tpl +6 -3
- package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
- package/src/cli/templates/provider/provider.json.tpl +6 -0
- package/src/config/loader.ts +690 -163
- package/src/contract-serialization.ts +5 -7
- package/src/contract-types.ts +12 -5
- package/src/contract.ts +24 -10
- package/src/declaration-validation.ts +330 -0
- package/src/define.ts +1048 -131
- package/src/dev.ts +4 -1
- package/src/engine.ts +279 -0
- package/src/error-observability.ts +64 -0
- package/src/error-resolution.ts +127 -0
- package/src/errors.ts +68 -0
- package/src/fixture-sanitization.ts +264 -0
- package/src/health-scenario.ts +875 -0
- package/src/index.ts +220 -13
- package/src/lint.ts +698 -90
- 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 +161 -3
- package/src/runtime/auth-flow.ts +12 -0
- package/src/runtime/browser.ts +661 -63
- package/src/runtime/cache.ts +189 -14
- package/src/runtime/choice-wordlist.ts +145 -0
- package/src/runtime/choice.ts +631 -120
- package/src/runtime/chrome149-header-order.ts +330 -0
- package/src/runtime/env.ts +13 -0
- package/src/runtime/executor.ts +43 -31
- package/src/runtime/http.ts +641 -61
- package/src/runtime/insights.ts +2 -2
- package/src/runtime/instrumentation.ts +520 -15
- 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 +467 -21
- package/src/runtime/proxy-errors.ts +12 -4
- package/src/runtime/proxy-nodemaven.ts +221 -0
- package/src/runtime/proxy-telemetry.ts +244 -75
- package/src/runtime/redirects.ts +66 -0
- package/src/runtime/redis.ts +7 -2
- package/src/runtime/request-options.ts +680 -1
- package/src/runtime/resolver-config.ts +6 -0
- package/src/runtime/resolver-public.ts +23 -0
- package/src/runtime/resolver-shared.ts +33 -0
- package/src/runtime/resolver-vendors/bindings.ts +56 -0
- package/src/runtime/resolver-vendors/browser.ts +533 -0
- package/src/runtime/resolver-vendors/capsolver.ts +707 -0
- package/src/runtime/resolver-vendors/hosts.ts +38 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +540 -0
- package/src/runtime/resolver-vendors/types.ts +212 -0
- package/src/runtime/resolver.ts +1157 -0
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +394 -77
- package/src/runtime/stealth-cookies.ts +132 -0
- package/src/runtime/stealth.ts +1280 -336
- package/src/runtime/stt.ts +1 -19
- package/src/runtime/timeout.ts +18 -0
- package/src/runtime/trace-config.ts +78 -0
- package/src/runtime/trace.ts +57 -17
- package/src/serve.ts +6 -1
- package/src/server/error-observability.ts +1 -0
- package/src/server/index.ts +34 -2
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/self-test.ts +191 -68
- package/src/server/serve-implementation.ts +3378 -0
- package/src/server/serve.ts +1 -1632
- package/src/server/trace-output.ts +129 -0
- package/src/server/types.ts +13 -1
- 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 +202 -231
- package/src/stream-evidence.ts +988 -0
- package/src/stream.ts +8 -1
- package/src/testing/index.ts +10 -1
- package/src/testing/run.ts +658 -15
- package/src/trace-sanitization.ts +63 -0
- package/src/types.ts +788 -174
- 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, 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,
|
|
@@ -145,6 +158,7 @@ function telemetryForPool(pool, cacheStatus, startedAt, extra = {}) {
|
|
|
145
158
|
function telemetryForFailure(cacheStatus, startedAt, extra = {}) {
|
|
146
159
|
return {
|
|
147
160
|
provider: "smartproxy",
|
|
161
|
+
outcome: "error",
|
|
148
162
|
cacheStatus,
|
|
149
163
|
cacheHit: false,
|
|
150
164
|
resolutionMs: Math.max(0, Date.now() - startedAt),
|
|
@@ -210,59 +224,7 @@ function serializeSmartproxyPool(pool) {
|
|
|
210
224
|
}
|
|
211
225
|
function normalizeProxyUrl(url) {
|
|
212
226
|
const normalized = url?.trim();
|
|
213
|
-
return normalized
|
|
214
|
-
}
|
|
215
|
-
function readPositiveIntegerEnv(name) {
|
|
216
|
-
const raw = process.env[name]?.trim();
|
|
217
|
-
if (!raw)
|
|
218
|
-
return undefined;
|
|
219
|
-
if (!/^[1-9]\d*$/.test(raw)) {
|
|
220
|
-
throw new Error(`${name} must be a positive integer`);
|
|
221
|
-
}
|
|
222
|
-
return raw;
|
|
223
|
-
}
|
|
224
|
-
function applyStickyProxySession(proxyUrl) {
|
|
225
|
-
let parsed;
|
|
226
|
-
try {
|
|
227
|
-
parsed = new URL(proxyUrl);
|
|
228
|
-
}
|
|
229
|
-
catch {
|
|
230
|
-
return proxyUrl;
|
|
231
|
-
}
|
|
232
|
-
if (!parsed.hostname || !parsed.username || !parsed.password) {
|
|
233
|
-
return proxyUrl;
|
|
234
|
-
}
|
|
235
|
-
const host = parsed.hostname.toLowerCase();
|
|
236
|
-
if (!host.includes("smartproxy") && !host.includes("decodo")) {
|
|
237
|
-
return proxyUrl;
|
|
238
|
-
}
|
|
239
|
-
const username = decodeURIComponent(parsed.username);
|
|
240
|
-
const sessionId = process.env.APIFUSE__PROXY__SESSION_ID?.trim() || "apifuse-shared";
|
|
241
|
-
const sessionDuration = readPositiveIntegerEnv("APIFUSE__PROXY__SESSION_DURATION");
|
|
242
|
-
const stickyUsername = host.includes("smartproxy")
|
|
243
|
-
? buildSmartproxyUsername(username, sessionId, sessionDuration)
|
|
244
|
-
: buildDecodoUsername(username, sessionId, sessionDuration ?? "60");
|
|
245
|
-
parsed.username = stickyUsername;
|
|
246
|
-
return parsed.toString();
|
|
247
|
-
}
|
|
248
|
-
function buildSmartproxyUsername(username, sessionId, sessionDuration) {
|
|
249
|
-
const parts = username.split("_");
|
|
250
|
-
const configuredLife = parts.find((part) => part.startsWith("life-"))?.slice("life-".length);
|
|
251
|
-
const baseUsername = parts
|
|
252
|
-
.filter((part) => !part.startsWith("session-") && !part.startsWith("life-"))
|
|
253
|
-
.join("_");
|
|
254
|
-
return `${baseUsername}_session-${sessionId}_life-${sessionDuration ?? configuredLife ?? "60"}`;
|
|
255
|
-
}
|
|
256
|
-
function buildDecodoUsername(username, sessionId, sessionDuration) {
|
|
257
|
-
const withoutSticky = username.replace(/-session-.+-sessionduration-\d+$/, "");
|
|
258
|
-
const baseUsername = withoutSticky.startsWith("user-") ? withoutSticky : `user-${withoutSticky}`;
|
|
259
|
-
return `${baseUsername}-session-${sessionId}-sessionduration-${sessionDuration}`;
|
|
260
|
-
}
|
|
261
|
-
function syncProxyEnv(config) {
|
|
262
|
-
const configProxyUrl = normalizeProxyUrl(config.proxy?.url);
|
|
263
|
-
if (!process.env.APIFUSE__PROXY__URL && configProxyUrl) {
|
|
264
|
-
process.env.APIFUSE__PROXY__URL = configProxyUrl;
|
|
265
|
-
}
|
|
227
|
+
return normalized || undefined;
|
|
266
228
|
}
|
|
267
229
|
export function resolveProxyConfig(options = {}) {
|
|
268
230
|
const explicitProxyUrl = normalizeProxyUrl(options.proxy);
|
|
@@ -277,14 +239,6 @@ export function resolveProxyConfig(options = {}) {
|
|
|
277
239
|
if (!legacyProxyRequested) {
|
|
278
240
|
return { shouldWarn: false };
|
|
279
241
|
}
|
|
280
|
-
const envProxyUrl = normalizeProxyUrl(process.env.APIFUSE__PROXY__URL);
|
|
281
|
-
if (envProxyUrl) {
|
|
282
|
-
return { shouldWarn: false, url: envProxyUrl };
|
|
283
|
-
}
|
|
284
|
-
const configuredProxyUrl = normalizeProxyUrl(options.apifuseConfig?.proxy?.url);
|
|
285
|
-
if (configuredProxyUrl) {
|
|
286
|
-
return { shouldWarn: false, url: configuredProxyUrl };
|
|
287
|
-
}
|
|
288
242
|
return { shouldWarn: true };
|
|
289
243
|
}
|
|
290
244
|
export async function resolveProxyConfigAsync(options = {}) {
|
|
@@ -299,47 +253,212 @@ export async function resolveProxyConfigAsync(options = {}) {
|
|
|
299
253
|
if (policy.mode === "disabled") {
|
|
300
254
|
return { shouldWarn: false };
|
|
301
255
|
}
|
|
302
|
-
const
|
|
303
|
-
if (
|
|
256
|
+
const chain = resolveVendorChain(policy);
|
|
257
|
+
if (chain.length === 0) {
|
|
258
|
+
const declared = declaredVendorChain(policy);
|
|
259
|
+
const deprecated = declared.filter((vendor) => vendor === "decodo" || vendor === "custom");
|
|
260
|
+
if (policy.mode === "required") {
|
|
261
|
+
const providerIds = declared.length > 0 ? declared.map((vendor) => `"${vendor}"`).join(", ") : "none";
|
|
262
|
+
const deprecatedDetail = deprecated.length > 0
|
|
263
|
+
? ` Deprecated vendor(s): ${deprecated.map((vendor) => `"${vendor}"`).join(", ")}.`
|
|
264
|
+
: "";
|
|
265
|
+
throw new ProxyResolutionError("PROXY_REQUIRED", `Required proxy policy has no SDK-managed adapter for provider id(s): ${providerIds}.${deprecatedDetail} Use "smartproxy" or "nodemaven".`);
|
|
266
|
+
}
|
|
267
|
+
// Deprecated decodo/custom providers have no SDK-managed adapter. Optional
|
|
268
|
+
// policies preserve the warning-only behavior and may continue directly.
|
|
304
269
|
return resolveProxyConfig({
|
|
305
270
|
...options,
|
|
306
271
|
upstream: { proxy: true },
|
|
307
272
|
});
|
|
308
273
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
274
|
+
// Protocol is chosen per vendor (each vendor's benchmarked-best), with an
|
|
275
|
+
// optional explicit override for the harness/tests. Both are tunnelling
|
|
276
|
+
// schemes. transportProtocols is what the calling transport can actually use.
|
|
277
|
+
const transportProtocols = options.transportProtocols ?? ["http", "socks5"];
|
|
278
|
+
const sizes = chain.map((vendor) => vendorPoolSize(vendor, policy));
|
|
279
|
+
const total = sizes.reduce((sum, size) => sum + size, 0);
|
|
280
|
+
const normalizedAttempt = normalizeAttemptIndex(options.proxyAttempt);
|
|
281
|
+
const { vendorIndex: startVendorIndex, poolIndex: startPoolIndex } = mapFlatAttempt(total > 0 ? normalizedAttempt % total : 0, sizes);
|
|
282
|
+
const refreshEpoch = normalizeAttemptIndex(options.proxyRefreshEpoch);
|
|
283
|
+
let lastError;
|
|
284
|
+
let blockedProtocol;
|
|
285
|
+
for (let vendorIndex = startVendorIndex; vendorIndex < chain.length; vendorIndex++) {
|
|
286
|
+
const vendor = chain[vendorIndex];
|
|
287
|
+
const nextVendor = chain[vendorIndex + 1];
|
|
288
|
+
const poolIndex = vendorIndex === startVendorIndex ? startPoolIndex : 0;
|
|
289
|
+
const protocol = options.protocol ?? VENDOR_DEFAULT_PROTOCOL[vendor];
|
|
290
|
+
if (!vendorHasCredentials(vendor, options.engineCredentials)) {
|
|
291
|
+
options.telemetry?.recordProxyVendorFailover?.({
|
|
292
|
+
vendor,
|
|
293
|
+
nextVendor,
|
|
294
|
+
phase: "resolution",
|
|
295
|
+
reason: "no_credentials",
|
|
296
|
+
});
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
// The calling transport must be able to use this vendor's protocol; if not,
|
|
300
|
+
// fail over to the next vendor rather than silently downgrading.
|
|
301
|
+
if (!transportProtocols.includes(protocol)) {
|
|
302
|
+
blockedProtocol = protocol;
|
|
303
|
+
options.telemetry?.recordProxyVendorFailover?.({
|
|
304
|
+
vendor,
|
|
305
|
+
nextVendor,
|
|
306
|
+
phase: "resolution",
|
|
307
|
+
reason: "protocol_unsupported",
|
|
308
|
+
});
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
return await resolveWithVendor(vendor, policy, options, {
|
|
313
|
+
protocol,
|
|
314
|
+
poolIndex,
|
|
315
|
+
refreshEpoch,
|
|
316
|
+
credentials: options.engineCredentials,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
// Config/programming errors (invalid filter, etc.) are not vendor
|
|
321
|
+
// outages — propagate them rather than failing over.
|
|
322
|
+
if (!(error instanceof ProxyResolutionError)) {
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
if (error.telemetry) {
|
|
326
|
+
options.telemetry?.recordProxyResolution(error.telemetry);
|
|
327
|
+
}
|
|
328
|
+
lastError = error;
|
|
329
|
+
options.telemetry?.recordProxyVendorFailover?.({
|
|
330
|
+
vendor,
|
|
331
|
+
nextVendor,
|
|
332
|
+
phase: "resolution",
|
|
333
|
+
reason: "allocation_failed",
|
|
334
|
+
});
|
|
313
335
|
}
|
|
314
|
-
return { shouldWarn: true };
|
|
315
336
|
}
|
|
316
|
-
|
|
337
|
+
if (policy.mode === "required") {
|
|
338
|
+
if (lastError) {
|
|
339
|
+
throw lastError instanceof ProxyResolutionError
|
|
340
|
+
? lastError
|
|
341
|
+
: new ProxyResolutionError("PROXY_ALLOCATION_FAILED", `All proxy vendors [${chain.join(", ")}] failed for required proxy egress.`, { cause: lastError, vendorChain: chain });
|
|
342
|
+
}
|
|
343
|
+
if (blockedProtocol) {
|
|
344
|
+
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 });
|
|
345
|
+
}
|
|
346
|
+
throw new ProxyResolutionError("PROXY_REQUIRED", `Proxy egress is required but no vendor credentials are configured. Missing: ${chain
|
|
347
|
+
.map((vendor) => `${missingCredentialEnv(vendor)} (${vendor})`)
|
|
348
|
+
.join(", ")}.`, { vendorChain: chain });
|
|
349
|
+
}
|
|
350
|
+
return { shouldWarn: true };
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Resolve the proxy URL for a provider-owned consumer such as a CAPTCHA solver.
|
|
354
|
+
* Vendor allocation and failover remain owned by the SDK.
|
|
355
|
+
*/
|
|
356
|
+
export async function resolveProxy(options = {}) {
|
|
357
|
+
const resolved = await resolveProxyConfigAsync(options);
|
|
358
|
+
const vendor = vendorFromResolvedSource(resolved.source);
|
|
359
|
+
return vendor ? { ...resolved, vendor } : resolved;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Each vendor's default egress protocol, chosen from live KR benchmarks. HTTP
|
|
363
|
+
* CONNECT wins for nodemaven (socks5 adds ~500ms through the gateway) and ties
|
|
364
|
+
* for smartproxy, and is the only protocol ctx.http (Bun native fetch) supports.
|
|
365
|
+
* Override per call via ProxyResolutionOptions.protocol (harness/tests).
|
|
366
|
+
*/
|
|
367
|
+
export const VENDOR_DEFAULT_PROTOCOL = {
|
|
368
|
+
smartproxy: "http",
|
|
369
|
+
nodemaven: NODEMAVEN_DEFAULT_PROTOCOL,
|
|
370
|
+
};
|
|
371
|
+
/**
|
|
372
|
+
* Guard the No-MITM invariant: a resolved proxy URL must use a tunnelling scheme
|
|
373
|
+
* (http CONNECT or socks5) so the client TLS handshake reaches the origin
|
|
374
|
+
* end-to-end. Anything else would intercept TLS and break fingerprinting.
|
|
375
|
+
*/
|
|
376
|
+
export function assertTunnelingScheme(url) {
|
|
377
|
+
let scheme;
|
|
317
378
|
try {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
379
|
+
scheme = new URL(url).protocol.replace(/:$/, "").toLowerCase();
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
throw new ProxyResolutionError("PROXY_ALLOCATION_FAILED", `Malformed proxy URL: ${url}`);
|
|
383
|
+
}
|
|
384
|
+
if (scheme !== "http" && scheme !== "socks5") {
|
|
385
|
+
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.`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
export async function resolveWithVendor(vendor, policy, options, context) {
|
|
389
|
+
if (vendor === "nodemaven") {
|
|
390
|
+
const startedAt = Date.now();
|
|
391
|
+
const username = (context.credentials === undefined
|
|
392
|
+
? process.env[NODEMAVEN_USERNAME_ENV]
|
|
393
|
+
: context.credentials[NODEMAVEN_USERNAME_ENV])?.trim();
|
|
394
|
+
const password = (context.credentials === undefined
|
|
395
|
+
? process.env[NODEMAVEN_PASSWORD_ENV]
|
|
396
|
+
: context.credentials[NODEMAVEN_PASSWORD_ENV])?.trim();
|
|
397
|
+
const filter = context.credentials === undefined
|
|
398
|
+
? process.env[NODEMAVEN_FILTER_ENV]
|
|
399
|
+
: context.credentials[NODEMAVEN_FILTER_ENV];
|
|
400
|
+
if (!username || !password) {
|
|
401
|
+
throw new ProxyResolutionError("PROXY_ALLOCATION_FAILED", `NodeMaven credentials missing: set ${NODEMAVEN_USERNAME_ENV} and ${NODEMAVEN_PASSWORD_ENV}.`, { vendor: "nodemaven" });
|
|
402
|
+
}
|
|
403
|
+
const synthesized = synthesizeNodemavenProxy({
|
|
404
|
+
policy,
|
|
405
|
+
credentials: {
|
|
406
|
+
username,
|
|
407
|
+
password,
|
|
408
|
+
...(filter ? { filter } : {}),
|
|
409
|
+
},
|
|
410
|
+
affinityKey: options.affinityKey,
|
|
411
|
+
protocol: context.protocol,
|
|
412
|
+
poolIndex: context.poolIndex,
|
|
413
|
+
refreshEpoch: context.refreshEpoch,
|
|
414
|
+
country: resolveSmartproxyCountry(policy, context.ambientDefaults !== false),
|
|
415
|
+
});
|
|
416
|
+
options.telemetry?.recordProxyResolution({
|
|
417
|
+
provider: "nodemaven",
|
|
418
|
+
protocol: synthesized.protocol,
|
|
419
|
+
cacheStatus: "disabled",
|
|
420
|
+
cacheHit: false,
|
|
421
|
+
resolutionMs: Math.max(0, Date.now() - startedAt),
|
|
422
|
+
attempts: 1,
|
|
423
|
+
});
|
|
424
|
+
assertTunnelingScheme(synthesized.url);
|
|
321
425
|
return {
|
|
322
426
|
shouldWarn: false,
|
|
323
|
-
url:
|
|
324
|
-
source: "
|
|
427
|
+
url: synthesized.url,
|
|
428
|
+
source: "nodemaven-gateway",
|
|
429
|
+
protocol: synthesized.protocol,
|
|
325
430
|
diagnostics: {
|
|
326
|
-
...
|
|
327
|
-
|
|
328
|
-
poolIndex,
|
|
431
|
+
...synthesized.diagnostics,
|
|
432
|
+
poolIndex: context.poolIndex,
|
|
329
433
|
},
|
|
330
434
|
};
|
|
331
435
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
436
|
+
// smartproxy allocation-style vendor.
|
|
437
|
+
const appKey = (context.credentials === undefined
|
|
438
|
+
? process.env[SMARTPROXY_APP_KEY_ENV]
|
|
439
|
+
: context.credentials[SMARTPROXY_APP_KEY_ENV])?.trim();
|
|
440
|
+
if (!appKey) {
|
|
441
|
+
// Guarded by vendorHasCredentials; treated as a vendor-internal failure.
|
|
442
|
+
throw new ProxyResolutionError("PROXY_ALLOCATION_FAILED", `${SMARTPROXY_APP_KEY_ENV} is not configured.`, { vendor: "smartproxy" });
|
|
443
|
+
}
|
|
444
|
+
const lifetimeMinutes = resolveSmartproxyLifetime(policy, context.ambientDefaults !== false);
|
|
445
|
+
const allocated = await allocateSmartproxy(policy, appKey, lifetimeMinutes, options.affinityKey, context.protocol, context.ambientDefaults !== false, context.sharedCache !== false);
|
|
446
|
+
options.telemetry?.recordProxyResolution({ ...allocated.telemetry, protocol: context.protocol });
|
|
447
|
+
const poolIndex = selectProxyPoolIndex(allocated.pool.urls.length, context.poolIndex);
|
|
448
|
+
const url = allocated.pool.urls[poolIndex];
|
|
449
|
+
if (url)
|
|
450
|
+
assertTunnelingScheme(url);
|
|
451
|
+
return {
|
|
452
|
+
shouldWarn: false,
|
|
453
|
+
url,
|
|
454
|
+
source: "smartproxy-allocator",
|
|
455
|
+
protocol: context.protocol,
|
|
456
|
+
diagnostics: {
|
|
457
|
+
...allocated.pool.diagnostics,
|
|
458
|
+
poolSize: allocated.pool.urls.length,
|
|
459
|
+
poolIndex,
|
|
460
|
+
},
|
|
461
|
+
};
|
|
343
462
|
}
|
|
344
463
|
function resolvePolicy(options) {
|
|
345
464
|
if (options.proxyPolicy) {
|
|
@@ -351,14 +470,187 @@ function resolvePolicy(options) {
|
|
|
351
470
|
}
|
|
352
471
|
return undefined;
|
|
353
472
|
}
|
|
354
|
-
function
|
|
355
|
-
return
|
|
473
|
+
function isRegistryVendor(name) {
|
|
474
|
+
return name === "smartproxy" || name === "nodemaven";
|
|
475
|
+
}
|
|
476
|
+
function declaredVendorChain(policy) {
|
|
477
|
+
const declared = policy.providers?.length
|
|
478
|
+
? policy.providers
|
|
479
|
+
: [policy.provider ?? envDefaultProvider()];
|
|
480
|
+
return declared.filter((vendor) => vendor !== undefined);
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Ordered list of SDK-native proxy vendors declared by the policy. `providers`
|
|
484
|
+
* takes precedence over the legacy singular `provider`; the platform default
|
|
485
|
+
* env is the final fallback. Non-registry names (decodo/custom) are dropped so
|
|
486
|
+
* an all-deprecated chain has no managed adapter.
|
|
487
|
+
*/
|
|
488
|
+
export function resolveVendorChain(policy) {
|
|
489
|
+
const chain = [];
|
|
490
|
+
for (const name of declaredVendorChain(policy)) {
|
|
491
|
+
if (isRegistryVendor(name) && !chain.includes(name)) {
|
|
492
|
+
chain.push(name);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return chain;
|
|
496
|
+
}
|
|
497
|
+
function envDefaultProvider() {
|
|
498
|
+
const raw = process.env[DEFAULT_PROXY_PROVIDER_ENV]?.trim().toLowerCase();
|
|
499
|
+
return raw ?? undefined;
|
|
500
|
+
}
|
|
501
|
+
function vendorHasCredentials(vendor, credentials) {
|
|
502
|
+
if (vendor === "nodemaven") {
|
|
503
|
+
return Boolean((credentials === undefined
|
|
504
|
+
? process.env[NODEMAVEN_USERNAME_ENV]
|
|
505
|
+
: credentials[NODEMAVEN_USERNAME_ENV])?.trim() &&
|
|
506
|
+
(credentials === undefined
|
|
507
|
+
? process.env[NODEMAVEN_PASSWORD_ENV]
|
|
508
|
+
: credentials[NODEMAVEN_PASSWORD_ENV])?.trim());
|
|
509
|
+
}
|
|
510
|
+
return Boolean((credentials === undefined
|
|
511
|
+
? process.env[SMARTPROXY_APP_KEY_ENV]
|
|
512
|
+
: credentials[SMARTPROXY_APP_KEY_ENV])?.trim());
|
|
513
|
+
}
|
|
514
|
+
function missingCredentialEnv(vendor) {
|
|
515
|
+
return vendor === "nodemaven" ? "APIFUSE__PROXY__NODEMAVEN_USERNAME" : SMARTPROXY_APP_KEY_ENV;
|
|
356
516
|
}
|
|
357
|
-
function
|
|
358
|
-
return
|
|
517
|
+
function vendorPoolSize(vendor, policy) {
|
|
518
|
+
return vendor === "nodemaven" ? nodemavenPoolSize(policy) : resolveSmartproxyPoolSize(policy);
|
|
359
519
|
}
|
|
360
|
-
|
|
361
|
-
|
|
520
|
+
/**
|
|
521
|
+
* Total attempt span across a policy's vendor chain — the sum of each vendor's
|
|
522
|
+
* pool size. Transports use this so successive attempts rotate a vendor's pool
|
|
523
|
+
* and then fail over to the next vendor via the flat attempt index. With one
|
|
524
|
+
* vendor this equals that vendor's pool size (today's behaviour).
|
|
525
|
+
*/
|
|
526
|
+
export function resolvePolicyProxyPoolSpan(policy) {
|
|
527
|
+
const chain = resolveVendorChain(policy);
|
|
528
|
+
if (chain.length === 0)
|
|
529
|
+
return resolveSmartproxyPoolSize(policy);
|
|
530
|
+
return chain.reduce((sum, vendor) => sum + vendorPoolSize(vendor, policy), 0);
|
|
531
|
+
}
|
|
532
|
+
function vendorMaxPoolSize(vendor) {
|
|
533
|
+
return vendor === "nodemaven" ? NODEMAVEN_MAX_POOL_SIZE : SMARTPROXY_MAX_POOL_SIZE;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Absolute upper bound on a chain's attempt span — the sum of each vendor's
|
|
537
|
+
* *maximum* pool size. Unlike `resolvePolicyProxyPoolSpan` (the configured
|
|
538
|
+
* span), this backstop is independent of `session.poolSize`, so it never
|
|
539
|
+
* truncates a legitimately large pool below the point where the flat attempt
|
|
540
|
+
* index would cross into the next vendor (e.g. a 50-slot NodeMaven pool).
|
|
541
|
+
*/
|
|
542
|
+
export function maxPolicyProxyPoolSpan(policy) {
|
|
543
|
+
const chain = resolveVendorChain(policy);
|
|
544
|
+
if (chain.length === 0)
|
|
545
|
+
return SMARTPROXY_MAX_POOL_SIZE;
|
|
546
|
+
return chain.reduce((sum, vendor) => sum + vendorMaxPoolSize(vendor), 0);
|
|
547
|
+
}
|
|
548
|
+
const UNSAFE_TRANSPORT_RETRY_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE", "TRACE"]);
|
|
549
|
+
/**
|
|
550
|
+
* Transport-retry attempt cap for a policy-managed request. A transport failure
|
|
551
|
+
* rotates the flat attempt index onto the *next* endpoint (and, once the index
|
|
552
|
+
* passes the primary vendor's pool span, the *next vendor*), so the cap must be
|
|
553
|
+
* the chain's full pool span for failover to reach the fallback vendor — the
|
|
554
|
+
* per-endpoint retry budget (default 3) never gets there.
|
|
555
|
+
*
|
|
556
|
+
* The span only widens beyond the caller's retry budget when ALL hold:
|
|
557
|
+
* - the request is policy-allocator managed (not a caller-supplied proxy URL);
|
|
558
|
+
* - the caller did NOT pin an explicit retry policy — `HttpRetryOptions.attempts`
|
|
559
|
+
* is the documented total-attempt ceiling and must be honoured verbatim;
|
|
560
|
+
* - the method is safe/idempotent — an unsafe request must never be duplicated
|
|
561
|
+
* across the pool even if some framework default would allow it;
|
|
562
|
+
* - the policy resolves a non-empty *registry* vendor chain (smartproxy /
|
|
563
|
+
* nodemaven). Deprecated vendors (custom / decodo) resolve no managed pool,
|
|
564
|
+
* so there is no possible endpoint crossover — they keep the retry budget.
|
|
565
|
+
*
|
|
566
|
+
* The widened cap is bounded by the chain's true maximum span (sum of each
|
|
567
|
+
* vendor's max pool size), so a large NodeMaven pool (≤50) stays reachable and
|
|
568
|
+
* a pathological chain can never spin unbounded.
|
|
569
|
+
*/
|
|
570
|
+
/**
|
|
571
|
+
* True when a policy request is in *implicit chain-rotation* mode: successive
|
|
572
|
+
* transport attempts rotate the flat index across the concatenated vendor pool
|
|
573
|
+
* spans (and, past the primary vendor's span, into the fallback vendor). This is
|
|
574
|
+
* the ONLY mode in which the transport loop widens its attempt cap AND
|
|
575
|
+
* de-duplicates repeated endpoints — the two behaviours must share one predicate
|
|
576
|
+
* so they never diverge. It holds when ALL of the widening conditions hold:
|
|
577
|
+
* - the request is policy-allocator managed (not a caller-supplied proxy URL);
|
|
578
|
+
* - the caller did NOT pin an explicit retry policy — its `attempts` ceiling is
|
|
579
|
+
* the documented contract and must be honoured verbatim against whatever
|
|
580
|
+
* endpoint each attempt resolves (even a repeated one), so no de-duplication;
|
|
581
|
+
* - the method is safe/idempotent — an unsafe request is never duplicated;
|
|
582
|
+
* - the policy resolves a non-empty registry vendor chain (smartproxy /
|
|
583
|
+
* nodemaven). Deprecated vendors (custom / decodo) resolve no managed
|
|
584
|
+
* endpoint, so there is nothing to rotate or de-duplicate.
|
|
585
|
+
*/
|
|
586
|
+
export function policyRotatesTransportVendorChain(input) {
|
|
587
|
+
if (!input.usesPolicyAllocator || !input.policy || input.explicitRetry) {
|
|
588
|
+
return false;
|
|
589
|
+
}
|
|
590
|
+
if (UNSAFE_TRANSPORT_RETRY_METHODS.has(input.method.toUpperCase())) {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
return resolveVendorChain(input.policy).length > 0;
|
|
594
|
+
}
|
|
595
|
+
export function resolvePolicyTransportAttemptCap(input) {
|
|
596
|
+
const budget = Math.max(1, Math.floor(input.retryAttempts));
|
|
597
|
+
if (!policyRotatesTransportVendorChain({
|
|
598
|
+
policy: input.policy,
|
|
599
|
+
usesPolicyAllocator: input.usesPolicyAllocator,
|
|
600
|
+
explicitRetry: input.explicitRetry,
|
|
601
|
+
method: input.method,
|
|
602
|
+
})) {
|
|
603
|
+
return budget;
|
|
604
|
+
}
|
|
605
|
+
const span = Math.min(maxPolicyProxyPoolSpan(input.policy), resolvePolicyProxyPoolSpan(input.policy));
|
|
606
|
+
return Math.max(budget, span);
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* A registry vendor chain (smartproxy/nodemaven) resolves a potentially
|
|
610
|
+
* *different* endpoint per flat attempt index, so a transport retry should
|
|
611
|
+
* advance across endpoints and de-duplicate once the chain stops yielding new
|
|
612
|
+
* ones. Deprecated custom/decodo policies have an empty registry chain and no
|
|
613
|
+
* managed endpoint, so the transport loop has nothing to rotate or de-duplicate.
|
|
614
|
+
*/
|
|
615
|
+
export function policyResolvesRegistryVendorChain(policy) {
|
|
616
|
+
return Boolean(policy) && resolveVendorChain(policy).length > 0;
|
|
617
|
+
}
|
|
618
|
+
/** Map a resolved proxy source label to the vendor that served it. */
|
|
619
|
+
export function vendorFromResolvedSource(source) {
|
|
620
|
+
if (source === "nodemaven-gateway")
|
|
621
|
+
return "nodemaven";
|
|
622
|
+
if (source === "smartproxy-allocator")
|
|
623
|
+
return "smartproxy";
|
|
624
|
+
return undefined;
|
|
625
|
+
}
|
|
626
|
+
function normalizeAttemptIndex(attempt) {
|
|
627
|
+
return Number.isFinite(attempt) ? Math.max(0, Math.floor(attempt)) : 0;
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Map a flat attempt index into (vendorIndex, poolIndex) by concatenating each
|
|
631
|
+
* vendor's pool space in chain order. With a single vendor this reduces to
|
|
632
|
+
* `attempt % poolSize`, preserving today's behaviour exactly.
|
|
633
|
+
*/
|
|
634
|
+
export function mapFlatAttempt(flat, sizes) {
|
|
635
|
+
let cursor = flat;
|
|
636
|
+
for (let vendorIndex = 0; vendorIndex < sizes.length; vendorIndex++) {
|
|
637
|
+
const size = Math.max(1, sizes[vendorIndex] ?? 1);
|
|
638
|
+
if (cursor < size) {
|
|
639
|
+
return { vendorIndex, poolIndex: cursor };
|
|
640
|
+
}
|
|
641
|
+
cursor -= size;
|
|
642
|
+
}
|
|
643
|
+
return { vendorIndex: 0, poolIndex: 0 };
|
|
644
|
+
}
|
|
645
|
+
function resolveSmartproxyCountry(policy, ambientDefaults = true) {
|
|
646
|
+
return (policy.geo?.country ??
|
|
647
|
+
(ambientDefaults
|
|
648
|
+
? process.env[DEFAULT_PROXY_COUNTRY_ENV]?.trim().toUpperCase() || undefined
|
|
649
|
+
: undefined));
|
|
650
|
+
}
|
|
651
|
+
function resolveSmartproxyLifetime(policy, ambientDefaults = true) {
|
|
652
|
+
const configuredLifetime = policy.session?.lifetimeMinutes ??
|
|
653
|
+
(ambientDefaults ? readPositiveNumberEnv(DEFAULT_PROXY_LIFETIME_ENV, 30) : 30);
|
|
362
654
|
return Math.min(SMARTPROXY_MAX_LIFETIME_MINUTES, Math.max(1, Math.floor(configuredLifetime)));
|
|
363
655
|
}
|
|
364
656
|
function readPositiveNumberEnv(name, fallback) {
|
|
@@ -381,19 +673,24 @@ function selectProxyPoolIndex(poolSize, attempt = 0) {
|
|
|
381
673
|
const normalizedAttempt = Number.isFinite(attempt) ? Math.max(0, Math.floor(attempt)) : 0;
|
|
382
674
|
return normalizedAttempt % poolSize;
|
|
383
675
|
}
|
|
384
|
-
function buildSmartproxyCacheKey(policy, affinityKey, lifetimeMinutes) {
|
|
676
|
+
function buildSmartproxyCacheKey(policy, appKey, affinityKey, lifetimeMinutes, protocol, ambientDefaults = true) {
|
|
385
677
|
const poolSize = resolveSmartproxyPoolSize(policy);
|
|
386
678
|
return JSON.stringify({
|
|
387
679
|
provider: "smartproxy",
|
|
388
|
-
|
|
680
|
+
credentialHash: createHash("sha256")
|
|
681
|
+
.update("apifuse-smartproxy-credential:v1\0")
|
|
682
|
+
.update(appKey)
|
|
683
|
+
.digest("hex"),
|
|
684
|
+
protocol,
|
|
685
|
+
country: resolveSmartproxyCountry(policy, ambientDefaults),
|
|
389
686
|
affinity: policy.session?.affinity ?? "request",
|
|
390
687
|
affinityKey: (policy.session?.affinity ?? "request") === "request" ? undefined : affinityKey,
|
|
391
688
|
lifetimeMinutes,
|
|
392
689
|
poolSize,
|
|
393
690
|
});
|
|
394
691
|
}
|
|
395
|
-
async function allocateSmartproxy(policy, appKey, lifetimeMinutes, affinityKey) {
|
|
396
|
-
const cacheKey = buildSmartproxyCacheKey(policy, affinityKey, lifetimeMinutes);
|
|
692
|
+
async function allocateSmartproxy(policy, appKey, lifetimeMinutes, affinityKey, protocol, ambientDefaults = true, sharedCache = true) {
|
|
693
|
+
const cacheKey = buildSmartproxyCacheKey(policy, appKey, affinityKey, lifetimeMinutes, protocol, ambientDefaults);
|
|
397
694
|
const startedAt = Date.now();
|
|
398
695
|
const now = startedAt;
|
|
399
696
|
const invalidatedUntil = invalidatedProxyKeys.get(cacheKey) ?? 0;
|
|
@@ -401,7 +698,7 @@ async function allocateSmartproxy(policy, appKey, lifetimeMinutes, affinityKey)
|
|
|
401
698
|
const cached = proxyCache.get(cacheKey);
|
|
402
699
|
if (!skipCached && cached && isFresh(cached, now)) {
|
|
403
700
|
if (shouldSoftRefresh(cached, now)) {
|
|
404
|
-
void refreshSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes);
|
|
701
|
+
void refreshSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes, protocol, ambientDefaults, sharedCache);
|
|
405
702
|
return {
|
|
406
703
|
pool: cached,
|
|
407
704
|
telemetry: telemetryForPool(cached, "soft_stale_refresh", startedAt, {
|
|
@@ -414,7 +711,7 @@ async function allocateSmartproxy(policy, appKey, lifetimeMinutes, affinityKey)
|
|
|
414
711
|
telemetry: telemetryForPool(cached, "memory_hit", startedAt),
|
|
415
712
|
};
|
|
416
713
|
}
|
|
417
|
-
if (!skipCached) {
|
|
714
|
+
if (!skipCached && sharedCache) {
|
|
418
715
|
const redisResult = await readSmartproxyRedisPool(cacheKey, startedAt);
|
|
419
716
|
if (redisResult)
|
|
420
717
|
return redisResult;
|
|
@@ -429,7 +726,7 @@ async function allocateSmartproxy(policy, appKey, lifetimeMinutes, affinityKey)
|
|
|
429
726
|
}),
|
|
430
727
|
};
|
|
431
728
|
}
|
|
432
|
-
const promise = allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinutes, startedAt).finally(() => {
|
|
729
|
+
const promise = allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinutes, startedAt, protocol, ambientDefaults, sharedCache).finally(() => {
|
|
433
730
|
proxyInflight.delete(cacheKey);
|
|
434
731
|
});
|
|
435
732
|
proxyInflight.set(cacheKey, promise);
|
|
@@ -460,9 +757,9 @@ async function readSmartproxyRedisPool(cacheKey, startedAt) {
|
|
|
460
757
|
telemetry: telemetryForPool(pool, "redis_hit", startedAt, { redisReadMs }),
|
|
461
758
|
};
|
|
462
759
|
}
|
|
463
|
-
async function refreshSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes) {
|
|
760
|
+
async function refreshSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes, protocol, ambientDefaults, sharedCache) {
|
|
464
761
|
try {
|
|
465
|
-
await allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinutes, Date.now(), {
|
|
762
|
+
await allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinutes, Date.now(), protocol, ambientDefaults, sharedCache, {
|
|
466
763
|
background: true,
|
|
467
764
|
});
|
|
468
765
|
}
|
|
@@ -470,10 +767,10 @@ async function refreshSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes)
|
|
|
470
767
|
// Soft refresh is opportunistic; current fresh pool remains usable.
|
|
471
768
|
}
|
|
472
769
|
}
|
|
473
|
-
async function allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinutes, startedAt, options = {}) {
|
|
474
|
-
const redis = getProxyRedis();
|
|
770
|
+
async function allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinutes, startedAt, protocol, ambientDefaults, sharedCache, options = {}) {
|
|
771
|
+
const redis = sharedCache ? getProxyRedis() : undefined;
|
|
475
772
|
if (!redis || !(await ensureRedisReady(redis))) {
|
|
476
|
-
return await allocateAndStoreSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes, startedAt, { cacheStatus: "allocator" });
|
|
773
|
+
return await allocateAndStoreSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes, startedAt, { cacheStatus: "allocator", protocol, ambientDefaults });
|
|
477
774
|
}
|
|
478
775
|
const poolKey = smartproxyRedisPoolKey(cacheKey);
|
|
479
776
|
const lockKey = smartproxyRedisLockKey(cacheKey);
|
|
@@ -487,6 +784,8 @@ async function allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinute
|
|
|
487
784
|
cacheStatus: options.background ? "soft_stale_refresh" : "allocator",
|
|
488
785
|
redis,
|
|
489
786
|
poolKey,
|
|
787
|
+
protocol,
|
|
788
|
+
ambientDefaults,
|
|
490
789
|
});
|
|
491
790
|
}
|
|
492
791
|
finally {
|
|
@@ -599,7 +898,7 @@ async function readSmartproxyAllocatorBodyWithDeadline(response, signal) {
|
|
|
599
898
|
}
|
|
600
899
|
async function allocateAndStoreSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes, startedAt, options) {
|
|
601
900
|
const poolSize = resolveSmartproxyPoolSize(policy);
|
|
602
|
-
const allocatorUrl = buildSmartproxyAllocatorUrl(policy, appKey, lifetimeMinutes, poolSize);
|
|
901
|
+
const allocatorUrl = buildSmartproxyAllocatorUrl(policy, appKey, lifetimeMinutes, poolSize, options.protocol, options.ambientDefaults);
|
|
603
902
|
const allocatorStartedAt = Date.now();
|
|
604
903
|
const allocatorDeadlineAt = allocatorStartedAt + smartproxyAllocatorDeadlineMs();
|
|
605
904
|
let allocation;
|
|
@@ -609,7 +908,7 @@ async function allocateAndStoreSmartproxyPool(cacheKey, policy, appKey, lifetime
|
|
|
609
908
|
lastFailure = smartproxyAllocatorDeadlineFailure(attempt);
|
|
610
909
|
break;
|
|
611
910
|
}
|
|
612
|
-
const attemptResult = await fetchSmartproxyAllocatorAttempt(allocatorUrl, attempt, allocatorDeadlineAt);
|
|
911
|
+
const attemptResult = await fetchSmartproxyAllocatorAttempt(allocatorUrl, attempt, allocatorDeadlineAt, options.protocol);
|
|
613
912
|
if (attemptResult.ok) {
|
|
614
913
|
allocation = attemptResult;
|
|
615
914
|
break;
|
|
@@ -657,7 +956,7 @@ async function allocateAndStoreSmartproxyPool(cacheKey, policy, appKey, lifetime
|
|
|
657
956
|
expiresAt: allocatedAt + ttlMs,
|
|
658
957
|
diagnostics: {
|
|
659
958
|
provider: "smartproxy",
|
|
660
|
-
country: resolveSmartproxyCountry(policy) ?? "default",
|
|
959
|
+
country: resolveSmartproxyCountry(policy, options.ambientDefaults) ?? "default",
|
|
661
960
|
lifetimeMinutes,
|
|
662
961
|
affinity: policy.session?.affinity ?? "request",
|
|
663
962
|
rawConnect: true,
|
|
@@ -692,7 +991,7 @@ async function allocateAndStoreSmartproxyPool(cacheKey, policy, appKey, lifetime
|
|
|
692
991
|
}),
|
|
693
992
|
};
|
|
694
993
|
}
|
|
695
|
-
async function fetchSmartproxyAllocatorAttempt(allocatorUrl, attempt, deadlineAt) {
|
|
994
|
+
async function fetchSmartproxyAllocatorAttempt(allocatorUrl, attempt, deadlineAt, protocol) {
|
|
696
995
|
const { controller, dispose } = createDeadlineAbortController(deadlineAt);
|
|
697
996
|
let response;
|
|
698
997
|
try {
|
|
@@ -735,7 +1034,7 @@ async function fetchSmartproxyAllocatorAttempt(allocatorUrl, attempt, deadlineAt
|
|
|
735
1034
|
bodyClass: "http_error",
|
|
736
1035
|
};
|
|
737
1036
|
}
|
|
738
|
-
const urls = parseSmartproxyAllocatorProxies(body);
|
|
1037
|
+
const urls = parseSmartproxyAllocatorProxies(body, protocol);
|
|
739
1038
|
const bodyClass = classifySmartproxyAllocatorBody(body, urls);
|
|
740
1039
|
if (urls.length === 0) {
|
|
741
1040
|
return {
|
|
@@ -770,17 +1069,24 @@ function smartproxyAllocatorFailureMessage(failure) {
|
|
|
770
1069
|
}
|
|
771
1070
|
return "Smartproxy allocator response did not contain a usable proxy endpoint.";
|
|
772
1071
|
}
|
|
773
|
-
|
|
1072
|
+
// Smartproxy get-ip-v3 `protocol` param: 1 = HTTP. The SOCKS5 value ("2") is a
|
|
1073
|
+
// best-effort mapping pending live vendor confirmation; http is the default and
|
|
1074
|
+
// the only value exercised in production today.
|
|
1075
|
+
const SMARTPROXY_PROTOCOL_PARAM = {
|
|
1076
|
+
http: "1",
|
|
1077
|
+
socks5: "2",
|
|
1078
|
+
};
|
|
1079
|
+
function buildSmartproxyAllocatorUrl(policy, appKey, lifetimeMinutes, poolSize, protocol, ambientDefaults = true) {
|
|
774
1080
|
const params = new URLSearchParams({
|
|
775
1081
|
app_key: appKey,
|
|
776
1082
|
pt: "9",
|
|
777
1083
|
num: String(poolSize),
|
|
778
1084
|
life: String(lifetimeMinutes),
|
|
779
|
-
protocol:
|
|
1085
|
+
protocol: SMARTPROXY_PROTOCOL_PARAM[protocol],
|
|
780
1086
|
format: "txt",
|
|
781
1087
|
lb: "\\n",
|
|
782
1088
|
});
|
|
783
|
-
const country = resolveSmartproxyCountry(policy);
|
|
1089
|
+
const country = resolveSmartproxyCountry(policy, ambientDefaults);
|
|
784
1090
|
if (country) {
|
|
785
1091
|
params.set("cc", country);
|
|
786
1092
|
}
|
|
@@ -788,7 +1094,8 @@ function buildSmartproxyAllocatorUrl(policy, appKey, lifetimeMinutes, poolSize)
|
|
|
788
1094
|
// old path 404s into the marketing site); the API lives on the api host.
|
|
789
1095
|
return `https://api.smartproxy.org/web_v1/ip/get-ip-v3?${params.toString()}`;
|
|
790
1096
|
}
|
|
791
|
-
function parseSmartproxyAllocatorProxies(body) {
|
|
1097
|
+
function parseSmartproxyAllocatorProxies(body, protocol) {
|
|
1098
|
+
const scheme = protocol === "socks5" ? "socks5" : "http";
|
|
792
1099
|
const trimmed = body.trim();
|
|
793
1100
|
if (!trimmed) {
|
|
794
1101
|
return [];
|
|
@@ -807,7 +1114,7 @@ function parseSmartproxyAllocatorProxies(body) {
|
|
|
807
1114
|
const port = "port" in item && (typeof item.port === "string" || typeof item.port === "number")
|
|
808
1115
|
? item.port
|
|
809
1116
|
: "";
|
|
810
|
-
return ip && port ?
|
|
1117
|
+
return ip && port ? `${scheme}://${ip}:${port}` : null;
|
|
811
1118
|
})
|
|
812
1119
|
.filter((url) => url !== null);
|
|
813
1120
|
}
|
|
@@ -819,7 +1126,7 @@ function parseSmartproxyAllocatorProxies(body) {
|
|
|
819
1126
|
.split(/\r?\n/)
|
|
820
1127
|
.map((item) => item.trim())
|
|
821
1128
|
.filter((item) => /^\d{1,3}(?:\.\d{1,3}){3}:\d{2,5}$/.test(item))
|
|
822
|
-
.map((line) =>
|
|
1129
|
+
.map((line) => `${scheme}://${line}`);
|
|
823
1130
|
}
|
|
824
1131
|
function classifySmartproxyAllocatorBody(body, urls) {
|
|
825
1132
|
if (urls.length > 0) {
|
|
@@ -847,11 +1154,16 @@ function markSmartproxyCacheInvalidated(options = {}) {
|
|
|
847
1154
|
if (!policy || policy.mode === "disabled") {
|
|
848
1155
|
return undefined;
|
|
849
1156
|
}
|
|
850
|
-
if (
|
|
1157
|
+
if (!resolveVendorChain(policy).includes("smartproxy")) {
|
|
851
1158
|
return undefined;
|
|
852
1159
|
}
|
|
853
1160
|
const lifetimeMinutes = resolveSmartproxyLifetime(policy);
|
|
854
|
-
const
|
|
1161
|
+
const appKey = (options.engineCredentials === undefined
|
|
1162
|
+
? process.env[SMARTPROXY_APP_KEY_ENV]
|
|
1163
|
+
: options.engineCredentials[SMARTPROXY_APP_KEY_ENV])?.trim();
|
|
1164
|
+
if (!appKey)
|
|
1165
|
+
return undefined;
|
|
1166
|
+
const cacheKey = buildSmartproxyCacheKey(policy, appKey, options.affinityKey, lifetimeMinutes, options.protocol ?? VENDOR_DEFAULT_PROTOCOL.smartproxy);
|
|
855
1167
|
invalidatedProxyKeys.set(cacheKey, Date.now() + SMARTPROXY_INVALIDATION_SKIP_REDIS_MS);
|
|
856
1168
|
proxyCache.delete(cacheKey);
|
|
857
1169
|
proxyInflight.delete(cacheKey);
|
|
@@ -897,16 +1209,12 @@ export async function loadApiFuseConfig(dir = process.cwd()) {
|
|
|
897
1209
|
const tsPath = path.resolve(dir, "apifuse.config.ts");
|
|
898
1210
|
if (existsSync(tsPath)) {
|
|
899
1211
|
const config = await importConfig(tsPath);
|
|
900
|
-
|
|
901
|
-
syncProxyEnv(resolvedConfig);
|
|
902
|
-
return resolvedConfig;
|
|
1212
|
+
return config ?? {};
|
|
903
1213
|
}
|
|
904
1214
|
const jsPath = path.resolve(dir, "apifuse.config.js");
|
|
905
1215
|
if (existsSync(jsPath)) {
|
|
906
1216
|
const config = await importConfig(jsPath);
|
|
907
|
-
|
|
908
|
-
syncProxyEnv(resolvedConfig);
|
|
909
|
-
return resolvedConfig;
|
|
1217
|
+
return config ?? {};
|
|
910
1218
|
}
|
|
911
1219
|
return {};
|
|
912
1220
|
}
|