whatsapp_rust/
keepalive.rs

1use crate::client::Client;
2use crate::request::{InfoQuery, InfoQueryType, IqError};
3use log::{debug, info, warn};
4use rand::Rng;
5use std::sync::Arc;
6use std::sync::atomic::Ordering;
7use std::time::Duration;
8use wacore_binary::builder::NodeBuilder;
9use wacore_binary::jid::SERVER_JID;
10use wacore_binary::node::NodeContent;
11
12const KEEP_ALIVE_INTERVAL_MIN: Duration = Duration::from_secs(20);
13const KEEP_ALIVE_INTERVAL_MAX: Duration = Duration::from_secs(30);
14const KEEP_ALIVE_MAX_FAIL_TIME: Duration = Duration::from_secs(180);
15const KEEP_ALIVE_RESPONSE_DEADLINE: Duration = Duration::from_secs(20);
16
17impl Client {
18    async fn send_keepalive(&self) -> bool {
19        if !self.is_connected() {
20            return false;
21        }
22
23        info!(target: "Client/Keepalive", "Sending keepalive ping");
24
25        let iq = InfoQuery {
26            namespace: "w:p",
27            query_type: InfoQueryType::Get,
28            to: SERVER_JID.parse().unwrap(),
29            target: None,
30            id: None,
31            content: Some(NodeContent::Nodes(vec![NodeBuilder::new("ping").build()])),
32            timeout: Some(KEEP_ALIVE_RESPONSE_DEADLINE),
33        };
34
35        match self.send_iq(iq).await {
36            Ok(_) => {
37                debug!(target: "Client/Keepalive", "Received keepalive pong");
38                true
39            }
40            Err(e) => {
41                warn!(target: "Client/Keepalive", "Keepalive ping failed: {e:?}");
42                !matches!(e, IqError::Socket(_) | IqError::Disconnected(_))
43            }
44        }
45    }
46
47    pub(crate) async fn keepalive_loop(self: Arc<Self>) {
48        let mut last_success = chrono::Utc::now();
49        let mut error_count = 0u32;
50
51        loop {
52            let interval_ms = rand::rng().random_range(
53                KEEP_ALIVE_INTERVAL_MIN.as_millis()..=KEEP_ALIVE_INTERVAL_MAX.as_millis(),
54            );
55            let interval = Duration::from_millis(interval_ms as u64);
56
57            tokio::select! {
58                _ = tokio::time::sleep(interval) => {
59                    if !self.is_connected() {
60                        debug!(target: "Client/Keepalive", "Not connected, exiting keepalive loop.");
61                        return;
62                    }
63
64                    let is_success = self.send_keepalive().await;
65
66                    if is_success {
67                        if error_count > 0 {
68                            info!(target: "Client/Keepalive", "Keepalive restored.");
69                        }
70                        error_count = 0;
71                        last_success = chrono::Utc::now();
72                    } else {
73                        error_count += 1;
74                        warn!(target: "Client/Keepalive", "Keepalive timeout, error count: {error_count}");
75
76                        if self.enable_auto_reconnect.load(Ordering::Relaxed)
77                            && chrono::Utc::now().signed_duration_since(last_success)
78                                > chrono::Duration::from_std(KEEP_ALIVE_MAX_FAIL_TIME).unwrap()
79                        {
80                            warn!(target: "Client/Keepalive", "Forcing reconnect due to keepalive failure for over {} seconds.", KEEP_ALIVE_MAX_FAIL_TIME.as_secs());
81                            self.disconnect().await;
82                            return;
83                        }
84                    }
85                },
86                _ = self.shutdown_notifier.notified() => {
87                    debug!(target: "Client/Keepalive", "Shutdown signaled, exiting keepalive loop.");
88                    return;
89                }
90            }
91        }
92    }
93}