use std::collections::HashMap;
use super::{Page, ResponseBody};
use crate::error::{Error, Result};
use crate::fetch::{BrowserFetchOutcome, BrowserFetchRequest, BrowserFetchResponse};
use crate::session::{BrowserState, SessionCookie};
impl Page {
async fn cookie_url(&self) -> Result<Option<String>> {
let url = self.url().await?;
Ok(if url == "about:blank" {
None
} else {
Some(url)
})
}
pub async fn fetch(&self, request: BrowserFetchRequest) -> Result<BrowserFetchResponse> {
let script = crate::fetch::fetch_script(&request)?;
self.evaluate(&script).await
}
pub async fn fetch_many(
&self,
requests: Vec<BrowserFetchRequest>,
) -> Result<Vec<BrowserFetchOutcome>> {
if requests.len() > 100 {
return Err(Error::cdp_msg("fetch_many accepts at most 100 requests"));
}
let script = crate::fetch::fetch_many_script(&requests)?;
self.evaluate(&script).await
}
pub async fn cookies(&self) -> Result<Vec<SessionCookie>> {
let cookies = self.session.get_cookies(None).await?;
Ok(cookies
.into_iter()
.map(|c| SessionCookie {
name: c.name,
value: c.value,
domain: c.domain,
path: c.path,
secure: c.secure,
http_only: c.http_only,
same_site: c.same_site,
expires: if c.session { None } else { Some(c.expires) },
})
.collect())
}
pub async fn set_cookie(
&self,
name: &str,
value: &str,
domain: Option<&str>,
path: Option<&str>,
) -> Result<()> {
let url = self.cookie_url().await?;
let success = self
.session
.set_cookie(name, value, url.as_deref(), domain, path)
.await?;
if !success {
return Err(Error::cdp_msg("Failed to set cookie"));
}
Ok(())
}
pub async fn delete_cookie(&self, name: &str, domain: Option<&str>) -> Result<()> {
let url = self.cookie_url().await?;
self.session
.delete_cookies(name, url.as_deref(), domain)
.await
}
pub async fn clear_all_cookies(&self) -> Result<()> {
self.session.clear_all_cookies().await
}
pub async fn set_cookies_bulk(&self, cookies: Vec<SessionCookie>) -> Result<()> {
let cdp_cookies = cookies
.into_iter()
.map(|c| crate::cdp::types::NetworkSetCookie {
name: c.name,
value: c.value,
url: None,
domain: Some(c.domain),
path: Some(c.path),
secure: Some(c.secure),
http_only: Some(c.http_only),
same_site: c.same_site,
expires: c.expires,
})
.collect();
self.session.set_cookies(cdp_cookies).await
}
pub async fn capture_state(&self) -> Result<BrowserState> {
let cookies = self.cookies().await?;
let local_storage = self
.evaluate("Object.fromEntries(Object.entries(localStorage))")
.await?;
let session_storage = self
.evaluate("Object.fromEntries(Object.entries(sessionStorage))")
.await?;
let user_agent = self.evaluate("navigator.userAgent").await?;
let url = self.url().await?;
Ok(BrowserState {
cookies,
local_storage,
session_storage,
user_agent,
url,
})
}
pub async fn restore_state(&self, state: &BrowserState) -> Result<()> {
self.set_user_agent(&state.user_agent).await?;
self.goto(&state.url).await?;
self.clear_all_cookies().await?;
self.set_cookies_bulk(state.cookies.clone()).await?;
let storage = serde_json::to_string(&serde_json::json!({
"localStorage": state.local_storage,
"sessionStorage": state.session_storage,
}))?;
self.execute(&format!(
"(() => {{ const state = {storage}; localStorage.clear(); sessionStorage.clear(); for (const [key, value] of Object.entries(state.localStorage)) localStorage.setItem(key, value); for (const [key, value] of Object.entries(state.sessionStorage)) sessionStorage.setItem(key, value); }})()"
))
.await?;
self.goto(&state.url).await
}
pub async fn set_extra_headers(&self, headers: HashMap<String, String>) -> Result<()> {
self.session.set_extra_headers(headers).await
}
pub async fn clear_extra_headers(&self) -> Result<()> {
self.session.clear_extra_headers().await
}
pub async fn enable_request_capture(&self) -> Result<()> {
self.session.network_enable().await
}
pub async fn disable_request_capture(&self) -> Result<()> {
self.session.network_disable().await
}
pub async fn get_response_body(&self, request_id: &str) -> Result<ResponseBody> {
let (body, base64_encoded) = self.session.get_response_body(request_id).await?;
if base64_encoded {
use base64::Engine;
let bytes = base64::engine::general_purpose::STANDARD
.decode(&body)
.map_err(|e| Error::Decode(e.to_string()))?;
Ok(ResponseBody::Binary(bytes))
} else {
Ok(ResponseBody::Text(body))
}
}
}