8733b7d175
- 0038: lanzar Chrome/Edge/Brave externo con --remote-debugging-port + --user-data-dir por profile, control via CDP desde cdp-cli Go. Decision Go vs C++ in-process documentada; deja la puerta abierta a un cliente C++ minimo solo para streaming en el futuro. Supersedes 0032. - 0039: gestor de cookies/sesiones por profile via CDP — list, export EditThisCookie, import, clear selectivo, health checks con selectores, locks cuando un enricher esta usando el profile. - 0040: profiles como concepto de primera clase — metadata (color, icon, browser_preference, UA, project, template), templates anon/auth/work/ investigation, ProfilePicker reusable, project default, tag en executions.metrics. Actualiza 0038 para apuntar a 0040 como duenio del UX de profiles.
242 lines
12 KiB
Markdown
242 lines
12 KiB
Markdown
---
|
|
id: 0038
|
|
title: Browser externo lanzable desde el app + control via CDP
|
|
status: pending
|
|
priority: high
|
|
created: 2026-05-04
|
|
depends_on: [0014, 0029]
|
|
supersedes: [0032]
|
|
---
|
|
|
|
## Contexto y objetivo
|
|
|
|
En lugar de embeber un browser dentro del app (CEF/WebView2) o automatizar
|
|
Chromium oculto via Playwright (issue 0032), el modelo elegido es:
|
|
|
|
1. **El usuario navega en su browser real** (Chrome / Edge / Brave / Vivaldi
|
|
instalado en el sistema), con su layout, sus extensiones, sus marcadores.
|
|
2. **graph_explorer lanza una instancia controlable** del browser cuando
|
|
hace falta, con `--remote-debugging-port` y `--user-data-dir` apuntando
|
|
a un profile gestionado por el app.
|
|
3. **El app habla con esa instancia via CDP** (Chrome DevTools Protocol)
|
|
para los enrichers que necesiten JS/cookies/auth.
|
|
4. **La extension del issue 0014** cierra el loop por el lado humano: lo
|
|
que el usuario lee en cualquier pestaña se manda al grafo con un click.
|
|
|
|
Beneficio: cero bytes de bundle (browser ya instalado), UX nativa,
|
|
control programatico cuando se necesita.
|
|
|
|
## Decision de stack
|
|
|
|
| Aspecto | Decision |
|
|
|---|---|
|
|
| Browser objetivo | Chromium-based detectado en runtime: Chrome > Edge > Brave > Vivaldi |
|
|
| Protocolo control | CDP (websocket a `localhost:<port>`) |
|
|
| Persistencia | `--user-data-dir=<app>/local_files/browser_profiles/<name>/` |
|
|
| **Cliente CDP** | **Go** — binario `cdp-cli` que envuelve las funciones del registry (issue 0029). El app C++ lo invoca via subprocess. |
|
|
| Descarga binaria | Ninguna — se exige browser instalado, error claro si no se detecta |
|
|
|
|
### Por que Go y no C++ in-process
|
|
|
|
Las funciones CDP (`cdp_connect`, `cdp_navigate`, `cdp_get_html`,
|
|
`cdp_evaluate`, `cdp_screenshot`, ...) ya estan planificadas en Go en
|
|
`0029`. Implementar un cliente CDP nativo en C++ duplicaria ~1500 LoC
|
|
(websocket + state machine + JSON dispatch) sin ganancia real:
|
|
|
|
- Las operaciones reales (get-html, screenshot, evaluate) tardan
|
|
500-3000 ms. El overhead de spawn de subprocess (~30-50 ms) es ruido.
|
|
- Aislamiento de fallos: un cuelgue del websocket muere con el
|
|
subprocess en vez de bloquear el render loop de ImGui.
|
|
- Reuso: el mismo `cdp-cli` lo invocan los enrichers Python, scripts
|
|
manuales y otras apps del registry. Una sola implementacion mantenida.
|
|
- Bundle: +8 MB de binario Go vs +deps C++ (websocket lib + JSON) que
|
|
hay que integrar en CMake.
|
|
|
|
Playwright (0032) queda **descartado** como dependencia del app: el
|
|
binario de 200 MB y la complejidad de gestionar profiles via Python no
|
|
compensan cuando el SO ya tiene browser. Se mantiene 0032 como nota
|
|
historica.
|
|
|
|
### Cliente C++ in-process: reservado para streaming (futuro)
|
|
|
|
La unica operacion donde un cliente CDP **nativo en C++** ganaria es
|
|
**streaming en vivo** del browser dentro del app (ej. `Page.startScreencast`
|
|
para mostrar la pestaña actual a 30fps en un panel ImGui, o capturar
|
|
eventos `Network.responseReceived` / `Page.frameNavigated` en tiempo
|
|
real). Para eso si hace falta un websocket persistente en el render
|
|
thread, sin overhead de spawn.
|
|
|
|
Cuando aparezca ese caso (no esta en `0038` ni `0039`), se abre un
|
|
issue separado para añadir un cliente C++ minimo orientado **solo** a
|
|
streaming. El flujo sera:
|
|
|
|
- Mantener `cdp-cli` Go para todo lo one-shot (sigue siendo la via
|
|
default).
|
|
- Añadir `cpp/functions/browser/cdp_stream_cpp_browser.{h,cpp}` con
|
|
websocket persistente, suscrito a un subset de eventos CDP.
|
|
- El stream se conecta al MISMO `--remote-debugging-port` que ya tiene
|
|
abierto el browser lanzado desde `0038`. Coexisten sin conflicto: CDP
|
|
permite multiples clientes contra la misma instancia.
|
|
|
|
Es decir: la arquitectura de `0038` no cierra la puerta — la deja
|
|
abierta. Empezamos en Go porque es la decision pragmatica hoy, y
|
|
añadimos C++ in-process **solo** si aparece un caso de streaming
|
|
concreto que lo justifique.
|
|
|
|
## Arquitectura
|
|
|
|
### Componentes
|
|
|
|
```
|
|
graph_explorer (C++)
|
|
├── browser_panel.{h,cpp} ← UI: detectar/lanzar/listar instancias
|
|
├── browser_control.{h,cpp} ← Wrapper sobre cdp-cli (subprocess Go)
|
|
└── local_files/browser_profiles/
|
|
├── default/ ← user-data-dir Chrome
|
|
├── linkedin/
|
|
└── osint_anon/
|
|
|
|
cpp/functions/browser/ ← funciones del registry (lang: cpp)
|
|
├── browser_detect.cpp ← localiza chrome.exe / brave.exe / ...
|
|
└── browser_launch.cpp ← spawn con flags --remote-debugging-port + --user-data-dir
|
|
|
|
functions/browser/ (Go, ya en 0029)
|
|
├── chrome_launch.go ← reusable
|
|
├── cdp_connect.go
|
|
├── cdp_navigate.go
|
|
├── cdp_get_html.go
|
|
├── cdp_evaluate.go
|
|
└── cdp_screenshot.go
|
|
|
|
apps/graph_explorer/cdp-cli/ ← binario Go que envuelve las funciones CDP
|
|
y expone subcomandos invocables desde C++
|
|
```
|
|
|
|
### Flujo lanzar browser
|
|
|
|
1. Usuario abre panel **Browsers** → selecciona profile (`default` o crea
|
|
uno nuevo) → click **Launch**.
|
|
2. `browser_detect` localiza el binario (busca registry de Windows /
|
|
`~/.local/share/applications` / `where chrome.exe`).
|
|
3. `browser_launch` hace spawn:
|
|
```
|
|
chrome.exe \
|
|
--remote-debugging-port=9222 \
|
|
--user-data-dir=<app>/local_files/browser_profiles/<profile> \
|
|
--no-first-run \
|
|
--no-default-browser-check \
|
|
about:blank
|
|
```
|
|
4. Puerto se asigna dinamicamente desde un pool (9222..9322) para soportar
|
|
varios profiles en paralelo.
|
|
5. App registra la instancia en una tabla `browser_sessions` de
|
|
`graph_explorer.db`: `{profile, pid, port, browser_path, started_at}`.
|
|
|
|
### Flujo enricher con CDP
|
|
|
|
1. Enricher (Python o Go) recibe el `profile` como param.
|
|
2. Llama a `cdp-cli` con el subcomando que necesite:
|
|
```
|
|
cdp-cli get-html --profile linkedin --url https://...
|
|
cdp-cli screenshot --profile default --url https://... --out cache/x.png
|
|
cdp-cli evaluate --profile osint --js "document.title"
|
|
```
|
|
3. `cdp-cli` localiza el puerto del profile (lee `browser_sessions`),
|
|
abre websocket CDP, navega, espera `Page.loadEventFired`, devuelve.
|
|
4. Si el profile no esta vivo, `cdp-cli` lanza el browser primero
|
|
(mismo path que el panel, pero headless: opcional con `--headless=new`).
|
|
|
|
### Coordinacion con la extension (issue 0014)
|
|
|
|
La extension del usuario (Firefox/Chrome) NO usa CDP — usa el endpoint
|
|
HTTP `POST /ingest/text|/ingest/url` (issue 0012). Es el camino humano:
|
|
"estoy leyendo esto, mandalo al grafo".
|
|
|
|
CDP es el camino programatico: "el app necesita el HTML post-JS de
|
|
esta URL para extraer entidades". Los dos son complementarios y
|
|
comparten el mismo profile (cookies/login compartidos):
|
|
- Usuario hace login en LinkedIn manualmente (extension activa).
|
|
- Enrichers posteriores con `profile=linkedin` heredan la sesion.
|
|
|
|
## Panel UI: Browsers
|
|
|
|
Este panel muestra **runtime** (instancias vivas, puertos, PIDs). La
|
|
gestion logica de profiles (metadata, templates, seleccion, project
|
|
defaults, clone/rename/export) vive en el panel **Profiles** del
|
|
issue `0040`. Aqui solo aparece lo que esta corriendo ahora.
|
|
|
|
Layout en un panel ImGui (toggle via `cfg.panels`):
|
|
|
|
```
|
|
┌─ Browsers ─────────────────────────────────────────┐
|
|
│ Detected: Chrome 130, Edge 132, Brave (not found) │
|
|
│ │
|
|
│ Profiles │
|
|
│ ┌─────────────────────────────────────────────────┐ │
|
|
│ │ default Chrome port 9222 ● [open] [stop]│ │
|
|
│ │ linkedin Chrome port 9223 ● [open] [stop]│ │
|
|
│ │ osint_anon Edge - ○ [launch] │ │
|
|
│ └─────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ [+ New profile] [Open profiles folder] │
|
|
└─────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
- ● = vivo, ○ = parado.
|
|
- Click en filas = expande con stats (pages abiertas, cookies count,
|
|
ultima URL — todo via CDP `Target.getTargets` + `Network.getCookies`).
|
|
- Doble click en profile abre el browser con ese profile (sin headless).
|
|
- Boton "Console" abre `chrome://inspect/#devices` para debug.
|
|
|
|
## Plan de implementacion
|
|
|
|
| Fase | Entregable |
|
|
|---|---|
|
|
| 0038a | `cpp/functions/browser/browser_detect.cpp` — localiza binarios chromium-based en el sistema. Tests con `which`/registry de Windows. |
|
|
| 0038b | `cpp/functions/browser/browser_launch.cpp` — spawn con flags. Stub de `kill_pid` para parar. |
|
|
| 0038c | `apps/graph_explorer/cdp-cli/` — binario Go que envuelve `chrome_launch_go_browser`, `cdp_*` (issue 0029). Subcomandos: `get-html`, `screenshot`, `evaluate`, `cookies`. |
|
|
| 0038d | `browser_panel.{h,cpp}` — panel ImGui con detect/launch/list. Tabla `browser_sessions` en `graph_explorer.db`. |
|
|
| 0038e | `enrichers/fetch_webpage_browser/` — port del enricher para invocar `cdp-cli get-html`. |
|
|
| 0038f | `enrichers/fetch_screenshot/` — invoca `cdp-cli screenshot`, guarda en `cache/<sha>.png`, escribe `screenshot_path` en metadata. |
|
|
| 0038g | Auto-start del browser cuando un enricher pide un profile que no esta vivo. |
|
|
| 0038h | Tests E2E con un Chrome local y `httpbin.org` (gateado por env var, no en CI por defecto). |
|
|
|
|
## Riesgos y mitigaciones
|
|
|
|
| Riesgo | Mitigacion |
|
|
|---|---|
|
|
| No hay browser instalado | Mensaje claro en el panel + link a chrome.com/edge. App sigue funcional sin browser para enrichers no-CDP. |
|
|
| Varias instancias del mismo profile | Chrome bloquea automaticamente el `user-data-dir`. Capturar el error de spawn y mostrar "profile already in use, port=X". |
|
|
| Puerto CDP ocupado | Pool dinamico 9222..9322 con probe `tcp.Dial` antes de spawnear. |
|
|
| El usuario cierra el browser | Heartbeat cada 5s desde el panel (`Target.getTargets`). Si falla N veces, marcar como dead. |
|
|
| Updates de Chrome rompen flags | Ninguno de los flags usados es experimental. `--remote-debugging-port` lleva 10+ anos. |
|
|
| CDP version skew | Usar el subset estable: `Page.*`, `Runtime.evaluate`, `Network.getCookies`. Evitar APIs experimentales. |
|
|
|
|
## Definicion de hecho
|
|
|
|
- Desde el panel **Browsers**, click en **Launch default** abre Chrome
|
|
con `local_files/browser_profiles/default/` como user-data-dir y queda
|
|
registrado en `browser_sessions` con su puerto.
|
|
- `cdp-cli get-html --profile default --url https://example.com` devuelve
|
|
el DOM post-JS por stdout en menos de 5s.
|
|
- Un enricher `fetch_webpage_browser` llamado con `profile=linkedin`
|
|
lanza el browser si no esta vivo y devuelve el HTML autenticado.
|
|
- La extension de issue 0014 sigue funcionando en paralelo sin
|
|
interferir (usan el mismo profile pero por mecanismos distintos).
|
|
- Cerrar el app NO mata los browsers lanzados — son procesos
|
|
independientes, persisten hasta que el usuario los cierra. (Decision
|
|
consciente: el usuario quiere seguir navegando aunque el app se reinicie.)
|
|
|
|
## Fuera de alcance
|
|
|
|
- Firefox / Safari / no-chromium — descartados (Firefox tiene su propio
|
|
protocolo, demasiado divergente). Solo navegadores chromium-based.
|
|
- Browser headless en CI — los tests E2E que requieran browser quedan
|
|
fuera del CI por defecto.
|
|
- Browser as a service (lanzar en VPS y conectar remoto) — fuera de v1.
|
|
- **Cliente CDP nativo en C++ para streaming** (`Page.startScreencast`,
|
|
eventos `Network.*` en vivo, mirror de pestaña en panel ImGui).
|
|
Reservado para un issue futuro cuando aparezca el caso de uso. La
|
|
arquitectura Go elegida aqui no lo bloquea — coexistirian contra el
|
|
mismo `--remote-debugging-port` (CDP soporta multiples clientes).
|