Files
WYF-koubo/electron/lib/browser-path.ts
T
2026-06-19 18:45:55 +08:00

269 lines
9.1 KiB
TypeScript

/**
* 浏览器路径工具
* 统一管理所有模块的浏览器可执行文件路径
* 支持自定义安装路径和离线使用
*/
import { app } from 'electron';
import path from 'path';
import fs from 'fs';
import { AppEnv } from '../mapi/env';
import { getRuntimeBundlePath } from './resource-path';
/**
* 获取Playwright Chromium浏览器可执行文件路径
* 优先使用打包的浏览器,支持自定义安装路径
*/
function getPlatformChromePaths(): string[] {
if (process.platform === 'win32') {
return ['chrome-win64/chrome.exe', 'chrome-win/chrome.exe'];
} else if (process.platform === 'darwin') {
return [
'chrome-mac/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing',
'chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing',
'chrome-mac/Chromium.app/Contents/MacOS/Chromium',
'chrome-mac/chrome',
];
} else {
return ['chrome-linux/chrome'];
}
}
function getPlatformCacheBase(): string[] {
const runtimeCandidates = [
getRuntimeBundlePath('playwright'),
AppEnv.resourceBundleRoot ? path.join(AppEnv.resourceBundleRoot, 'playwright') : '',
AppEnv.dataRoot ? path.join(AppEnv.dataRoot, 'playwright') : '',
].filter(Boolean);
if (process.platform === 'win32') {
return [
...runtimeCandidates,
path.join(process.env.LOCALAPPDATA || '', 'ms-playwright'),
path.join(process.env.APPDATA || '', 'ms-playwright'),
];
} else if (process.platform === 'darwin') {
return [
...runtimeCandidates,
path.join(process.env.HOME || '', 'Library/Caches/ms-playwright'),
];
} else {
return [
...runtimeCandidates,
path.join(process.env.HOME || '', '.cache/ms-playwright'),
];
}
}
export function getPlaywrightChromiumPath(): string | undefined {
const runtimePath = getRuntimeBundlePath('playwright');
const basePaths = [
runtimePath,
path.join(process.resourcesPath || app.getAppPath(), 'extra/common/playwright'),
path.join(app.getAppPath(), 'resources/extra/common/playwright'),
path.join(app.getAppPath(), 'electron/resources/extra/common/playwright'),
].filter(Boolean);
const chromiumVersions = ['chromium-1200'];
const chromePaths = getPlatformChromePaths();
for (const basePath of basePaths) {
for (const version of chromiumVersions) {
for (const chromePath of chromePaths) {
const browserPath = path.join(basePath, version, chromePath);
if (fs.existsSync(browserPath)) {
console.log('✅ [Browser] 使用打包的浏览器:', browserPath);
return browserPath;
}
}
}
}
const playwrightCacheBase = getPlatformCacheBase();
// 也尝试查找任何可用的 chromium 版本(仅用于开发环境回退)
for (const cacheBase of playwrightCacheBase) {
if (fs.existsSync(cacheBase)) {
const allDirs = fs.readdirSync(cacheBase);
const chromiumDirs = allDirs.filter(d => d.startsWith('chromium-')).sort().reverse();
for (const version of chromiumDirs) {
for (const chromePath of chromePaths) {
const browserPath = path.join(cacheBase, version, chromePath);
if (fs.existsSync(browserPath)) {
console.log('⚠️ [Browser] 使用系统缓存的浏览器:', browserPath);
return browserPath;
}
}
}
}
}
console.log('⚠️ [Browser] 警告:未找到Playwright浏览器');
return undefined;
}
/**
* 获取用户数据目录(用于持久化浏览器数据)
*/
export function getBrowserUserDataDir(contextName: string): string {
return path.join(app.getPath('userData'), 'browser-data', contextName);
}
/**
* 通用的浏览器启动参数
* 参考 aigc-human 项目的反爬和性能优化配置
*/
export const COMMON_BROWSER_ARGS = [
// === 核心反爬与沙箱配置 ===
'--disable-blink-features=AutomationControlled',
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-infobars',
// === 启动速度优化 ===
'--no-first-run',
'--no-default-browser-check',
'--disable-breakpad', // 禁用崩溃报告
'--disable-component-update', // 禁用组件自动更新
'--disable-sync', // 禁用同步
'--disable-default-apps',
'--disable-popup-blocking',
// === 后台性能优化 (防止页面被浏览器通过节流策略降速) ===
'--disable-background-networking',
'--disable-background-timer-throttling',
'--disable-renderer-backgrounding',
'--disable-backgrounding-occluded-windows',
// === 媒体与渲染优化 (针对视频发布场景) ===
'--enable-gpu',
'--enable-webgl',
'--ignore-gpu-blacklist',
'--enable-accelerated-video-decode',
'--enable-gpu-rasterization',
'--enable-proprietary-codecs', // 启用H.264等编解码器
// === 自动播放策略 ===
'--autoplay-policy=no-user-gesture-required',
// === 安全 ===
'--disable-features=IsolateOrigins,site-per-process',
];
/**
* 应该忽略的默认启动参数
* 移除 Chromium 的自动化标识,避免被平台检测
*/
export const BROWSER_IGNORE_DEFAULT_ARGS = [
'--enable-automation',
];
/**
* 反检测注入脚本
* 在每个页面加载前执行,隐藏 Playwright/webdriver 痕迹
*/
export const STEALTH_INIT_SCRIPT = `
// 隐藏 webdriver 标识
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined,
configurable: true
});
// 伪造 chrome.runtime(完善方法)
window.chrome = {
runtime: {
PlatformOs: { MAC: 'mac', WIN: 'win', ANDROID: 'android', CROS: 'cros', LINUX: 'linux', OPENBSD: 'openbsd' },
PlatformArch: { ARM: 'arm', X86_32: 'x86-32', X86_64: 'x86-64', MIPS: 'mips', MIPS64: 'mips64' },
PlatformNaclArch: { ARM: 'arm', X86_32: 'x86-32', X86_64: 'x86-64', MIPS: 'mips', MIPS64: 'mips64' },
RequestUpdateCheckStatus: { THROTTLED: 'throttled', NO_UPDATE: 'no_update', UPDATE_AVAILABLE: 'update_available' },
OnInstalledReason: { INSTALL: 'install', UPDATE: 'update', CHROME_UPDATE: 'chrome_update', SHARED_MODULE_UPDATE: 'shared_module_update' },
OnRestartRequiredReason: { APP_UPDATE: 'app_update', OS_UPDATE: 'os_update', PERIODIC: 'periodic' },
connect: function() { return { onDisconnect: { addListener: function() {} }, onMessage: { addListener: function() {} }, postMessage: function() {}, disconnect: function() {} }; },
sendMessage: function() {},
id: undefined
},
loadTimes: function() { return { firstPaintTime: 0, startLoadTime: 0, commitLoadTime: 0, finishDocumentLoadTime: 0, finishLoadTime: 0, firstPaintAfterLoadTime: 0, navigationType: 'Other', requestTime: Date.now() / 1000 }; },
csi: function() { return { startE: Date.now(), onloadT: Date.now(), pageT: Math.random() * 1000 + 500, tran: 15 }; },
app: { isInstalled: false, InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' }, RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' } },
};
// 伪造 navigator.plugins
Object.defineProperty(navigator, 'plugins', {
get: () => {
return [{
name: 'Chrome PDF Plugin',
description: 'Portable Document Format',
filename: 'internal-pdf-viewer',
length: 1,
}, {
name: 'Chrome PDF Viewer',
description: '',
filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai',
length: 1,
}, {
name: 'Native Client',
description: '',
filename: 'internal-nacl-plugin',
length: 2,
}];
},
configurable: true
});
// 伪造 navigator.languages
Object.defineProperty(navigator, 'languages', {
get: () => ['zh-CN', 'zh', 'en'],
configurable: true
});
// 伪造 navigator.hardwareConcurrency
Object.defineProperty(navigator, 'hardwareConcurrency', {
get: () => 8,
configurable: true
});
// 伪造 navigator.connection
if (!navigator.connection) {
Object.defineProperty(navigator, 'connection', {
get: () => ({ effectiveType: '4g', rtt: 50, downlink: 10, saveData: false }),
configurable: true
});
}
// 伪造 navigator.getBattery
if (!navigator.getBattery) {
navigator.getBattery = () => Promise.resolve({
charging: true,
chargingTime: 0,
dischargingTime: Infinity,
level: 1,
addEventListener: function() {},
removeEventListener: function() {},
dispatchEvent: function() { return true; }
});
}
// 清理 Playwright 特征
delete window.__playwright;
delete window.__pw_manual;
// 清理自动化工具特征
delete window._REACT_DEVTOOLS_GLOBAL_HOOK__;
delete window.__selenium_unwrapped;
delete window.__webdriver_evaluate;
delete window.__selenium_evaluate;
delete window.__fxdriver_evaluate;
delete window.__driver_unwrapped;
delete window.__driver_evaluate;
delete window.__phantomas;
delete window.__nightmare;
delete window.callSelenium;
delete window._phantom;
delete window.callPhantom;
delete window.Buffer;
delete window.emit;
delete window.spawn;
delete window.webdriver;
`;