Spaces:
Paused
Paused
icebear0828 Claude Opus 4.6 commited on
Commit ·
27e0738
1
Parent(s): 3838101
fix: auto-fallback for unsupported TLS impersonate profiles
Browse filescurl-impersonate skips Chrome versions when the TLS fingerprint is
unchanged (e.g. chrome137 doesn't exist, chrome136 covers it).
Previously, configuring an unsupported profile like chrome137 caused
`curl: (43) Unknown impersonation target` at runtime.
Now `detectImpersonateSupport()` validates the configured profile by
test-running it, and automatically descends to the nearest supported
Chrome version. Also unified the FFI transport to use the resolved
profile instead of a hardcoded "chrome136".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- CHANGELOG.md +2 -0
- src/tls/curl-binary.ts +56 -5
- src/tls/libcurl-ffi-transport.ts +2 -2
CHANGELOG.md
CHANGED
|
@@ -25,6 +25,8 @@
|
|
| 25 |
|
| 26 |
### Fixed
|
| 27 |
|
|
|
|
|
|
|
| 28 |
- `getModels()` 死代码:`allModels` 作用域修复,消除不可达分支
|
| 29 |
- `reloadAllConfigs()` 异步 lazy import 改为同步直接导入,避免日志时序不准
|
| 30 |
- 模型合并 reasoning efforts 判断逻辑从 `length > 1` 改为显式标志
|
|
|
|
| 25 |
|
| 26 |
### Fixed
|
| 27 |
|
| 28 |
+
- TLS 伪装 profile 自动回退:配置的 `impersonate_profile` 不被 curl-impersonate 支持时,自动降级到最近的可用 Chrome 版本(如 `chrome137` → `chrome136`)
|
| 29 |
+
- FFI transport 硬编码 `"chrome136"` 改为使用统一解析的 profile(`getResolvedProfile()`)
|
| 30 |
- `getModels()` 死代码:`allModels` 作用域修复,消除不可达分支
|
| 31 |
- `reloadAllConfigs()` 异步 lazy import 改为同步直接导入,避免日志时序不准
|
| 32 |
- 模型合并 reasoning efforts 判断逻辑从 `length > 1` 改为显式标志
|
src/tls/curl-binary.ts
CHANGED
|
@@ -69,6 +69,7 @@ const CHROME_TLS_ARGS: string[] = [
|
|
| 69 |
let _resolved: string | null = null;
|
| 70 |
let _isImpersonate = false;
|
| 71 |
let _tlsArgs: string[] | null = null;
|
|
|
|
| 72 |
|
| 73 |
/**
|
| 74 |
* Resolve the curl binary path. Result is cached after first call.
|
|
@@ -105,10 +106,45 @@ export function resolveCurlBinary(): string {
|
|
| 105 |
return _resolved;
|
| 106 |
}
|
| 107 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
/**
|
| 109 |
* Detect if curl-impersonate supports the --impersonate flag.
|
| 110 |
-
*
|
| 111 |
-
*
|
| 112 |
*/
|
| 113 |
function detectImpersonateSupport(binary: string): string[] {
|
| 114 |
try {
|
|
@@ -117,9 +153,14 @@ function detectImpersonateSupport(binary: string): string[] {
|
|
| 117 |
timeout: 5000,
|
| 118 |
});
|
| 119 |
if (helpOutput.includes("--impersonate")) {
|
| 120 |
-
const
|
| 121 |
-
|
| 122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
}
|
| 124 |
} catch {
|
| 125 |
// --help failed, fall back to manual args
|
|
@@ -219,6 +260,15 @@ export function isImpersonate(): boolean {
|
|
| 219 |
return _isImpersonate;
|
| 220 |
}
|
| 221 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
/**
|
| 223 |
* Get the detected proxy URL (or null if no proxy).
|
| 224 |
* Used by LibcurlFfiTransport which needs the URL directly (not CLI args).
|
|
@@ -234,4 +284,5 @@ export function resetCurlBinaryCache(): void {
|
|
| 234 |
_resolved = null;
|
| 235 |
_isImpersonate = false;
|
| 236 |
_tlsArgs = null;
|
|
|
|
| 237 |
}
|
|
|
|
| 69 |
let _resolved: string | null = null;
|
| 70 |
let _isImpersonate = false;
|
| 71 |
let _tlsArgs: string[] | null = null;
|
| 72 |
+
let _resolvedProfile = "chrome136";
|
| 73 |
|
| 74 |
/**
|
| 75 |
* Resolve the curl binary path. Result is cached after first call.
|
|
|
|
| 106 |
return _resolved;
|
| 107 |
}
|
| 108 |
|
| 109 |
+
/** Test if a specific --impersonate profile is accepted by the binary. */
|
| 110 |
+
function testProfile(binary: string, profile: string): boolean {
|
| 111 |
+
try {
|
| 112 |
+
execFileSync(binary, ["--impersonate", profile, "-V"], {
|
| 113 |
+
encoding: "utf-8",
|
| 114 |
+
timeout: 5000,
|
| 115 |
+
stdio: ["ignore", "pipe", "pipe"],
|
| 116 |
+
});
|
| 117 |
+
return true;
|
| 118 |
+
} catch {
|
| 119 |
+
return false;
|
| 120 |
+
}
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
/**
|
| 124 |
+
* Find a working impersonate profile.
|
| 125 |
+
* Tries the configured profile first, then descends Chrome versions.
|
| 126 |
+
*/
|
| 127 |
+
function resolveProfile(binary: string, configured: string): string | null {
|
| 128 |
+
if (testProfile(binary, configured)) return configured;
|
| 129 |
+
|
| 130 |
+
const match = configured.match(/^chrome(\d+)/);
|
| 131 |
+
if (!match) return null;
|
| 132 |
+
|
| 133 |
+
const ver = parseInt(match[1], 10);
|
| 134 |
+
for (let v = ver - 1; v >= ver - 10 && v > 0; v--) {
|
| 135 |
+
const candidate = `chrome${v}`;
|
| 136 |
+
if (testProfile(binary, candidate)) {
|
| 137 |
+
console.warn(`[TLS] Profile "${configured}" not supported, using "${candidate}"`);
|
| 138 |
+
return candidate;
|
| 139 |
+
}
|
| 140 |
+
}
|
| 141 |
+
return null;
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
/**
|
| 145 |
* Detect if curl-impersonate supports the --impersonate flag.
|
| 146 |
+
* Validates the configured profile and auto-falls back to the nearest
|
| 147 |
+
* supported Chrome version if needed.
|
| 148 |
*/
|
| 149 |
function detectImpersonateSupport(binary: string): string[] {
|
| 150 |
try {
|
|
|
|
| 153 |
timeout: 5000,
|
| 154 |
});
|
| 155 |
if (helpOutput.includes("--impersonate")) {
|
| 156 |
+
const configured = getConfig().tls.impersonate_profile ?? "chrome136";
|
| 157 |
+
const profile = resolveProfile(binary, configured);
|
| 158 |
+
if (profile) {
|
| 159 |
+
_resolvedProfile = profile;
|
| 160 |
+
console.log(`[TLS] Using --impersonate ${profile}`);
|
| 161 |
+
return ["--impersonate", profile];
|
| 162 |
+
}
|
| 163 |
+
console.warn("[TLS] No supported impersonate profile found, using manual TLS args");
|
| 164 |
}
|
| 165 |
} catch {
|
| 166 |
// --help failed, fall back to manual args
|
|
|
|
| 260 |
return _isImpersonate;
|
| 261 |
}
|
| 262 |
|
| 263 |
+
/**
|
| 264 |
+
* Get the resolved impersonate profile (e.g. "chrome136").
|
| 265 |
+
* Used by FFI transport which needs the profile name directly.
|
| 266 |
+
*/
|
| 267 |
+
export function getResolvedProfile(): string {
|
| 268 |
+
getChromeTlsArgs(); // ensure detection has run
|
| 269 |
+
return _resolvedProfile;
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
/**
|
| 273 |
* Get the detected proxy URL (or null if no proxy).
|
| 274 |
* Used by LibcurlFfiTransport which needs the URL directly (not CLI args).
|
|
|
|
| 284 |
_resolved = null;
|
| 285 |
_isImpersonate = false;
|
| 286 |
_tlsArgs = null;
|
| 287 |
+
_resolvedProfile = "chrome136";
|
| 288 |
}
|
src/tls/libcurl-ffi-transport.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { resolve } from "path";
|
|
| 12 |
import { existsSync } from "fs";
|
| 13 |
import type { IKoffiLib, IKoffiCType, IKoffiRegisteredCallback, KoffiFunction } from "koffi";
|
| 14 |
import type { TlsTransport, TlsTransportResponse } from "./transport.js";
|
| 15 |
-
import { getProxyUrl } from "./curl-binary.js";
|
| 16 |
|
| 17 |
// ── libcurl constants ──────────────────────────────────────────────
|
| 18 |
|
|
@@ -450,7 +450,7 @@ export class LibcurlFfiTransport implements TlsTransport {
|
|
| 450 |
if (!easy) throw new Error("curl_easy_init() returned null");
|
| 451 |
|
| 452 |
// Impersonate Chrome — 0 = don't inject default headers (we control them)
|
| 453 |
-
b.curl_easy_impersonate(easy,
|
| 454 |
|
| 455 |
b.curl_easy_setopt_str(easy, CURLOPT_URL, url);
|
| 456 |
b.curl_easy_setopt_long(easy, CURLOPT_NOSIGNAL, 1);
|
|
|
|
| 12 |
import { existsSync } from "fs";
|
| 13 |
import type { IKoffiLib, IKoffiCType, IKoffiRegisteredCallback, KoffiFunction } from "koffi";
|
| 14 |
import type { TlsTransport, TlsTransportResponse } from "./transport.js";
|
| 15 |
+
import { getProxyUrl, getResolvedProfile } from "./curl-binary.js";
|
| 16 |
|
| 17 |
// ── libcurl constants ──────────────────────────────────────────────
|
| 18 |
|
|
|
|
| 450 |
if (!easy) throw new Error("curl_easy_init() returned null");
|
| 451 |
|
| 452 |
// Impersonate Chrome — 0 = don't inject default headers (we control them)
|
| 453 |
+
b.curl_easy_impersonate(easy, getResolvedProfile(), 0);
|
| 454 |
|
| 455 |
b.curl_easy_setopt_str(easy, CURLOPT_URL, url);
|
| 456 |
b.curl_easy_setopt_long(easy, CURLOPT_NOSIGNAL, 1);
|