firebase_rs_sdk/messaging/
support.rs

1//! Environment capability checks for Firebase Messaging.
2//!
3//! The JavaScript SDK exposes `isSupported()` so consumers can guard calls to
4//! messaging APIs on browsers that implement the Notification and Push APIs.
5//! This module mirrors the behaviour for WebAssembly builds and falls back to
6//! `false` for native targets.
7
8#[cfg(all(feature = "wasm-web", target_arch = "wasm32"))]
9use js_sys::Reflect;
10#[cfg(all(feature = "wasm-web", target_arch = "wasm32"))]
11use wasm_bindgen::{JsCast, JsValue};
12
13/// Returns `true` when the current environment exposes the browser APIs that
14/// Firebase Cloud Messaging requires.
15///
16/// Port of `packages/messaging/src/api/isSupported.ts` in the Firebase JS SDK.
17///
18/// # Examples
19///
20/// ```
21/// use firebase_rs_sdk::messaging;
22///
23/// if messaging::is_supported() {
24///     // Safe to call messaging APIs that rely on browser push features.
25/// }
26/// ```
27#[cfg(all(feature = "wasm-web", target_arch = "wasm32"))]
28pub fn is_supported() -> bool {
29    let window = match web_sys::window() {
30        Some(window) => window,
31        None => return false,
32    };
33    let navigator = window.navigator();
34    let navigator_js = JsValue::from(navigator.clone());
35
36    let cookie_enabled = Reflect::get(&navigator_js, &JsValue::from_str("cookieEnabled"))
37        .ok()
38        .and_then(|value| value.as_bool())
39        .unwrap_or(true);
40    if !cookie_enabled {
41        return false;
42    }
43
44    let service_worker_available = Reflect::get(&navigator_js, &JsValue::from_str("serviceWorker"))
45        .ok()
46        .map(|value| !value.is_undefined() && !value.is_null())
47        .unwrap_or(false);
48    if !service_worker_available {
49        return false;
50    }
51
52    // Ensure indexedDB is available. We only check that the factory exists; the
53    // JavaScript SDK further verifies openability, which we can add once async
54    // event handling is wired up.
55    match window.indexed_db() {
56        Ok(Some(_)) => {}
57        _ => return false,
58    }
59
60    let window_js = JsValue::from(window.clone());
61    if !property_in(&window_js, "PushManager")
62        || !property_in(&window_js, "Notification")
63        || !property_in(&window_js, "fetch")
64    {
65        return false;
66    }
67
68    if !prototype_has_property(&window_js, "ServiceWorkerRegistration", "showNotification") {
69        return false;
70    }
71
72    if !prototype_has_property(&window_js, "PushSubscription", "getKey") {
73        return false;
74    }
75
76    true
77}
78
79#[cfg(all(feature = "wasm-web", target_arch = "wasm32"))]
80fn property_in(target: &JsValue, property: &str) -> bool {
81    Reflect::has(target, &JsValue::from_str(property)).unwrap_or(false)
82}
83
84#[cfg(all(feature = "wasm-web", target_arch = "wasm32"))]
85fn prototype_has_property(target: &JsValue, constructor: &str, property: &str) -> bool {
86    let ctor = match Reflect::get(target, &JsValue::from_str(constructor)) {
87        Ok(value) => value,
88        Err(_) => return false,
89    };
90
91    let prototype = match Reflect::get(&ctor, &JsValue::from_str("prototype")) {
92        Ok(value) => value,
93        Err(_) => return false,
94    };
95
96    prototype
97        .dyn_ref::<js_sys::Object>()
98        .map(|obj| obj.has_own_property(&JsValue::from_str(property)))
99        .unwrap_or(false)
100}
101
102/// Returns `false` outside a web environment, where the required browser APIs
103/// are unavailable.
104#[cfg(not(all(feature = "wasm-web", target_arch = "wasm32")))]
105pub fn is_supported() -> bool {
106    false
107}
108
109#[cfg(all(test, not(all(feature = "wasm-web", target_arch = "wasm32"))))]
110mod tests {
111    #[test]
112    fn non_wasm_targets_are_not_supported() {
113        assert!(!super::is_supported());
114    }
115}