use tracing::{debug, instrument};
use viewpoint_cdp::protocol::storage::{
ClearCookiesParams as StorageClearCookiesParams,
DeleteCookiesParams as StorageDeleteCookiesParams, GetCookiesParams as StorageGetCookiesParams,
GetCookiesResult as StorageGetCookiesResult, SetCookiesParams as StorageSetCookiesParams,
};
use viewpoint_cdp::protocol::{CookieParam, CookieSameSite};
use super::BrowserContext;
use super::types::{Cookie, SameSite};
use crate::error::ContextError;
impl BrowserContext {
#[instrument(level = "debug", skip(self, cookies))]
pub async fn add_cookies(&self, cookies: Vec<Cookie>) -> Result<(), ContextError> {
if self.is_closed() {
return Err(ContextError::Closed);
}
debug!(count = cookies.len(), "Adding cookies");
let cdp_cookies: Vec<CookieParam> = cookies
.into_iter()
.map(|c| {
let mut param = CookieParam::new(&c.name, &c.value);
if let Some(url) = c.url {
param = param.url(url);
}
if let Some(domain) = c.domain {
param = param.domain(domain);
}
if let Some(path) = c.path {
param = param.path(path);
}
if let Some(secure) = c.secure {
param = param.secure(secure);
}
if let Some(http_only) = c.http_only {
param = param.http_only(http_only);
}
if let Some(expires) = c.expires {
param = param.expires(expires);
}
if let Some(same_site) = c.same_site {
param.same_site = Some(match same_site {
SameSite::Strict => CookieSameSite::Strict,
SameSite::Lax => CookieSameSite::Lax,
SameSite::None => CookieSameSite::None,
});
}
param
})
.collect();
self.connection()
.send_command::<_, serde_json::Value>(
"Storage.setCookies",
Some(
StorageSetCookiesParams::new(cdp_cookies)
.browser_context_id(self.context_id().to_string()),
),
None,
)
.await?;
Ok(())
}
#[instrument(level = "debug", skip(self))]
pub async fn cookies(&self) -> Result<Vec<Cookie>, ContextError> {
if self.is_closed() {
return Err(ContextError::Closed);
}
let result: StorageGetCookiesResult = self
.connection()
.send_command(
"Storage.getCookies",
Some(
StorageGetCookiesParams::new()
.browser_context_id(self.context_id().to_string()),
),
None,
)
.await?;
let cookies = result
.cookies
.into_iter()
.map(|c| Cookie {
name: c.name,
value: c.value,
domain: Some(c.domain),
path: Some(c.path),
url: None,
expires: if c.expires > 0.0 {
Some(c.expires)
} else {
None
},
http_only: Some(c.http_only),
secure: Some(c.secure),
same_site: c.same_site.map(|s| match s {
CookieSameSite::Strict => SameSite::Strict,
CookieSameSite::Lax => SameSite::Lax,
CookieSameSite::None => SameSite::None,
}),
})
.collect();
Ok(cookies)
}
#[instrument(level = "debug", skip(self, urls))]
pub async fn cookies_for_urls(&self, urls: Vec<String>) -> Result<Vec<Cookie>, ContextError> {
if self.is_closed() {
return Err(ContextError::Closed);
}
let all_cookies = self.cookies().await?;
let domains: Vec<String> = urls
.iter()
.filter_map(|url| {
url::Url::parse(url)
.ok()
.and_then(|u| u.host_str().map(std::string::ToString::to_string))
})
.collect();
let cookies = all_cookies
.into_iter()
.filter(|c| {
if let Some(ref cookie_domain) = c.domain {
domains.iter().any(|d| {
let cookie_domain = cookie_domain.trim_start_matches('.');
d == cookie_domain || d.ends_with(&format!(".{cookie_domain}"))
})
} else {
false
}
})
.collect();
Ok(cookies)
}
pub async fn cookies_for_url(
&self,
url: impl Into<String>,
) -> Result<Vec<Cookie>, ContextError> {
self.cookies_for_urls(vec![url.into()]).await
}
#[instrument(level = "debug", skip(self))]
pub async fn clear_cookies(&self) -> Result<(), ContextError> {
if self.is_closed() {
return Err(ContextError::Closed);
}
self.connection()
.send_command::<_, serde_json::Value>(
"Storage.clearCookies",
Some(
StorageClearCookiesParams::new()
.browser_context_id(self.context_id().to_string()),
),
None,
)
.await?;
Ok(())
}
pub fn clear_cookies_builder(&self) -> ClearCookiesBuilder<'_> {
ClearCookiesBuilder::new(self)
}
}
#[derive(Debug)]
pub struct ClearCookiesBuilder<'a> {
context: &'a BrowserContext,
name: Option<String>,
domain: Option<String>,
path: Option<String>,
}
impl<'a> ClearCookiesBuilder<'a> {
pub(crate) fn new(context: &'a BrowserContext) -> Self {
Self {
context,
name: None,
domain: None,
path: None,
}
}
#[must_use]
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn domain(mut self, domain: impl Into<String>) -> Self {
self.domain = Some(domain.into());
self
}
#[must_use]
pub fn path(mut self, path: impl Into<String>) -> Self {
self.path = Some(path.into());
self
}
pub async fn execute(self) -> Result<(), ContextError> {
if self.context.is_closed() {
return Err(ContextError::Closed);
}
if self.name.is_none() && self.domain.is_none() && self.path.is_none() {
return self.context.clear_cookies().await;
}
let cookies = self.context.cookies().await?;
for cookie in cookies {
let matches_name = self.name.as_ref().is_none_or(|n| cookie.name == *n);
let matches_domain = self
.domain
.as_ref()
.is_none_or(|d| cookie.domain.as_deref() == Some(d.as_str()));
let matches_path = self
.path
.as_ref()
.is_none_or(|p| cookie.path.as_deref() == Some(p.as_str()));
if matches_name && matches_domain && matches_path {
let mut params = StorageDeleteCookiesParams::new(&cookie.name)
.browser_context_id(self.context.context_id().to_string());
if let Some(domain) = &cookie.domain {
params = params.domain(domain);
}
if let Some(path) = &cookie.path {
params = params.path(path);
}
self.context
.connection()
.send_command::<_, serde_json::Value>(
"Storage.deleteCookies",
Some(params),
None,
)
.await?;
}
}
Ok(())
}
}