v0.4.1: Chrome cookie import, config.json, session persistence
- Add Chrome cookie importer (macOS Keychain AES-128-CBC decryption) - Add ~/.config/browse/config.json (headless, fullscreen, stealth, preview defaults) - Add ~/.config/browse/session.json (auto-save/restore cookies + localStorage) - Auto-import from all browsers (Safari + Firefox + Chrome) on first launch - Deduplicate cookies across browsers (domain+name+path key) - Defaults: headed, fullscreen, stealth, preview all enabled - Fix BigInt overflow for Chrome timestamps (CAST to TEXT in SQL)
This commit is contained in:
Vendored
+97
-10
@@ -6,19 +6,21 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import { z } from 'zod';
|
||||
import { ClaudeBrowser } from './browser.js';
|
||||
import { importAllToSession, loadConfig, loadSession, saveSession, } from './config.js';
|
||||
import * as image from './image.js';
|
||||
import { stderrLogger as log } from './logger.js';
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const pkg = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf-8'));
|
||||
// Browser options configurable via launch tool
|
||||
// Load user config from ~/.config/browse/config.json
|
||||
const userConfig = loadConfig();
|
||||
let browserOptions = {
|
||||
headless: true,
|
||||
width: 1280,
|
||||
height: 800,
|
||||
fullscreen: false,
|
||||
preview: false,
|
||||
previewDelay: 2000,
|
||||
stealth: false,
|
||||
headless: userConfig.headless,
|
||||
width: userConfig.width,
|
||||
height: userConfig.height,
|
||||
fullscreen: userConfig.fullscreen,
|
||||
preview: userConfig.preview,
|
||||
previewDelay: userConfig.previewDelay,
|
||||
stealth: userConfig.stealth,
|
||||
};
|
||||
let browser = new ClaudeBrowser(browserOptions);
|
||||
let launched = false;
|
||||
@@ -27,6 +29,56 @@ async function ensureLaunched() {
|
||||
if (!launched) {
|
||||
await browser.launch();
|
||||
launched = true;
|
||||
// Auto-restore session if enabled
|
||||
// If no session.json exists, import all browser cookies first
|
||||
if (userConfig.autoRestore) {
|
||||
let session = loadSession();
|
||||
if (!session) {
|
||||
try {
|
||||
const result = await importAllToSession();
|
||||
log.command({
|
||||
cmd: 'auto_import',
|
||||
url: `${result.total} cookies from ${Object.entries(result.sources)
|
||||
.filter(([, v]) => v > 0)
|
||||
.map(([k, v]) => `${k}:${v}`)
|
||||
.join(', ')}`,
|
||||
});
|
||||
session = loadSession();
|
||||
}
|
||||
catch (err) {
|
||||
log.result({ cmd: 'auto_import' }, { ok: false, error: err.message });
|
||||
}
|
||||
}
|
||||
if (session) {
|
||||
try {
|
||||
const context = browser.getContext();
|
||||
if (context && session.cookies?.length) {
|
||||
await context.addCookies(session.cookies);
|
||||
}
|
||||
const page = browser.getPage();
|
||||
if (page && session.url && session.url !== 'about:blank') {
|
||||
await page.goto(session.url, { waitUntil: 'domcontentloaded' });
|
||||
await Promise.race([
|
||||
page.waitForLoadState('networkidle'),
|
||||
page.waitForTimeout(5000),
|
||||
]).catch(() => { });
|
||||
// Restore localStorage/sessionStorage after navigation
|
||||
if (session.localStorage || session.sessionStorage) {
|
||||
const local = session.localStorage || {};
|
||||
const sessionStorage = session.sessionStorage || {};
|
||||
await page.evaluate(`((data) => {
|
||||
for (const [k, v] of Object.entries(data.local)) localStorage.setItem(k, v);
|
||||
for (const [k, v] of Object.entries(data.session)) sessionStorage.setItem(k, v);
|
||||
})({ local: ${JSON.stringify(local)}, session: ${JSON.stringify(sessionStorage)} })`);
|
||||
}
|
||||
}
|
||||
log.command({ cmd: 'auto_restore', url: session.url });
|
||||
}
|
||||
catch (err) {
|
||||
log.result({ cmd: 'auto_restore' }, { ok: false, error: err.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function textResult(text) {
|
||||
@@ -399,6 +451,41 @@ server.tool('wait', 'Wait for a specified time in milliseconds', { ms: z.number(
|
||||
// Session management
|
||||
server.tool('close', 'Close the browser and end the current session', {}, withLogging('close', async () => {
|
||||
if (launched) {
|
||||
// Auto-save session before closing
|
||||
if (userConfig.autoSave) {
|
||||
try {
|
||||
const page = browser.getPage();
|
||||
const context = browser.getContext();
|
||||
if (page && context) {
|
||||
const url = page.url();
|
||||
const title = await page.title();
|
||||
const cookies = await context.cookies();
|
||||
const storage = (await page.evaluate(`({
|
||||
localStorage: Object.fromEntries(
|
||||
Array.from({ length: localStorage.length }, (_, i) => localStorage.key(i))
|
||||
.filter(k => k !== null)
|
||||
.map(k => [k, localStorage.getItem(k) || ''])
|
||||
),
|
||||
sessionStorage: Object.fromEntries(
|
||||
Array.from({ length: sessionStorage.length }, (_, i) => sessionStorage.key(i))
|
||||
.filter(k => k !== null)
|
||||
.map(k => [k, sessionStorage.getItem(k) || ''])
|
||||
),
|
||||
})`));
|
||||
saveSession({
|
||||
url,
|
||||
title,
|
||||
cookies,
|
||||
localStorage: storage.localStorage,
|
||||
sessionStorage: storage.sessionStorage,
|
||||
});
|
||||
log.command({ cmd: 'auto_save', url });
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
log.result({ cmd: 'auto_save' }, { ok: false, error: err.message });
|
||||
}
|
||||
}
|
||||
await browser.close();
|
||||
launched = false;
|
||||
}
|
||||
@@ -481,8 +568,8 @@ server.tool('session_restore', 'Restore a previously saved session state from a
|
||||
}));
|
||||
}));
|
||||
// Browser import
|
||||
server.tool('import', 'Import cookies from Safari or Firefox browser. Safari requires Full Disk Access permission (macOS only). Firefox works on macOS, Linux, and Windows.', {
|
||||
source: z.enum(['safari', 'firefox']).describe('Browser to import from'),
|
||||
server.tool('import', 'Import cookies from Safari, Firefox, or Chrome browser. Safari requires Full Disk Access (macOS). Chrome requires Keychain access (macOS). Firefox works on all platforms.', {
|
||||
source: z.enum(['safari', 'firefox', 'chrome']).describe('Browser to import from'),
|
||||
domain: z
|
||||
.string()
|
||||
.optional()
|
||||
|
||||
Reference in New Issue
Block a user