feat: 新增浏览器用户数据目录多级兜底解析逻辑
This commit is contained in:
2
.env
2
.env
@@ -9,7 +9,9 @@ BROWSER_PATH=
|
|||||||
BROWSER_DEBUG_PORT=
|
BROWSER_DEBUG_PORT=
|
||||||
|
|
||||||
# 浏览器用户数据目录(保持登录态、cookies 等)
|
# 浏览器用户数据目录(保持登录态、cookies 等)
|
||||||
|
# 不设则自动解析:OpenClaw 状态 → 浏览器默认目录 → ~/.gemini-skill/browser-data
|
||||||
# Browser user data directory (persists login session, cookies, etc.)
|
# Browser user data directory (persists login session, cookies, etc.)
|
||||||
|
# Auto-resolves if not set: OpenClaw status → browser default dir → ~/.gemini-skill/browser-data
|
||||||
BROWSER_USER_DATA_DIR=
|
BROWSER_USER_DATA_DIR=
|
||||||
|
|
||||||
# 是否无头模式(true / false)
|
# 是否无头模式(true / false)
|
||||||
|
|||||||
129
src/browser.js
129
src/browser.js
@@ -16,8 +16,10 @@ import puppeteerCore from 'puppeteer-core';
|
|||||||
import { addExtra } from 'puppeteer-extra';
|
import { addExtra } from 'puppeteer-extra';
|
||||||
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
|
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
|
||||||
import { createConnection } from 'node:net';
|
import { createConnection } from 'node:net';
|
||||||
import { existsSync } from 'node:fs';
|
import { existsSync, mkdirSync } from 'node:fs';
|
||||||
import { platform } from 'node:os';
|
import { platform, homedir } from 'node:os';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { execFileSync } from 'node:child_process';
|
||||||
import config from './config.js';
|
import config from './config.js';
|
||||||
|
|
||||||
// ── 用 puppeteer-extra 包装 puppeteer-core,注入 stealth 插件 ──
|
// ── 用 puppeteer-extra 包装 puppeteer-core,注入 stealth 插件 ──
|
||||||
@@ -91,6 +93,120 @@ function detectBrowser() {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── userDataDir 兜底目录 ──
|
||||||
|
const SKILL_FALLBACK_DATA_DIR = join(homedir(), '.gemini-skill', 'browser-data');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试从 OpenClaw 获取 userDataDir
|
||||||
|
*
|
||||||
|
* 执行: openclaw browser --browser-profile openclaw status --json
|
||||||
|
* 解析返回的 JSON 中的 userDataDir 字段
|
||||||
|
*
|
||||||
|
* @returns {string | undefined}
|
||||||
|
*/
|
||||||
|
function getOpenClawUserDataDir() {
|
||||||
|
try {
|
||||||
|
const stdout = execFileSync('openclaw', [
|
||||||
|
'browser', '--browser-profile', 'openclaw', 'status', '--json',
|
||||||
|
], { timeout: 5000, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
||||||
|
|
||||||
|
const json = JSON.parse(stdout);
|
||||||
|
if (json.userDataDir && typeof json.userDataDir === 'string') {
|
||||||
|
console.log('[browser] got userDataDir from OpenClaw:', json.userDataDir);
|
||||||
|
return json.userDataDir;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// openclaw 不存在或执行失败,静默跳过
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取浏览器默认 userDataDir 路径(不同浏览器/平台)
|
||||||
|
*
|
||||||
|
* @returns {string | undefined}
|
||||||
|
*/
|
||||||
|
function getDefaultBrowserDataDir() {
|
||||||
|
const os = platform();
|
||||||
|
const home = homedir();
|
||||||
|
|
||||||
|
const candidates = [];
|
||||||
|
|
||||||
|
if (os === 'win32') {
|
||||||
|
const localAppData = process.env.LOCALAPPDATA || join(home, 'AppData', 'Local');
|
||||||
|
candidates.push(
|
||||||
|
join(localAppData, 'Google', 'Chrome', 'User Data'),
|
||||||
|
join(localAppData, 'Microsoft', 'Edge', 'User Data'),
|
||||||
|
join(localAppData, 'Chromium', 'User Data'),
|
||||||
|
);
|
||||||
|
} else if (os === 'darwin') {
|
||||||
|
const lib = join(home, 'Library', 'Application Support');
|
||||||
|
candidates.push(
|
||||||
|
join(lib, 'Google', 'Chrome'),
|
||||||
|
join(lib, 'Microsoft Edge'),
|
||||||
|
join(lib, 'Chromium'),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Linux
|
||||||
|
candidates.push(
|
||||||
|
join(home, '.config', 'google-chrome'),
|
||||||
|
join(home, '.config', 'microsoft-edge'),
|
||||||
|
join(home, '.config', 'chromium'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const dir of candidates) {
|
||||||
|
if (existsSync(dir)) {
|
||||||
|
console.log('[browser] found default browser data dir:', dir);
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多级兜底解析 userDataDir
|
||||||
|
*
|
||||||
|
* 优先级:
|
||||||
|
* 1. 环境变量 BROWSER_USER_DATA_DIR(config 已处理)
|
||||||
|
* 2. OpenClaw 运行状态中的 userDataDir
|
||||||
|
* 3. 浏览器默认 userDataDir(Chrome > Edge > Chromium)
|
||||||
|
* 4. Skill 内部创建 ~/.gemini-skill/browser-data(兜底 + warning)
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function resolveUserDataDir() {
|
||||||
|
// 1. 环境变量(已由 config 读取)
|
||||||
|
if (config.browserUserDataDir) {
|
||||||
|
return config.browserUserDataDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. OpenClaw
|
||||||
|
const openclawDir = getOpenClawUserDataDir();
|
||||||
|
if (openclawDir) {
|
||||||
|
return openclawDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 浏览器默认目录
|
||||||
|
const defaultDir = getDefaultBrowserDataDir();
|
||||||
|
if (defaultDir) {
|
||||||
|
return defaultDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Skill 兜底
|
||||||
|
console.warn(
|
||||||
|
`[browser] ⚠ 未找到任何已有的 userDataDir,将使用 skill 内部目录:${SKILL_FALLBACK_DATA_DIR}\n` +
|
||||||
|
` 建议通过以下方式指定:\n` +
|
||||||
|
` - 设置环境变量 BROWSER_USER_DATA_DIR\n` +
|
||||||
|
` - 安装 OpenClaw 并配置 browser profile`
|
||||||
|
);
|
||||||
|
if (!existsSync(SKILL_FALLBACK_DATA_DIR)) {
|
||||||
|
mkdirSync(SKILL_FALLBACK_DATA_DIR, { recursive: true });
|
||||||
|
}
|
||||||
|
return SKILL_FALLBACK_DATA_DIR;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 探测指定端口是否有浏览器在监听
|
* 探测指定端口是否有浏览器在监听
|
||||||
* @param {number} port
|
* @param {number} port
|
||||||
@@ -225,10 +341,13 @@ async function findOrCreateGeminiPage(browser) {
|
|||||||
* 2. 检查端口是否有浏览器 → connect
|
* 2. 检查端口是否有浏览器 → connect
|
||||||
* 3. 否则自动检测 / 使用配置的路径启动浏览器
|
* 3. 否则自动检测 / 使用配置的路径启动浏览器
|
||||||
*
|
*
|
||||||
|
* userDataDir 解析优先级:
|
||||||
|
* opts.userDataDir > env BROWSER_USER_DATA_DIR > OpenClaw > 浏览器默认 > skill 兜底
|
||||||
|
*
|
||||||
* @param {object} [opts]
|
* @param {object} [opts]
|
||||||
* @param {string} [opts.executablePath] - 浏览器路径(仅 launch 时需要,不传则自动检测)
|
* @param {string} [opts.executablePath] - 浏览器路径(不传则自动检测)
|
||||||
* @param {number} [opts.port] - 调试端口(env: BROWSER_DEBUG_PORT,默认 9222)
|
* @param {number} [opts.port] - 调试端口(env: BROWSER_DEBUG_PORT,默认 9222)
|
||||||
* @param {string} [opts.userDataDir] - 用户数据目录(env: BROWSER_USER_DATA_DIR)
|
* @param {string} [opts.userDataDir] - 用户数据目录(env: BROWSER_USER_DATA_DIR,不传则多级兜底)
|
||||||
* @param {boolean} [opts.headless] - 无头模式(env: BROWSER_HEADLESS,默认 false)
|
* @param {boolean} [opts.headless] - 无头模式(env: BROWSER_HEADLESS,默认 false)
|
||||||
* @returns {Promise<{browser: import('puppeteer-core').Browser, page: import('puppeteer-core').Page}>}
|
* @returns {Promise<{browser: import('puppeteer-core').Browser, page: import('puppeteer-core').Page}>}
|
||||||
*/
|
*/
|
||||||
@@ -236,7 +355,7 @@ export async function ensureBrowser(opts = {}) {
|
|||||||
const {
|
const {
|
||||||
executablePath = config.browserPath,
|
executablePath = config.browserPath,
|
||||||
port = config.browserDebugPort,
|
port = config.browserDebugPort,
|
||||||
userDataDir = config.browserUserDataDir,
|
userDataDir = resolveUserDataDir(),
|
||||||
headless = config.browserHeadless,
|
headless = config.browserHeadless,
|
||||||
} = opts;
|
} = opts;
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,7 @@
|
|||||||
* 2. .env 文件(需调用方自行加载,如 dotenv)
|
* 2. .env 文件(需调用方自行加载,如 dotenv)
|
||||||
* 3. 代码默认值
|
* 3. 代码默认值
|
||||||
*/
|
*/
|
||||||
import { homedir } from 'node:os';
|
import { resolve } from 'node:path';
|
||||||
import { join, resolve } from 'node:path';
|
|
||||||
|
|
||||||
const env = process.env;
|
const env = process.env;
|
||||||
|
|
||||||
@@ -44,11 +43,8 @@ const config = {
|
|||||||
/** CDP 远程调试端口 */
|
/** CDP 远程调试端口 */
|
||||||
browserDebugPort: envInt('BROWSER_DEBUG_PORT', 9222),
|
browserDebugPort: envInt('BROWSER_DEBUG_PORT', 9222),
|
||||||
|
|
||||||
/** 浏览器用户数据目录 */
|
/** 浏览器用户数据目录(不设则自动解析,见 browser.js resolveUserDataDir) */
|
||||||
browserUserDataDir: envStr(
|
browserUserDataDir: envStr('BROWSER_USER_DATA_DIR', undefined),
|
||||||
'BROWSER_USER_DATA_DIR',
|
|
||||||
join(homedir(), '.gemini-skill', 'browser-data'),
|
|
||||||
),
|
|
||||||
|
|
||||||
/** 是否无头模式 */
|
/** 是否无头模式 */
|
||||||
browserHeadless: envBool('BROWSER_HEADLESS', false),
|
browserHeadless: envBool('BROWSER_HEADLESS', false),
|
||||||
|
|||||||
Reference in New Issue
Block a user