@apifuse/provider-sdk 2.2.0-beta.43 → 2.2.0-beta.45
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 +1 -1
- package/dist/runtime/stealth.js +14 -5
- package/dist/server/serve-implementation.js +5 -44
- 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 +17 -5
- package/src/server/serve-implementation.ts +5 -47
- package/src/stealth/profiles.ts +135 -16
- package/src/types.ts +4 -0
|
@@ -23,10 +23,7 @@ import {
|
|
|
23
23
|
type ProviderErrorObservability,
|
|
24
24
|
type ProviderErrorOptions,
|
|
25
25
|
} from "../errors.js";
|
|
26
|
-
import {
|
|
27
|
-
REDACTED_FIXTURE_VALUE,
|
|
28
|
-
sanitizeDiagnosticText,
|
|
29
|
-
} from "../fixture-sanitization.js";
|
|
26
|
+
import { sanitizeDiagnosticText } from "../fixture-sanitization.js";
|
|
30
27
|
import {
|
|
31
28
|
loadProviderLocaleCatalogs,
|
|
32
29
|
localizeAuthTurn,
|
|
@@ -718,7 +715,7 @@ function createProviderContext(
|
|
|
718
715
|
? stealthProfile
|
|
719
716
|
? capabilityModules.stealth.createStealthClient(
|
|
720
717
|
stealthBaseUrl,
|
|
721
|
-
|
|
718
|
+
provider.stealth!.profile,
|
|
722
719
|
stealthClientOptions,
|
|
723
720
|
)
|
|
724
721
|
: capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
|
|
@@ -726,7 +723,7 @@ function createProviderContext(
|
|
|
726
723
|
? createLazyStealthClient(
|
|
727
724
|
logStealthCleanupError,
|
|
728
725
|
stealthBaseUrl,
|
|
729
|
-
|
|
726
|
+
provider.stealth!.profile,
|
|
730
727
|
stealthClientOptions,
|
|
731
728
|
)
|
|
732
729
|
: createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
|
|
@@ -933,7 +930,7 @@ function createAuthFlowContext(
|
|
|
933
930
|
? stealthProfile
|
|
934
931
|
? capabilityModules.stealth.createStealthClient(
|
|
935
932
|
stealthBaseUrl,
|
|
936
|
-
|
|
933
|
+
provider.stealth!.profile,
|
|
937
934
|
stealthClientOptions,
|
|
938
935
|
)
|
|
939
936
|
: capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
|
|
@@ -941,7 +938,7 @@ function createAuthFlowContext(
|
|
|
941
938
|
? createLazyStealthClient(
|
|
942
939
|
logStealthCleanupError,
|
|
943
940
|
stealthBaseUrl,
|
|
944
|
-
|
|
941
|
+
provider.stealth!.profile,
|
|
945
942
|
stealthClientOptions,
|
|
946
943
|
)
|
|
947
944
|
: createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
|
|
@@ -1526,48 +1523,9 @@ export type ProviderErrorCauseFrame = {
|
|
|
1526
1523
|
|
|
1527
1524
|
const MAX_PROVIDER_ERROR_CAUSE_FRAMES = 5;
|
|
1528
1525
|
const MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH = 300;
|
|
1529
|
-
const UNSTRUCTURED_PROVIDER_ERROR_CAUSE_MESSAGE = "[UNSTRUCTURED_UPSTREAM_TEXT]";
|
|
1530
|
-
const PROVIDER_ERROR_CAUSE_RETAINED_URL_RUN = /https?:\/\/[^\s"'<>]+/giu;
|
|
1531
|
-
const PROVIDER_ERROR_CAUSE_TOKEN_RUN = /\S+/gu;
|
|
1532
|
-
const PROVIDER_ERROR_CAUSE_TOKEN_EDGE_PUNCTUATION =
|
|
1533
|
-
/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu;
|
|
1534
|
-
const STRUCTURALLY_SAFE_PROVIDER_ERROR_CAUSE_WORDS = new Set([
|
|
1535
|
-
"completion",
|
|
1536
|
-
"diagnostic",
|
|
1537
|
-
"provider",
|
|
1538
|
-
"rejected",
|
|
1539
|
-
"returned",
|
|
1540
|
-
"upstream",
|
|
1541
|
-
]);
|
|
1542
|
-
|
|
1543
|
-
/**
|
|
1544
|
-
* Cause frames fail closed when sanitization leaves a plausible credential-shaped free-text run.
|
|
1545
|
-
* Redaction sentinels and retained URLs are ignored. Every other whitespace token (or each side
|
|
1546
|
-
* of a structured key=value token) with at least eight Unicode characters must reduce to this
|
|
1547
|
-
* small vocabulary drawn from SDK diagnostics; counting punctuation keeps bare passwords opaque.
|
|
1548
|
-
*/
|
|
1549
|
-
function isStructurallySafeProviderErrorCauseMessage(message: string): boolean {
|
|
1550
|
-
const classifiableMessage = message
|
|
1551
|
-
.replaceAll(REDACTED_FIXTURE_VALUE, " ")
|
|
1552
|
-
.replace(PROVIDER_ERROR_CAUSE_RETAINED_URL_RUN, " ");
|
|
1553
|
-
for (const match of classifiableMessage.matchAll(PROVIDER_ERROR_CAUSE_TOKEN_RUN)) {
|
|
1554
|
-
const token = match[0];
|
|
1555
|
-
for (const run of token.split("=")) {
|
|
1556
|
-
const diagnosticWord = run
|
|
1557
|
-
.replace(PROVIDER_ERROR_CAUSE_TOKEN_EDGE_PUNCTUATION, "")
|
|
1558
|
-
.toLowerCase();
|
|
1559
|
-
if (STRUCTURALLY_SAFE_PROVIDER_ERROR_CAUSE_WORDS.has(diagnosticWord)) continue;
|
|
1560
|
-
if ([...run].length >= 8) return false;
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
return true;
|
|
1564
|
-
}
|
|
1565
1526
|
|
|
1566
1527
|
function providerErrorCauseMessage(message: string): string {
|
|
1567
1528
|
const sanitized = sanitizeDiagnosticText(message);
|
|
1568
|
-
if (!isStructurallySafeProviderErrorCauseMessage(sanitized)) {
|
|
1569
|
-
return UNSTRUCTURED_PROVIDER_ERROR_CAUSE_MESSAGE;
|
|
1570
|
-
}
|
|
1571
1529
|
return sanitized.length > MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH
|
|
1572
1530
|
? `${sanitized.slice(0, MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH)}… [truncated]`
|
|
1573
1531
|
: sanitized;
|
package/src/stealth/profiles.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
|
|
3
|
+
import type { BrowserProfile } from "wreq-js";
|
|
4
|
+
|
|
1
5
|
import { SDKError } from "../errors.js";
|
|
2
6
|
import type { StealthPlatform, StealthProfile } from "../types.js";
|
|
3
7
|
|
|
@@ -5,6 +9,16 @@ type StealthProfileDefinition = Omit<StealthProfile, "name" | "platform"> & {
|
|
|
5
9
|
platform: StealthPlatform;
|
|
6
10
|
};
|
|
7
11
|
|
|
12
|
+
type WreqProfileApi = Pick<typeof import("wreq-js"), "getEmulationHeaders" | "getProfiles">;
|
|
13
|
+
|
|
14
|
+
const requireModule = createRequire(import.meta.url);
|
|
15
|
+
let wreqProfileApi: WreqProfileApi | undefined;
|
|
16
|
+
|
|
17
|
+
function getWreqProfileApi(): WreqProfileApi {
|
|
18
|
+
wreqProfileApi ??= requireModule("wreq-js") as WreqProfileApi;
|
|
19
|
+
return wreqProfileApi;
|
|
20
|
+
}
|
|
21
|
+
|
|
8
22
|
const CHROMIUM_HEADER_ORDER = [
|
|
9
23
|
":method",
|
|
10
24
|
":authority",
|
|
@@ -81,6 +95,52 @@ const FIREFOX_JA3 =
|
|
|
81
95
|
const SAFARI_JA3 =
|
|
82
96
|
"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";
|
|
83
97
|
|
|
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
|
+
*/
|
|
113
|
+
function resolveLatestChromiumProfile(): {
|
|
114
|
+
readonly wreqName: BrowserProfile;
|
|
115
|
+
readonly version: string;
|
|
116
|
+
} {
|
|
117
|
+
const { getProfiles } = getWreqProfileApi();
|
|
118
|
+
let newest: { name: BrowserProfile; version: number } | undefined;
|
|
119
|
+
for (const name of getProfiles()) {
|
|
120
|
+
const match = /^chrome_(\d+)$/.exec(name);
|
|
121
|
+
if (!match) continue;
|
|
122
|
+
const version = Number(match[1]);
|
|
123
|
+
if (!newest || version > newest.version) newest = { name, version };
|
|
124
|
+
}
|
|
125
|
+
if (!newest) {
|
|
126
|
+
throw new SDKError("wreq-js exposes no chrome_<version> emulation profile.", {
|
|
127
|
+
code: "STEALTH_PROFILE_UNAVAILABLE",
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return { wreqName: newest.name, version: `${newest.version}.0.0.0` };
|
|
131
|
+
}
|
|
132
|
+
|
|
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")) {
|
|
137
|
+
if (String(name).toLowerCase() === "user-agent") return String(value);
|
|
138
|
+
}
|
|
139
|
+
throw new SDKError(`wreq-js profile ${latest.wreqName} exposes no user-agent header.`, {
|
|
140
|
+
code: "STEALTH_PROFILE_UNAVAILABLE",
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
84
144
|
function createProfile(name: string, definition: StealthProfileDefinition): StealthProfile {
|
|
85
145
|
return {
|
|
86
146
|
name,
|
|
@@ -145,11 +205,24 @@ export function generateLayer2Headers(profile: StealthProfile): Record<string, s
|
|
|
145
205
|
return headers;
|
|
146
206
|
}
|
|
147
207
|
|
|
148
|
-
const
|
|
149
|
-
"
|
|
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",
|
|
150
212
|
};
|
|
151
213
|
|
|
152
|
-
const
|
|
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> = {
|
|
153
226
|
"chrome-146": createProfile("chrome-146", {
|
|
154
227
|
platform: "macos",
|
|
155
228
|
version: "146.0.0.0",
|
|
@@ -260,16 +333,6 @@ const STEALTH_PROFILES: Record<string, StealthProfile> = {
|
|
|
260
333
|
h2Settings: SAFARI_H2_SETTINGS,
|
|
261
334
|
headerOrder: SAFARI_HEADER_ORDER,
|
|
262
335
|
}),
|
|
263
|
-
"generic-desktop": createProfile("generic-desktop", {
|
|
264
|
-
platform: "macos",
|
|
265
|
-
version: "146.0.0.0",
|
|
266
|
-
userAgent:
|
|
267
|
-
"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",
|
|
268
|
-
tlsClientIdentifier: "chrome_146",
|
|
269
|
-
ja3: CHROMIUM_JA3,
|
|
270
|
-
h2Settings: CHROMIUM_H2_SETTINGS,
|
|
271
|
-
headerOrder: CHROMIUM_HEADER_ORDER,
|
|
272
|
-
}),
|
|
273
336
|
"generic-mobile": createProfile("generic-mobile", {
|
|
274
337
|
platform: "ios",
|
|
275
338
|
version: "26.0",
|
|
@@ -282,9 +345,45 @@ const STEALTH_PROFILES: Record<string, StealthProfile> = {
|
|
|
282
345
|
}),
|
|
283
346
|
};
|
|
284
347
|
|
|
348
|
+
type StealthProfileCatalog = {
|
|
349
|
+
aliases: Record<string, string>;
|
|
350
|
+
profiles: Record<string, StealthProfile>;
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
let stealthProfileCatalog: StealthProfileCatalog | undefined;
|
|
354
|
+
|
|
355
|
+
function getStealthProfileCatalog(): StealthProfileCatalog {
|
|
356
|
+
if (stealthProfileCatalog) return stealthProfileCatalog;
|
|
357
|
+
|
|
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,
|
|
373
|
+
},
|
|
374
|
+
profiles: {
|
|
375
|
+
[currentName]: currentProfile,
|
|
376
|
+
...STATIC_STEALTH_PROFILES,
|
|
377
|
+
"generic-desktop": createProfile("generic-desktop", currentProfile),
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
return stealthProfileCatalog;
|
|
381
|
+
}
|
|
382
|
+
|
|
285
383
|
export function getStealthProfile(name: string): StealthProfile {
|
|
286
|
-
const
|
|
287
|
-
const
|
|
384
|
+
const catalog = getStealthProfileCatalog();
|
|
385
|
+
const canonicalName = catalog.aliases[name] ?? name;
|
|
386
|
+
const profile = catalog.profiles[canonicalName];
|
|
288
387
|
|
|
289
388
|
if (!profile) {
|
|
290
389
|
throw new SDKError(`Unknown stealth profile: ${name}`);
|
|
@@ -297,6 +396,26 @@ export function getStealthProfile(name: string): StealthProfile {
|
|
|
297
396
|
};
|
|
298
397
|
}
|
|
299
398
|
|
|
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
|
+
|
|
300
419
|
export function listStealthProfiles(): string[] {
|
|
301
|
-
return [...
|
|
420
|
+
return [...PUBLIC_STEALTH_PROFILE_NAMES];
|
|
302
421
|
}
|
package/src/types.ts
CHANGED
|
@@ -1130,6 +1130,10 @@ export interface ProviderMeta {
|
|
|
1130
1130
|
publicProfile?: ProviderPublicProfile;
|
|
1131
1131
|
contract?: {
|
|
1132
1132
|
publicSchemaFieldNames?: "normalized";
|
|
1133
|
+
readonly pinnedWireFieldPaths?: readonly {
|
|
1134
|
+
readonly path: string;
|
|
1135
|
+
readonly reason: string;
|
|
1136
|
+
}[];
|
|
1133
1137
|
};
|
|
1134
1138
|
}
|
|
1135
1139
|
|