1use crate::{
2 attachments::Attachments,
3 context::XmtpSharedContext,
4 groups::{
5 ConversationListItem, GroupError, MlsGroup, group_permissions::PolicySet,
6 welcome_sync::WelcomeService,
7 },
8 identity::{Identity, IdentityError, parse_credential},
9 identity_updates::{
10 IdentityUpdateError, IdentityUpdates, load_identity_updates,
11 load_identity_updates_for_client,
12 },
13 inbox_handles::{self, HandleError, IdentityTarget, account_targets, resolve_verified_in},
14 mls_store::{MlsStore, MlsStoreError},
15 server_configuration::{BlockedConnection, ServerConfigError},
16 subscriptions::{
17 LocalEventError,
18 internal::{emit_consent_changes, emit_deleted_messages},
19 },
20 utils::VersionInfo,
21 worker::device_sync::{DeviceSyncClient, worker::SyncMetric},
22 worker::{WorkerRunner, metrics::WorkerMetrics},
23};
24pub mod notifications;
25pub use crate::core::config::DeviceSync;
26use crate::subscriptions::catch_up::{CatchUpError, CatchUpSummary};
27#[cfg(not(target_arch = "wasm32"))]
28use crate::subscriptions::router_callbacks::{self, ApiClientIdentity};
29#[cfg(any(test, feature = "test-utils"))]
30use crate::subscriptions::stream_messages::stream_stats::StreamWithStats;
31use crate::{
32 core::events::PreferenceUpdate,
33 subscriptions::{self, SubscribeError},
34};
35use crate::{
36 groups::welcome_sync::GroupSyncSummary,
37 identity_updates::{batch_get_association_state_with_verifier, get_creation_signature_kind},
38 messages::{
39 decoded_message::DecodedMessage,
40 enrichment::{EnrichMessageError, enrich_messages},
41 },
42};
43use futures::Stream;
44use notifications::{NotificationConfig, NotificationError, NotificationState};
45use openmls::prelude::tls_codec::Error as TlsCodecError;
46use std::{
47 collections::HashMap,
48 sync::{Arc, atomic::Ordering},
49};
50use thiserror::Error;
51use xmtp_api::{ApiClientWrapper, XmtpApi};
52use xmtp_common::{ErrorCode, Event, Retry, retry_async, retryable};
53use xmtp_common::{MaybeSend, StreamHandle};
54use xmtp_configuration::{
55 CONVERSATION_LIST_DEFAULT_LIMIT, CREATE_PQ_KEY_PACKAGE_EXTENSION,
56 KEY_PACKAGE_ROTATION_INTERVAL_NS, LOCAL_PAGE_MAX_LIMIT,
57};
58use xmtp_cryptography::signature::IdentifierValidationError;
59use xmtp_db::TransactionOutcome::Continue;
60use xmtp_db::consent_record::StoredConsentRecord;
61use xmtp_db::{
62 ConnectionExt, NotFound, StorageError, TransactionOutcome, XmtpDb,
63 consent_record::{
64 ConsentActError, ConsentKey, ConsentState, ConsentType, ConsentWrite, Decision, Owed,
65 conversation_consent, write_consent,
66 },
67 db_connection::DbConnection,
68 encrypted_store::conversation_list::ConversationListItem as DbConversationListItem,
69 group::{ConversationType, GroupMembershipState, GroupQueryArgs},
70 group_message::StoredGroupMessage,
71 identity::StoredIdentity,
72};
73use xmtp_db::{group::GroupQueryOrderBy, prelude::*};
74use xmtp_events::DeletionCause;
75use xmtp_id::associations::handle::Handle;
76use xmtp_id::key_package::{KeyPackageVerificationError, VerifiedKeyPackageV2};
77use xmtp_id::{
78 AsIdRef, InboxId, InboxIdRef, InboxOwner,
79 associations::{
80 AssociationError, AssociationState, Identifier, MemberIdentifier, SignatureError,
81 builder::{SignatureRequest, SignatureRequestError},
82 },
83 scw_verifier::SmartContractSignatureVerifier,
84};
85use xmtp_macro::log_event;
86use xmtp_mls_group_state::{
87 group::{DMMetadataOptions, GroupMetadataOptions},
88 group_metadata::DmMembers,
89 group_mutable_metadata::MessageDisappearingSettings,
90};
91#[cfg(not(target_arch = "wasm32"))]
92use xmtp_proto::api_client::XmtpMlsBidiStreams;
93use xmtp_proto::api_client::XmtpMlsStreams;
94use xmtp_proto::types::InstallationId;
95use xmtp_proto::{
96 api::HasStats,
97 api_client::{ApiStats, IdentityStats, XmtpBackendClient},
98};
99
100use xmtp_proto::types::GroupId;
101#[derive(Clone, Copy, Default, Debug)]
103pub enum Network {
104 Local(&'static str),
105 #[default]
106 Dev,
107 Prod,
108}
109
110#[derive(Debug, Clone)]
112pub struct VisibilityConfirmationOptions {
113 pub timeout_ms: u64,
114}
115
116const REGISTRATION_INITIAL_BACKOFF: std::time::Duration = std::time::Duration::from_millis(50);
117const REGISTRATION_MAX_BACKOFF: std::time::Duration = std::time::Duration::from_secs(1);
118
119impl Default for VisibilityConfirmationOptions {
120 fn default() -> Self {
121 Self { timeout_ms: 30_000 }
122 }
123}
124
125#[derive(Clone, Debug, PartialEq, Eq)]
128pub struct ConsentAct {
129 pub entity_type: ConsentType,
130 pub entity: String,
131 pub state: ConsentState,
132}
133
134impl ConsentAct {
135 pub fn new(entity_type: ConsentType, state: ConsentState, entity: String) -> Self {
136 Self {
137 entity_type,
138 entity,
139 state,
140 }
141 }
142}
143
144#[derive(Debug, Error, ErrorCode)]
145pub enum ClientError {
146 #[error(transparent)]
147 #[error_code(inherit)]
148 AddressValidation(#[from] IdentifierValidationError),
149 #[error("could not publish: {0}")]
153 PublishError(String),
154 #[error("storage error: {0}")]
158 Storage(#[from] StorageError),
159 #[error("API error: {0}")]
163 Api(#[from] xmtp_api::ApiError),
164 #[error("identity error: {0}")]
168 Identity(#[from] crate::identity::IdentityError),
169 #[error("TLS Codec error: {0}")]
173 TlsError(#[from] TlsCodecError),
174 #[error("key package verification: {0}")]
178 KeyPackageVerification(#[from] KeyPackageVerificationError),
179 #[error("Stream inconsistency error: {0}")]
183 StreamInconsistency(String),
184 #[error("Association error: {0}")]
188 Association(#[from] AssociationError),
189 #[error("signature validation error: {0}")]
193 SignatureValidation(#[from] SignatureError),
194 #[error(transparent)]
198 IdentityUpdate(#[from] IdentityUpdateError),
199 #[error(transparent)]
203 SignatureRequest(#[from] SignatureRequestError),
204 #[error(transparent)]
209 Group(Box<GroupError>),
210 #[error(transparent)]
214 LocalEvent(#[from] LocalEventError),
215 #[error(transparent)]
219 Db(#[from] xmtp_db::ConnectionError),
220 #[error("Failed to decode message")]
225 MessageDecodeFailed,
226 #[error(transparent)]
230 MlsStore(#[from] MlsStoreError),
231 #[error(transparent)]
235 EnrichMessage(#[from] EnrichMessageError),
236 #[error(transparent)]
240 Conversion(#[from] xmtp_proto::ConversionError),
241 #[error(transparent)]
242 #[error_code(inherit)]
243 Handle(#[from] crate::inbox_handles::HandleError),
244 #[error("Registration is not visible")]
249 RegistrationNotVisible,
250 #[error("client is closed")]
255 AlreadyClosed,
256 #[error("server configuration unavailable: {0}")]
263 ConfigurationUnavailable(#[source] Box<crate::server_configuration::ConfigurationFetchError>),
264 #[error("server configuration invalid: {0}")]
270 ConfigurationInvalid(#[from] xmtp_configuration::ServerConfigurationError),
271 #[error("this database is bound to backend {stored}, but {received} answered")]
277 BackendMismatch { stored: String, received: String },
278 #[error("client version {client} is below the {minimum} this backend requires")]
283 ClientVersionTooOld { client: String, minimum: String },
284 #[error(
289 "this backend requires authentication; required scopes: [{}]",
290 required_scopes.join(", ")
291 )]
292 AuthRequired { required_scopes: Vec<String> },
293 #[error(
298 "this backend does not accept smart contract wallet signatures on {chain}; accepted: [{}]",
299 accepted.join(", ")
300 )]
301 ChainNotAccepted {
302 chain: String,
303 accepted: Vec<String>,
304 },
305 #[error("invalid list options: {0}")]
311 InvalidListOptions(&'static str),
312 #[error("consent act: {0}")]
317 #[error_code(inherit)]
318 ConsentAct(#[from] ConsentActError),
319}
320
321impl ClientError {
322 #[deny(clippy::wildcard_enum_match_arm)]
323 pub fn db_needs_connection(&self) -> bool {
324 use crate::worker::NeedsDbReconnect;
325 match self {
326 Self::Storage(s) | Self::ConsentAct(ConsentActError::Storage(s)) => {
327 s.db_needs_connection()
328 }
329 Self::Db(c) => c.db_needs_connection(),
330 Self::Group(error) => error.needs_db_reconnect(),
331 Self::Identity(error) => error.needs_db_reconnect(),
332 Self::IdentityUpdate(error) => error.needs_db_reconnect(),
333 Self::MlsStore(error) => error.needs_db_reconnect(),
334 Self::ConfigurationUnavailable(error) => error.db_needs_connection(),
335 Self::ConsentAct(_)
336 | Self::AddressValidation(..)
337 | Self::PublishError(..)
338 | Self::Api(..)
339 | Self::TlsError(..)
340 | Self::KeyPackageVerification(..)
341 | Self::StreamInconsistency(..)
342 | Self::Association(..)
343 | Self::SignatureValidation(..)
344 | Self::SignatureRequest(..)
345 | Self::LocalEvent(..)
346 | Self::MessageDecodeFailed
347 | Self::EnrichMessage(..)
348 | Self::Conversion(..)
349 | Self::Handle(..)
350 | Self::RegistrationNotVisible
351 | Self::AlreadyClosed
352 | Self::ConfigurationInvalid(..)
353 | Self::BackendMismatch { .. }
354 | Self::ClientVersionTooOld { .. }
355 | Self::AuthRequired { .. }
356 | Self::ChainNotAccepted { .. }
357 | Self::InvalidListOptions(..) => false,
358 }
359 }
360}
361
362impl From<NotFound> for ClientError {
363 fn from(value: NotFound) -> Self {
364 ClientError::Storage(StorageError::NotFound(value))
365 }
366}
367
368impl From<GroupError> for ClientError {
369 fn from(err: GroupError) -> ClientError {
370 ClientError::Group(Box::new(err))
371 }
372}
373
374impl From<BlockedConnection> for ClientError {
375 fn from(blocked_connection: BlockedConnection) -> Self {
376 match blocked_connection {
377 BlockedConnection::BackendMismatch { stored, received } => {
378 ClientError::BackendMismatch { stored, received }
379 }
380 BlockedConnection::ClientVersionTooOld { client, minimum } => {
381 ClientError::ClientVersionTooOld { client, minimum }
382 }
383 }
384 }
385}
386
387impl From<ServerConfigError> for ClientError {
388 fn from(error: ServerConfigError) -> Self {
389 match error {
390 ServerConfigError::Unavailable(error) => ClientError::ConfigurationUnavailable(error),
391 ServerConfigError::Invalid(error) => ClientError::ConfigurationInvalid(error),
392 ServerConfigError::Blocked(blocked_connection) => blocked_connection.into(),
393 ServerConfigError::Closed => ClientError::AlreadyClosed,
394 }
395 }
396}
397
398impl xmtp_common::RetryableError for ClientError {
399 fn is_retryable(&self) -> bool {
400 match self {
401 ClientError::Group(group_error) => retryable!(group_error),
402 ClientError::IdentityUpdate(error) => retryable!(error),
403 ClientError::Api(api_error) => retryable!(api_error),
404 ClientError::Storage(storage_error) => retryable!(storage_error),
405 ClientError::Db(db) => retryable!(db),
406 ClientError::SignatureValidation(e) => retryable!(e),
410 ClientError::ConfigurationUnavailable(e) => retryable!(e),
414 _ => false,
415 }
416 }
417
418 fn rate_limit_delay(&self) -> Option<xmtp_common::time::Duration> {
419 match self {
420 ClientError::Group(group_error) => group_error.rate_limit_delay(),
421 ClientError::IdentityUpdate(error) => error.rate_limit_delay(),
422 ClientError::Api(api_error) => api_error.rate_limit_delay(),
423 ClientError::SignatureValidation(e) => e.rate_limit_delay(),
424 ClientError::ConfigurationUnavailable(e) => e.rate_limit_delay(),
425 _ => None,
426 }
427 }
428}
429
430pub fn stream_failure_details(
435 error: &(dyn std::error::Error + 'static),
436) -> Option<crate::subscriptions::stream_failure::StreamFailureDetails> {
437 crate::subscriptions::stream_failure::stream_failure_details_opening(error, |error| {
438 match error.downcast_ref::<ClientError>() {
439 Some(ClientError::Group(error)) => Some(error.as_ref()),
440 _ => None,
441 }
442 })
443}
444
445pub struct Client<Context> {
447 pub context: Context,
448 pub installation_id: InstallationId,
449 pub(crate) workers: Arc<WorkerRunner>,
450 pub(crate) app_lifetime: Arc<AppLifetime>,
451}
452
453pub(crate) struct AppLifetime {
454 pub(crate) events: xmtp_events::EventBus<crate::subscriptions::internal::InternalEvent>,
455}
456
457impl Drop for AppLifetime {
458 fn drop(&mut self) {
459 self.events.close_app_subscriptions();
460 }
461}
462
463impl<Context> Drop for Client<Context> {
464 fn drop(&mut self) {
465 log_event!(Event::ClientDropped, self.installation_id);
466 }
467}
468
469impl<Context: Clone> Clone for Client<Context> {
471 fn clone(&self) -> Self {
472 Self {
473 context: self.context.clone(),
474 installation_id: self.installation_id,
475 workers: self.workers.clone(),
476 app_lifetime: self.app_lifetime.clone(),
477 }
478 }
479}
480
481impl<Context> Client<Context>
482where
483 Context: XmtpSharedContext,
484{
485 pub fn identity_updates(&self) -> IdentityUpdates<&Context> {
486 IdentityUpdates::new(&self.context)
487 }
488
489 pub fn mls_store(&self) -> MlsStore<Context> {
490 MlsStore::new(self.context.clone())
491 }
492
493 pub fn scw_verifier(&self) -> Arc<Box<dyn SmartContractSignatureVerifier>> {
494 self.context.scw_verifier()
495 }
496
497 pub fn version_info(&self) -> &VersionInfo {
498 self.context.version_info()
499 }
500}
501
502impl<Context> Client<Context>
503where
504 Context: XmtpSharedContext,
505 Context::ApiClient: HasStats,
506{
507 pub fn api_stats(&self) -> ApiStats {
508 self.context.api().api_client.mls_stats()
509 }
510
511 pub fn identity_api_stats(&self) -> IdentityStats {
512 self.context.api().api_client.identity_stats()
513 }
514
515 pub fn clear_stats(&self) {
516 self.context.api().api_client.mls_stats().clear();
517 self.context.api().api_client.identity_stats().clear();
518 }
519}
520
521pub async fn inbox_addresses_with_verifier<ApiClient: XmtpApi>(
523 api_client: &ApiClientWrapper<ApiClient>,
524 conn: &impl DbQuery,
525 inbox_ids: Vec<InboxIdRef<'_>>,
526 scw_verifier: &impl SmartContractSignatureVerifier,
527) -> Result<Vec<AssociationState>, ClientError> {
528 load_identity_updates(api_client, conn, &inbox_ids).await?;
529 let state = batch_get_association_state_with_verifier(
530 conn,
531 &inbox_ids.into_iter().map(|i| (i, None)).collect::<Vec<_>>(),
532 scw_verifier,
533 )
534 .await?;
535 Ok(state)
536}
537
538impl<Context> Client<Context>
539where
540 Context: XmtpSharedContext + 'static,
541{
542 pub fn reconnect_db(&self) -> Result<(), ClientError> {
544 let _call = self
548 .context
549 .foreground_calls()
550 .enter()
551 .ok_or(ClientError::AlreadyClosed)?;
552 if self.context.is_closed() {
553 return Err(ClientError::AlreadyClosed);
554 }
555 #[cfg(all(test, not(target_arch = "wasm32")))]
556 test_hooks::pause_reconnect(&self.context.installation_id().to_vec());
557 self.context.db().reconnect().map_err(StorageError::from)?;
558 self.workers.spawn(self.context.clone());
559 Ok(())
560 }
561
562 pub async fn close(&self) -> Result<(), ClientError> {
569 if self.context.shutdown_complete() {
574 return Ok(());
575 }
576 self.context
577 .api()
578 .api_client
579 .unregister_client_event_writer(self.context.public_event_writer());
580 self.context.events().close_app_subscriptions();
581 self.context.cancellation_token().cancel();
582 self.context.foreground_calls().close_and_wait().await;
586 let delivery_result = self.context.close_message_delivery();
587 self.workers.shutdown().await;
588 self.context.events().close_internal_subscriptions();
589 delivery_result?;
592 self.context
593 .db()
594 .disconnect()
595 .map_err(xmtp_db::StorageError::from)?;
596 self.context.mark_shutdown_complete();
597 log_event!(Event::ClientClosed, self.installation_id);
598 Ok(())
599 }
600
601 pub async fn wait_for_sync_worker_init(&self) {
603 self.workers.wait_for_sync_worker_init().await;
604 }
605
606 pub fn sync_metrics(&self) -> Option<Arc<WorkerMetrics<SyncMetric>>> {
607 self.workers.sync_metrics()
608 }
609}
610
611impl<Context> Client<Context>
612where
613 Context: XmtpSharedContext,
614{
615 pub fn server_configuration(&self) -> &xmtp_configuration::ServerConfiguration {
619 self.context.server_configuration().configuration()
620 }
621
622 pub async fn refresh_server_configuration(
628 &self,
629 ) -> Result<xmtp_configuration::ServerConfiguration, ClientError> {
630 Ok(crate::core::preflight::refresh(&self.context).await?)
631 }
632
633 pub fn installation_public_key(&self) -> InstallationId {
635 self.context.installation_id()
636 }
637 pub fn inbox_id(&self) -> InboxIdRef<'_> {
639 self.context.identity().inbox_id()
640 }
641
642 pub fn db(&self) -> <Context::Db as XmtpDb>::DbQuery {
645 self.context.db()
646 }
647
648 #[cfg(any(test, feature = "test-utils"))]
651 pub fn set_name(&self, name: &str) {
652 log_event!(Event::AssociateName, self.context.installation_id(), name);
653 }
654
655 pub fn device_sync_client(&self) -> DeviceSyncClient<Context> {
656 let metrics = self.context.sync_metrics();
657 DeviceSyncClient::new(
658 self.context.clone(),
659 metrics.unwrap_or(Arc::new(WorkerMetrics::new(self.context.installation_id()))),
660 )
661 }
662
663 pub async fn find_inbox_id_from_identifier(
667 &self,
668 identifier: Identifier,
669 ) -> Result<Option<String>, ClientError> {
670 Ok(
671 resolve_verified_in(&self.context, &[IdentityTarget::Account(identifier)])
672 .await?
673 .pop()
674 .flatten(),
675 )
676 }
677
678 pub fn inbox_sequence_id(
681 &self,
682 conn: &DbConnection<<Context::Db as XmtpDb>::Connection>,
683 ) -> Result<i64, StorageError> {
684 self.context
685 .identity()
686 .sequence_id(conn)
687 .map_err(Into::into)
688 }
689
690 pub async fn inbox_state(
692 &self,
693 refresh_from_network: bool,
694 ) -> Result<AssociationState, ClientError> {
695 let conn = self.context.db();
696 let inbox_id = self.inbox_id();
697 if refresh_from_network {
698 load_identity_updates_for_client(&self.context, &conn, &[inbox_id]).await?;
699 }
700 let identity_service = IdentityUpdates::new(&self.context);
701 let state = identity_service
702 .get_association_state(&conn, inbox_id, None)
703 .await?;
704 Ok(state)
705 }
706
707 pub async fn inbox_addresses(
709 &self,
710 refresh_from_network: bool,
711 inbox_ids: Vec<InboxIdRef<'_>>,
712 ) -> Result<Vec<AssociationState>, ClientError> {
713 let conn = self.context.db();
714 if refresh_from_network {
715 load_identity_updates_for_client(&self.context, &conn, &inbox_ids).await?;
716 }
717 let identity_service = IdentityUpdates::new(&self.context);
718 let state = identity_service
719 .batch_get_association_state(
720 &conn,
721 &inbox_ids.into_iter().map(|i| (i, None)).collect::<Vec<_>>(),
722 )
723 .await?;
724 Ok(state)
725 }
726
727 pub async fn fetch_inbox_updates_count(
731 &self,
732 refresh_from_network: bool,
733 inbox_ids: Vec<InboxIdRef<'_>>,
734 ) -> Result<HashMap<InboxId, u32>, ClientError> {
735 let conn = self.context.db();
736 if refresh_from_network {
737 load_identity_updates_for_client(&self.context, &conn, &inbox_ids).await?;
738 }
739 let inbox_id_strs = inbox_ids.to_vec();
740 let counts = conn.count_inbox_updates(&inbox_id_strs)?;
741 Ok(counts.into_iter().map(|(k, v)| (k, v as u32)).collect())
742 }
743
744 pub async fn fetch_own_inbox_updates_count(
748 &self,
749 refresh_from_network: bool,
750 ) -> Result<u32, ClientError> {
751 let inbox_id = self.inbox_id();
752 Ok(self
753 .fetch_inbox_updates_count(refresh_from_network, vec![inbox_id])
754 .await?
755 .get(inbox_id)
756 .copied()
757 .unwrap_or(0))
758 }
759
760 pub async fn inbox_creation_signature_kind(
770 &self,
771 inbox_id: InboxIdRef<'_>,
772 refresh_from_network: bool,
773 ) -> Result<Option<xmtp_id::associations::SignatureKind>, ClientError> {
774 let conn = self.context.db();
775
776 if refresh_from_network {
778 load_identity_updates_for_client(&self.context, &conn, &[inbox_id]).await?;
779 }
780
781 let verifier = self.context.scw_verifier();
782
783 let signature_kind = get_creation_signature_kind(&conn, verifier, inbox_id).await?;
784
785 Ok(signature_kind)
786 }
787
788 pub async fn set_consent_states(&self, acts: &[ConsentAct]) -> Result<(), ClientError> {
792 let own = self.context.inbox_id();
793 let keys = acts
794 .iter()
795 .map(|act| ConsentKey::for_app(&self.context.db(), own, act.entity_type, &act.entity))
796 .collect::<Result<Vec<_>, _>>()?;
797 crate::state_tx::state_write_with_events(
798 self.context.mls_storage(),
799 self.context.events(),
800 |tx, events| {
801 let storage = tx.storage();
802 let db = storage.db();
803 let mut changes = vec![];
804 for (key, act) in keys.iter().zip(acts) {
805 let decision = ConsentWrite::Decide(Decision::Set(act.state));
806 changes.extend(write_consent(&db, key, decision, Owed::Mark)?);
807 }
808 emit_consent_changes(&self.context, events, &db, &changes)?;
809 Ok::<_, StorageError>(Continue(()))
810 },
811 )?
812 .into_continued();
813
814 Ok(())
815 }
816
817 pub async fn get_consent_state(
821 &self,
822 entity_type: ConsentType,
823 entity: String,
824 ) -> Result<ConsentState, ClientError> {
825 let db = self.context.db();
826 let Some(key) = ConsentKey::parse(entity_type, &entity) else {
827 return Ok(ConsentState::Unknown);
828 };
829 Ok(match key.group_id() {
830 Some(id) => conversation_consent(&db, self.context.inbox_id(), &id)?,
831 None => db
832 .consent_row(&key)?
833 .map_or(ConsentState::Unknown, |r| r.state),
834 })
835 }
836
837 pub fn release_db_connection(&self) -> Result<(), ClientError> {
839 self.context
840 .db()
841 .disconnect()
842 .map_err(xmtp_db::StorageError::from)?;
843 Ok(())
844 }
845
846 pub fn identity(&self) -> &Identity {
848 self.context.identity()
849 }
850
851 pub(crate) fn ensure_identity_ready<E: From<BlockedConnection> + From<IdentityError>>(
854 &self,
855 ) -> Result<(), E> {
856 self.context.server_configuration().check()?;
860 if !self.identity().is_ready() {
861 tracing::warn!(
862 inbox_id = %self.inbox_id(),
863 "Operation attempted before register_identity() was called"
864 );
865 return Err(IdentityError::UninitializedIdentity.into());
866 }
867 Ok(())
868 }
869
870 pub fn create_group(
873 &self,
874 permissions_policy_set: Option<PolicySet>,
875 opts: Option<GroupMetadataOptions>,
876 ) -> Result<MlsGroup<Context>, ClientError> {
877 self.ensure_identity_ready::<ClientError>()?;
878
879 let group: MlsGroup<Context> = MlsGroup::create_and_insert(
880 self.context.clone(),
881 ConversationType::Group,
882 permissions_policy_set.unwrap_or_default(),
883 opts.unwrap_or_default(),
884 None,
885 )?;
886
887 log_event!(
888 Event::CreatedGroup,
889 self.context.installation_id(),
890 group_id = group.group_id
891 );
892
893 Ok(group)
894 }
895
896 pub async fn create_group_with_identifiers(
900 &self,
901 account_identifiers: &[Identifier],
902 permissions_policy_set: Option<PolicySet>,
903 opts: Option<GroupMetadataOptions>,
904 ) -> Result<MlsGroup<Context>, ClientError> {
905 self.create_group_with_targets(
906 &account_targets(account_identifiers),
907 permissions_policy_set,
908 opts,
909 )
910 .await
911 }
912
913 #[tracing::instrument(level = "debug", skip_all, fields(size = inbox_ids.len()))]
916 pub async fn create_group_with_members(
917 &self,
918 inbox_ids: &[impl AsIdRef],
919 permissions_policy_set: Option<PolicySet>,
920 opts: Option<GroupMetadataOptions>,
921 ) -> Result<MlsGroup<Context>, ClientError> {
922 tracing::info!("creating group");
923 self.ensure_identity_ready::<ClientError>()?;
924 let opts = opts.unwrap_or_default();
925 let draft = MlsGroup::<Context>::prepare_new(
926 &self.context,
927 None,
928 ConversationType::Group,
929 permissions_policy_set.unwrap_or_default(),
930 &opts,
931 None,
932 )?;
933 let ids = inbox_ids.iter().map(AsIdRef::as_ref).collect::<Vec<_>>();
934 let add = MlsGroup::<Context>::initial_add(&self.context, &draft, &ids).await?;
936 let (stored, intent) = MlsGroup::<Context>::store_new(
937 &self.context,
938 draft,
939 GroupMembershipState::Allowed,
940 ConversationType::Group,
941 opts,
942 true,
943 add,
944 )?;
945 let group = MlsGroup::new(
946 self.context.clone(),
947 stored.id,
948 stored.dm_id,
949 stored.conversation_type,
950 stored.created_at_ns,
951 );
952 log_event!(
953 Event::CreatedGroup,
954 self.context.installation_id(),
955 group_id = group.group_id
956 );
957 let Some(intent) = intent else {
958 return Ok(group);
959 };
960 match group.sync_until_intent_resolved(intent.id).await {
961 Err(error) if !group.intent_pending(intent.id)? => return Err(error.into()),
962 Err(error) => tracing::warn!(?error, "first add pending; a sync finishes it"),
963 Ok(_) => {}
964 }
965
966 Ok(group)
967 }
968
969 #[tracing::instrument(level = "debug", skip_all)]
971 async fn create_dm_by_inbox_id(
972 &self,
973 target_inbox_id: InboxId,
974 opts: Option<DMMetadataOptions>,
975 ) -> Result<MlsGroup<Context>, ClientError> {
976 let group: MlsGroup<Context> = MlsGroup::create_dm_and_insert(
977 &self.context,
978 GroupMembershipState::Allowed,
979 target_inbox_id.clone(),
980 GroupMetadataOptions {
981 message_disappearing_settings: opts
982 .unwrap_or_default()
983 .message_disappearing_settings,
984 ..Default::default()
985 },
986 None,
987 )?;
988
989 log_event!(
990 Event::CreatedDM,
991 self.context.installation_id(),
992 group_id = group.group_id,
993 target_inbox = target_inbox_id
994 );
995 group.add_members(&[target_inbox_id]).await?;
996
997 Ok(group)
998 }
999
1000 pub async fn find_or_create_dm_by_identity(
1002 &self,
1003 target_identity: Identifier,
1004 opts: Option<DMMetadataOptions>,
1005 ) -> Result<MlsGroup<Context>, ClientError> {
1006 self.ensure_identity_ready::<ClientError>()?;
1007 tracing::info!("finding or creating dm with address: {target_identity}");
1008 let inbox_id = match self
1009 .find_inbox_id_from_identifier(target_identity.clone())
1010 .await?
1011 {
1012 Some(id) => id,
1013 None => {
1014 return Err(NotFound::InboxIdForAddress(target_identity.to_string()).into());
1015 }
1016 };
1017
1018 self.find_or_create_dm(inbox_id, opts).await
1019 }
1020
1021 pub async fn find_or_create_dm(
1026 &self,
1027 inbox_id: impl AsIdRef,
1028 opts: Option<DMMetadataOptions>,
1029 ) -> Result<MlsGroup<Context>, ClientError> {
1030 self.ensure_identity_ready::<ClientError>()?;
1031 let inbox_id = inbox_id.as_ref();
1032 tracing::info!("finding or creating dm with inbox_id: {}", inbox_id);
1033 let db = self.context.db();
1034 let group = db.find_active_dm_group(&DmMembers {
1035 member_one_inbox_id: self.inbox_id(),
1036 member_two_inbox_id: inbox_id,
1037 })?;
1038
1039 if let Some(group) = group {
1040 let group = MlsGroup::new(
1041 self.context.clone(),
1042 group.id,
1043 group.dm_id,
1044 group.conversation_type,
1045 group.created_at_ns,
1046 );
1047 group.add_missing_dm_peer().await?;
1049 return Ok(group);
1050 }
1051 self.create_dm_by_inbox_id(inbox_id.to_string(), opts).await
1052 }
1053
1054 pub fn group(&self, group_id: &GroupId) -> Result<MlsGroup<Context>, ClientError> {
1059 MlsGroup::load_by_id(&self.context, group_id).map_err(Into::into)
1060 }
1061
1062 pub fn stitched_group(&self, group_id: &GroupId) -> Result<MlsGroup<Context>, ClientError> {
1067 let conn = self.context.db();
1068 let stored_group = conn.fetch_stitched(group_id)?;
1069 stored_group
1070 .map(|g| {
1071 MlsGroup::new(
1072 self.context.clone(),
1073 g.id,
1074 g.dm_id,
1075 g.conversation_type,
1076 g.created_at_ns,
1077 )
1078 })
1079 .ok_or(NotFound::GroupById(*group_id))
1080 .map_err(Into::into)
1081 }
1082
1083 pub fn find_duplicate_dms_for_group(
1085 &self,
1086 group_id: &GroupId,
1087 ) -> Result<Vec<MlsGroup<Context>>, ClientError> {
1088 let (group, _) = MlsGroup::new_cached(self.context.clone(), group_id)?;
1089 Ok(group.find_duplicate_dms()?)
1090 }
1091
1092 pub fn group_disappearing_settings(
1097 &self,
1098 group_id: &GroupId,
1099 ) -> Result<Option<MessageDisappearingSettings>, ClientError> {
1100 let (group, _) = MlsGroup::new_cached(self.context.clone(), group_id)?;
1101 Ok(group.disappearing_settings()?)
1102 }
1103
1104 pub fn dm_group_from_target_inbox(
1110 &self,
1111 target_inbox_id: String,
1112 ) -> Result<MlsGroup<Context>, ClientError> {
1113 let conn = self.context.db();
1114
1115 let group = conn
1116 .find_active_dm_group(&DmMembers {
1117 member_one_inbox_id: self.inbox_id(),
1118 member_two_inbox_id: &target_inbox_id,
1119 })?
1120 .ok_or(NotFound::DmByInbox(target_inbox_id))?;
1121 Ok(MlsGroup::new(
1122 self.context.clone(),
1123 group.id,
1124 group.dm_id,
1125 group.conversation_type,
1126 group.created_at_ns,
1127 ))
1128 }
1129
1130 pub fn message(&self, message_id: Vec<u8>) -> Result<StoredGroupMessage, ClientError> {
1133 let conn = &mut self.context.db();
1134 let message =
1136 conn.get_app_visible_group_message(&message_id, xmtp_common::time::now_ns())?;
1137 Ok(message.ok_or(NotFound::MessageById(message_id))?)
1138 }
1139
1140 pub async fn message_with_group(
1143 &self,
1144 id: &[u8],
1145 ) -> Result<Option<(StoredGroupMessage, MlsGroup<Context>)>, ClientError> {
1146 let message = match self.message(id.to_vec()) {
1147 Ok(message) => message,
1148 Err(ClientError::Storage(StorageError::NotFound(NotFound::MessageById(_)))) => {
1149 return Ok(None);
1150 }
1151 Err(error) => return Err(error),
1152 };
1153 let group = self.stitched_group(&message.group_id)?;
1154 Ok(Some((message, group)))
1155 }
1156
1157 #[xmtp_common::mls_span]
1160 pub fn message_v2(&self, message_id: Vec<u8>) -> Result<DecodedMessage, ClientError> {
1161 let conn = self.context.db();
1162 let message = conn
1163 .get_app_visible_group_message(&message_id, xmtp_common::time::now_ns())?
1164 .ok_or_else(|| NotFound::MessageById(message_id.clone()))?;
1165
1166 let group_id = message.group_id;
1167
1168 let enriched = enrich_messages(conn, &group_id, vec![message])?;
1169
1170 enriched
1172 .into_iter()
1173 .next()
1174 .ok_or(ClientError::MessageDecodeFailed)
1176 }
1177
1178 pub fn delete_message(&self, message_id: Vec<u8>) -> Result<usize, ClientError> {
1182 Ok(crate::state_tx::state_write_with_events(
1183 self.context.mls_storage(),
1184 self.context.events(),
1185 |tx, events| {
1186 let storage = tx.storage();
1187 let db = storage.db();
1188 let message = db.get_group_message(&message_id)?;
1189 let deleted = db.delete_message_by_id(&message_id)?;
1190 if deleted > 0
1191 && let Some(message) = message
1192 {
1193 emit_deleted_messages(
1194 events,
1195 vec![message],
1196 DeletionCause::DeletedLocally,
1197 None,
1199 &db,
1200 )?;
1201 }
1202 Ok::<_, StorageError>(Continue(deleted))
1203 },
1204 )?
1205 .into_continued())
1206 }
1207
1208 pub fn find_groups(&self, args: GroupQueryArgs) -> Result<Vec<MlsGroup<Context>>, ClientError> {
1216 MlsGroup::find(&self.context, args).map_err(Into::into)
1217 }
1218
1219 pub fn list_conversations(
1229 &self,
1230 mut args: GroupQueryArgs,
1231 ) -> Result<Vec<ConversationListItem<Context>>, ClientError> {
1232 if args.order_by.is_none() {
1233 args.order_by = Some(GroupQueryOrderBy::LastActivity);
1234 }
1235 let limit = args
1236 .limit
1237 .unwrap_or(i64::from(CONVERSATION_LIST_DEFAULT_LIMIT));
1238 if !(1..=i64::from(LOCAL_PAGE_MAX_LIMIT)).contains(&limit) {
1239 return Err(ClientError::InvalidListOptions(
1240 "limit must be at least 1 and at most the maximum page size",
1241 ));
1242 }
1243 args.limit = Some(limit);
1244 if args.before_id.is_some() && args.order_before_ns().is_none() {
1245 return Err(ClientError::InvalidListOptions(
1246 "before_id needs the before time of the list order",
1247 ));
1248 }
1249 if args.include_sync_groups || args.conversation_type.is_some_and(|kind| kind.is_virtual())
1252 {
1253 return Err(ClientError::InvalidListOptions(
1254 "a conversation listing cannot include sync or one-shot groups",
1255 ));
1256 }
1257 Ok(self
1258 .context
1259 .db()
1260 .fetch_conversation_list(args)?
1261 .into_iter()
1262 .map(
1263 |conversation_item: DbConversationListItem| ConversationListItem {
1264 membership_state: conversation_item.membership_state,
1265 added_by_inbox_id: conversation_item.added_by_inbox_id,
1266 group: MlsGroup::new(
1267 self.context.clone(),
1268 conversation_item.id,
1269 conversation_item.dm_id,
1270 conversation_item.conversation_type,
1271 conversation_item.created_at_ns,
1272 ),
1273 is_commit_log_forked: conversation_item.is_commit_log_forked,
1274 listing_time_ns: conversation_item.listing_time_ns,
1275 },
1276 )
1277 .collect())
1278 }
1279
1280 pub async fn register_with_owner(&self, owner: &impl InboxOwner) -> Result<(), ClientError> {
1282 if self.identity().is_ready() {
1283 return self.ensure_registration_visible().await;
1284 }
1285
1286 let mut signature_request = self
1287 .context
1288 .signature_request()
1289 .ok_or(SignatureRequestError::MissingSigner)?;
1290 let signature = owner
1291 .sign(&signature_request.signature_text())
1292 .map_err(SignatureError::from)?;
1293 signature_request
1294 .add_signature(signature, self.scw_verifier())
1295 .await?;
1296 self.register_identity(signature_request).await
1297 }
1298
1299 #[xmtp_common::mls_span]
1303 pub async fn register_identity(
1304 &self,
1305 signature_request: SignatureRequest,
1306 ) -> Result<(), ClientError> {
1307 tracing::info!("registering identity");
1308 self.context.server_configuration().check()?;
1310
1311 let stored_identity: Option<StoredIdentity> = self.context.db().fetch(&())?;
1313 if stored_identity.is_some() {
1314 tracing::info!("Resuming stored identity registration");
1315 self.identity().set_ready();
1316 return self.ensure_registration_visible().await;
1317 }
1318
1319 let (kp_bytes, history_id) = crate::identity_lifecycle::generate_and_store_key_package(
1321 self.identity(),
1322 self.context.mls_storage(),
1323 CREATE_PQ_KEY_PACKAGE_EXTENSION,
1324 )?;
1325
1326 let identity_update = signature_request
1328 .build_identity_update()
1329 .map_err(IdentityUpdateError::from)?;
1330 identity_update
1331 .to_verified(&self.context.scw_verifier())
1332 .await?;
1333
1334 let key_package_meta = self.context.api().upload_key_package(kp_bytes).await?;
1336 let key_package_cursor = key_package_meta
1337 .cursor
1338 .filter(|cursor| cursor.sequence_id > 0)
1339 .ok_or(xmtp_api::ApiError::InvalidResponse(
1340 "key package publish cursor",
1341 ))?;
1342
1343 let inbox_id = self.inbox_id().to_string();
1347 let registration_cursor = self
1348 .identity_updates()
1349 .with_own_installation_events(
1350 &self.context.db(),
1351 Box::pin(async {
1352 let registration_cursor = crate::identity_updates::publish_with_conflict_retry(
1354 self.context.api(),
1355 &self.context.db(),
1356 identity_update,
1357 &self.context.scw_verifier(),
1358 )
1359 .await
1360 .map_err(|error| IdentityUpdateError::Publish(Box::new(error)))?;
1361 retry_async!(
1363 Retry::default(),
1364 (async {
1365 load_identity_updates(
1366 self.context.api(),
1367 &self.context.db(),
1368 &[inbox_id.as_str()],
1369 )
1370 .await
1371 })
1372 )?;
1373 Ok(registration_cursor)
1374 }),
1375 )
1376 .await?;
1377
1378 crate::state_tx::state_write(self.context.mls_storage(), |tx| {
1380 let storage = tx.storage();
1381 storage.db().record_key_package_publication(
1382 history_id,
1383 xmtp_proto::types::Cursor(key_package_cursor.sequence_id),
1384 )?;
1385 storage
1386 .db()
1387 .reset_key_package_rotation_queue(KEY_PACKAGE_ROTATION_INTERVAL_NS)?;
1388 Ok::<_, StorageError>(Continue(()))
1389 })
1390 .map(TransactionOutcome::into_continued)?;
1391
1392 let mut stored_identity = StoredIdentity::try_from(self.identity())?;
1394 stored_identity.registration_cursor_sequence_id = Some(registration_cursor.0 as i64);
1395 stored_identity.store(&self.context.db())?;
1396 self.context
1397 .registration_event_pending()
1398 .store(true, Ordering::Release);
1399 self.identity().set_ready();
1400 self.ensure_registration_visible().await
1401 }
1402
1403 pub fn is_registration_visible(&self) -> Result<bool, ClientError> {
1406 let stored: Option<StoredIdentity> = self.context.db().fetch(&())?;
1407 Ok(self.identity().is_ready()
1408 && stored.is_some_and(|identity| identity.registration_cursor_sequence_id.is_none()))
1409 }
1410
1411 pub async fn ensure_registration_visible(&self) -> Result<(), ClientError> {
1413 let stored: Option<StoredIdentity> = self.context.db().fetch(&())?;
1414 if stored.is_some_and(|identity| identity.registration_cursor_sequence_id.is_some()) {
1415 self.wait_for_registration_visible(VisibilityConfirmationOptions::default())
1416 .await?;
1417 }
1418 Ok(())
1419 }
1420
1421 pub async fn wait_for_registration_visible(
1424 &self,
1425 options: VisibilityConfirmationOptions,
1426 ) -> Result<(), ClientError> {
1427 use xmtp_common::time::{Duration, sleep, timeout};
1428 if !self.identity().is_ready() {
1429 return Err(ClientError::RegistrationNotVisible);
1430 }
1431 let stored: Option<StoredIdentity> = self.context.db().fetch(&())?;
1432 let stored = stored.ok_or(ClientError::RegistrationNotVisible)?;
1433 let Some(receipt) = stored.registration_cursor_sequence_id else {
1434 return Ok(());
1435 };
1436 self.context.server_configuration().check()?;
1437 let sequence_id = u64::try_from(receipt)
1438 .ok()
1439 .filter(|sequence_id| *sequence_id != 0)
1440 .ok_or(ClientError::RegistrationNotVisible)?;
1441 timeout(Duration::from_millis(options.timeout_ms), async {
1442 let mut delay = REGISTRATION_INITIAL_BACKOFF;
1443 let inbox = hex::decode(self.inbox_id())
1444 .map_err(|_| xmtp_api::ApiError::InvalidRequest("registration inbox id"))?;
1445 let topic = xmtp_proto::types::Topic::new_identity_update(inbox);
1446 xmtp_proto::types::Topic::parse(&topic)?;
1447 loop {
1448 let heads = self
1449 .context
1450 .api()
1451 .newest_topic_cursors(vec![topic.clone()])
1452 .await?;
1453 let head = heads
1454 .get(&topic)
1455 .ok_or(xmtp_api::ApiError::InvalidResponse(
1456 "registration identity head",
1457 ))?;
1458 if head.0 >= sequence_id {
1460 return Ok::<(), ClientError>(());
1461 }
1462 sleep(delay).await;
1463 delay = (delay * 2).min(REGISTRATION_MAX_BACKOFF);
1464 }
1465 })
1466 .await
1467 .map_err(|_| ClientError::RegistrationNotVisible)??;
1468 if self
1469 .context
1470 .registration_event_pending()
1471 .swap(false, Ordering::AcqRel)
1472 {
1473 xmtp_events::EventWriter::emit(
1474 self.context.events(),
1475 Some(xmtp_events::ClientEvent::IdentityRegistered(
1476 xmtp_events::IdentityRegistered {
1477 inbox_id: self.inbox_id().to_string(),
1478 installation_key: self.installation_id.to_vec(),
1479 },
1480 )),
1481 None,
1482 );
1483 }
1484 self.context.db().clear_registration_cursor(receipt)?;
1485 Ok(())
1486 }
1487
1488 pub fn queue_key_rotation(&self) -> Result<(), ClientError> {
1490 crate::worker::key_package_maintenance::queue_key_rotation(&self.context)?;
1491 Ok(())
1492 }
1493
1494 pub async fn rotate_and_upload_key_package(&self) -> Result<(), ClientError> {
1496 self.ensure_identity_ready::<ClientError>()?;
1500 crate::identity_lifecycle::rotate_and_upload_key_package(
1501 self.identity(),
1502 self.context.api(),
1503 self.context.mls_storage(),
1504 CREATE_PQ_KEY_PACKAGE_EXTENSION,
1505 )
1506 .await?;
1507 crate::worker::key_package_maintenance::nudge_deletion(&self.context)?;
1510
1511 Ok(())
1512 }
1513
1514 #[tracing::instrument(skip_all)]
1516 pub async fn get_key_packages_for_installation_ids(
1517 &self,
1518 installation_ids: Vec<Vec<u8>>,
1519 ) -> Result<
1520 HashMap<Vec<u8>, Result<VerifiedKeyPackageV2, KeyPackageVerificationError>>,
1521 ClientError,
1522 > {
1523 MlsStore::new(self.context.clone())
1524 .get_key_packages_for_installation_ids(installation_ids)
1525 .await
1526 .map_err(Into::into)
1527 }
1528
1529 #[tracing::instrument(skip_all)]
1532 pub async fn sync_welcomes(&self) -> Result<Vec<MlsGroup<Context>>, GroupError> {
1533 self.ensure_identity_ready::<GroupError>()?;
1534 WelcomeService::new(self.context.clone())
1535 .sync_welcomes()
1536 .await
1537 .map_err(|error| self.context.server_configuration().blocked_or(error))
1538 }
1539
1540 #[tracing::instrument(err, skip_all, fields(operation = "sync_all_groups"))]
1543 pub async fn sync_all_groups(
1544 &self,
1545 groups: Vec<MlsGroup<Context>>,
1546 ) -> Result<GroupSyncSummary, GroupError> {
1547 self.ensure_identity_ready::<GroupError>()?;
1548 WelcomeService::new(self.context.clone())
1549 .sync_all_groups(groups)
1550 .await
1551 .map_err(|error| self.context.server_configuration().blocked_or(error))
1552 }
1553
1554 #[xmtp_common::mls_span]
1557 pub async fn sync_all_welcomes_and_groups(
1558 &self,
1559 consent_states: Option<Vec<ConsentState>>,
1560 ) -> Result<GroupSyncSummary, GroupError> {
1561 self.ensure_identity_ready::<GroupError>()?;
1562 WelcomeService::new(self.context.clone())
1563 .sync_all_welcomes_and_groups(consent_states)
1564 .await
1565 .map_err(|error| self.context.server_configuration().blocked_or(error))
1566 }
1567
1568 pub async fn unstick_paused_groups(&self) -> Result<usize, GroupError> {
1578 self.ensure_identity_ready::<GroupError>()?;
1579 WelcomeService::new(self.context.clone())
1580 .unstick_paused_groups()
1581 .await
1582 }
1583
1584 pub async fn sync_all_welcomes_and_device_sync_groups(
1585 &self,
1586 ) -> Result<GroupSyncSummary, ClientError> {
1587 self.sync_welcomes().await?;
1588 self.sync_all_device_sync_groups().await
1589 }
1590
1591 pub async fn sync_all_device_sync_groups(&self) -> Result<GroupSyncSummary, ClientError> {
1592 let groups = self
1593 .context
1594 .db()
1595 .all_sync_groups()?
1596 .into_iter()
1597 .map(|g| {
1598 MlsGroup::new(
1599 self.context.clone(),
1600 g.id,
1601 g.dm_id,
1602 g.conversation_type,
1603 g.created_at_ns,
1604 )
1605 })
1606 .collect();
1607
1608 Ok(self.sync_all_groups(groups).await?)
1609 }
1610
1611 pub async fn validate_credential_against_network(
1618 &self,
1619 conn: &DbConnection<<Context::Db as XmtpDb>::Connection>,
1620 credential: &[u8],
1621 installation_pub_key: Vec<u8>,
1622 ) -> Result<InboxId, ClientError> {
1623 let inbox_id = parse_credential(credential)?;
1624 let association_state = IdentityUpdates::new(&self.context)
1625 .get_latest_association_state(conn, &inbox_id)
1626 .await?;
1627 let ident = MemberIdentifier::installation(installation_pub_key);
1628
1629 match association_state.get(&ident) {
1630 Some(_) => Ok(inbox_id),
1631 None => Err(IdentityError::InstallationIdNotFound(inbox_id).into()),
1632 }
1633 }
1634
1635 pub async fn can_message(
1646 &self,
1647 account_identifiers: &[Identifier],
1648 ) -> Result<HashMap<Identifier, bool>, ClientError> {
1649 self.context.server_configuration().check()?;
1653 let requests = account_identifiers.iter().map(Into::into).collect();
1654
1655 let results = self.context.api().get_inbox_ids(requests).await?;
1656 Ok(account_identifiers
1657 .iter()
1658 .cloned()
1659 .zip(results.into_iter().map(|inbox_id| inbox_id.is_some()))
1660 .collect())
1661 }
1662}
1663
1664impl<Context: XmtpSharedContext> Client<Context> {
1665 pub fn attachments(&self) -> Attachments<Context> {
1666 Attachments {
1667 context: self.context.clone(),
1668 }
1669 }
1670}
1671
1672impl<Context: XmtpSharedContext> Client<Context> {
1673 pub async fn enable_notifications(
1675 &self,
1676 config: NotificationConfig,
1677 ) -> Result<NotificationState, NotificationError> {
1678 notifications::enable_notifications(&self.context, config).await
1679 }
1680
1681 pub async fn disable_notifications(&self) -> Result<(), NotificationError> {
1684 notifications::disable_notifications(&self.context).await
1685 }
1686
1687 pub fn notification_state(&self) -> Result<NotificationState, StorageError> {
1689 notifications::notification_state(&self.context)
1690 }
1691}
1692
1693impl<Context: XmtpSharedContext> Client<Context> {
1694 pub async fn resolve_handles(
1699 &self,
1700 handles: &[Handle],
1701 ) -> Result<Vec<Option<InboxId>>, ClientError> {
1702 Ok(inbox_handles::resolve_verified_in(
1703 &self.context,
1704 &inbox_handles::handle_targets(handles),
1705 )
1706 .await?)
1707 }
1708
1709 pub async fn handles_have_owner(&self, handles: &[Handle]) -> Result<Vec<bool>, ClientError> {
1713 Ok(inbox_handles::handles_have_owner(&self.context, handles).await?)
1714 }
1715
1716 pub async fn available_handles(&self, input: &str) -> Result<Vec<Handle>, ClientError> {
1724 Ok(inbox_handles::available_handles(&self.context, input).await?)
1725 }
1726
1727 pub async fn claim_handle(&self, handle: &Handle) -> Result<(), ClientError> {
1733 self.ensure_identity_ready::<ClientError>()?;
1734 Ok(inbox_handles::claim_handle(&self.context, handle).await?)
1735 }
1736
1737 pub async fn resolve_targets(
1741 &self,
1742 targets: &[IdentityTarget],
1743 ) -> Result<Vec<InboxId>, ClientError> {
1744 Ok(inbox_handles::resolve_targets(&self.context, targets).await?)
1745 }
1746
1747 pub async fn find_dm_by_target(
1750 &self,
1751 target: &IdentityTarget,
1752 ) -> Result<Option<MlsGroup<Context>>, ClientError> {
1753 let Some(peer) =
1754 inbox_handles::resolve_verified_in(&self.context, std::slice::from_ref(target))
1755 .await?
1756 .pop()
1757 .flatten()
1758 else {
1759 return Ok(None);
1760 };
1761 let stored = self.context.db().find_active_dm_group(&DmMembers {
1762 member_one_inbox_id: self.inbox_id(),
1763 member_two_inbox_id: peer.as_str(),
1764 })?;
1765 stored.map(|stored| self.group(&stored.id)).transpose()
1766 }
1767
1768 pub async fn find_or_create_dm_by_target(
1771 &self,
1772 target: &IdentityTarget,
1773 opts: DMMetadataOptions,
1774 ) -> Result<MlsGroup<Context>, ClientError> {
1775 match target {
1776 IdentityTarget::Account(identifier) => {
1777 self.find_or_create_dm_by_identity(identifier.clone(), Some(opts))
1778 .await
1779 }
1780 IdentityTarget::Handle(handle) => {
1781 let peer = self
1782 .resolve_handles(std::slice::from_ref(handle))
1783 .await?
1784 .pop()
1785 .flatten()
1786 .ok_or(HandleError::NotFound)?;
1787 self.find_or_create_dm(peer, Some(opts)).await
1788 }
1789 }
1790 }
1791
1792 pub async fn create_group_with_targets(
1797 &self,
1798 targets: &[IdentityTarget],
1799 permissions_policy_set: Option<PolicySet>,
1800 opts: Option<GroupMetadataOptions>,
1801 ) -> Result<MlsGroup<Context>, ClientError> {
1802 let inboxes = inbox_handles::resolve_targets_in(&self.context, targets).await?;
1803 let inbox_ids = crate::groups::targets::require_account_inboxes(targets, inboxes)?;
1804 self.create_group_with_members(&inbox_ids, permissions_policy_set, opts)
1805 .await
1806 }
1807}
1808
1809impl<Context> Client<Context>
1810where
1811 Context: XmtpSharedContext + 'static,
1812{
1813 pub async fn process_streamed_welcome_message(
1815 &self,
1816 envelope_bytes: Vec<u8>,
1817 ) -> Result<Vec<MlsGroup<Context>>, SubscribeError> {
1818 subscriptions::process_streamed_welcome_message(&self.context, envelope_bytes).await
1819 }
1820
1821 pub async fn stream_conversations(
1822 &self,
1823 conversation_type: Option<ConversationType>,
1824 include_duplicate_dms: bool,
1825 ) -> Result<
1826 impl Stream<Item = Result<MlsGroup<Context>, SubscribeError>> + use<'_, Context>,
1827 SubscribeError,
1828 >
1829 where
1830 Context::ApiClient: XmtpMlsStreams,
1831 {
1832 subscriptions::stream_conversations(&self.context, conversation_type, include_duplicate_dms)
1833 .await
1834 }
1835
1836 pub async fn stream_conversations_owned(
1838 &self,
1839 conversation_type: Option<ConversationType>,
1840 include_duplicate_dms: bool,
1841 ) -> Result<
1842 impl Stream<Item = Result<MlsGroup<Context>, SubscribeError>> + 'static + use<Context>,
1843 SubscribeError,
1844 >
1845 where
1846 Context::ApiClient: XmtpMlsStreams,
1847 {
1848 subscriptions::stream_conversations_owned(
1849 self.context.clone(),
1850 conversation_type,
1851 include_duplicate_dms,
1852 )
1853 .await
1854 }
1855}
1856
1857impl<Context> Client<Context>
1858where
1859 Context: XmtpSharedContext + 'static,
1860 Context::ApiClient: XmtpMlsStreams + 'static,
1861 Context::MlsStorage: 'static,
1862{
1863 pub fn stream_conversations_with_callback(
1864 client: Arc<Client<Context>>,
1865 conversation_type: Option<ConversationType>,
1866 convo_callback: impl FnMut(Result<MlsGroup<Context>, SubscribeError>) + MaybeSend + 'static,
1867 on_close: impl FnOnce() + MaybeSend + 'static,
1868 include_duplicate_dms: bool,
1869 ) -> impl StreamHandle<StreamOutput = Result<(), SubscribeError>> {
1870 subscriptions::stream_conversations_with_callback(
1871 client.context.clone(),
1872 client,
1873 conversation_type,
1874 convo_callback,
1875 on_close,
1876 include_duplicate_dms,
1877 )
1878 }
1879
1880 pub async fn stream_all_messages(
1881 &self,
1882 conversation_type: Option<ConversationType>,
1883 consent_state: Option<Vec<ConsentState>>,
1884 ) -> Result<impl Stream<Item = Result<StoredGroupMessage, SubscribeError>> + '_, SubscribeError>
1885 {
1886 subscriptions::stream_all_messages(&self.context, conversation_type, consent_state).await
1887 }
1888
1889 pub async fn stream_all_messages_owned(
1890 &self,
1891 conversation_type: Option<ConversationType>,
1892 consent_state: Option<Vec<ConsentState>>,
1893 ) -> Result<
1894 impl Stream<Item = Result<StoredGroupMessage, SubscribeError>> + 'static + use<Context>,
1895 SubscribeError,
1896 > {
1897 subscriptions::stream_all_messages_owned(
1898 self.context.clone(),
1899 conversation_type,
1900 consent_state,
1901 )
1902 .await
1903 }
1904
1905 pub fn stream_consent_with_callback(
1906 client: Arc<Client<Context>>,
1907 callback: impl FnMut(Result<Vec<StoredConsentRecord>, SubscribeError>) + MaybeSend + 'static,
1908 on_close: impl FnOnce() + MaybeSend + 'static,
1909 ) -> impl StreamHandle<StreamOutput = Result<(), SubscribeError>> {
1910 subscriptions::stream_consent_with_callback(
1911 client.context.clone(),
1912 client,
1913 callback,
1914 on_close,
1915 )
1916 }
1917
1918 pub fn stream_preferences_with_callback(
1919 client: Arc<Client<Context>>,
1920 callback: impl FnMut(Result<Vec<PreferenceUpdate>, SubscribeError>) + MaybeSend + 'static,
1921 on_close: impl FnOnce() + MaybeSend + 'static,
1922 ) -> impl StreamHandle<StreamOutput = Result<(), SubscribeError>> {
1923 subscriptions::stream_preferences_with_callback(
1924 client.context.clone(),
1925 client,
1926 callback,
1927 on_close,
1928 )
1929 }
1930
1931 pub fn stream_message_deletions_with_callback(
1932 client: Arc<Client<Context>>,
1933 callback: impl FnMut(Result<DecodedMessage, SubscribeError>) + MaybeSend + 'static,
1934 on_close: impl FnOnce() + MaybeSend + 'static,
1935 ) -> impl StreamHandle<StreamOutput = Result<(), SubscribeError>> {
1936 subscriptions::stream_message_deletions_with_callback(
1937 client.context.clone(),
1938 client,
1939 callback,
1940 on_close,
1941 )
1942 }
1943}
1944
1945impl<Context> Client<Context>
1946where
1947 Context: XmtpSharedContext + 'static,
1948 Context::ApiClient: XmtpMlsStreams + 'static,
1949 Context::MlsStorage: 'static,
1950 <Context::ApiClient as XmtpMlsStreams>::GroupMessageStream: Unpin,
1951 <Context::ApiClient as XmtpMlsStreams>::WelcomeMessageStream: Unpin,
1952{
1953 #[cfg(any(test, feature = "test-utils"))]
1954 pub async fn stream_all_messages_owned_with_stats(
1955 &self,
1956 conversation_type: Option<ConversationType>,
1957 consent_state: Option<Vec<ConsentState>>,
1958 ) -> Result<
1959 impl StreamWithStats<Item = Result<StoredGroupMessage, SubscribeError>> + 'static,
1960 SubscribeError,
1961 > {
1962 subscriptions::stream_all_messages_owned_with_stats(
1963 self.context.clone(),
1964 conversation_type,
1965 consent_state,
1966 )
1967 .await
1968 }
1969}
1970
1971impl<C: XmtpSharedContext + 'static> Client<C> {
1972 pub async fn catch_up_to_live(
1974 &self,
1975 timeout: Option<xmtp_common::time::Duration>,
1976 ) -> Result<CatchUpSummary, CatchUpError> {
1977 subscriptions::catch_up::catch_up_to_live(&self.context, timeout).await
1978 }
1979}
1980
1981#[cfg(not(target_arch = "wasm32"))]
1982impl<C> Client<C>
1983where
1984 C: XmtpSharedContext + 'static,
1985 C::ApiClient:
1986 XmtpMlsBidiStreams + XmtpMlsStreams + ApiClientIdentity + Clone + Send + Sync + 'static,
1987 <C::ApiClient as XmtpMlsBidiStreams>::SubscribeStream: 'static,
1988{
1989 pub fn stream_all_messages_with_callback_dispatch(
1990 client: Arc<Client<C>>,
1991 conversation_type: Option<ConversationType>,
1992 consent_states: Option<Vec<ConsentState>>,
1993 callback: impl FnMut(Result<StoredGroupMessage, SubscribeError>) + MaybeSend + 'static,
1994 on_close: impl FnOnce() + MaybeSend + 'static,
1995 ) -> impl StreamHandle<StreamOutput = Result<(), SubscribeError>> {
1996 router_callbacks::stream_all_messages_with_callback_dispatch(
1997 client.context.clone(),
1998 client,
1999 conversation_type,
2000 consent_states,
2001 callback,
2002 on_close,
2003 )
2004 }
2005
2006 pub fn stream_conversations_with_callback_dispatch(
2007 client: Arc<Client<C>>,
2008 conversation_type: Option<ConversationType>,
2009 include_duplicate_dms: bool,
2010 callback: impl FnMut(Result<MlsGroup<C>, SubscribeError>) + MaybeSend + 'static,
2011 on_close: impl FnOnce() + MaybeSend + 'static,
2012 ) -> impl StreamHandle<StreamOutput = Result<(), SubscribeError>> {
2013 router_callbacks::stream_conversations_with_callback_dispatch(
2014 client.context.clone(),
2015 client,
2016 conversation_type,
2017 include_duplicate_dms,
2018 callback,
2019 on_close,
2020 )
2021 }
2022}
2023
2024#[cfg(test)]
2025pub(crate) mod tests;
2026
2027#[cfg(all(test, not(target_arch = "wasm32")))]
2028pub(crate) mod test_hooks {
2029 use std::sync::Arc;
2030 use tokio::sync::Barrier;
2031
2032 type PauseHook = (Vec<u8>, Arc<Barrier>, Arc<Barrier>);
2033 pub(crate) static PAUSE_RECONNECT: parking_lot::Mutex<Option<PauseHook>> =
2036 parking_lot::Mutex::new(None);
2037
2038 pub(super) fn pause_reconnect(installation_id: &[u8]) {
2039 let paused = {
2040 let mut hook = PAUSE_RECONNECT.lock();
2041 if hook
2042 .as_ref()
2043 .is_some_and(|(installation, _, _)| installation.as_slice() == installation_id)
2044 {
2045 hook.take()
2046 } else {
2047 None
2048 }
2049 };
2050 if let Some((_, entered, release)) = paused {
2051 tokio::runtime::Handle::current().block_on(async {
2052 entered.wait().await;
2053 release.wait().await;
2054 });
2055 }
2056 }
2057}