package browser import ( "encoding/json" "fmt" "os" "strings" ) // jsQuote serializa s como literal string JavaScript con comillas dobles y // caracteres escapados correctamente. Usa json.Marshal internamente para // reutilizar el mismo escapado que JSON (compatible con JS). func jsQuote(s string) string { b, err := json.Marshal(s) if err != nil { // Fallback seguro: comillas dobles escapando backslash y comilla doble return fmt.Sprintf("%q", s) } return string(b) } // CdpLoadStorageState lee el JSON generado por CdpSaveStorageState y restaura // cookies y localStorage en la pestaña activa. Permite retomar una sesion // autenticada sin repetir el login. // // CRITICO: el localStorage es por-origen. Antes de llamar a esta funcion hay // que haber navegado al origen correcto (CdpNavigate al dominio). Orden // correcto: navegar -> CdpLoadStorageState -> recargar pagina. func CdpLoadStorageState(c *CDPConn, inPath string) error { if c == nil { return fmt.Errorf("cdp load storage state: conexion nula") } if inPath == "" { return fmt.Errorf("cdp load storage state: inPath vacio") } data, err := os.ReadFile(inPath) if err != nil { return fmt.Errorf("cdp load storage state: leer archivo: %w", err) } var state CdpStorageState if err := json.Unmarshal(data, &state); err != nil { return fmt.Errorf("cdp load storage state: unmarshal: %w", err) } // Habilitar dominio Network para manipular cookies if _, err := c.sendCDP("Network.enable", nil); err != nil { return fmt.Errorf("cdp load storage state: Network.enable: %w", err) } // Restaurar cookies. Network.setCookies aplica de forma fiable las cookies // (sobre todo httpOnly y de sesión) cuando cada una lleva el campo `url`: de // ahí deriva scheme y scope. getAllCookies no lo incluye, así que lo // sintetizamos a partir de domain/secure/path cuando falta. if len(state.Cookies) > 0 { for _, ck := range state.Cookies { if _, has := ck["url"]; has { continue } dom, _ := ck["domain"].(string) dom = strings.TrimPrefix(dom, ".") if dom == "" { continue } scheme := "http" if sec, _ := ck["secure"].(bool); sec { scheme = "https" } path, _ := ck["path"].(string) if path == "" { path = "/" } ck["url"] = scheme + "://" + dom + path } if _, err := c.sendCDP("Network.setCookies", map[string]any{ "cookies": state.Cookies, }); err != nil { return fmt.Errorf("cdp load storage state: setCookies: %w", err) } } // Restaurar localStorage y sessionStorage — setItem por cada par clave/valor for k, v := range state.LocalStorage { expr := fmt.Sprintf("window.localStorage.setItem(%s, %s)", jsQuote(k), jsQuote(v)) if _, err := CdpEvaluate(c, expr); err != nil { return fmt.Errorf("cdp load storage state: localStorage setItem %q: %w", k, err) } } for k, v := range state.SessionStorage { expr := fmt.Sprintf("window.sessionStorage.setItem(%s, %s)", jsQuote(k), jsQuote(v)) if _, err := CdpEvaluate(c, expr); err != nil { return fmt.Errorf("cdp load storage state: sessionStorage setItem %q: %w", k, err) } } return nil }