feat: 非登录模式下浏览器崩溃时重启浏览器

This commit is contained in:
foxhui
2026-01-18 04:46:05 +08:00
Unverified
parent 2fcc90088b
commit 85be113b40
4 changed files with 148 additions and 5 deletions
+6
View File
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.4.8] - 2026-01-18
### 🔄 Changed
- **崩溃重启**
- 非登录模式下浏览器崩溃或者被关闭时不导致项目退出而是重启
## [3.4.7] - 2026-01-14
### ✨ Added
+4 -3
View File
@@ -331,11 +331,12 @@ export async function initBrowserBase(config, options = {}) {
// 注册清理处理器
registerCleanupHandlers();
// 注册断开连接事件
// 注册断开连接事件(不再自动退出进程,由 Worker 决定后续行为)
context.on('close', async () => {
logger.warn('浏览器', `[${markLabel}] 浏览器已断开连接`);
await cleanup();
process.exit(0);
// 清理全局状态,但不退出进程
globalContext = null;
globalBrowserProcess = null;
});
// 获取或创建 Page
+6 -1
View File
@@ -104,12 +104,17 @@ export class PoolManager {
logger.debug('工作池', `[${worker.name}] 将与其他 Worker 共享浏览器 (${worker.userDataDir})`);
await worker.init(existing.browser);
// 建立共享关系:设置所有者引用,并添加到所有者的共享列表
worker._browserOwner = existing.ownerWorker;
existing.ownerWorker._sharedWorkers.push(worker);
} else {
await worker.init();
browserMap.set(worker.userDataDir, {
browser: worker.browser,
proxyConfig: worker.proxyConfig,
firstWorkerName: worker.name
firstWorkerName: worker.name,
ownerWorker: worker // 保存所有者 Worker 引用
});
}
+132 -1
View File
@@ -35,6 +35,11 @@ export class Worker {
this.page = null;
this.busyCount = 0;
this.initialized = false;
// 浏览器所有权(用于共享浏览器场景的协调重启)
this._isBrowserOwner = false; // 是否是浏览器的所有者(负责重启)
this._browserOwner = null; // 如果是共享者,指向所有者 Worker
this._sharedWorkers = []; // 如果是所有者,保存共享该浏览器的 Worker 列表
}
/**
@@ -93,8 +98,10 @@ export class Worker {
if (sharedBrowser) {
await this._initWithSharedBrowser(sharedBrowser, targetUrl, navigationHandler);
this._isBrowserOwner = false;
} else {
await this._initNewBrowser(targetUrl, navigationHandler);
this._isBrowserOwner = true;
}
this.initialized = true;
@@ -111,6 +118,10 @@ export class Worker {
this.page.authState = { isHandlingAuth: false };
this.page.cursor = createCursor(this.page);
// 保存参数用于重新初始化
this._targetUrl = targetUrl;
this._navigationHandler = navigationHandler;
await this._navigateToTarget(targetUrl);
if (navigationHandler) {
@@ -119,9 +130,57 @@ export class Worker {
});
}
// 监听标签页关闭事件,自动重新创建(仅针对共享者)
this._registerPageCloseHandler();
logger.info('工作池', `[${this.name}] 初始化完成`);
}
/**
* 注册标签页关闭事件处理器
* @private
*/
_registerPageCloseHandler() {
if (!this.page) return;
this.page.on('close', async () => {
// 如果浏览器还在运行,说明只是标签页被关闭
if (this.browser && !this.browser.isClosed?.()) {
logger.warn('工作池', `[${this.name}] 标签页已关闭,正在重新创建...`);
this.initialized = false;
this.page = null;
try {
await this._recreatePage();
} catch (e) {
logger.error('工作池', `[${this.name}] 重新创建标签页失败: ${e.message}`);
}
}
});
}
/**
* 重新创建标签页(标签页关闭恢复)
* @private
*/
async _recreatePage() {
this.page = await this.browser.newPage();
this.page.authState = { isHandlingAuth: false };
this.page.cursor = createCursor(this.page);
await this._navigateToTarget(this._targetUrl || 'about:blank');
if (this._navigationHandler) {
this.page.on('framenavigated', async () => {
try { await this._navigationHandler(this.page); } catch (e) { /* ignore */ }
});
}
// 重新注册标签页关闭处理器
this._registerPageCloseHandler();
this.initialized = true;
logger.info('工作池', `[${this.name}] 标签页已成功重新创建`);
}
/**
* 启动新浏览器初始化
* @private
@@ -144,10 +203,14 @@ export class Worker {
});
}
// 保存 navigationHandler 用于重新初始化
this._navigationHandler = navigationHandler;
this._targetUrl = targetUrl;
logger.info('工作池', `[${this.name}] 正在连接目标页面...`);
await this._navigateToTarget(targetUrl);
// 登录模式:注册浏览器关闭事件(不阻塞)
// 登录模式:注册浏览器关闭事件(不阻塞,关闭后退出进程
const isLoginMode = process.argv.some(arg => arg.startsWith('-login'));
if (isLoginMode) {
logger.info('工作池', `[${this.name}] 登录模式已就绪,请在浏览器中完成登录`);
@@ -155,6 +218,48 @@ export class Worker {
logger.info('工作池', `[${this.name}] 浏览器已关闭,登录模式结束`);
process.exit(0);
});
} else {
// 非登录模式:注册断开事件,所有者负责重启并同步到共享者
this.browser.on('close', async () => {
logger.warn('工作池', `[${this.name}] 浏览器已断开连接,正在自动重新初始化...`);
// 标记自己和所有共享者为未初始化
this.initialized = false;
this.browser = null;
this.page = null;
for (const sharedWorker of this._sharedWorkers) {
sharedWorker.initialized = false;
sharedWorker.browser = null;
sharedWorker.page = null;
}
try {
// 重新初始化浏览器
await this._reinit();
// 为所有共享者创建新的标签页
for (const sharedWorker of this._sharedWorkers) {
try {
logger.info('工作池', `[${sharedWorker.name}] 正在恢复共享浏览器连接...`);
sharedWorker.browser = this.browser;
sharedWorker.page = await this.browser.newPage();
sharedWorker.page.authState = { isHandlingAuth: false };
sharedWorker.page.cursor = createCursor(sharedWorker.page);
await sharedWorker._navigateToTarget(sharedWorker._targetUrl || 'about:blank');
sharedWorker._registerPageCloseHandler(); // 重新注册标签页关闭处理器
sharedWorker.initialized = true;
logger.info('工作池', `[${sharedWorker.name}] 共享浏览器连接已恢复`);
} catch (e) {
logger.error('工作池', `[${sharedWorker.name}] 恢复共享浏览器连接失败: ${e.message}`);
}
}
} catch (e) {
logger.error('工作池', `[${this.name}] 自动重新初始化失败: ${e.message}`);
}
});
// 所有者也需要监听标签页关闭事件
this._registerPageCloseHandler();
}
logger.info('工作池', `[${this.name}] 初始化完成`);
@@ -330,6 +435,17 @@ export class Worker {
* @private
*/
async _executeAdapter(ctx, type, modelId, prompt, paths, meta) {
// 检查 Worker 是否已初始化(浏览器崩溃后会被标记为 false)
if (!this.initialized || !this.page || this.page.isClosed()) {
logger.info('工作池', `[${this.name}] 浏览器已断开,正在自动重新初始化...`, meta);
try {
await this._reinit();
} catch (e) {
logger.error('工作池', `[${this.name}] 重新初始化失败`, { error: e.message, ...meta });
return { error: `Worker 重新初始化失败: ${e.message}` };
}
}
const adapter = registry.getAdapter(type);
if (!adapter) {
return { error: `适配器不存在: ${type}` };
@@ -354,6 +470,21 @@ export class Worker {
}
}
/**
* 重新初始化浏览器(崩溃恢复)
* @private
*/
async _reinit() {
this.initialized = false;
this.browser = null;
this.page = null;
// 使用保存的参数重新初始化
await this._initNewBrowser(this._targetUrl || 'about:blank', this._navigationHandler || null);
this.initialized = true;
logger.info('工作池', `[${this.name}] 浏览器已成功重新初始化`);
}
/**
* 获取支持的模型列表
*/