firebase_rs_sdk/auth/persistence/
mod.rs

1use std::sync::atomic::{AtomicUsize, Ordering};
2use std::sync::{Arc, Mutex};
3
4use serde::{Deserialize, Serialize};
5
6use crate::auth::error::AuthResult;
7
8#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
9pub struct PersistedAuthState {
10    pub user_id: String,
11    pub email: Option<String>,
12    pub refresh_token: Option<String>,
13    pub access_token: Option<String>,
14    /// Expiration timestamp in seconds since the Unix epoch.
15    pub expires_at: Option<i64>,
16}
17
18pub type PersistenceListener = Arc<dyn Fn(Option<PersistedAuthState>) + Send + Sync>;
19
20#[derive(Default)]
21struct InMemoryState {
22    value: Option<PersistedAuthState>,
23    listeners: Vec<(usize, PersistenceListener)>,
24}
25
26pub struct PersistenceSubscription {
27    cleanup: Option<Box<dyn FnOnce() + Send + 'static>>,
28}
29
30impl PersistenceSubscription {
31    /// Creates a subscription with a cleanup callback that runs on drop.
32    pub fn new<F>(cleanup: F) -> Self
33    where
34        F: FnOnce() + Send + 'static,
35    {
36        Self {
37            cleanup: Some(Box::new(cleanup)),
38        }
39    }
40
41    /// Returns a subscription that performs no cleanup work.
42    pub fn noop() -> Self {
43        Self { cleanup: None }
44    }
45}
46
47impl Default for PersistenceSubscription {
48    fn default() -> Self {
49        Self::noop()
50    }
51}
52
53impl Drop for PersistenceSubscription {
54    fn drop(&mut self) {
55        if let Some(cleanup) = self.cleanup.take() {
56            cleanup();
57        }
58    }
59}
60
61/// Storage backend for serialized authentication state.
62///
63/// Library consumers can implement this trait to plug in platform-specific
64/// persistence (filesystem, databases, JS shims, etc.). Callbacks registered via
65/// `subscribe` should emit whenever the underlying state changes outside the
66/// current process so multi-instance listeners stay in sync.
67pub trait AuthPersistence: Send + Sync {
68    fn set(&self, state: Option<PersistedAuthState>) -> AuthResult<()>;
69    fn get(&self) -> AuthResult<Option<PersistedAuthState>>;
70
71    fn subscribe(&self, _listener: PersistenceListener) -> AuthResult<PersistenceSubscription> {
72        Ok(PersistenceSubscription::noop())
73    }
74}
75
76pub struct InMemoryPersistence {
77    state: Arc<Mutex<InMemoryState>>,
78    next_id: AtomicUsize,
79}
80
81impl Default for InMemoryPersistence {
82    fn default() -> Self {
83        Self {
84            state: Arc::new(Mutex::new(InMemoryState::default())),
85            next_id: AtomicUsize::new(1),
86        }
87    }
88}
89
90impl AuthPersistence for InMemoryPersistence {
91    fn set(&self, state: Option<PersistedAuthState>) -> AuthResult<()> {
92        let listeners = {
93            let mut guard = self.state.lock().unwrap();
94            guard.value = state.clone();
95            guard
96                .listeners
97                .iter()
98                .map(|(_, listener)| listener.clone())
99                .collect::<Vec<_>>()
100        };
101
102        for listener in listeners {
103            listener(state.clone());
104        }
105
106        Ok(())
107    }
108
109    fn get(&self) -> AuthResult<Option<PersistedAuthState>> {
110        Ok(self.state.lock().unwrap().value.clone())
111    }
112
113    fn subscribe(&self, listener: PersistenceListener) -> AuthResult<PersistenceSubscription> {
114        let id = self.next_id.fetch_add(1, Ordering::SeqCst);
115        {
116            let mut guard = self.state.lock().unwrap();
117            guard.listeners.push((id, listener));
118        }
119
120        let state = Arc::downgrade(&self.state);
121        Ok(PersistenceSubscription::new(move || {
122            if let Some(state) = state.upgrade() {
123                if let Ok(mut guard) = state.lock() {
124                    guard
125                        .listeners
126                        .retain(|(listener_id, _)| *listener_id != id);
127                }
128            }
129        }))
130    }
131}
132
133type DynSetFn = dyn Fn(Option<PersistedAuthState>) -> AuthResult<()> + Send + Sync;
134type DynGetFn = dyn Fn() -> AuthResult<Option<PersistedAuthState>> + Send + Sync;
135type DynSubscribeFn =
136    dyn Fn(PersistenceListener) -> AuthResult<PersistenceSubscription> + Send + Sync;
137
138pub struct ClosurePersistence {
139    set_fn: Arc<DynSetFn>,
140    get_fn: Arc<DynGetFn>,
141    subscribe_fn: Arc<DynSubscribeFn>,
142}
143
144impl ClosurePersistence {
145    /// Creates a persistence backend from set/get closures.
146    pub fn new<Set, Get>(set: Set, get: Get) -> Self
147    where
148        Set: Fn(Option<PersistedAuthState>) -> AuthResult<()> + Send + Sync + 'static,
149        Get: Fn() -> AuthResult<Option<PersistedAuthState>> + Send + Sync + 'static,
150    {
151        Self::with_subscribe(set, get, |_| Ok(PersistenceSubscription::noop()))
152    }
153
154    /// Creates a persistence backend with custom set/get/subscribe behavior.
155    pub fn with_subscribe<Set, Get, Subscribe>(set: Set, get: Get, subscribe: Subscribe) -> Self
156    where
157        Set: Fn(Option<PersistedAuthState>) -> AuthResult<()> + Send + Sync + 'static,
158        Get: Fn() -> AuthResult<Option<PersistedAuthState>> + Send + Sync + 'static,
159        Subscribe:
160            Fn(PersistenceListener) -> AuthResult<PersistenceSubscription> + Send + Sync + 'static,
161    {
162        Self {
163            set_fn: Arc::new(set),
164            get_fn: Arc::new(get),
165            subscribe_fn: Arc::new(subscribe),
166        }
167    }
168}
169
170#[cfg(all(
171    feature = "wasm-web",
172    target_arch = "wasm32",
173    feature = "experimental-indexed-db"
174))]
175pub mod indexed_db;
176
177#[cfg(not(all(feature = "wasm-web", target_arch = "wasm32")))]
178pub mod file;
179
180impl AuthPersistence for ClosurePersistence {
181    fn set(&self, state: Option<PersistedAuthState>) -> AuthResult<()> {
182        (self.set_fn)(state)
183    }
184
185    fn get(&self) -> AuthResult<Option<PersistedAuthState>> {
186        (self.get_fn)()
187    }
188
189    fn subscribe(&self, listener: PersistenceListener) -> AuthResult<PersistenceSubscription> {
190        (self.subscribe_fn)(listener)
191    }
192}
193
194#[cfg(all(target_arch = "wasm32", feature = "wasm-web"))]
195pub mod web;