mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-21 20:28:24 +08:00
feat: wire cookie-import-browser into browse server
Add cookie-picker route dispatch (no auth, localhost-only), add cookie-import-browser to WRITE_COMMANDS and CHAIN_WRITE, add serverPort property to BrowserManager, add write command with two modes (picker UI vs --domain direct import), update CLI help text.
This commit is contained in:
@@ -27,6 +27,9 @@ export class BrowserManager {
|
|||||||
private extraHeaders: Record<string, string> = {};
|
private extraHeaders: Record<string, string> = {};
|
||||||
private customUserAgent: string | null = null;
|
private customUserAgent: string | null = null;
|
||||||
|
|
||||||
|
/** Server port — set after server starts, used by cookie-import-browser command */
|
||||||
|
public serverPort: number = 0;
|
||||||
|
|
||||||
// ─── Ref Map (snapshot → @e1, @e2, @c1, @c2, ...) ────────
|
// ─── Ref Map (snapshot → @e1, @e2, @c1, @c2, ...) ────────
|
||||||
private refMap: Map<string, Locator> = new Map();
|
private refMap: Map<string, Locator> = new Map();
|
||||||
|
|
||||||
|
|||||||
@@ -188,6 +188,7 @@ Interaction: click <sel> | fill <sel> <val> | select <sel> <val>
|
|||||||
scroll [sel] | wait <sel|--networkidle|--load> | viewport <WxH>
|
scroll [sel] | wait <sel|--networkidle|--load> | viewport <WxH>
|
||||||
upload <sel> <file1> [file2...]
|
upload <sel> <file1> [file2...]
|
||||||
cookie-import <json-file>
|
cookie-import <json-file>
|
||||||
|
cookie-import-browser [browser] [--domain <d>]
|
||||||
Inspection: js <expr> | eval <file> | css <sel> <prop> | attrs <sel>
|
Inspection: js <expr> | eval <file> | css <sel> <prop> | attrs <sel>
|
||||||
console [--clear|--errors] | network [--clear] | dialog [--clear]
|
console [--clear|--errors] | network [--clear] | dialog [--clear]
|
||||||
cookies | storage [set <k> <v>] | perf
|
cookies | storage [set <k> <v>] | perf
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const CHAIN_WRITE = new Set([
|
|||||||
'click', 'fill', 'select', 'hover', 'type', 'press', 'scroll', 'wait',
|
'click', 'fill', 'select', 'hover', 'type', 'press', 'scroll', 'wait',
|
||||||
'viewport', 'cookie', 'header', 'useragent',
|
'viewport', 'cookie', 'header', 'useragent',
|
||||||
'upload', 'dialog-accept', 'dialog-dismiss',
|
'upload', 'dialog-accept', 'dialog-dismiss',
|
||||||
|
'cookie-import-browser',
|
||||||
]);
|
]);
|
||||||
const CHAIN_META = new Set([
|
const CHAIN_META = new Set([
|
||||||
'tabs', 'tab', 'newtab', 'closetab',
|
'tabs', 'tab', 'newtab', 'closetab',
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { BrowserManager } from './browser-manager';
|
|||||||
import { handleReadCommand } from './read-commands';
|
import { handleReadCommand } from './read-commands';
|
||||||
import { handleWriteCommand } from './write-commands';
|
import { handleWriteCommand } from './write-commands';
|
||||||
import { handleMetaCommand } from './meta-commands';
|
import { handleMetaCommand } from './meta-commands';
|
||||||
|
import { handleCookiePickerRoute } from './cookie-picker-routes';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
@@ -115,7 +116,7 @@ export const READ_COMMANDS = new Set([
|
|||||||
export const WRITE_COMMANDS = new Set([
|
export const WRITE_COMMANDS = new Set([
|
||||||
'goto', 'back', 'forward', 'reload',
|
'goto', 'back', 'forward', 'reload',
|
||||||
'click', 'fill', 'select', 'hover', 'type', 'press', 'scroll', 'wait',
|
'click', 'fill', 'select', 'hover', 'type', 'press', 'scroll', 'wait',
|
||||||
'viewport', 'cookie', 'cookie-import', 'header', 'useragent',
|
'viewport', 'cookie', 'cookie-import', 'cookie-import-browser', 'header', 'useragent',
|
||||||
'upload', 'dialog-accept', 'dialog-dismiss',
|
'upload', 'dialog-accept', 'dialog-dismiss',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -264,6 +265,11 @@ async function start() {
|
|||||||
|
|
||||||
const url = new URL(req.url);
|
const url = new URL(req.url);
|
||||||
|
|
||||||
|
// Cookie picker routes — no auth required (localhost-only)
|
||||||
|
if (url.pathname.startsWith('/cookie-picker')) {
|
||||||
|
return handleCookiePickerRoute(url, req, browserManager);
|
||||||
|
}
|
||||||
|
|
||||||
// Health check — no auth required (now async)
|
// Health check — no auth required (now async)
|
||||||
if (url.pathname === '/health') {
|
if (url.pathname === '/health') {
|
||||||
const healthy = await browserManager.isHealthy();
|
const healthy = await browserManager.isHealthy();
|
||||||
@@ -305,6 +311,7 @@ async function start() {
|
|||||||
};
|
};
|
||||||
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), { mode: 0o600 });
|
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), { mode: 0o600 });
|
||||||
|
|
||||||
|
browserManager.serverPort = port;
|
||||||
console.log(`[browse] Server running on http://127.0.0.1:${port} (PID: ${process.pid})`);
|
console.log(`[browse] Server running on http://127.0.0.1:${port} (PID: ${process.pid})`);
|
||||||
console.log(`[browse] State file: ${STATE_FILE}`);
|
console.log(`[browse] State file: ${STATE_FILE}`);
|
||||||
console.log(`[browse] Idle timeout: ${IDLE_TIMEOUT_MS / 1000}s`);
|
console.log(`[browse] Idle timeout: ${IDLE_TIMEOUT_MS / 1000}s`);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { BrowserManager } from './browser-manager';
|
import type { BrowserManager } from './browser-manager';
|
||||||
|
import { findInstalledBrowsers, importCookies } from './cookie-import-browser';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
@@ -253,6 +254,45 @@ export async function handleWriteCommand(
|
|||||||
return `Loaded ${cookies.length} cookies from ${filePath}`;
|
return `Loaded ${cookies.length} cookies from ${filePath}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'cookie-import-browser': {
|
||||||
|
// Two modes:
|
||||||
|
// 1. Direct CLI import: cookie-import-browser <browser> --domain <domain>
|
||||||
|
// 2. Open picker UI: cookie-import-browser [browser]
|
||||||
|
const browserArg = args[0];
|
||||||
|
const domainIdx = args.indexOf('--domain');
|
||||||
|
|
||||||
|
if (domainIdx !== -1 && domainIdx + 1 < args.length) {
|
||||||
|
// Direct import mode — no UI
|
||||||
|
const domain = args[domainIdx + 1];
|
||||||
|
const browser = browserArg || 'comet';
|
||||||
|
const result = await importCookies(browser, [domain]);
|
||||||
|
if (result.cookies.length > 0) {
|
||||||
|
await page.context().addCookies(result.cookies);
|
||||||
|
}
|
||||||
|
const msg = [`Imported ${result.count} cookies for ${domain} from ${browser}`];
|
||||||
|
if (result.failed > 0) msg.push(`(${result.failed} failed to decrypt)`);
|
||||||
|
return msg.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Picker UI mode — open in user's browser
|
||||||
|
const port = bm.serverPort;
|
||||||
|
if (!port) throw new Error('Server port not available');
|
||||||
|
|
||||||
|
const browsers = findInstalledBrowsers();
|
||||||
|
if (browsers.length === 0) {
|
||||||
|
throw new Error('No Chromium browsers found. Supported: Comet, Chrome, Arc, Brave, Edge');
|
||||||
|
}
|
||||||
|
|
||||||
|
const pickerUrl = `http://127.0.0.1:${port}/cookie-picker`;
|
||||||
|
try {
|
||||||
|
Bun.spawn(['open', pickerUrl], { stdout: 'ignore', stderr: 'ignore' });
|
||||||
|
} catch {
|
||||||
|
// open may fail silently — URL is in the message below
|
||||||
|
}
|
||||||
|
|
||||||
|
return `Cookie picker opened at ${pickerUrl}\nDetected browsers: ${browsers.map(b => b.name).join(', ')}\nSelect domains to import, then close the picker when done.`;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown write command: ${command}`);
|
throw new Error(`Unknown write command: ${command}`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user