fix: 修复用户级安装的 Chrome 探测失败

- LOCALAPPDATA 环境变量缺失(受限启动环境)时退回 os.homedir() 拼接 AppData/Local 候选路径
- 注册表 App Paths 兜底从仅 HKLM 扩展为 HKLM + WOW6432Node + HKCU(用户级安装的 Chrome 注册在 HKCU)
- 本机复现: Chrome 装于 C:/Users/6/AppData/Local, 注册于 HKCU App Paths, 修复后可探测到
This commit is contained in:
yu 2026-10-30 11:36:30 +08:00
parent f28b5ffc5e
commit fd507e4f62

View File

@ -52,7 +52,7 @@ const BROWSER_CANDIDATE_PATHS: Record<ExternalBrowserType, string[]> = {
chrome: [
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
path.join(process.env.LOCALAPPDATA ?? "", "Google", "Chrome", "Application", "chrome.exe")
path.join(localAppDataDir(), "Google", "Chrome", "Application", "chrome.exe")
],
edge: [
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
@ -60,9 +60,24 @@ const BROWSER_CANDIDATE_PATHS: Record<ExternalBrowserType, string[]> = {
]
};
const BROWSER_APP_PATHS_REGISTRY: Record<ExternalBrowserType, string> = {
chrome: "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe",
edge: "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\msedge.exe"
/** 用户级 AppData\Local 目录;LOCALAPPDATA 缺失(如受限启动环境)时退回 USERPROFILE 拼接 */
function localAppDataDir(): string {
if (process.env.LOCALAPPDATA) return process.env.LOCALAPPDATA;
return path.join(os.homedir(), "AppData", "Local");
}
/** 注册表 App Paths 兜底:HKLM(64 位 + WOW6432Node)与 HKCU(用户级安装)都查 */
const BROWSER_APP_PATHS_REGISTRIES: Record<ExternalBrowserType, string[]> = {
chrome: [
"HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe",
"HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe",
"HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe"
],
edge: [
"HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\msedge.exe",
"HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\App Paths\\msedge.exe",
"HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\msedge.exe"
]
};
async function fileExists(p: string): Promise<boolean> {
@ -84,20 +99,22 @@ function queryRegistry(command: string): Promise<string> {
});
}
/** 探测本机浏览器安装路径:常见目录优先,注册表 App Paths 兜底 */
/** 探测本机浏览器安装路径:常见目录优先,注册表 App Paths(HKLM/HKCU)兜底 */
export async function detectBrowserPath(browserType: ExternalBrowserType): Promise<string> {
for (const candidate of BROWSER_CANDIDATE_PATHS[browserType]) {
if (await fileExists(candidate)) return candidate;
}
try {
const stdout = await queryRegistry(BROWSER_APP_PATHS_REGISTRY[browserType]);
const match = stdout.match(/REG_SZ\s+(\S[^\r\n]*)/);
if (match) {
const registryPath = match[1].trim();
if (await fileExists(registryPath)) return registryPath;
for (const registryKey of BROWSER_APP_PATHS_REGISTRIES[browserType]) {
try {
const stdout = await queryRegistry(registryKey);
const match = stdout.match(/REG_SZ\s+(\S[^\r\n]*)/);
if (match) {
const registryPath = match[1].trim();
if (await fileExists(registryPath)) return registryPath;
}
} catch {
// 该键不存在继续查下一个
}
} catch {
// 注册表查询失败继续走报错
}
throw new RuntimeError(
"browser_not_found",