viewpoint_core/context/api/
mod.rs

1//! Context-level API request functionality.
2
3use tracing::debug;
4
5use crate::api::{APIContextOptions, APIRequestContext};
6use crate::error::ContextError;
7
8use super::BrowserContext;
9
10impl BrowserContext {
11    /// Get an API request context associated with this browser context.
12    ///
13    /// The returned `APIRequestContext` can be used to make HTTP requests.
14    /// Cookies from the browser context are automatically synced to the API context.
15    ///
16    /// # Example
17    ///
18    /// ```no_run
19    /// use viewpoint_core::Browser;
20    ///
21    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
22    /// let browser = Browser::launch().headless(true).launch().await?;
23    /// let context = browser.new_context().await?;
24    ///
25    /// // Get API context (includes browser cookies)
26    /// let api = context.request().await?;
27    ///
28    /// // Make API requests with browser cookies
29    /// let response = api.get("https://api.example.com/data").send().await?;
30    /// # Ok(())
31    /// # }
32    /// ```
33    ///
34    /// # Errors
35    ///
36    /// Returns an error if the API context cannot be created.
37    pub async fn request(&self) -> Result<APIRequestContext, ContextError> {
38        if self.is_closed() {
39            return Err(ContextError::Closed);
40        }
41
42        debug!("Creating API request context for browser context");
43
44        // Build options from context settings
45        let mut options = APIContextOptions::new();
46
47        // Copy extra HTTP headers from context
48        if !self.options.extra_http_headers.is_empty() {
49            options = options.extra_http_headers(
50                self.options
51                    .extra_http_headers
52                    .iter()
53                    .map(|(k, v)| (k.clone(), v.clone())),
54            );
55        }
56
57        // Copy HTTP credentials if set
58        if let Some(ref creds) = self.options.http_credentials {
59            options = options.http_credentials(crate::api::HttpCredentials::new(
60                &creds.username,
61                &creds.password,
62            ));
63        }
64
65        // Create API context
66        let api = APIRequestContext::new(options)
67            .await
68            .map_err(|e| ContextError::Internal(e.to_string()))?;
69
70        // Sync cookies from browser to API context
71        let browser_cookies = self.cookies().await?;
72        crate::api::cookies::sync_to_jar(&browser_cookies, api.cookie_jar());
73        debug!(
74            "Synced {} browser cookies to API context",
75            browser_cookies.len()
76        );
77
78        Ok(api)
79    }
80
81    /// Sync cookies from API responses back to the browser context.
82    ///
83    /// Call this after making API requests that may have set cookies
84    /// (e.g., login endpoints) to ensure the browser has the same cookies.
85    ///
86    /// # Example
87    ///
88    /// ```no_run
89    /// use viewpoint_core::Browser;
90    ///
91    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
92    /// let browser = Browser::launch().headless(true).launch().await?;
93    /// let context = browser.new_context().await?;
94    ///
95    /// // Login via API
96    /// let api = context.request().await?;
97    /// let response = api.post("https://api.example.com/login")
98    ///     .json(&serde_json::json!({"user": "admin", "pass": "secret"}))
99    ///     .send()
100    ///     .await?;
101    ///
102    /// // Sync cookies back to browser (e.g., session cookies from Set-Cookie)
103    /// context.sync_cookies_from_api(&api, "https://api.example.com").await?;
104    ///
105    /// // Now browser pages will have the session cookie
106    /// # Ok(())
107    /// # }
108    /// ```
109    ///
110    /// # Errors
111    ///
112    /// Returns an error if cookie syncing fails.
113    pub async fn sync_cookies_from_api(
114        &self,
115        api: &APIRequestContext,
116        url: &str,
117    ) -> Result<(), ContextError> {
118        if self.is_closed() {
119            return Err(ContextError::Closed);
120        }
121
122        let cookies = crate::api::cookies::extract_from_jar(api.cookie_jar(), url);
123        if !cookies.is_empty() {
124            debug!("Syncing {} cookies from API to browser", cookies.len());
125            self.add_cookies(cookies).await?;
126        }
127
128        Ok(())
129    }
130}