firebase_rs_sdk/app_check/
interop.rs1use 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 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 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}