Skip to main content

whatsapp_rust/
mediaconn.rs

1//! Media connection management.
2//!
3//! Protocol types are defined in `wacore::iq::mediaconn`.
4
5use crate::client::Client;
6use crate::request::IqError;
7use std::time::Duration;
8use wacore::iq::mediaconn::MediaConnSpec;
9use wacore::time::Instant;
10
11/// Re-export the host type from wacore.
12pub use wacore::iq::mediaconn::MediaConnHost;
13
14/// Number of retry attempts after a media auth error (401/403).
15/// On auth failure, the media connection is invalidated and refreshed before retrying.
16pub(crate) const MEDIA_AUTH_REFRESH_RETRY_ATTEMPTS: usize = 1;
17
18/// Returns `true` if the HTTP status code indicates a media auth error
19/// that should trigger a media connection refresh and retry.
20pub(crate) fn is_media_auth_error(status_code: u16) -> bool {
21    matches!(status_code, 401 | 403)
22}
23
24/// Media connection with runtime-specific fields.
25#[derive(Debug, Clone)]
26pub struct MediaConn {
27    /// Authentication token for media operations.
28    pub auth: String,
29    /// Time-to-live in seconds for route info.
30    pub ttl: u64,
31    /// Time-to-live in seconds for auth token (may differ from route TTL).
32    pub auth_ttl: Option<u64>,
33    /// Available media hosts (sorted: primary first, fallback second).
34    pub hosts: Vec<MediaConnHost>,
35    /// When this connection info was fetched (runtime-specific).
36    pub fetched_at: Instant,
37}
38
39impl MediaConn {
40    /// Check if this connection info has expired.
41    /// Uses the earlier of route TTL and auth TTL (auth may expire before routes).
42    pub fn is_expired(&self) -> bool {
43        let effective_ttl = self.auth_ttl.map_or(self.ttl, |at| self.ttl.min(at));
44        self.fetched_at.elapsed() > Duration::from_secs(effective_ttl)
45    }
46}
47
48impl Client {
49    pub(crate) async fn invalidate_media_conn(&self) {
50        *self.media_conn.write().await = None;
51    }
52
53    pub async fn refresh_media_conn(&self, force: bool) -> Result<MediaConn, IqError> {
54        {
55            let guard = self.media_conn.read().await;
56            if !force
57                && let Some(conn) = &*guard
58                && !conn.is_expired()
59            {
60                return Ok(conn.clone());
61            }
62        }
63
64        let response = self.execute(MediaConnSpec::new()).await?;
65
66        let new_conn = MediaConn {
67            auth: response.auth,
68            ttl: response.ttl,
69            auth_ttl: response.auth_ttl,
70            hosts: response.hosts,
71            fetched_at: Instant::now(),
72        };
73
74        let mut write_guard = self.media_conn.write().await;
75        *write_guard = Some(new_conn.clone());
76
77        Ok(new_conn)
78    }
79}