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}