Skip to main content

browsing/browser/
storage.rs

1//! Local and session storage access via JavaScript evaluation
2
3use crate::actor::Page;
4use crate::error::{BrowsingError, Result};
5use serde::{Deserialize, Serialize};
6
7/// A single storage entry
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct StorageEntry {
10    /// Storage key
11    pub key: String,
12    /// Storage value
13    pub value: String,
14}
15
16/// Manages localStorage and sessionStorage via page JavaScript evaluation
17#[derive(Debug, Clone, Copy, Default)]
18pub struct StorageManager;
19
20impl StorageManager {
21    /// Create a new storage manager
22    pub fn new() -> Self {
23        Self
24    }
25
26    /// Get a value from localStorage
27    pub async fn get_local_storage(&self, page: &Page, key: &str) -> Result<Option<String>> {
28        let script = format!(
29            "(function() {{ try {{ return window.localStorage.getItem({}) || ''; }} catch(e) {{ return ''; }} }})()",
30            serde_json::json!(key)
31        );
32        let result = page.evaluate(&script).await?;
33        if result.is_empty() {
34            Ok(None)
35        } else {
36            Ok(Some(result))
37        }
38    }
39
40    /// Set a value in localStorage
41    pub async fn set_local_storage(&self, page: &Page, key: &str, value: &str) -> Result<()> {
42        let script = format!(
43            "(function() {{ try {{ window.localStorage.setItem({}, {}); return 'ok'; }} catch(e) {{ return e.message; }} }})()",
44            serde_json::json!(key),
45            serde_json::json!(value)
46        );
47        let result = page.evaluate(&script).await?;
48        if result != "ok" {
49            return Err(BrowsingError::Browser(format!(
50                "localStorage setItem failed: {result}"
51            )));
52        }
53        Ok(())
54    }
55
56    /// Remove a key from localStorage
57    pub async fn remove_local_storage(&self, page: &Page, key: &str) -> Result<()> {
58        let script = format!(
59            "(function() {{ try {{ window.localStorage.removeItem({}); return 'ok'; }} catch(e) {{ return e.message; }} }})()",
60            serde_json::json!(key)
61        );
62        page.evaluate(&script).await?;
63        Ok(())
64    }
65
66    /// Clear all localStorage entries
67    pub async fn clear_local_storage(&self, page: &Page) -> Result<()> {
68        let script = "(function() { try { window.localStorage.clear(); return 'ok'; } catch(e) { return e.message; } })()";
69        let result = page.evaluate(script).await?;
70        if result != "ok" {
71            return Err(BrowsingError::Browser(format!(
72                "localStorage clear failed: {result}"
73            )));
74        }
75        Ok(())
76    }
77
78    /// Get all localStorage entries
79    pub async fn get_all_local_storage(&self, page: &Page) -> Result<Vec<StorageEntry>> {
80        let script = r#"
81            (function() {
82                try {
83                    var entries = [];
84                    for (var i = 0; i < window.localStorage.length; i++) {
85                        var key = window.localStorage.key(i);
86                        entries.push({ key: key, value: window.localStorage.getItem(key) });
87                    }
88                    return JSON.stringify(entries);
89                } catch(e) {
90                    return '[]';
91                }
92            })()
93        "#;
94        let result = page.evaluate(script).await?;
95        let entries: Vec<StorageEntry> =
96            serde_json::from_str(&result).unwrap_or_default();
97        Ok(entries)
98    }
99
100    /// Get a value from sessionStorage
101    pub async fn get_session_storage(&self, page: &Page, key: &str) -> Result<Option<String>> {
102        let script = format!(
103            "(function() {{ try {{ return window.sessionStorage.getItem({}) || ''; }} catch(e) {{ return ''; }} }})()",
104            serde_json::json!(key)
105        );
106        let result = page.evaluate(&script).await?;
107        if result.is_empty() {
108            Ok(None)
109        } else {
110            Ok(Some(result))
111        }
112    }
113
114    /// Set a value in sessionStorage
115    pub async fn set_session_storage(&self, page: &Page, key: &str, value: &str) -> Result<()> {
116        let script = format!(
117            "(function() {{ try {{ window.sessionStorage.setItem({}, {}); return 'ok'; }} catch(e) {{ return e.message; }} }})()",
118            serde_json::json!(key),
119            serde_json::json!(value)
120        );
121        let result = page.evaluate(&script).await?;
122        if result != "ok" {
123            return Err(BrowsingError::Browser(format!(
124                "sessionStorage setItem failed: {result}"
125            )));
126        }
127        Ok(())
128    }
129
130    /// Remove a key from sessionStorage
131    pub async fn remove_session_storage(&self, page: &Page, key: &str) -> Result<()> {
132        let script = format!(
133            "(function() {{ try {{ window.sessionStorage.removeItem({}); return 'ok'; }} catch(e) {{ return e.message; }} }})()",
134            serde_json::json!(key)
135        );
136        page.evaluate(&script).await?;
137        Ok(())
138    }
139
140    /// Clear all sessionStorage entries
141    pub async fn clear_session_storage(&self, page: &Page) -> Result<()> {
142        let script = "(function() { try { window.sessionStorage.clear(); return 'ok'; } catch(e) { return e.message; } })()";
143        let result = page.evaluate(script).await?;
144        if result != "ok" {
145            return Err(BrowsingError::Browser(format!(
146                "sessionStorage clear failed: {result}"
147            )));
148        }
149        Ok(())
150    }
151
152    /// Get all sessionStorage entries
153    pub async fn get_all_session_storage(&self, page: &Page) -> Result<Vec<StorageEntry>> {
154        let script = r#"
155            (function() {
156                try {
157                    var entries = [];
158                    for (var i = 0; i < window.sessionStorage.length; i++) {
159                        var key = window.sessionStorage.key(i);
160                        entries.push({ key: key, value: window.sessionStorage.getItem(key) });
161                    }
162                    return JSON.stringify(entries);
163                } catch(e) {
164                    return '[]';
165                }
166            })()
167        "#;
168        let result = page.evaluate(script).await?;
169        let entries: Vec<StorageEntry> =
170            serde_json::from_str(&result).unwrap_or_default();
171        Ok(entries)
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn test_storage_entry_creation() {
181        let entry = StorageEntry {
182            key: "token".to_string(),
183            value: "abc123".to_string(),
184        };
185        assert_eq!(entry.key, "token");
186        assert_eq!(entry.value, "abc123");
187    }
188
189    #[test]
190    fn test_storage_entry_serialization() {
191        let entry = StorageEntry {
192            key: "foo".to_string(),
193            value: "bar".to_string(),
194        };
195        let json = serde_json::to_string(&entry).unwrap();
196        assert!(json.contains("foo"));
197        assert!(json.contains("bar"));
198    }
199
200    #[test]
201    fn test_storage_entry_deserialization() {
202        let json = r#"{"key":"foo","value":"bar"}"#;
203        let entry: StorageEntry = serde_json::from_str(json).unwrap();
204        assert_eq!(entry.key, "foo");
205        assert_eq!(entry.value, "bar");
206    }
207
208    #[test]
209    fn test_storage_manager_default() {
210        let manager = StorageManager::default();
211        let _ = std::format!("{:?}", manager);
212    }
213}