Spaces:
Sleeping
Sleeping
| import { chromium } from 'playwright' | |
| import { mkdirSync, existsSync, readdirSync, readFileSync } from 'fs' | |
| import { resolve, dirname } from 'path' | |
| import { fileURLToPath } from 'url' | |
| const __dirname = dirname(fileURLToPath(import.meta.url)) | |
| const PROFILE_DIR = resolve(__dirname, '../sessions/chrome-profile') | |
| const SNAPSHOTS_DIR = resolve(__dirname, '../sessions/snapshots') | |
| const INJECT_SRC = resolve(__dirname, './inject.browser.js') | |
| const PUBLIC_BASE = process.env.BEACON_PUBLIC_URL || `http://localhost:${process.env.BEACON_PORT || 3700}` | |
| // Inject script with beacon base URL substituted in | |
| function buildInjectScript() { | |
| return readFileSync(INJECT_SRC, 'utf8').replaceAll('__BEACON_BASE__', PUBLIC_BASE) | |
| } | |
| // Wire auto-inject onto a context — fires on every new page that opens | |
| function attachInject(context) { | |
| const script = buildInjectScript() | |
| // addInitScript runs before any page JS — functions are available immediately | |
| context.addInitScript(script).catch(() => {}) | |
| } | |
| const IS_MAC = process.platform === 'darwin' | |
| const IS_HF = !!process.env.SPACE_ID // HuggingFace injects this | |
| let _context = null | |
| let _browser = null // only set in non-persistent launch | |
| export async function getContext() { | |
| if (_context) return _context | |
| mkdirSync(PROFILE_DIR, { recursive: true }) | |
| mkdirSync(SNAPSHOTS_DIR, { recursive: true }) | |
| // ── Mac path ────────────────────────────────────────────────────────────── | |
| if (IS_MAC && !IS_HF) { | |
| // 1. CDP bridge — rides your already-running Chrome (zero copy) | |
| // Enable with: open -a "Google Chrome" --args --remote-debugging-port=9222 | |
| try { | |
| const browser = await chromium.connectOverCDP('http://localhost:9222', { timeout: 2000 }) | |
| const contexts = browser.contexts() | |
| _context = contexts[0] ?? await browser.newContext() | |
| attachInject(_context) | |
| console.log('[beacon] CDP bridge — live Chrome session active') | |
| return _context | |
| } catch {} | |
| // 2. Chrome profile clone (npm run sync-profile) | |
| const hasProfile = existsSync(`${PROFILE_DIR}/Cookies`) | |
| _context = await chromium.launchPersistentContext(PROFILE_DIR, { | |
| channel: 'chrome', | |
| headless: true, | |
| viewport: { width: 1280, height: 900 }, | |
| userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', | |
| args: ['--no-sandbox', '--disable-blink-features=AutomationControlled'], | |
| ignoreDefaultArgs: ['--enable-automation'], | |
| }) | |
| attachInject(_context) | |
| console.log(hasProfile | |
| ? '[beacon] Chrome profile loaded — all sessions active' | |
| : '[beacon] Fresh profile — run: npm run sync-profile') | |
| return _context | |
| } | |
| // ── Linux / HuggingFace path ────────────────────────────────────────────── | |
| // No local Chrome. Load auth from committed session snapshots instead. | |
| const storageState = mergeSnapshots(SNAPSHOTS_DIR) | |
| _browser = await chromium.launch({ | |
| headless: true, | |
| args: [ | |
| '--no-sandbox', | |
| '--disable-dev-shm-usage', | |
| '--disable-blink-features=AutomationControlled', | |
| ], | |
| }) | |
| _context = await _browser.newContext({ | |
| storageState: storageState || undefined, | |
| viewport: { width: 1280, height: 900 }, | |
| userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', | |
| ignoreHTTPSErrors: false, | |
| }) | |
| attachInject(_context) | |
| const snapshotCount = storageState?.cookies?.length ?? 0 | |
| console.log(snapshotCount > 0 | |
| ? `[beacon] Loaded ${snapshotCount} cookies from session snapshots` | |
| : '[beacon] No snapshots found — browsing anonymously') | |
| return _context | |
| } | |
| // Merge all snapshot JSON files into a single storageState object | |
| function mergeSnapshots(dir) { | |
| const files = readdirSync(dir).filter(f => f.endsWith('.json')) | |
| if (!files.length) return null | |
| const cookies = [] | |
| const origins = [] | |
| for (const file of files) { | |
| try { | |
| const state = JSON.parse(readFileSync(`${dir}/${file}`, 'utf8')) | |
| if (state.cookies) cookies.push(...state.cookies) | |
| if (state.origins) origins.push(...state.origins) | |
| } catch {} | |
| } | |
| return { cookies, origins } | |
| } | |
| export async function closeContext() { | |
| if (_context) { await _context.close(); _context = null } | |
| if (_browser) { await _browser.close(); _browser = null } | |
| } | |