browsing 0.1.7

Browser automation: navigate, click, extract, screenshot. Standalone browser control via CDP.
Documentation
//! Local and session storage access via JavaScript evaluation

use crate::actor::Page;
use crate::error::{BrowsingError, Result};
use serde::{Deserialize, Serialize};

/// A single storage entry
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageEntry {
    /// Storage key
    pub key: String,
    /// Storage value
    pub value: String,
}

/// Manages localStorage and sessionStorage via page JavaScript evaluation
#[derive(Debug, Clone, Copy, Default)]
pub struct StorageManager;

impl StorageManager {
    /// Create a new storage manager
    pub fn new() -> Self {
        Self
    }

    /// Get a value from localStorage
    pub async fn get_local_storage(&self, page: &Page, key: &str) -> Result<Option<String>> {
        let script = format!(
            "(function() {{ try {{ return window.localStorage.getItem({}) || ''; }} catch(e) {{ return ''; }} }})()",
            serde_json::json!(key)
        );
        let result = page.evaluate(&script).await?;
        if result.is_empty() {
            Ok(None)
        } else {
            Ok(Some(result))
        }
    }

    /// Set a value in localStorage
    pub async fn set_local_storage(&self, page: &Page, key: &str, value: &str) -> Result<()> {
        let script = format!(
            "(function() {{ try {{ window.localStorage.setItem({}, {}); return 'ok'; }} catch(e) {{ return e.message; }} }})()",
            serde_json::json!(key),
            serde_json::json!(value)
        );
        let result = page.evaluate(&script).await?;
        if result != "ok" {
            return Err(BrowsingError::Browser(format!(
                "localStorage setItem failed: {result}"
            )));
        }
        Ok(())
    }

    /// Remove a key from localStorage
    pub async fn remove_local_storage(&self, page: &Page, key: &str) -> Result<()> {
        let script = format!(
            "(function() {{ try {{ window.localStorage.removeItem({}); return 'ok'; }} catch(e) {{ return e.message; }} }})()",
            serde_json::json!(key)
        );
        page.evaluate(&script).await?;
        Ok(())
    }

    /// Clear all localStorage entries
    pub async fn clear_local_storage(&self, page: &Page) -> Result<()> {
        let script = "(function() { try { window.localStorage.clear(); return 'ok'; } catch(e) { return e.message; } })()";
        let result = page.evaluate(script).await?;
        if result != "ok" {
            return Err(BrowsingError::Browser(format!(
                "localStorage clear failed: {result}"
            )));
        }
        Ok(())
    }

    /// Get all localStorage entries
    pub async fn get_all_local_storage(&self, page: &Page) -> Result<Vec<StorageEntry>> {
        let script = r#"
            (function() {
                try {
                    var entries = [];
                    for (var i = 0; i < window.localStorage.length; i++) {
                        var key = window.localStorage.key(i);
                        entries.push({ key: key, value: window.localStorage.getItem(key) });
                    }
                    return JSON.stringify(entries);
                } catch(e) {
                    return '[]';
                }
            })()
        "#;
        let result = page.evaluate(script).await?;
        let entries: Vec<StorageEntry> =
            serde_json::from_str(&result).unwrap_or_default();
        Ok(entries)
    }

    /// Get a value from sessionStorage
    pub async fn get_session_storage(&self, page: &Page, key: &str) -> Result<Option<String>> {
        let script = format!(
            "(function() {{ try {{ return window.sessionStorage.getItem({}) || ''; }} catch(e) {{ return ''; }} }})()",
            serde_json::json!(key)
        );
        let result = page.evaluate(&script).await?;
        if result.is_empty() {
            Ok(None)
        } else {
            Ok(Some(result))
        }
    }

    /// Set a value in sessionStorage
    pub async fn set_session_storage(&self, page: &Page, key: &str, value: &str) -> Result<()> {
        let script = format!(
            "(function() {{ try {{ window.sessionStorage.setItem({}, {}); return 'ok'; }} catch(e) {{ return e.message; }} }})()",
            serde_json::json!(key),
            serde_json::json!(value)
        );
        let result = page.evaluate(&script).await?;
        if result != "ok" {
            return Err(BrowsingError::Browser(format!(
                "sessionStorage setItem failed: {result}"
            )));
        }
        Ok(())
    }

    /// Remove a key from sessionStorage
    pub async fn remove_session_storage(&self, page: &Page, key: &str) -> Result<()> {
        let script = format!(
            "(function() {{ try {{ window.sessionStorage.removeItem({}); return 'ok'; }} catch(e) {{ return e.message; }} }})()",
            serde_json::json!(key)
        );
        page.evaluate(&script).await?;
        Ok(())
    }

    /// Clear all sessionStorage entries
    pub async fn clear_session_storage(&self, page: &Page) -> Result<()> {
        let script = "(function() { try { window.sessionStorage.clear(); return 'ok'; } catch(e) { return e.message; } })()";
        let result = page.evaluate(script).await?;
        if result != "ok" {
            return Err(BrowsingError::Browser(format!(
                "sessionStorage clear failed: {result}"
            )));
        }
        Ok(())
    }

    /// Get all sessionStorage entries
    pub async fn get_all_session_storage(&self, page: &Page) -> Result<Vec<StorageEntry>> {
        let script = r#"
            (function() {
                try {
                    var entries = [];
                    for (var i = 0; i < window.sessionStorage.length; i++) {
                        var key = window.sessionStorage.key(i);
                        entries.push({ key: key, value: window.sessionStorage.getItem(key) });
                    }
                    return JSON.stringify(entries);
                } catch(e) {
                    return '[]';
                }
            })()
        "#;
        let result = page.evaluate(script).await?;
        let entries: Vec<StorageEntry> =
            serde_json::from_str(&result).unwrap_or_default();
        Ok(entries)
    }
}

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

    #[test]
    fn test_storage_entry_creation() {
        let entry = StorageEntry {
            key: "token".to_string(),
            value: "abc123".to_string(),
        };
        assert_eq!(entry.key, "token");
        assert_eq!(entry.value, "abc123");
    }

    #[test]
    fn test_storage_entry_serialization() {
        let entry = StorageEntry {
            key: "foo".to_string(),
            value: "bar".to_string(),
        };
        let json = serde_json::to_string(&entry).unwrap();
        assert!(json.contains("foo"));
        assert!(json.contains("bar"));
    }

    #[test]
    fn test_storage_entry_deserialization() {
        let json = r#"{"key":"foo","value":"bar"}"#;
        let entry: StorageEntry = serde_json::from_str(json).unwrap();
        assert_eq!(entry.key, "foo");
        assert_eq!(entry.value, "bar");
    }

    #[test]
    fn test_storage_manager_default() {
        let manager = StorageManager::default();
        let _ = std::format!("{:?}", manager);
    }
}