dioxus_cookie/lib.rs
1//! Cross-platform cookie management for Dioxus fullstack applications.
2//!
3//! Dioxus apps can target web, desktop, iOS, and Android from a single codebase.
4//! Native platforms lack built-in cookie storage—when a server function sets a cookie,
5//! native apps silently discard it. **dioxus-cookie** provides a unified cookie API that
6//! works identically across all platforms.
7//!
8//! # Quick Start
9//!
10//! ```rust,ignore
11//! fn main() {
12//! dioxus_cookie::init(); // Call before dioxus::launch()
13//! dioxus::launch(App);
14//! }
15//!
16//! #[server]
17//! async fn login(credentials: Credentials) -> Result<(), ServerFnError> {
18//! dioxus_cookie::set("session", &token, &CookieOptions::default())?;
19//! Ok(())
20//! }
21//! ```
22//!
23//! # Platform Behavior
24//!
25//! | Platform | Storage |
26//! |----------|---------|
27//! | Server | HTTP `Set-Cookie` headers |
28//! | Browser | `document.cookie` |
29//! | Desktop | System keyring |
30//! | iOS | Keychain |
31//! | Android | Keystore |
32//!
33//! # Features
34//!
35//! - `server` — Server-side cookie handling via HTTP headers
36//! - `desktop` — Desktop platforms with system keyring storage
37//! - `mobile` — iOS/Android with Keychain/Keystore storage
38//! - `mobile-sim` — Mobile + file fallback for iOS Simulator development
39//! - `file-store` — Encrypted file fallback (see security note below)
40//!
41//! # File Storage Fallback
42//!
43//! The `file-store` feature provides encrypted file-based storage for environments
44//! where the system keychain is unavailable (iOS Simulator, Linux without D-Bus,
45//! CI/CD pipelines, Docker containers).
46//!
47//! **Security limitations:**
48//! - **Debug builds only** — automatically disabled in release builds
49//! - **Obfuscation, not protection** — deters casual inspection but does not
50//! protect against local attackers with file system access
51//! - **Not for production** — production apps must use real keychain storage
52
53#![deny(warnings)]
54#![forbid(unsafe_code)]
55
56mod types;
57
58#[cfg(feature = "server")]
59mod server;
60
61#[cfg(all(feature = "keyring", not(feature = "server")))]
62mod native;
63
64#[cfg(all(feature = "keyring", feature = "file-store", not(feature = "server")))]
65mod file_store;
66
67#[cfg(all(not(feature = "server"), not(feature = "keyring")))]
68mod stubs;
69
70pub use types::*;
71
72macro_rules! platform_dispatch {
73 ($fn_name:ident($($arg:expr),*)) => {{
74 #[cfg(feature = "server")]
75 { server::$fn_name($($arg),*) }
76
77 #[cfg(all(feature = "keyring", not(feature = "server")))]
78 { native::$fn_name($($arg),*) }
79
80 #[cfg(all(not(feature = "server"), not(feature = "keyring")))]
81 { stubs::$fn_name($($arg),*) }
82 }};
83}
84
85/// Initialize the cookie system.
86///
87/// Call this **before** `dioxus::launch()` in your `main()` function.
88/// This sets up the platform-appropriate cookie storage backend.
89///
90/// - **Server**: No-op (cookies handled via HTTP headers)
91/// - **Browser**: No-op (cookies handled via `document.cookie`)
92/// - **Desktop/Mobile**: Initializes system keyring and configures HTTP client
93///
94/// # Example
95///
96/// ```rust,ignore
97/// fn main() {
98/// dioxus_cookie::init();
99/// dioxus::launch(App);
100/// }
101/// ```
102///
103/// # Panics
104///
105/// Panics if called after `dioxus::launch()` has already initialized the HTTP client.
106pub fn init() {
107 #[cfg(all(feature = "keyring", not(feature = "server")))]
108 native::init();
109}
110
111/// Retrieves a cookie value by name.
112///
113/// Returns `None` if:
114/// - The cookie doesn't exist
115/// - The cookie is `HttpOnly` (blocked for security)
116/// - The cookie has expired
117///
118/// # Example
119///
120/// ```rust,ignore
121/// if let Some(theme) = dioxus_cookie::get("theme") {
122/// println!("User prefers: {}", theme);
123/// }
124/// ```
125///
126/// # HttpOnly Cookies
127///
128/// This function respects `HttpOnly` just like browsers do—it returns `None`
129/// for HttpOnly cookies to prevent client-side access. Use [`get_internal`]
130/// only for server-side session restoration.
131pub fn get(name: &str) -> Option<String> {
132 platform_dispatch!(get(name))
133}
134
135/// Retrieves a cookie value, bypassing the `HttpOnly` restriction.
136///
137/// **Warning**: Only use this for server-side operations like session restoration
138/// on native platforms. Never expose HttpOnly cookie values to user-facing code.
139///
140/// # Example
141///
142/// ```rust,ignore
143/// // In app initialization, restore session from stored cookie
144/// if let Some(token) = dioxus_cookie::get_internal("session") {
145/// restore_session(&token).await;
146/// }
147/// ```
148pub fn get_internal(name: &str) -> Option<String> {
149 platform_dispatch!(get_internal(name))
150}
151
152/// Sets a cookie with the given name, value, and options.
153///
154/// # Arguments
155///
156/// * `name` — Cookie name (should be alphanumeric with hyphens/underscores)
157/// * `value` — Cookie value (will be URL-encoded automatically)
158/// * `options` — Cookie attributes (expiration, security flags, etc.)
159///
160/// # Example
161///
162/// ```rust,ignore
163/// use dioxus_cookie::{CookieOptions, SameSite};
164/// use std::time::Duration;
165///
166/// #[server]
167/// async fn login(credentials: Credentials) -> Result<User, ServerFnError> {
168/// let user = authenticate(credentials).await?;
169///
170/// dioxus_cookie::set("session", &user.token, &CookieOptions {
171/// max_age: Some(Duration::from_secs(86400 * 7)),
172/// http_only: true,
173/// secure: true,
174/// same_site: SameSite::Strict,
175/// path: "/".to_string(),
176/// })?;
177///
178/// Ok(user)
179/// }
180/// ```
181///
182/// # Platform Behavior
183///
184/// - **Server**: Sets `Set-Cookie` HTTP header in the response
185/// - **Browser**: Writes to `document.cookie`
186/// - **Desktop/Mobile**: Stores in system keyring
187pub fn set(name: &str, value: &str, options: &CookieOptions) -> Result<(), CookieError> {
188 platform_dispatch!(set(name, value, options))
189}
190
191/// Deletes a cookie by name.
192///
193/// # Example
194///
195/// ```rust,ignore
196/// #[server]
197/// async fn logout() -> Result<(), ServerFnError> {
198/// dioxus_cookie::clear("session")?;
199/// Ok(())
200/// }
201/// ```
202///
203/// # Platform Behavior
204///
205/// - **Server**: Sets cookie with immediate expiration
206/// - **Browser**: Removes from `document.cookie`
207/// - **Desktop/Mobile**: Removes from system keyring
208pub fn clear(name: &str) -> Result<(), CookieError> {
209 platform_dispatch!(clear(name))
210}
211
212/// Lists names of all accessible cookies.
213///
214/// Returns only non-HttpOnly cookies (matching browser behavior).
215/// Expired cookies are automatically excluded.
216///
217/// # Example
218///
219/// ```rust,ignore
220/// let names = dioxus_cookie::list_names();
221/// for name in names {
222/// println!("Cookie: {}", name);
223/// }
224/// ```
225pub fn list_names() -> Vec<String> {
226 platform_dispatch!(list_names())
227}
228
229/// Returns the active storage backend type.
230///
231/// Useful for debugging and diagnostics.
232///
233/// # Returns
234///
235/// - `"server"` — HTTP headers (server-side)
236/// - `"keychain"` — System keyring (desktop/mobile)
237/// - `"file"` — Encrypted file fallback (iOS Simulator, debug only)
238/// - `"browser"` — `document.cookie` (WASM)
239/// - `"stub"` — No-op implementation
240/// - `"uninitialized"` — [`init`] not yet called
241pub fn get_storage_type() -> &'static str {
242 #[cfg(feature = "server")]
243 {
244 "server"
245 }
246
247 #[cfg(all(feature = "keyring", not(feature = "server")))]
248 {
249 native::get_storage_type()
250 }
251
252 #[cfg(all(not(feature = "server"), not(feature = "keyring")))]
253 {
254 stubs::get_storage_type()
255 }
256}