browsing 0.1.5

Browser automation: navigate, click, extract, screenshot. Standalone browser control via CDP.
Documentation
//! Session lifecycle management

use crate::browser::cdp::{CdpClient, CdpSession};
use crate::browser::launcher::BrowserLauncher;
use crate::browser::profile::BrowserProfile;
use crate::error::{BrowsingError, Result};
use std::sync::Arc;

/// Manager for browser session lifecycle
pub struct SessionManager {
    profile: BrowserProfile,
    cdp_client: Option<Arc<CdpClient>>,
    cdp_url: Option<String>,
    launcher: Option<BrowserLauncher>,
}

impl SessionManager {
    /// Create a new session manager with given profile
    pub fn new(profile: BrowserProfile) -> Self {
        Self {
            profile,
            cdp_client: None,
            cdp_url: None,
            launcher: None,
        }
    }

    /// Set CDP URL to connect to existing browser instead of launching new one
    pub fn with_cdp_url(mut self, cdp_url: String) -> Self {
        self.cdp_url = Some(cdp_url);
        self
    }

    /// Start the browser session (launches browser or connects to existing)
    pub async fn start(&mut self) -> Result<String> {
        let cdp_url = if let Some(ref cdp_url) = self.cdp_url {
            cdp_url.clone()
        } else {
            let mut launcher = BrowserLauncher::new(self.profile.clone());
            let cdp_url = launcher.launch().await?;
            self.launcher = Some(launcher);
            self.cdp_url = Some(cdp_url.clone());
            cdp_url
        };

        let mut client = CdpClient::new(cdp_url.clone());
        client.start().await?;
        let client_arc = Arc::new(client);
        self.cdp_client = Some(Arc::clone(&client_arc));

        Ok(cdp_url)
    }

    /// Stop the browser session and clean up resources
    pub async fn stop(&mut self) -> Result<()> {
        // Send WebSocket Close frame and drop CDP client so background task exits cleanly
        if let Some(client) = self.cdp_client.take() {
            client.close().await;
            drop(client);
        }
        tokio::task::yield_now().await;

        // Stop launcher (kills browser process)
        if let Some(ref mut launcher) = self.launcher {
            launcher.stop().await?;
        }
        self.launcher = None;
        Ok(())
    }

    /// Get the CDP client
    pub fn get_cdp_client(&self) -> Result<Arc<CdpClient>> {
        self.cdp_client
            .as_ref()
            .map(Arc::clone)
            .ok_or_else(|| BrowsingError::Browser("No active CDP client".to_string()))
    }

    /// Check if session is active
    pub fn is_active(&self) -> bool {
        self.cdp_client.is_some()
    }

    /// Create a session for a specific target
    pub async fn create_session(
        &self,
        target_id: String,
        _session_id: Option<String>,
    ) -> Result<CdpSession> {
        let client = self.get_cdp_client()?;
        CdpSession::for_target(client, target_id, None).await
    }
}