@apifuse/provider-sdk 2.2.0-beta.27 → 2.2.0-beta.29

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.
Files changed (98) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/bin/apifuse-dev.ts +34 -5
  3. package/bin/apifuse-pack-smoke.ts +1 -1
  4. package/bin/apifuse-pack-types.ts +26 -2
  5. package/bin/apifuse-perf.ts +2 -4
  6. package/bin/apifuse-record.ts +39 -6
  7. package/dist/auth-turn/index.d.ts +1 -1
  8. package/dist/auth-turn/index.js +1 -1
  9. package/dist/auth.d.ts +14 -0
  10. package/dist/auth.js +38 -0
  11. package/dist/ceremonies/index.js +52 -11
  12. package/dist/config/loader.d.ts +3 -1
  13. package/dist/config/loader.js +4 -2
  14. package/dist/index.d.ts +8 -7
  15. package/dist/index.js +5 -7
  16. package/dist/provider.d.ts +2 -1
  17. package/dist/provider.js +1 -1
  18. package/dist/runtime/auth-flow.js +1 -1
  19. package/dist/runtime/browser.js +45 -2
  20. package/dist/runtime/http.d.ts +1 -0
  21. package/dist/runtime/http.js +135 -12
  22. package/dist/runtime/instrumentation.js +1 -1
  23. package/dist/runtime/native-network-errors.d.ts +33 -0
  24. package/dist/runtime/native-network-errors.js +69 -0
  25. package/dist/runtime/native-network.d.ts +2 -33
  26. package/dist/runtime/native-network.js +2 -68
  27. package/dist/runtime/proxy-telemetry.js +3 -0
  28. package/dist/runtime/redis.d.ts +1 -1
  29. package/dist/runtime/redis.js +4 -2
  30. package/dist/runtime/resolver-config.d.ts +6 -0
  31. package/dist/runtime/resolver-config.js +6 -0
  32. package/dist/runtime/resolver-public.d.ts +1 -0
  33. package/dist/runtime/resolver-public.js +1 -0
  34. package/dist/runtime/resolver-shared.d.ts +3 -0
  35. package/dist/runtime/resolver-shared.js +12 -0
  36. package/dist/runtime/resolver-vendors/browser.js +14 -4
  37. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +2 -1
  38. package/dist/runtime/resolver-vendors/twocaptcha.js +157 -53
  39. package/dist/runtime/resolver-vendors/types.d.ts +2 -2
  40. package/dist/runtime/resolver-vendors/types.js +3 -1
  41. package/dist/runtime/resolver.d.ts +15 -10
  42. package/dist/runtime/resolver.js +92 -23
  43. package/dist/runtime/state.js +5 -115
  44. package/dist/runtime/stealth-cookies.d.ts +20 -0
  45. package/dist/runtime/stealth-cookies.js +111 -0
  46. package/dist/runtime/stealth.d.ts +1 -0
  47. package/dist/runtime/stealth.js +8 -131
  48. package/dist/serve.d.ts +1 -1
  49. package/dist/serve.js +1 -1
  50. package/dist/server/index.d.ts +1 -1
  51. package/dist/server/index.js +1 -1
  52. package/dist/server/self-test.d.ts +13 -0
  53. package/dist/server/self-test.js +124 -46
  54. package/dist/server/serve-implementation.d.ts +199 -0
  55. package/dist/server/serve-implementation.js +2072 -0
  56. package/dist/server/serve.d.ts +1 -187
  57. package/dist/server/serve.js +1 -1827
  58. package/dist/stateful/errors.d.ts +5 -0
  59. package/dist/stateful/errors.js +10 -0
  60. package/dist/stateful/stateful-provider-session-routing.d.ts +1 -5
  61. package/dist/stateful/stateful-provider-session-routing.js +2 -10
  62. package/dist/stream.js +7 -1
  63. package/dist/testing/index.d.ts +1 -0
  64. package/dist/testing/index.js +1 -0
  65. package/package.json +27 -2
  66. package/src/auth-turn/index.ts +1 -1
  67. package/src/auth.ts +78 -0
  68. package/src/ceremonies/index.ts +68 -18
  69. package/src/config/loader.ts +8 -2
  70. package/src/index.ts +18 -24
  71. package/src/provider.ts +12 -14
  72. package/src/runtime/auth-flow.ts +1 -1
  73. package/src/runtime/browser.ts +50 -2
  74. package/src/runtime/http.ts +155 -11
  75. package/src/runtime/instrumentation.ts +1 -1
  76. package/src/runtime/native-network-errors.ts +99 -0
  77. package/src/runtime/native-network.ts +16 -97
  78. package/src/runtime/proxy-telemetry.ts +5 -0
  79. package/src/runtime/redis.ts +7 -2
  80. package/src/runtime/resolver-config.ts +6 -0
  81. package/src/runtime/resolver-public.ts +18 -0
  82. package/src/runtime/resolver-shared.ts +17 -0
  83. package/src/runtime/resolver-vendors/browser.ts +14 -4
  84. package/src/runtime/resolver-vendors/twocaptcha.ts +190 -56
  85. package/src/runtime/resolver-vendors/types.ts +8 -2
  86. package/src/runtime/resolver.ts +140 -28
  87. package/src/runtime/state.ts +5 -144
  88. package/src/runtime/stealth-cookies.ts +132 -0
  89. package/src/runtime/stealth.ts +15 -158
  90. package/src/serve.ts +6 -1
  91. package/src/server/index.ts +1 -0
  92. package/src/server/self-test.ts +184 -59
  93. package/src/server/serve-implementation.ts +3042 -0
  94. package/src/server/serve.ts +1 -2661
  95. package/src/stateful/errors.ts +12 -0
  96. package/src/stateful/stateful-provider-session-routing.ts +2 -11
  97. package/src/stream.ts +8 -1
  98. package/src/testing/index.ts +1 -0
