Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion api/tctoken.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -224,17 +224,21 @@ client.send_message(jid, message).await?;
let picture = client.contacts().get_profile_picture(&jid, true).await?;
```

The tctoken (no cstoken fallback) is also attached automatically on two other privacy-gated IQs, matching WA Web's `USyncStatusProtocol`/`OutSpamTCTokenMixin`:
The tctoken (no cstoken fallback) is also attached automatically at three other call sites that use privacy tokens, matching WA Web's `USyncStatusProtocol`/`OutSpamTCTokenMixin`/`StartCall.js`:

- **`get_user_info`** — a per-recipient tctoken is attached to each queried `<user>` node in the usync IQ, so status/about for a privacy-restricted contact resolves instead of coming back hidden. Gated behind `profile_scraping_privacy_token_in_about_usync`.
- **`send_spam_report`** — when `SpamReportRequest::from_jid` is set, that contact's tctoken is attached to the spam report IQ so the report is accepted for a privacy-restricted account. Gated behind `enable_spam_report_iq_with_privacy_token`. Group reports that only set `group_jid`/`participant_jid` (no `from_jid`) don't get a token attached.
- **Outgoing 1:1 call offers** (`voip` feature) — placing a call attaches the callee's stored, unexpired tctoken as the offer's leading `<privacy>` node, and issues a fresh token to the callee in the background after the offer sends. This mirrors WA Web's `sendTcToken` in `StartCall.js` and prevents 463 nacks on later offers to a privacy-restricted contact. Unlike the other paths, it isn't gated by any AB prop — issuance is rate-limited only by the same sender bucket that governs message reissuance (see [Post-send issuance](#post-send-issuance)). Group-call initiation isn't implemented yet.

```rust
// Attaches the queried contact's tctoken automatically when the AB prop is on
let info = client.contacts().get_user_info(&[jid]).await?;

// Attaches the reported contact's tctoken automatically when the AB prop is on
let result = client.send_spam_report(request).await?;

// Attaches the callee's tctoken automatically, then issues a fresh one after the offer sends
let handle = client.voip().call(&peer).audio(mic_source, speaker_sink).start().await?;
```

### AB prop gating
Expand All @@ -248,6 +252,8 @@ Token *issuance scheduling* (requesting new tokens from the server after sending

The usync and spam-report attachment points each have their own independent gating prop (`profile_scraping_privacy_token_in_about_usync`, `enable_spam_report_iq_with_privacy_token`) — unrelated to the message-stanza props above, and with no cstoken fallback.

Outgoing call offers (`voip` feature) attach and issue tokens **unconditionally** — there is no AB prop gate for this path at all, matching WA Web's `StartCall.js`.

### Post-send issuance

After sending a 1:1 message, the library checks whether a new token should be issued for the recipient. If the sender-side bucket has rolled over since the last issuance, a background IQ request fires.
Expand Down Expand Up @@ -380,6 +386,17 @@ sequenceDiagram
Client->>Client: Resolve sender LID
Client->>Backend: store_received_tc_token (monotonicity guard, preserves sender_timestamp)
Client->>Server: Re-subscribe presence

Note over App,Server: Outgoing 1:1 call offer (voip feature, unconditional — no AB prop gate)
App->>Client: voip().call(&peer).audio(...).start()
Client->>Backend: Get tc_token for peer
Backend-->>Client: TcTokenEntry or None
Client->>Server: Call offer, tctoken as leading <privacy> child if stored
Server-->>Client: Offer ack
Client-->>App: CallHandle
Client->>Server: IQ set (issue fresh token to callee, if sender bucket rolled over)
Server-->>Client: IQ success
Client->>Backend: touch_tc_token_sender_timestamp
```

## Expiration
Expand Down
4 changes: 4 additions & 0 deletions guides/voip-calls.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ client.voip()
handle.wait_ended().await;
```

<Note>
If the callee has a stored [trusted-contact token](/api/tctoken), it's attached to the offer automatically, and a fresh token is issued to them in the background afterward if the sender-side bucket has rolled over — matching WhatsApp Web's `sendTcToken` in `StartCall.js`. This prevents 463 nacks on calls to privacy-restricted contacts and needs no action from the caller. Group-call initiation doesn't implement this yet.
</Note>

## Audio I/O

You supply the audio I/O by implementing the `AudioSource` and `AudioSink` traits. Both traits are channel-based — the library reads from a `Receiver` and writes decoded PCM to a `Sender`. The bundled `examples/voip-cli/src/main.rs` wires up [cpal](https://crates.io/crates/cpal)/PipeWire as a reference.
Expand Down