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