whatsapp_rust/
keepalive.rs1use 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}