browsing 0.1.7

Browser automation: navigate, click, extract, screenshot. Standalone browser control via CDP.
Documentation
//! Cookie management via CDP Network domain

use crate::browser::views::{Cookie, CookieParam};
use crate::error::{BrowsingError, Result};
use serde_json::Value;
use std::sync::Arc;

/// Manages browser cookies through CDP
pub struct CookieManager {
    client: Arc<crate::browser::cdp::CdpClient>,
}

impl CookieManager {
    /// Create a new cookie manager with the given CDP client
    pub fn new(client: Arc<crate::browser::cdp::CdpClient>) -> Self {
        Self { client }
    }

    /// Get all cookies for the current browser context
    pub async fn get_cookies(&self) -> Result<Vec<Cookie>> {
        let result = self
            .client
            .send_command("Network.getCookies", serde_json::json!({}))
            .await?;

        let cookies = result
            .get("cookies")
            .and_then(|v| v.as_array())
            .ok_or_else(|| BrowsingError::Browser("No cookies array in response".to_string()))?;

        cookies
            .iter()
            .map(parse_cookie)
            .collect()
    }

    /// Get cookies for a specific URL
    pub async fn get_cookies_for_url(&self, url: &str) -> Result<Vec<Cookie>> {
        let result = self
            .client
            .send_command(
                "Network.getCookies",
                serde_json::json!({ "urls": [url] }),
            )
            .await?;

        let cookies = result
            .get("cookies")
            .and_then(|v| v.as_array())
            .ok_or_else(|| BrowsingError::Browser("No cookies array in response".to_string()))?;

        cookies
            .iter()
            .map(parse_cookie)
            .collect()
    }

    /// Set a cookie
    pub async fn set_cookie(&self, param: &CookieParam) -> Result<()> {
        let mut params = serde_json::json!({
            "name": param.name,
            "value": param.value,
        });

        if let Some(url) = &param.url {
            params["url"] = serde_json::json!(url);
        }
        if let Some(domain) = &param.domain {
            params["domain"] = serde_json::json!(domain);
        }
        if let Some(path) = &param.path {
            params["path"] = serde_json::json!(path);
        }
        if let Some(secure) = param.secure {
            params["secure"] = serde_json::json!(secure);
        }
        if let Some(http_only) = param.http_only {
            params["httpOnly"] = serde_json::json!(http_only);
        }
        if let Some(expires) = param.expires {
            params["expires"] = serde_json::json!(expires);
        }
        if let Some(same_site) = &param.same_site {
            params["sameSite"] = serde_json::json!(same_site);
        }

        self.client
            .send_command("Network.setCookie", params)
            .await?;
        Ok(())
    }

    /// Delete cookies by name (and optionally domain/path)
    pub async fn delete_cookies(&self, name: &str, url: Option<&str>, domain: Option<&str>, path: Option<&str>) -> Result<()> {
        let mut params = serde_json::json!({ "name": name });

        if let Some(url) = url {
            params["url"] = serde_json::json!(url);
        }
        if let Some(domain) = domain {
            params["domain"] = serde_json::json!(domain);
        }
        if let Some(path) = path {
            params["path"] = serde_json::json!(path);
        }

        self.client
            .send_command("Network.deleteCookies", params)
            .await?;
        Ok(())
    }

    /// Clear all cookies
    pub async fn clear_cookies(&self) -> Result<()> {
        self.client
            .send_command("Network.clearBrowserCookies", serde_json::json!({}))
            .await?;
        Ok(())
    }
}

fn parse_cookie(value: &Value) -> Result<Cookie> {
    let name = value
        .get("name")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_string();
    let cookie_value = value
        .get("value")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_string();
    let domain = value
        .get("domain")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_string();
    let path = value
        .get("path")
        .and_then(|v| v.as_str())
        .unwrap_or("/")
        .to_string();
    let secure = value
        .get("secure")
        .and_then(|v| v.as_bool())
        .unwrap_or(false);
    let http_only = value
        .get("httpOnly")
        .and_then(|v| v.as_bool())
        .unwrap_or(false);
    let expires = value.get("expires").and_then(|v| v.as_f64());
    let same_site = value
        .get("sameSite")
        .and_then(|v| v.as_str())
        .map(|s| s.to_string());

    Ok(Cookie {
        name,
        value: cookie_value,
        domain,
        path,
        secure,
        http_only,
        expires,
        same_site,
        size: None,
        third_party: None,
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_cookie() {
        let json = serde_json::json!({
            "name": "session_id",
            "value": "abc123",
            "domain": ".example.com",
            "path": "/",
            "secure": true,
            "httpOnly": true,
            "expires": 1893456000.0,
            "sameSite": "Lax"
        });

        let cookie = parse_cookie(&json).unwrap();
        assert_eq!(cookie.name, "session_id");
        assert_eq!(cookie.value, "abc123");
        assert_eq!(cookie.domain, ".example.com");
        assert_eq!(cookie.path, "/");
        assert!(cookie.secure);
        assert!(cookie.http_only);
        assert_eq!(cookie.expires, Some(1893456000.0));
        assert_eq!(cookie.same_site, Some("Lax".to_string()));
    }

    #[test]
    fn test_parse_cookie_minimal() {
        let json = serde_json::json!({
            "name": "test",
            "value": "val"
        });

        let cookie = parse_cookie(&json).unwrap();
        assert_eq!(cookie.name, "test");
        assert_eq!(cookie.value, "val");
        assert_eq!(cookie.domain, "");
        assert_eq!(cookie.path, "/");
        assert!(!cookie.secure);
        assert!(!cookie.http_only);
        assert!(cookie.expires.is_none());
        assert!(cookie.same_site.is_none());
    }

    #[test]
    fn test_cookie_param_default() {
        let param = CookieParam {
            name: "foo".to_string(),
            value: "bar".to_string(),
            ..Default::default()
        };
        assert_eq!(param.name, "foo");
        assert_eq!(param.value, "bar");
        assert!(param.domain.is_none());
        assert!(param.path.is_none());
    }
}