@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.
Files changed (108) hide show
  1. package/AUTHORING.md +91 -36
  2. package/CHANGELOG.md +4 -0
  3. package/README.md +11 -9
  4. package/SUBMISSION.md +1 -1
  5. package/bin/apifuse-dev.ts +24 -13
  6. package/bin/apifuse-migrate-operation-declaration.ts +55 -0
  7. package/bin/apifuse-pack-smoke.ts +1 -1
  8. package/bin/apifuse-pack-types.ts +2 -1
  9. package/bin/apifuse-record.ts +30 -16
  10. package/bin/apifuse-submit-check.ts +20 -35
  11. package/dist/cli/commands.d.ts +1 -1
  12. package/dist/cli/commands.js +11 -0
  13. package/dist/cli/migrate-operation-declaration.d.ts +59 -0
  14. package/dist/cli/migrate-operation-declaration.js +1178 -0
  15. package/dist/cli/templates/provider/README.md.tpl +3 -3
  16. package/dist/cli/templates/provider/operations/ping.ts.tpl +2 -0
  17. package/dist/config/loader.d.ts +2 -0
  18. package/dist/config/loader.js +18 -7
  19. package/dist/contract-types.d.ts +11 -5
  20. package/dist/contract.js +21 -10
  21. package/dist/define.d.ts +25 -22
  22. package/dist/define.js +49 -75
  23. package/dist/dev.d.ts +3 -0
  24. package/dist/dev.js +1 -1
  25. package/dist/engine.d.ts +78 -0
  26. package/dist/engine.js +133 -0
  27. package/dist/index.d.ts +3 -2
  28. package/dist/index.js +2 -1
  29. package/dist/lint.d.ts +7 -15
  30. package/dist/lint.js +45 -70
  31. package/dist/provider.d.ts +3 -1
  32. package/dist/provider.js +1 -0
  33. package/dist/runtime/chrome149-header-order.d.ts +58 -0
  34. package/dist/runtime/chrome149-header-order.js +289 -0
  35. package/dist/runtime/env.js +12 -0
  36. package/dist/runtime/executor.d.ts +2 -1
  37. package/dist/runtime/executor.js +3 -36
  38. package/dist/runtime/insights.js +2 -2
  39. package/dist/runtime/otlp.d.ts +71 -2
  40. package/dist/runtime/otlp.js +397 -16
  41. package/dist/runtime/resolver-vendors/capsolver.js +3 -3
  42. package/dist/runtime/resolver.js +3 -3
  43. package/dist/runtime/stealth.d.ts +13 -4
  44. package/dist/runtime/stealth.js +362 -85
  45. package/dist/runtime/trace-config.js +2 -1
  46. package/dist/runtime/trace.d.ts +5 -0
  47. package/dist/runtime/trace.js +43 -10
  48. package/dist/server/self-test.d.ts +1 -3
  49. package/dist/server/self-test.js +2 -12
  50. package/dist/server/serve-implementation.d.ts +6 -1
  51. package/dist/server/serve-implementation.js +55 -40
  52. package/dist/server/trace-output.d.ts +3 -1
  53. package/dist/server/trace-output.js +61 -2
  54. package/dist/stealth/profiles.d.ts +9 -8
  55. package/dist/stealth/profiles.js +123 -286
  56. package/dist/types.d.ts +111 -108
  57. package/package.json +2 -1
  58. package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +6 -0
  59. package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +3 -0
  60. package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +10 -0
  61. package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +8 -0
  62. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +5 -0
  63. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +16 -0
  64. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +3 -0
  65. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +31 -0
  66. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +11 -0
  67. package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +11 -0
  68. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +11 -0
  69. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +21 -0
  70. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +11 -0
  71. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +14 -0
  72. package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +7 -0
  73. package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +6 -0
  74. package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +7 -0
  75. package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +6 -0
  76. package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +7 -0
  77. package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +7 -0
  78. package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +15 -0
  79. package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +4 -0
  80. package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +12 -0
  81. package/src/cli/commands.ts +13 -0
  82. package/src/cli/migrate-operation-declaration.ts +1654 -0
  83. package/src/cli/templates/provider/README.md.tpl +3 -3
  84. package/src/cli/templates/provider/operations/ping.ts.tpl +2 -0
  85. package/src/config/loader.ts +31 -6
  86. package/src/contract-types.ts +11 -5
  87. package/src/contract.ts +21 -10
  88. package/src/define.ts +107 -119
  89. package/src/dev.ts +4 -1
  90. package/src/engine.ts +279 -0
  91. package/src/index.ts +13 -5
  92. package/src/lint.ts +58 -92
  93. package/src/provider.ts +25 -3
  94. package/src/runtime/chrome149-header-order.ts +330 -0
  95. package/src/runtime/env.ts +13 -0
  96. package/src/runtime/executor.ts +7 -40
  97. package/src/runtime/insights.ts +2 -2
  98. package/src/runtime/otlp.ts +467 -21
  99. package/src/runtime/resolver-vendors/capsolver.ts +4 -3
  100. package/src/runtime/resolver.ts +3 -3
  101. package/src/runtime/stealth.ts +435 -103
  102. package/src/runtime/trace-config.ts +3 -2
  103. package/src/runtime/trace.ts +57 -17
  104. package/src/server/self-test.ts +2 -9
  105. package/src/server/serve-implementation.ts +89 -72
  106. package/src/server/trace-output.ts +99 -2
  107. package/src/stealth/profiles.ts +169 -327
  108. package/src/types.ts +109 -137
