use anyhow::Result;
use chromiumoxide::page::Page;
use std::sync::{Arc, OnceLock};
use tokio::sync::Mutex;
use tracing::info;
use crate::browser::{BrowserWrapper, launch_browser};
static GLOBAL_MANAGER: OnceLock<Arc<BrowserManager>> = OnceLock::new();
pub struct BrowserManager {
browser: Arc<Mutex<Option<BrowserWrapper>>>,
current_page: Arc<Mutex<Option<Page>>>,
}
impl BrowserManager {
#[must_use]
pub fn global() -> Arc<BrowserManager> {
GLOBAL_MANAGER
.get_or_init(|| Arc::new(BrowserManager::new()))
.clone()
}
fn new() -> Self {
Self {
browser: Arc::new(Mutex::new(None)),
current_page: Arc::new(Mutex::new(None)),
}
}
pub async fn get_or_launch(&self) -> Result<Arc<Mutex<Option<BrowserWrapper>>>> {
let mut guard = self.browser.lock().await;
if let Some(wrapper) = guard.as_ref() {
match wrapper.browser().version().await {
Ok(_) => {
tracing::debug!("Browser health check passed, reusing existing browser");
drop(guard); return Ok(self.browser.clone());
}
Err(e) => {
tracing::warn!("Browser health check failed: {}. Triggering recovery...", e);
if let Some(mut crashed_wrapper) = guard.take() {
let _ = crashed_wrapper.browser_mut().close().await;
let _ = crashed_wrapper.browser_mut().wait().await;
crashed_wrapper.cleanup_temp_dir();
}
tracing::info!("Crashed browser cleaned up, launching new instance");
}
}
}
tracing::info!("Launching browser (first time or after recovery)");
let (browser, handler, user_data_dir) = launch_browser().await?;
let wrapper = BrowserWrapper::new(browser, handler, user_data_dir);
*guard = Some(wrapper);
drop(guard);
Ok(self.browser.clone())
}
pub async fn shutdown(&self) -> Result<()> {
let mut guard = self.browser.lock().await;
if let Some(mut wrapper) = guard.take() {
info!("Shutting down browser");
if let Err(e) = wrapper.browser_mut().close().await {
tracing::warn!("Failed to close browser cleanly: {}", e);
}
if let Err(e) = wrapper.browser_mut().wait().await {
tracing::warn!("Failed to wait for browser exit: {}", e);
}
wrapper.cleanup_temp_dir();
drop(wrapper);
}
Ok(())
}
pub async fn get_current_page(&self) -> Option<Page> {
self.current_page.lock().await.clone()
}
pub async fn set_current_page(&self, page: Page) {
*self.current_page.lock().await = Some(page);
}
pub async fn is_browser_running(&self) -> bool {
self.browser.lock().await.is_some()
}
}
impl Drop for BrowserManager {
fn drop(&mut self) {
info!("BrowserManager dropping - browser will be cleaned up");
}
}
#[cfg(feature = "server")]
use kodegen_server_http::ShutdownHook;
#[cfg(feature = "server")]
impl ShutdownHook for BrowserManager {
fn shutdown(&self) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send + '_>> {
Box::pin(async move {
BrowserManager::shutdown(self).await
})
}
}