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 = wacore::time::now_secs();
44
45        for received in &response.tokens {
46            let entry = TcTokenEntry {
47                token: received.token.clone(),
48                token_timestamp: received.timestamp,
49                sender_timestamp: Some(now),
50            };
51
52            if let Err(e) = backend.put_tc_token(&received.jid.user, &entry).await {
53                log::warn!(target: "Client/TcToken", "Failed to store issued tc_token for {}: {e}", received.jid);
54            }
55        }
56
57        Ok(response.tokens)
58    }
59
60    /// Prune expired tc tokens from the store.
61    ///
62    /// Deletes all tokens older than the rolling window (28 days by default).
63    /// Returns the number of tokens deleted.
64    pub async fn prune_expired(&self) -> Result<u32, anyhow::Error> {
65        let backend = self.client.persistence_manager.backend();
66        let cutoff = tc_token_expiration_cutoff();
67        let deleted = backend.delete_expired_tc_tokens(cutoff).await?;
68
69        if deleted > 0 {
70            log::info!(target: "Client/TcToken", "Pruned {} expired tc_tokens", deleted);
71        }
72
73        Ok(deleted)
74    }
75
76    /// Get a stored tc token for a JID.
77    pub async fn get(&self, jid: &str) -> Result<Option<TcTokenEntry>, anyhow::Error> {
78        let backend = self.client.persistence_manager.backend();
79        Ok(backend.get_tc_token(jid).await?)
80    }
81
82    /// Get all JIDs that have stored tc tokens.
83    pub async fn get_all_jids(&self) -> Result<Vec<String>, anyhow::Error> {
84        let backend = self.client.persistence_manager.backend();
85        Ok(backend.get_all_tc_token_jids().await?)
86    }
87}
88
89impl Client {
90    /// Access trusted contact token operations.
91    pub fn tc_token(&self) -> TcToken<'_> {
92        TcToken::new(self)
93    }
94}