-
-
Notifications
You must be signed in to change notification settings - Fork 126
feat: add AB props cache and privacy tokens conditional #442
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d58b984
9a1b912
7469d61
3198b1b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -205,6 +205,16 @@ impl<'a> Groups<'a> { | |
|
|
||
| options.participants = normalize_participants(&resolved_participants); | ||
|
|
||
| if self | ||
| .client | ||
| .ab_props() | ||
| .is_enabled(wacore::iq::props::config_codes::PRIVACY_TOKEN_ON_GROUP_CREATE) | ||
| .await | ||
| { | ||
| self.attach_tokens_to_participants(&mut options.participants) | ||
| .await; | ||
| } | ||
|
|
||
| let gid = self.client.execute(GroupCreateIq::new(options)).await?; | ||
|
|
||
| Ok(CreateGroupResult { gid }) | ||
|
|
@@ -244,10 +254,19 @@ impl<'a> Groups<'a> { | |
| jid: &Jid, | ||
| participants: &[Jid], | ||
| ) -> Result<Vec<ParticipantChangeResponse>, anyhow::Error> { | ||
| let result = self | ||
| let iq = if self | ||
| .client | ||
| .execute(AddParticipantsIq::new(jid, participants)) | ||
| .await?; | ||
| .ab_props() | ||
| .is_enabled(wacore::iq::props::config_codes::PRIVACY_TOKEN_ON_GROUP_PARTICIPANT_ADD) | ||
| .await | ||
| { | ||
|
Comment on lines
258
to
+262
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
| let options = self.resolve_participant_tokens(participants).await; | ||
| AddParticipantsIq::with_options(jid, options) | ||
| } else { | ||
| AddParticipantsIq::new(jid, participants) | ||
| }; | ||
|
|
||
| let result = self.client.execute(iq).await?; | ||
| // Patch cache with only the participants the server accepted (status 200). | ||
| // Note: the get→mutate→insert is not atomic; a concurrent notification | ||
| // for the same group could race. This is acceptable — the cache is | ||
|
|
@@ -452,6 +471,105 @@ impl<'a> Groups<'a> { | |
| .execute(SetMemberAddModeIq::new(jid, mode)) | ||
| .await?) | ||
| } | ||
|
|
||
| async fn resolve_participant_tokens(&self, jids: &[Jid]) -> Vec<GroupParticipantOptions> { | ||
| if jids.is_empty() { | ||
| return Vec::new(); | ||
| } | ||
| let only_lid = self.only_check_lid().await; | ||
| let futs = jids.iter().map(|jid| async move { | ||
| let mut opt = GroupParticipantOptions::new(jid.clone()); | ||
| if let Some(token_key) = self.resolve_token_key(jid, only_lid).await | ||
| && let Some(token) = self.lookup_valid_token(&token_key).await | ||
| { | ||
| opt = opt.with_privacy(token); | ||
| } | ||
| opt | ||
| }); | ||
| futures::future::join_all(futs).await | ||
| } | ||
|
|
||
| /// Skips participants that already have a token set by the caller. | ||
| async fn attach_tokens_to_participants(&self, participants: &mut [GroupParticipantOptions]) { | ||
| if participants.is_empty() { | ||
| return; | ||
| } | ||
| let only_lid = self.only_check_lid().await; | ||
| let futs = participants.iter().enumerate().map(|(i, p)| async move { | ||
| if p.privacy.is_some() { | ||
| return (i, None); | ||
| } | ||
| let Some(token_key) = self.resolve_token_key(&p.jid, only_lid).await else { | ||
| log::debug!( | ||
| target: "Client/Groups", | ||
| "No LID mapping for participant {}, skipping privacy attachment", | ||
| p.jid | ||
| ); | ||
| return (i, None); | ||
| }; | ||
| let token = self.lookup_valid_token(&token_key).await; | ||
| if token.is_none() { | ||
| log::debug!( | ||
| target: "Client/Groups", | ||
| "No valid tc_token for participant {} (key={}), skipping privacy attachment", | ||
| p.jid, token_key | ||
| ); | ||
| } | ||
| (i, token) | ||
| }); | ||
| for (i, token) in futures::future::join_all(futs).await { | ||
| if token.is_some() { | ||
| participants[i].privacy = token; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| async fn only_check_lid(&self) -> bool { | ||
| self.client | ||
| .ab_props() | ||
| .is_enabled(wacore::iq::props::config_codes::PRIVACY_TOKEN_ONLY_CHECK_LID) | ||
| .await | ||
| } | ||
|
|
||
| /// Resolve JID to tc_token store key. When `only_lid`, PN JIDs without a | ||
| /// LID mapping return `None` instead of falling back to the PN user. | ||
| async fn resolve_token_key(&self, jid: &Jid, only_lid: bool) -> Option<String> { | ||
| if jid.is_lid() { | ||
| Some(jid.user.clone()) | ||
| } else if only_lid { | ||
| self.client.lid_pn_cache.get_current_lid(&jid.user).await | ||
| } else { | ||
| Some( | ||
| self.client | ||
| .lid_pn_cache | ||
| .get_current_lid(&jid.user) | ||
| .await | ||
| .unwrap_or_else(|| jid.user.clone()), | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| /// Returns the tc_token if present and not expired. | ||
| async fn lookup_valid_token(&self, token_key: &str) -> Option<Vec<u8>> { | ||
| use wacore::iq::tctoken::is_tc_token_expired; | ||
| let backend = self.client.persistence_manager.backend(); | ||
| match backend.get_tc_token(token_key).await { | ||
| Ok(Some(entry)) | ||
| if !entry.token.is_empty() && !is_tc_token_expired(entry.token_timestamp) => | ||
| { | ||
| Some(entry.token) | ||
| } | ||
| Ok(_) => None, | ||
| Err(e) => { | ||
| log::warn!( | ||
| target: "Client/Groups", | ||
| "Failed to get tc_token for {}: {e}", | ||
| token_key | ||
| ); | ||
| None | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl Client { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
create_groupnow gates privacy-token attachment onab_props().is_enabled(...), but the AB cache is populated asynchronously in background init (fetch_propsis spawned after connect), so this check returnsfalseon a fresh connection until that job completes. In AB-enabled deployments where group create/add requires privacy tokens, the first operations afterwait_for_connected()can still be sent without tokens and fail, which undermines the feature this commit adds. Consider treating props fetch as readiness-critical for these flows or using a fallback that attaches a valid token when one exists.Useful? React with 👍 / 👎.