@@ -1,14 +1,17 @@
1
1
  import { createRequire } from "node:module";
2
2
 
3
- import type { BrowserProfile } from "wreq-js";
3
+ import type { BrowserProfile, EmulationOS } from "wreq-js";
4
4
 
5
5
  import { SDKError } from "../errors.js";
6
- import type { StealthPlatform, StealthProfile } from "../types.js";
7
-
8
- type StealthProfileDefinition = Omit<StealthProfile, "name" | "platform"> & {
9
- platform: StealthPlatform;
10
- };
11
-
6
+ import type {
7
+ StealthBrowser,
8
+ StealthProfile,
9
+ StealthProfileDescriptor,
10
+ StealthProfileSelection,
11
+ StealthOS,
12
+ } from "../types.js";
13
+
14
+ type StealthProfileDefinition = Omit<StealthProfile, "browser" | "os">;
12
15
  type WreqProfileApi = Pick<typeof import("wreq-js"), "getEmulationHeaders" | "getProfiles">;
13
16
 
14
17
  const requireModule = createRequire(import.meta.url);
@@ -19,53 +22,8 @@ function getWreqProfileApi(): WreqProfileApi {
19
22
  return wreqProfileApi;
20
23
  }
21
24
 
22
- const CHROMIUM_HEADER_ORDER = [
23
- ":method",
24
- ":authority",
25
- ":scheme",
26
- ":path",
27
- "user-agent",
28
- "accept",
29
- "accept-encoding",
30
- "accept-language",
31
- "cache-control",
32
- "pragma",
33
- "cookie",
34
- "sec-ch-ua",
35
- "sec-ch-ua-mobile",
36
- "sec-ch-ua-platform",
37
- ];
38
-
39
- const FIREFOX_HEADER_ORDER = [
40
- ":method",
41
- ":path",
42
- ":authority",
43
- ":scheme",
44
- "user-agent",
45
- "accept",
46
- "accept-language",
47
- "accept-encoding",
48
- "referer",
49
- "cookie",
50
- "upgrade-insecure-requests",
51
- "sec-fetch-dest",
52
- "sec-fetch-mode",
53
- "sec-fetch-site",
54
- "sec-fetch-user",
55
- ];
56
-
57
- const SAFARI_HEADER_ORDER = [
58
- ":method",
59
- ":scheme",
60
- ":path",
61
- ":authority",
62
- "accept",
63
- "user-agent",
64
- "accept-language",
65
- "accept-encoding",
66
- "cookie",
67
- "upgrade-insecure-requests",
68
- ];
25
+ export const DEFAULT_STEALTH_BROWSER = "chrome" as const;
26
+ export const DEFAULT_STEALTH_OS = "macos" as const;
69
27
 
