Skip to main content

whatsapp_rust/features/
tctoken.rs

1//! Trusted contact privacy token feature.
2//!
3//! Provides high-level APIs for managing tcTokens, matching WhatsApp Web's
4//! `WAWebTrustedContactsUtils` and `WAWebPrivacyTokenJob`.
5//!
6//! ## Usage
7//! ```ignore
8//! // Issue tokens to contacts
9//! let tokens = client.tc_token().issue_tokens(&[jid]).await?;
10//!
11//! // Prune expired tokens
12//! let count = client.tc_token().prune_expired().await?;
13//! ```
14
15use crate::client::Client;
16use crate::request::IqError;
17use wacore::iq::tctoken::{IssuePrivacyTokensSpec, ReceivedTcToken, tc_token_expiration_cutoff};
18use wacore::store::traits::TcTokenEntry;
19use wacore_binary::jid::Jid;
20
21/// Feature handle for trusted contact token operations.
22pub struct TcToken<'a> {
23    client: &'a Client,
24}
25
26impl<'a> TcToken<'a> {
27    pub(crate) fn new(client: &'a Client) -> Self {
28        Self { client }
29    }
30
31    /// Issue privacy tokens for the given contacts.
32    ///
33    /// Sends an IQ to the server requesting tokens for the specified JIDs (should be LID JIDs).
34    /// Stores the received tokens and returns them.
35    pub async fn issue_tokens(&self, jids: &[Jid]) -> Result<Vec<ReceivedTcToken>, IqError> {
36        if jids.is_empty() {
37            return Ok(Vec::new());
38        }
39
40        let spec = IssuePrivacyTokensSpec::new(jids);
41        let response = self.client.execute(spec).await?;
42        let backend = self.client.persistence_manager.backend();
43        let now = std::time::SystemTime::now()
44            .duration_since(std::time::UNIX_EPOCH)
45            .unwrap_or_default()
46            .as_secs() as i64;
47
48        for received in &response.tokens {
49            let entry = TcTokenEntry {
50                token: received.token.clone(),
51                token_timestamp: received.timestamp,
52                sender_timestamp: Some(now),
53            };
54
55            if let Err(e) = backend.put_tc_token(&received.jid.user, &entry).await {
56                log::warn!(target: "Client/TcToken", "Failed to store issued tc_token for {}: {e}", received.jid);
57            }
58        }
59
60        Ok(response.tokens)
61    }
62
63    /// Prune expired tc tokens from the store.
64    ///
65    /// Deletes all tokens older than the rolling window (28 days by default).
66    /// Returns the number of tokens deleted.
67    pub async fn prune_expired(&self) -> Result<u32, anyhow::Error> {
68        let backend = self.client.persistence_manager.backend();
69        let cutoff = tc_token_expiration_cutoff();
70        let deleted = backend.delete_expired_tc_tokens(cutoff).await?;
71
72        if deleted > 0 {
73            log::info!(target: "Client/TcToken", "Pruned {} expired tc_tokens", deleted);
74        }
75
76        Ok(deleted)
77    }
78
79    /// Get a stored tc token for a JID.
80    pub async fn get(&self, jid: &str) -> Result<Option<TcTokenEntry>, anyhow::Error> {
81        let backend = self.client.persistence_manager.backend();
82        Ok(backend.get_tc_token(jid).await?)
83    }
84
85    /// Get all JIDs that have stored tc tokens.
86    pub async fn get_all_jids(&self) -> Result<Vec<String>, anyhow::Error> {
87        let backend = self.client.persistence_manager.backend();
88        Ok(backend.get_all_tc_token_jids().await?)
89    }
90}
91
92impl Client {
93    /// Access trusted contact token operations.
94    pub fn tc_token(&self) -> TcToken<'_> {
95        TcToken::new(self)
96    }
97}