tx5_online/
lib.rs

1#![deny(missing_docs)]
2#![deny(unsafe_code)]
3#![doc = tx5_core::__doc_header!()]
4//! # tx5-online
5//!
6//! Holochain WebRTC p2p communication ecosystem online connectivity events.
7
8use once_cell::sync::Lazy;
9use trust_dns_resolver::config::*;
10use trust_dns_resolver::error::*;
11use trust_dns_resolver::*;
12
13/// An online status event.
14#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub enum OnlineEvent {
16    /// The machine is now likely online (we were able to reach the network).
17    Online,
18
19    /// The machine is now likely offline (we were unable to reach the network).
20    Offline,
21}
22
23impl std::fmt::Display for OnlineEvent {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        write!(f, "{self:?}")
26    }
27}
28
29/// A receiver handle to the online event emitter.
30#[derive(Clone)]
31pub struct OnlineReceiver(tokio::sync::watch::Receiver<OnlineEvent>);
32
33impl Default for OnlineReceiver {
34    fn default() -> Self {
35        RCV.clone()
36    }
37}
38
39impl OnlineReceiver {
40    /// Get a new receiver handle to the online event emitter.
41    pub fn new() -> Self {
42        Self::default()
43    }
44
45    /// Get the current online status.
46    pub fn status(&mut self) -> OnlineEvent {
47        // the sender is never destroyed, so this can never error
48        while self.0.has_changed().unwrap() {
49            self.0.borrow_and_update();
50        }
51        *self.0.borrow_and_update()
52    }
53
54    /// Await the next online status event.
55    pub async fn recv(&mut self) -> OnlineEvent {
56        // the sender is never destroyed, so this can never error
57        let _ = self.0.changed().await;
58        *self.0.borrow_and_update()
59    }
60}
61
62static RCV: Lazy<OnlineReceiver> = Lazy::new(|| {
63    let (snd, rcv) = tokio::sync::watch::channel(OnlineEvent::Offline);
64
65    tokio::task::spawn(async move {
66        loop {
67            let status = check_status().await;
68
69            snd.send_if_modified(move |s| {
70                if *s == status {
71                    false
72                } else {
73                    *s = status;
74                    true
75                }
76            });
77
78            let s = rand::Rng::random_range(&mut rand::rng(), 4.0..8.0);
79            let s = std::time::Duration::from_secs_f64(s);
80            tokio::time::sleep(s).await;
81        }
82    });
83
84    OnlineReceiver(rcv)
85});
86
87async fn check_status() -> OnlineEvent {
88    let mut servers = Vec::new();
89    servers.append(&mut NameServerConfigGroup::cloudflare().into_inner());
90    servers.append(&mut NameServerConfigGroup::google().into_inner());
91    servers.append(&mut NameServerConfigGroup::quad9().into_inner());
92
93    rand::seq::SliceRandom::shuffle(&mut servers[..], &mut rand::rng());
94
95    let conf = ResolverConfig::from_parts(None, Vec::new(), servers);
96
97    let mut opts = ResolverOpts::default();
98    opts.server_ordering_strategy = ServerOrderingStrategy::UserProvidedOrder;
99    opts.cache_size = 0;
100    opts.use_hosts_file = false;
101
102    let r = TokioAsyncResolver::tokio(conf, opts);
103
104    const TLD: [&str; 5] = [".com.", ".net.", ".org.", ".edu.", ".gov."];
105
106    let tld =
107        rand::seq::IndexedRandom::choose(&TLD[..], &mut rand::rng()).unwrap();
108
109    let mut nonce = [0_u8; 8];
110    rand::Rng::fill(&mut rand::rng(), &mut nonce);
111    let mut name = String::new();
112    for c in nonce {
113        name.push_str(&format!("{c:02x}"));
114    }
115    name.push_str(tld);
116
117    r.clear_cache();
118
119    match r.lookup_ip(name).await {
120        Ok(_) => OnlineEvent::Online,
121        Err(err) => match err.kind() {
122            ResolveErrorKind::NoRecordsFound { .. } => OnlineEvent::Online,
123            _ => OnlineEvent::Offline,
124        },
125    }
126}