Compare commits
2 Commits
78a35bafc4
...
4f387b5ae7
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f387b5ae7 | |||
| a34e3c72f0 |
@@ -4,6 +4,8 @@ import StealthPlugin from 'puppeteer-extra-plugin-stealth';
|
||||
import type { Browser, BrowserContext, Page } from 'playwright';
|
||||
import type { FingerprintProfile, PageInfo } from '@visionl/core';
|
||||
import { PageRegistry } from './page-registry.js';
|
||||
import { getProfile } from './stealth/profiles/index.js';
|
||||
import { applyStealth } from './stealth/index.js';
|
||||
|
||||
// Apply stealth plugin once at module level / 模块级别一次性注入隐身插件
|
||||
chromium.use(StealthPlugin());
|
||||
@@ -18,8 +20,14 @@ export class BrowserManager {
|
||||
private registry = new PageRegistry();
|
||||
private profile: FingerprintProfile;
|
||||
|
||||
constructor(profile: FingerprintProfile) {
|
||||
this.profile = profile;
|
||||
constructor(profileOrId: FingerprintProfile | string = 'desktop-chrome') {
|
||||
if (typeof profileOrId === 'string') {
|
||||
const resolved = getProfile(profileOrId);
|
||||
if (!resolved) throw new Error(`Unknown profile: ${profileOrId}`);
|
||||
this.profile = resolved;
|
||||
} else {
|
||||
this.profile = profileOrId;
|
||||
}
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
@@ -61,6 +69,10 @@ export class BrowserManager {
|
||||
});
|
||||
|
||||
const page = await context.newPage();
|
||||
|
||||
// Apply stealth injections before navigation / 导航前注入隐身模块
|
||||
await applyStealth(context, page, this.profile);
|
||||
|
||||
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||||
const title = await page.title();
|
||||
|
||||
|
||||
@@ -1,2 +1,16 @@
|
||||
// @visionl/daemon — placeholder, to be implemented in Task 3
|
||||
// @visionl/daemon — browser daemon entry point / 浏览器守护进程入口
|
||||
export const DAEMON_VERSION = '0.1.0';
|
||||
|
||||
// Re-export stealth orchestrator and helpers / 重新导出隐身编排器和辅助函数
|
||||
export {
|
||||
applyStealth,
|
||||
getProfile,
|
||||
listProfiles,
|
||||
humanClick,
|
||||
humanType,
|
||||
humanScroll,
|
||||
injectHeaderStealth,
|
||||
} from './stealth/index.js';
|
||||
|
||||
export { BrowserManager } from './browser-manager.js';
|
||||
export { startServer } from './server.js';
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// Stealth Injection Orchestrator — single entry point for all stealth modules / 隐身注入编排器 — 所有隐身模块的统一入口
|
||||
import type { BrowserContext, Page } from 'playwright';
|
||||
import type { FingerprintProfile } from '@visionl/core';
|
||||
import { injectNavigatorStealth } from './navigator.js';
|
||||
import { injectChromeRuntime } from './chrome-runtime.js';
|
||||
import { injectScreenStealth } from './screen.js';
|
||||
import { injectPermissionsStealth } from './permissions.js';
|
||||
import { injectCanvasNoise } from './canvas-noise.js';
|
||||
import { injectHeaderStealth } from './headers.js';
|
||||
|
||||
// Re-export for external use / 重新导出供外部使用
|
||||
export { getProfile, listProfiles } from './profiles/index.js';
|
||||
export { humanClick, humanType, humanScroll } from './human-input.js';
|
||||
export { injectHeaderStealth } from './headers.js';
|
||||
|
||||
/**
|
||||
* Apply all stealth injections to a newly created page.
|
||||
* Phase 1: addInitScript (must run before navigation).
|
||||
* Phase 2: page.route interception (must run after page creation).
|
||||
*
|
||||
* @param context BrowserContext
|
||||
* @param page Playwright Page
|
||||
* @param profile Fingerprint profile to emulate
|
||||
*/
|
||||
export async function applyStealth(
|
||||
context: BrowserContext,
|
||||
page: Page,
|
||||
profile: FingerprintProfile,
|
||||
): Promise<void> {
|
||||
// Phase 1: initScript injections (must happen before page navigation)
|
||||
// 阶段 1: initScript 注入(必须在页面导航前执行)
|
||||
await injectNavigatorStealth(context, profile);
|
||||
await injectChromeRuntime(context);
|
||||
await injectScreenStealth(context, profile);
|
||||
await injectPermissionsStealth(context, profile);
|
||||
if (profile.canvasNoise.enabled) {
|
||||
await injectCanvasNoise(context, profile.canvasNoise);
|
||||
}
|
||||
|
||||
// Phase 2: page.route interception (must happen after page creation)
|
||||
// 阶段 2: page.route 拦截(必须在页面创建后执行)
|
||||
await injectHeaderStealth(page, profile);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Default desktop Chrome fingerprint (Linux) / 默认桌面 Chrome 指纹 (Linux)
|
||||
import type { FingerprintProfile } from '@visionl/core';
|
||||
|
||||
export const desktopChrome: FingerprintProfile = {
|
||||
id: 'desktop-chrome',
|
||||
name: '桌面 Chrome (通用)',
|
||||
userAgent:
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36',
|
||||
platform: 'Linux x86_64',
|
||||
languages: ['zh-CN', 'en-US'],
|
||||
acceptLanguage: 'zh-CN,zh;q=0.9,en;q=0.8',
|
||||
screen: { width: 1920, height: 1080, colorDepth: 24, pixelRatio: 1 },
|
||||
viewport: { width: 1920, height: 937 },
|
||||
webgl: {
|
||||
vendor: 'Google Inc. (Intel)',
|
||||
renderer:
|
||||
'ANGLE (Intel, Mesa Intel(R) UHD Graphics (CML GT2), OpenGL 4.6)',
|
||||
},
|
||||
timezone: 'Asia/Shanghai',
|
||||
permissions: {
|
||||
notifications: 'prompt',
|
||||
geolocation: 'prompt',
|
||||
camera: 'prompt',
|
||||
microphone: 'prompt',
|
||||
},
|
||||
behavior: {
|
||||
mouseMoveDelay: { min: 5, max: 15 },
|
||||
keyPressDelay: { min: 50, max: 150 },
|
||||
scrollStepDelay: { min: 20, max: 40 },
|
||||
},
|
||||
canvasNoise: { enabled: true, strength: 0.3 },
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
// macOS Chrome fingerprint / macOS Chrome 指纹
|
||||
import type { FingerprintProfile } from '@visionl/core';
|
||||
|
||||
export const desktopMac: FingerprintProfile = {
|
||||
id: 'desktop-mac',
|
||||
name: '桌面 Chrome (macOS)',
|
||||
userAgent:
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36',
|
||||
platform: 'MacIntel',
|
||||
languages: ['zh-CN', 'en-US'],
|
||||
acceptLanguage: 'zh-CN,zh;q=0.9,en;q=0.8',
|
||||
screen: { width: 1920, height: 1080, colorDepth: 24, pixelRatio: 2 },
|
||||
viewport: { width: 1920, height: 937 },
|
||||
webgl: {
|
||||
vendor: 'Apple Inc.',
|
||||
renderer: 'Apple M1',
|
||||
},
|
||||
timezone: 'Asia/Shanghai',
|
||||
permissions: {
|
||||
notifications: 'prompt',
|
||||
geolocation: 'prompt',
|
||||
camera: 'prompt',
|
||||
microphone: 'prompt',
|
||||
},
|
||||
behavior: {
|
||||
mouseMoveDelay: { min: 5, max: 15 },
|
||||
keyPressDelay: { min: 50, max: 150 },
|
||||
scrollStepDelay: { min: 20, max: 40 },
|
||||
},
|
||||
canvasNoise: { enabled: true, strength: 0.3 },
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
// Windows 10 Chrome fingerprint / Windows 10 Chrome 指纹
|
||||
import type { FingerprintProfile } from '@visionl/core';
|
||||
|
||||
export const desktopWindows: FingerprintProfile = {
|
||||
id: 'desktop-windows',
|
||||
name: '桌面 Chrome (Windows)',
|
||||
userAgent:
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36',
|
||||
platform: 'Win32',
|
||||
languages: ['zh-CN', 'en-US'],
|
||||
acceptLanguage: 'zh-CN,zh;q=0.9,en;q=0.8',
|
||||
screen: { width: 1920, height: 1080, colorDepth: 24, pixelRatio: 1 },
|
||||
viewport: { width: 1920, height: 937 },
|
||||
webgl: {
|
||||
vendor: 'Google Inc. (NVIDIA)',
|
||||
renderer:
|
||||
'ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 Ti Direct3D11 vs_5_0 ps_5_0)',
|
||||
},
|
||||
timezone: 'Asia/Shanghai',
|
||||
permissions: {
|
||||
notifications: 'prompt',
|
||||
geolocation: 'prompt',
|
||||
camera: 'prompt',
|
||||
microphone: 'prompt',
|
||||
},
|
||||
behavior: {
|
||||
mouseMoveDelay: { min: 5, max: 15 },
|
||||
keyPressDelay: { min: 50, max: 150 },
|
||||
scrollStepDelay: { min: 20, max: 40 },
|
||||
},
|
||||
canvasNoise: { enabled: true, strength: 0.3 },
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
// Built-in fingerprint profiles registry / 内置指纹配置注册表
|
||||
import type { FingerprintProfile } from '@visionl/core';
|
||||
import { desktopChrome } from './desktop-chrome.js';
|
||||
import { desktopWindows } from './desktop-windows.js';
|
||||
import { desktopMac } from './desktop-mac.js';
|
||||
|
||||
const profiles = new Map<string, FingerprintProfile>([
|
||||
[desktopChrome.id, desktopChrome],
|
||||
[desktopWindows.id, desktopWindows],
|
||||
[desktopMac.id, desktopMac],
|
||||
]);
|
||||
|
||||
export function getProfile(id: string): FingerprintProfile | undefined {
|
||||
return profiles.get(id);
|
||||
}
|
||||
|
||||
export function listProfiles(): Array<{ id: string; name: string }> {
|
||||
return Array.from(profiles.values()).map(({ id, name }) => ({ id, name }));
|
||||
}
|
||||
|
||||
export { desktopChrome, desktopWindows, desktopMac };
|
||||
Reference in New Issue
Block a user