yew_hooks/hooks/
use_cookie.rs1use std::ops::Deref;
2use std::rc::Rc;
3
4use gloo::utils::document;
5use serde::{Deserialize, Serialize};
6use wasm_bindgen::JsValue;
7use yew::prelude::*;
8
9pub struct UseCookieHandle<T> {
11 inner: UseStateHandle<Option<T>>,
12 key: Rc<String>,
13}
14
15impl<T> UseCookieHandle<T> {
16 pub fn set(&self, value: T)
18 where
19 T: Serialize + Clone,
20 {
21 if let Ok(cookie_str) = serde_json::to_string(&value) {
22 let encoded_value = urlencoding::encode(&cookie_str);
24 let cookie = format!("{}={}", self.key, encoded_value);
25 set_cookie(&cookie);
26 self.inner.set(Some(value));
27 }
28 }
29
30 pub fn set_with_attributes(&self, value: T, attributes: CookieAttributes)
32 where
33 T: Serialize + Clone,
34 {
35 if let Ok(cookie_str) = serde_json::to_string(&value) {
36 let encoded_value = urlencoding::encode(&cookie_str);
38 let mut cookie = format!("{}={}", self.key, encoded_value);
39
40 if let Some(max_age) = attributes.max_age {
41 cookie.push_str(&format!("; max-age={}", max_age));
42 }
43 if let Some(path) = &attributes.path {
44 cookie.push_str(&format!("; path={}", path));
45 }
46 if let Some(domain) = &attributes.domain {
47 cookie.push_str(&format!("; domain={}", domain));
48 }
49 if attributes.secure {
50 cookie.push_str("; secure");
51 }
52 if let Some(same_site) = &attributes.same_site {
55 cookie.push_str(&format!("; SameSite={}", same_site));
56 }
57
58 set_cookie(&cookie);
59 self.inner.set(Some(value));
60 }
61 }
62
63 pub fn delete(&self) {
65 let cookie = format!("{}=; expires=Thu, 01 Jan 1970 00:00:00 GMT", self.key);
67 set_cookie(&cookie);
68 self.inner.set(None);
69 }
70}
71
72impl<T> Deref for UseCookieHandle<T> {
73 type Target = Option<T>;
74
75 fn deref(&self) -> &Self::Target {
76 &self.inner
77 }
78}
79
80impl<T> Clone for UseCookieHandle<T> {
81 fn clone(&self) -> Self {
82 Self {
83 inner: self.inner.clone(),
84 key: self.key.clone(),
85 }
86 }
87}
88
89impl<T> PartialEq for UseCookieHandle<T>
90where
91 T: PartialEq,
92{
93 fn eq(&self, other: &Self) -> bool {
94 *self.inner == *other.inner
95 }
96}
97
98#[derive(Debug, Clone, Default)]
100pub struct CookieAttributes {
101 pub max_age: Option<i64>,
103 pub path: Option<String>,
105 pub domain: Option<String>,
107 pub secure: bool,
109 pub http_only: bool,
112 pub same_site: Option<SameSite>,
114}
115
116#[derive(Debug, Clone)]
118pub enum SameSite {
119 Strict,
121 Lax,
123 None,
125}
126
127impl std::fmt::Display for SameSite {
128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 match self {
130 SameSite::Strict => write!(f, "Strict"),
131 SameSite::Lax => write!(f, "Lax"),
132 SameSite::None => write!(f, "None"),
133 }
134 }
135}
136
137fn set_cookie(cookie: &str) {
139 let doc = document();
140 let _ = js_sys::Reflect::set(
141 &doc,
142 &JsValue::from_str("cookie"),
143 &JsValue::from_str(cookie),
144 );
145}
146
147fn get_cookie_string() -> Option<String> {
149 let doc = document();
150 js_sys::Reflect::get(&doc, &JsValue::from_str("cookie"))
151 .ok()
152 .and_then(|v| v.as_string())
153}
154
155fn get_cookie_value(key: &str) -> Option<String> {
157 get_cookie_string().and_then(|cookie_str| {
158 cookie_str
159 .split(';')
160 .map(|cookie| cookie.trim())
161 .find(|cookie| cookie.starts_with(&format!("{}=", key)))
162 .and_then(|cookie| cookie.split('=').nth(1))
163 .map(|value| value.to_string())
164 })
165}
166
167#[hook]
208pub fn use_cookie<T>(key: String) -> UseCookieHandle<T>
209where
210 T: for<'de> Deserialize<'de> + 'static,
211{
212 let inner: UseStateHandle<Option<T>> = use_state(|| {
213 get_cookie_value(&key).and_then(|value| {
214 let decoded_value = urlencoding::decode(&value).ok()?;
216 serde_json::from_str(&decoded_value).ok()
217 })
218 });
219 let key = use_memo((), |_| key);
220
221 UseCookieHandle { inner, key }
222}