1use std::collections::HashMap;
2use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
3use std::sync::{Arc, Mutex};
4
5use crate::app::errors::{AppError, AppResult};
6use crate::component::constants::DEFAULT_ENTRY_NAME;
7use crate::component::types::DynService;
8use crate::component::{Component, ComponentContainer};
9use crate::platform::environment;
10use crate::platform::runtime::spawn_detached;
11
12#[allow(dead_code)]
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct VersionService {
15 pub library: String,
16 pub version: String,
17}
18
19#[allow(dead_code)]
20pub trait PlatformLoggerService: Send + Sync {
21 fn platform_info_string(&self) -> String;
22}
23
24#[cfg_attr(
25 all(feature = "wasm-web", target_arch = "wasm32"),
26 async_trait::async_trait(?Send)
27)]
28#[cfg_attr(
29 not(all(feature = "wasm-web", target_arch = "wasm32")),
30 async_trait::async_trait
31)]
32pub trait HeartbeatService: Send + Sync {
33 async fn trigger_heartbeat(&self) -> AppResult<()>;
34 #[allow(dead_code)]
35 async fn heartbeats_header(&self) -> AppResult<Option<String>>;
36}
37
38#[cfg_attr(
39 all(feature = "wasm-web", target_arch = "wasm32"),
40 async_trait::async_trait(?Send)
41)]
42#[cfg_attr(
43 not(all(feature = "wasm-web", target_arch = "wasm32")),
44 async_trait::async_trait
45)]
46pub trait HeartbeatStorage: Send + Sync {
47 async fn read(&self) -> AppResult<HeartbeatsInStorage>;
48 async fn overwrite(&self, value: &HeartbeatsInStorage) -> AppResult<()>;
49}
50
51#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
52pub struct HeartbeatsInStorage {
53 pub last_sent_heartbeat_date: Option<String>,
54 pub heartbeats: Vec<SingleDateHeartbeat>,
55}
56
57#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
58pub struct SingleDateHeartbeat {
59 pub agent: String,
60 pub date: String,
61}
62
63#[derive(Clone, Debug, Default, PartialEq, Eq)]
64pub struct FirebaseOptions {
65 pub api_key: Option<String>,
66 pub auth_domain: Option<String>,
67 pub database_url: Option<String>,
68 pub project_id: Option<String>,
69 pub storage_bucket: Option<String>,
70 pub messaging_sender_id: Option<String>,
71 pub app_id: Option<String>,
72 pub measurement_id: Option<String>,
73}
74
75#[derive(Clone, Debug, PartialEq, Eq, Default)]
76pub struct FirebaseAppSettings {
77 pub name: Option<String>,
78 pub automatic_data_collection_enabled: Option<bool>,
79}
80
81#[derive(Clone, Debug, PartialEq, Eq)]
82pub struct FirebaseAppConfig {
83 pub name: Arc<str>,
84 pub automatic_data_collection_enabled: bool,
85}
86
87#[derive(Clone, Debug, PartialEq, Eq, Default)]
88pub struct FirebaseServerAppSettings {
89 pub automatic_data_collection_enabled: Option<bool>,
90 pub auth_id_token: Option<String>,
91 pub app_check_token: Option<String>,
92 pub release_on_deref: Option<bool>,
93}
94
95#[derive(Clone)]
96pub struct FirebaseApp {
97 inner: Arc<FirebaseAppInner>,
98}
99
100struct FirebaseAppInner {
101 options: FirebaseOptions,
102 config: FirebaseAppConfig,
103 automatic_data_collection_enabled: Mutex<bool>,
104 is_deleted: AtomicBool,
105 container: ComponentContainer,
106}
107
108impl FirebaseApp {
109 pub fn new(
111 options: FirebaseOptions,
112 config: FirebaseAppConfig,
113 container: ComponentContainer,
114 ) -> Self {
115 let automatic = config.automatic_data_collection_enabled;
116 let inner = Arc::new(FirebaseAppInner {
117 options,
118 config,
119 automatic_data_collection_enabled: Mutex::new(automatic),
120 is_deleted: AtomicBool::new(false),
121 container,
122 });
123 let app = Self {
124 inner: inner.clone(),
125 };
126 let dyn_service: DynService = Arc::new(app.clone());
127 app.inner.container.attach_root_service(dyn_service);
128 app
129 }
130
131 pub fn name(&self) -> &str {
133 &self.inner.config.name
134 }
135
136 pub fn options(&self) -> FirebaseOptions {
138 self.inner.options.clone()
139 }
140
141 pub fn config(&self) -> FirebaseAppConfig {
143 self.inner.config.clone()
144 }
145
146 pub fn automatic_data_collection_enabled(&self) -> bool {
148 *self
149 .inner
150 .automatic_data_collection_enabled
151 .lock()
152 .unwrap_or_else(|poison| poison.into_inner())
153 }
154
155 pub fn set_automatic_data_collection_enabled(&self, value: bool) {
157 *self
158 .inner
159 .automatic_data_collection_enabled
160 .lock()
161 .unwrap_or_else(|poison| poison.into_inner()) = value;
162 }
163
164 pub fn container(&self) -> ComponentContainer {
166 self.inner.container.clone()
167 }
168
169 pub fn add_component(&self, component: Component) -> AppResult<()> {
171 self.check_destroyed()?;
172 self.inner
173 .container
174 .add_component(component)
175 .map_err(AppError::from)
176 }
177
178 pub fn add_or_overwrite_component(&self, component: Component) -> AppResult<()> {
180 self.check_destroyed()?;
181 self.inner.container.add_or_overwrite_component(component);
182 Ok(())
183 }
184
185 pub fn remove_service_instance(&self, name: &str, identifier: Option<&str>) {
187 let provider = self.inner.container.get_provider(name);
188 if let Some(id) = identifier {
189 provider.clear_instance(id);
190 } else {
191 provider.clear_instance(DEFAULT_ENTRY_NAME);
192 }
193 }
194
195 pub fn is_deleted(&self) -> bool {
197 self.inner.is_deleted.load(Ordering::SeqCst)
198 }
199
200 pub fn set_is_deleted(&self, value: bool) {
202 self.inner.is_deleted.store(value, Ordering::SeqCst);
203 }
204
205 pub fn check_destroyed(&self) -> AppResult<()> {
207 if self.is_deleted() {
208 return Err(AppError::AppDeleted {
209 app_name: self.name().to_owned(),
210 });
211 }
212 Ok(())
213 }
214}
215
216impl std::fmt::Debug for FirebaseApp {
217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218 f.debug_struct("FirebaseApp")
219 .field("name", &self.name())
220 .field(
221 "automatic_data_collection_enabled",
222 &self.automatic_data_collection_enabled(),
223 )
224 .finish()
225 }
226}
227
228impl FirebaseAppConfig {
229 pub fn new(name: impl Into<String>, automatic: bool) -> Self {
231 Self {
232 name: to_arc_str(name),
233 automatic_data_collection_enabled: automatic,
234 }
235 }
236}
237
238#[derive(Clone)]
239pub struct FirebaseServerApp {
240 inner: Arc<FirebaseServerAppInner>,
241}
242
243struct FirebaseServerAppInner {
244 base: FirebaseApp,
245 settings: FirebaseServerAppSettings,
246 ref_count: AtomicUsize,
247 release_on_drop: AtomicBool,
248}
249
250impl FirebaseServerApp {
251 pub fn new(base: FirebaseApp, mut settings: FirebaseServerAppSettings) -> Self {
253 let release_on_drop = settings.release_on_deref.unwrap_or(false);
254 settings.release_on_deref = None;
255 base.set_is_deleted(false);
256
257 Self {
258 inner: Arc::new(FirebaseServerAppInner {
259 base,
260 settings,
261 ref_count: AtomicUsize::new(1),
262 release_on_drop: AtomicBool::new(release_on_drop),
263 }),
264 }
265 }
266
267 pub fn base(&self) -> &FirebaseApp {
269 &self.inner.base
270 }
271
272 pub fn settings(&self) -> FirebaseServerAppSettings {
274 self.inner.settings.clone()
275 }
276
277 pub fn name(&self) -> &str {
279 self.inner.base.name()
280 }
281
282 pub fn inc_ref_count(&self) {
284 self.inner.ref_count.fetch_add(1, Ordering::SeqCst);
285 }
286
287 pub fn dec_ref_count(&self) -> usize {
289 self.inner.ref_count.fetch_sub(1, Ordering::SeqCst) - 1
290 }
291
292 pub fn set_release_on_drop(&self, enabled: bool) {
294 self.inner.release_on_drop.store(enabled, Ordering::SeqCst);
295 }
296
297 pub fn release_on_drop(&self) -> bool {
299 self.inner.release_on_drop.load(Ordering::SeqCst)
300 }
301}
302
303pub fn is_browser() -> bool {
305 environment::is_browser()
306}
307
308pub fn is_web_worker() -> bool {
310 environment::is_web_worker()
311}
312
313pub fn get_default_app_config() -> Option<FirebaseOptions> {
315 let map = environment::default_app_config_json()?;
316 map_to_options(&map)
317}
318
319pub fn deep_equal_options(a: &FirebaseOptions, b: &FirebaseOptions) -> bool {
321 a == b
322}
323
324#[derive(Clone, Debug)]
325pub struct FirebaseAuthTokenData {
326 pub access_token: String,
327}
328
329pub trait FirebaseServiceInternals: Send + Sync {
330 fn delete(&self) -> AppResult<()>;
331}
332
333#[allow(dead_code)]
334pub trait FirebaseService: Send + Sync {
335 fn app(&self) -> FirebaseApp;
336 fn internals(&self) -> Option<&dyn FirebaseServiceInternals> {
337 None
338 }
339}
340
341#[allow(dead_code)]
342pub type AppHook = Arc<dyn Fn(&str, &FirebaseApp) + Send + Sync>;
343
344#[allow(dead_code)]
345pub type FirebaseServiceFactory<T> = Arc<
346 dyn Fn(
347 &FirebaseApp,
348 Option<Arc<dyn Fn(&HashMap<String, serde_json::Value>) + Send + Sync>>,
349 Option<&str>,
350 ) -> T
351 + Send
352 + Sync,
353>;
354
355#[allow(dead_code)]
356pub type FirebaseServiceNamespace<T> = Arc<dyn Fn(Option<&FirebaseApp>) -> T + Send + Sync>;
357
358#[allow(dead_code)]
359pub trait FirebaseAppInternals: Send + Sync {
360 fn get_token(&self, refresh_token: bool) -> AppResult<Option<FirebaseAuthTokenData>>;
361 fn get_uid(&self) -> Option<String>;
362 fn add_auth_token_listener(&self, listener: Arc<dyn Fn(Option<String>) + Send + Sync>);
363 fn remove_auth_token_listener(&self, listener_id: usize);
364 fn log_event(
365 &self,
366 event_name: &str,
367 event_params: HashMap<String, serde_json::Value>,
368 global: bool,
369 );
370}
371
372pub fn deep_equal_config(a: &FirebaseAppConfig, b: &FirebaseAppConfig) -> bool {
374 a == b
375}
376
377fn to_arc_str(value: impl Into<String>) -> Arc<str> {
378 Arc::from(value.into().into_boxed_str())
379}
380
381fn map_to_options(map: &serde_json::Map<String, serde_json::Value>) -> Option<FirebaseOptions> {
382 let mut options = FirebaseOptions::default();
383
384 options.api_key = string_value(map, "apiKey");
385 options.auth_domain = string_value(map, "authDomain");
386 options.database_url = string_value(map, "databaseURL");
387 options.project_id = string_value(map, "projectId");
388 options.storage_bucket = string_value(map, "storageBucket");
389 options.messaging_sender_id = string_value(map, "messagingSenderId");
390 options.app_id = string_value(map, "appId");
391 options.measurement_id = string_value(map, "measurementId");
392
393 if options.api_key.is_some()
394 || options.project_id.is_some()
395 || options.app_id.is_some()
396 || options.database_url.is_some()
397 || options.storage_bucket.is_some()
398 || options.messaging_sender_id.is_some()
399 || options.measurement_id.is_some()
400 || options.auth_domain.is_some()
401 {
402 Some(options)
403 } else {
404 None
405 }
406}
407
408fn string_value(map: &serde_json::Map<String, serde_json::Value>, key: &str) -> Option<String> {
409 map.get(key)
410 .and_then(|value| value.as_str())
411 .map(|value| value.to_string())
412}
413
414impl Drop for FirebaseServerApp {
415 fn drop(&mut self) {
416 if !self.release_on_drop() {
417 return;
418 }
419
420 if self.base().is_deleted() {
421 return;
422 }
423
424 let was_enabled = self.inner.release_on_drop.swap(false, Ordering::SeqCst);
425 if !was_enabled {
426 return;
427 }
428
429 let app = self.inner.base.clone();
430 spawn_detached(async move {
431 let _ = crate::app::api::delete_app(&app).await;
432 });
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439 use std::sync::{LazyLock, Mutex};
440
441 static ENV_GUARD: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
442
443 #[test]
444 fn map_to_options_returns_some_when_fields_present() {
445 let mut map = serde_json::Map::new();
446 map.insert("apiKey".into(), serde_json::Value::String("foo".into()));
447 let options = map_to_options(&map).expect("options");
448 assert_eq!(options.api_key.as_deref(), Some("foo"));
449 }
450
451 #[test]
452 fn map_to_options_returns_none_for_empty_map() {
453 let map = serde_json::Map::new();
454 assert!(map_to_options(&map).is_none());
455 }
456
457 #[test]
458 fn get_default_app_config_reads_environment() {
459 let _guard = ENV_GUARD.lock().unwrap();
460
461 let key = "FIREBASE_CONFIG";
462 let previous = std::env::var(key).ok();
463 std::env::set_var(key, "{\"apiKey\":\"env-key\"}");
464
465 let options = get_default_app_config().expect("config");
466 assert_eq!(options.api_key.as_deref(), Some("env-key"));
467
468 match previous {
469 Some(value) => std::env::set_var(key, value),
470 None => std::env::remove_var(key),
471 }
472 }
473}