| 147 | } |
| 148 | |
| 149 | export class BrowserManager { |
| 150 | private browser: Browser | null = null; |
| 151 | private context: BrowserContext | null = null; |
| 152 | // Proxy config applied to chromium.launch() when set (D8). Set by server.ts |
| 153 | // at startup based on BROWSE_PROXY_URL. For SOCKS5 with auth, server.ts |
| 154 | // points this at the local bridge (socks5://127.0.0.1:<bridgePort>); for |
| 155 | // HTTP/HTTPS or unauth SOCKS5, it's the upstream URL directly. |
| 156 | private proxyConfig: { server: string; username?: string; password?: string } | null = null; |
| 157 | private pages: Map<number, Page> = new Map(); |
| 158 | private tabSessions: Map<number, TabSession> = new Map(); |
| 159 | private activeTabId: number = 0; |
| 160 | private nextTabId: number = 1; |
| 161 | private extraHeaders: Record<string, string> = {}; |
| 162 | private customUserAgent: string | null = null; |
| 163 | |
| 164 | // ─── Viewport + deviceScaleFactor (context options) ────────── |
| 165 | // Tracked at the manager level so recreateContext() preserves them. |
| 166 | // deviceScaleFactor is a *context* option, not a page-level setter — changes |
| 167 | // require recreateContext(). Viewport width/height can change on-page, but we |
| 168 | // track the latest so context recreation restores it instead of hardcoding 1280x720. |
| 169 | private deviceScaleFactor: number = 1; |
| 170 | private currentViewport: { width: number; height: number } = { width: 1280, height: 720 }; |
| 171 | |
| 172 | /** Server port — set after server starts, used by cookie-import-browser command */ |
| 173 | public serverPort: number = 0; |
| 174 | |
| 175 | // ─── Tab Ownership (multi-agent isolation) ────────────── |
| 176 | // Maps tabId → clientId. Unowned tabs (not in this map) are root-only for writes. |
| 177 | private tabOwnership: Map<number, string> = new Map(); |
| 178 | |
| 179 | // ─── Dialog Handling (global, not per-tab) ────────────────── |
| 180 | private dialogAutoAccept: boolean = true; |
| 181 | private dialogPromptText: string | null = null; |
| 182 | |
| 183 | // ─── Cookie Origin Tracking ──────────────────────────────── |
| 184 | private cookieImportedDomains: Set<string> = new Set(); |
| 185 | |
| 186 | // ─── Handoff State ───────────────────────────────────────── |
| 187 | private isHeaded: boolean = false; |
| 188 | private consecutiveFailures: number = 0; |
| 189 | |
| 190 | // ─── Watch Mode ───────────────────────────────────────── |
| 191 | private watching = false; |
| 192 | public watchInterval: ReturnType<typeof setInterval> | null = null; |
| 193 | private watchSnapshots: string[] = []; |
| 194 | private watchStartTime: number = 0; |
| 195 | |
| 196 | // ─── Headed State ──────────────────────────────────────── |
| 197 | private connectionMode: 'launched' | 'headed' = 'launched'; |
| 198 | private intentionalDisconnect = false; |
| 199 | |
| 200 | // ─── Tab Count Guardrail (D5 + Codex single-tab flag) ─────── |
| 201 | // Idempotent threshold trackers: each guardrail fires exactly once per |
| 202 | // upward crossing of its threshold and re-arms when the tab count drops |
| 203 | // back below. Pre-guardrail, nothing tracked tab count growth and a |
| 204 | // user could accumulate hundreds of tabs (each holding 50–300 MB of |
| 205 | // Chromium-side RSS) without warning until the OS OOM-killer fired. |
| 206 | // The toast UX lives in the sidebar (extension/sidepanel.js); the |
nothing calls this directly
no outgoing calls
no test coverage detected