@apifuse/provider-sdk 2.2.0-beta.16 → 2.2.0-beta.18
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 +40 -0
- package/CHANGELOG.md +9 -0
- package/dist/config/loader.d.ts +19 -0
- package/dist/config/loader.js +59 -28
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/native-address.d.ts +43 -0
- package/dist/native-address.js +281 -0
- package/dist/native-egress-policy.d.ts +4 -0
- package/dist/native-egress-policy.js +86 -23
- package/dist/provider.d.ts +1 -1
- package/dist/provider.js +1 -1
- package/dist/runtime/native-network.d.ts +46 -7
- package/dist/runtime/native-network.js +590 -114
- package/dist/runtime/proxy-nodemaven.d.ts +7 -0
- package/dist/runtime/proxy-nodemaven.js +5 -5
- package/dist/server/serve.js +3 -1
- package/dist/types.d.ts +10 -1
- package/package.json +1 -1
- package/src/config/loader.ts +110 -18
- package/src/index.ts +5 -0
- package/src/native-address.ts +340 -0
- package/src/native-egress-policy.ts +105 -32
- package/src/provider.ts +5 -0
- package/src/runtime/native-network.ts +770 -136
- package/src/runtime/proxy-nodemaven.ts +13 -5
- package/src/server/serve.ts +8 -1
- package/src/types.ts +10 -1
package/AUTHORING.md
CHANGED
|
@@ -742,6 +742,46 @@ const credentialsAuth = defineCredentialsAuth({
|
|
|
742
742
|
`bunx playwright install chromium`, or set
|
|
743
743
|
`APIFUSE__CDP_POOL__URL` for remote browser debugging.
|
|
744
744
|
|
|
745
|
+
### Native gateway adapter migration
|
|
746
|
+
|
|
747
|
+
Native proxy resolution supports both HTTP CONNECT and SOCKS5 without
|
|
748
|
+
terminating origin TLS. The built-in vendor order follows `proxy.providers`
|
|
749
|
+
exactly: `smartproxy` means the `api.smartproxy.org` allocation vendor (raw
|
|
750
|
+
`ip:port` endpoints), while `nodemaven` is the credentialed gateway. It is not
|
|
751
|
+
the company formerly called Smartproxy; that separate company is represented
|
|
752
|
+
by the deprecated `decodo` name.
|
|
753
|
+
|
|
754
|
+
Custom `gatewaySynthesizers` must now accept the selected `protocol` and the
|
|
755
|
+
injected `credentials` resolver on `NativeGatewayProxySynthesisInput`.
|
|
756
|
+
Synthesizers may be async because allocation vendors perform network I/O. They
|
|
757
|
+
may return a proxy, `undefined` when they do not implement the offered vendor,
|
|
758
|
+
or `{ kind: "skipped", reason }` so an exhausted required chain can explain an
|
|
759
|
+
absent credential, unsupported protocol, or allocation failure. Accordingly,
|
|
760
|
+
`resolveNativeGatewayProxy(...)` must now be awaited.
|
|
761
|
+
|
|
762
|
+
Callers that do not supply custom synthesizers or credentials keep env-backed
|
|
763
|
+
behavior. Hosts that already have an allowlisted `EnvContext` can inject it
|
|
764
|
+
explicitly, and vault-backed or per-tenant hosts can supply their own resolver:
|
|
765
|
+
|
|
766
|
+
```ts
|
|
767
|
+
import {
|
|
768
|
+
createEnvVendorCredentialResolver,
|
|
769
|
+
createNativeNetworkClient,
|
|
770
|
+
} from "@apifuse/provider-sdk";
|
|
771
|
+
|
|
772
|
+
const network = createNativeNetworkClient({
|
|
773
|
+
proxyPolicy: { mode: "required", providers: ["smartproxy", "nodemaven"] },
|
|
774
|
+
credentials: createEnvVendorCredentialResolver(ctx.env),
|
|
775
|
+
// Optional advanced override; omit for each vendor's default.
|
|
776
|
+
proxyProtocol: "socks5",
|
|
777
|
+
});
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
Never include credential values in adapter skip messages or thrown errors. The
|
|
781
|
+
SDK redacts built-in proxy URL userinfo, CONNECT authentication, and allocator
|
|
782
|
+
causes, but a custom adapter remains responsible for not publishing secrets in
|
|
783
|
+
its own diagnostics.
|
|
784
|
+
|
|
745
785
|
### Limiting stealth response bodies
|
|
746
786
|
|
|
747
787
|
Set `maxBodyBytes` on `ctx.stealth.fetch()` or `session.redirects.run()` when an
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @apifuse/provider-sdk Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.0-beta.18
|
|
4
|
+
|
|
5
|
+
- Release candidate for main commit cebaf4b994918d2641c0fe6681fbffd3a3284c5d.
|
|
6
|
+
|
|
7
|
+
## 2.2.0-beta.17
|
|
8
|
+
|
|
9
|
+
- Release candidate for main commit 5a0f9127a5861992d5c144b07ad379a085756544.
|
|
10
|
+
|
|
3
11
|
## 2.2.0-beta.16
|
|
4
12
|
|
|
5
13
|
- Release candidate for main commit c5deb2bc31a4a4a236d27a2ce3d214b533ed358f.
|
|
@@ -94,6 +102,7 @@
|
|
|
94
102
|
|
|
95
103
|
## Unreleased
|
|
96
104
|
|
|
105
|
+
- **Breaking for custom native gateway adapters:** `NativeGatewayProxySynthesisInput` now includes an injected `credentials` resolver and selected `protocol`; synthesizers may return promises and structured skip reasons, and `resolveNativeGatewayProxy` is async. Default callers retain env-backed behavior. Native transport now supports both HTTP CONNECT and SOCKS5, defaults per vendor with an explicit runtime override, registers smartproxy allocation ahead of nodemaven when declared in that order, and reports every exhausted vendor reason without exposing proxy credentials.
|
|
97
106
|
- Honor operation `docs.errorCodes` at runtime: declared provider-owned statuses and retryability now drive the HTTP envelope, observability header, and structured log; invalid statuses fail `defineProvider`, declared codes no longer emit the unregistered-code signal, and `TransportError` status-preservation workarounds are obsolete.
|
|
98
107
|
- Add an opt-in same-origin redirect hop policy to `ctx.http`, with bounded manual following and typed failures before a refused target is requested.
|
|
99
108
|
- Enforce provider-declared native TCP/TLS egress before proxy or socket setup, with revocable and expiring dynamic grants plus typed authorization failures; providers without a native egress declaration retain legacy behavior.
|
package/dist/config/loader.d.ts
CHANGED
|
@@ -150,12 +150,31 @@ export declare function resolveProxyConfigAsync(options?: ProxyResolutionOptions
|
|
|
150
150
|
* Vendor allocation and failover remain owned by the SDK.
|
|
151
151
|
*/
|
|
152
152
|
export declare function resolveProxy(options?: ProxyResolutionOptions): Promise<ResolvedProxyConfig>;
|
|
153
|
+
/**
|
|
154
|
+
* Each vendor's default egress protocol, chosen from live KR benchmarks. HTTP
|
|
155
|
+
* CONNECT wins for nodemaven (socks5 adds ~500ms through the gateway) and ties
|
|
156
|
+
* for smartproxy, and is the only protocol ctx.http (Bun native fetch) supports.
|
|
157
|
+
* Override per call via ProxyResolutionOptions.protocol (harness/tests).
|
|
158
|
+
*/
|
|
159
|
+
export declare const VENDOR_DEFAULT_PROTOCOL: Readonly<Record<ProxyVendorName, ProxyProtocol>>;
|
|
153
160
|
/**
|
|
154
161
|
* Guard the No-MITM invariant: a resolved proxy URL must use a tunnelling scheme
|
|
155
162
|
* (http CONNECT or socks5) so the client TLS handshake reaches the origin
|
|
156
163
|
* end-to-end. Anything else would intercept TLS and break fingerprinting.
|
|
157
164
|
*/
|
|
158
165
|
export declare function assertTunnelingScheme(url: string): void;
|
|
166
|
+
export type ProxyVendorResolutionContext = {
|
|
167
|
+
readonly protocol: ProxyProtocol;
|
|
168
|
+
readonly poolIndex: number;
|
|
169
|
+
readonly refreshEpoch: number;
|
|
170
|
+
/** Explicit vendor credentials. Omit only on the legacy ambient-env path. */
|
|
171
|
+
readonly credentials?: Readonly<Record<string, string>>;
|
|
172
|
+
/** Disable non-policy env defaults for deterministic injected adapters. */
|
|
173
|
+
readonly ambientDefaults?: boolean;
|
|
174
|
+
/** Disable env-discovered Redis sharing for deterministic injected adapters. */
|
|
175
|
+
readonly sharedCache?: boolean;
|
|
176
|
+
};
|
|
177
|
+
export declare function resolveWithVendor(vendor: ProxyVendorName, policy: ProviderProxyPolicy, options: ProxyResolutionOptions, context: ProxyVendorResolutionContext): Promise<ResolvedProxyConfig>;
|
|
159
178
|
/**
|
|
160
179
|
* Ordered list of SDK-native proxy vendors declared by the policy. `providers`
|
|
161
180
|
* takes precedence over the legacy singular `provider`; the platform default
|
package/dist/config/loader.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { Redis } from "ioredis";
|
|
5
|
-
import { NODEMAVEN_DEFAULT_PROTOCOL, NODEMAVEN_MAX_POOL_SIZE, hasNodemavenCredentials, nodemavenPoolSize, synthesizeNodemavenProxy, } from "../runtime/proxy-nodemaven.js";
|
|
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
6
|
// "smartproxy" here is api.smartproxy.org — a residential proxy with an IP
|
|
7
7
|
// extraction API (app_key → raw ip:port pool). It is NOT the company formerly
|
|
8
8
|
// named Smartproxy (smartproxy.com), which rebranded to Decodo in 2025 and is
|
|
@@ -415,7 +415,7 @@ export async function resolveProxy(options = {}) {
|
|
|
415
415
|
* for smartproxy, and is the only protocol ctx.http (Bun native fetch) supports.
|
|
416
416
|
* Override per call via ProxyResolutionOptions.protocol (harness/tests).
|
|
417
417
|
*/
|
|
418
|
-
const VENDOR_DEFAULT_PROTOCOL = {
|
|
418
|
+
export const VENDOR_DEFAULT_PROTOCOL = {
|
|
419
419
|
smartproxy: "http",
|
|
420
420
|
nodemaven: NODEMAVEN_DEFAULT_PROTOCOL,
|
|
421
421
|
};
|
|
@@ -436,16 +436,33 @@ export function assertTunnelingScheme(url) {
|
|
|
436
436
|
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.`);
|
|
437
437
|
}
|
|
438
438
|
}
|
|
439
|
-
async function resolveWithVendor(vendor, policy, options, context) {
|
|
439
|
+
export async function resolveWithVendor(vendor, policy, options, context) {
|
|
440
440
|
if (vendor === "nodemaven") {
|
|
441
441
|
const startedAt = Date.now();
|
|
442
|
+
const username = (context.credentials === undefined
|
|
443
|
+
? process.env[NODEMAVEN_USERNAME_ENV]
|
|
444
|
+
: context.credentials[NODEMAVEN_USERNAME_ENV])?.trim();
|
|
445
|
+
const password = (context.credentials === undefined
|
|
446
|
+
? process.env[NODEMAVEN_PASSWORD_ENV]
|
|
447
|
+
: context.credentials[NODEMAVEN_PASSWORD_ENV])?.trim();
|
|
448
|
+
const filter = context.credentials === undefined
|
|
449
|
+
? process.env[NODEMAVEN_FILTER_ENV]
|
|
450
|
+
: context.credentials[NODEMAVEN_FILTER_ENV];
|
|
451
|
+
if (!username || !password) {
|
|
452
|
+
throw new ProxyResolutionError("PROXY_ALLOCATION_FAILED", `NodeMaven credentials missing: set ${NODEMAVEN_USERNAME_ENV} and ${NODEMAVEN_PASSWORD_ENV}.`, { vendor: "nodemaven" });
|
|
453
|
+
}
|
|
442
454
|
const synthesized = synthesizeNodemavenProxy({
|
|
443
455
|
policy,
|
|
456
|
+
credentials: {
|
|
457
|
+
username,
|
|
458
|
+
password,
|
|
459
|
+
...(filter ? { filter } : {}),
|
|
460
|
+
},
|
|
444
461
|
affinityKey: options.affinityKey,
|
|
445
462
|
protocol: context.protocol,
|
|
446
463
|
poolIndex: context.poolIndex,
|
|
447
464
|
refreshEpoch: context.refreshEpoch,
|
|
448
|
-
country: resolveSmartproxyCountry(policy),
|
|
465
|
+
country: resolveSmartproxyCountry(policy, context.ambientDefaults !== false),
|
|
449
466
|
});
|
|
450
467
|
options.telemetry?.recordProxyResolution({
|
|
451
468
|
provider: "nodemaven",
|
|
@@ -468,13 +485,15 @@ async function resolveWithVendor(vendor, policy, options, context) {
|
|
|
468
485
|
};
|
|
469
486
|
}
|
|
470
487
|
// smartproxy allocation-style vendor.
|
|
471
|
-
const appKey =
|
|
488
|
+
const appKey = (context.credentials === undefined
|
|
489
|
+
? process.env[SMARTPROXY_APP_KEY_ENV]
|
|
490
|
+
: context.credentials[SMARTPROXY_APP_KEY_ENV])?.trim();
|
|
472
491
|
if (!appKey) {
|
|
473
492
|
// Guarded by vendorHasCredentials; treated as a vendor-internal failure.
|
|
474
493
|
throw new ProxyResolutionError("PROXY_ALLOCATION_FAILED", `${SMARTPROXY_APP_KEY_ENV} is not configured.`, { vendor: "smartproxy" });
|
|
475
494
|
}
|
|
476
|
-
const lifetimeMinutes = resolveSmartproxyLifetime(policy);
|
|
477
|
-
const allocated = await allocateSmartproxy(policy, appKey, lifetimeMinutes, options.affinityKey, context.protocol);
|
|
495
|
+
const lifetimeMinutes = resolveSmartproxyLifetime(policy, context.ambientDefaults !== false);
|
|
496
|
+
const allocated = await allocateSmartproxy(policy, appKey, lifetimeMinutes, options.affinityKey, context.protocol, context.ambientDefaults !== false, context.sharedCache !== false);
|
|
478
497
|
options.telemetry?.recordProxyResolution({ ...allocated.telemetry, protocol: context.protocol });
|
|
479
498
|
const poolIndex = selectProxyPoolIndex(allocated.pool.urls.length, context.poolIndex);
|
|
480
499
|
const url = allocated.pool.urls[poolIndex];
|
|
@@ -665,11 +684,15 @@ export function mapFlatAttempt(flat, sizes) {
|
|
|
665
684
|
}
|
|
666
685
|
return { vendorIndex: 0, poolIndex: 0 };
|
|
667
686
|
}
|
|
668
|
-
function resolveSmartproxyCountry(policy) {
|
|
669
|
-
return (policy.geo?.country ??
|
|
687
|
+
function resolveSmartproxyCountry(policy, ambientDefaults = true) {
|
|
688
|
+
return (policy.geo?.country ??
|
|
689
|
+
(ambientDefaults
|
|
690
|
+
? process.env[DEFAULT_PROXY_COUNTRY_ENV]?.trim().toUpperCase() || undefined
|
|
691
|
+
: undefined));
|
|
670
692
|
}
|
|
671
|
-
function resolveSmartproxyLifetime(policy) {
|
|
672
|
-
const configuredLifetime = policy.session?.lifetimeMinutes ??
|
|
693
|
+
function resolveSmartproxyLifetime(policy, ambientDefaults = true) {
|
|
694
|
+
const configuredLifetime = policy.session?.lifetimeMinutes ??
|
|
695
|
+
(ambientDefaults ? readPositiveNumberEnv(DEFAULT_PROXY_LIFETIME_ENV, 30) : 30);
|
|
673
696
|
return Math.min(SMARTPROXY_MAX_LIFETIME_MINUTES, Math.max(1, Math.floor(configuredLifetime)));
|
|
674
697
|
}
|
|
675
698
|
function readPositiveNumberEnv(name, fallback) {
|
|
@@ -692,20 +715,24 @@ function selectProxyPoolIndex(poolSize, attempt = 0) {
|
|
|
692
715
|
const normalizedAttempt = Number.isFinite(attempt) ? Math.max(0, Math.floor(attempt)) : 0;
|
|
693
716
|
return normalizedAttempt % poolSize;
|
|
694
717
|
}
|
|
695
|
-
function buildSmartproxyCacheKey(policy, affinityKey, lifetimeMinutes, protocol) {
|
|
718
|
+
function buildSmartproxyCacheKey(policy, appKey, affinityKey, lifetimeMinutes, protocol, ambientDefaults = true) {
|
|
696
719
|
const poolSize = resolveSmartproxyPoolSize(policy);
|
|
697
720
|
return JSON.stringify({
|
|
698
721
|
provider: "smartproxy",
|
|
722
|
+
credentialHash: createHash("sha256")
|
|
723
|
+
.update("apifuse-smartproxy-credential:v1\0")
|
|
724
|
+
.update(appKey)
|
|
725
|
+
.digest("hex"),
|
|
699
726
|
protocol,
|
|
700
|
-
country: resolveSmartproxyCountry(policy),
|
|
727
|
+
country: resolveSmartproxyCountry(policy, ambientDefaults),
|
|
701
728
|
affinity: policy.session?.affinity ?? "request",
|
|
702
729
|
affinityKey: (policy.session?.affinity ?? "request") === "request" ? undefined : affinityKey,
|
|
703
730
|
lifetimeMinutes,
|
|
704
731
|
poolSize,
|
|
705
732
|
});
|
|
706
733
|
}
|
|
707
|
-
async function allocateSmartproxy(policy, appKey, lifetimeMinutes, affinityKey, protocol) {
|
|
708
|
-
const cacheKey = buildSmartproxyCacheKey(policy, affinityKey, lifetimeMinutes, protocol);
|
|
734
|
+
async function allocateSmartproxy(policy, appKey, lifetimeMinutes, affinityKey, protocol, ambientDefaults = true, sharedCache = true) {
|
|
735
|
+
const cacheKey = buildSmartproxyCacheKey(policy, appKey, affinityKey, lifetimeMinutes, protocol, ambientDefaults);
|
|
709
736
|
const startedAt = Date.now();
|
|
710
737
|
const now = startedAt;
|
|
711
738
|
const invalidatedUntil = invalidatedProxyKeys.get(cacheKey) ?? 0;
|
|
@@ -713,7 +740,7 @@ async function allocateSmartproxy(policy, appKey, lifetimeMinutes, affinityKey,
|
|
|
713
740
|
const cached = proxyCache.get(cacheKey);
|
|
714
741
|
if (!skipCached && cached && isFresh(cached, now)) {
|
|
715
742
|
if (shouldSoftRefresh(cached, now)) {
|
|
716
|
-
void refreshSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes, protocol);
|
|
743
|
+
void refreshSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes, protocol, ambientDefaults, sharedCache);
|
|
717
744
|
return {
|
|
718
745
|
pool: cached,
|
|
719
746
|
telemetry: telemetryForPool(cached, "soft_stale_refresh", startedAt, {
|
|
@@ -726,7 +753,7 @@ async function allocateSmartproxy(policy, appKey, lifetimeMinutes, affinityKey,
|
|
|
726
753
|
telemetry: telemetryForPool(cached, "memory_hit", startedAt),
|
|
727
754
|
};
|
|
728
755
|
}
|
|
729
|
-
if (!skipCached) {
|
|
756
|
+
if (!skipCached && sharedCache) {
|
|
730
757
|
const redisResult = await readSmartproxyRedisPool(cacheKey, startedAt);
|
|
731
758
|
if (redisResult)
|
|
732
759
|
return redisResult;
|
|
@@ -741,7 +768,7 @@ async function allocateSmartproxy(policy, appKey, lifetimeMinutes, affinityKey,
|
|
|
741
768
|
}),
|
|
742
769
|
};
|
|
743
770
|
}
|
|
744
|
-
const promise = allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinutes, startedAt, protocol).finally(() => {
|
|
771
|
+
const promise = allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinutes, startedAt, protocol, ambientDefaults, sharedCache).finally(() => {
|
|
745
772
|
proxyInflight.delete(cacheKey);
|
|
746
773
|
});
|
|
747
774
|
proxyInflight.set(cacheKey, promise);
|
|
@@ -772,9 +799,9 @@ async function readSmartproxyRedisPool(cacheKey, startedAt) {
|
|
|
772
799
|
telemetry: telemetryForPool(pool, "redis_hit", startedAt, { redisReadMs }),
|
|
773
800
|
};
|
|
774
801
|
}
|
|
775
|
-
async function refreshSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes, protocol) {
|
|
802
|
+
async function refreshSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes, protocol, ambientDefaults, sharedCache) {
|
|
776
803
|
try {
|
|
777
|
-
await allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinutes, Date.now(), protocol, {
|
|
804
|
+
await allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinutes, Date.now(), protocol, ambientDefaults, sharedCache, {
|
|
778
805
|
background: true,
|
|
779
806
|
});
|
|
780
807
|
}
|
|
@@ -782,10 +809,10 @@ async function refreshSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes,
|
|
|
782
809
|
// Soft refresh is opportunistic; current fresh pool remains usable.
|
|
783
810
|
}
|
|
784
811
|
}
|
|
785
|
-
async function allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinutes, startedAt, protocol, options = {}) {
|
|
786
|
-
const redis = getProxyRedis();
|
|
812
|
+
async function allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinutes, startedAt, protocol, ambientDefaults, sharedCache, options = {}) {
|
|
813
|
+
const redis = sharedCache ? getProxyRedis() : undefined;
|
|
787
814
|
if (!redis || !(await ensureRedisReady(redis))) {
|
|
788
|
-
return await allocateAndStoreSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes, startedAt, { cacheStatus: "allocator", protocol });
|
|
815
|
+
return await allocateAndStoreSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes, startedAt, { cacheStatus: "allocator", protocol, ambientDefaults });
|
|
789
816
|
}
|
|
790
817
|
const poolKey = smartproxyRedisPoolKey(cacheKey);
|
|
791
818
|
const lockKey = smartproxyRedisLockKey(cacheKey);
|
|
@@ -800,6 +827,7 @@ async function allocateSmartproxyShared(cacheKey, policy, appKey, lifetimeMinute
|
|
|
800
827
|
redis,
|
|
801
828
|
poolKey,
|
|
802
829
|
protocol,
|
|
830
|
+
ambientDefaults,
|
|
803
831
|
});
|
|
804
832
|
}
|
|
805
833
|
finally {
|
|
@@ -912,7 +940,7 @@ async function readSmartproxyAllocatorBodyWithDeadline(response, signal) {
|
|
|
912
940
|
}
|
|
913
941
|
async function allocateAndStoreSmartproxyPool(cacheKey, policy, appKey, lifetimeMinutes, startedAt, options) {
|
|
914
942
|
const poolSize = resolveSmartproxyPoolSize(policy);
|
|
915
|
-
const allocatorUrl = buildSmartproxyAllocatorUrl(policy, appKey, lifetimeMinutes, poolSize, options.protocol);
|
|
943
|
+
const allocatorUrl = buildSmartproxyAllocatorUrl(policy, appKey, lifetimeMinutes, poolSize, options.protocol, options.ambientDefaults);
|
|
916
944
|
const allocatorStartedAt = Date.now();
|
|
917
945
|
const allocatorDeadlineAt = allocatorStartedAt + smartproxyAllocatorDeadlineMs();
|
|
918
946
|
let allocation;
|
|
@@ -970,7 +998,7 @@ async function allocateAndStoreSmartproxyPool(cacheKey, policy, appKey, lifetime
|
|
|
970
998
|
expiresAt: allocatedAt + ttlMs,
|
|
971
999
|
diagnostics: {
|
|
972
1000
|
provider: "smartproxy",
|
|
973
|
-
country: resolveSmartproxyCountry(policy) ?? "default",
|
|
1001
|
+
country: resolveSmartproxyCountry(policy, options.ambientDefaults) ?? "default",
|
|
974
1002
|
lifetimeMinutes,
|
|
975
1003
|
affinity: policy.session?.affinity ?? "request",
|
|
976
1004
|
rawConnect: true,
|
|
@@ -1090,7 +1118,7 @@ const SMARTPROXY_PROTOCOL_PARAM = {
|
|
|
1090
1118
|
http: "1",
|
|
1091
1119
|
socks5: "2",
|
|
1092
1120
|
};
|
|
1093
|
-
function buildSmartproxyAllocatorUrl(policy, appKey, lifetimeMinutes, poolSize, protocol) {
|
|
1121
|
+
function buildSmartproxyAllocatorUrl(policy, appKey, lifetimeMinutes, poolSize, protocol, ambientDefaults = true) {
|
|
1094
1122
|
const params = new URLSearchParams({
|
|
1095
1123
|
app_key: appKey,
|
|
1096
1124
|
pt: "9",
|
|
@@ -1100,7 +1128,7 @@ function buildSmartproxyAllocatorUrl(policy, appKey, lifetimeMinutes, poolSize,
|
|
|
1100
1128
|
format: "txt",
|
|
1101
1129
|
lb: "\\n",
|
|
1102
1130
|
});
|
|
1103
|
-
const country = resolveSmartproxyCountry(policy);
|
|
1131
|
+
const country = resolveSmartproxyCountry(policy, ambientDefaults);
|
|
1104
1132
|
if (country) {
|
|
1105
1133
|
params.set("cc", country);
|
|
1106
1134
|
}
|
|
@@ -1172,7 +1200,10 @@ function markSmartproxyCacheInvalidated(options = {}) {
|
|
|
1172
1200
|
return undefined;
|
|
1173
1201
|
}
|
|
1174
1202
|
const lifetimeMinutes = resolveSmartproxyLifetime(policy);
|
|
1175
|
-
const
|
|
1203
|
+
const appKey = process.env[SMARTPROXY_APP_KEY_ENV]?.trim();
|
|
1204
|
+
if (!appKey)
|
|
1205
|
+
return undefined;
|
|
1206
|
+
const cacheKey = buildSmartproxyCacheKey(policy, appKey, options.affinityKey, lifetimeMinutes, options.protocol ?? VENDOR_DEFAULT_PROTOCOL.smartproxy);
|
|
1176
1207
|
invalidatedProxyKeys.set(cacheKey, Date.now() + SMARTPROXY_INVALIDATION_SKIP_REDIS_MS);
|
|
1177
1208
|
proxyCache.delete(cacheKey);
|
|
1178
1209
|
proxyInflight.delete(cacheKey);
|
package/dist/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export { type CreateCredentialContextOptions, createCredentialContext, } from ".
|
|
|
22
22
|
export { createEnvContext } from "./runtime/env.js";
|
|
23
23
|
export { executeOperation } from "./runtime/executor.js";
|
|
24
24
|
export { createHttpClient } from "./runtime/http.js";
|
|
25
|
-
export { createNativeNetworkClient, deriveNativeCredentialAffinityKey, NativeEgressGrantExpiredError, NativeEgressNotDeclaredError, NativeIdleTimeoutError, NativeNetworkError, NativeProxyExpiredError, resolveNativeGatewayProxy, type NativeGatewayProxy, type NativeGatewayProxyResolutionInput, type NativeGatewayProxySynthesizer, type NativeGatewayProxySynthesisInput, type NativeNetworkClientOptions, type NativeNetworkErrorCode, } from "./runtime/native-network.js";
|
|
25
|
+
export { createNativeNetworkClient, createEnvVendorCredentialResolver, deriveNativeCredentialAffinityKey, NativeEgressGrantExpiredError, NativeEgressNotDeclaredError, NativeIdleTimeoutError, NativeNetworkError, NativeProxyExpiredError, resolveNativeGatewayProxy, type NativeGatewayProxy, type NativeGatewayProxyResolutionInput, type NativeGatewayProxySkipReason, type NativeGatewayProxySynthesizer, type NativeGatewayProxySynthesisResult, type NativeGatewayProxySynthesisInput, type NativeNetworkClientOptions, type NativeNetworkErrorCode, type VendorCredentialLookup, type VendorCredentialResolver, } from "./runtime/native-network.js";
|
|
26
26
|
export type { Insight, InsightSeverity } from "./runtime/insights.js";
|
|
27
27
|
export { generateInsights } from "./runtime/insights.js";
|
|
28
28
|
export { type InstrumentationOptions, type InstrumentedProviderContext, wrapWithInstrumentation, } from "./runtime/instrumentation.js";
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ export { createCredentialContext, } from "./runtime/credential.js";
|
|
|
20
20
|
export { createEnvContext } from "./runtime/env.js";
|
|
21
21
|
export { executeOperation } from "./runtime/executor.js";
|
|
22
22
|
export { createHttpClient } from "./runtime/http.js";
|
|
23
|
-
export { createNativeNetworkClient, deriveNativeCredentialAffinityKey, NativeEgressGrantExpiredError, NativeEgressNotDeclaredError, NativeIdleTimeoutError, NativeNetworkError, NativeProxyExpiredError, resolveNativeGatewayProxy, } from "./runtime/native-network.js";
|
|
23
|
+
export { createNativeNetworkClient, createEnvVendorCredentialResolver, deriveNativeCredentialAffinityKey, NativeEgressGrantExpiredError, NativeEgressNotDeclaredError, NativeIdleTimeoutError, NativeNetworkError, NativeProxyExpiredError, resolveNativeGatewayProxy, } from "./runtime/native-network.js";
|
|
24
24
|
export { generateInsights } from "./runtime/insights.js";
|
|
25
25
|
export { wrapWithInstrumentation, } from "./runtime/instrumentation.js";
|
|
26
26
|
export { prevalidate } from "./runtime/prevalidate.js";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type EgressHostKind = "ipv4" | "ipv6" | "ipv4-mapped-ipv6" | "numeric-ambiguous" | "dns";
|
|
2
|
+
export type EgressHostCanonicalizationFailure = "not-string" | "reserved-delimiter" | "control-character" | "whitespace" | "canonicalization-empty";
|
|
3
|
+
export type EgressHostCanonicalizationResult = {
|
|
4
|
+
readonly ok: true;
|
|
5
|
+
readonly host: string;
|
|
6
|
+
} | {
|
|
7
|
+
readonly ok: false;
|
|
8
|
+
readonly reason: EgressHostCanonicalizationFailure;
|
|
9
|
+
};
|
|
10
|
+
export type Ipv6CidrOverlap = "ipv4-mapped" | "ipv4-compatible";
|
|
11
|
+
export type Ipv6CidrParseResult = {
|
|
12
|
+
readonly ok: true;
|
|
13
|
+
readonly network: Uint8Array;
|
|
14
|
+
readonly prefix: number;
|
|
15
|
+
} | {
|
|
16
|
+
readonly ok: false;
|
|
17
|
+
readonly reason: "malformed";
|
|
18
|
+
readonly overlap?: Ipv6CidrOverlap;
|
|
19
|
+
} | {
|
|
20
|
+
readonly ok: false;
|
|
21
|
+
readonly reason: "non-canonical-network";
|
|
22
|
+
};
|
|
23
|
+
export declare function parseStrictIpv4(value: string): number | undefined;
|
|
24
|
+
/** Parse the RFC 4291 IPv6 text forms accepted at both policy and runtime boundaries. */
|
|
25
|
+
export declare function parseIpv6(value: string): Uint8Array | undefined;
|
|
26
|
+
export declare function parseIpv4Cidr(value: string): {
|
|
27
|
+
readonly ok: true;
|
|
28
|
+
readonly network: number;
|
|
29
|
+
readonly prefix: number;
|
|
30
|
+
} | {
|
|
31
|
+
readonly ok: false;
|
|
32
|
+
readonly reason: "malformed" | "non-canonical-network";
|
|
33
|
+
};
|
|
34
|
+
export declare function parseIpv6Cidr(value: string): Ipv6CidrParseResult;
|
|
35
|
+
export declare function ipv4InCidr(address: number, cidr: string): boolean;
|
|
36
|
+
export declare function ipv6InCidr(address: Uint8Array, cidr: string): boolean;
|
|
37
|
+
export declare function embeddedIpv4FromIpv6(address: Uint8Array): number | undefined;
|
|
38
|
+
export declare function formatIpv6(address: Uint8Array): string;
|
|
39
|
+
/** The single family and ambiguity classifier used by policy and runtime matching. */
|
|
40
|
+
export declare function classifyEgressHost(host: string): EgressHostKind;
|
|
41
|
+
export declare function hasReservedEgressHostDelimiter(value: string): boolean;
|
|
42
|
+
export declare function hasEgressHostControlCharacter(value: string): boolean;
|
|
43
|
+
export declare function canonicalizeEgressHost(value: unknown): EgressHostCanonicalizationResult;
|