1use crate::error::Result;
12use crate::protocol::route::FetchOptions;
13use crate::protocol::route::FetchResponse;
14use crate::server::channel::Channel;
15use crate::server::channel_owner::{
16 ChannelOwner, ChannelOwnerImpl, DisposeReason, ParentOrConnection,
17};
18use crate::server::connection::ConnectionLike;
19use serde::de::DeserializeOwned;
20use serde_json::{Value, json};
21use std::any::Any;
22use std::collections::HashMap;
23use std::sync::Arc;
24
25#[derive(Clone)]
35pub struct APIRequestContext {
36 base: ChannelOwnerImpl,
37}
38
39impl APIRequestContext {
40 pub fn new(
41 parent: ParentOrConnection,
42 type_name: String,
43 guid: Arc<str>,
44 initializer: Value,
45 ) -> Result<Self> {
46 Ok(Self {
47 base: ChannelOwnerImpl::new(parent, type_name, guid, initializer),
48 })
49 }
50
51 pub async fn get(
55 &self,
56 url: &str,
57 options: impl Into<Option<FetchOptions>>,
58 ) -> Result<APIResponse> {
59 let options = options.into();
60 let mut opts = options.unwrap_or_default();
61 opts.method = Some("GET".to_string());
62 self.fetch(url, Some(opts)).await
63 }
64
65 pub async fn post(
69 &self,
70 url: &str,
71 options: impl Into<Option<FetchOptions>>,
72 ) -> Result<APIResponse> {
73 let options = options.into();
74 let mut opts = options.unwrap_or_default();
75 opts.method = Some("POST".to_string());
76 self.fetch(url, Some(opts)).await
77 }
78
79 pub async fn put(
83 &self,
84 url: &str,
85 options: impl Into<Option<FetchOptions>>,
86 ) -> Result<APIResponse> {
87 let options = options.into();
88 let mut opts = options.unwrap_or_default();
89 opts.method = Some("PUT".to_string());
90 self.fetch(url, Some(opts)).await
91 }
92
93 pub async fn delete(
97 &self,
98 url: &str,
99 options: impl Into<Option<FetchOptions>>,
100 ) -> Result<APIResponse> {
101 let options = options.into();
102 let mut opts = options.unwrap_or_default();
103 opts.method = Some("DELETE".to_string());
104 self.fetch(url, Some(opts)).await
105 }
106
107 pub async fn patch(
111 &self,
112 url: &str,
113 options: impl Into<Option<FetchOptions>>,
114 ) -> Result<APIResponse> {
115 let options = options.into();
116 let mut opts = options.unwrap_or_default();
117 opts.method = Some("PATCH".to_string());
118 self.fetch(url, Some(opts)).await
119 }
120
121 pub async fn head(
125 &self,
126 url: &str,
127 options: impl Into<Option<FetchOptions>>,
128 ) -> Result<APIResponse> {
129 let options = options.into();
130 let mut opts = options.unwrap_or_default();
131 opts.method = Some("HEAD".to_string());
132 self.fetch(url, Some(opts)).await
133 }
134
135 pub async fn fetch(
142 &self,
143 url: &str,
144 options: impl Into<Option<FetchOptions>>,
145 ) -> Result<APIResponse> {
146 let options = options.into();
147 let opts = options.unwrap_or_default();
148
149 let mut params = json!({
150 "url": url,
151 "timeout": opts.timeout.unwrap_or(crate::DEFAULT_TIMEOUT_MS)
152 });
153
154 if let Some(method) = opts.method {
155 params["method"] = json!(method);
156 }
157 if let Some(headers) = opts.headers {
158 params["headers"] = Value::Array(crate::protocol::route_params::header_array(headers));
159 }
160 if let Some(encoded) =
161 crate::protocol::route_params::post_data_param(opts.post_data, opts.post_data_bytes)
162 {
163 params["postData"] = Value::String(encoded);
164 }
165 if let Some(max_redirects) = opts.max_redirects {
166 params["maxRedirects"] = json!(max_redirects);
167 }
168 if let Some(max_retries) = opts.max_retries {
169 params["maxRetries"] = json!(max_retries);
170 }
171
172 #[derive(serde::Deserialize)]
173 struct FetchResult {
174 response: ApiResponseData,
175 }
176
177 #[derive(serde::Deserialize)]
178 #[serde(rename_all = "camelCase")]
179 struct ApiResponseData {
180 fetch_uid: String,
181 url: String,
182 status: u16,
183 status_text: String,
184 headers: Vec<HeaderEntry>,
185 #[serde(default)]
186 security_details: Option<crate::protocol::response::SecurityDetails>,
187 #[serde(default)]
188 server_addr: Option<crate::protocol::response::RemoteAddr>,
189 #[serde(default)]
190 timing: Option<serde_json::Value>,
191 #[serde(default)]
192 response_end_timing: Option<f64>,
193 }
194
195 #[derive(serde::Deserialize)]
196 struct HeaderEntry {
197 name: String,
198 value: String,
199 }
200
201 let result: FetchResult = self.base.channel().send("fetch", params).await?;
202
203 let headers: HashMap<String, String> = result
204 .response
205 .headers
206 .into_iter()
207 .map(|h| (h.name, h.value))
208 .collect();
209
210 Ok(APIResponse {
211 context: self.clone(),
212 url: result.response.url,
213 status: result.response.status,
214 status_text: result.response.status_text,
215 headers,
216 fetch_uid: result.response.fetch_uid,
217 security_details: result.response.security_details,
218 server_addr: result.response.server_addr,
219 timing: result.response.timing.clone().and_then(|mut t| {
220 crate::protocol::ResourceTiming::merge_response_end(
221 &mut t,
222 result.response.response_end_timing,
223 );
224 crate::protocol::ResourceTiming::from_protocol(&t)
225 }),
226 response_end_timing: result.response.response_end_timing,
227 })
228 }
229
230 #[tracing::instrument(level = "debug", skip_all, fields(count = cookies.len()))]
243 pub async fn add_cookies(&self, cookies: &[crate::protocol::Cookie]) -> Result<()> {
244 self.base
245 .channel()
246 .send_no_result("addCookies", json!({ "cookies": cookies }))
247 .await
248 }
249
250 #[tracing::instrument(level = "debug", skip_all, fields(count = tracing::field::Empty))]
259 pub async fn cookies(&self, urls: Option<&[&str]>) -> Result<Vec<crate::protocol::Cookie>> {
260 #[derive(serde::Deserialize)]
261 struct CookiesResponse {
262 cookies: Vec<crate::protocol::Cookie>,
263 }
264 let response: CookiesResponse = self
265 .base
266 .channel()
267 .send("cookies", json!({ "urls": urls.unwrap_or(&[]) }))
268 .await?;
269 tracing::Span::current().record("count", response.cookies.len());
270 Ok(response.cookies)
271 }
272
273 #[tracing::instrument(level = "debug", skip_all)]
282 pub async fn clear_cookies(
283 &self,
284 options: impl Into<Option<crate::protocol::ClearCookiesOptions>>,
285 ) -> Result<()> {
286 self.base
287 .channel()
288 .send_no_result("clearCookies", options.into().unwrap_or_default())
289 .await
290 }
291
292 pub async fn dispose(&self) -> Result<()> {
298 self.base
299 .channel()
300 .send_no_result("dispose", json!({}))
301 .await
302 }
303
304 pub(crate) async fn inner_fetch(
316 &self,
317 url: &str,
318 options: Option<InnerFetchOptions>,
319 ) -> Result<FetchResponse> {
320 let opts = options.unwrap_or_default();
321
322 let mut params = json!({
323 "url": url,
324 "timeout": opts.timeout.unwrap_or(crate::DEFAULT_TIMEOUT_MS)
325 });
326
327 if let Some(method) = opts.method {
328 params["method"] = json!(method);
329 }
330 if let Some(headers) = opts.headers {
331 params["headers"] = Value::Array(crate::protocol::route_params::header_array(headers));
332 }
333 if let Some(encoded) =
334 crate::protocol::route_params::post_data_param(opts.post_data, opts.post_data_bytes)
335 {
336 params["postData"] = Value::String(encoded);
337 }
338 if let Some(max_redirects) = opts.max_redirects {
339 params["maxRedirects"] = json!(max_redirects);
340 }
341 if let Some(max_retries) = opts.max_retries {
342 params["maxRetries"] = json!(max_retries);
343 }
344
345 #[derive(serde::Deserialize)]
347 struct FetchResult {
348 response: ApiResponseData,
349 }
350
351 #[derive(serde::Deserialize)]
352 #[serde(rename_all = "camelCase")]
353 struct ApiResponseData {
354 fetch_uid: String,
355 #[allow(dead_code)]
356 url: String,
357 status: u16,
358 status_text: String,
359 headers: Vec<HeaderEntry>,
360 }
361
362 #[derive(serde::Deserialize)]
363 struct HeaderEntry {
364 name: String,
365 value: String,
366 }
367
368 let result: FetchResult = self.base.channel().send("fetch", params).await?;
369
370 let body = self.fetch_response_body(&result.response.fetch_uid).await?;
372
373 let _ = self.dispose_api_response(&result.response.fetch_uid).await;
375
376 Ok(FetchResponse {
377 status: result.response.status,
378 status_text: result.response.status_text,
379 headers: result
380 .response
381 .headers
382 .into_iter()
383 .map(|h| (h.name, h.value))
384 .collect(),
385 body,
386 })
387 }
388
389 async fn fetch_response_body(&self, fetch_uid: &str) -> Result<Vec<u8>> {
391 #[derive(serde::Deserialize)]
392 struct BodyResult {
393 #[serde(default)]
394 binary: Option<String>,
395 }
396
397 let result: BodyResult = self
398 .base
399 .channel()
400 .send("fetchResponseBody", json!({ "fetchUid": fetch_uid }))
401 .await?;
402
403 match result.binary {
404 Some(encoded) if !encoded.is_empty() => {
405 use base64::Engine;
406 base64::engine::general_purpose::STANDARD
407 .decode(&encoded)
408 .map_err(|e| {
409 crate::error::Error::ProtocolError(format!(
410 "Failed to decode response body: {}",
411 e
412 ))
413 })
414 }
415 _ => Ok(vec![]),
416 }
417 }
418
419 async fn dispose_api_response(&self, fetch_uid: &str) -> Result<()> {
421 self.base
422 .channel()
423 .send_no_result("disposeAPIResponse", json!({ "fetchUid": fetch_uid }))
424 .await
425 }
426}
427
428#[derive(Clone)]
435pub struct APIResponse {
436 context: APIRequestContext,
437 url: String,
438 status: u16,
439 status_text: String,
440 headers: HashMap<String, String>,
441 fetch_uid: String,
442 security_details: Option<crate::protocol::response::SecurityDetails>,
443 server_addr: Option<crate::protocol::response::RemoteAddr>,
444 timing: Option<crate::protocol::ResourceTiming>,
445 response_end_timing: Option<f64>,
446}
447
448impl APIResponse {
449 pub fn url(&self) -> &str {
451 &self.url
452 }
453
454 pub fn status(&self) -> u16 {
456 self.status
457 }
458
459 pub fn status_text(&self) -> &str {
461 &self.status_text
462 }
463
464 pub fn ok(&self) -> bool {
466 (200..300).contains(&self.status)
467 }
468
469 pub fn headers(&self) -> &HashMap<String, String> {
471 &self.headers
472 }
473
474 pub fn timing(&self) -> Option<&crate::protocol::ResourceTiming> {
488 self.timing.as_ref()
489 }
490
491 pub fn response_end_timing(&self) -> Option<f64> {
494 self.response_end_timing
495 }
496
497 pub fn security_details(&self) -> Option<&crate::protocol::response::SecurityDetails> {
502 self.security_details.as_ref()
503 }
504
505 pub fn server_addr(&self) -> Option<&crate::protocol::response::RemoteAddr> {
510 self.server_addr.as_ref()
511 }
512
513 pub async fn body(&self) -> Result<Vec<u8>> {
517 self.context.fetch_response_body(&self.fetch_uid).await
518 }
519
520 pub async fn text(&self) -> Result<String> {
524 let bytes = self.body().await?;
525 String::from_utf8(bytes).map_err(|e| {
526 crate::error::Error::ProtocolError(format!("Response body is not valid UTF-8: {}", e))
527 })
528 }
529
530 pub async fn json<T: DeserializeOwned>(&self) -> Result<T> {
534 let bytes = self.body().await?;
535 serde_json::from_slice(&bytes).map_err(|e| {
536 crate::error::Error::ProtocolError(format!("Failed to parse response JSON: {}", e))
537 })
538 }
539
540 pub async fn dispose(&self) -> Result<()> {
544 self.context.dispose_api_response(&self.fetch_uid).await
545 }
546}
547
548impl std::fmt::Debug for APIResponse {
549 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
550 f.debug_struct("APIResponse")
551 .field("url", &self.url)
552 .field("status", &self.status)
553 .field("status_text", &self.status_text)
554 .finish()
555 }
556}
557
558#[derive(Debug, Clone, Default)]
562#[non_exhaustive]
563pub struct APIRequestContextOptions {
564 pub base_url: Option<String>,
566 pub extra_http_headers: Option<HashMap<String, String>>,
568 pub ignore_https_errors: Option<bool>,
570 pub user_agent: Option<String>,
572 pub timeout: Option<f64>,
574 pub http_credentials: Option<Vec<crate::protocol::HttpCredentials>>,
579}
580
581impl APIRequestContextOptions {
582 pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
584 self.base_url = Some(base_url.into());
585 self
586 }
587 pub fn extra_http_headers(mut self, headers: HashMap<String, String>) -> Self {
589 self.extra_http_headers = Some(headers);
590 self
591 }
592 pub fn ignore_https_errors(mut self, ignore: bool) -> Self {
594 self.ignore_https_errors = Some(ignore);
595 self
596 }
597 pub fn http_credentials(mut self, credentials: Vec<crate::protocol::HttpCredentials>) -> Self {
599 self.http_credentials = Some(credentials);
600 self
601 }
602 pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
604 self.user_agent = Some(user_agent.into());
605 self
606 }
607 pub fn timeout(mut self, timeout: f64) -> Self {
609 self.timeout = Some(timeout);
610 self
611 }
612}
613
614pub struct APIRequest {
645 channel: crate::server::channel::Channel,
646 connection: Arc<dyn ConnectionLike>,
647}
648
649impl APIRequest {
650 pub(crate) fn new(
651 channel: crate::server::channel::Channel,
652 connection: Arc<dyn ConnectionLike>,
653 ) -> Self {
654 Self {
655 channel,
656 connection,
657 }
658 }
659
660 pub async fn new_context(
668 &self,
669 options: impl Into<Option<APIRequestContextOptions>>,
670 ) -> Result<APIRequestContext> {
671 use crate::server::connection::ConnectionExt;
672
673 let options = options.into();
674 let mut params = json!({});
675
676 if let Some(opts) = options {
677 if let Some(base_url) = opts.base_url {
678 params["baseURL"] = json!(base_url);
679 }
680 if let Some(headers) = opts.extra_http_headers {
681 params["extraHTTPHeaders"] =
682 Value::Array(crate::protocol::route_params::header_array(headers));
683 }
684 if let Some(ignore) = opts.ignore_https_errors {
685 params["ignoreHTTPSErrors"] = json!(ignore);
686 }
687 if let Some(ua) = opts.user_agent {
688 params["userAgent"] = json!(ua);
689 }
690 if let Some(credentials) = opts.http_credentials {
691 params["httpCredentials"] = json!(credentials);
692 }
693 if let Some(timeout) = opts.timeout {
694 params["timeout"] = json!(timeout);
695 }
696 }
697
698 #[derive(serde::Deserialize)]
699 struct NewRequestResult {
700 request: GuidRef,
701 }
702
703 #[derive(serde::Deserialize)]
704 struct GuidRef {
705 guid: String,
706 }
707
708 let result: NewRequestResult = self.channel.send("newRequest", params).await?;
709
710 self.connection
711 .get_typed::<APIRequestContext>(&result.request.guid)
712 .await
713 }
714}
715
716impl std::fmt::Debug for APIRequest {
717 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
718 f.debug_struct("APIRequest").finish()
719 }
720}
721
722#[derive(Debug, Clone, Default)]
724pub(crate) struct InnerFetchOptions {
725 pub method: Option<String>,
726 pub headers: Option<std::collections::HashMap<String, String>>,
727 pub post_data: Option<String>,
728 pub post_data_bytes: Option<Vec<u8>>,
729 pub max_redirects: Option<u32>,
730 pub max_retries: Option<u32>,
731 pub timeout: Option<f64>,
732}
733
734impl ChannelOwner for APIRequestContext {
735 fn guid(&self) -> &str {
736 self.base.guid()
737 }
738
739 fn type_name(&self) -> &str {
740 self.base.type_name()
741 }
742
743 fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
744 self.base.parent()
745 }
746
747 fn connection(&self) -> Arc<dyn ConnectionLike> {
748 self.base.connection()
749 }
750
751 fn initializer(&self) -> &Value {
752 self.base.initializer()
753 }
754
755 fn channel(&self) -> &Channel {
756 self.base.channel()
757 }
758
759 fn dispose(&self, reason: DisposeReason) {
760 self.base.dispose(reason)
761 }
762
763 fn adopt(&self, child: Arc<dyn ChannelOwner>) {
764 self.base.adopt(child)
765 }
766
767 fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
768 self.base.add_child(guid, child)
769 }
770
771 fn remove_child(&self, guid: &str) {
772 self.base.remove_child(guid)
773 }
774
775 fn on_event(&self, method: &str, params: Value) {
776 self.base.on_event(method, params)
777 }
778
779 fn was_collected(&self) -> bool {
780 self.base.was_collected()
781 }
782
783 fn as_any(&self) -> &dyn Any {
784 self
785 }
786}
787
788impl std::fmt::Debug for APIRequestContext {
789 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
790 f.debug_struct("APIRequestContext")
791 .field("guid", &self.guid())
792 .finish()
793 }
794}