diff --git a/bindings_ffi/src/mls.rs b/bindings_ffi/src/mls.rs index 55a5e7a72..338d4feee 100644 --- a/bindings_ffi/src/mls.rs +++ b/bindings_ffi/src/mls.rs @@ -16,6 +16,7 @@ use xmtp_id::{ InboxId, }; use xmtp_mls::groups::scoped_client::LocalScopedGroupClient; +use xmtp_mls::storage::group::ConversationType; use xmtp_mls::storage::group_message::MsgQueryArgs; use xmtp_mls::storage::group_message::SortDirection; use xmtp_mls::{ @@ -23,7 +24,7 @@ use xmtp_mls::{ builder::ClientBuilder, client::{Client as MlsClient, ClientError}, groups::{ - group_metadata::{ConversationType, GroupMetadata}, + group_metadata::GroupMetadata, group_mutable_metadata::MetadataField, group_permissions::{ BasePolicies, GroupMutablePermissions, GroupMutablePermissionsError, @@ -857,7 +858,7 @@ impl FfiConversations { pub async fn sync_all_conversations(&self) -> Result { let inner = self.inner_client.as_ref(); - let groups = inner.find_groups(GroupQueryArgs::default())?; + let groups = inner.find_groups(GroupQueryArgs::default().include_sync_groups())?; log::info!( "groups for client inbox id {:?}: {:?}", diff --git a/bindings_node/src/conversation.rs b/bindings_node/src/conversation.rs index e8b1c31df..9caf28b69 100644 --- a/bindings_node/src/conversation.rs +++ b/bindings_node/src/conversation.rs @@ -8,11 +8,10 @@ use napi::{ use xmtp_cryptography::signature::ed25519_public_key_to_address; use xmtp_mls::{ groups::{ - group_metadata::{ConversationType, GroupMetadata as XmtpGroupMetadata}, - members::PermissionLevel as XmtpPermissionLevel, - MlsGroup, UpdateAdminListType, + group_metadata::GroupMetadata as XmtpGroupMetadata, + members::PermissionLevel as XmtpPermissionLevel, MlsGroup, UpdateAdminListType, }, - storage::group_message::MsgQueryArgs, + storage::{group::ConversationType, group_message::MsgQueryArgs}, }; use xmtp_proto::xmtp::mls::message_contents::EncodedContent as XmtpEncodedContent; diff --git a/bindings_node/src/conversations.rs b/bindings_node/src/conversations.rs index 4db816490..2babb9007 100644 --- a/bindings_node/src/conversations.rs +++ b/bindings_node/src/conversations.rs @@ -6,8 +6,8 @@ use napi::bindgen_prelude::{Error, Result, Uint8Array}; use napi::threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode}; use napi::JsFunction; use napi_derive::napi; -use xmtp_mls::groups::group_metadata::ConversationType as XmtpConversationType; use xmtp_mls::groups::{GroupMetadataOptions, PreconfiguredPolicies}; +use xmtp_mls::storage::group::ConversationType as XmtpConversationType; use xmtp_mls::storage::group::GroupMembershipState as XmtpGroupMembershipState; use xmtp_mls::storage::group::GroupQueryArgs; diff --git a/bindings_wasm/src/conversation.rs b/bindings_wasm/src/conversation.rs index 72cb0f65f..0c892e4d1 100644 --- a/bindings_wasm/src/conversation.rs +++ b/bindings_wasm/src/conversation.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use wasm_bindgen::JsValue; use wasm_bindgen::{prelude::wasm_bindgen, JsError}; +use xmtp_mls::storage::group::ConversationType; use crate::client::RustXmtpClient; use crate::encoded_content::EncodedContent; @@ -8,9 +9,8 @@ use crate::messages::{ListMessagesOptions, Message}; use crate::{consent_state::ConsentState, permissions::GroupPermissions}; use xmtp_cryptography::signature::ed25519_public_key_to_address; use xmtp_mls::groups::{ - group_metadata::{ConversationType, GroupMetadata as XmtpGroupMetadata}, - members::PermissionLevel as XmtpPermissionLevel, - MlsGroup, UpdateAdminListType, + group_metadata::GroupMetadata as XmtpGroupMetadata, + members::PermissionLevel as XmtpPermissionLevel, MlsGroup, UpdateAdminListType, }; use xmtp_proto::xmtp::mls::message_contents::EncodedContent as XmtpEncodedContent; diff --git a/bindings_wasm/src/conversations.rs b/bindings_wasm/src/conversations.rs index b9214deb5..07931f5b4 100644 --- a/bindings_wasm/src/conversations.rs +++ b/bindings_wasm/src/conversations.rs @@ -1,8 +1,8 @@ use std::sync::Arc; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsError, JsValue}; -use xmtp_mls::groups::group_metadata::ConversationType as XmtpConversationType; use xmtp_mls::groups::{GroupMetadataOptions, PreconfiguredPolicies}; +use xmtp_mls::storage::group::ConversationType as XmtpConversationType; use xmtp_mls::storage::group::GroupMembershipState as XmtpGroupMembershipState; use xmtp_mls::storage::group::GroupQueryArgs; diff --git a/bindings_wasm/tests/web.rs b/bindings_wasm/tests/web.rs index 14c633d27..32f6d02d6 100644 --- a/bindings_wasm/tests/web.rs +++ b/bindings_wasm/tests/web.rs @@ -1,8 +1,5 @@ -use bindings_wasm::client::Level; -use bindings_wasm::{ - client::{create_client, LogOptions}, - inbox_id::get_inbox_id_for_address, -}; +use bindings_wasm::client::LogLevel; +use bindings_wasm::client::{create_client, LogOptions}; use wasm_bindgen::prelude::*; use wasm_bindgen_test::*; use xmtp_api_http::constants::ApiUrls; @@ -35,7 +32,7 @@ pub async fn test_create_client() { Some(LogOptions { structured: false, performance: false, - level: Some(Level::Info), + level: Some(LogLevel::Info), }), ) .await; diff --git a/xmtp_mls/migrations/2024-11-13-145830_add_conversation_type_remove_purpose/down.sql b/xmtp_mls/migrations/2024-11-13-145830_add_conversation_type_remove_purpose/down.sql new file mode 100644 index 000000000..5649cb2bb --- /dev/null +++ b/xmtp_mls/migrations/2024-11-13-145830_add_conversation_type_remove_purpose/down.sql @@ -0,0 +1,10 @@ +ALTER TABLE groups +ADD COLUMN purpose INTEGER NOT NULL; + +UPDATE groups +SET purpose = CASE + WHEN conversation_type = 3 THEN 2 + ELSE 1 +END; + +ALTER TABLE groups DROP COLUMN conversation_type; diff --git a/xmtp_mls/migrations/2024-11-13-145830_add_conversation_type_remove_purpose/up.sql b/xmtp_mls/migrations/2024-11-13-145830_add_conversation_type_remove_purpose/up.sql new file mode 100644 index 000000000..9f0e21227 --- /dev/null +++ b/xmtp_mls/migrations/2024-11-13-145830_add_conversation_type_remove_purpose/up.sql @@ -0,0 +1,16 @@ +ALTER TABLE groups +ADD COLUMN conversation_type INTEGER NOT NULL; + +UPDATE groups +SET conversation_type = CASE + -- Purpose is conversation and is not a DM + -- Then set to 1 (ConversationType::Group) + WHEN purpose = 1 AND dm_inbox_id IS NULL THEN 1 + -- Otherwise dm_inbox_id is not null + -- Then set to 2 (ConversationType::Dm) + WHEN purpose = 1 THEN 2 + -- Otherwise this is a Sync Group + ELSE 3 +END; + +ALTER TABLE groups DROP COLUMN purpose; diff --git a/xmtp_mls/src/groups/device_sync.rs b/xmtp_mls/src/groups/device_sync.rs index 266c23301..0729c1c70 100644 --- a/xmtp_mls/src/groups/device_sync.rs +++ b/xmtp_mls/src/groups/device_sync.rs @@ -1,8 +1,7 @@ -use super::group_metadata::ConversationType; use super::{GroupError, MlsGroup}; use crate::configuration::NS_IN_HOUR; use crate::retry::{RetryBuilder, RetryableError}; -use crate::storage::group::GroupQueryArgs; +use crate::storage::group::{ConversationType, GroupQueryArgs}; use crate::storage::group_message::MsgQueryArgs; use crate::storage::DbConnection; use crate::subscriptions::{StreamMessages, SubscribeError, SyncMessage}; @@ -219,6 +218,10 @@ where * Will auto-send a sync request if sync group is created. */ pub async fn sync_init(&self, provider: &XmtpOpenMlsProvider) -> Result<(), DeviceSyncError> { + tracing::info!( + "Initializing device sync... url: {:?}", + self.history_sync_url + ); if self.get_sync_group().is_err() { self.ensure_sync_group(provider).await?; @@ -227,6 +230,7 @@ where self.send_sync_request(provider, DeviceSyncKind::MessageHistory) .await?; } + tracing::info!("Device sync initialized."); Ok(()) } @@ -252,6 +256,7 @@ where provider: &XmtpOpenMlsProvider, kind: DeviceSyncKind, ) -> Result { + tracing::info!("Sending a sync request for {kind:?}"); let request = DeviceSyncRequest::new(kind); // find the sync group diff --git a/xmtp_mls/src/groups/device_sync/message_sync.rs b/xmtp_mls/src/groups/device_sync/message_sync.rs index f427e0b18..8cd0bddba 100644 --- a/xmtp_mls/src/groups/device_sync/message_sync.rs +++ b/xmtp_mls/src/groups/device_sync/message_sync.rs @@ -15,10 +15,11 @@ where conn: &DbConnection, ) -> Result, DeviceSyncError> { let groups = conn - .find_groups(GroupQueryArgs::default().conversation_type(ConversationType::Group))? + .find_groups(GroupQueryArgs::default())? .into_iter() .map(Syncable::Group) .collect(); + Ok(groups) } diff --git a/xmtp_mls/src/groups/group_metadata.rs b/xmtp_mls/src/groups/group_metadata.rs index b66fe3cc3..528e0e9ce 100644 --- a/xmtp_mls/src/groups/group_metadata.rs +++ b/xmtp_mls/src/groups/group_metadata.rs @@ -7,6 +7,8 @@ use xmtp_proto::xmtp::mls::message_contents::{ GroupMetadataV1 as GroupMetadataProto, Inbox as InboxProto, }; +use crate::storage::group::ConversationType; + #[derive(Debug, Error)] pub enum GroupMetadataError { #[error("serialization: {0}")] @@ -104,12 +106,6 @@ impl TryFrom<&Extensions> for GroupMetadata { * *DM*: A conversation between 2 members with simplified permissions * *Sync*: A conversation between all the devices of a single member with simplified permissions */ -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum ConversationType { - Group, - Dm, - Sync, -} impl From for ConversationTypeProto { fn from(value: ConversationType) -> Self { diff --git a/xmtp_mls/src/groups/mls_sync.rs b/xmtp_mls/src/groups/mls_sync.rs index bcf46d48f..07eb96da0 100644 --- a/xmtp_mls/src/groups/mls_sync.rs +++ b/xmtp_mls/src/groups/mls_sync.rs @@ -500,6 +500,7 @@ where // Ignore this installation's sync messages if !sent_from_this_installation { + tracing::info!("Received a history request."); let _ = self.client.local_events().send(LocalEvents::SyncMessage( SyncMessage::Request { message_id }, )); @@ -531,6 +532,7 @@ where // Ignore this installation's sync messages if !sent_from_this_installation { + tracing::info!("Received a history reply."); let _ = self.client.local_events().send(LocalEvents::SyncMessage( SyncMessage::Reply { message_id }, )); diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index 272617bb9..2c30176df 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -52,7 +52,7 @@ use self::{ validated_commit::extract_group_membership, }; use self::{ - group_metadata::{ConversationType, GroupMetadata, GroupMetadataError}, + group_metadata::{GroupMetadata, GroupMetadataError}, group_permissions::PolicySet, validated_commit::CommitValidationError, }; @@ -85,7 +85,7 @@ use crate::{ storage::{ consent_record::{ConsentState, ConsentType, StoredConsentRecord}, db_connection::DbConnection, - group::{GroupMembershipState, Purpose, StoredGroup}, + group::{ConversationType, GroupMembershipState, StoredGroup}, group_intent::IntentKind, group_message::{DeliveryStatus, GroupMessageKind, MsgQueryArgs, StoredGroupMessage}, sql_key_store, @@ -313,7 +313,7 @@ impl MlsGroup { let provider = XmtpOpenMlsProvider::new(conn); let creator_inbox_id = context.inbox_id(); let protected_metadata = - build_protected_metadata_extension(creator_inbox_id, Purpose::Conversation)?; + build_protected_metadata_extension(creator_inbox_id, ConversationType::Group)?; let mutable_metadata = build_mutable_metadata_extension_default(creator_inbox_id, opts)?; let group_membership = build_starting_group_membership_extension(creator_inbox_id, 0); let mutable_permissions = build_mutable_permissions_extension(permissions_policy_set)?; @@ -427,16 +427,16 @@ impl MlsGroup { } else { None }; - let group_type = metadata.conversation_type; + let conversation_type = metadata.conversation_type; - let to_store = match group_type { + let to_store = match conversation_type { ConversationType::Group => StoredGroup::new_from_welcome( group_id.clone(), now_ns(), GroupMembershipState::Pending, added_by_inbox, welcome_id, - Purpose::Conversation, + conversation_type, dm_inbox_id, ), ConversationType::Dm => { @@ -447,7 +447,7 @@ impl MlsGroup { GroupMembershipState::Pending, added_by_inbox, welcome_id, - Purpose::Conversation, + conversation_type, dm_inbox_id, ) } @@ -457,7 +457,7 @@ impl MlsGroup { GroupMembershipState::Allowed, added_by_inbox, welcome_id, - Purpose::Sync, + conversation_type, dm_inbox_id, ), }; @@ -517,7 +517,7 @@ impl MlsGroup { let provider = client.mls_provider()?; let protected_metadata = - build_protected_metadata_extension(creator_inbox_id, Purpose::Sync)?; + build_protected_metadata_extension(creator_inbox_id, ConversationType::Sync)?; let mutable_metadata = build_mutable_metadata_extension_default( creator_inbox_id, GroupMetadataOptions::default(), @@ -1156,14 +1156,9 @@ pub fn extract_group_id(message: &GroupMessage) -> Result, MessageProces fn build_protected_metadata_extension( creator_inbox_id: &str, - group_purpose: Purpose, + conversation_type: ConversationType, ) -> Result { - let group_type = match group_purpose { - Purpose::Conversation => ConversationType::Group, - Purpose::Sync => ConversationType::Sync, - }; - - let metadata = GroupMetadata::new(group_type, creator_inbox_id.to_string(), None); + let metadata = GroupMetadata::new(conversation_type, creator_inbox_id.to_string(), None); let protected_metadata = Metadata::new(metadata.try_into()?); Ok(Extension::ImmutableMetadata(protected_metadata)) @@ -1541,7 +1536,7 @@ pub(crate) mod tests { groups::{ build_dm_protected_metadata_extension, build_mutable_metadata_extension_default, build_protected_metadata_extension, - group_metadata::{ConversationType, GroupMetadata}, + group_metadata::GroupMetadata, group_mutable_metadata::MetadataField, intents::{PermissionPolicyOption, PermissionUpdateType}, members::{GroupMember, PermissionLevel}, @@ -1550,8 +1545,7 @@ pub(crate) mod tests { }, storage::{ consent_record::ConsentState, - group::GroupQueryArgs, - group::Purpose, + group::{ConversationType, GroupQueryArgs}, group_intent::{IntentKind, IntentState}, group_message::{GroupMessageKind, MsgQueryArgs, StoredGroupMessage}, }, @@ -3721,7 +3715,7 @@ pub(crate) mod tests { // Test case 2: Invalid conversation type let invalid_protected_metadata = - build_protected_metadata_extension(creator_inbox_id, Purpose::Conversation).unwrap(); + build_protected_metadata_extension(creator_inbox_id, ConversationType::Group).unwrap(); let invalid_type_group = MlsGroup::::create_test_dm_group( client.clone().into(), dm_target_inbox_id.clone(), diff --git a/xmtp_mls/src/storage/encrypted_store/group.rs b/xmtp_mls/src/storage/encrypted_store/group.rs index 8babb7e5c..f5241f53e 100644 --- a/xmtp_mls/src/storage/encrypted_store/group.rs +++ b/xmtp_mls/src/storage/encrypted_store/group.rs @@ -5,9 +5,7 @@ use super::{ schema::groups::{self, dsl}, Sqlite, }; -use crate::{ - groups::group_metadata::ConversationType, impl_fetch, impl_store, DuplicateItem, StorageError, -}; +use crate::{impl_fetch, impl_store, DuplicateItem, StorageError}; use diesel::{ backend::Backend, deserialize::{self, FromSql, FromSqlRow}, @@ -34,8 +32,8 @@ pub struct StoredGroup { pub membership_state: GroupMembershipState, /// Track when the latest, most recent installations were checked pub installations_last_checked: i64, - /// Enum, [`Purpose`] signifies the group purpose which extends to who can access it. - pub purpose: Purpose, + /// Enum, [`ConversationType`] signifies the group conversation type which extends to who can access it. + pub conversation_type: ConversationType, /// The inbox_id of who added the user to a group. pub added_by_inbox_id: String, /// The sequence id of the welcome message @@ -57,7 +55,7 @@ impl StoredGroup { membership_state: GroupMembershipState, added_by_inbox_id: String, welcome_id: i64, - purpose: Purpose, + conversation_type: ConversationType, dm_inbox_id: Option, ) -> Self { Self { @@ -65,7 +63,7 @@ impl StoredGroup { created_at_ns, membership_state, installations_last_checked: 0, - purpose, + conversation_type, added_by_inbox_id, welcome_id: Some(welcome_id), rotated_at_ns: 0, @@ -86,7 +84,10 @@ impl StoredGroup { created_at_ns, membership_state, installations_last_checked: 0, - purpose: Purpose::Conversation, + conversation_type: match dm_inbox_id { + Some(_) => ConversationType::Dm, + None => ConversationType::Group, + }, added_by_inbox_id, welcome_id: None, rotated_at_ns: 0, @@ -106,7 +107,7 @@ impl StoredGroup { created_at_ns, membership_state, installations_last_checked: 0, - purpose: Purpose::Sync, + conversation_type: ConversationType::Sync, added_by_inbox_id: "".into(), welcome_id: None, rotated_at_ns: 0, @@ -123,6 +124,7 @@ pub struct GroupQueryArgs { pub limit: Option, pub conversation_type: Option, pub consent_state: Option, + pub include_sync_groups: bool, } impl AsRef for GroupQueryArgs { @@ -183,11 +185,15 @@ impl GroupQueryArgs { pub fn consent_state(self, consent_state: ConsentState) -> Self { self.maybe_consent_state(Some(consent_state)) } - pub fn maybe_consent_state(mut self, consent_state: Option) -> Self { self.consent_state = consent_state; self } + + pub fn include_sync_groups(mut self) -> Self { + self.include_sync_groups = true; + self + } } impl DbConnection { @@ -205,6 +211,7 @@ impl DbConnection { limit, conversation_type, consent_state, + include_sync_groups, } = args.as_ref(); let mut query = groups_dsl::groups @@ -228,18 +235,14 @@ impl DbConnection { } if let Some(conversation_type) = conversation_type { - match conversation_type { - ConversationType::Group => { - query = query.filter(groups_dsl::dm_inbox_id.is_null()); - } - ConversationType::Dm => { - query = query.filter(groups_dsl::dm_inbox_id.is_not_null()); - } - ConversationType::Sync => {} - } + query = query.filter(groups_dsl::conversation_type.eq(conversation_type)); } - query = query.filter(groups_dsl::purpose.eq(Purpose::Conversation)); + // Were sync groups explicitly asked for? Was the include_sync_groups flag set to true? + // Otherwise filter sync groups out by default. + if !matches!(conversation_type, Some(ConversationType::Sync)) && !include_sync_groups { + query = query.filter(groups_dsl::conversation_type.ne(ConversationType::Sync)); + } let groups = if let Some(consent_state) = consent_state { if *consent_state == ConsentState::Unknown { @@ -285,7 +288,7 @@ impl DbConnection { pub fn latest_sync_group(&self) -> Result, StorageError> { let query = dsl::groups .order(dsl::created_at_ns.desc()) - .filter(dsl::purpose.eq(Purpose::Sync)) + .filter(dsl::conversation_type.eq(ConversationType::Sync)) .limit(1); Ok(self.raw_query(|conn| query.load(conn))?.pop()) @@ -484,12 +487,12 @@ where #[repr(i32)] #[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq, AsExpression, FromSqlRow)] #[diesel(sql_type = Integer)] -pub enum Purpose { - Conversation = 1, - Sync = 2, +pub enum ConversationType { + Group = 1, + Dm = 2, + Sync = 3, } - -impl ToSql for Purpose +impl ToSql for ConversationType where i32: ToSql, { @@ -499,14 +502,15 @@ where } } -impl FromSql for Purpose +impl FromSql for ConversationType where i32: FromSql, { fn from_sql(bytes: ::RawValue<'_>) -> deserialize::Result { match i32::from_sql(bytes)? { - 1 => Ok(Purpose::Conversation), - 2 => Ok(Purpose::Sync), + 1 => Ok(ConversationType::Group), + 2 => Ok(ConversationType::Dm), + 3 => Ok(ConversationType::Sync), x => Err(format!("Unrecognized variant {}", x).into()), } } @@ -735,8 +739,8 @@ pub(crate) mod tests { let fetched_group: Option = conn.fetch(&test_group.id).unwrap(); assert_eq!(fetched_group, Some(test_group)); - let purpose = fetched_group.unwrap().purpose; - assert_eq!(purpose, Purpose::Conversation); + let conversation_type = fetched_group.unwrap().conversation_type; + assert_eq!(conversation_type, ConversationType::Group); }) .await } @@ -750,14 +754,14 @@ pub(crate) mod tests { let membership_state = GroupMembershipState::Allowed; let sync_group = StoredGroup::new_sync_group(id, created_at_ns, membership_state); - let purpose = sync_group.purpose; - assert_eq!(purpose, Purpose::Sync); + let conversation_type = sync_group.conversation_type; + assert_eq!(conversation_type, ConversationType::Sync); sync_group.store(conn).unwrap(); let found = conn.latest_sync_group().unwrap(); assert!(found.is_some()); - assert_eq!(found.unwrap().purpose, Purpose::Sync) + assert_eq!(found.unwrap().conversation_type, ConversationType::Sync) }) .await } diff --git a/xmtp_mls/src/storage/encrypted_store/schema.rs b/xmtp_mls/src/storage/encrypted_store/schema.rs index 93254c687..5a6e3385b 100644 --- a/xmtp_mls/src/storage/encrypted_store/schema.rs +++ b/xmtp_mls/src/storage/encrypted_store/schema.rs @@ -50,7 +50,7 @@ diesel::table! { created_at_ns -> BigInt, membership_state -> Integer, installations_last_checked -> BigInt, - purpose -> Integer, + conversation_type -> Integer, added_by_inbox_id -> Text, welcome_id -> Nullable, dm_inbox_id -> Nullable, diff --git a/xmtp_mls/src/subscriptions.rs b/xmtp_mls/src/subscriptions.rs index be4c98775..d5d987043 100644 --- a/xmtp_mls/src/subscriptions.rs +++ b/xmtp_mls/src/subscriptions.rs @@ -11,11 +11,11 @@ use xmtp_proto::{api_client::XmtpMlsStreams, xmtp::mls::api::v1::WelcomeMessage} use crate::{ client::{extract_welcome_message, ClientError, MessageProcessingError}, - groups::{group_metadata::ConversationType, subscriptions, GroupError, MlsGroup}, + groups::{subscriptions, GroupError, MlsGroup}, retry::{Retry, RetryableError}, retry_async, retryable, storage::{ - group::{GroupQueryArgs, StoredGroup}, + group::{ConversationType, GroupQueryArgs, StoredGroup}, group_message::StoredGroupMessage, StorageError, }, @@ -421,8 +421,11 @@ pub(crate) mod tests { use crate::{ builder::ClientBuilder, - groups::{group_metadata::ConversationType, GroupMetadataOptions}, - storage::{group::GroupQueryArgs, group_message::StoredGroupMessage}, + groups::GroupMetadataOptions, + storage::{ + group::{ConversationType, GroupQueryArgs}, + group_message::StoredGroupMessage, + }, utils::test::{Delivery, FullXmtpClient, TestClient}, Client, StreamHandle, };