@@ -141,7 +141,13 @@ function formatExpression(fn) {
141
141
  }
142
142
  function toLaunchOptions(options) {
143
143
  return {
144
- args: options.extraArgs,
144
+ // `extraArgs` is optional, but playwright-extra's stealth evasions mutate
145
+ // `options.args` unguarded (navigator.webdriver does
146
+ // `options.args.findIndex(...)` in beforeLaunch). Forwarding `undefined`
147
+ // therefore crashes every stealth launch that omits extraArgs with
148
+ // "TypeError: undefined is not an object (evaluating 'options.args.findIndex')".
149
+ // Always hand the launcher a concrete array.
150
+ args: options.extraArgs ?? [],
145
151
  executablePath: options.executablePath,
146
152
  headless: options.headless ?? true,
147
153
  proxy: options.proxy ? { server: options.proxy } : undefined,
@@ -684,6 +690,17 @@ function getCdpExecutionContext(params) {
684
690
  id: typeof contextId === "number" ? contextId : undefined,
685
691
  };
686
692
  }
693
+ function getCdpDestroyedExecutionContextId(params) {
694
+ if (!isRecord(params)) {
695
+ return undefined;
696
+ }
697
+ return typeof params.executionContextId === "number"
698
+ ? params.executionContextId
699
+ : undefined;
700
+ }
701
+ function isMissingExecutionContextError(error) {
702
+ return error instanceof Error && /\b(?:cannot|failed to) find context\b/i.test(error.message);
703
+ }
687
704
  class CdpBrowserLocator {
688
705
  frame;
689
706
  selector;
@@ -809,7 +826,19 @@ class CdpPoolBrowserPage {
809
826
  async evaluateInFrame(frameId, fn) {
810
827
  await this.initialize();
811
828
  const contextId = await this.getFrameExecutionContextId(frameId);
812
- return await this.evaluateWithContext(fn, contextId);
829
+ try {
830
+ return await this.evaluateWithContext(fn, contextId);
831
+ }
832
+ catch (error) {
833
+ if (!isMissingExecutionContextError(error)) {
834
+ throw error;
835
+ }
836
+ if (this.frameExecutionContexts.get(frameId) === contextId) {
837
+ this.frameExecutionContexts.delete(frameId);
838
+ }
839
+ const refreshedContextId = await this.getFrameExecutionContextId(frameId);
840
+ return await this.evaluateWithContext(fn, refreshedContextId);
841
+ }
813
842
  }
814
843
  async waitForSelectorInFrame(frameId, selector, options) {
815
844
  const timeout = options?.timeout ?? DEFAULT_WAIT_TIMEOUT_MS;
@@ -1010,6 +1039,20 @@ class CdpPoolBrowserPage {
1010
1039
  this.frameExecutionContexts.set(context.frameId, context.id);
1011
1040
  }
1012
1041
  });
1042
+ this.pageClient.on("Runtime.executionContextDestroyed", (params) => {
1043
+ const destroyedContextId = getCdpDestroyedExecutionContextId(params);
1044
+ if (destroyedContextId === undefined) {
1045
+ return;
1046
+ }
1047
+ for (const [frameId, contextId] of this.frameExecutionContexts) {
1048
+ if (contextId === destroyedContextId) {
1049
+ this.frameExecutionContexts.delete(frameId);
1050
+ }
1051
+ }
1052
+ });
1053
+ this.pageClient.on("Runtime.executionContextsCleared", () => {
1054
+ this.frameExecutionContexts.clear();
1055
+ });
1013
1056
  await this.pageClient.send("Page.enable");
1014
1057
  await this.pageClient.send("Runtime.enable");
1015
1058
  this.initialized = true;
@@ -4,5 +4,6 @@ export type HttpClientOptions = ProxyResolutionOptions & {
4
4
  warn?: (message: string) => void;
5
5
  userAgent?: string;
6
6
  onRetrySummary?: (summary: HttpRetrySummary) => void;
7
+ signal?: AbortSignal;
7
8
  };
8
9
  export declare function createHttpClient(baseUrl?: string, clientOptions?: HttpClientOptions): HttpClient;
@@ -11,10 +11,42 @@ function isHttpStatusOutcome(outcome) {
11
11
  function isDedupeSkipOutcome(outcome) {
12
12
  return "kind" in outcome && outcome.kind === "dedupe-skip";
13
13
  }
14
- async function sleep(ms) {
14
+ function toAmbientCancellationError(signal, error = signal.reason) {
15
+ if (error instanceof TransportError && error.code === "transport_cancelled") {
16
+ return error;
17
+ }
18
+ return new TransportError("Request cancelled", {
19
+ code: "transport_cancelled",
20
+ status: 0,
21
+ retryable: false,
22
+ ...(error !== undefined
23
+ ? { cause: error instanceof Error ? error : new Error(String(error)) }
24
+ : {}),
25
+ });
26
+ }
27
+ function throwIfAmbientAborted(signal) {
28
+ if (signal?.aborted)
29
+ throw toAmbientCancellationError(signal);
30
+ }
31
+ async function sleep(ms, signal) {
32
+ throwIfAmbientAborted(signal);
15
33
  if (ms <= 0)
16
34
  return;
17
- await new Promise((resolve) => setTimeout(resolve, ms));
35
+ if (!signal) {
36
+ await new Promise((resolve) => setTimeout(resolve, ms));
37
+ return;
38
+ }
39
+ await new Promise((resolve, reject) => {
40
+ const onAbort = () => {
41
+ clearTimeout(timer);
42
+ reject(toAmbientCancellationError(signal));
43
+ };
44
+ const timer = setTimeout(() => {
45
+ signal.removeEventListener("abort", onAbort);
46
+ resolve();
47
+ }, ms);
48
+ signal.addEventListener("abort", onAbort, { once: true });
49
+ });
18
50
  }
19
51
  function toUpstreamHttpError(status) {
20
52
  return new TransportError(`Upstream request failed with status ${status}`, {
@@ -53,7 +85,17 @@ function parseJson(body) {
53
85
  function isTimeoutMessage(message) {
54
86
  return /\b(timed out|timeout|deadline exceeded)\b/i.test(message);
55
87
  }
56
- function toHttpTransportError(error) {
88
+ function toHttpTransportError(error, ambientSignal, timeoutSignal) {
89
+ if (ambientSignal?.aborted) {
90
+ return toAmbientCancellationError(ambientSignal, error);
91
+ }
92
+ if (timeoutSignal?.aborted) {
93
+ return new TransportError("Request timed out", {
94
+ code: "transport_timeout",
95
+ status: 0,
96
+ ...(error instanceof Error ? { cause: error } : {}),
97
+ });
98
+ }
57
99
  if (error instanceof TransportError) {
58
100
  if (error.code) {
59
101
  return error;
@@ -125,6 +167,76 @@ function requireNativeResponseBody(response) {
125
167
  }
126
168
  return response.body;
127
169
  }
170
+ function mergeAbortSignals(...signals) {
171
+ const activeSignals = signals.filter((signal) => signal != null);
172
+ if (activeSignals.length === 0)
173
+ return undefined;
174
+ if (activeSignals.length === 1)
175
+ return activeSignals[0];
176
+ return AbortSignal.any(activeSignals);
177
+ }
178
+ function cancelStreamOnAbort(body, signal) {
179
+ if (!signal)
180
+ return body;
181
+ let reader;
182
+ let finished = false;
183
+ let streamController;
184
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
185
+ const onAbort = () => {
186
+ if (finished)
187
+ return;
188
+ finished = true;
189
+ cleanup();
190
+ const reason = toAmbientCancellationError(signal);
191
+ streamController?.error(reason);
192
+ const cancellation = reader ? reader.cancel(reason) : body.cancel(reason);
193
+ void cancellation.catch(() => undefined).finally(() => reader?.releaseLock());
194
+ };
195
+ return new ReadableStream({
196
+ start(controller) {
197
+ streamController = controller;
198
+ signal.addEventListener("abort", onAbort, { once: true });
199
+ if (signal.aborted)
200
+ onAbort();
201
+ },
202
+ async pull(controller) {
203
+ try {
204
+ reader ??= body.getReader();
205
+ const chunk = await reader.read();
206
+ if (finished)
207
+ return;
208
+ if (chunk.done) {
209
+ finished = true;
210
+ cleanup();
211
+ controller.close();
212
+ reader.releaseLock();
213
+ return;
214
+ }
215
+ controller.enqueue(chunk.value);
216
+ }
217
+ catch (error) {
218
+ if (finished)
219
+ return;
220
+ finished = true;
221
+ cleanup();
222
+ controller.error(error);
223
+ reader?.releaseLock();
224
+ }
225
+ },
226
+ async cancel(reason) {
227
+ if (finished)
228
+ return;
229
+ finished = true;
230
+ cleanup();
231
+ try {
232
+ await (reader ? reader.cancel(reason) : body.cancel(reason));
233
+ }
234
+ finally {
235
+ reader?.releaseLock();
236
+ }
237
+ },
238
+ }, { highWaterMark: 0 });
239
+ }
128
240
  function sanitizeStreamErrors(body, serializedUrl) {
129
241
  if (serializedUrl.sensitiveValues.length === 0)
130
242
  return body;
@@ -154,9 +266,9 @@ function sanitizeStreamErrors(body, serializedUrl) {
154
266
  },
155
267
  }, { highWaterMark: 0 });
156
268
  }
157
- function toNativeHttpStreamResponse(response, serializedUrl) {
269
+ function toNativeHttpStreamResponse(response, serializedUrl, signal) {
158
270
  const headers = Object.fromEntries(response.headers.entries());
159
- const body = sanitizeStreamErrors(requireNativeResponseBody(response), serializedUrl);
271
+ const body = sanitizeStreamErrors(cancelStreamOnAbort(requireNativeResponseBody(response), signal), serializedUrl);
160
272
  return {
161
273
  body,
162
274
  headers,
@@ -418,15 +530,18 @@ async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, war
418
530
  const serializedUrl = serializeHttpRequestUrl(baseUrl, url, options);
419
531
  const { requestUrl } = serializedUrl;
420
532
  const controller = options.timeout ? new AbortController() : undefined;
533
+ const signal = mergeAbortSignals(clientOptions.signal, controller?.signal);
421
534
  const timeoutHandle = options.timeout
422
535
  ? setTimeout(() => controller?.abort(), options.timeout)
423
536
  : undefined;
424
537
  let proxy;
425
538
  try {
539
+ throwIfAmbientAborted(clientOptions.signal);
426
540
  // Resolve inside the try (and after the timeout is armed) so allocator
427
541
  // failures are branded as TransportErrors and count against the request
428
542
  // deadline, exactly as an inline resolve would.
429
543
  proxy = await resolveNativeProxy(options, clientOptions, warn, proxyAttemptOffset);
544
+ throwIfAmbientAborted(clientOptions.signal);
430
545
  // For a registry allocator chain, skip an endpoint a prior attempt already
431
546
  // tried rather than re-issuing the same request. Returning the sentinel
432
547
  // (instead of breaking) lets the loop keep advancing the flat offset until
@@ -441,7 +556,7 @@ async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, war
441
556
  headers: options.headers,
442
557
  method,
443
558
  ...(proxy ? { proxy } : {}),
444
- signal: controller?.signal,
559
+ ...(signal ? { signal } : {}),
445
560
  };
446
561
  if (options.body !== undefined) {
447
562
  requestInit.body = normalizeNativeFetchBody(options.body);
@@ -471,7 +586,7 @@ async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, war
471
586
  if (error instanceof SyntaxError) {
472
587
  throw redactSensitiveError(error, serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
473
588
  }
474
- const transportError = redactSensitiveError(toHttpTransportError(error), serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
589
+ const transportError = redactSensitiveError(toHttpTransportError(error, clientOptions.signal, controller?.signal), serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
475
590
  transportError.proxyUsed = Boolean(proxy);
476
591
  throw transportError;
477
592
  }
@@ -484,16 +599,19 @@ async function fetchNativeHttpStream(baseUrl, url, method, options, clientOption
484
599
  const serializedUrl = serializeHttpRequestUrl(baseUrl, url, options);
485
600
  const { requestUrl } = serializedUrl;
486
601
  const controller = options.timeout ? new AbortController() : undefined;
602
+ const signal = mergeAbortSignals(clientOptions.signal, controller?.signal);
487
603
  const timeoutHandle = options.timeout
488
604
  ? setTimeout(() => controller?.abort(), options.timeout)
489
605
  : undefined;
490
606
  try {
607
+ throwIfAmbientAborted(clientOptions.signal);
491
608
  const proxy = await resolveNativeProxy(options, clientOptions, warn);
609
+ throwIfAmbientAborted(clientOptions.signal);
492
610
  const requestInit = {
493
611
  headers: options.headers,
494
612
  method,
495
613
  ...(proxy ? { proxy } : {}),
496
- signal: controller?.signal,
614
+ ...(signal ? { signal } : {}),
497
615
  };
498
616
  if (options.body !== undefined) {
499
617
  requestInit.body = normalizeNativeFetchBody(options.body);
@@ -506,13 +624,15 @@ async function fetchNativeHttpStream(baseUrl, url, method, options, clientOption
506
624
  status: response.status,
507
625
  });
508
626
  }
509
- return toNativeHttpStreamResponse(response, serializedUrl);
627
+ // Per-call timeout remains header-scoped, while the ambient request signal
628
+ // stays attached to the response body for its full consumption lifetime.
629
+ return toNativeHttpStreamResponse(response, serializedUrl, clientOptions.signal);
510
630
  }
511
631
  catch (error) {
512
632
  if (error instanceof SyntaxError) {
513
633
  throw redactSensitiveError(error, serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
514
634
  }
515
- throw redactSensitiveError(toHttpTransportError(error), serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
635
+ throw redactSensitiveError(toHttpTransportError(error, clientOptions.signal, controller?.signal), serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
516
636
  }
517
637
  finally {
518
638
  if (timeoutHandle)
@@ -616,6 +736,7 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
616
736
  const dedupeContext = dedupeAllocatorEndpoints ? { attempted: new Set() } : undefined;
617
737
  const executeOnce = (proxyAttemptOffset = 0) => fetchNativeHttp(baseUrl, url, methodName, attemptOptions, clientOptions, warnOnce, statusRetryEnabled ? retryOptions?.statusCodes : undefined, proxyAttemptOffset, dedupeContext);
618
738
  if (!retryEnabled || !retryOptions) {
739
+ throwIfAmbientAborted(clientOptions.signal);
619
740
  const outcome = await executeOnce();
620
741
  if (isDedupeSkipOutcome(outcome)) {
621
742
  // Single-shot path never de-duplicates (dedupeContext is undefined),
@@ -639,6 +760,7 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
639
760
  // allocations skip offsets.
640
761
  let issued = 0;
641
762
  for (let attempt = 1; attempt <= transportAttemptCap; attempt += 1) {
763
+ throwIfAmbientAborted(clientOptions.signal);
642
764
  // Whether this offset actually issued a request (vs. a skipped duplicate),
643
765
  // so the catch counts a thrown *transport* failure once without
644
766
  // double-counting a status outcome that already incremented before it
@@ -657,7 +779,7 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
657
779
  if (isHttpStatusOutcome(outcome)) {
658
780
  lastStatus = outcome.status;
659
781
  if (outcome.retryable && issued < retryOptions.attempts) {
660
- await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt, outcome.headers));
782
+ await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt, outcome.headers), clientOptions.signal);
661
783
  continue;
662
784
  }
663
785
  throw toUpstreamHttpError(outcome.status);
@@ -680,6 +802,7 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
680
802
  return response;
681
803
  }
682
804
  catch (error) {
805
+ throwIfAmbientAborted(clientOptions.signal);
683
806
  if (!issuedThisAttempt)
684
807
  issued += 1;
685
808
  lastError = error;
@@ -694,7 +817,7 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
694
817
  options: retryOptions,
695
818
  proxyUsed,
696
819
  })) {
697
- await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt));
820
+ await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt), clientOptions.signal);
698
821
  continue;
699
822
  }
700
823
  throw error;
@@ -1,7 +1,7 @@
1
1
  import { readableBytes, readableLines, readableTextChunks } from "../stream.js";
2
2
  import { parseHttpRequestInvocation, isSensitiveKey, redactSensitiveError, redactSensitiveText, redactUrlQueryParams, requestOptionsFromHttpInvocation, serializeRequestUrl, } from "./request-options.js";
3
3
  import { createTraceContext, getTraceRecorder, } from "./trace.js";
4
- import { RESOLVER_INSTRUMENTATION_METADATA } from "./resolver.js";
4
+ import { RESOLVER_INSTRUMENTATION_METADATA } from "./resolver-shared.js";
5
5
  const BROWSER_PAGE_METHODS = new Set(["goto", "fill", "click", "type", "waitForSelector"]);
6
6
  const DIAGNOSTIC_BASE_URL = "http://apifuse-instrumentation.invalid";
7
7
  function isThenable(value) {
@@ -0,0 +1,33 @@
1
+ import { TransportError } from "../errors.js";
2
+ export type NativeNetworkErrorCode = "native_connection_aborted" | "native_connection_closed" | "native_connection_failed" | "native_connection_idle_timeout" | "native_connection_timeout" | "native_egress_authorization_failed" | "native_egress_grant_expired" | "native_egress_grant_invalid" | "native_egress_grant_limit_exceeded" | "native_egress_input_invalid" | "native_egress_not_declared" | "native_egress_policy_invalid" | "native_dynamic_egress_unsupported" | "native_proxy_expired" | "native_proxy_invalid";
3
+ export declare class NativeNetworkError extends TransportError {
4
+ constructor(message: string, code: NativeNetworkErrorCode, cause?: Error);
5
+ get code(): NativeNetworkErrorCode;
6
+ }
7
+ export declare function safeDiagnosticEgressHost(value: unknown): string;
8
+ export declare class NativeProxyExpiredError extends NativeNetworkError {
9
+ readonly expiresAt: string;
10
+ constructor(expiresAt: string);
11
+ }
12
+ /** Raised before transport setup when a native destination is not authorized. */
13
+ export declare class NativeEgressNotDeclaredError extends NativeNetworkError {
14
+ readonly port: number;
15
+ readonly tls: "required" | "disabled";
16
+ readonly host: string;
17
+ constructor(host: string, port: number, tls: "required" | "disabled");
18
+ }
19
+ /**
20
+ * Raised when the destination was authorized by a grant whose TTL elapsed and
21
+ * its expiry remains in the client's bounded recent-expiry evidence window.
22
+ */
23
+ export declare class NativeEgressGrantExpiredError extends NativeNetworkError {
24
+ readonly port: number;
25
+ readonly tls: "required" | "disabled";
26
+ readonly expiresAt: string;
27
+ readonly host: string;
28
+ constructor(host: string, port: number, tls: "required" | "disabled", expiresAt: string);
29
+ }
30
+ /** Raised when an established connection exceeds its opt-in read-idle window. */
31
+ export declare class NativeIdleTimeoutError extends NativeNetworkError {
32
+ constructor();
33
+ }
@@ -0,0 +1,69 @@
1
+ import { TransportError } from "../errors.js";
2
+ import { canonicalizeEgressHost } from "../native-address.js";
3
+ export class NativeNetworkError extends TransportError {
4
+ constructor(message, code, cause) {
5
+ const isEgressPolicyFailure = code.startsWith("native_egress_") || code === "native_dynamic_egress_unsupported";
6
+ super(message, {
7
+ code,
8
+ status: 0,
9
+ ...(cause ? { cause } : {}),
10
+ ...(isEgressPolicyFailure ? { category: "provider_error", retryable: false } : {}),
11
+ });
12
+ this.name = "NativeNetworkError";
13
+ }
14
+ get code() {
15
+ return super.code;
16
+ }
17
+ }
18
+ export function safeDiagnosticEgressHost(value) {
19
+ const canonical = canonicalizeEgressHost(value);
20
+ return canonical.ok ? canonical.host : `<invalid-host:${canonical.reason}>`;
21
+ }
22
+ export class NativeProxyExpiredError extends NativeNetworkError {
23
+ expiresAt;
24
+ constructor(expiresAt) {
25
+ super("Native connection closed at sticky proxy expiry", "native_proxy_expired");
26
+ this.expiresAt = expiresAt;
27
+ this.name = "NativeProxyExpiredError";
28
+ }
29
+ }
30
+ /** Raised before transport setup when a native destination is not authorized. */
31
+ export class NativeEgressNotDeclaredError extends NativeNetworkError {
32
+ port;
33
+ tls;
34
+ host;
35
+ constructor(host, port, tls) {
36
+ const diagnosticHost = safeDiagnosticEgressHost(host);
37
+ super(`Native ${tls === "required" ? "TLS" : "TCP"} egress is not declared for ${diagnosticHost}:${port}`, "native_egress_not_declared");
38
+ this.port = port;
39
+ this.tls = tls;
40
+ this.host = diagnosticHost;
41
+ this.name = "NativeEgressNotDeclaredError";
42
+ }
43
+ }
44
+ /**
45
+ * Raised when the destination was authorized by a grant whose TTL elapsed and
46
+ * its expiry remains in the client's bounded recent-expiry evidence window.
47
+ */
48
+ export class NativeEgressGrantExpiredError extends NativeNetworkError {
49
+ port;
50
+ tls;
51
+ expiresAt;
52
+ host;
53
+ constructor(host, port, tls, expiresAt) {
54
+ const diagnosticHost = safeDiagnosticEgressHost(host);
55
+ super(`Native ${tls === "required" ? "TLS" : "TCP"} egress grant expired for ${diagnosticHost}:${port}`, "native_egress_grant_expired");
56
+ this.port = port;
57
+ this.tls = tls;
58
+ this.expiresAt = expiresAt;
59
+ this.host = diagnosticHost;
60
+ this.name = "NativeEgressGrantExpiredError";
61
+ }
62
+ }
63
+ /** Raised when an established connection exceeds its opt-in read-idle window. */
64
+ export class NativeIdleTimeoutError extends NativeNetworkError {
65
+ constructor() {
66
+ super("Native network socket timed out while reading.", "native_connection_idle_timeout");
67
+ this.name = "NativeIdleTimeoutError";
68
+ }
69
+ }
@@ -1,40 +1,10 @@
1
1
  import { Socket } from "node:net";
2
2
  import { type TLSSocket } from "node:tls";
3
3
  import { type ProxyProtocol } from "../config/loader.js";
4
- import { TransportError } from "../errors.js";
5
4
  import { type DynamicEgressRuleSnapshot } from "../native-egress-policy.js";
6
5
  import type { NativeNetworkClient, NativeNetworkConnectInput, NativeNetworkConnection, NativeNetworkDynamicGrantOptions, NativeNetworkEgressGrant, NativeProviderConfig, NativeProxyEgressInfo, ProviderProxyPolicy, ProviderProxyProvider, EnvContext } from "../types.js";
7
- export type NativeNetworkErrorCode = "native_connection_aborted" | "native_connection_closed" | "native_connection_failed" | "native_connection_idle_timeout" | "native_connection_timeout" | "native_egress_authorization_failed" | "native_egress_grant_expired" | "native_egress_grant_invalid" | "native_egress_grant_limit_exceeded" | "native_egress_input_invalid" | "native_egress_not_declared" | "native_egress_policy_invalid" | "native_dynamic_egress_unsupported" | "native_proxy_expired" | "native_proxy_invalid";
8
- export declare class NativeNetworkError extends TransportError {
9
- constructor(message: string, code: NativeNetworkErrorCode, cause?: Error);
10
- get code(): NativeNetworkErrorCode;
11
- }
12
- export declare class NativeProxyExpiredError extends NativeNetworkError {
13
- readonly expiresAt: string;
14
- constructor(expiresAt: string);
15
- }
16
- /** Raised before transport setup when a native destination is not authorized. */
17
- export declare class NativeEgressNotDeclaredError extends NativeNetworkError {
18
- readonly port: number;
19
- readonly tls: "required" | "disabled";
20
- readonly host: string;
21
- constructor(host: string, port: number, tls: "required" | "disabled");
22
- }
23
- /**
24
- * Raised when the destination was authorized by a grant whose TTL elapsed and
25
- * its expiry remains in the client's bounded recent-expiry evidence window.
26
- */
27
- export declare class NativeEgressGrantExpiredError extends NativeNetworkError {
28
- readonly port: number;
29
- readonly tls: "required" | "disabled";
30
- readonly expiresAt: string;
31
- readonly host: string;
32
- constructor(host: string, port: number, tls: "required" | "disabled", expiresAt: string);
33
- }
34
- /** Raised when an established connection exceeds its opt-in read-idle window. */
35
- export declare class NativeIdleTimeoutError extends NativeNetworkError {
36
- constructor();
37
- }
6
+ export { NativeEgressGrantExpiredError, NativeEgressNotDeclaredError, NativeIdleTimeoutError, NativeNetworkError, NativeProxyExpiredError, } from "./native-network-errors.js";
7
+ export type { NativeNetworkErrorCode } from "./native-network-errors.js";
38
8
  export type NativeGatewayProxy = NativeProxyEgressInfo & {
39
9
  readonly url: string;
40
10
  };
@@ -124,4 +94,3 @@ export declare function createNativeEgressAuthorization(options: NativeNetworkCl
124
94
  };
125
95
  /** Create the SDK byte-stream runtime with provider-declared egress enforcement. */
126
96
  export declare function createNativeNetworkClient(options?: NativeNetworkClientOptions): NativeNetworkClient;
127
- export {};
@@ -3,79 +3,13 @@ import { Socket } from "node:net";
3
3
  import { connect as connectTlsSocket } from "node:tls";
4
4
  import { SocksClient } from "socks";
5
5
  import { assertTunnelingScheme, ProxyResolutionError, SMARTPROXY_APP_KEY_ENV, VENDOR_DEFAULT_PROTOCOL, resolveWithVendor, } from "../config/loader.js";
6
- import { TransportError } from "../errors.js";
7
6
  import { NativeEgressPolicyValidationError, parseNativeEgressPolicy, } from "../native-egress-policy.js";
8
7
  import { canonicalizeEgressHost, classifyEgressHost, embeddedIpv4FromIpv6, ipv4InCidr, ipv6InCidr, parseIpv6, parseStrictIpv4, } from "../native-address.js";
9
8
  import { createEnvContext } from "./env.js";
9
+ import { NativeEgressGrantExpiredError, NativeEgressNotDeclaredError, NativeIdleTimeoutError, NativeNetworkError, NativeProxyExpiredError, safeDiagnosticEgressHost, } from "./native-network-errors.js";
10
10
  import { NODEMAVEN_FILTER_ENV, NODEMAVEN_PASSWORD_ENV, NODEMAVEN_USERNAME_ENV, nodemavenSessionWindow, synthesizeNodemavenProxy, } from "./proxy-nodemaven.js";
11
11
  import { redactSensitiveError } from "./request-options.js";
12
- export class NativeNetworkError extends TransportError {
13
- constructor(message, code, cause) {
14
- const isEgressPolicyFailure = code.startsWith("native_egress_") || code === "native_dynamic_egress_unsupported";
15
- super(message, {
16
- code,
17
- status: 0,
18
- ...(cause ? { cause } : {}),
19
- ...(isEgressPolicyFailure ? { category: "provider_error", retryable: false } : {}),
20
- });
21
- this.name = "NativeNetworkError";
22
- }
23
- get code() {
24
- return super.code;
25
- }
26
- }
27
- function safeDiagnosticEgressHost(value) {
28
- const canonical = canonicalizeEgressHost(value);
29
- return canonical.ok ? canonical.host : `<invalid-host:${canonical.reason}>`;
30
- }
31
- export class NativeProxyExpiredError extends NativeNetworkError {
32
- expiresAt;
33
- constructor(expiresAt) {
34
- super("Native connection closed at sticky proxy expiry", "native_proxy_expired");
35
- this.expiresAt = expiresAt;
36
- this.name = "NativeProxyExpiredError";
37
- }
38
- }
39
- /** Raised before transport setup when a native destination is not authorized. */
40
- export class NativeEgressNotDeclaredError extends NativeNetworkError {
41
- port;
42
- tls;
43
- host;
44
- constructor(host, port, tls) {
45
- const diagnosticHost = safeDiagnosticEgressHost(host);
46
- super(`Native ${tls === "required" ? "TLS" : "TCP"} egress is not declared for ${diagnosticHost}:${port}`, "native_egress_not_declared");
47
- this.port = port;
48
- this.tls = tls;
49
- this.host = diagnosticHost;
50
- this.name = "NativeEgressNotDeclaredError";
51
- }
52
- }
53
- /**
54
- * Raised when the destination was authorized by a grant whose TTL elapsed and
55
- * its expiry remains in the client's bounded recent-expiry evidence window.
56
- */
57
- export class NativeEgressGrantExpiredError extends NativeNetworkError {
58
- port;
59
- tls;
60
- expiresAt;
61
- host;
62
- constructor(host, port, tls, expiresAt) {
63
- const diagnosticHost = safeDiagnosticEgressHost(host);
64
- super(`Native ${tls === "required" ? "TLS" : "TCP"} egress grant expired for ${diagnosticHost}:${port}`, "native_egress_grant_expired");
65
- this.port = port;
66
- this.tls = tls;
67
- this.expiresAt = expiresAt;
68
- this.host = diagnosticHost;
69
- this.name = "NativeEgressGrantExpiredError";
70
- }
71
- }
72
- /** Raised when an established connection exceeds its opt-in read-idle window. */
73
- export class NativeIdleTimeoutError extends NativeNetworkError {
74
- constructor() {
75
- super("Native network socket timed out while reading.", "native_connection_idle_timeout");
76
- this.name = "NativeIdleTimeoutError";
77
- }
78
- }
12
+ export { NativeEgressGrantExpiredError, NativeEgressNotDeclaredError, NativeIdleTimeoutError, NativeNetworkError, NativeProxyExpiredError, } from "./native-network-errors.js";
79
13
  const VENDOR_CREDENTIAL_NAMES = {
80
14
  smartproxy: [SMARTPROXY_APP_KEY_ENV],
81
15
  nodemaven: [NODEMAVEN_USERNAME_ENV, NODEMAVEN_PASSWORD_ENV],
@@ -33,6 +33,7 @@ export class ProxyTelemetryCollector {
33
33
  recordProxyResolution(event) {
34
34
  this.#events.push({
35
35
  provider: event.provider,
36
+ ...(event.userAgentSource ? { userAgentSource: event.userAgentSource } : {}),
36
37
  ...(event.protocol ? { protocol: event.protocol } : {}),
37
38
  cacheStatus: event.cacheStatus,
38
39
  cacheHit: event.cacheHit,
@@ -99,6 +100,7 @@ export class ProxyTelemetryCollector {
99
100
  }
100
101
  const aggregate = rest.reduce((acc, event) => ({
101
102
  provider: event.provider,
103
+ userAgentSource: event.userAgentSource ?? acc.userAgentSource,
102
104
  cacheStatus: worseStatus(acc.cacheStatus, event.cacheStatus),
103
105
  cacheHit: acc.cacheHit && event.cacheHit,
104
106
  resolutionMs: acc.resolutionMs + event.resolutionMs,
@@ -118,6 +120,7 @@ export class ProxyTelemetryCollector {
118
120
  v: 1,
119
121
  proxy: {
120
122
  provider: serving.provider,
123
+ ...(aggregate.userAgentSource ? { userAgentSource: aggregate.userAgentSource } : {}),
121
124
  ...(serving.protocol ? { protocol: serving.protocol } : {}),
122
125
  cacheStatus: aggregate.cacheStatus,
123
126
  cacheHit: aggregate.cacheHit,
@@ -1,4 +1,4 @@
1
- import { Redis } from "ioredis";
1
+ import type { Redis } from "ioredis";
2
2
  export type ProviderRedisClient = Redis;
3
3
  export type ProviderRedisClientOptions = {
4
4
  readonly redisUrl: string;
@@ -1,6 +1,8 @@
1
- import { Redis } from "ioredis";
1
+ import { createRequire } from "node:module";
2
+ const require = createRequire(import.meta.url);
2
3
  export function createProviderRedisClient(options) {
3
- const redis = new Redis(options.redisUrl, {
4
+ const { Redis: RedisClient } = require("ioredis");
5
+ const redis = new RedisClient(options.redisUrl, {
4
6
  connectTimeout: options.timeoutMs,
5
7
  enableOfflineQueue: false,
6
8
  lazyConnect: true,
@@ -0,0 +1,6 @@
1
+ export declare const APIFUSE__RESOLVER__2CAPTCHA__API_KEY = "APIFUSE__RESOLVER__2CAPTCHA__API_KEY";
2
+ export declare const APIFUSE__RESOLVER__CAPSOLVER__API_KEY = "APIFUSE__RESOLVER__CAPSOLVER__API_KEY";
3
+ export declare const APIFUSE__RESOLVER__CAPMONSTER__API_KEY = "APIFUSE__RESOLVER__CAPMONSTER__API_KEY";
4
+ export declare const APIFUSE__RESOLVER__TIMEOUT_MS = "APIFUSE__RESOLVER__TIMEOUT_MS";
5
+ export declare const APIFUSE__CDP_POOL__URL = "APIFUSE__CDP_POOL__URL";
6
+ export declare const DEFAULT_RESOLVER_TIMEOUT_MS = 180000;
@@ -0,0 +1,6 @@
1
+ export const APIFUSE__RESOLVER__2CAPTCHA__API_KEY = "APIFUSE__RESOLVER__2CAPTCHA__API_KEY";
2
+ export const APIFUSE__RESOLVER__CAPSOLVER__API_KEY = "APIFUSE__RESOLVER__CAPSOLVER__API_KEY";
3
+ export const APIFUSE__RESOLVER__CAPMONSTER__API_KEY = "APIFUSE__RESOLVER__CAPMONSTER__API_KEY";
4
+ export const APIFUSE__RESOLVER__TIMEOUT_MS = "APIFUSE__RESOLVER__TIMEOUT_MS";
5
+ export const APIFUSE__CDP_POOL__URL = "APIFUSE__CDP_POOL__URL";
6
+ export const DEFAULT_RESOLVER_TIMEOUT_MS = 180_000;
@@ -0,0 +1 @@
1
+ export { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, bindResolverSignal, createResolverClient, createResolverClientFromEnv, createUnsupportedResolverClient, DEFAULT_RESOLVER_TIMEOUT_MS, invalidateResolverSolution, RESOLVER_ADAPTER_REGISTRY, RESOLVER_INSTRUMENTATION_METADATA, type ResolverAdapterFactory, type ResolverInstrumentationMetadata, type ResolverRuntimeOptions, } from "./resolver.js";
@@ -0,0 +1 @@
1
+ export { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, bindResolverSignal, createResolverClient, createResolverClientFromEnv, createUnsupportedResolverClient, DEFAULT_RESOLVER_TIMEOUT_MS, invalidateResolverSolution, RESOLVER_ADAPTER_REGISTRY, RESOLVER_INSTRUMENTATION_METADATA, } from "./resolver.js";