Skip to main content

cranpose_services/
network_status.rs

1//! Network status (connectivity + whether the connection is metered). Apps use
2//! it to defer or confirm large transfers on cellular/metered links.
3//!
4//! The default reports online + unmetered; platform backends can install a real
5//! monitor via [`set_platform_network_monitor`] (iOS `NWPathMonitor`, Android
6//! `ConnectivityManager`, web `navigator.connection`).
7
8use std::sync::{Arc, OnceLock};
9
10use crate::registry::{RecoveryGate, ServiceRegistry};
11
12/// A snapshot of the current network state.
13#[derive(Clone, Copy, PartialEq, Eq, Debug)]
14pub struct NetworkStatus {
15    /// Whether the device currently has a usable network path.
16    pub online: bool,
17    /// Whether the active path is metered/expensive (cellular, hotspot, …).
18    pub metered: bool,
19}
20
21impl Default for NetworkStatus {
22    fn default() -> Self {
23        Self {
24            online: true,
25            metered: false,
26        }
27    }
28}
29
30/// Reports the current network status.
31pub trait NetworkMonitor: Send + Sync {
32    fn status(&self) -> NetworkStatus;
33    fn is_alive(&self) -> bool;
34    fn reconnect(&self);
35}
36
37pub type NetworkMonitorRef = Arc<dyn NetworkMonitor>;
38
39struct DefaultNetworkMonitor;
40
41impl NetworkMonitor for DefaultNetworkMonitor {
42    fn status(&self) -> NetworkStatus {
43        NetworkStatus::default()
44    }
45
46    fn is_alive(&self) -> bool {
47        true
48    }
49
50    fn reconnect(&self) {}
51}
52
53static PLATFORM_NETWORK_MONITOR: ServiceRegistry<dyn NetworkMonitor> = ServiceRegistry::new();
54static DEFAULT_NETWORK_MONITOR: OnceLock<NetworkMonitorRef> = OnceLock::new();
55static NETWORK_MONITOR_HANDLE: OnceLock<NetworkMonitorRef> = OnceLock::new();
56static NETWORK_RECOVERY: RecoveryGate = RecoveryGate::new();
57
58struct PlatformNetworkMonitor;
59
60fn registered_network_monitor() -> NetworkMonitorRef {
61    PLATFORM_NETWORK_MONITOR
62        .get_or_warn("network monitor")
63        .unwrap_or_else(|| {
64            DEFAULT_NETWORK_MONITOR
65                .get_or_init(|| Arc::new(DefaultNetworkMonitor))
66                .clone()
67        })
68}
69
70fn active_network_monitor() -> NetworkMonitorRef {
71    let monitor = registered_network_monitor();
72    if monitor.is_alive() {
73        NETWORK_RECOVERY.succeeded();
74    } else if NETWORK_RECOVERY.try_start() {
75        monitor.reconnect();
76    }
77    monitor
78}
79
80impl NetworkMonitor for PlatformNetworkMonitor {
81    fn status(&self) -> NetworkStatus {
82        active_network_monitor().status()
83    }
84
85    fn is_alive(&self) -> bool {
86        registered_network_monitor().is_alive()
87    }
88
89    fn reconnect(&self) {
90        registered_network_monitor().reconnect();
91    }
92}
93
94/// Installs a platform network monitor, replacing any previous one.
95pub fn set_platform_network_monitor(monitor: NetworkMonitorRef) {
96    PLATFORM_NETWORK_MONITOR.set(monitor);
97    NETWORK_RECOVERY.succeeded();
98}
99
100/// Removes any registered platform network monitor (tests and teardown).
101pub fn clear_platform_network_monitor() {
102    PLATFORM_NETWORK_MONITOR.clear();
103}
104
105/// The active network monitor: the platform one if installed, else the default
106/// (online, unmetered).
107pub fn network_monitor() -> NetworkMonitorRef {
108    NETWORK_MONITOR_HANDLE
109        .get_or_init(|| Arc::new(PlatformNetworkMonitor))
110        .clone()
111}
112
113/// Convenience: the current network status.
114pub fn network_status() -> NetworkStatus {
115    let monitor = network_monitor();
116    if monitor.is_alive() {
117        NETWORK_RECOVERY.succeeded();
118    } else if NETWORK_RECOVERY.try_start() {
119        monitor.reconnect();
120    }
121    monitor.status()
122}
123
124#[cfg(test)]
125#[path = "tests/network_status_tests.rs"]
126mod tests;