重构浏览器启动逻辑,优化连接设置,更新依赖项,增强功能和可读性
This commit is contained in:
@@ -17,9 +17,8 @@
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"init:browser": "npx playwright install",
|
||||
"build": "bun run bun.config.ts",
|
||||
"browser": "pm2 start start-browser.js --name browser-helper",
|
||||
"browser": "pm2 start start-browser.js --name browser ",
|
||||
"cmd": "tsx src/test/cmd.ts ",
|
||||
"pm2": "pm2 start dist/app.js --name /root/browser-helper",
|
||||
"init": "pnpm run init:pnpm && pnpm run init:db && pnpm run init:browser",
|
||||
"init:pnpm": "pnpm approve-builds",
|
||||
"init:db": "npx drizzle-kit push",
|
||||
@@ -57,7 +56,6 @@
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"es-toolkit": "^1.43.0",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"lru-cache": "^11.2.4",
|
||||
"window-size": "^1.1.1"
|
||||
"lru-cache": "^11.2.4"
|
||||
}
|
||||
}
|
||||
80
pnpm-lock.yaml
generated
80
pnpm-lock.yaml
generated
@@ -60,9 +60,6 @@ importers:
|
||||
lru-cache:
|
||||
specifier: ^11.2.4
|
||||
version: 11.2.4
|
||||
window-size:
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1
|
||||
|
||||
packages:
|
||||
|
||||
@@ -487,10 +484,6 @@ packages:
|
||||
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
||||
define-property@1.0.0:
|
||||
resolution: {integrity: sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
depd@2.0.0:
|
||||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -656,19 +649,12 @@ packages:
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
|
||||
get-tsconfig@4.13.0:
|
||||
resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==}
|
||||
|
||||
github-from-package@0.0.0:
|
||||
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
|
||||
|
||||
hasown@2.0.2:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
http-errors@2.0.1:
|
||||
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -682,29 +668,6 @@ packages:
|
||||
ini@1.3.8:
|
||||
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
|
||||
|
||||
is-accessor-descriptor@1.0.1:
|
||||
resolution: {integrity: sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
is-buffer@1.1.6:
|
||||
resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
|
||||
|
||||
is-data-descriptor@1.0.1:
|
||||
resolution: {integrity: sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-descriptor@1.0.3:
|
||||
resolution: {integrity: sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-number@3.0.0:
|
||||
resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
kind-of@3.2.2:
|
||||
resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
lru-cache@11.2.4:
|
||||
resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==}
|
||||
engines: {node: 20 || >=22}
|
||||
@@ -872,11 +835,6 @@ packages:
|
||||
util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
||||
window-size@1.1.1:
|
||||
resolution: {integrity: sha512-5D/9vujkmVQ7pSmc0SCBmHXbkv6eaHwXEx65MywhmUMsI8sGqJ972APq1lotfcwMKPFLuCFfL8xGHLIp7jaBmA==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
hasBin: true
|
||||
|
||||
wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
||||
@@ -1220,10 +1178,6 @@ snapshots:
|
||||
|
||||
deep-extend@0.6.0: {}
|
||||
|
||||
define-property@1.0.0:
|
||||
dependencies:
|
||||
is-descriptor: 1.0.3
|
||||
|
||||
depd@2.0.0: {}
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
@@ -1333,18 +1287,12 @@ snapshots:
|
||||
fsevents@2.3.2:
|
||||
optional: true
|
||||
|
||||
function-bind@1.1.2: {}
|
||||
|
||||
get-tsconfig@4.13.0:
|
||||
dependencies:
|
||||
resolve-pkg-maps: 1.0.0
|
||||
|
||||
github-from-package@0.0.0: {}
|
||||
|
||||
hasown@2.0.2:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
http-errors@2.0.1:
|
||||
dependencies:
|
||||
depd: 2.0.0
|
||||
@@ -1359,29 +1307,6 @@ snapshots:
|
||||
|
||||
ini@1.3.8: {}
|
||||
|
||||
is-accessor-descriptor@1.0.1:
|
||||
dependencies:
|
||||
hasown: 2.0.2
|
||||
|
||||
is-buffer@1.1.6: {}
|
||||
|
||||
is-data-descriptor@1.0.1:
|
||||
dependencies:
|
||||
hasown: 2.0.2
|
||||
|
||||
is-descriptor@1.0.3:
|
||||
dependencies:
|
||||
is-accessor-descriptor: 1.0.1
|
||||
is-data-descriptor: 1.0.1
|
||||
|
||||
is-number@3.0.0:
|
||||
dependencies:
|
||||
kind-of: 3.2.2
|
||||
|
||||
kind-of@3.2.2:
|
||||
dependencies:
|
||||
is-buffer: 1.1.6
|
||||
|
||||
lru-cache@11.2.4: {}
|
||||
|
||||
mime-db@1.54.0: {}
|
||||
@@ -1561,9 +1486,4 @@ snapshots:
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
window-size@1.1.1:
|
||||
dependencies:
|
||||
define-property: 1.0.0
|
||||
is-number: 3.0.0
|
||||
|
||||
wrappy@1.0.2: {}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
onlyBuiltDependencies:
|
||||
- better-sqlite3
|
||||
- esbuild
|
||||
|
||||
92
src/playwright/browser.ts
Normal file
92
src/playwright/browser.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { spawn } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
|
||||
export const getExecutablePath = () => {
|
||||
// 根据不同平台返回 Chrome 的可执行文件路径
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
return 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe';
|
||||
case 'darwin':
|
||||
return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
||||
case 'linux':
|
||||
return '/usr/bin/google-chrome';
|
||||
default:
|
||||
throw new Error('Unsupported platform: ' + process.platform);
|
||||
}
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @param {*} opts
|
||||
* executablePath: 可执行文件路径
|
||||
* userDataDir: 用户数据目录
|
||||
* debugPort: 远程调试端口
|
||||
* kiosk: 是否全屏模式
|
||||
*
|
||||
* 启动 Chrome 浏览器,带远程调试端口
|
||||
* 注意:需要手动登录账号和安装插件
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const main = async (opts?: {
|
||||
executablePath?: string;
|
||||
userDataDir?: string;
|
||||
debugPort?: number;
|
||||
kiosk?: boolean;
|
||||
}) => {
|
||||
// Chrome 路径和配置
|
||||
const executablePath = opts?.executablePath || getExecutablePath();
|
||||
// 使用独立的用户数据目录,避免与 Chrome 冲突
|
||||
const userDataDir = opts?.userDataDir || path.join(process.cwd(), 'browser-context');
|
||||
const debugPort = opts?.debugPort || 9223;
|
||||
|
||||
console.log('启动 Chrome...', executablePath);
|
||||
console.log(`端口: ${debugPort}`);
|
||||
console.log(`用户数据目录: ${userDataDir}`);
|
||||
// console.log('注意:需要手动登录账号和安装插件');
|
||||
const params = [
|
||||
`--remote-debugging-port=${debugPort}`,
|
||||
`--user-data-dir=${userDataDir}`,
|
||||
// '--kiosk', // 全屏模式,无修改边框
|
||||
];
|
||||
console.log('启动参数:', params);
|
||||
if (opts?.kiosk) {
|
||||
params.push('--kiosk'); // 全屏模式,无修改边框
|
||||
}
|
||||
|
||||
// 确保用户数据目录存在
|
||||
try {
|
||||
await fs.promises.mkdir(userDataDir, { recursive: true });
|
||||
} catch (err) {
|
||||
console.error('创建用户数据目录失败:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查 Chrome 可执行文件是否存在
|
||||
if (!fs.existsSync(executablePath)) {
|
||||
console.error('Chrome 可执行文件不存在:', executablePath);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 启动 Chrome(带远程调试端口)
|
||||
const chromeProcess = spawn(
|
||||
executablePath,
|
||||
params,
|
||||
{
|
||||
windowsHide: true, // 隐藏 CMD 窗口
|
||||
detached: false,
|
||||
stdio: ['ignore', 'ignore', 'ignore'],
|
||||
},
|
||||
);
|
||||
|
||||
chromeProcess.on('error', (err) => {
|
||||
console.error('Chrome 启动失败:', err);
|
||||
// 需要重新启动
|
||||
});
|
||||
|
||||
chromeProcess.on('exit', (code, signal) => {
|
||||
console.log(`Chrome 进程退出,代码: ${code}, 信号: ${signal}`);
|
||||
});
|
||||
return chromeProcess;
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import { chromium, Page, BrowserContext, Browser, CDPSession, Request } from 'pl
|
||||
import { execSync } from 'node:child_process';
|
||||
import { EventEmitter } from 'eventemitter3'
|
||||
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
import { main } from "./browser.ts";
|
||||
|
||||
type RequestObject = {
|
||||
url: string;
|
||||
@@ -25,25 +26,33 @@ export class Core<T = {}> {
|
||||
browser: Browser | null = null;
|
||||
page: Page | null = null;
|
||||
debugPort = 9223;
|
||||
debugHost = '127.0.0.1';
|
||||
status: 'disconnected' | 'connecting' | 'connected' | 'failed' = 'disconnected';
|
||||
emitter = new EventEmitter();
|
||||
listeners: Listener[] = [];
|
||||
recordReady: boolean = false;
|
||||
data: T | null = null;
|
||||
constructor(opts?: { debugPort?: number, listeners?: Listener[] }) {
|
||||
constructor(opts?: { debugPort?: number, debugHost?: string, listeners?: Listener[] }) {
|
||||
if (opts?.debugPort) {
|
||||
this.debugPort = opts.debugPort;
|
||||
}
|
||||
if (opts?.debugHost) {
|
||||
this.debugHost = opts.debugHost;
|
||||
}
|
||||
if (opts?.listeners) {
|
||||
this.listeners = opts.listeners;
|
||||
}
|
||||
}
|
||||
async createBrowser() {
|
||||
await main({ debugPort: this.debugPort });
|
||||
}
|
||||
async init() {
|
||||
const debugPort = this.debugPort;
|
||||
try {
|
||||
const stdout = execSync(`netstat -ano | findstr :${debugPort}`);
|
||||
console.log(`端口 ${debugPort} 已在监听:\n${stdout}`);
|
||||
const browser = await chromium.connectOverCDP(`http://127.0.0.1:${debugPort}`);
|
||||
const debugHost = this.debugHost;
|
||||
const browser = await chromium.connectOverCDP(`http://${debugHost}:${debugPort}`);
|
||||
console.log('成功连接到 Chrome CDP!');
|
||||
this.browser = browser;
|
||||
this.browserContext = browser.contexts()[0];
|
||||
@@ -74,13 +83,18 @@ export class Core<T = {}> {
|
||||
}
|
||||
if (this.status === 'disconnected' || this.status === 'failed') {
|
||||
this.status = 'connecting';
|
||||
for (let i = 0; i < 10; i++) {
|
||||
for (let i = 0; i < 6; i++) {
|
||||
if (i === 3) {
|
||||
console.log('尝试启动浏览器实例...');
|
||||
await this.createBrowser();
|
||||
await sleep(5000);
|
||||
}
|
||||
try {
|
||||
await this.init();
|
||||
this.status = 'connected'
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.log(`尝试 ${i + 1}/10 连接失败: ${(e as Error).message.slice(0, 100)}`);
|
||||
console.log(`尝试 ${i + 1}/3 连接失败: ${(e as Error).message.slice(0, 100)}`);
|
||||
await sleep(2000);
|
||||
}
|
||||
}
|
||||
|
||||
25
src/routes/browser/browser.ts
Normal file
25
src/routes/browser/browser.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { app, core } from "../../app.ts";
|
||||
|
||||
|
||||
app.route({
|
||||
path: 'browser',
|
||||
key: 'getBrowser',
|
||||
middleware: ['auth'],
|
||||
description: '获取浏览器实例。',
|
||||
metadata: {
|
||||
note: "此接口用于获取当前浏览器实例的连接状态和版本信息。开发环境不要使用此接口。最好默认启动一个实例,使用pnpm browser",
|
||||
tags: ['浏览器操作', '核心功能'],
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
try {
|
||||
const browser = await core.getBrowser();
|
||||
const isConnected = browser.isConnected();
|
||||
const browserVersion = browser.version()
|
||||
ctx.body = {
|
||||
isConnected,
|
||||
version: browserVersion
|
||||
};
|
||||
} catch (error) {
|
||||
ctx.throw!(500, error.message);
|
||||
}
|
||||
}).addTo(app);
|
||||
@@ -1,2 +1,3 @@
|
||||
import './pane-manager.ts';
|
||||
import './page.ts';
|
||||
import './page.ts';
|
||||
import './browser.ts'
|
||||
@@ -7,7 +7,7 @@ app.route({
|
||||
middleware: ['auth'],
|
||||
description: '导航到指定页面。参数:url (string, 必需) - 目标 URL 地址',
|
||||
metadata: {
|
||||
tags: ['浏览器操作'],
|
||||
tags: ['浏览器操作', '核心功能'],
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const url = ctx.query?.url as string;
|
||||
|
||||
@@ -40,7 +40,7 @@ app.route({
|
||||
description: desc,
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['浏览器操作', '窗口管理'],
|
||||
tags: ['浏览器操作', '窗口管理', '核心功能'],
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const bounds = ctx.query as Bounds || {};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Command } from "commander";
|
||||
import { core, program, exit } from './common.ts'
|
||||
// import size from 'window-size'
|
||||
const main = async () => {
|
||||
const connected = await core.connect();
|
||||
if (!connected) {
|
||||
|
||||
@@ -1,45 +1,3 @@
|
||||
import { spawn } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import { main } from "./src/playwright/browser.ts";
|
||||
|
||||
export const main = async () => {
|
||||
// Chrome 路径和配置
|
||||
const executablePath = 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe';
|
||||
// 使用独立的用户数据目录,避免与 Chrome 冲突
|
||||
const userDataDir = path.join(process.cwd(), 'browser-context');
|
||||
const debugPort = 9223;
|
||||
|
||||
console.log('启动 Chrome...');
|
||||
console.log(`端口: ${debugPort}`);
|
||||
console.log(`用户数据目录: ${userDataDir}`);
|
||||
// console.log('注意:需要手动登录账号和安装插件');
|
||||
|
||||
// 启动 Chrome(带远程调试端口)
|
||||
const chromeProcess = spawn(
|
||||
executablePath,
|
||||
[
|
||||
`--remote-debugging-port=${debugPort}`,
|
||||
`--user-data-dir=${userDataDir}`,
|
||||
// '--kiosk', // 全屏模式,无修改边框
|
||||
],
|
||||
{
|
||||
windowsHide: true, // 隐藏 CMD 窗口
|
||||
detached: false,
|
||||
stdio: ['ignore', 'ignore', 'ignore'],
|
||||
},
|
||||
);
|
||||
|
||||
chromeProcess.on('error', (err) => {
|
||||
console.error('Chrome 启动失败:', err);
|
||||
// 需要重新启动
|
||||
});
|
||||
|
||||
chromeProcess.on('exit', (code, signal) => {
|
||||
console.log(`Chrome 进程退出,代码: ${code}, 信号: ${signal}`);
|
||||
if (code === 0) {
|
||||
// 重启
|
||||
main();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
main();
|
||||
await main()
|
||||
Reference in New Issue
Block a user