package main import ( "strings" "sync" "fn-registry/functions/browser" ) // connPool reusa conexiones CDP entre invocaciones de tools. Clave = puerto CDP. // Una conexión = una sesión viva a una tab "page". Mantenerla evita pagar el // handshake WebSocket en cada tool y preserva estado (event handlers, contexto). type connPool struct { mu sync.Mutex conns map[int]*browser.CDPConn cancels map[int]func() // cancels de handlers persistentes (handle_dialog) } func newConnPool() *connPool { return &connPool{conns: map[int]*browser.CDPConn{}, cancels: map[int]func(){}} } func (p *connPool) get(port int) (*browser.CDPConn, error) { p.mu.Lock() defer p.mu.Unlock() if c, ok := p.conns[port]; ok && c != nil { return c, nil } c, err := browser.CdpConnect(port) if err != nil { return nil, err } p.conns[port] = c return c, nil } func (p *connPool) drop(port int) { p.mu.Lock() defer p.mu.Unlock() if cancel, ok := p.cancels[port]; ok && cancel != nil { cancel() delete(p.cancels, port) } if c, ok := p.conns[port]; ok && c != nil { _ = browser.CdpClose(c, 0) delete(p.conns, port) } } // connectTarget descarta la conexión actual del puerto y reconecta a un target // determinista (por id o substring de URL). Asegura que el agente opera sobre una // pestaña conocida y no sobre "la primera al azar". func (p *connPool) connectTarget(port int, match string) (*browser.CDPConn, error) { p.drop(port) c, err := browser.CdpConnectTarget("localhost", port, match) if err != nil { return nil, err } p.mu.Lock() p.conns[port] = c p.mu.Unlock() return c, nil } func (p *connPool) setCancel(port int, cancel func()) { p.mu.Lock() defer p.mu.Unlock() if old := p.cancels[port]; old != nil { old() } p.cancels[port] = cancel } func (p *connPool) closeAll() { p.mu.Lock() defer p.mu.Unlock() for port, c := range p.conns { if cancel := p.cancels[port]; cancel != nil { cancel() } if c != nil { _ = browser.CdpClose(c, 0) } } p.conns = map[int]*browser.CDPConn{} p.cancels = map[int]func(){} } // isConnErr reconoce errores de conexión CDP muerta para reintentar UNA vez. func isConnErr(err error) bool { s := err.Error() return strings.Contains(s, "connection close") || strings.Contains(s, "broken pipe") || strings.Contains(s, "use of closed") || strings.Contains(s, "ws read") || strings.Contains(s, "EOF") }