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//!
15//! ## VoIP call integration
16//! Outgoing 1:1 call offers attach the callee's stored token to the offer's
17//! `<privacy>` node and issue a fresh token after send (WA Web's `sendTcToken`
18//! in StartCall.js), both driven from `voip::facade::place_call`. Group-call
19//! initiation is not yet implemented; when it is, it should attach/issue per
20//! participant the same way to avoid 463 nacks on call offers.
21
22use crate::client::Client;
23use crate::request::IqError;
24use crate::store::error::StoreError;
25use thiserror::Error;
26use wacore::iq::tctoken::{IssuePrivacyTokensSpec, ReceivedTcToken};
27use wacore::store::traits::TcTokenEntry;
28use wacore_binary::Jid;
29
30/// Error returned by trusted-contact token operations.
31#[derive(Debug, Error)]
32#[non_exhaustive]
33pub enum TcTokenError {
34    /// The IQ requesting tokens from the server failed.
35    #[error("{0}")]
36    Iq(#[from] IqError),
37    /// A token store (persistence) operation failed.
38    #[error("{0}")]
39    Store(#[from] StoreError),
40}
41
42/// Feature handle for trusted contact token operations.
43pub struct TcToken<'a> {
44    client: &'a Client,
45}
46
47impl<'a> TcToken<'a> {
48    pub(crate) fn new(client: &'a Client) -> Self {
49        Self { client }
50    }
51
52    /// Issue privacy tokens for the given contacts.
53    ///
54    /// Sends an IQ to the server requesting tokens for the specified JIDs (should be LID JIDs).
55    /// Stores the received tokens and returns them.
56    pub async fn issue_tokens(&self, jids: &[Jid]) -> Result<Vec<ReceivedTcToken>, TcTokenError> {
57        if jids.is_empty() {
58            return Ok(Vec::new());
59        }
60
61        let spec = IssuePrivacyTokensSpec::new(jids);
62        let response = self.client.execute(spec).await?;
63        self.client.store_issued_tc_tokens(&response.tokens).await;
64
65        Ok(response.tokens)
66    }
67
68    /// Prune expired tc tokens from the store.
69    ///
70    /// Cutoffs are AB-prop-aware via `Client::tc_token_config()` — the server
71    /// may override the default 28-day window (e.g. 26 buckets = 182 days). The
72    /// received token and the sender bucket expire on independent windows, so a
73    /// row is dropped only when both are stale.
74    pub async fn prune_expired(&self) -> Result<u32, TcTokenError> {
75        use wacore::iq::tctoken::{
76            sender_tc_token_expiration_cutoff_with, tc_token_expiration_cutoff_with,
77        };
78
79        let backend = self.client.persistence_manager.backend();
80        let tc_config = self.client.tc_token_config().await;
81        let token_cutoff = tc_token_expiration_cutoff_with(&tc_config);
82        let sender_cutoff = sender_tc_token_expiration_cutoff_with(&tc_config);
83        let deleted = backend
84            .delete_expired_tc_tokens(token_cutoff, sender_cutoff)
85            .await?;
86
87        if deleted > 0 {
88            log::info!(target: "Client/TcToken", "Pruned {} expired tc_tokens", deleted);
89        }
90
91        Ok(deleted)
92    }
93
94    /// Get a stored tc token for a JID.
95    pub async fn get(&self, jid: &str) -> Result<Option<TcTokenEntry>, TcTokenError> {
96        let backend = self.client.persistence_manager.backend();
97        Ok(backend.get_tc_token(jid).await?)
98    }
99
100    /// Get all JIDs that have stored tc tokens.
101    pub async fn get_all_jids(&self) -> Result<Vec<String>, TcTokenError> {
102        let backend = self.client.persistence_manager.backend();
103        Ok(backend.get_all_tc_token_jids().await?)
104    }
105}
106
107impl Client {
108    /// Access trusted contact token operations.
109    pub fn tc_token(&self) -> TcToken<'_> {
110        TcToken::new(self)
111    }
112}