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 {
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 serde_json::json!({
138 "action": "click",
139 "selector": selector,
140 "result": "success"
141 })
142 }
143 BrowserAction::Type { selector, text } => {
144 serde_json::json!({
147 "action": "type",
148 "selector": selector,
149 "text": text,
150 "result": "success"
151 })
152 }
153 BrowserAction::ExecuteScript { script } => {
154 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 serde_json::json!({
170 "action": "wait_for_element",
171 "selector": selector,
172 "found": true,
173 "result": "success"
174 })
175 }
176 BrowserAction::GetTitle {} => {
177 serde_json::json!({
180 "title": "Title"
181 })
182 }
183 BrowserAction::GetUrl {} => {
184 serde_json::json!({
187 "url": "http://example.com"
188 })
189 }
190 BrowserAction::Refresh {} => {
191 serde_json::json!({
194 "action": "refresh",
195 "result": "success"
196 })
197 }
198 BrowserAction::Back {} => {
199 serde_json::json!({
202 "action": "back",
203 "result": "success"
204 })
205 }
206 BrowserAction::Forward {} => {
207 serde_json::json!({
210 "action": "forward",
211 "result": "success"
212 })
213 }
214 BrowserAction::GetText { selector } => {
215 serde_json::json!({
219 "action": "get_text",
220 "selector": selector,
221 "text": ""
222 })
223 }
224 BrowserAction::GetAttribute {
225 selector,
226 attribute,
227 } => {
228 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 serde_json::json!({
246 "action": "screenshot",
247 "path": screenshot_path,
248 "result": "success"
249 })
250 }
251 BrowserAction::ScrollTo { selector } => {
252 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}