v0.4.1: Chrome cookie import, config.json, session persistence
CI / test (push) Has been cancelled
style / style (push) Has been cancelled

- 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:
marauder-actual
2026-06-06 16:07:12 +02:00
parent 16bc55bcb9
commit 5e1375f1a1
26 changed files with 1343 additions and 31 deletions
+165
View File
@@ -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