Files
fn_registry/functions/browser/cdp_list_tabs.go
T
egutierrez 750b7abcd5 chore: auto-commit (97 archivos)
- .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>
2026-05-09 18:11:24 +02:00

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
}