viewpoint_core/context/cookies/
mod.rs1use tracing::{debug, instrument};
6
7use viewpoint_cdp::protocol::{CookieParam, CookieSameSite};
8use viewpoint_cdp::protocol::storage::{
9 ClearCookiesParams as StorageClearCookiesParams,
10 DeleteCookiesParams as StorageDeleteCookiesParams, GetCookiesParams as StorageGetCookiesParams,
11 GetCookiesResult as StorageGetCookiesResult, SetCookiesParams as StorageSetCookiesParams,
12};
13
14use super::types::{Cookie, SameSite};
15use super::BrowserContext;
16use crate::error::ContextError;
17
18impl BrowserContext {
19 #[instrument(level = "debug", skip(self, cookies))]
42 pub async fn add_cookies(&self, cookies: Vec<Cookie>) -> Result<(), ContextError> {
43 if self.is_closed() {
44 return Err(ContextError::Closed);
45 }
46
47 debug!(count = cookies.len(), "Adding cookies");
48
49 let cdp_cookies: Vec<CookieParam> = cookies
50 .into_iter()
51 .map(|c| {
52 let mut param = CookieParam::new(&c.name, &c.value);
53 if let Some(url) = c.url {
54 param = param.url(url);
55 }
56 if let Some(domain) = c.domain {
57 param = param.domain(domain);
58 }
59 if let Some(path) = c.path {
60 param = param.path(path);
61 }
62 if let Some(secure) = c.secure {
63 param = param.secure(secure);
64 }
65 if let Some(http_only) = c.http_only {
66 param = param.http_only(http_only);
67 }
68 if let Some(expires) = c.expires {
69 param = param.expires(expires);
70 }
71 if let Some(same_site) = c.same_site {
72 param.same_site = Some(match same_site {
73 SameSite::Strict => CookieSameSite::Strict,
74 SameSite::Lax => CookieSameSite::Lax,
75 SameSite::None => CookieSameSite::None,
76 });
77 }
78 param
79 })
80 .collect();
81
82 self.connection()
83 .send_command::<_, serde_json::Value>(
84 "Storage.setCookies",
85 Some(
86 StorageSetCookiesParams::new(cdp_cookies)
87 .browser_context_id(self.context_id().to_string()),
88 ),
89 None,
90 )
91 .await?;
92
93 Ok(())
94 }
95
96 #[instrument(level = "debug", skip(self))]
102 pub async fn cookies(&self) -> Result<Vec<Cookie>, ContextError> {
103 if self.is_closed() {
104 return Err(ContextError::Closed);
105 }
106
107 let result: StorageGetCookiesResult = self
108 .connection()
109 .send_command(
110 "Storage.getCookies",
111 Some(StorageGetCookiesParams::new().browser_context_id(self.context_id().to_string())),
112 None,
113 )
114 .await?;
115
116 let cookies = result
117 .cookies
118 .into_iter()
119 .map(|c| Cookie {
120 name: c.name,
121 value: c.value,
122 domain: Some(c.domain),
123 path: Some(c.path),
124 url: None,
125 expires: if c.expires > 0.0 {
126 Some(c.expires)
127 } else {
128 None
129 },
130 http_only: Some(c.http_only),
131 secure: Some(c.secure),
132 same_site: c.same_site.map(|s| match s {
133 CookieSameSite::Strict => SameSite::Strict,
134 CookieSameSite::Lax => SameSite::Lax,
135 CookieSameSite::None => SameSite::None,
136 }),
137 })
138 .collect();
139
140 Ok(cookies)
141 }
142
143 #[instrument(level = "debug", skip(self, urls))]
151 pub async fn cookies_for_urls(&self, urls: Vec<String>) -> Result<Vec<Cookie>, ContextError> {
152 if self.is_closed() {
153 return Err(ContextError::Closed);
154 }
155
156 let all_cookies = self.cookies().await?;
158
159 let domains: Vec<String> = urls
161 .iter()
162 .filter_map(|url| {
163 url::Url::parse(url)
164 .ok()
165 .and_then(|u| u.host_str().map(std::string::ToString::to_string))
166 })
167 .collect();
168
169 let cookies = all_cookies
171 .into_iter()
172 .filter(|c| {
173 if let Some(ref cookie_domain) = c.domain {
174 domains.iter().any(|d| {
175 let cookie_domain = cookie_domain.trim_start_matches('.');
177 d == cookie_domain || d.ends_with(&format!(".{cookie_domain}"))
178 })
179 } else {
180 false
181 }
182 })
183 .collect();
184
185 Ok(cookies)
186 }
187
188 pub async fn cookies_for_url(&self, url: impl Into<String>) -> Result<Vec<Cookie>, ContextError> {
194 self.cookies_for_urls(vec![url.into()]).await
195 }
196
197 #[instrument(level = "debug", skip(self))]
203 pub async fn clear_cookies(&self) -> Result<(), ContextError> {
204 if self.is_closed() {
205 return Err(ContextError::Closed);
206 }
207
208 self.connection()
209 .send_command::<_, serde_json::Value>(
210 "Storage.clearCookies",
211 Some(StorageClearCookiesParams::new().browser_context_id(self.context_id().to_string())),
212 None,
213 )
214 .await?;
215
216 Ok(())
217 }
218
219 pub fn clear_cookies_builder(&self) -> ClearCookiesBuilder<'_> {
221 ClearCookiesBuilder::new(self)
222 }
223}
224
225#[derive(Debug)]
227pub struct ClearCookiesBuilder<'a> {
228 context: &'a BrowserContext,
229 name: Option<String>,
230 domain: Option<String>,
231 path: Option<String>,
232}
233
234impl<'a> ClearCookiesBuilder<'a> {
235 pub(crate) fn new(context: &'a BrowserContext) -> Self {
236 Self {
237 context,
238 name: None,
239 domain: None,
240 path: None,
241 }
242 }
243
244 #[must_use]
246 pub fn name(mut self, name: impl Into<String>) -> Self {
247 self.name = Some(name.into());
248 self
249 }
250
251 #[must_use]
253 pub fn domain(mut self, domain: impl Into<String>) -> Self {
254 self.domain = Some(domain.into());
255 self
256 }
257
258 #[must_use]
260 pub fn path(mut self, path: impl Into<String>) -> Self {
261 self.path = Some(path.into());
262 self
263 }
264
265 pub async fn execute(self) -> Result<(), ContextError> {
271 if self.context.is_closed() {
272 return Err(ContextError::Closed);
273 }
274
275 if self.name.is_none() && self.domain.is_none() && self.path.is_none() {
277 return self.context.clear_cookies().await;
278 }
279
280 let cookies = self.context.cookies().await?;
282
283 for cookie in cookies {
284 let matches_name = self
285 .name
286 .as_ref()
287 .is_none_or(|n| cookie.name == *n);
288 let matches_domain = self
289 .domain
290 .as_ref()
291 .is_none_or(|d| cookie.domain.as_deref() == Some(d.as_str()));
292 let matches_path = self
293 .path
294 .as_ref()
295 .is_none_or(|p| cookie.path.as_deref() == Some(p.as_str()));
296
297 if matches_name && matches_domain && matches_path {
298 let mut params = StorageDeleteCookiesParams::new(&cookie.name)
299 .browser_context_id(self.context.context_id().to_string());
300 if let Some(domain) = &cookie.domain {
301 params = params.domain(domain);
302 }
303 if let Some(path) = &cookie.path {
304 params = params.path(path);
305 }
306
307 self.context
308 .connection()
309 .send_command::<_, serde_json::Value>("Storage.deleteCookies", Some(params), None)
310 .await?;
311 }
312 }
313
314 Ok(())
315 }
316}