Use full Chrome for interactive Reddit login
This commit is contained in:
1 parent
70d62b2da3
commit
674cd89cd7
5 files changed
+241
-3
No files matched your search
+69
-3
@@ -4,6 +4,25 @@ import type { Logger } from '../utils/logger.js';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
export interface ConnectedBrowser {
|
||||
contexts(): BrowserContext[];
|
||||
close(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface BrowserRuntime {
|
||||
connectOverCDP(endpoint: string): Promise<ConnectedBrowser>;
|
||||
launchPersistentContext(
|
||||
userDataDir: string,
|
||||
options: Parameters<typeof chromium.launchPersistentContext>[1]
|
||||
): Promise<BrowserContext>;
|
||||
}
|
||||
|
||||
const defaultBrowserRuntime: BrowserRuntime = {
|
||||
connectOverCDP: (endpoint) => chromium.connectOverCDP(endpoint),
|
||||
launchPersistentContext: (userDataDir, options) =>
|
||||
chromium.launchPersistentContext(userDataDir, options),
|
||||
};
|
||||
|
||||
export type RedditSessionState =
|
||||
| 'unknown'
|
||||
| 'authenticated'
|
||||
@@ -19,13 +38,16 @@ export function classifyRedditSessionError(error: unknown): Exclude<RedditSessio
|
||||
|
||||
export class BrowserClient {
|
||||
private context: BrowserContext | null = null;
|
||||
private connectedBrowser: ConnectedBrowser | null = null;
|
||||
private usingExternalChrome = false;
|
||||
private sessionDir: string | null = null;
|
||||
private sessionValidityInFlight: Promise<boolean> | null = null;
|
||||
private sessionState: RedditSessionState = 'unknown';
|
||||
|
||||
constructor(
|
||||
private config: Config,
|
||||
private logger: Logger
|
||||
private logger: Logger,
|
||||
private browserRuntime: BrowserRuntime = defaultBrowserRuntime
|
||||
) { }
|
||||
|
||||
async initialize(): Promise<BrowserContext> {
|
||||
@@ -37,10 +59,31 @@ export class BrowserClient {
|
||||
fs.mkdirSync(sessionDir, { recursive: true });
|
||||
}
|
||||
|
||||
this.logger.info(`Launching browser with session dir: ${sessionDir}`);
|
||||
const cdpEndpoint = process.env.CHROME_CDP_URL?.trim();
|
||||
if (cdpEndpoint) {
|
||||
try {
|
||||
this.logger.info(`Attaching to independently launched Google Chrome at ${cdpEndpoint}`);
|
||||
const browser = await this.browserRuntime.connectOverCDP(cdpEndpoint);
|
||||
const context = browser.contexts()[0];
|
||||
if (!context) {
|
||||
await browser.close().catch(() => undefined);
|
||||
throw new Error('External Chrome did not expose a default browser context');
|
||||
}
|
||||
|
||||
this.connectedBrowser = browser;
|
||||
this.usingExternalChrome = true;
|
||||
this.context = context;
|
||||
this.logger.info('Attached to Google Chrome; interactive login is available through VNC');
|
||||
return context;
|
||||
} catch (error) {
|
||||
this.logger.warn('Could not attach to Google Chrome; falling back to Playwright browser launch:', error);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.info(`Launching fallback Playwright browser with session dir: ${sessionDir}`);
|
||||
|
||||
// Use launchPersistentContext to maintain cookies/localStorage
|
||||
this.context = await chromium.launchPersistentContext(sessionDir, {
|
||||
this.context = await this.browserRuntime.launchPersistentContext(sessionDir, {
|
||||
headless: this.config.browser.headless,
|
||||
viewport: { width: 1280, height: 720 },
|
||||
userAgent: this.config.reddit.userAgent,
|
||||
@@ -70,6 +113,15 @@ export class BrowserClient {
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
if (this.connectedBrowser) {
|
||||
this.logger.info('Closing Google Chrome connection');
|
||||
await this.connectedBrowser.close();
|
||||
this.connectedBrowser = null;
|
||||
this.context = null;
|
||||
this.usingExternalChrome = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.context) {
|
||||
this.logger.info('Closing browser');
|
||||
await this.context.close();
|
||||
@@ -150,6 +202,20 @@ export class BrowserClient {
|
||||
}
|
||||
|
||||
async login(): Promise<boolean> {
|
||||
if (this.usingExternalChrome) {
|
||||
try {
|
||||
const page = await this.getPage();
|
||||
await page.bringToFront();
|
||||
await page.goto('https://www.reddit.com/login', { waitUntil: 'domcontentloaded' });
|
||||
this.sessionState = 'login-required';
|
||||
this.logger.warn('Interactive Reddit login required. Open the VNC page and sign in using Google Chrome.');
|
||||
} catch (error) {
|
||||
this.sessionState = classifyRedditSessionError(error);
|
||||
this.logger.error('Failed to open Google Chrome for interactive login:', error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.config.reddit.username || !this.config.reddit.password) {
|
||||
this.sessionState = 'login-required';
|
||||
this.logger.error('Login failed: Missing credentials in config/env');
|
||||
|
||||
Reference in new issue
Block a user