Skip to main content

xmtp_mls/groups/
members.rs

1use crate::{context::XmtpSharedContext, identity_updates::IdentityUpdates};
2
3use super::{GroupError, MlsGroup, validated_commit::extract_group_membership};
4use xmtp_db::prelude::*;
5use xmtp_db::{
6    StorageError,
7    consent_record::{ConsentState, ConsentType},
8};
9use xmtp_id::{
10    InboxId,
11    associations::{AssociationState, Identifier},
12};
13
14#[derive(Debug, Clone)]
15pub struct GroupMember {
16    pub inbox_id: InboxId,
17    pub account_identifiers: Vec<Identifier>,
18    pub installation_ids: Vec<Vec<u8>>,
19    pub permission_level: PermissionLevel,
20    pub consent_state: ConsentState,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum PermissionLevel {
25    Member,
26    Admin,
27    SuperAdmin,
28}
29
30impl<Context> MlsGroup<Context>
31where
32    Context: XmtpSharedContext,
33{
34    /// Load the member list for the group from the DB, merging together multiple installations into a single entry
35    pub async fn members(&self) -> Result<Vec<GroupMember>, GroupError> {
36        let db = self.context.db();
37        let group_membership = self.with_group_snapshot(|mls_group| {
38            Ok(extract_group_membership(mls_group.extensions())?)
39        })?;
40        let requests = group_membership
41            .members
42            .into_iter()
43            .map(|(inbox_id, sequence_id)| (inbox_id, sequence_id as i64))
44            .filter(|(_, sequence_id)| *sequence_id != 0) // Skip the initial state
45            .collect::<Vec<_>>();
46
47        let association_states = db.batch_read_from_cache(requests.clone())?;
48        let mut association_states: Vec<AssociationState> = association_states
49            .into_iter()
50            .map(|a| a.try_into())
51            .collect::<Result<_, _>>()
52            .map_err(StorageError::from)?;
53        if association_states.len() != requests.len() {
54            // Attempt to rebuild the cache.
55            let missing_requests: Vec<_> = requests
56                .iter()
57                .filter_map(|(id, sequence)| {
58                    // Filter out association states we already have to avoid unnecessary requests.
59                    if association_states
60                        .iter()
61                        .any(|state| state.inbox_id() == id)
62                    {
63                        return None;
64                    }
65                    Some((id.as_str(), Some(*sequence)))
66                })
67                .collect();
68            let identity_updates = IdentityUpdates::new(&self.context);
69            let mut new_states = identity_updates
70                .batch_get_association_state(&db, &missing_requests)
71                .await?;
72            association_states.append(&mut new_states);
73
74            if association_states.len() != requests.len() {
75                // Cache miss - not expected to happen because:
76                // 1. We don't allow updates to the group metadata unless we have already validated the association state
77                // 2. When validating the association state, we must have written it to the cache
78                tracing::error!(
79                    "Failed to load all members for group - metadata: {:?}, computed members: {:?}",
80                    requests,
81                    association_states
82                );
83                return Err(GroupError::InvalidGroupMembership);
84            }
85        }
86        let mutable_metadata = self.mutable_metadata()?;
87        let members = association_states
88            .into_iter()
89            .map(|association_state| {
90                let inbox_id_str = association_state.inbox_id().to_string();
91                let is_admin = mutable_metadata.is_admin(&inbox_id_str);
92                let is_super_admin = mutable_metadata.is_super_admin(&inbox_id_str);
93                let permission_level = if is_super_admin {
94                    PermissionLevel::SuperAdmin
95                } else if is_admin {
96                    PermissionLevel::Admin
97                } else {
98                    PermissionLevel::Member
99                };
100
101                let consent = db.get_consent_record(inbox_id_str.clone(), ConsentType::InboxId)?;
102
103                Ok(GroupMember {
104                    inbox_id: inbox_id_str.clone(),
105                    account_identifiers: association_state.identifiers(),
106                    installation_ids: association_state.installation_ids(),
107                    permission_level,
108                    consent_state: consent.map_or(ConsentState::Unknown, |c| c.state),
109                })
110            })
111            .collect::<Result<Vec<GroupMember>, GroupError>>()?;
112
113        Ok(members)
114    }
115}