firebase_rs_sdk/app_check/
interop.rs

1use std::sync::{Arc, Mutex};
2
3use crate::app_check::api;
4use crate::app_check::errors::AppCheckResult;
5use crate::app_check::types::{
6    AppCheck, AppCheckInternalListener, AppCheckTokenResult, ListenerHandle, ListenerType,
7};
8use crate::firestore::remote::datastore::TokenProviderArc;
9
10use super::token_provider::app_check_token_provider_arc;
11
12#[derive(Clone)]
13pub struct FirebaseAppCheckInternal {
14    app_check: AppCheck,
15    listeners: Arc<Mutex<Vec<(AppCheckInternalListener, ListenerHandle)>>>,
16}
17
18impl FirebaseAppCheckInternal {
19    pub fn new(app_check: AppCheck) -> Self {
20        Self {
21            app_check,
22            listeners: Arc::new(Mutex::new(Vec::new())),
23        }
24    }
25
26    pub fn app_check(&self) -> &AppCheck {
27        &self.app_check
28    }
29
30    pub fn get_token(&self, force_refresh: bool) -> AppCheckResult<AppCheckTokenResult> {
31        api::get_token(&self.app_check, force_refresh)
32    }
33
34    pub fn get_limited_use_token(&self) -> AppCheckResult<AppCheckTokenResult> {
35        api::get_limited_use_token(&self.app_check)
36    }
37
38    pub fn add_token_listener(&self, listener: AppCheckInternalListener) -> AppCheckResult<()> {
39        let listeners = Arc::clone(&self.listeners);
40        let listener_clone = Arc::clone(&listener);
41        let bridge = Arc::new(move |result: &AppCheckTokenResult| {
42            (*listener_clone)(result.clone());
43        });
44
45        let handle = api::add_token_listener(&self.app_check, bridge, ListenerType::Internal)?;
46        listeners.lock().unwrap().push((listener, handle));
47        Ok(())
48    }
49
50    pub fn remove_token_listener(&self, listener: &AppCheckInternalListener) {
51        let mut listeners = self.listeners.lock().unwrap();
52        if let Some(pos) = listeners
53            .iter()
54            .position(|(stored, _)| Arc::ptr_eq(stored, listener))
55        {
56            let (_, handle) = listeners.remove(pos);
57            handle.unsubscribe();
58        }
59    }
60
61    /// Exposes the internal App Check instance as a Firestore token provider.
62    pub fn token_provider(&self) -> TokenProviderArc {
63        app_check_token_provider_arc(self.clone())
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use crate::app::{FirebaseApp, FirebaseAppConfig, FirebaseOptions};
71    use crate::app_check::api::{initialize_app_check, token_with_ttl};
72    use crate::app_check::types::{AppCheckOptions, AppCheckProvider, AppCheckToken};
73    use crate::component::ComponentContainer;
74    use std::sync::atomic::{AtomicUsize, Ordering};
75    use std::time::Duration;
76
77    #[derive(Clone)]
78    struct TestProvider;
79
80    impl AppCheckProvider for TestProvider {
81        fn get_token(&self) -> AppCheckResult<AppCheckToken> {
82            token_with_ttl("token", Duration::from_secs(60))
83        }
84
85        fn get_limited_use_token(&self) -> AppCheckResult<AppCheckToken> {
86            token_with_ttl("limited", Duration::from_secs(60))
87        }
88    }
89
90    fn test_app(name: &str) -> FirebaseApp {
91        FirebaseApp::new(
92            FirebaseOptions::default(),
93            FirebaseAppConfig::new(name.to_string(), false),
94            ComponentContainer::new(name.to_string()),
95        )
96    }
97
98    fn setup_internal(name: &str) -> FirebaseAppCheckInternal {
99        let app = test_app(name);
100        let provider = Arc::new(TestProvider);
101        let options = AppCheckOptions::new(provider);
102        let app_check = initialize_app_check(Some(app), options).unwrap();
103        FirebaseAppCheckInternal::new(app_check)
104    }
105
106    #[test]
107    fn get_token_returns_value() {
108        let internal = setup_internal("app-check-internal-test");
109        let result = internal.get_token(false).unwrap();
110        assert_eq!(result.token, "token");
111    }
112
113    #[test]
114    fn listener_receives_updates_and_can_be_removed() {
115        let internal = setup_internal("app-check-listener-test");
116        let counter = Arc::new(AtomicUsize::new(0));
117        let listener: AppCheckInternalListener = {
118            let counter = counter.clone();
119            Arc::new(move |result: AppCheckTokenResult| {
120                assert_eq!(result.token, "token");
121                counter.fetch_add(1, Ordering::SeqCst);
122            })
123        };
124
125        // populate token cache
126        internal.get_token(false).unwrap();
127        internal.add_token_listener(listener.clone()).unwrap();
128        assert_eq!(counter.load(Ordering::SeqCst), 1);
129
130        internal.get_token(true).unwrap();
131        assert_eq!(counter.load(Ordering::SeqCst), 2);
132
133        internal.remove_token_listener(&listener);
134        internal.get_token(true).unwrap();
135        assert_eq!(counter.load(Ordering::SeqCst), 2);
136    }
137}