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