750b7abcd5
- .claude/CLAUDE.md - .claude/agents/fn-recopilador/SKILL.md - .claude/rules/INDEX.md - .claude/rules/cpp_apps.md - bash/functions/infra/build_cpp_windows.sh - cpp/CMakeLists.txt - cpp/PATTERNS.md - cpp/framework/app_base.cpp - cpp/framework/app_base.h - dev/issues/README.md - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
132 lines
3.9 KiB
Go
132 lines
3.9 KiB
Go
package browser
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
)
|
|
|
|
// CdpTab representa una pestaña/target devuelta por el endpoint /json de CDP.
|
|
// Campos publicos para que apps consumidoras puedan filtrar/render.
|
|
type CdpTab struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"` // "page", "iframe", "service_worker", ...
|
|
Title string `json:"title"`
|
|
URL string `json:"url"`
|
|
WebSocketDebuggerURL string `json:"webSocketDebuggerUrl"`
|
|
DevtoolsFrontendURL string `json:"devtoolsFrontendUrl,omitempty"`
|
|
}
|
|
|
|
// CdpListTabs llama GET http://{host}:{port}/json y retorna la lista de
|
|
// targets. Sin filtrar por tipo — el caller decide si se queda solo con
|
|
// type=="page", incluye iframes, etc.
|
|
//
|
|
// host vacio = "localhost". No requiere websocket (CDP expone /json en HTTP).
|
|
func CdpListTabs(host string, port int) ([]CdpTab, error) {
|
|
if host == "" {
|
|
host = "localhost"
|
|
}
|
|
resp, err := http.Get(fmt.Sprintf("http://%s:%d/json", host, port))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cdp list tabs: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("cdp list tabs: status %d", resp.StatusCode)
|
|
}
|
|
var tabs []CdpTab
|
|
if err := json.NewDecoder(resp.Body).Decode(&tabs); err != nil {
|
|
return nil, fmt.Errorf("cdp list tabs: decode: %w", err)
|
|
}
|
|
return tabs, nil
|
|
}
|
|
|
|
// CdpNewTab abre una pestaña nueva via PUT /json/new?<startURL>. Si startURL
|
|
// es vacio Chrome abre about:blank. Retorna el CdpTab recien creado.
|
|
//
|
|
// Nota: desde Chrome 126 /json/new requiere PUT (no GET). Mantenemos el
|
|
// fallback a GET por compatibilidad con builds antiguos.
|
|
func CdpNewTab(host string, port int, startURL string) (CdpTab, error) {
|
|
if host == "" {
|
|
host = "localhost"
|
|
}
|
|
endpoint := fmt.Sprintf("http://%s:%d/json/new", host, port)
|
|
if startURL != "" {
|
|
endpoint += "?" + url.QueryEscape(startURL)
|
|
}
|
|
|
|
tryRequest := func(method string) (CdpTab, error) {
|
|
var out CdpTab
|
|
req, err := http.NewRequest(method, endpoint, nil)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return out, fmt.Errorf("status %d", resp.StatusCode)
|
|
}
|
|
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
|
return out, fmt.Errorf("decode: %w", err)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
tab, err := tryRequest(http.MethodPut)
|
|
if err == nil && tab.ID != "" {
|
|
return tab, nil
|
|
}
|
|
// Fallback GET (Chrome < 126).
|
|
tab, err2 := tryRequest(http.MethodGet)
|
|
if err2 == nil && tab.ID != "" {
|
|
return tab, nil
|
|
}
|
|
if err == nil {
|
|
err = err2
|
|
}
|
|
return CdpTab{}, fmt.Errorf("cdp new tab: %w", err)
|
|
}
|
|
|
|
// CdpCloseTab cierra una pestaña por su ID via GET /json/close/<id>.
|
|
// Util complemento — incluido aqui porque comparte estructura HTTP /json.
|
|
func CdpCloseTab(host string, port int, tabID string) error {
|
|
if host == "" {
|
|
host = "localhost"
|
|
}
|
|
if tabID == "" {
|
|
return fmt.Errorf("cdp close tab: tabID vacio")
|
|
}
|
|
resp, err := http.Get(fmt.Sprintf("http://%s:%d/json/close/%s", host, port, url.PathEscape(tabID)))
|
|
if err != nil {
|
|
return fmt.Errorf("cdp close tab: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("cdp close tab: status %d", resp.StatusCode)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CdpActivateTab pone la pestaña en foreground (focus) via /json/activate/<id>.
|
|
func CdpActivateTab(host string, port int, tabID string) error {
|
|
if host == "" {
|
|
host = "localhost"
|
|
}
|
|
if tabID == "" {
|
|
return fmt.Errorf("cdp activate tab: tabID vacio")
|
|
}
|
|
resp, err := http.Get(fmt.Sprintf("http://%s:%d/json/activate/%s", host, port, url.PathEscape(tabID)))
|
|
if err != nil {
|
|
return fmt.Errorf("cdp activate tab: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("cdp activate tab: status %d", resp.StatusCode)
|
|
}
|
|
return nil
|
|
}
|