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, AppCheckTokenError, AppCheckTokenResult, ListenerHandle,
7 ListenerType,
8};
9use crate::firestore::TokenProviderArc;
11
12use super::token_provider::app_check_token_provider_arc;
14
15#[derive(Clone)]
16pub struct FirebaseAppCheckInternal {
17 app_check: AppCheck,
18 listeners: Arc<Mutex<Vec<(AppCheckInternalListener, ListenerHandle)>>>,
19}
20
21impl FirebaseAppCheckInternal {
22 pub fn new(app_check: AppCheck) -> Self {
23 Self {
24 app_check,
25 listeners: Arc::new(Mutex::new(Vec::new())),
26 }
27 }
28
29 pub fn app_check(&self) -> &AppCheck {
30 &self.app_check
31 }
32
33 pub async fn get_token(
34 &self,
35 force_refresh: bool,
36 ) -> Result<AppCheckTokenResult, AppCheckTokenError> {
37 api::get_token(&self.app_check, force_refresh).await
38 }
39
40 pub async fn get_limited_use_token(&self) -> Result<AppCheckTokenResult, AppCheckTokenError> {
41 api::get_limited_use_token(&self.app_check).await
42 }
43
44 pub async fn heartbeat_header(&self) -> AppCheckResult<Option<String>> {
45 self.app_check.heartbeat_header().await
46 }
47
48 pub fn add_token_listener(&self, listener: AppCheckInternalListener) -> AppCheckResult<()> {
49 let listeners = Arc::clone(&self.listeners);
50 let listener_clone = Arc::clone(&listener);
51 let bridge = Arc::new(move |result: &AppCheckTokenResult| {
52 (*listener_clone)(result.clone());
53 });
54
55 let handle =
56 api::add_token_listener(&self.app_check, bridge, None, ListenerType::Internal)?;
57 listeners.lock().unwrap().push((listener, handle));
58 Ok(())
59 }
60
61 pub fn remove_token_listener(&self, listener: &AppCheckInternalListener) {
62 let mut listeners = self.listeners.lock().unwrap();
63 if let Some(pos) = listeners
64 .iter()
65 .position(|(stored, _)| Arc::ptr_eq(stored, listener))
66 {
67 let (_, handle) = listeners.remove(pos);
68 handle.unsubscribe();
69 }
70 }
71
72 pub fn token_provider(&self) -> TokenProviderArc {
75 app_check_token_provider_arc(self.clone())
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82 use crate::app::{FirebaseApp, FirebaseAppConfig, FirebaseOptions};
83 use crate::app_check::api::{
84 clear_registry, clear_state_for_tests, initialize_app_check, test_guard, token_with_ttl,
85 };
86 use crate::app_check::types::{
87 box_app_check_future, AppCheckOptions, AppCheckProvider, AppCheckProviderFuture,
88 AppCheckToken,
89 };
90 use crate::component::ComponentContainer;
91 use std::sync::atomic::{AtomicUsize, Ordering};
92 use std::time::Duration;
93
94 #[derive(Clone)]
95 struct TestProvider;
96
97 impl AppCheckProvider for TestProvider {
98 fn get_token(&self) -> AppCheckProviderFuture<'_, AppCheckResult<AppCheckToken>> {
99 box_app_check_future(async { token_with_ttl("token", Duration::from_secs(60)) })
100 }
101
102 fn get_limited_use_token(
103 &self,
104 ) -> AppCheckProviderFuture<'_, AppCheckResult<AppCheckToken>> {
105 box_app_check_future(async { token_with_ttl("limited", Duration::from_secs(60)) })
106 }
107 }
108
109 fn test_app(name: &str) -> FirebaseApp {
110 FirebaseApp::new(
111 FirebaseOptions::default(),
112 FirebaseAppConfig::new(name.to_string(), false),
113 ComponentContainer::new(name.to_string()),
114 )
115 }
116
117 async fn setup_internal(name: &str) -> FirebaseAppCheckInternal {
118 clear_state_for_tests();
119 clear_registry();
120 let app = test_app(name);
121 let provider = Arc::new(TestProvider);
122 let options = AppCheckOptions::new(provider);
123 let app_check = initialize_app_check(Some(app), options)
124 .await
125 .expect("initialize app check");
126 FirebaseAppCheckInternal::new(app_check)
127 }
128
129 #[tokio::test(flavor = "current_thread")]
130 async fn get_token_returns_value() {
131 let _guard = test_guard();
132 let internal = setup_internal("app-check-internal-test").await;
133 let result = internal.get_token(false).await.unwrap();
134 assert_eq!(result.token, "token");
135
136 let heartbeat = internal.heartbeat_header().await.unwrap();
137 assert!(heartbeat.is_none());
138 }
139
140 #[tokio::test(flavor = "current_thread")]
141 async fn listener_receives_updates_and_can_be_removed() {
142 let _guard = test_guard();
143 let internal = setup_internal("app-check-listener-test").await;
144 let counter = Arc::new(AtomicUsize::new(0));
145 let listener: AppCheckInternalListener = {
146 let counter = counter.clone();
147 Arc::new(move |result: AppCheckTokenResult| {
148 assert_eq!(result.token, "token");
149 counter.fetch_add(1, Ordering::SeqCst);
150 })
151 };
152
153 internal.get_token(false).await.unwrap();
155 internal.add_token_listener(listener.clone()).unwrap();
156 assert_eq!(counter.load(Ordering::SeqCst), 1);
157
158 internal.get_token(true).await.unwrap();
159 assert_eq!(counter.load(Ordering::SeqCst), 2);
160
161 internal.remove_token_listener(&listener);
162 internal.get_token(true).await.unwrap();
163 assert_eq!(counter.load(Ordering::SeqCst), 2);
164 }
165}