Skip to main content

ditto_os/browser/
manager.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3use tokio::sync::RwLock;
4use tracing::{debug, error, info};
5use uuid::Uuid;
6
7use super::types::*;
8
9pub struct BrowserManager {
10    sessions: Arc<RwLock<HashMap<String, BrowserSession>>>,
11    browsers: Arc<RwLock<HashMap<String, chromiumoxide::Browser>>>,
12    pages: Arc<RwLock<HashMap<String, chromiumoxide::Page>>>,
13}
14
15impl Default for BrowserManager {
16    fn default() -> Self {
17        Self::new()
18    }
19}
20
21#[allow(dead_code)]
22impl BrowserManager {
23    pub fn new() -> Self {
24        Self {
25            sessions: Arc::new(RwLock::new(HashMap::new())),
26            browsers: Arc::new(RwLock::new(HashMap::new())),
27            pages: Arc::new(RwLock::new(HashMap::new())),
28        }
29    }
30
31    pub async fn init_browsers(&self) -> Result<(), anyhow::Error> {
32        info!("Initializing chromiumoxide browsers...");
33        info!("Chromiumoxide browsers initialized successfully");
34        Ok(())
35    }
36
37    pub async fn create_session(
38        &self,
39        agent_id: String,
40        browser_type: BrowserType,
41    ) -> Result<String, anyhow::Error> {
42        let session_id = Uuid::new_v4().to_string();
43
44        info!(
45            "Creating browser session {} for agent {} with {:?}",
46            session_id, agent_id, browser_type
47        );
48
49        let _browsers = self.browsers.write().await;
50        let _pages = self.pages.write().await;
51        let _sessions = self.sessions.write().await;
52
53        if let Err(e) = self.start_browser_container(&session_id).await {
54            error!(
55                "Failed to start browser container for {}: {}",
56                session_id, e
57            );
58            let mut browsers = self.browsers.write().await;
59            browsers.remove(&session_id);
60            let mut pages = self.pages.write().await;
61            pages.remove(&session_id);
62            let mut sessions = self.sessions.write().await;
63            sessions.remove(&session_id);
64            return Err(e);
65        }
66
67        info!("Browser session {} created successfully", session_id);
68        Ok(session_id)
69    }
70
71    pub async fn get_session(&self, session_id: &str) -> Option<BrowserSession> {
72        let sessions = self.sessions.read().await;
73        sessions.get(session_id).cloned()
74    }
75
76    pub async fn list_sessions(&self, agent_id: Option<&str>) -> Vec<BrowserSession> {
77        let sessions = self.sessions.read().await;
78
79        sessions
80            .values()
81            .filter(|session| agent_id.is_none_or(|id| session.agent_id == id))
82            .cloned()
83            .collect()
84    }
85
86    pub async fn execute_command(
87        &self,
88        session_id: &str,
89        command: BrowserCommand,
90    ) -> Result<CommandResult, anyhow::Error> {
91        let start_time = std::time::Instant::now();
92
93        let sessions = self.sessions.read().await;
94        if let Some(session) = sessions.get(session_id) {
95            if session.status != SessionStatus::Running {
96                return Err(anyhow::anyhow!("Session {} is not running", session_id));
97            }
98        } else {
99            return Err(anyhow::anyhow!("Session {} not found", session_id));
100        }
101        drop(sessions);
102
103        debug!(
104            "Executing command {:?} on session {}",
105            command.action, session_id
106        );
107
108        let pages = self.pages.read().await;
109        let _page = pages
110            .get(session_id)
111            .ok_or_else(|| anyhow::anyhow!("Page not found for session: {}", session_id))?;
112        drop(pages);
113
114        let result: serde_json::Value = match &command.action {
115            BrowserAction::Navigate { url } => {
116                let _page = self.pages.read().await;
117                // page.goto(url).await?;
118                // let title = page.get_title().await?;
119
120                {
121                    let mut sessions = self.sessions.write().await;
122                    if let Some(session) = sessions.get_mut(session_id) {
123                        session.url = Some(url.clone());
124                        session.title = Some("Navigated".to_string());
125                    }
126                }
127
128                serde_json::json!({
129                    "url": url,
130                    "title": "Navigated",
131                    "status": "loaded"
132                })
133            }
134            BrowserAction::Click { selector } => {
135                // page.find_element(selector).await?.click().await?;
136
137                serde_json::json!({
138                    "action": "click",
139                    "selector": selector,
140                    "result": "success"
141                })
142            }
143            BrowserAction::Type { selector, text } => {
144                // page.find_element(selector).await?.type_str(text).await?;
145
146                serde_json::json!({
147                    "action": "type",
148                    "selector": selector,
149                    "text": text,
150                    "result": "success"
151                })
152            }
153            BrowserAction::ExecuteScript { script } => {
154                // let result: serde_json::Value = page.evaluate(script, false).await?;
155
156                serde_json::json!({
157                    "action": "execute_script",
158                    "script": script,
159                    "result": serde_json::Value::Null,
160                    "status": "executed"
161                })
162            }
163            BrowserAction::WaitForElement {
164                selector,
165                timeout_ms: _,
166            } => {
167                // page.wait_for_element(selector).await?;
168
169                serde_json::json!({
170                    "action": "wait_for_element",
171                    "selector": selector,
172                    "found": true,
173                    "result": "success"
174                })
175            }
176            BrowserAction::GetTitle {} => {
177                // let title = page.get_title().await?;
178
179                serde_json::json!({
180                    "title": "Title"
181                })
182            }
183            BrowserAction::GetUrl {} => {
184                // let url = page.get_url().await?;
185
186                serde_json::json!({
187                    "url": "http://example.com"
188                })
189            }
190            BrowserAction::Refresh {} => {
191                // page.reload().await?;
192
193                serde_json::json!({
194                    "action": "refresh",
195                    "result": "success"
196                })
197            }
198            BrowserAction::Back {} => {
199                // page.go_back().await?;
200
201                serde_json::json!({
202                    "action": "back",
203                    "result": "success"
204                })
205            }
206            BrowserAction::Forward {} => {
207                // page.go_forward().await?;
208
209                serde_json::json!({
210                    "action": "forward",
211                    "result": "success"
212                })
213            }
214            BrowserAction::GetText { selector } => {
215                // let element = page.find_element(selector).await?;
216                // let text = element.inner_text().await?;
217
218                serde_json::json!({
219                    "action": "get_text",
220                    "selector": selector,
221                    "text": ""
222                })
223            }
224            BrowserAction::GetAttribute {
225                selector,
226                attribute,
227            } => {
228                // let element = page.find_element(selector).await?;
229                // let attr_value = element.get_attribute(attribute).await?;
230
231                serde_json::json!({
232                    "action": "get_attribute",
233                    "selector": selector,
234                    "attribute": attribute,
235                    "value": serde_json::Value::Null
236                })
237            }
238            BrowserAction::Screenshot { path } => {
239                let screenshot_path = match path {
240                    Some(p) => p.clone(),
241                    None => format!("screenshot_{}.png", session_id),
242                };
243                // page.save_screenshot(Path::new(&screenshot_path), CaptureScreenshotFormat::Png).await?;
244
245                serde_json::json!({
246                    "action": "screenshot",
247                    "path": screenshot_path,
248                    "result": "success"
249                })
250            }
251            BrowserAction::ScrollTo { selector } => {
252                // if let Some(sel) = selector {
253                //     page.find_element(sel).await?.scroll_into_view().await?;
254                // } else {
255                //     page.evaluate("window.scrollTo(0, 0);", false).await?;
256                // }
257
258                serde_json::json!({
259                    "action": "scroll_to",
260                    "selector": selector,
261                    "result": "success"
262                })
263            }
264        };
265
266        let execution_time = start_time.elapsed().as_millis() as u64;
267
268        {
269            let mut sessions = self.sessions.write().await;
270            if let Some(session) = sessions.get_mut(session_id) {
271                session.last_activity = chrono::Utc::now();
272            }
273        }
274
275        let command_result = CommandResult {
276            success: true,
277            data: result,
278            error: None,
279            execution_time_ms: execution_time,
280        };
281
282        Ok(command_result)
283    }
284
285    pub async fn close_session(&self, session_id: &str) -> Result<(), anyhow::Error> {
286        info!("Closing browser session: {}", session_id);
287
288        {
289            let mut browsers = self.browsers.write().await;
290            let mut pages = self.pages.write().await;
291            browsers.remove(session_id);
292            pages.remove(session_id);
293            info!("Chromiumoxide browser closed for session: {}", session_id);
294        }
295
296        {
297            let mut sessions = self.sessions.write().await;
298            sessions.remove(session_id);
299        }
300
301        info!("Browser session {} closed successfully", session_id);
302        Ok(())
303    }
304
305    async fn start_browser_container(&self, session_id: &str) -> Result<(), anyhow::Error> {
306        debug!("Starting browser instance for session: {}", session_id);
307
308        let pages = self.pages.read().await;
309        let _page = pages.get(session_id);
310        drop(pages);
311
312        {
313            let mut sessions = self.sessions.write().await;
314            if let Some(session) = sessions.get_mut(session_id) {
315                session.status = SessionStatus::Running;
316                session.url = Some("about:blank".to_string());
317                session.title = Some("Blank Page".to_string());
318            }
319        }
320
321        info!(
322            "Browser container started successfully for session: {}",
323            session_id
324        );
325
326        Ok(())
327    }
328
329    async fn stop_browser_container(&self, session_id: &str) -> Result<(), anyhow::Error> {
330        debug!("Stopping browser instance for session: {}", session_id);
331
332        {
333            let mut browsers = self.browsers.write().await;
334            let mut pages = self.pages.write().await;
335            browsers.remove(session_id);
336            pages.remove(session_id);
337            info!("Chromiumoxide browser closed for session: {}", session_id);
338        }
339
340        Ok(())
341    }
342
343    async fn update_session_activity(&self, session_id: &str) {
344        let mut sessions = self.sessions.write().await;
345        if let Some(session) = sessions.get_mut(session_id) {
346            session.last_activity = chrono::Utc::now();
347        }
348    }
349
350    async fn cleanup_idle_sessions(
351        &self,
352        idle_timeout_minutes: u64,
353    ) -> Result<usize, anyhow::Error> {
354        let now = chrono::Utc::now();
355        let timeout = chrono::Duration::minutes(idle_timeout_minutes as i64);
356
357        let mut sessions_to_close: Vec<String> = Vec::new();
358
359        {
360            let sessions = self.sessions.read().await;
361            for (session_id, session) in sessions.iter() {
362                let duration_since_activity = now.signed_duration_since(session.last_activity);
363                if duration_since_activity > timeout && session.status == SessionStatus::Running {
364                    sessions_to_close.push(session_id.clone());
365                }
366            }
367        }
368
369        for session_id in &sessions_to_close {
370            if let Err(e) = self.close_session(session_id).await {
371                error!("Failed to close idle session {}: {}", session_id, e);
372            } else {
373                info!("Closed idle session: {}", session_id);
374            }
375        }
376
377        Ok(sessions_to_close.len())
378    }
379
380    pub async fn get_session_stats(&self) -> Result<SessionStats, anyhow::Error> {
381        let sessions = self.sessions.read().await;
382
383        let mut browser_type_counts = HashMap::new();
384        let mut running_count = 0;
385        let mut idle_count = 0;
386        let mut error_count = 0;
387
388        for session in sessions.values() {
389            let browser_type_str = format!("{:?}", session.browser_type);
390            *browser_type_counts.entry(browser_type_str).or_insert(0) += 1;
391
392            match session.status {
393                SessionStatus::Running => running_count += 1,
394                SessionStatus::Idle => idle_count += 1,
395                SessionStatus::Error => error_count += 1,
396                _ => {}
397            }
398        }
399
400        Ok(SessionStats {
401            total_sessions: sessions.len(),
402            running_sessions: running_count,
403            idle_sessions: idle_count,
404            error_sessions: error_count,
405            browser_type_counts,
406        })
407    }
408}