Beacon / src /browser.js
Ig0tU
beacon v1 — authenticated web proxy + AGENTS.md
770ac3d
Raw
History Blame Contribute Delete
4.59 kB
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 }
}