diff --git a/src/browser.js b/src/browser.js index e56f10f..67e0ed5 100644 --- a/src/browser.js +++ b/src/browser.js @@ -286,9 +286,19 @@ async function connectBrowser(port) { * @param {number} opts.port * @param {string} opts.userDataDir * @param {boolean} opts.headless + * @param {object} [opts.debugOpts] - 调试/信号控制选项 + * @param {boolean} [opts.debugOpts.handleSIGINT=true] - Puppeteer 是否在 SIGINT 时自动关闭浏览器 + * @param {boolean} [opts.debugOpts.handleSIGTERM=true] - Puppeteer 是否在 SIGTERM 时自动关闭浏览器 + * @param {boolean} [opts.debugOpts.handleSIGHUP=true] - Puppeteer 是否在 SIGHUP 时自动关闭浏览器 * @returns {Promise} */ -async function launchBrowser({ executablePath, port, userDataDir, headless }) { +async function launchBrowser({ executablePath, port, userDataDir, headless, debugOpts = {} }) { + const { + handleSIGINT = true, + handleSIGTERM = true, + handleSIGHUP = true, + } = debugOpts; + const browser = await puppeteer.launch({ executablePath, headless, @@ -300,6 +310,9 @@ async function launchBrowser({ executablePath, port, userDataDir, headless }) { ], ignoreDefaultArgs: ['--enable-automation'], protocolTimeout: config.browserProtocolTimeout, + handleSIGINT, + handleSIGTERM, + handleSIGHUP, }); console.log('[browser] launched, pid:', browser.process()?.pid, 'port:', port, 'path:', executablePath); return browser; @@ -349,6 +362,7 @@ async function findOrCreateGeminiPage(browser) { * @param {number} [opts.port] - 调试端口(env: BROWSER_DEBUG_PORT,默认 9222) * @param {string} [opts.userDataDir] - 用户数据目录(env: BROWSER_USER_DATA_DIR,不传则多级兜底) * @param {boolean} [opts.headless] - 无头模式(env: BROWSER_HEADLESS,默认 false) + * @param {object} [opts.debugOpts] - 调试/信号控制选项(透传给 Puppeteer launch) * @returns {Promise<{browser: import('puppeteer-core').Browser, page: import('puppeteer-core').Page}>} */ export async function ensureBrowser(opts = {}) { @@ -357,6 +371,7 @@ export async function ensureBrowser(opts = {}) { port = config.browserDebugPort, userDataDir = resolveUserDataDir(), headless = config.browserHeadless, + debugOpts, } = opts; // 1. 复用已有连接 @@ -393,7 +408,7 @@ export async function ensureBrowser(opts = {}) { } try { - _browser = await launchBrowser({ executablePath: resolvedPath, port, userDataDir, headless }); + _browser = await launchBrowser({ executablePath: resolvedPath, port, userDataDir, headless, debugOpts }); } catch (err) { // 大概率是用户数据目录被正在运行的浏览器锁住了 if (err.message?.includes('EPERM') || err.message?.includes('lock') || err.message?.includes('already')) { diff --git a/src/demo.js b/src/demo.js index e00ce1f..566ccec 100644 --- a/src/demo.js +++ b/src/demo.js @@ -50,8 +50,18 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); * 创建会话,如果因浏览器目录被锁而失败,自动杀掉全部浏览器进程后重试一次 */ async function createSessionWithRetry() { + // 禁止 Puppeteer 在 Ctrl+C 等信号时自动杀浏览器进程, + // 由 demo 自己处理 SIGINT → disconnect,浏览器保持运行可复用。 + const opts = { + debugOpts: { + handleSIGINT: false, + handleSIGTERM: false, + handleSIGHUP: false, + }, + }; + try { - return await createGeminiSession(); + return await createGeminiSession(opts); } catch (err) { const msg = err.message || ''; const isLocked = msg.includes('EPERM') || msg.includes('lock') || msg.includes('already'); @@ -67,7 +77,7 @@ async function createSessionWithRetry() { await sleep(2000); // 重试一次,还失败就直接抛出 - return await createGeminiSession(); + return await createGeminiSession(opts); } } @@ -77,6 +87,13 @@ async function main() { // 创建会话:自带杀进程重试逻辑 const { ops } = await createSessionWithRetry(); + // ── Ctrl+C 时只断开连接,不杀浏览器进程(下次可复用) ── + process.on('SIGINT', () => { + console.log('\n[demo] Ctrl+C 收到,断开浏览器连接(浏览器保持运行)...'); + disconnect(); + process.exit(0); + }); + try { // 1. 探测页面状态 console.log('[1] 探测页面元素...'); diff --git a/src/index.js b/src/index.js index aa32188..86f6b2c 100644 --- a/src/index.js +++ b/src/index.js @@ -30,6 +30,10 @@ export { disconnect, close }; * @param {number} [opts.port] - 调试端口(env: BROWSER_DEBUG_PORT,默认 9222) * @param {string} [opts.userDataDir] - 用户数据目录(env: BROWSER_USER_DATA_DIR) * @param {boolean} [opts.headless] - 无头模式(env: BROWSER_HEADLESS,默认 false) + * @param {object} [opts.debugOpts] - 调试/信号控制选项(透传给 Puppeteer launch) + * @param {boolean} [opts.debugOpts.handleSIGINT=true] - Puppeteer 是否在 SIGINT 时自动关闭浏览器 + * @param {boolean} [opts.debugOpts.handleSIGTERM=true] - Puppeteer 是否在 SIGTERM 时自动关闭浏览器 + * @param {boolean} [opts.debugOpts.handleSIGHUP=true] - Puppeteer 是否在 SIGHUP 时自动关闭浏览器 * @returns {Promise<{ops: ReturnType, page: import('puppeteer-core').Page, browser: import('puppeteer-core').Browser}>} */ export async function createGeminiSession(opts = {}) {