@apifuse/provider-sdk 2.2.0-beta.44 → 2.2.0-beta.46
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/CHANGELOG.md +8 -0
- package/README.md +2 -1
- package/bin/apifuse-check.ts +14 -5
- package/dist/declaration-validation.d.ts +2 -1
- package/dist/declaration-validation.js +59 -0
- package/dist/define.d.ts +4 -0
- package/dist/lint.d.ts +15 -0
- package/dist/lint.js +171 -2
- package/dist/runtime/insights.js +2 -2
- package/dist/runtime/stealth.d.ts +3 -1
- package/dist/runtime/stealth.js +187 -22
- package/dist/server/serve-implementation.js +6 -4
- package/dist/stealth/profiles.d.ts +4 -0
- package/dist/stealth/profiles.js +118 -15
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
- package/src/declaration-validation.ts +65 -1
- package/src/define.ts +4 -0
- package/src/lint.ts +240 -2
- package/src/runtime/insights.ts +2 -2
- package/src/runtime/stealth.ts +252 -26
- package/src/server/serve-implementation.ts +6 -4
- package/src/stealth/profiles.ts +135 -16
- package/src/types.ts +4 -0
package/src/runtime/stealth.ts
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
vendorFromResolvedSource,
|
|
19
19
|
} from "../config/loader.js";
|
|
20
20
|
import { SDKError, TransportError } from "../errors.js";
|
|
21
|
-
import { getStealthProfile } from "../stealth/profiles.js";
|
|
21
|
+
import { getStealthProfile, getStealthProfileIntentAlias } from "../stealth/profiles.js";
|
|
22
22
|
import type {
|
|
23
23
|
HttpMethod,
|
|
24
24
|
StealthClient,
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
serializeRequestUrl,
|
|
68
68
|
} from "./request-options.js";
|
|
69
69
|
|
|
70
|
-
export const DEFAULT_PROFILE = "chrome-
|
|
70
|
+
export const DEFAULT_PROFILE = "chrome-desktop";
|
|
71
71
|
|
|
72
72
|
const MISSING_PROXY_WARNING =
|
|
73
73
|
"[provider-sdk] Provider requested proxy routing, but no proxy URL was configured. Continuing without proxy.";
|
|
@@ -101,6 +101,8 @@ function sensitiveQueryParamNames(url: string): string[] {
|
|
|
101
101
|
|
|
102
102
|
export type StealthClientOptions = ProxyResolutionOptions & {
|
|
103
103
|
warn?: (message: string) => void;
|
|
104
|
+
/** Abort all requests issued by this client. */
|
|
105
|
+
signal?: AbortSignal;
|
|
104
106
|
/**
|
|
105
107
|
* Proxy-only stealth transport overrides. Use only for upstream proxy products
|
|
106
108
|
* that terminate CONNECT with a private CA instead of tunneling the origin
|
|
@@ -244,7 +246,12 @@ function resolveDefaultWreqProfileMapping(): { identifier: string; os: Emulation
|
|
|
244
246
|
return { identifier, os: profile.platform };
|
|
245
247
|
}
|
|
246
248
|
|
|
247
|
-
|
|
249
|
+
let defaultWreqProfileMapping: ReturnType<typeof resolveDefaultWreqProfileMapping> | undefined;
|
|
250
|
+
|
|
251
|
+
function getDefaultWreqProfileMapping(): ReturnType<typeof resolveDefaultWreqProfileMapping> {
|
|
252
|
+
defaultWreqProfileMapping ??= resolveDefaultWreqProfileMapping();
|
|
253
|
+
return defaultWreqProfileMapping;
|
|
254
|
+
}
|
|
248
255
|
|
|
249
256
|
export function resolveWreqProfile(
|
|
250
257
|
profileName: string,
|
|
@@ -268,8 +275,9 @@ export function resolveWreqProfile(
|
|
|
268
275
|
// profile strings still run with the transport default instead of failing
|
|
269
276
|
// before the request starts. Removed built-in profile aliases above remain
|
|
270
277
|
// explicit errors so callers do not accidentally pin retired fingerprints.
|
|
271
|
-
|
|
272
|
-
|
|
278
|
+
const defaultMapping = getDefaultWreqProfileMapping();
|
|
279
|
+
identifier = defaultMapping.identifier;
|
|
280
|
+
os = defaultMapping.os;
|
|
273
281
|
}
|
|
274
282
|
|
|
275
283
|
const browser = closestWreqProfile(identifier, wreqProfiles);
|
|
@@ -354,6 +362,15 @@ export async function normalizeResponse(
|
|
|
354
362
|
response: StealthTransportResponse,
|
|
355
363
|
requestUrl?: string,
|
|
356
364
|
maxBodyBytes?: number,
|
|
365
|
+
): Promise<StealthResponse> {
|
|
366
|
+
return normalizeResponseWithSignal(response, requestUrl, maxBodyBytes);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function normalizeResponseWithSignal(
|
|
370
|
+
response: StealthTransportResponse,
|
|
371
|
+
requestUrl?: string,
|
|
372
|
+
maxBodyBytes?: number,
|
|
373
|
+
signal?: AbortSignal,
|
|
357
374
|
): Promise<StealthResponse> {
|
|
358
375
|
const headers = Object.fromEntries(response.headers.entries());
|
|
359
376
|
const cookies = new StealthCookieJar(
|
|
@@ -362,8 +379,8 @@ export async function normalizeResponse(
|
|
|
362
379
|
);
|
|
363
380
|
const bodyBytes =
|
|
364
381
|
maxBodyBytes === undefined
|
|
365
|
-
? await response
|
|
366
|
-
: await readResponseBodyWithLimit(response, maxBodyBytes);
|
|
382
|
+
? await readResponseArrayBuffer(response, signal)
|
|
383
|
+
: await readResponseBodyWithLimit(response, maxBodyBytes, signal);
|
|
367
384
|
const body = new TextDecoder().decode(bodyBytes);
|
|
368
385
|
|
|
369
386
|
return {
|
|
@@ -391,6 +408,42 @@ export async function normalizeResponse(
|
|
|
391
408
|
};
|
|
392
409
|
}
|
|
393
410
|
|
|
411
|
+
async function readResponseArrayBuffer(
|
|
412
|
+
response: StealthTransportResponse,
|
|
413
|
+
signal?: AbortSignal,
|
|
414
|
+
): Promise<ArrayBuffer> {
|
|
415
|
+
if (!signal) return response.arrayBuffer();
|
|
416
|
+
throwIfAmbientAborted(signal);
|
|
417
|
+
return new Promise((resolve, reject) => {
|
|
418
|
+
let settled = false;
|
|
419
|
+
const settle = (operation: () => void) => {
|
|
420
|
+
if (settled) return;
|
|
421
|
+
settled = true;
|
|
422
|
+
signal.removeEventListener("abort", onAbort);
|
|
423
|
+
operation();
|
|
424
|
+
};
|
|
425
|
+
const onAbort = () => {
|
|
426
|
+
const error = toAmbientCancellationError(signal);
|
|
427
|
+
try {
|
|
428
|
+
void response.body?.cancel().catch(() => undefined);
|
|
429
|
+
} catch {
|
|
430
|
+
// Preserve the cancellation error if accessing or cancelling the body fails.
|
|
431
|
+
}
|
|
432
|
+
settle(() => reject(error));
|
|
433
|
+
};
|
|
434
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
435
|
+
try {
|
|
436
|
+
void response.arrayBuffer().then(
|
|
437
|
+
(body) => settle(() => resolve(body)),
|
|
438
|
+
(error) => settle(() => reject(error)),
|
|
439
|
+
);
|
|
440
|
+
} catch (error) {
|
|
441
|
+
settle(() => reject(error));
|
|
442
|
+
}
|
|
443
|
+
if (signal.aborted) onAbort();
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
394
447
|
function responseTooLargeError(maxBodyBytes: number, observedBytes: number): TransportError {
|
|
395
448
|
return new TransportError(
|
|
396
449
|
`Response body exceeded maxBodyBytes limit of ${maxBodyBytes} bytes (observed ${observedBytes} bytes)`,
|
|
@@ -413,7 +466,9 @@ function declaredContentLength(headers: StealthTransportHeaders): number | undef
|
|
|
413
466
|
async function readResponseBodyWithLimit(
|
|
414
467
|
response: StealthTransportResponse,
|
|
415
468
|
maxBodyBytes: number,
|
|
469
|
+
signal?: AbortSignal,
|
|
416
470
|
): Promise<ArrayBuffer> {
|
|
471
|
+
throwIfAmbientAborted(signal);
|
|
417
472
|
const contentLength = declaredContentLength(response.headers);
|
|
418
473
|
if (contentLength !== undefined && contentLength > maxBodyBytes) {
|
|
419
474
|
await response.body?.cancel().catch(() => undefined);
|
|
@@ -433,7 +488,7 @@ async function readResponseBodyWithLimit(
|
|
|
433
488
|
let receivedBytes = 0;
|
|
434
489
|
try {
|
|
435
490
|
while (true) {
|
|
436
|
-
const { done, value } = await reader
|
|
491
|
+
const { done, value } = await readResponseBodyChunk(reader, signal);
|
|
437
492
|
if (done) break;
|
|
438
493
|
if (!value) continue;
|
|
439
494
|
receivedBytes += value.byteLength;
|
|
@@ -447,6 +502,13 @@ async function readResponseBodyWithLimit(
|
|
|
447
502
|
reader.releaseLock();
|
|
448
503
|
}
|
|
449
504
|
|
|
505
|
+
return concatenateResponseBodyChunks(chunks, receivedBytes);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function concatenateResponseBodyChunks(
|
|
509
|
+
chunks: readonly Uint8Array[],
|
|
510
|
+
receivedBytes: number,
|
|
511
|
+
): ArrayBuffer {
|
|
450
512
|
const bodyBytes = new Uint8Array(receivedBytes);
|
|
451
513
|
let offset = 0;
|
|
452
514
|
for (const chunk of chunks) {
|
|
@@ -456,6 +518,34 @@ async function readResponseBodyWithLimit(
|
|
|
456
518
|
return bodyBytes.buffer;
|
|
457
519
|
}
|
|
458
520
|
|
|
521
|
+
function readResponseBodyChunk(
|
|
522
|
+
reader: ReturnType<StealthTransportBody["getReader"]>,
|
|
523
|
+
signal?: AbortSignal,
|
|
524
|
+
): Promise<{ done: boolean; value?: Uint8Array }> {
|
|
525
|
+
if (!signal) return reader.read();
|
|
526
|
+
throwIfAmbientAborted(signal);
|
|
527
|
+
return new Promise((resolve, reject) => {
|
|
528
|
+
let settled = false;
|
|
529
|
+
const settle = (operation: () => void) => {
|
|
530
|
+
if (settled) return;
|
|
531
|
+
settled = true;
|
|
532
|
+
signal.removeEventListener("abort", onAbort);
|
|
533
|
+
operation();
|
|
534
|
+
};
|
|
535
|
+
const onAbort = () => {
|
|
536
|
+
const error = toAmbientCancellationError(signal);
|
|
537
|
+
void reader.cancel().catch(() => undefined);
|
|
538
|
+
settle(() => reject(error));
|
|
539
|
+
};
|
|
540
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
541
|
+
void reader.read().then(
|
|
542
|
+
(chunk) => settle(() => resolve(chunk)),
|
|
543
|
+
(error) => settle(() => reject(error)),
|
|
544
|
+
);
|
|
545
|
+
if (signal.aborted) onAbort();
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
459
549
|
function normalizeBody(body: StealthFetchOptions["body"]): string {
|
|
460
550
|
if (body === undefined) {
|
|
461
551
|
return "";
|
|
@@ -621,8 +711,41 @@ function normalizeStealthTransportError(error: unknown): TransportError {
|
|
|
621
711
|
});
|
|
622
712
|
}
|
|
623
713
|
|
|
624
|
-
function
|
|
625
|
-
|
|
714
|
+
function toAmbientCancellationError(
|
|
715
|
+
signal: AbortSignal,
|
|
716
|
+
error: unknown = signal.reason,
|
|
717
|
+
): TransportError {
|
|
718
|
+
if (error instanceof TransportError && error.code === "transport_cancelled") {
|
|
719
|
+
return error;
|
|
720
|
+
}
|
|
721
|
+
return new TransportError("Request cancelled", {
|
|
722
|
+
code: "transport_cancelled",
|
|
723
|
+
status: 0,
|
|
724
|
+
retryable: false,
|
|
725
|
+
...(error !== undefined
|
|
726
|
+
? { cause: error instanceof Error ? error : new Error(String(error)) }
|
|
727
|
+
: {}),
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function throwIfAmbientAborted(signal: AbortSignal | undefined): void {
|
|
732
|
+
if (signal?.aborted) throw toAmbientCancellationError(signal);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
736
|
+
if (!signal) return new Promise((resolve) => setTimeout(resolve, ms));
|
|
737
|
+
throwIfAmbientAborted(signal);
|
|
738
|
+
return new Promise((resolve, reject) => {
|
|
739
|
+
const onAbort = () => {
|
|
740
|
+
clearTimeout(timer);
|
|
741
|
+
reject(toAmbientCancellationError(signal));
|
|
742
|
+
};
|
|
743
|
+
const timer = setTimeout(() => {
|
|
744
|
+
signal.removeEventListener("abort", onAbort);
|
|
745
|
+
resolve();
|
|
746
|
+
}, ms);
|
|
747
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
748
|
+
});
|
|
626
749
|
}
|
|
627
750
|
|
|
628
751
|
function normalizeMethod(method: HttpMethod | string): StealthMethod {
|
|
@@ -687,6 +810,7 @@ async function fetchStealthRedirectChain(
|
|
|
687
810
|
requestUrl: string,
|
|
688
811
|
method: StealthMethod,
|
|
689
812
|
options: StealthFetchOptions,
|
|
813
|
+
signal?: AbortSignal,
|
|
690
814
|
): Promise<{ normalized: StealthResponse; response: StealthTransportResponse }> {
|
|
691
815
|
let currentUrl = requestUrl;
|
|
692
816
|
let currentMethod = method;
|
|
@@ -697,6 +821,7 @@ async function fetchStealthRedirectChain(
|
|
|
697
821
|
const deadline = options.timeout ? performance.now() + options.timeout : undefined;
|
|
698
822
|
|
|
699
823
|
while (true) {
|
|
824
|
+
throwIfAmbientAborted(signal);
|
|
700
825
|
const headers = { ...currentHeaders };
|
|
701
826
|
if (!hasHeader(headers, "Cookie")) {
|
|
702
827
|
const cookieHeader = cookieJar.toHeader(currentUrl);
|
|
@@ -706,10 +831,12 @@ async function fetchStealthRedirectChain(
|
|
|
706
831
|
headers: normalizeHeaders(headers),
|
|
707
832
|
method: currentMethod,
|
|
708
833
|
redirect: "manual",
|
|
834
|
+
...(signal ? { signal } : {}),
|
|
709
835
|
};
|
|
710
836
|
if (currentBody !== undefined) requestInit.body = currentBody;
|
|
711
837
|
|
|
712
838
|
await transport.clearCookies();
|
|
839
|
+
throwIfAmbientAborted(signal);
|
|
713
840
|
const remainingTimeout =
|
|
714
841
|
deadline === undefined ? undefined : Math.ceil(deadline - performance.now());
|
|
715
842
|
if (remainingTimeout !== undefined && remainingTimeout <= 0) {
|
|
@@ -720,6 +847,10 @@ async function fetchStealthRedirectChain(
|
|
|
720
847
|
}
|
|
721
848
|
if (remainingTimeout !== undefined) requestInit.timeout = remainingTimeout;
|
|
722
849
|
response = await transport.fetch(currentUrl, requestInit);
|
|
850
|
+
if (signal?.aborted) {
|
|
851
|
+
discardStealthRedirectBody(response);
|
|
852
|
+
throw toAmbientCancellationError(signal);
|
|
853
|
+
}
|
|
723
854
|
cookieJar.setFromCookieStrings(
|
|
724
855
|
setCookieHeadersFromResponse(response.headers),
|
|
725
856
|
response.url ?? currentUrl,
|
|
@@ -758,7 +889,12 @@ async function fetchStealthRedirectChain(
|
|
|
758
889
|
followedHops += 1;
|
|
759
890
|
}
|
|
760
891
|
|
|
761
|
-
const normalized = await
|
|
892
|
+
const normalized = await normalizeResponseWithSignal(
|
|
893
|
+
response,
|
|
894
|
+
currentUrl,
|
|
895
|
+
options.maxBodyBytes,
|
|
896
|
+
signal,
|
|
897
|
+
);
|
|
762
898
|
if (followedHops > 0) normalized.redirected = true;
|
|
763
899
|
return { normalized, response };
|
|
764
900
|
}
|
|
@@ -772,6 +908,12 @@ function createSessionFetcher(
|
|
|
772
908
|
let closed = false;
|
|
773
909
|
let hasWarnedMissingProxy = false;
|
|
774
910
|
const warn = clientOptions.warn ?? console.warn;
|
|
911
|
+
const intentAlias = getStealthProfileIntentAlias(defaultProfile);
|
|
912
|
+
if (intentAlias) {
|
|
913
|
+
warn(
|
|
914
|
+
`[provider-sdk] Stealth profile "${defaultProfile}" pins a browser version and is deprecated. Use the intent profile "${intentAlias}" so TLS, headers, and User-Agent stay aligned with SDK updates; derive an explicit User-Agent with getStealthProfile("${intentAlias}").userAgent instead of hardcoding one.`,
|
|
915
|
+
);
|
|
916
|
+
}
|
|
775
917
|
const cookieJar = new StealthCookieJar([], baseUrl);
|
|
776
918
|
|
|
777
919
|
async function getClientEntry(
|
|
@@ -807,19 +949,57 @@ function createSessionFetcher(
|
|
|
807
949
|
proxyUrl: string | undefined,
|
|
808
950
|
ignoreTlsErrors: boolean,
|
|
809
951
|
operation: (client: WreqSession) => Promise<T>,
|
|
952
|
+
signal?: AbortSignal,
|
|
810
953
|
): Promise<T> {
|
|
954
|
+
throwIfAmbientAborted(signal);
|
|
811
955
|
const entry = await getClientEntry(profileName, proxyUrl, ignoreTlsErrors);
|
|
956
|
+
throwIfAmbientAborted(signal);
|
|
812
957
|
const previous = entry.tail;
|
|
813
958
|
let release!: () => void;
|
|
814
959
|
entry.tail = new Promise<void>((resolve) => {
|
|
815
960
|
release = resolve;
|
|
816
961
|
});
|
|
817
|
-
|
|
962
|
+
let acquired = false;
|
|
818
963
|
try {
|
|
819
|
-
|
|
964
|
+
await waitForClientTurn(previous, signal);
|
|
965
|
+
acquired = true;
|
|
966
|
+
throwIfAmbientAborted(signal);
|
|
967
|
+
const client = await entry.session;
|
|
968
|
+
throwIfAmbientAborted(signal);
|
|
969
|
+
const result = await operation(client);
|
|
970
|
+
throwIfAmbientAborted(signal);
|
|
971
|
+
return result;
|
|
820
972
|
} finally {
|
|
821
|
-
|
|
973
|
+
if (acquired) {
|
|
974
|
+
release();
|
|
975
|
+
} else {
|
|
976
|
+
void previous.then(release, release);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
async function waitForClientTurn(previous: Promise<void>, signal?: AbortSignal): Promise<void> {
|
|
982
|
+
if (!signal) {
|
|
983
|
+
await previous;
|
|
984
|
+
return;
|
|
822
985
|
}
|
|
986
|
+
throwIfAmbientAborted(signal);
|
|
987
|
+
await new Promise<void>((resolve, reject) => {
|
|
988
|
+
let settled = false;
|
|
989
|
+
const settle = (operation: () => void) => {
|
|
990
|
+
if (settled) return;
|
|
991
|
+
settled = true;
|
|
992
|
+
signal.removeEventListener("abort", onAbort);
|
|
993
|
+
operation();
|
|
994
|
+
};
|
|
995
|
+
const onAbort = () => settle(() => reject(toAmbientCancellationError(signal)));
|
|
996
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
997
|
+
void previous.then(
|
|
998
|
+
() => settle(resolve),
|
|
999
|
+
(error) => settle(() => reject(error)),
|
|
1000
|
+
);
|
|
1001
|
+
if (signal.aborted) onAbort();
|
|
1002
|
+
});
|
|
823
1003
|
}
|
|
824
1004
|
|
|
825
1005
|
async function resolveRequestProxy(
|
|
@@ -881,6 +1061,7 @@ function createSessionFetcher(
|
|
|
881
1061
|
throw redactSensitiveRequestError(error, url, options.sensitiveParams);
|
|
882
1062
|
}
|
|
883
1063
|
})();
|
|
1064
|
+
throwIfAmbientAborted(clientOptions.signal);
|
|
884
1065
|
const hasPolicyProxy = isPolicyManagedProxy(clientOptions);
|
|
885
1066
|
const usesPolicyAllocator = hasPolicyProxy && !options.proxy && !clientOptions.proxy;
|
|
886
1067
|
const retryAttemptCap = Math.max(1, stealthRetryOptions?.attempts ?? 1);
|
|
@@ -921,6 +1102,7 @@ function createSessionFetcher(
|
|
|
921
1102
|
const attemptedProxies = new Set<string>();
|
|
922
1103
|
|
|
923
1104
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
1105
|
+
throwIfAmbientAborted(clientOptions.signal);
|
|
924
1106
|
let proxy: string | undefined;
|
|
925
1107
|
let attemptProxy: ResolvedAttemptProxy | undefined;
|
|
926
1108
|
// Reuse the exact serialization used by this outbound attempt in its catch path.
|
|
@@ -951,6 +1133,7 @@ function createSessionFetcher(
|
|
|
951
1133
|
});
|
|
952
1134
|
};
|
|
953
1135
|
try {
|
|
1136
|
+
throwIfAmbientAborted(clientOptions.signal);
|
|
954
1137
|
const sensitiveParams = normalizeSensitiveParams(options.sensitiveParams);
|
|
955
1138
|
const structural = redactUrlQueryParams(url, Object.keys(sensitiveParams ?? {}));
|
|
956
1139
|
fallbackSensitiveValues = [
|
|
@@ -963,6 +1146,7 @@ function createSessionFetcher(
|
|
|
963
1146
|
fallbackRedactedUrl = structural.redactedUrl;
|
|
964
1147
|
assertNoUnsupportedFingerprintOverrides(options);
|
|
965
1148
|
attemptProxy = await resolveRequestProxy(options, attempt, refreshAttempt);
|
|
1149
|
+
throwIfAmbientAborted(clientOptions.signal);
|
|
966
1150
|
proxy = attemptProxy.url;
|
|
967
1151
|
if (proxy && dedupeAllocatorEndpoints) {
|
|
968
1152
|
// An under-filled allocation repeats endpoints (via the modulo
|
|
@@ -991,7 +1175,15 @@ function createSessionFetcher(
|
|
|
991
1175
|
proxy,
|
|
992
1176
|
ignoreTlsErrors,
|
|
993
1177
|
(transport) =>
|
|
994
|
-
fetchStealthRedirectChain(
|
|
1178
|
+
fetchStealthRedirectChain(
|
|
1179
|
+
transport,
|
|
1180
|
+
cookieJar,
|
|
1181
|
+
requestUrl,
|
|
1182
|
+
method,
|
|
1183
|
+
options,
|
|
1184
|
+
clientOptions.signal,
|
|
1185
|
+
),
|
|
1186
|
+
clientOptions.signal,
|
|
995
1187
|
);
|
|
996
1188
|
|
|
997
1189
|
if (proxy && isProxyConnectFailureResponse(response, normalized.body)) {
|
|
@@ -1039,14 +1231,26 @@ function createSessionFetcher(
|
|
|
1039
1231
|
const sensitiveValues = serializedUrl?.sensitiveValues ?? fallbackSensitiveValues;
|
|
1040
1232
|
let normalizedError: TransportError;
|
|
1041
1233
|
try {
|
|
1234
|
+
throwIfAmbientAborted(clientOptions.signal);
|
|
1042
1235
|
normalizedError = normalizeStealthTransportError(error);
|
|
1043
1236
|
} catch (normalizationError) {
|
|
1044
|
-
|
|
1237
|
+
const redactedNormalizationError = redactSensitiveError(
|
|
1045
1238
|
normalizationError,
|
|
1046
1239
|
sensitiveValues,
|
|
1047
1240
|
serializedUrl?.requestUrl ?? fallbackRequestUrl,
|
|
1048
1241
|
serializedUrl?.redactedUrl ?? fallbackRedactedUrl,
|
|
1049
1242
|
);
|
|
1243
|
+
if (
|
|
1244
|
+
normalizationError instanceof TransportError &&
|
|
1245
|
+
normalizationError.code === "transport_cancelled"
|
|
1246
|
+
) {
|
|
1247
|
+
recordProxyAttempt(
|
|
1248
|
+
"error",
|
|
1249
|
+
proxyAttemptErrorCode(normalizationError),
|
|
1250
|
+
proxyAttemptStatus(normalizationError),
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
1253
|
+
throw redactedNormalizationError;
|
|
1050
1254
|
}
|
|
1051
1255
|
const retryErrorCode = proxyAttemptErrorCode(normalizedError);
|
|
1052
1256
|
const refreshableProxyError = isProxyPoolRefreshableError(normalizedError);
|
|
@@ -1104,8 +1308,12 @@ function createSessionFetcher(
|
|
|
1104
1308
|
})
|
|
1105
1309
|
) {
|
|
1106
1310
|
if (stealthRetryOptions) {
|
|
1107
|
-
await sleep(
|
|
1311
|
+
await sleep(
|
|
1312
|
+
computeProxyTransportRetryDelayMs(stealthRetryOptions, attempt + 1),
|
|
1313
|
+
clientOptions.signal,
|
|
1314
|
+
);
|
|
1108
1315
|
}
|
|
1316
|
+
throwIfAmbientAborted(clientOptions.signal);
|
|
1109
1317
|
continue;
|
|
1110
1318
|
}
|
|
1111
1319
|
throw normalizedError;
|
|
@@ -1117,11 +1325,13 @@ function createSessionFetcher(
|
|
|
1117
1325
|
stalePoolError &&
|
|
1118
1326
|
refreshAttempt < MAX_POLICY_PROXY_POOL_REFRESHES
|
|
1119
1327
|
) {
|
|
1328
|
+
throwIfAmbientAborted(clientOptions.signal);
|
|
1120
1329
|
await invalidateProxyResolutionCacheAsync({
|
|
1121
1330
|
proxyPolicy: clientOptions.proxyPolicy,
|
|
1122
1331
|
upstream: clientOptions.upstream,
|
|
1123
1332
|
affinityKey: clientOptions.affinityKey,
|
|
1124
1333
|
});
|
|
1334
|
+
throwIfAmbientAborted(clientOptions.signal);
|
|
1125
1335
|
continue;
|
|
1126
1336
|
}
|
|
1127
1337
|
|
|
@@ -1157,6 +1367,7 @@ function createSessionFetcher(
|
|
|
1157
1367
|
break;
|
|
1158
1368
|
}
|
|
1159
1369
|
|
|
1370
|
+
throwIfAmbientAborted(clientOptions.signal);
|
|
1160
1371
|
throw normalizeStealthTransportError(lastError);
|
|
1161
1372
|
},
|
|
1162
1373
|
cookies: cookieJar,
|
|
@@ -1380,16 +1591,31 @@ function createSessionFetcher(
|
|
|
1380
1591
|
proxy: string,
|
|
1381
1592
|
): Promise<"source_ip_denied" | "edge_auth_rejected" | undefined> {
|
|
1382
1593
|
try {
|
|
1383
|
-
return await withClient(
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1594
|
+
return await withClient(
|
|
1595
|
+
profileName,
|
|
1596
|
+
proxy,
|
|
1597
|
+
false,
|
|
1598
|
+
async (client) => {
|
|
1599
|
+
throwIfAmbientAborted(clientOptions.signal);
|
|
1600
|
+
await client.clearCookies();
|
|
1601
|
+
throwIfAmbientAborted(clientOptions.signal);
|
|
1602
|
+
const response = await client.fetch(PROXY_AUTH_DIAGNOSTIC_URL, {
|
|
1603
|
+
method: "GET",
|
|
1604
|
+
timeout: PROXY_AUTH_DIAGNOSTIC_TIMEOUT_MS,
|
|
1605
|
+
...(clientOptions.signal ? { signal: clientOptions.signal } : {}),
|
|
1606
|
+
});
|
|
1607
|
+
const normalized = await normalizeResponseWithSignal(
|
|
1608
|
+
response,
|
|
1609
|
+
undefined,
|
|
1610
|
+
undefined,
|
|
1611
|
+
clientOptions.signal,
|
|
1612
|
+
);
|
|
1613
|
+
return classifyProxyAuthDiagnosticMessage(normalized.body);
|
|
1614
|
+
},
|
|
1615
|
+
clientOptions.signal,
|
|
1616
|
+
);
|
|
1392
1617
|
} catch (error) {
|
|
1618
|
+
throwIfAmbientAborted(clientOptions.signal);
|
|
1393
1619
|
const message =
|
|
1394
1620
|
error instanceof Error
|
|
1395
1621
|
? [error.message, error.cause instanceof Error ? error.cause.message : ""]
|
|
@@ -667,6 +667,7 @@ function createProviderContext(
|
|
|
667
667
|
upstream: proxyClientOptions.upstream,
|
|
668
668
|
affinityKey: proxyClientOptions.affinityKey,
|
|
669
669
|
telemetry: proxyTelemetry,
|
|
670
|
+
...(signal ? { signal } : {}),
|
|
670
671
|
};
|
|
671
672
|
const { capabilityModules } = options;
|
|
672
673
|
const logStealthCleanupError = (error: unknown) =>
|
|
@@ -715,7 +716,7 @@ function createProviderContext(
|
|
|
715
716
|
? stealthProfile
|
|
716
717
|
? capabilityModules.stealth.createStealthClient(
|
|
717
718
|
stealthBaseUrl,
|
|
718
|
-
|
|
719
|
+
provider.stealth!.profile,
|
|
719
720
|
stealthClientOptions,
|
|
720
721
|
)
|
|
721
722
|
: capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
|
|
@@ -723,7 +724,7 @@ function createProviderContext(
|
|
|
723
724
|
? createLazyStealthClient(
|
|
724
725
|
logStealthCleanupError,
|
|
725
726
|
stealthBaseUrl,
|
|
726
|
-
|
|
727
|
+
provider.stealth!.profile,
|
|
727
728
|
stealthClientOptions,
|
|
728
729
|
)
|
|
729
730
|
: createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
|
|
@@ -891,6 +892,7 @@ function createAuthFlowContext(
|
|
|
891
892
|
upstream: proxyClientOptions.upstream,
|
|
892
893
|
affinityKey: proxyClientOptions.affinityKey,
|
|
893
894
|
telemetry: proxyTelemetry,
|
|
895
|
+
...(signal ? { signal } : {}),
|
|
894
896
|
};
|
|
895
897
|
const { capabilityModules } = options;
|
|
896
898
|
const logStealthCleanupError = (error: unknown) =>
|
|
@@ -930,7 +932,7 @@ function createAuthFlowContext(
|
|
|
930
932
|
? stealthProfile
|
|
931
933
|
? capabilityModules.stealth.createStealthClient(
|
|
932
934
|
stealthBaseUrl,
|
|
933
|
-
|
|
935
|
+
provider.stealth!.profile,
|
|
934
936
|
stealthClientOptions,
|
|
935
937
|
)
|
|
936
938
|
: capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
|
|
@@ -938,7 +940,7 @@ function createAuthFlowContext(
|
|
|
938
940
|
? createLazyStealthClient(
|
|
939
941
|
logStealthCleanupError,
|
|
940
942
|
stealthBaseUrl,
|
|
941
|
-
|
|
943
|
+
provider.stealth!.profile,
|
|
942
944
|
stealthClientOptions,
|
|
943
945
|
)
|
|
944
946
|
: createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
|