@apifuse/provider-sdk 2.2.0-beta.48 → 2.2.0-beta.49
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 +91 -36
- package/CHANGELOG.md +4 -0
- package/README.md +11 -9
- package/SUBMISSION.md +1 -1
- package/bin/apifuse-dev.ts +24 -13
- package/bin/apifuse-migrate-operation-declaration.ts +55 -0
- package/bin/apifuse-pack-smoke.ts +1 -1
- package/bin/apifuse-pack-types.ts +2 -1
- package/bin/apifuse-record.ts +30 -16
- package/bin/apifuse-submit-check.ts +20 -35
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +11 -0
- package/dist/cli/migrate-operation-declaration.d.ts +59 -0
- package/dist/cli/migrate-operation-declaration.js +1178 -0
- package/dist/cli/templates/provider/README.md.tpl +3 -3
- package/dist/cli/templates/provider/operations/ping.ts.tpl +2 -0
- package/dist/config/loader.d.ts +2 -0
- package/dist/config/loader.js +18 -7
- package/dist/contract-types.d.ts +11 -5
- package/dist/contract.js +21 -10
- package/dist/define.d.ts +25 -22
- package/dist/define.js +49 -75
- 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/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/lint.d.ts +7 -15
- package/dist/lint.js +45 -70
- package/dist/provider.d.ts +3 -1
- package/dist/provider.js +1 -0
- 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 +2 -1
- package/dist/runtime/executor.js +3 -36
- package/dist/runtime/insights.js +2 -2
- package/dist/runtime/otlp.d.ts +71 -2
- package/dist/runtime/otlp.js +397 -16
- package/dist/runtime/resolver-vendors/capsolver.js +3 -3
- package/dist/runtime/resolver.js +3 -3
- package/dist/runtime/stealth.d.ts +13 -4
- package/dist/runtime/stealth.js +362 -85
- package/dist/runtime/trace-config.js +2 -1
- package/dist/runtime/trace.d.ts +5 -0
- package/dist/runtime/trace.js +43 -10
- package/dist/server/self-test.d.ts +1 -3
- package/dist/server/self-test.js +2 -12
- package/dist/server/serve-implementation.d.ts +6 -1
- package/dist/server/serve-implementation.js +55 -40
- package/dist/server/trace-output.d.ts +3 -1
- package/dist/server/trace-output.js +61 -2
- package/dist/stealth/profiles.d.ts +9 -8
- package/dist/stealth/profiles.js +123 -286
- package/dist/types.d.ts +111 -108
- package/package.json +2 -1
- 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 +13 -0
- package/src/cli/migrate-operation-declaration.ts +1654 -0
- package/src/cli/templates/provider/README.md.tpl +3 -3
- package/src/cli/templates/provider/operations/ping.ts.tpl +2 -0
- package/src/config/loader.ts +31 -6
- package/src/contract-types.ts +11 -5
- package/src/contract.ts +21 -10
- package/src/define.ts +107 -119
- package/src/dev.ts +4 -1
- package/src/engine.ts +279 -0
- package/src/index.ts +13 -5
- package/src/lint.ts +58 -92
- package/src/provider.ts +25 -3
- package/src/runtime/chrome149-header-order.ts +330 -0
- package/src/runtime/env.ts +13 -0
- package/src/runtime/executor.ts +7 -40
- package/src/runtime/insights.ts +2 -2
- package/src/runtime/otlp.ts +467 -21
- package/src/runtime/resolver-vendors/capsolver.ts +4 -3
- package/src/runtime/resolver.ts +3 -3
- package/src/runtime/stealth.ts +435 -103
- package/src/runtime/trace-config.ts +3 -2
- package/src/runtime/trace.ts +57 -17
- package/src/server/self-test.ts +2 -9
- package/src/server/serve-implementation.ts +89 -72
- package/src/server/trace-output.ts +99 -2
- package/src/stealth/profiles.ts +169 -327
- package/src/types.ts +109 -137
package/src/runtime/stealth.ts
CHANGED
|
@@ -18,16 +18,23 @@ import {
|
|
|
18
18
|
vendorFromResolvedSource,
|
|
19
19
|
} from "../config/loader.js";
|
|
20
20
|
import { SDKError, TransportError } from "../errors.js";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
DEFAULT_STEALTH_BROWSER,
|
|
23
|
+
DEFAULT_STEALTH_OS,
|
|
24
|
+
getStealthProfile,
|
|
25
|
+
resolveStealthProfileSelection,
|
|
26
|
+
} from "../stealth/profiles.js";
|
|
22
27
|
import type {
|
|
23
28
|
HttpMethod,
|
|
24
29
|
StealthClient,
|
|
25
30
|
StealthFetchOptions,
|
|
31
|
+
StealthProfileDescriptor,
|
|
32
|
+
StealthProfileSelection,
|
|
26
33
|
StealthRedirectHop,
|
|
27
34
|
StealthResponse,
|
|
28
35
|
StealthSession,
|
|
29
36
|
} from "../types.js";
|
|
30
|
-
import {
|
|
37
|
+
import { chrome149HeaderOrder } from "./chrome149-header-order.js";
|
|
31
38
|
import {
|
|
32
39
|
createProxyAuthIpDeniedError,
|
|
33
40
|
createProxyEdgeAuthRejectedError,
|
|
@@ -66,8 +73,12 @@ import {
|
|
|
66
73
|
redactUrlQueryParams,
|
|
67
74
|
serializeRequestUrl,
|
|
68
75
|
} from "./request-options.js";
|
|
76
|
+
import { StealthCookieJar } from "./stealth-cookies.js";
|
|
69
77
|
|
|
70
|
-
export const
|
|
78
|
+
export const DEFAULT_STEALTH_PROFILE: StealthProfileDescriptor = Object.freeze({
|
|
79
|
+
browser: DEFAULT_STEALTH_BROWSER,
|
|
80
|
+
os: DEFAULT_STEALTH_OS,
|
|
81
|
+
});
|
|
71
82
|
|
|
72
83
|
const MISSING_PROXY_WARNING =
|
|
73
84
|
"[provider-sdk] Provider requested proxy routing, but no proxy URL was configured. Continuing without proxy.";
|
|
@@ -103,6 +114,15 @@ export type StealthClientOptions = ProxyResolutionOptions & {
|
|
|
103
114
|
warn?: (message: string) => void;
|
|
104
115
|
/** Abort all requests issued by this client. */
|
|
105
116
|
signal?: AbortSignal;
|
|
117
|
+
/** Browser identity and declaration-wide HTTP language defaults. */
|
|
118
|
+
stealth?: StealthProfileSelection & {
|
|
119
|
+
/**
|
|
120
|
+
* Default Accept-Language value, emitted where Chrome's network layer appends
|
|
121
|
+
* it (after Accept-Encoding). Requests may override it through `headers`, in
|
|
122
|
+
* which case it is placed like any other caller header.
|
|
123
|
+
*/
|
|
124
|
+
acceptLanguage?: string;
|
|
125
|
+
};
|
|
106
126
|
/**
|
|
107
127
|
* Proxy-only stealth transport overrides. Use only for upstream proxy products
|
|
108
128
|
* that terminate CONNECT with a private CA instead of tunneling the origin
|
|
@@ -111,20 +131,6 @@ export type StealthClientOptions = ProxyResolutionOptions & {
|
|
|
111
131
|
proxyStealth?: { insecureSkipVerify?: boolean };
|
|
112
132
|
};
|
|
113
133
|
|
|
114
|
-
const REMOVED_CHROME_PROFILE_NAMES = new Set([
|
|
115
|
-
"chrome-120",
|
|
116
|
-
"chrome-124",
|
|
117
|
-
"chrome-129",
|
|
118
|
-
"chrome-130",
|
|
119
|
-
"chrome-131",
|
|
120
|
-
"chrome-133",
|
|
121
|
-
"chrome-144",
|
|
122
|
-
"chrome-146-psk",
|
|
123
|
-
"chrome-131-psk",
|
|
124
|
-
"chrome-130-psk",
|
|
125
|
-
"edge-131",
|
|
126
|
-
]);
|
|
127
|
-
|
|
128
134
|
type StealthTransportHeaders = {
|
|
129
135
|
entries(): IterableIterator<[string, string]>;
|
|
130
136
|
get(name: string): string | null;
|
|
@@ -226,64 +232,21 @@ function closestWreqProfile(
|
|
|
226
232
|
return closest?.name;
|
|
227
233
|
}
|
|
228
234
|
|
|
229
|
-
function resolveDefaultWreqProfileMapping(): { identifier: string; os: EmulationOS } {
|
|
230
|
-
let profile: ReturnType<typeof getStealthProfile>;
|
|
231
|
-
try {
|
|
232
|
-
profile = getStealthProfile(DEFAULT_PROFILE);
|
|
233
|
-
} catch (error) {
|
|
234
|
-
throw new SDKError(
|
|
235
|
-
`Default stealth profile "${DEFAULT_PROFILE}" cannot be mapped to a wreq-js browser profile.`,
|
|
236
|
-
{ cause: error instanceof Error ? error : undefined },
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const identifier = profile.tlsClientIdentifier?.toLowerCase() ?? "";
|
|
241
|
-
if (!parseProfileIdentifier(identifier)) {
|
|
242
|
-
throw new SDKError(
|
|
243
|
-
`Default stealth profile "${DEFAULT_PROFILE}" cannot be mapped to a wreq-js browser profile.`,
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
return { identifier, os: profile.platform };
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
let defaultWreqProfileMapping: ReturnType<typeof resolveDefaultWreqProfileMapping> | undefined;
|
|
250
|
-
|
|
251
|
-
function getDefaultWreqProfileMapping(): ReturnType<typeof resolveDefaultWreqProfileMapping> {
|
|
252
|
-
defaultWreqProfileMapping ??= resolveDefaultWreqProfileMapping();
|
|
253
|
-
return defaultWreqProfileMapping;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
235
|
export function resolveWreqProfile(
|
|
257
|
-
|
|
236
|
+
selection: StealthProfileSelection,
|
|
258
237
|
wreqProfiles: readonly BrowserProfile[],
|
|
259
238
|
): {
|
|
260
239
|
browser: BrowserProfile;
|
|
261
240
|
os: EmulationOS;
|
|
262
241
|
} {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
let identifier: string;
|
|
268
|
-
let os: EmulationOS;
|
|
269
|
-
try {
|
|
270
|
-
const profile = getStealthProfile(profileName);
|
|
271
|
-
identifier = profile.tlsClientIdentifier?.toLowerCase() ?? "";
|
|
272
|
-
os = profile.platform;
|
|
273
|
-
} catch {
|
|
274
|
-
// Preserve the previous ctx.stealth.fetch() compatibility behavior: unknown
|
|
275
|
-
// profile strings still run with the transport default instead of failing
|
|
276
|
-
// before the request starts. Removed built-in profile aliases above remain
|
|
277
|
-
// explicit errors so callers do not accidentally pin retired fingerprints.
|
|
278
|
-
const defaultMapping = getDefaultWreqProfileMapping();
|
|
279
|
-
identifier = defaultMapping.identifier;
|
|
280
|
-
os = defaultMapping.os;
|
|
281
|
-
}
|
|
242
|
+
const profile = getStealthProfile(selection);
|
|
243
|
+
const identifier = profile.tlsClientIdentifier?.toLowerCase() ?? "";
|
|
244
|
+
const os = profile.os;
|
|
282
245
|
|
|
283
246
|
const browser = closestWreqProfile(identifier, wreqProfiles);
|
|
284
247
|
if (!browser) {
|
|
285
248
|
throw new SDKError(
|
|
286
|
-
`Stealth profile
|
|
249
|
+
`Stealth profile ${profile.browser}/${profile.os} cannot be mapped to a wreq-js browser profile.`,
|
|
287
250
|
);
|
|
288
251
|
}
|
|
289
252
|
return { browser, os };
|
|
@@ -297,15 +260,337 @@ function headerEntriesFromHeaders(headers: StealthTransportHeaders): [string, st
|
|
|
297
260
|
return Array.from(headers.entries());
|
|
298
261
|
}
|
|
299
262
|
|
|
263
|
+
type HeaderTuple = [string, string];
|
|
264
|
+
|
|
300
265
|
function normalizeHeaders(
|
|
301
266
|
headers: Record<string, string | string[] | undefined>,
|
|
302
267
|
): Record<string, string> {
|
|
303
|
-
|
|
304
|
-
|
|
268
|
+
return Object.fromEntries(normalizedHeaderEntries(headers));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function trimOuterHttpWhitespace(value: string): string {
|
|
272
|
+
return value.replace(/^[\t\n\r ]+|[\t\n\r ]+$/g, "");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function normalizedHeaderEntries(
|
|
276
|
+
headers: Record<string, string | string[] | undefined>,
|
|
277
|
+
): HeaderTuple[] {
|
|
278
|
+
const entries: HeaderTuple[] = [];
|
|
279
|
+
const indices = new Map<string, number>();
|
|
280
|
+
for (const [originalName, originalValue] of Object.entries(headers)) {
|
|
281
|
+
if (originalValue === undefined) continue;
|
|
282
|
+
const name = originalName.toLowerCase();
|
|
283
|
+
const value = (Array.isArray(originalValue) ? originalValue : [originalValue])
|
|
284
|
+
.map(trimOuterHttpWhitespace)
|
|
285
|
+
.join(", ");
|
|
286
|
+
const existingIndex = indices.get(name);
|
|
287
|
+
if (existingIndex === undefined) {
|
|
288
|
+
indices.set(name, entries.length);
|
|
289
|
+
entries.push([name, value]);
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
const existing = entries[existingIndex];
|
|
293
|
+
if (existing) existing[1] = `${existing[1]}, ${value}`;
|
|
294
|
+
}
|
|
295
|
+
return entries;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const SDK_OWNED_EXACT_CHROME_HEADERS = new Set([
|
|
299
|
+
"host",
|
|
300
|
+
"connection",
|
|
301
|
+
"user-agent",
|
|
302
|
+
"sec-ch-ua",
|
|
303
|
+
"sec-ch-ua-mobile",
|
|
304
|
+
"sec-ch-ua-platform",
|
|
305
|
+
"accept-encoding",
|
|
306
|
+
]);
|
|
307
|
+
|
|
308
|
+
// Non-pseudo-header order of real Chrome 149. The fixture captures
|
|
309
|
+
// (chrome-ground-truth-capture.json, chrome-extended-capture.json,
|
|
310
|
+
// h1-casing-capture.json) were taken through Playwright's `locale` option, which
|
|
311
|
+
// installs Accept-Language via DevTools next to User-Agent; real Chrome only
|
|
312
|
+
// receives it from //net (URLRequestHttpJob::AddExtraHeaders), after
|
|
313
|
+
// Accept-Encoding and before Cookie, as al-placement-capture.json B/C show.
|
|
314
|
+
const CHROME_HEADER_ORDERS = {
|
|
315
|
+
navigation: [
|
|
316
|
+
"sec-ch-ua",
|
|
317
|
+
"sec-ch-ua-mobile",
|
|
318
|
+
"sec-ch-ua-platform",
|
|
319
|
+
"upgrade-insecure-requests",
|
|
320
|
+
"user-agent",
|
|
321
|
+
"accept",
|
|
322
|
+
"sec-fetch-site",
|
|
323
|
+
"sec-fetch-mode",
|
|
324
|
+
"sec-fetch-user",
|
|
325
|
+
"sec-fetch-dest",
|
|
326
|
+
"accept-encoding",
|
|
327
|
+
"accept-language",
|
|
328
|
+
"cookie",
|
|
329
|
+
"priority",
|
|
330
|
+
],
|
|
331
|
+
// xhr has no fixed table: it is emulated by chrome149HeaderOrder.
|
|
332
|
+
post: [
|
|
333
|
+
"content-length",
|
|
334
|
+
"sec-ch-ua-platform",
|
|
335
|
+
"user-agent",
|
|
336
|
+
"sec-ch-ua",
|
|
337
|
+
"content-type",
|
|
338
|
+
"sec-ch-ua-mobile",
|
|
339
|
+
"accept",
|
|
340
|
+
"origin",
|
|
341
|
+
"sec-fetch-site",
|
|
342
|
+
"sec-fetch-mode",
|
|
343
|
+
"sec-fetch-dest",
|
|
344
|
+
"referer",
|
|
345
|
+
"accept-encoding",
|
|
346
|
+
"accept-language",
|
|
347
|
+
"cookie",
|
|
348
|
+
"priority",
|
|
349
|
+
],
|
|
350
|
+
} as const;
|
|
351
|
+
|
|
352
|
+
const CHROME_H1_HEADER_ORDERS = {
|
|
353
|
+
navigation: [
|
|
354
|
+
"host",
|
|
355
|
+
"connection",
|
|
356
|
+
"sec-ch-ua",
|
|
357
|
+
"sec-ch-ua-mobile",
|
|
358
|
+
"sec-ch-ua-platform",
|
|
359
|
+
"upgrade-insecure-requests",
|
|
360
|
+
"user-agent",
|
|
361
|
+
"accept",
|
|
362
|
+
"sec-fetch-site",
|
|
363
|
+
"sec-fetch-mode",
|
|
364
|
+
"sec-fetch-user",
|
|
365
|
+
"sec-fetch-dest",
|
|
366
|
+
"accept-encoding",
|
|
367
|
+
"accept-language",
|
|
368
|
+
"cookie",
|
|
369
|
+
],
|
|
370
|
+
xhr: [
|
|
371
|
+
"host",
|
|
372
|
+
"connection",
|
|
373
|
+
"sec-ch-ua-platform",
|
|
374
|
+
"cache-control",
|
|
375
|
+
"x-requested-with",
|
|
376
|
+
"user-agent",
|
|
377
|
+
"sec-ch-ua",
|
|
378
|
+
"sec-ch-ua-mobile",
|
|
379
|
+
"accept",
|
|
380
|
+
"sec-fetch-site",
|
|
381
|
+
"sec-fetch-mode",
|
|
382
|
+
"sec-fetch-dest",
|
|
383
|
+
"referer",
|
|
384
|
+
"accept-encoding",
|
|
385
|
+
"accept-language",
|
|
386
|
+
"cookie",
|
|
387
|
+
],
|
|
388
|
+
post: [
|
|
389
|
+
"host",
|
|
390
|
+
"connection",
|
|
391
|
+
"content-length",
|
|
392
|
+
"sec-ch-ua-platform",
|
|
393
|
+
"user-agent",
|
|
394
|
+
"sec-ch-ua",
|
|
395
|
+
"content-type",
|
|
396
|
+
"sec-ch-ua-mobile",
|
|
397
|
+
"accept",
|
|
398
|
+
"origin",
|
|
399
|
+
"sec-fetch-site",
|
|
400
|
+
"sec-fetch-mode",
|
|
401
|
+
"sec-fetch-dest",
|
|
402
|
+
"referer",
|
|
403
|
+
"accept-encoding",
|
|
404
|
+
"accept-language",
|
|
405
|
+
"cookie",
|
|
406
|
+
],
|
|
407
|
+
} as const;
|
|
408
|
+
|
|
409
|
+
const CHROME_H1_HEADER_NAMES: Record<string, string> = {
|
|
410
|
+
host: "Host",
|
|
411
|
+
connection: "Connection",
|
|
412
|
+
"upgrade-insecure-requests": "Upgrade-Insecure-Requests",
|
|
413
|
+
"user-agent": "User-Agent",
|
|
414
|
+
"accept-language": "Accept-Language",
|
|
415
|
+
accept: "Accept",
|
|
416
|
+
"sec-fetch-site": "Sec-Fetch-Site",
|
|
417
|
+
"sec-fetch-mode": "Sec-Fetch-Mode",
|
|
418
|
+
"sec-fetch-user": "Sec-Fetch-User",
|
|
419
|
+
"sec-fetch-dest": "Sec-Fetch-Dest",
|
|
420
|
+
"accept-encoding": "Accept-Encoding",
|
|
421
|
+
cookie: "Cookie",
|
|
422
|
+
"cache-control": "Cache-Control",
|
|
423
|
+
"x-requested-with": "X-Requested-With",
|
|
424
|
+
referer: "Referer",
|
|
425
|
+
"content-length": "Content-Length",
|
|
426
|
+
"content-type": "Content-Type",
|
|
427
|
+
origin: "Origin",
|
|
428
|
+
range: "Range",
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
type ChromeRequestClass = keyof typeof CHROME_HEADER_ORDERS | "xhr";
|
|
432
|
+
|
|
433
|
+
function chromeHeaderOrder(
|
|
434
|
+
requestClass: ChromeRequestClass,
|
|
435
|
+
isHttp1: boolean,
|
|
436
|
+
caller: ReadonlyMap<string, string>,
|
|
437
|
+
): readonly string[] {
|
|
438
|
+
if (isHttp1) return CHROME_H1_HEADER_ORDERS[requestClass];
|
|
439
|
+
if (requestClass !== "xhr") return CHROME_HEADER_ORDERS[requestClass];
|
|
440
|
+
// Cookie and Referer are forbidden Fetch headers, so they never enter the page
|
|
441
|
+
// Fetch Headers map the emulator models. Range does occupy a map bucket
|
|
442
|
+
// (m1-capture.json range_*), but HttpCache::Transaction removes it from the
|
|
443
|
+
// request headers and PartialData re-adds it at the tail.
|
|
444
|
+
const order = chrome149HeaderOrder(
|
|
445
|
+
[...caller.keys()].filter((name) => name !== "cookie" && name !== "referer"),
|
|
446
|
+
);
|
|
447
|
+
if (caller.has("range")) order.splice(order.indexOf("range"), 1);
|
|
448
|
+
// //net appends Accept-Encoding and, unless the caller supplied one,
|
|
449
|
+
// Accept-Language (URLRequestHttpJob::AddExtraHeaders), then writes the Cookie
|
|
450
|
+
// header (SetCookieHeaderAndStart); the HTTP cache re-adds a caller Range after
|
|
451
|
+
// that, and the HTTP/2 Priority header stays last.
|
|
452
|
+
let insertAt = order.indexOf("accept-encoding") + 1;
|
|
453
|
+
if (order[insertAt] === "accept-language") insertAt += 1;
|
|
454
|
+
for (const name of ["cookie", "range"] as const) {
|
|
455
|
+
if (!caller.has(name)) continue;
|
|
456
|
+
order.splice(insertAt, 0, name);
|
|
457
|
+
insertAt += 1;
|
|
458
|
+
}
|
|
459
|
+
return order;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function normalizedCallerHeaderEntries(
|
|
463
|
+
headers: Record<string, string | string[] | undefined>,
|
|
464
|
+
): HeaderTuple[] {
|
|
465
|
+
const entries = normalizedHeaderEntries(headers);
|
|
466
|
+
assertCallerHeadersSupported(entries);
|
|
467
|
+
return entries;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function normalizedCallerHeaderEntriesFromRecord(headers: Record<string, string>): HeaderTuple[] {
|
|
471
|
+
// The record has been through normalizeHeaders (lowercase, merged names); the
|
|
472
|
+
// lowercase here only guards later insertions such as the cookie jar's header.
|
|
473
|
+
const entries: HeaderTuple[] = Object.entries(headers).map(([name, value]) => [
|
|
474
|
+
name.toLowerCase(),
|
|
475
|
+
value,
|
|
476
|
+
]);
|
|
477
|
+
assertCallerHeadersSupported(entries);
|
|
478
|
+
return entries;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function assertCallerHeadersSupported(entries: readonly HeaderTuple[]): void {
|
|
482
|
+
for (const [name] of entries) {
|
|
483
|
+
if (SDK_OWNED_EXACT_CHROME_HEADERS.has(name) || name.startsWith("sec-fetch-")) {
|
|
484
|
+
throw new SDKError(`Stealth transport owns the "${name}" header; remove it from headers.`, {
|
|
485
|
+
code: "STEALTH_HEADER_OVERRIDE_UNSUPPORTED",
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function chromeRequestClass(
|
|
492
|
+
method: StealthMethod,
|
|
493
|
+
requestedClass?: ChromeRequestClass,
|
|
494
|
+
): ChromeRequestClass {
|
|
495
|
+
if (requestedClass) return requestedClass;
|
|
496
|
+
if (method === "POST") return "post";
|
|
497
|
+
return "navigation";
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function secFetchSite(requestUrl: string, referer: string | undefined): string {
|
|
501
|
+
if (!referer) return "none";
|
|
502
|
+
try {
|
|
503
|
+
return new URL(referer).origin === new URL(requestUrl).origin ? "same-origin" : "cross-site";
|
|
504
|
+
} catch {
|
|
505
|
+
return "cross-site";
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function requiredEmulationHeader(headers: ReadonlyMap<string, string>, name: string): string {
|
|
510
|
+
const value = headers.get(name);
|
|
511
|
+
if (value !== undefined) return value;
|
|
512
|
+
throw new SDKError(`wreq-js Chrome emulation exposes no ${name} header.`, {
|
|
513
|
+
code: "STEALTH_PROFILE_UNAVAILABLE",
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function buildChromeHeaderTuples(options: {
|
|
518
|
+
emulationHeaders: Iterable<[string, string]>;
|
|
519
|
+
method: StealthMethod;
|
|
520
|
+
body?: string;
|
|
521
|
+
headers: Record<string, string>;
|
|
522
|
+
requestUrl: string;
|
|
523
|
+
acceptLanguage?: string;
|
|
524
|
+
requestClass?: ChromeRequestClass;
|
|
525
|
+
}): HeaderTuple[] {
|
|
526
|
+
const callerEntries = normalizedCallerHeaderEntriesFromRecord(options.headers);
|
|
527
|
+
const caller = new Map(callerEntries);
|
|
528
|
+
const emulation = new Map(
|
|
529
|
+
Array.from(
|
|
530
|
+
options.emulationHeaders,
|
|
531
|
+
([name, value]) => [name.toLowerCase(), value] as HeaderTuple,
|
|
532
|
+
),
|
|
533
|
+
);
|
|
534
|
+
const requestClass = chromeRequestClass(options.method, options.requestClass);
|
|
535
|
+
const referer = caller.get("referer");
|
|
536
|
+
const fetchSite = secFetchSite(options.requestUrl, referer);
|
|
537
|
+
const isNavigation = requestClass === "navigation";
|
|
538
|
+
const values = new Map<string, string>([
|
|
539
|
+
["sec-ch-ua", requiredEmulationHeader(emulation, "sec-ch-ua")],
|
|
540
|
+
["sec-ch-ua-mobile", requiredEmulationHeader(emulation, "sec-ch-ua-mobile")],
|
|
541
|
+
["sec-ch-ua-platform", requiredEmulationHeader(emulation, "sec-ch-ua-platform")],
|
|
542
|
+
["user-agent", requiredEmulationHeader(emulation, "user-agent")],
|
|
543
|
+
[
|
|
544
|
+
"accept-language",
|
|
545
|
+
caller.get("accept-language") ??
|
|
546
|
+
options.acceptLanguage ??
|
|
547
|
+
requiredEmulationHeader(emulation, "accept-language"),
|
|
548
|
+
],
|
|
549
|
+
["accept", isNavigation ? requiredEmulationHeader(emulation, "accept") : "*/*"],
|
|
550
|
+
[
|
|
551
|
+
"accept-encoding",
|
|
552
|
+
caller.has("range") ? "identity" : requiredEmulationHeader(emulation, "accept-encoding"),
|
|
553
|
+
],
|
|
554
|
+
["priority", isNavigation ? requiredEmulationHeader(emulation, "priority") : "u=1, i"],
|
|
555
|
+
["sec-fetch-site", fetchSite],
|
|
556
|
+
["sec-fetch-mode", isNavigation ? "navigate" : "cors"],
|
|
557
|
+
["sec-fetch-dest", isNavigation ? "document" : "empty"],
|
|
558
|
+
]);
|
|
559
|
+
if (isNavigation) {
|
|
560
|
+
values.set("upgrade-insecure-requests", "1");
|
|
561
|
+
values.set("sec-fetch-user", "?1");
|
|
562
|
+
}
|
|
563
|
+
for (const [name, value] of callerEntries) values.set(name, value);
|
|
564
|
+
for (const name of ["content-type", "origin", "referer"] as const) {
|
|
565
|
+
const value = caller.get(name);
|
|
566
|
+
if (value !== undefined) values.set(name, value);
|
|
567
|
+
}
|
|
568
|
+
if (requestClass === "post") {
|
|
569
|
+
values.set(
|
|
570
|
+
"content-length",
|
|
571
|
+
caller.get("content-length") ?? String(Buffer.byteLength(options.body ?? "")),
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const isHttp1 = new URL(options.requestUrl).protocol === "http:";
|
|
576
|
+
if (isHttp1) {
|
|
577
|
+
values.set("host", new URL(options.requestUrl).host);
|
|
578
|
+
values.set("connection", "keep-alive");
|
|
579
|
+
}
|
|
580
|
+
const order = chromeHeaderOrder(requestClass, isHttp1, caller);
|
|
581
|
+
const tuples: HeaderTuple[] = [];
|
|
582
|
+
const placed = new Set<string>();
|
|
583
|
+
for (const name of order) {
|
|
584
|
+
const value = values.get(name);
|
|
305
585
|
if (value === undefined) continue;
|
|
306
|
-
|
|
586
|
+
tuples.push([isHttp1 ? (CHROME_H1_HEADER_NAMES[name] ?? name) : name, value]);
|
|
587
|
+
placed.add(name);
|
|
588
|
+
}
|
|
589
|
+
for (const [name, value] of callerEntries) {
|
|
590
|
+
if (placed.has(name)) continue;
|
|
591
|
+
tuples.push([isHttp1 ? (CHROME_H1_HEADER_NAMES[name] ?? name) : name, value]);
|
|
307
592
|
}
|
|
308
|
-
return
|
|
593
|
+
return tuples;
|
|
309
594
|
}
|
|
310
595
|
|
|
311
596
|
function hasOwn(object: object, key: string): boolean {
|
|
@@ -322,7 +607,7 @@ function assertNoUnsupportedFingerprintOverrides(options: unknown): void {
|
|
|
322
607
|
if (unsupported.length === 0) return;
|
|
323
608
|
|
|
324
609
|
throw new SDKError(
|
|
325
|
-
`ctx.stealth.fetch uses transport-managed browser fingerprints and no longer accepts low-level stealth overrides: ${unsupported.join(", ")}. Use
|
|
610
|
+
`ctx.stealth.fetch uses transport-managed browser fingerprints and no longer accepts low-level stealth overrides: ${unsupported.join(", ")}. Use stealth.browser and stealth.os instead.`,
|
|
326
611
|
);
|
|
327
612
|
}
|
|
328
613
|
|
|
@@ -811,11 +1096,17 @@ async function fetchStealthRedirectChain(
|
|
|
811
1096
|
method: StealthMethod,
|
|
812
1097
|
options: StealthFetchOptions,
|
|
813
1098
|
signal?: AbortSignal,
|
|
1099
|
+
buildHeaders?: (
|
|
1100
|
+
url: string,
|
|
1101
|
+
method: StealthMethod,
|
|
1102
|
+
body: string | undefined,
|
|
1103
|
+
headers: Record<string, string>,
|
|
1104
|
+
) => HeaderTuple[],
|
|
814
1105
|
): Promise<{ normalized: StealthResponse; response: StealthTransportResponse }> {
|
|
815
1106
|
let currentUrl = requestUrl;
|
|
816
1107
|
let currentMethod = method;
|
|
817
1108
|
let currentBody = options.body === undefined ? undefined : normalizeBody(options.body);
|
|
818
|
-
let currentHeaders = { ...(options.headers ?? {}) };
|
|
1109
|
+
let currentHeaders = normalizeHeaders({ ...(options.headers ?? {}) });
|
|
819
1110
|
let followedHops = 0;
|
|
820
1111
|
let response: StealthTransportResponse;
|
|
821
1112
|
const deadline = options.timeout ? performance.now() + options.timeout : undefined;
|
|
@@ -823,14 +1114,17 @@ async function fetchStealthRedirectChain(
|
|
|
823
1114
|
while (true) {
|
|
824
1115
|
throwIfAmbientAborted(signal);
|
|
825
1116
|
const headers = { ...currentHeaders };
|
|
826
|
-
if (!hasHeader(headers, "
|
|
1117
|
+
if (!hasHeader(headers, "cookie")) {
|
|
827
1118
|
const cookieHeader = cookieJar.toHeader(currentUrl);
|
|
828
|
-
if (cookieHeader) headers.
|
|
1119
|
+
if (cookieHeader) headers.cookie = cookieHeader;
|
|
829
1120
|
}
|
|
830
1121
|
const requestInit: StealthRequestInit = {
|
|
831
|
-
headers:
|
|
1122
|
+
headers: buildHeaders
|
|
1123
|
+
? buildHeaders(currentUrl, currentMethod, currentBody, headers)
|
|
1124
|
+
: headers,
|
|
832
1125
|
method: currentMethod,
|
|
833
1126
|
redirect: "manual",
|
|
1127
|
+
...(new URL(currentUrl).protocol === "http:" ? { disableDefaultHeaders: true } : {}),
|
|
834
1128
|
...(signal ? { signal } : {}),
|
|
835
1129
|
};
|
|
836
1130
|
if (currentBody !== undefined) requestInit.body = currentBody;
|
|
@@ -901,38 +1195,40 @@ async function fetchStealthRedirectChain(
|
|
|
901
1195
|
|
|
902
1196
|
function createSessionFetcher(
|
|
903
1197
|
baseUrl: string,
|
|
904
|
-
defaultProfile:
|
|
1198
|
+
defaultProfile: StealthProfileDescriptor,
|
|
905
1199
|
clientOptions: StealthClientOptions,
|
|
906
1200
|
): StealthSession {
|
|
907
1201
|
const clients = new Map<string, WreqSessionCacheEntry>();
|
|
908
1202
|
let closed = false;
|
|
909
1203
|
let hasWarnedMissingProxy = false;
|
|
910
1204
|
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
|
-
}
|
|
917
1205
|
const cookieJar = new StealthCookieJar([], baseUrl);
|
|
918
1206
|
|
|
919
1207
|
async function getClientEntry(
|
|
920
|
-
|
|
1208
|
+
profile: StealthProfileDescriptor,
|
|
921
1209
|
proxyUrl: string | undefined,
|
|
922
1210
|
ignoreTlsErrors: boolean,
|
|
1211
|
+
defaultHeaders?: HeaderTuple[],
|
|
923
1212
|
): Promise<WreqSessionCacheEntry> {
|
|
924
1213
|
if (closed) {
|
|
925
1214
|
throw new TransportError("Stealth session is closed", { status: 0 });
|
|
926
1215
|
}
|
|
927
1216
|
const wreq = await getWreqModule();
|
|
928
|
-
const { browser, os } = resolveWreqProfile(
|
|
929
|
-
const cacheKey = JSON.stringify({
|
|
1217
|
+
const { browser, os } = resolveWreqProfile(profile, wreq.getProfiles());
|
|
1218
|
+
const cacheKey = JSON.stringify({
|
|
1219
|
+
browser,
|
|
1220
|
+
os,
|
|
1221
|
+
proxyUrl,
|
|
1222
|
+
ignoreTlsErrors,
|
|
1223
|
+
headerOrder: defaultHeaders?.map(([name]) => name),
|
|
1224
|
+
});
|
|
930
1225
|
let entry = clients.get(cacheKey);
|
|
931
1226
|
if (!entry) {
|
|
932
1227
|
entry = {
|
|
933
1228
|
session: wreq.createSession({
|
|
934
1229
|
browser,
|
|
935
1230
|
os,
|
|
1231
|
+
...(defaultHeaders ? { defaultHeaders } : {}),
|
|
936
1232
|
...(proxyUrl ? { proxy: proxyUrl } : {}),
|
|
937
1233
|
...(ignoreTlsErrors ? { insecure: true } : {}),
|
|
938
1234
|
timeout: 30_000,
|
|
@@ -945,14 +1241,15 @@ function createSessionFetcher(
|
|
|
945
1241
|
}
|
|
946
1242
|
|
|
947
1243
|
async function withClient<T>(
|
|
948
|
-
|
|
1244
|
+
profile: StealthProfileDescriptor,
|
|
949
1245
|
proxyUrl: string | undefined,
|
|
950
1246
|
ignoreTlsErrors: boolean,
|
|
951
1247
|
operation: (client: WreqSession) => Promise<T>,
|
|
952
1248
|
signal?: AbortSignal,
|
|
1249
|
+
defaultHeaders?: HeaderTuple[],
|
|
953
1250
|
): Promise<T> {
|
|
954
1251
|
throwIfAmbientAborted(signal);
|
|
955
|
-
const entry = await getClientEntry(
|
|
1252
|
+
const entry = await getClientEntry(profile, proxyUrl, ignoreTlsErrors, defaultHeaders);
|
|
956
1253
|
throwIfAmbientAborted(signal);
|
|
957
1254
|
const previous = entry.tail;
|
|
958
1255
|
let release!: () => void;
|
|
@@ -1038,9 +1335,11 @@ function createSessionFetcher(
|
|
|
1038
1335
|
|
|
1039
1336
|
const session: StealthSession = {
|
|
1040
1337
|
async fetch(url, options: StealthFetchOptions = {}) {
|
|
1338
|
+
const requestProfile = resolveStealthProfileSelection(options.stealth, defaultProfile);
|
|
1041
1339
|
const { hasExplicitRetryPolicy, method, stealthRetryOptions } = (() => {
|
|
1042
1340
|
try {
|
|
1043
1341
|
const method = normalizeMethod(options.method ?? "GET");
|
|
1342
|
+
normalizedCallerHeaderEntries(options.headers ?? {});
|
|
1044
1343
|
const hasExplicitRetryPolicy = options.retry !== undefined;
|
|
1045
1344
|
const stealthRetryOptions =
|
|
1046
1345
|
normalizeProxyTransportRetryOptions(options.retry, {
|
|
@@ -1163,15 +1462,50 @@ function createSessionFetcher(
|
|
|
1163
1462
|
options.stealth?.insecureSkipVerify ??
|
|
1164
1463
|
(!hasPolicyProxy && proxy && clientOptions.proxyStealth?.insecureSkipVerify),
|
|
1165
1464
|
);
|
|
1166
|
-
const profileName = options.profile ?? defaultProfile;
|
|
1167
1465
|
serializedUrl = serializeRequestUrl(
|
|
1168
1466
|
resolveUrl(baseUrl, url),
|
|
1169
1467
|
options.params,
|
|
1170
1468
|
sensitiveParams,
|
|
1171
1469
|
);
|
|
1172
1470
|
const { requestUrl } = serializedUrl;
|
|
1471
|
+
const wreq = await getWreqModule();
|
|
1472
|
+
const mapping = resolveWreqProfile(requestProfile, wreq.getProfiles());
|
|
1473
|
+
const chromeEmulationHeaders = mapping.browser.startsWith("chrome_")
|
|
1474
|
+
? Array.from(
|
|
1475
|
+
wreq.getEmulationHeaders(mapping.browser, mapping.os),
|
|
1476
|
+
([name, value]) => [String(name), String(value)] as HeaderTuple,
|
|
1477
|
+
)
|
|
1478
|
+
: undefined;
|
|
1479
|
+
const buildOrderedHeaders = chromeEmulationHeaders
|
|
1480
|
+
? (
|
|
1481
|
+
currentUrl: string,
|
|
1482
|
+
currentMethod: StealthMethod,
|
|
1483
|
+
currentBody: string | undefined,
|
|
1484
|
+
currentHeaders: Record<string, string>,
|
|
1485
|
+
) =>
|
|
1486
|
+
buildChromeHeaderTuples({
|
|
1487
|
+
emulationHeaders: chromeEmulationHeaders,
|
|
1488
|
+
method: currentMethod,
|
|
1489
|
+
body: currentBody,
|
|
1490
|
+
headers: currentHeaders,
|
|
1491
|
+
requestUrl: currentUrl,
|
|
1492
|
+
acceptLanguage: clientOptions.stealth?.acceptLanguage,
|
|
1493
|
+
requestClass: options.stealth?.requestClass,
|
|
1494
|
+
})
|
|
1495
|
+
: undefined;
|
|
1496
|
+
const initialHeaders = normalizeHeaders({ ...(options.headers ?? {}) });
|
|
1497
|
+
if (!hasHeader(initialHeaders, "cookie")) {
|
|
1498
|
+
const cookieHeader = cookieJar.toHeader(requestUrl);
|
|
1499
|
+
if (cookieHeader) initialHeaders.cookie = cookieHeader;
|
|
1500
|
+
}
|
|
1501
|
+
const defaultHeaders = buildOrderedHeaders?.(
|
|
1502
|
+
requestUrl,
|
|
1503
|
+
method,
|
|
1504
|
+
options.body === undefined ? undefined : normalizeBody(options.body),
|
|
1505
|
+
initialHeaders,
|
|
1506
|
+
);
|
|
1173
1507
|
const { normalized, response } = await withClient(
|
|
1174
|
-
|
|
1508
|
+
requestProfile,
|
|
1175
1509
|
proxy,
|
|
1176
1510
|
ignoreTlsErrors,
|
|
1177
1511
|
(transport) =>
|
|
@@ -1182,8 +1516,10 @@ function createSessionFetcher(
|
|
|
1182
1516
|
method,
|
|
1183
1517
|
options,
|
|
1184
1518
|
clientOptions.signal,
|
|
1519
|
+
buildOrderedHeaders,
|
|
1185
1520
|
),
|
|
1186
1521
|
clientOptions.signal,
|
|
1522
|
+
defaultHeaders,
|
|
1187
1523
|
);
|
|
1188
1524
|
|
|
1189
1525
|
if (proxy && isProxyConnectFailureResponse(response, normalized.body)) {
|
|
@@ -1337,10 +1673,7 @@ function createSessionFetcher(
|
|
|
1337
1673
|
|
|
1338
1674
|
const proxyAuthDiagnostic =
|
|
1339
1675
|
stalePoolError && stalePoolDiagnosticProxy
|
|
1340
|
-
? await classifyProxyAuthDiagnostic(
|
|
1341
|
-
options.profile ?? defaultProfile,
|
|
1342
|
-
stalePoolDiagnosticProxy,
|
|
1343
|
-
)
|
|
1676
|
+
? await classifyProxyAuthDiagnostic(requestProfile, stalePoolDiagnosticProxy)
|
|
1344
1677
|
: undefined;
|
|
1345
1678
|
if (proxyAuthDiagnostic === "source_ip_denied") {
|
|
1346
1679
|
throw createProxyAuthIpDeniedError(
|
|
@@ -1587,12 +1920,12 @@ function createSessionFetcher(
|
|
|
1587
1920
|
return session;
|
|
1588
1921
|
|
|
1589
1922
|
async function classifyProxyAuthDiagnostic(
|
|
1590
|
-
|
|
1923
|
+
profile: StealthProfileDescriptor,
|
|
1591
1924
|
proxy: string,
|
|
1592
1925
|
): Promise<"source_ip_denied" | "edge_auth_rejected" | undefined> {
|
|
1593
1926
|
try {
|
|
1594
1927
|
return await withClient(
|
|
1595
|
-
|
|
1928
|
+
profile,
|
|
1596
1929
|
proxy,
|
|
1597
1930
|
false,
|
|
1598
1931
|
async (client) => {
|
|
@@ -1654,18 +1987,17 @@ function hasHeader(headers: Record<string, string>, name: string): boolean {
|
|
|
1654
1987
|
|
|
1655
1988
|
export function createStealthClient(
|
|
1656
1989
|
baseUrl: string,
|
|
1657
|
-
defaultProfileOrOptions: string | StealthClientOptions = DEFAULT_PROFILE,
|
|
1658
1990
|
clientOptions: StealthClientOptions = {},
|
|
1659
1991
|
): StealthClient {
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1992
|
+
if (typeof clientOptions === "string") {
|
|
1993
|
+
resolveStealthProfileSelection(clientOptions as unknown as StealthProfileSelection);
|
|
1994
|
+
}
|
|
1995
|
+
const defaultProfile = resolveStealthProfileSelection(clientOptions.stealth);
|
|
1664
1996
|
let sharedSession: StealthSession | null = null;
|
|
1665
1997
|
|
|
1666
1998
|
function getSharedSession(): StealthSession {
|
|
1667
1999
|
if (!sharedSession) {
|
|
1668
|
-
sharedSession = createSessionFetcher(baseUrl, defaultProfile,
|
|
2000
|
+
sharedSession = createSessionFetcher(baseUrl, defaultProfile, clientOptions);
|
|
1669
2001
|
}
|
|
1670
2002
|
|
|
1671
2003
|
return sharedSession;
|
|
@@ -1675,9 +2007,9 @@ export function createStealthClient(
|
|
|
1675
2007
|
fetch(url: string, options?: StealthFetchOptions) {
|
|
1676
2008
|
return getSharedSession().fetch(url, options);
|
|
1677
2009
|
},
|
|
1678
|
-
createSession(opts?: {
|
|
1679
|
-
const sessionProfile = opts?.
|
|
1680
|
-
return createSessionFetcher(baseUrl, sessionProfile,
|
|
2010
|
+
createSession(opts?: { stealth?: StealthProfileSelection }) {
|
|
2011
|
+
const sessionProfile = resolveStealthProfileSelection(opts?.stealth, defaultProfile);
|
|
2012
|
+
return createSessionFetcher(baseUrl, sessionProfile, clientOptions);
|
|
1681
2013
|
},
|
|
1682
2014
|
close() {
|
|
1683
2015
|
sharedSession?.close();
|