70
28
  const CHROMIUM_H2_SETTINGS = {
71
29
  HEADER_TABLE_SIZE: 65536,
@@ -95,21 +53,104 @@ const FIREFOX_JA3 =
95
53
  const SAFARI_JA3 =
96
54
  "771,4865-4866-4867-49196-49195-52393-49200-49199-49188-49192-159-158-107-103-57-51-157-156-61-60-53-47-255,0-23-65281-10-11-16-5-13-18-51-45-43-27,29-23-24-25,0";
97
55
 
98
- /**
99
- * The newest Chromium build wreq-js can emulate, resolved on first profile use.
100
- *
101
- * A literal version here rots: it went stale twice (146 was six releases behind
102
- * stable when an upstream integrity analyzer flagged it), and a fingerprint that
103
- * advertises an old Chrome is exactly what bot managers score against. wreq-js
104
- * already ships the profile table, so the newest entry is derived from it rather
105
- * than restated. `resolveProfile("chrome")` is deliberately NOT used because its
106
- * conservative default can lag newer entries in that table.
107
- *
108
- * package.json pins wreq-js exactly on purpose. Even a semver-minor wreq-js update
109
- * can change the profile table, emitted headers, native bindings, and therefore
110
- * the wire fingerprint inherited by every provider. Upgrade that exact version
111
- * only in a dedicated change with profile-parity and packed-native verification.
112
- */
56
+ const SUPPORTED_STEALTH_PROFILES: readonly StealthProfileDescriptor[] = [
57
+ { browser: "chrome", os: "windows" },
58
+ { browser: "chrome", os: "macos" },
59
+ { browser: "chrome", os: "linux" },
60
+ { browser: "firefox", os: "windows" },
61
+ { browser: "firefox", os: "macos" },
62
+ { browser: "firefox", os: "linux" },
63
+ { browser: "safari", os: "macos" },
64
+ { browser: "safari", os: "ios" },
65
+ ];
66
+
67
+ const DESKTOP_OSES = new Set<StealthOS>(["windows", "macos", "linux"]);
68
+
69
+ function structuredReplacement(browser: StealthBrowser, os: StealthOS): string {
70
+ return `stealth: { browser: "${browser}", os: "${os}" }`;
71
+ }
72
+
73
+ /** Internal detector used to keep version-pinned string failures actionable. */
74
+ export function getVersionPinnedStealthReplacement(name: string): string | undefined {
75
+ if (/^(?:chrome|chromium|edge)[-_]\d/i.test(name)) {
76
+ return structuredReplacement("chrome", DEFAULT_STEALTH_OS);
77
+ }
78
+ if (/^firefox[-_]\d/i.test(name)) {
79
+ return structuredReplacement("firefox", DEFAULT_STEALTH_OS);
80
+ }
81
+ if (/^(?:ios[-_]safari|safari[-_](?:ios|ipad))[-_]\d/i.test(name)) {
82
+ return structuredReplacement("safari", "ios");
83
+ }
84
+ if (/^safari[-_]\d/i.test(name)) {
85
+ return structuredReplacement("safari", DEFAULT_STEALTH_OS);
86
+ }
87
+ return undefined;
88
+ }
89
+
90
+ function rejectStringSelection(name: string): never {
91
+ const replacement = getVersionPinnedStealthReplacement(name);
92
+ if (replacement) {
93
+ throw new SDKError(
94
+ `Stealth profile "${name}" pins a browser version and is not supported. Use ${replacement}.`,
95
+ { code: "STEALTH_VERSION_PIN_UNSUPPORTED" },
96
+ );
97
+ }
98
+ throw new SDKError(
99
+ `Stealth profile names are no longer supported: "${name}". Use structured browser/os options.`,
100
+ { code: "STEALTH_PROFILE_NAME_UNSUPPORTED" },
101
+ );
102
+ }
103
+
104
+ function assertSupportedDescriptor(browser: StealthBrowser, os: StealthOS): void {
105
+ const supported =
106
+ (browser === "chrome" && DESKTOP_OSES.has(os)) ||
107
+ (browser === "firefox" && DESKTOP_OSES.has(os)) ||
108
+ (browser === "safari" && (os === "macos" || os === "ios"));
109
+ if (!supported) {
110
+ throw new SDKError(
111
+ `Unsupported stealth browser/OS combination: ${browser}/${os}. ` +
112
+ "Chrome and Firefox support windows, macos, and linux; Safari supports macos and ios.",
113
+ { code: "STEALTH_PROFILE_UNAVAILABLE" },
114
+ );
115
+ }
116
+ }
117
+
118
+ /** Resolve omitted axes and validate that the resulting pair is supported. */
119
+ export function resolveStealthProfileSelection(
120
+ selection: StealthProfileSelection | undefined,
121
+ base?: StealthProfileDescriptor,
122
+ ): StealthProfileDescriptor {
123
+ if (typeof selection === "string") rejectStringSelection(selection);
124
+ if (selection !== undefined && (typeof selection !== "object" || selection === null)) {
125
+ throw new SDKError("Stealth profile selection must use structured browser/os options.");
126
+ }
127
+ const unsafeSelection = selection as
128
+ | (Record<string, unknown> & { browser?: unknown; os?: unknown })
129
+ | undefined;
130
+ if (unsafeSelection && "profile" in unsafeSelection) {
131
+ if (typeof unsafeSelection.profile === "string") rejectStringSelection(unsafeSelection.profile);
132
+ throw new SDKError(
133
+ "stealth.profile is no longer supported. Use stealth.browser and stealth.os.",
134
+ { code: "STEALTH_PROFILE_NAME_UNSUPPORTED" },
135
+ );
136
+ }
137
+
138
+ const browser = unsafeSelection?.browser ?? base?.browser ?? DEFAULT_STEALTH_BROWSER;
139
+ if (browser !== "chrome" && browser !== "firefox" && browser !== "safari") {
140
+ throw new SDKError(`Unsupported stealth browser: ${String(browser)}`);
141
+ }
142
+ const browserChanged =
143
+ base !== undefined && unsafeSelection?.browser !== undefined && browser !== base.browser;
144
+ const os =
145
+ unsafeSelection?.os ?? (browserChanged ? DEFAULT_STEALTH_OS : base?.os) ?? DEFAULT_STEALTH_OS;
146
+ if (os !== "windows" && os !== "macos" && os !== "linux" && os !== "ios") {
147
+ throw new SDKError(`Unsupported stealth OS: ${String(os)}`);
148
+ }
149
+ assertSupportedDescriptor(browser, os);
150
+ return { browser, os } as StealthProfileDescriptor;
151
+ }
152
+
153
+ /** Resolve the newest Chromium build exposed by the exactly pinned wreq-js package. */
113
154
  function resolveLatestChromiumProfile(): {
114
155
  readonly wreqName: BrowserProfile;
115
156
  readonly version: string;
@@ -130,292 +171,93 @@ function resolveLatestChromiumProfile(): {
130
171
  return { wreqName: newest.name, version: `${newest.version}.0.0.0` };
131
172
  }
132
173
 
133
- /** The user agent wreq-js itself emits for that profile, so the two never disagree. */
134
- function chromiumUserAgent(latest: ReturnType<typeof resolveLatestChromiumProfile>): string {
135
- const { getEmulationHeaders } = getWreqProfileApi();
136
- for (const [name, value] of getEmulationHeaders(latest.wreqName, "macos")) {
174
+ /** Read the identity wreq itself emits so the accessor and transport cannot diverge. */
175
+ function emulationUserAgent(profile: BrowserProfile, os: EmulationOS): string {
176
+ for (const [name, value] of getWreqProfileApi().getEmulationHeaders(profile, os)) {
137
177
  if (String(name).toLowerCase() === "user-agent") return String(value);
138
178
  }
139
- throw new SDKError(`wreq-js profile ${latest.wreqName} exposes no user-agent header.`, {
179
+ throw new SDKError(`wreq-js profile ${profile} exposes no user-agent header.`, {
140
180
  code: "STEALTH_PROFILE_UNAVAILABLE",
141
181
  });
142
182
  }
143
183
 
144
- function createProfile(name: string, definition: StealthProfileDefinition): StealthProfile {
184
+ function createProfile(
185
+ descriptor: StealthProfileDescriptor,
186
+ definition: StealthProfileDefinition,
187
+ ): StealthProfile {
145
188
  return {
146
- name,
147
- platform: definition.platform,
148
- version: definition.version,
149
- userAgent: definition.userAgent,
150
- tlsClientIdentifier: definition.tlsClientIdentifier,
151
- ja3: definition.ja3,
152
- ja4: definition.ja4,
189
+ ...descriptor,
190
+ ...definition,
153
191
  h2Settings: definition.h2Settings ? { ...definition.h2Settings } : undefined,
154
- headerOrder: definition.headerOrder ? [...definition.headerOrder] : undefined,
155
192
  };
156
193
  }
157
194
 
158
- function extractBrowserMajorVersion(profile: StealthProfile): string {
159
- const chromeVersion = profile.userAgent.match(/Chrome\/(\d+)/)?.[1];
160
- if (chromeVersion) {
161
- return chromeVersion;
162
- }
163
-
164
- const identifierVersion = profile.tlsClientIdentifier?.match(/(\d+)(?!.*\d)/)?.[1];
165
- if (identifierVersion) {
166
- return identifierVersion;
167
- }
168
-
169
- return profile.version.split(".")[0] ?? profile.version;
170
- }
171
-
172
- function toPlatformHeaderValue(platform: StealthPlatform): string {
173
- switch (platform) {
174
- case "macos":
175
- return '"macOS"';
176
- case "windows":
177
- return '"Windows"';
178
- case "linux":
179
- return '"Linux"';
180
- case "android":
181
- return '"Android"';
182
- case "ios":
183
- return '"iOS"';
184
- }
185
- }
186
-
187
- export function generateLayer2Headers(profile: StealthProfile): Record<string, string> {
188
- const headers: Record<string, string> = {
189
- "Accept-Language": "ko-KR,ko;q=0.9,en-US;q=0.8",
190
- };
191
-
192
- const identifier = profile.tlsClientIdentifier?.toLowerCase() ?? "";
193
- const majorVersion = extractBrowserMajorVersion(profile);
194
-
195
- if (identifier.startsWith("chrome_") || identifier.startsWith("edge_")) {
196
- const isEdge = identifier.startsWith("edge_") || /\bEdg\//.test(profile.userAgent);
197
- headers["Sec-Ch-Ua"] = isEdge
198
- ? `"Chromium";v="${majorVersion}", "Microsoft Edge";v="${majorVersion}", "Not)A;Brand";v="99"`
199
- : `"Chromium";v="${majorVersion}", "Google Chrome";v="${majorVersion}", "Not)A;Brand";v="99"`;
200
- headers["Sec-Ch-Ua-Platform"] = toPlatformHeaderValue(profile.platform);
201
- headers["Sec-Ch-Ua-Mobile"] =
202
- profile.platform === "android" || profile.platform === "ios" ? "?1" : "?0";
203
- }
204
-
205
- return headers;
195
+ function profileKey(descriptor: StealthProfileDescriptor): string {
196
+ return `${descriptor.browser}:${descriptor.os}`;
206
197
  }
207
198
 
208
- const STATIC_STEALTH_PROFILE_ALIASES: Record<string, string> = {
209
- "firefox-desktop": "firefox-147",
210
- "safari-desktop": "safari-17",
211
- "safari-mobile": "ios-safari-26",
212
- };
213
-
214
- const PUBLIC_STEALTH_PROFILE_NAMES = [
215
- "chrome-desktop",
216
- "firefox-desktop",
217
- "safari-desktop",
218
- "safari-mobile",
219
- "generic-desktop",
220
- "generic-mobile",
221
- ] as const;
222
-
223
- const PUBLIC_STEALTH_PROFILE_NAME_SET = new Set<string>(PUBLIC_STEALTH_PROFILE_NAMES);
224
-
225
- const STATIC_STEALTH_PROFILES: Record<string, StealthProfile> = {
226
- "chrome-146": createProfile("chrome-146", {
227
- platform: "macos",
228
- version: "146.0.0.0",
229
- userAgent:
230
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
231
- tlsClientIdentifier: "chrome_146",
232
- ja3: CHROMIUM_JA3,
233
- h2Settings: CHROMIUM_H2_SETTINGS,
234
- headerOrder: CHROMIUM_HEADER_ORDER,
235
- }),
236
- "firefox-147": createProfile("firefox-147", {
237
- platform: "macos",
238
- version: "147.0",
239
- userAgent:
240
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:147.0) Gecko/20100101 Firefox/147.0",
241
- tlsClientIdentifier: "firefox_147",
242
- ja3: FIREFOX_JA3,
243
- h2Settings: FIREFOX_H2_SETTINGS,
244
- headerOrder: FIREFOX_HEADER_ORDER,
245
- }),
246
- "firefox-135": createProfile("firefox-135", {
247
- platform: "macos",
248
- version: "135.0",
249
- userAgent:
250
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:135.0) Gecko/20100101 Firefox/135.0",
251
- tlsClientIdentifier: "firefox_135",
252
- ja3: FIREFOX_JA3,
253
- h2Settings: FIREFOX_H2_SETTINGS,
254
- headerOrder: FIREFOX_HEADER_ORDER,
255
- }),
256
- "firefox-133": createProfile("firefox-133", {
257
- platform: "macos",
258
- version: "133.0",
259
- userAgent:
260
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0",
261
- tlsClientIdentifier: "firefox_133",
262
- ja3: FIREFOX_JA3,
263
- h2Settings: FIREFOX_H2_SETTINGS,
264
- headerOrder: FIREFOX_HEADER_ORDER,
265
- }),
266
- "firefox-132": createProfile("firefox-132", {
267
- platform: "macos",
268
- version: "133.0",
269
- userAgent:
270
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0",
271
- tlsClientIdentifier: "firefox_132",
272
- ja3: FIREFOX_JA3,
273
- h2Settings: FIREFOX_H2_SETTINGS,
274
- headerOrder: FIREFOX_HEADER_ORDER,
275
- }),
276
- "safari-16": createProfile("safari-16", {
277
- platform: "macos",
278
- version: "16.0",
279
- userAgent:
280
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15",
281
- tlsClientIdentifier: "safari_16_0",
282
- ja3: SAFARI_JA3,
283
- h2Settings: SAFARI_H2_SETTINGS,
284
- headerOrder: SAFARI_HEADER_ORDER,
285
- }),
286
- "safari-17": createProfile("safari-17", {
287
- platform: "macos",
288
- version: "17.0",
289
- userAgent:
290
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15",
291
- tlsClientIdentifier: "safari_17_0",
292
- ja3: SAFARI_JA3,
293
- h2Settings: SAFARI_H2_SETTINGS,
294
- headerOrder: SAFARI_HEADER_ORDER,
295
- }),
296
- "safari-15": createProfile("safari-15", {
297
- platform: "macos",
298
- version: "15.6.1",
299
- userAgent:
300
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6.1 Safari/605.1.15",
301
- tlsClientIdentifier: "safari_15_6_1",
302
- ja3: SAFARI_JA3,
303
- h2Settings: SAFARI_H2_SETTINGS,
304
- headerOrder: SAFARI_HEADER_ORDER,
305
- }),
306
- "ios-safari-26": createProfile("ios-safari-26", {
307
- platform: "ios",
308
- version: "26.0",
309
- userAgent:
310
- "Mozilla/5.0 (iPhone; CPU iPhone OS 26_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",
311
- tlsClientIdentifier: "safari_ios_26_0",
312
- ja3: SAFARI_JA3,
313
- h2Settings: SAFARI_H2_SETTINGS,
314
- headerOrder: SAFARI_HEADER_ORDER,
315
- }),
316
- "ios-safari-18": createProfile("ios-safari-18", {
317
- platform: "ios",
318
- version: "18.1.1",
319
- userAgent:
320
- "Mozilla/5.0 (iPhone; CPU iPhone OS 18_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1.1 Mobile/15E148 Safari/604.1",
321
- tlsClientIdentifier: "safari_ios_18_0",
322
- ja3: SAFARI_JA3,
323
- h2Settings: SAFARI_H2_SETTINGS,
324
- headerOrder: SAFARI_HEADER_ORDER,
325
- }),
326
- "ios-safari-17": createProfile("ios-safari-17", {
327
- platform: "ios",
328
- version: "17.2",
329
- userAgent:
330
- "Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1",
331
- tlsClientIdentifier: "safari_ios_17_0",
332
- ja3: SAFARI_JA3,
333
- h2Settings: SAFARI_H2_SETTINGS,
334
- headerOrder: SAFARI_HEADER_ORDER,
335
- }),
336
- "generic-mobile": createProfile("generic-mobile", {
337
- platform: "ios",
338
- version: "26.0",
339
- userAgent:
340
- "Mozilla/5.0 (iPhone; CPU iPhone OS 26_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",
341
- tlsClientIdentifier: "safari_ios_26_0",
342
- ja3: SAFARI_JA3,
343
- h2Settings: SAFARI_H2_SETTINGS,
344
- headerOrder: SAFARI_HEADER_ORDER,
345
- }),
346
- };
347
-
348
- type StealthProfileCatalog = {
349
- aliases: Record<string, string>;
350
- profiles: Record<string, StealthProfile>;
351
- };
352
-
353
- let stealthProfileCatalog: StealthProfileCatalog | undefined;
199
+ let stealthProfileCatalog: Map<string, StealthProfile> | undefined;
354
200
 
355
- function getStealthProfileCatalog(): StealthProfileCatalog {
201
+ function getStealthProfileCatalog(): Map<string, StealthProfile> {
356
202
  if (stealthProfileCatalog) return stealthProfileCatalog;
357
203
 
358
- const latest = resolveLatestChromiumProfile();
359
- const currentName = `chrome-${latest.version.split(".")[0]}`;
360
- const currentProfile = createProfile(currentName, {
361
- platform: "macos",
362
- version: latest.version,
363
- userAgent: chromiumUserAgent(latest),
364
- tlsClientIdentifier: latest.wreqName,
365
- ja3: CHROMIUM_JA3,
366
- h2Settings: CHROMIUM_H2_SETTINGS,
367
- headerOrder: CHROMIUM_HEADER_ORDER,
368
- });
369
- stealthProfileCatalog = {
370
- aliases: {
371
- "chrome-desktop": currentName,
372
- ...STATIC_STEALTH_PROFILE_ALIASES,
204
+ const latestChrome = resolveLatestChromiumProfile();
205
+ const definitions: Record<StealthBrowser, Omit<StealthProfileDefinition, "userAgent">> = {
206
+ chrome: {
207
+ version: latestChrome.version,
208
+ tlsClientIdentifier: latestChrome.wreqName,
209
+ ja3: CHROMIUM_JA3,
210
+ h2Settings: CHROMIUM_H2_SETTINGS,
211
+ },
212
+ firefox: {
213
+ version: "147.0",
214
+ tlsClientIdentifier: "firefox_147",
215
+ ja3: FIREFOX_JA3,
216
+ h2Settings: FIREFOX_H2_SETTINGS,
373
217
  },
374
- profiles: {
375
- [currentName]: currentProfile,
376
- ...STATIC_STEALTH_PROFILES,
377
- "generic-desktop": createProfile("generic-desktop", currentProfile),
218
+ safari: {
219
+ version: "17.0",
220
+ tlsClientIdentifier: "safari_17.0",
221
+ ja3: SAFARI_JA3,
222
+ h2Settings: SAFARI_H2_SETTINGS,
378
223
  },
379
224
  };
225
+
226
+ stealthProfileCatalog = new Map();
227
+ for (const descriptor of SUPPORTED_STEALTH_PROFILES) {
228
+ const isMobileSafari = descriptor.browser === "safari" && descriptor.os === "ios";
229
+ const definition = isMobileSafari
230
+ ? {
231
+ version: "26.0",
232
+ tlsClientIdentifier: "safari_ios_26" as BrowserProfile,
233
+ ja3: SAFARI_JA3,
234
+ h2Settings: SAFARI_H2_SETTINGS,
235
+ }
236
+ : definitions[descriptor.browser];
237
+ const wreqName = definition.tlsClientIdentifier as BrowserProfile;
238
+ stealthProfileCatalog.set(
239
+ profileKey(descriptor),
240
+ createProfile(descriptor, {
241
+ ...definition,
242
+ userAgent: emulationUserAgent(wreqName, descriptor.os),
243
+ }),
244
+ );
245
+ }
380
246
  return stealthProfileCatalog;
381
247
  }
382
248
 
383
- export function getStealthProfile(name: string): StealthProfile {
384
- const catalog = getStealthProfileCatalog();
385
- const canonicalName = catalog.aliases[name] ?? name;
386
- const profile = catalog.profiles[canonicalName];
387
-
249
+ export function getStealthProfile(selection: StealthProfileSelection = {}): StealthProfile {
250
+ const descriptor = resolveStealthProfileSelection(selection);
251
+ const profile = getStealthProfileCatalog().get(profileKey(descriptor));
388
252
  if (!profile) {
389
- throw new SDKError(`Unknown stealth profile: ${name}`);
253
+ throw new SDKError(`Unknown stealth profile: ${descriptor.browser}/${descriptor.os}`);
390
254
  }
391
-
392
255
  return {
393
256
  ...profile,
394
257
  h2Settings: profile.h2Settings ? { ...profile.h2Settings } : undefined,
395
- headerOrder: profile.headerOrder ? [...profile.headerOrder] : undefined,
396
258
  };
397
259
  }
398
260
 
399
- /** Returns the intent alias that replaces a registered version-pinned profile. */
400
- export function getStealthProfileIntentAlias(name: string): string | undefined {
401
- if (PUBLIC_STEALTH_PROFILE_NAME_SET.has(name)) return undefined;
402
- if (/^(?:chrome|chromium|edge)[-_]\d/i.test(name)) {
403
- return "chrome-desktop";
404
- }
405
- if (/^firefox[-_]\d/i.test(name)) return "firefox-desktop";
406
- if (/^(?:ios[-_]safari|safari[-_](?:ios|ipad))[-_]\d/i.test(name)) {
407
- return "safari-mobile";
408
- }
409
- if (/^safari[-_]\d/i.test(name)) return "safari-desktop";
410
- return undefined;
411
- }
412
-
413
- /** Internal compatibility catalog used by transport-parity tests. */
414
- export function listRegisteredStealthProfiles(): string[] {
415
- const catalog = getStealthProfileCatalog();
416
- return [...Object.keys(catalog.profiles), ...Object.keys(catalog.aliases)];
417
- }
418
-
419
- export function listStealthProfiles(): string[] {
420
- return [...PUBLIC_STEALTH_PROFILE_NAMES];
261
+ export function listStealthProfiles(): StealthProfileDescriptor[] {
262
+ return SUPPORTED_STEALTH_PROFILES.map((profile) => ({ ...profile }));
421
263
  }