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
+165
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Browse configuration and session persistence
|
||||
*
|
||||
* Config: ~/.config/browse/config.json — user defaults for launch options
|
||||
* Session: ~/.config/browse/session.json — persistent cookies, storage, last URL
|
||||
*
|
||||
* Config is loaded once at startup and merged under explicit tool args.
|
||||
* Session is auto-saved on close and auto-restored on launch (if present).
|
||||
*/
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { homedir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
const CONFIG_DIR = join(homedir(), '.config', 'browse');
|
||||
const CONFIG_PATH = join(CONFIG_DIR, 'config.json');
|
||||
const SESSION_PATH = join(CONFIG_DIR, 'session.json');
|
||||
const DEFAULTS = {
|
||||
headless: false,
|
||||
width: 1280,
|
||||
height: 800,
|
||||
fullscreen: true,
|
||||
preview: true,
|
||||
previewDelay: 2000,
|
||||
stealth: true,
|
||||
autoRestore: true,
|
||||
autoSave: true,
|
||||
importFrom: 'safari',
|
||||
importDomain: '',
|
||||
};
|
||||
function ensureDir() {
|
||||
if (!existsSync(CONFIG_DIR)) {
|
||||
mkdirSync(CONFIG_DIR, { recursive: true });
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Load config from ~/.config/browse/config.json
|
||||
* Returns defaults merged with user config. Missing file = all defaults.
|
||||
*/
|
||||
export function loadConfig() {
|
||||
if (!existsSync(CONFIG_PATH))
|
||||
return { ...DEFAULTS };
|
||||
try {
|
||||
const raw = readFileSync(CONFIG_PATH, 'utf-8');
|
||||
const user = JSON.parse(raw);
|
||||
return { ...DEFAULTS, ...user };
|
||||
}
|
||||
catch {
|
||||
return { ...DEFAULTS };
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Save config to ~/.config/browse/config.json
|
||||
*/
|
||||
export function saveConfig(config) {
|
||||
ensureDir();
|
||||
writeFileSync(CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`);
|
||||
}
|
||||
/**
|
||||
* Get the config file path (for display/debugging)
|
||||
*/
|
||||
export function getConfigPath() {
|
||||
return CONFIG_PATH;
|
||||
}
|
||||
/**
|
||||
* Load session from ~/.config/browse/session.json
|
||||
* Returns null if no session file exists.
|
||||
*/
|
||||
export function loadSession() {
|
||||
if (!existsSync(SESSION_PATH))
|
||||
return null;
|
||||
try {
|
||||
const raw = readFileSync(SESSION_PATH, 'utf-8');
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Save session to ~/.config/browse/session.json
|
||||
*/
|
||||
export function saveSession(session) {
|
||||
ensureDir();
|
||||
session.savedAt = new Date().toISOString();
|
||||
writeFileSync(SESSION_PATH, `${JSON.stringify(session, null, 2)}\n`);
|
||||
}
|
||||
/**
|
||||
* Delete the session file
|
||||
*/
|
||||
export function clearSession() {
|
||||
if (existsSync(SESSION_PATH)) {
|
||||
const { unlinkSync } = require('node:fs');
|
||||
unlinkSync(SESSION_PATH);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the session file path (for display/debugging)
|
||||
*/
|
||||
export function getSessionPath() {
|
||||
return SESSION_PATH;
|
||||
}
|
||||
/**
|
||||
* Import cookies from all available browsers, deduplicate, and save to session.json.
|
||||
* Dedup key: domain + name + path. Last-write wins (Chrome > Firefox > Safari priority).
|
||||
* Returns the merged cookie count.
|
||||
*/
|
||||
export async function importAllToSession() {
|
||||
const all = [];
|
||||
const sources = {};
|
||||
// Safari (async — binary parser)
|
||||
try {
|
||||
const { importSafariCookies, toPlaywrightCookie } = await import('./safari.js');
|
||||
const cookies = await importSafariCookies();
|
||||
const converted = cookies.map(toPlaywrightCookie);
|
||||
all.push(...converted);
|
||||
sources.safari = cookies.length;
|
||||
}
|
||||
catch {
|
||||
sources.safari = 0;
|
||||
}
|
||||
// Firefox (sync — SQLite)
|
||||
try {
|
||||
const { importFirefoxCookies, listFirefoxProfiles, toPlaywrightCookie } = await import('./firefox.js');
|
||||
// Try default-release first (main profile on macOS), fall back to default
|
||||
const profiles = listFirefoxProfiles();
|
||||
const profile = profiles.find((p) => p.name === 'default-release') ||
|
||||
profiles.find((p) => p.isDefault) ||
|
||||
profiles[0];
|
||||
if (profile) {
|
||||
const cookies = importFirefoxCookies({ profile: profile.name });
|
||||
const converted = cookies.map(toPlaywrightCookie);
|
||||
all.push(...converted);
|
||||
sources.firefox = cookies.length;
|
||||
}
|
||||
else {
|
||||
sources.firefox = 0;
|
||||
}
|
||||
}
|
||||
catch {
|
||||
sources.firefox = 0;
|
||||
}
|
||||
// Chrome (sync — SQLite + Keychain decryption, macOS only)
|
||||
try {
|
||||
const { importChromeCookies, toPlaywrightCookie } = await import('./chrome.js');
|
||||
const cookies = importChromeCookies();
|
||||
const converted = cookies.map(toPlaywrightCookie);
|
||||
all.push(...converted);
|
||||
sources.chrome = cookies.length;
|
||||
}
|
||||
catch {
|
||||
sources.chrome = 0;
|
||||
}
|
||||
// Deduplicate: last-write wins (Chrome overwrites Firefox overwrites Safari)
|
||||
const seen = new Map();
|
||||
for (const cookie of all) {
|
||||
const key = `${cookie.domain}|${cookie.name}|${cookie.path}`;
|
||||
seen.set(key, cookie);
|
||||
}
|
||||
const deduped = [...seen.values()];
|
||||
// Save to session.json
|
||||
saveSession({
|
||||
cookies: deduped,
|
||||
});
|
||||
return { total: deduped.length, sources };
|
||||
}
|
||||
//# sourceMappingURL=config.js.map
|
||||
Reference in New Issue
Block a user