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.extra_http_headers.iter()
51 .map(|(k, v)| (k.clone(), v.clone()))
52 );
53 }
54
55 // Copy HTTP credentials if set
56 if let Some(ref creds) = self.options.http_credentials {
57 options = options.http_credentials(
58 crate::api::HttpCredentials::new(&creds.username, &creds.password)
59 );
60 }
61
62 // Create API context
63 let api = APIRequestContext::new(options)
64 .await
65 .map_err(|e| ContextError::Internal(e.to_string()))?;
66
67 // Sync cookies from browser to API context
68 let browser_cookies = self.cookies().await?;
69 crate::api::cookies::sync_to_jar(&browser_cookies, api.cookie_jar());
70 debug!("Synced {} browser cookies to API context", browser_cookies.len());
71
72 Ok(api)
73 }
74
75 /// Sync cookies from API responses back to the browser context.
76 ///
77 /// Call this after making API requests that may have set cookies
78 /// (e.g., login endpoints) to ensure the browser has the same cookies.
79 ///
80 /// # Example
81 ///
82 /// ```no_run
83 /// use viewpoint_core::Browser;
84 ///
85 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
86 /// let browser = Browser::launch().headless(true).launch().await?;
87 /// let context = browser.new_context().await?;
88 ///
89 /// // Login via API
90 /// let api = context.request().await?;
91 /// let response = api.post("https://api.example.com/login")
92 /// .json(&serde_json::json!({"user": "admin", "pass": "secret"}))
93 /// .send()
94 /// .await?;
95 ///
96 /// // Sync cookies back to browser (e.g., session cookies from Set-Cookie)
97 /// context.sync_cookies_from_api(&api, "https://api.example.com").await?;
98 ///
99 /// // Now browser pages will have the session cookie
100 /// # Ok(())
101 /// # }
102 /// ```
103 ///
104 /// # Errors
105 ///
106 /// Returns an error if cookie syncing fails.
107 pub async fn sync_cookies_from_api(
108 &self,
109 api: &APIRequestContext,
110 url: &str,
111 ) -> Result<(), ContextError> {
112 if self.is_closed() {
113 return Err(ContextError::Closed);
114 }
115
116 let cookies = crate::api::cookies::extract_from_jar(api.cookie_jar(), url);
117 if !cookies.is_empty() {
118 debug!("Syncing {} cookies from API to browser", cookies.len());
119 self.add_cookies(cookies).await?;
120 }
121
122 Ok(())
123 }
124}