1use crate::{
2 builder::DeviceSyncMode,
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 mls_store::{MlsStore, MlsStoreError},
14 subscriptions::{
15 LocalEventError,
16 internal::{PreferenceOrigin, emit_deleted_messages, emit_preference_updates},
17 },
18 utils::VersionInfo,
19 worker::device_sync::{
20 DeviceSyncClient, preference_sync::PreferenceUpdate, worker::SyncMetric,
21 },
22 worker::{WorkerRunner, metrics::WorkerMetrics},
23};
24pub mod notifications;
25use crate::{
26 groups::welcome_sync::GroupSyncSummary,
27 identity_updates::{batch_get_association_state_with_verifier, get_creation_signature_kind},
28 messages::{
29 decoded_message::DecodedMessage,
30 enrichment::{EnrichMessageError, enrich_messages},
31 },
32};
33use itertools::Itertools;
34use openmls::prelude::tls_codec::Error as TlsCodecError;
35use std::{
36 collections::{HashMap, HashSet},
37 sync::{Arc, atomic::Ordering},
38};
39use thiserror::Error;
40use xmtp_api::{ApiClientWrapper, XmtpApi};
41use xmtp_common::{ErrorCode, Event, Retry, retry_async, retryable};
42use xmtp_configuration::{CREATE_PQ_KEY_PACKAGE_EXTENSION, KEY_PACKAGE_ROTATION_INTERVAL_NS};
43use xmtp_cryptography::signature::IdentifierValidationError;
44use xmtp_db::TransactionOutcome::Continue;
45use xmtp_db::{
46 ConnectionExt, NotFound, StorageError, TransactionOutcome, XmtpDb,
47 consent_record::{ConsentState, ConsentType, StoredConsentRecord},
48 db_connection::DbConnection,
49 encrypted_store::conversation_list::ConversationListItem as DbConversationListItem,
50 group::{ConversationType, GroupMembershipState, GroupQueryArgs},
51 group_message::StoredGroupMessage,
52 identity::StoredIdentity,
53 identity_cache::StoredIdentityKind,
54};
55use xmtp_db::{group::GroupQueryOrderBy, prelude::*};
56use xmtp_events::DeletionCause;
57use xmtp_id::key_package::{KeyPackageVerificationError, VerifiedKeyPackageV2};
58use xmtp_id::{
59 AsIdRef, InboxId, InboxIdRef, InboxOwner,
60 associations::{
61 AssociationError, AssociationState, Identifier, MemberIdentifier, SignatureError,
62 builder::{SignatureRequest, SignatureRequestError},
63 },
64 scw_verifier::SmartContractSignatureVerifier,
65};
66use xmtp_macro::log_event;
67use xmtp_mls_common::{
68 group::{DMMetadataOptions, GroupMetadataOptions},
69 group_metadata::DmMembers,
70 group_mutable_metadata::MessageDisappearingSettings,
71};
72use xmtp_proto::{
73 ConversionError,
74 api::HasStats,
75 api_client::{ApiStats, IdentityStats, XmtpBackendClient},
76};
77use xmtp_proto::{types::InstallationId, xmtp::identity::associations::IdentifierKind};
78
79use xmtp_proto::types::GroupId;
80#[derive(Clone, Copy, Default, Debug)]
82pub enum Network {
83 Local(&'static str),
84 #[default]
85 Dev,
86 Prod,
87}
88
89#[derive(Debug, Clone)]
91pub struct VisibilityConfirmationOptions {
92 pub timeout_ms: u64,
93}
94
95const REGISTRATION_INITIAL_BACKOFF: std::time::Duration = std::time::Duration::from_millis(50);
96const REGISTRATION_MAX_BACKOFF: std::time::Duration = std::time::Duration::from_secs(1);
97
98impl Default for VisibilityConfirmationOptions {
99 fn default() -> Self {
100 Self { timeout_ms: 30_000 }
101 }
102}
103
104#[derive(Debug, Error, ErrorCode)]
105pub enum ClientError {
106 #[error(transparent)]
107 #[error_code(inherit)]
108 AddressValidation(#[from] IdentifierValidationError),
109 #[error("could not publish: {0}")]
113 PublishError(String),
114 #[error("storage error: {0}")]
118 Storage(#[from] StorageError),
119 #[error("API error: {0}")]
123 Api(#[from] xmtp_api::ApiError),
124 #[error("identity error: {0}")]
128 Identity(#[from] crate::identity::IdentityError),
129 #[error("TLS Codec error: {0}")]
133 TlsError(#[from] TlsCodecError),
134 #[error("key package verification: {0}")]
138 KeyPackageVerification(#[from] KeyPackageVerificationError),
139 #[error("Stream inconsistency error: {0}")]
143 StreamInconsistency(String),
144 #[error("Association error: {0}")]
148 Association(#[from] AssociationError),
149 #[error("signature validation error: {0}")]
153 SignatureValidation(#[from] SignatureError),
154 #[error(transparent)]
158 IdentityUpdate(#[from] IdentityUpdateError),
159 #[error(transparent)]
163 SignatureRequest(#[from] SignatureRequestError),
164 #[error(transparent)]
169 Group(Box<GroupError>),
170 #[error(transparent)]
174 LocalEvent(#[from] LocalEventError),
175 #[error(transparent)]
179 Db(#[from] xmtp_db::ConnectionError),
180 #[error("generic:{0}")]
184 Generic(String),
185 #[error(transparent)]
189 MlsStore(#[from] MlsStoreError),
190 #[error(transparent)]
194 EnrichMessage(#[from] EnrichMessageError),
195 #[error(transparent)]
199 Conversion(#[from] xmtp_proto::ConversionError),
200 #[error(transparent)]
201 #[error_code(inherit)]
202 Handle(#[from] crate::inbox_handles::HandleError),
203 #[error("Registration is not visible")]
208 RegistrationNotVisible,
209 #[error("client is closed")]
214 AlreadyClosed,
215 #[error("server configuration unavailable: {0}")]
222 ConfigurationUnavailable(#[source] Box<crate::server_configuration::ConfigurationFetchError>),
223 #[error("server configuration invalid: {0}")]
229 ConfigurationInvalid(#[from] xmtp_configuration::ServerConfigurationError),
230 #[error("this database is bound to backend {stored}, but {received} answered")]
236 BackendMismatch { stored: String, received: String },
237 #[error("client version {client} is below the {minimum} this backend requires")]
242 ClientVersionTooOld { client: String, minimum: String },
243 #[error(
248 "this backend requires authentication; required scopes: [{}]",
249 required_scopes.join(", ")
250 )]
251 AuthRequired { required_scopes: Vec<String> },
252 #[error(
257 "this backend does not accept smart contract wallet signatures on {chain}; accepted: [{}]",
258 accepted.join(", ")
259 )]
260 ChainNotAccepted {
261 chain: String,
262 accepted: Vec<String>,
263 },
264}
265
266impl ClientError {
267 pub fn db_needs_connection(&self) -> bool {
268 match self {
269 Self::Storage(s) => s.db_needs_connection(),
270 Self::Db(c) => c.db_needs_connection(),
271 _ => false,
272 }
273 }
274}
275
276impl From<NotFound> for ClientError {
277 fn from(value: NotFound) -> Self {
278 ClientError::Storage(StorageError::NotFound(value))
279 }
280}
281
282impl From<GroupError> for ClientError {
283 fn from(err: GroupError) -> ClientError {
284 ClientError::Group(Box::new(err))
285 }
286}
287
288impl xmtp_common::RetryableError for ClientError {
289 fn is_retryable(&self) -> bool {
290 match self {
291 ClientError::Group(group_error) => retryable!(group_error),
292 ClientError::Api(api_error) => retryable!(api_error),
293 ClientError::Storage(storage_error) => retryable!(storage_error),
294 ClientError::Db(db) => retryable!(db),
295 ClientError::SignatureValidation(e) => retryable!(e),
299 ClientError::ConfigurationUnavailable(e) => retryable!(e),
303 ClientError::Generic(err) => err.contains("database is locked"),
304 _ => false,
305 }
306 }
307}
308
309impl From<String> for ClientError {
310 fn from(value: String) -> Self {
311 Self::Generic(value)
312 }
313}
314
315impl From<&str> for ClientError {
316 fn from(value: &str) -> Self {
317 Self::Generic(value.to_string())
318 }
319}
320
321pub struct Client<Context> {
323 pub context: Context,
324 pub installation_id: InstallationId,
325 pub(crate) workers: Arc<WorkerRunner>,
326 pub(crate) app_lifetime: Arc<AppLifetime>,
327}
328
329pub(crate) struct AppLifetime {
330 pub(crate) events: xmtp_events::EventBus<crate::subscriptions::internal::InternalEvent>,
331}
332
333impl Drop for AppLifetime {
334 fn drop(&mut self) {
335 self.events.close_app_subscriptions();
336 }
337}
338
339impl<Context> Drop for Client<Context> {
340 fn drop(&mut self) {
341 log_event!(Event::ClientDropped, self.installation_id);
342 }
343}
344
345#[derive(Clone)]
346pub struct DeviceSync {
347 pub(crate) mode: DeviceSyncMode,
348}
349
350impl<Context: Clone> Clone for Client<Context> {
352 fn clone(&self) -> Self {
353 Self {
354 context: self.context.clone(),
355 installation_id: self.installation_id,
356 workers: self.workers.clone(),
357 app_lifetime: self.app_lifetime.clone(),
358 }
359 }
360}
361
362impl<Context> Client<Context>
363where
364 Context: XmtpSharedContext,
365{
366 pub fn identity_updates(&self) -> IdentityUpdates<&Context> {
367 IdentityUpdates::new(&self.context)
368 }
369
370 pub fn mls_store(&self) -> MlsStore<Context> {
371 MlsStore::new(self.context.clone())
372 }
373
374 pub fn scw_verifier(&self) -> Arc<Box<dyn SmartContractSignatureVerifier>> {
375 self.context.scw_verifier()
376 }
377
378 pub fn version_info(&self) -> &VersionInfo {
379 self.context.version_info()
380 }
381}
382
383impl<Context> Client<Context>
384where
385 Context: XmtpSharedContext,
386 Context::ApiClient: HasStats,
387{
388 pub fn api_stats(&self) -> ApiStats {
389 self.context.api().api_client.mls_stats()
390 }
391
392 pub fn identity_api_stats(&self) -> IdentityStats {
393 self.context.api().api_client.identity_stats()
394 }
395
396 pub fn clear_stats(&self) {
397 self.context.api().api_client.mls_stats().clear();
398 self.context.api().api_client.identity_stats().clear();
399 }
400}
401
402pub async fn inbox_addresses_with_verifier<ApiClient: XmtpApi>(
404 api_client: &ApiClientWrapper<ApiClient>,
405 conn: &impl DbQuery,
406 inbox_ids: Vec<InboxIdRef<'_>>,
407 scw_verifier: &impl SmartContractSignatureVerifier,
408) -> Result<Vec<AssociationState>, ClientError> {
409 load_identity_updates(api_client, conn, &inbox_ids).await?;
410 let state = batch_get_association_state_with_verifier(
411 conn,
412 &inbox_ids.into_iter().map(|i| (i, None)).collect::<Vec<_>>(),
413 scw_verifier,
414 )
415 .await?;
416 Ok(state)
417}
418
419impl<Context> Client<Context>
420where
421 Context: XmtpSharedContext + 'static,
422{
423 pub fn reconnect_db(&self) -> Result<(), ClientError> {
425 let _call = self
429 .context
430 .foreground_calls()
431 .enter()
432 .ok_or(ClientError::AlreadyClosed)?;
433 if self.context.is_closed() {
434 return Err(ClientError::AlreadyClosed);
435 }
436 #[cfg(all(test, not(target_arch = "wasm32")))]
437 test_hooks::pause_reconnect(&self.context.installation_id().to_vec());
438 self.context.db().reconnect().map_err(StorageError::from)?;
439 self.workers.spawn(self.context.clone());
440 Ok(())
441 }
442
443 pub async fn close(&self) -> Result<(), ClientError> {
450 if self.context.shutdown_complete() {
455 return Ok(());
456 }
457 self.context
458 .api()
459 .api_client
460 .unregister_client_event_writer(self.context.public_event_writer());
461 self.context.events().close_app_subscriptions();
462 self.context.cancellation_token().cancel();
463 self.context.foreground_calls().close_and_wait().await;
467 let delivery_result = self.context.close_message_delivery();
468 self.workers.shutdown().await;
469 self.context.events().close_internal_subscriptions();
470 delivery_result?;
473 self.context
474 .db()
475 .disconnect()
476 .map_err(xmtp_db::StorageError::from)?;
477 self.context.mark_shutdown_complete();
478 log_event!(Event::ClientClosed, self.installation_id);
479 Ok(())
480 }
481
482 pub async fn wait_for_sync_worker_init(&self) {
484 self.workers.wait_for_sync_worker_init().await;
485 }
486
487 pub fn sync_metrics(&self) -> Option<Arc<WorkerMetrics<SyncMetric>>> {
488 self.workers.sync_metrics()
489 }
490}
491
492impl<Context> Client<Context>
493where
494 Context: XmtpSharedContext,
495{
496 pub fn server_configuration(&self) -> &xmtp_configuration::ServerConfiguration {
500 self.context.server_configuration().configuration()
501 }
502
503 pub async fn refresh_server_configuration(
509 &self,
510 ) -> Result<xmtp_configuration::ServerConfiguration, ClientError> {
511 crate::server_configuration::refresh(&self.context).await
512 }
513
514 pub fn installation_public_key(&self) -> InstallationId {
516 self.context.installation_id()
517 }
518 pub fn inbox_id(&self) -> InboxIdRef<'_> {
520 self.context.identity().inbox_id()
521 }
522
523 pub fn db(&self) -> <Context::Db as XmtpDb>::DbQuery {
526 self.context.db()
527 }
528
529 #[cfg(any(test, feature = "test-utils"))]
532 pub fn set_name(&self, name: &str) {
533 log_event!(Event::AssociateName, self.context.installation_id(), name);
534 }
535
536 pub fn device_sync_client(&self) -> DeviceSyncClient<Context> {
537 let metrics = self.context.sync_metrics();
538 DeviceSyncClient::new(
539 self.context.clone(),
540 metrics.unwrap_or(Arc::new(WorkerMetrics::new(self.context.installation_id()))),
541 )
542 }
543
544 pub async fn find_inbox_id_from_identifier(
546 &self,
547 conn: &impl DbQuery,
548 identifier: Identifier,
549 ) -> Result<Option<String>, ClientError> {
550 let results = self
551 .find_inbox_ids_from_identifiers(conn, &[identifier])
552 .await?;
553 Ok(results.into_iter().next().flatten())
554 }
555
556 pub(crate) async fn find_inbox_ids_from_identifiers(
559 &self,
560 conn: &impl DbQuery,
561 identifiers: &[Identifier],
562 ) -> Result<Vec<Option<String>>, ClientError> {
563 let ids: Vec<(String, StoredIdentityKind)> = identifiers
564 .iter()
565 .map(|i| {
566 Ok::<_, ConversionError>((
567 i.clone().to_string(),
568 StoredIdentityKind::try_from(IdentifierKind::from(i))?,
569 ))
570 })
571 .try_collect()?;
572 let cached_inbox_ids = conn.fetch_cached_inbox_ids(&ids)?;
573 let mut new_inbox_ids: HashMap<&Identifier, Option<String>> = HashMap::new();
574
575 let missing: Vec<_> = identifiers
576 .iter()
577 .filter(|ident| !cached_inbox_ids.contains_key(&format!("{ident}")))
578 .collect();
579
580 if !missing.is_empty() {
581 let requests = missing
582 .iter()
583 .map(|identifier| (*identifier).into())
584 .collect();
585 let results = self.context.api().get_inbox_ids(requests).await?;
586 new_inbox_ids = missing.into_iter().zip(results).collect();
587 }
588
589 let inbox_ids = identifiers
590 .iter()
591 .map(|ident| {
592 let cache_key = format!("{ident}");
593 if let Some(inbox_id) = cached_inbox_ids.get(&cache_key) {
594 return Some(inbox_id.clone());
595 }
596 new_inbox_ids.get(ident).cloned().flatten()
597 })
598 .collect();
599 Ok(inbox_ids)
600 }
601
602 pub fn inbox_sequence_id(
605 &self,
606 conn: &DbConnection<<Context::Db as XmtpDb>::Connection>,
607 ) -> Result<i64, StorageError> {
608 self.context
609 .identity()
610 .sequence_id(conn)
611 .map_err(Into::into)
612 }
613
614 pub async fn inbox_state(
616 &self,
617 refresh_from_network: bool,
618 ) -> Result<AssociationState, ClientError> {
619 let conn = self.context.db();
620 let inbox_id = self.inbox_id();
621 if refresh_from_network {
622 load_identity_updates_for_client(&self.context, &conn, &[inbox_id]).await?;
623 }
624 let identity_service = IdentityUpdates::new(&self.context);
625 let state = identity_service
626 .get_association_state(&conn, inbox_id, None)
627 .await?;
628 Ok(state)
629 }
630
631 pub async fn inbox_addresses(
633 &self,
634 refresh_from_network: bool,
635 inbox_ids: Vec<InboxIdRef<'_>>,
636 ) -> Result<Vec<AssociationState>, ClientError> {
637 let conn = self.context.db();
638 if refresh_from_network {
639 load_identity_updates_for_client(&self.context, &conn, &inbox_ids).await?;
640 }
641 let identity_service = IdentityUpdates::new(&self.context);
642 let state = identity_service
643 .batch_get_association_state(
644 &conn,
645 &inbox_ids.into_iter().map(|i| (i, None)).collect::<Vec<_>>(),
646 )
647 .await?;
648 Ok(state)
649 }
650
651 pub async fn fetch_inbox_updates_count(
655 &self,
656 refresh_from_network: bool,
657 inbox_ids: Vec<InboxIdRef<'_>>,
658 ) -> Result<HashMap<InboxId, u32>, ClientError> {
659 let conn = self.context.db();
660 if refresh_from_network {
661 load_identity_updates_for_client(&self.context, &conn, &inbox_ids).await?;
662 }
663 let inbox_id_strs = inbox_ids.to_vec();
664 let counts = conn.count_inbox_updates(&inbox_id_strs)?;
665 Ok(counts.into_iter().map(|(k, v)| (k, v as u32)).collect())
666 }
667
668 pub async fn fetch_own_inbox_updates_count(
672 &self,
673 refresh_from_network: bool,
674 ) -> Result<u32, ClientError> {
675 let inbox_id = self.inbox_id();
676 Ok(self
677 .fetch_inbox_updates_count(refresh_from_network, vec![inbox_id])
678 .await?
679 .get(inbox_id)
680 .copied()
681 .unwrap_or(0))
682 }
683
684 pub async fn inbox_creation_signature_kind(
694 &self,
695 inbox_id: InboxIdRef<'_>,
696 refresh_from_network: bool,
697 ) -> Result<Option<xmtp_id::associations::SignatureKind>, ClientError> {
698 let conn = self.context.db();
699
700 if refresh_from_network {
702 load_identity_updates_for_client(&self.context, &conn, &[inbox_id]).await?;
703 }
704
705 let verifier = self.context.scw_verifier();
706
707 let signature_kind = get_creation_signature_kind(&conn, verifier, inbox_id).await?;
708
709 Ok(signature_kind)
710 }
711
712 pub async fn set_consent_states(
715 &self,
716 records: &[StoredConsentRecord],
717 ) -> Result<(), ClientError> {
718 let mut seen = HashSet::new();
720 let mut final_records: Vec<_> = records
721 .iter()
722 .rev()
723 .filter(|record| seen.insert((record.entity_type as i32, record.entity.as_str())))
724 .cloned()
725 .collect();
726 final_records.reverse();
727 crate::state_tx::state_write_with_events(
728 self.context.mls_storage(),
729 self.context.events(),
730 |tx, events| {
731 let storage = tx.storage();
732 let db = storage.db();
733 let changed = db.insert_or_replace_consent_records(&final_records)?;
734 let updates: Vec<_> = changed.into_iter().map(PreferenceUpdate::Consent).collect();
735 if !updates.is_empty() {
736 self.context.task_channels().mark_notification_changed();
737 }
738 emit_preference_updates(events, updates, PreferenceOrigin::Local, &db)?;
739 Ok::<_, StorageError>(Continue(()))
740 },
741 )?
742 .into_continued();
743
744 Ok(())
745 }
746
747 pub async fn get_consent_state(
749 &self,
750 entity_type: ConsentType,
751 entity: String,
752 ) -> Result<ConsentState, ClientError> {
753 let conn = self.context.db();
754 let record = conn.get_consent_record(entity, entity_type)?;
755
756 match record {
757 Some(rec) => Ok(rec.state),
758 None => Ok(ConsentState::Unknown),
759 }
760 }
761
762 pub fn release_db_connection(&self) -> Result<(), ClientError> {
764 self.context
765 .db()
766 .disconnect()
767 .map_err(xmtp_db::StorageError::from)?;
768 Ok(())
769 }
770
771 pub fn identity(&self) -> &Identity {
773 self.context.identity()
774 }
775
776 pub(crate) fn ensure_identity_ready(&self) -> Result<(), ClientError> {
779 self.context.server_configuration().check()?;
783 if !self.identity().is_ready() {
784 tracing::warn!(
785 inbox_id = %self.inbox_id(),
786 "Operation attempted before register_identity() was called"
787 );
788 return Err(IdentityError::UninitializedIdentity.into());
789 }
790 Ok(())
791 }
792
793 pub fn create_group(
796 &self,
797 permissions_policy_set: Option<PolicySet>,
798 opts: Option<GroupMetadataOptions>,
799 ) -> Result<MlsGroup<Context>, ClientError> {
800 self.ensure_identity_ready()?;
801
802 let group: MlsGroup<Context> = MlsGroup::create_and_insert(
803 self.context.clone(),
804 ConversationType::Group,
805 permissions_policy_set.unwrap_or_default(),
806 opts.unwrap_or_default(),
807 None,
808 )?;
809
810 log_event!(
811 Event::CreatedGroup,
812 self.context.installation_id(),
813 group_id = group.group_id
814 );
815
816 Ok(group)
817 }
818
819 pub async fn create_group_with_identifiers(
821 &self,
822 account_identifiers: &[Identifier],
823 permissions_policy_set: Option<PolicySet>,
824 opts: Option<GroupMetadataOptions>,
825 ) -> Result<MlsGroup<Context>, ClientError> {
826 let group = self.create_group(permissions_policy_set, opts)?;
827
828 group.add_members_by_identity(account_identifiers).await?;
829
830 Ok(group)
831 }
832
833 #[tracing::instrument(level = "debug", skip_all, fields(size = inbox_ids.len()))]
834 pub async fn create_group_with_members(
835 &self,
836 inbox_ids: &[impl AsIdRef],
837 permissions_policy_set: Option<PolicySet>,
838 opts: Option<GroupMetadataOptions>,
839 ) -> Result<MlsGroup<Context>, ClientError> {
840 tracing::info!("creating group");
841 let group = self.create_group(permissions_policy_set, opts)?;
842
843 group.add_members(inbox_ids).await?;
844
845 Ok(group)
846 }
847
848 #[tracing::instrument(level = "debug", skip_all)]
850 async fn create_dm_by_inbox_id(
851 &self,
852 target_inbox_id: InboxId,
853 opts: Option<DMMetadataOptions>,
854 ) -> Result<MlsGroup<Context>, ClientError> {
855 let group: MlsGroup<Context> = MlsGroup::create_dm_and_insert(
856 &self.context,
857 GroupMembershipState::Allowed,
858 target_inbox_id.clone(),
859 GroupMetadataOptions {
860 message_disappearing_settings: opts
861 .unwrap_or_default()
862 .message_disappearing_settings,
863 ..Default::default()
864 },
865 None,
866 )?;
867
868 log_event!(
869 Event::CreatedDM,
870 self.context.installation_id(),
871 group_id = group.group_id,
872 target_inbox = target_inbox_id
873 );
874 group.add_members(&[target_inbox_id]).await?;
875
876 Ok(group)
877 }
878
879 pub async fn find_or_create_dm_by_identity(
881 &self,
882 target_identity: Identifier,
883 opts: Option<DMMetadataOptions>,
884 ) -> Result<MlsGroup<Context>, ClientError> {
885 self.ensure_identity_ready()?;
886 tracing::info!("finding or creating dm with address: {target_identity}");
887 let inbox_id = match self
888 .find_inbox_id_from_identifier(&self.context.db(), target_identity.clone())
889 .await?
890 {
891 Some(id) => id,
892 None => {
893 return Err(NotFound::InboxIdForAddress(target_identity.to_string()).into());
894 }
895 };
896
897 self.find_or_create_dm(inbox_id, opts).await
898 }
899
900 pub async fn find_or_create_dm(
902 &self,
903 inbox_id: impl AsIdRef,
904 opts: Option<DMMetadataOptions>,
905 ) -> Result<MlsGroup<Context>, ClientError> {
906 self.ensure_identity_ready()?;
907 let inbox_id = inbox_id.as_ref();
908 tracing::info!("finding or creating dm with inbox_id: {}", inbox_id);
909 let db = self.context.db();
910 let group = db.find_active_dm_group(&DmMembers {
911 member_one_inbox_id: self.inbox_id(),
912 member_two_inbox_id: inbox_id,
913 })?;
914
915 if let Some(group) = group {
916 return Ok(MlsGroup::new(
917 self.context.clone(),
918 group.id,
919 group.dm_id,
920 group.conversation_type,
921 group.created_at_ns,
922 ));
923 }
924 self.create_dm_by_inbox_id(inbox_id.to_string(), opts).await
925 }
926
927 pub fn group(&self, group_id: &GroupId) -> Result<MlsGroup<Context>, ClientError> {
932 MlsStore::new(self.context.clone())
933 .group(group_id)
934 .map_err(Into::into)
935 }
936
937 pub fn stitched_group(&self, group_id: &GroupId) -> Result<MlsGroup<Context>, ClientError> {
942 let conn = self.context.db();
943 let stored_group = conn.fetch_stitched(group_id)?;
944 stored_group
945 .map(|g| {
946 MlsGroup::new(
947 self.context.clone(),
948 g.id,
949 g.dm_id,
950 g.conversation_type,
951 g.created_at_ns,
952 )
953 })
954 .ok_or(NotFound::GroupById(*group_id))
955 .map_err(Into::into)
956 }
957
958 pub fn find_duplicate_dms_for_group(
960 &self,
961 group_id: &GroupId,
962 ) -> Result<Vec<MlsGroup<Context>>, ClientError> {
963 let (group, _) = MlsGroup::new_cached(self.context.clone(), group_id)?;
964 group.find_duplicate_dms()
965 }
966
967 pub fn group_disappearing_settings(
972 &self,
973 group_id: &GroupId,
974 ) -> Result<Option<MessageDisappearingSettings>, ClientError> {
975 let (group, _) = MlsGroup::new_cached(self.context.clone(), group_id)?;
976 Ok(group.disappearing_settings()?)
977 }
978
979 pub fn dm_group_from_target_inbox(
985 &self,
986 target_inbox_id: String,
987 ) -> Result<MlsGroup<Context>, ClientError> {
988 let conn = self.context.db();
989
990 let group = conn
991 .find_active_dm_group(&DmMembers {
992 member_one_inbox_id: self.inbox_id(),
993 member_two_inbox_id: &target_inbox_id,
994 })?
995 .ok_or(NotFound::DmByInbox(target_inbox_id))?;
996 Ok(MlsGroup::new(
997 self.context.clone(),
998 group.id,
999 group.dm_id,
1000 group.conversation_type,
1001 group.created_at_ns,
1002 ))
1003 }
1004
1005 pub fn message(&self, message_id: Vec<u8>) -> Result<StoredGroupMessage, ClientError> {
1008 let conn = &mut self.context.db();
1009 let message =
1011 conn.get_app_visible_group_message(&message_id, xmtp_common::time::now_ns())?;
1012 Ok(message.ok_or(NotFound::MessageById(message_id))?)
1013 }
1014
1015 pub async fn message_with_group(
1018 &self,
1019 id: &[u8],
1020 ) -> Result<Option<(StoredGroupMessage, MlsGroup<Context>)>, ClientError> {
1021 let message = match self.message(id.to_vec()) {
1022 Ok(message) => message,
1023 Err(ClientError::Storage(StorageError::NotFound(NotFound::MessageById(_)))) => {
1024 return Ok(None);
1025 }
1026 Err(error) => return Err(error),
1027 };
1028 let group = self.stitched_group(&message.group_id)?;
1029 Ok(Some((message, group)))
1030 }
1031
1032 #[xmtp_common::mls_span]
1035 pub fn message_v2(&self, message_id: Vec<u8>) -> Result<DecodedMessage, ClientError> {
1036 let conn = self.context.db();
1037 let message = conn
1038 .get_app_visible_group_message(&message_id, xmtp_common::time::now_ns())?
1039 .ok_or_else(|| NotFound::MessageById(message_id.clone()))?;
1040
1041 let group_id = message.group_id;
1042
1043 let enriched = enrich_messages(conn, &group_id, vec![message])?;
1044
1045 enriched
1047 .into_iter()
1048 .next()
1049 .ok_or_else(|| ClientError::Generic("Failed to decode message".to_string()))
1051 }
1052
1053 pub fn delete_message(&self, message_id: Vec<u8>) -> Result<usize, ClientError> {
1057 Ok(crate::state_tx::state_write_with_events(
1058 self.context.mls_storage(),
1059 self.context.events(),
1060 |tx, events| {
1061 let storage = tx.storage();
1062 let db = storage.db();
1063 let message = db.get_group_message(&message_id)?;
1064 let deleted = db.delete_message_by_id(&message_id)?;
1065 if deleted > 0
1066 && let Some(message) = message
1067 {
1068 emit_deleted_messages(
1069 events,
1070 vec![message],
1071 DeletionCause::DeletedLocally,
1072 None,
1074 &db,
1075 )?;
1076 }
1077 Ok::<_, StorageError>(Continue(deleted))
1078 },
1079 )?
1080 .into_continued())
1081 }
1082
1083 pub fn find_groups(&self, args: GroupQueryArgs) -> Result<Vec<MlsGroup<Context>>, ClientError> {
1091 MlsStore::new(self.context.clone())
1092 .find_groups(args)
1093 .map_err(Into::into)
1094 }
1095
1096 pub fn list_conversations(
1097 &self,
1098 args: GroupQueryArgs,
1099 ) -> Result<Vec<ConversationListItem<Context>>, ClientError> {
1100 let mut args = args.clone();
1101 if args.order_by.is_none() {
1103 args.order_by = Some(GroupQueryOrderBy::LastActivity);
1104 }
1105 Ok(self
1106 .context
1107 .db()
1108 .fetch_conversation_list(args)?
1109 .into_iter()
1110 .map(|conversation_item: DbConversationListItem| {
1111 let message = conversation_item.message_id.and_then(|message_id| {
1112 if conversation_item
1113 .expire_at_ns
1114 .is_some_and(|deadline| deadline <= xmtp_common::time::now_ns())
1115 {
1116 return None;
1117 }
1118 let msg: Option<StoredGroupMessage> = Some(StoredGroupMessage {
1120 id: message_id,
1121 group_id: conversation_item.id,
1122 decrypted_message_bytes: conversation_item.decrypted_message_bytes?,
1123 sent_at_ns: conversation_item.sent_at_ns?,
1124 sender_installation_id: conversation_item.sender_installation_id?,
1125 sender_inbox_id: conversation_item.sender_inbox_id?,
1126 kind: conversation_item.kind?,
1127 delivery_status: conversation_item.delivery_status?,
1128 content_type: conversation_item.content_type?,
1129 version_major: conversation_item.version_major?,
1130 version_minor: conversation_item.version_minor?,
1131 authority_id: conversation_item.authority_id?,
1132 reference_id: None, sequence_id: conversation_item.sequence_id?,
1134 envelope_hash: None,
1135 expiry_ns: conversation_item.expiry_ns,
1136 expire_at_ns: conversation_item.expire_at_ns,
1137 inserted_at_ns: 0, should_push: true, idempotency_key: conversation_item.sent_at_ns.unwrap_or_default().to_string(),
1142 });
1143 if msg.is_none() {
1144 tracing::warn!("tried listing message, but message had missing fields so it was skipped");
1145 }
1146 msg
1147 });
1148
1149 ConversationListItem {
1150 membership_state: conversation_item.membership_state,
1151 added_by_inbox_id: conversation_item.added_by_inbox_id,
1152 group: MlsGroup::new(
1153 self.context.clone(),
1154 conversation_item.id,
1155 conversation_item.dm_id,
1156 conversation_item.conversation_type,
1157 conversation_item.created_at_ns,
1158 ),
1159 last_message: message,
1160 is_commit_log_forked: conversation_item.is_commit_log_forked,
1161 }
1162 })
1163 .collect())
1164 }
1165
1166 pub async fn register_with_owner(&self, owner: &impl InboxOwner) -> Result<(), ClientError> {
1168 if self.identity().is_ready() {
1169 return self.ensure_registration_visible().await;
1170 }
1171
1172 let mut signature_request = self
1173 .context
1174 .signature_request()
1175 .ok_or(SignatureRequestError::MissingSigner)?;
1176 let signature = owner
1177 .sign(&signature_request.signature_text())
1178 .map_err(SignatureError::from)?;
1179 signature_request
1180 .add_signature(signature, self.scw_verifier())
1181 .await?;
1182 self.register_identity(signature_request).await
1183 }
1184
1185 #[xmtp_common::mls_span]
1189 pub async fn register_identity(
1190 &self,
1191 signature_request: SignatureRequest,
1192 ) -> Result<(), ClientError> {
1193 tracing::info!("registering identity");
1194 self.context.server_configuration().check()?;
1196
1197 let stored_identity: Option<StoredIdentity> = self.context.db().fetch(&())?;
1199 if stored_identity.is_some() {
1200 tracing::info!("Resuming stored identity registration");
1201 self.identity().set_ready();
1202 return self.ensure_registration_visible().await;
1203 }
1204
1205 let (kp_bytes, history_id) = self.identity().generate_and_store_key_package(
1207 self.context.mls_storage(),
1208 CREATE_PQ_KEY_PACKAGE_EXTENSION,
1209 )?;
1210
1211 let identity_update = signature_request
1213 .build_identity_update()
1214 .map_err(IdentityUpdateError::from)?;
1215 identity_update
1216 .to_verified(&self.context.scw_verifier())
1217 .await?;
1218
1219 let key_package_meta = self.context.api().upload_key_package(kp_bytes).await?;
1221 let key_package_cursor = key_package_meta
1222 .cursor
1223 .filter(|cursor| cursor.sequence_id > 0)
1224 .ok_or(xmtp_api::ApiError::InvalidResponse(
1225 "key package publish cursor",
1226 ))?;
1227
1228 let _own_refresh = self
1232 .context
1233 .identity_resolution_registry()
1234 .own_inbox_refresh()
1235 .lock()
1236 .await;
1237 let inbox_id = self.inbox_id().to_string();
1238 let previous_sequence = self
1239 .context
1240 .db()
1241 .get_latest_sequence_id(&[inbox_id.as_str()])?
1242 .get(&inbox_id)
1243 .copied();
1244 let before = if previous_sequence.is_some() {
1245 Some(
1246 self.identity_updates()
1247 .get_association_state(&self.context.db(), &inbox_id, None)
1248 .await?,
1249 )
1250 } else {
1251 None
1252 };
1253
1254 let registration_cursor = crate::identity_updates::publish_with_conflict_retry(
1256 self.context.api(),
1257 &self.context.db(),
1258 identity_update,
1259 &self.context.scw_verifier(),
1260 )
1261 .await?;
1262
1263 retry_async!(
1265 Retry::default(),
1266 (async {
1267 load_identity_updates(self.context.api(), &self.context.db(), &[inbox_id.as_str()])
1268 .await
1269 })
1270 )?;
1271 self.identity_updates()
1272 .emit_own_installation_updates(&self.context.db(), &inbox_id, before, previous_sequence)
1273 .await?;
1274
1275 crate::state_tx::state_write(self.context.mls_storage(), |tx| {
1277 let storage = tx.storage();
1278 storage.db().record_key_package_publication(
1279 history_id,
1280 xmtp_proto::types::Cursor(key_package_cursor.sequence_id),
1281 )?;
1282 storage
1283 .db()
1284 .reset_key_package_rotation_queue(KEY_PACKAGE_ROTATION_INTERVAL_NS)?;
1285 Ok::<_, StorageError>(Continue(()))
1286 })
1287 .map(TransactionOutcome::into_continued)?;
1288
1289 let mut stored_identity = StoredIdentity::try_from(self.identity())?;
1291 stored_identity.registration_cursor_sequence_id = Some(registration_cursor.0 as i64);
1292 stored_identity.store(&self.context.db())?;
1293 self.context
1294 .registration_event_pending()
1295 .store(true, Ordering::Release);
1296 self.identity().set_ready();
1297 drop(_own_refresh);
1298 self.ensure_registration_visible().await
1299 }
1300
1301 pub fn is_registration_visible(&self) -> Result<bool, ClientError> {
1304 let stored: Option<StoredIdentity> = self.context.db().fetch(&())?;
1305 Ok(self.identity().is_ready()
1306 && stored.is_some_and(|identity| identity.registration_cursor_sequence_id.is_none()))
1307 }
1308
1309 pub async fn ensure_registration_visible(&self) -> Result<(), ClientError> {
1311 let stored: Option<StoredIdentity> = self.context.db().fetch(&())?;
1312 if stored.is_some_and(|identity| identity.registration_cursor_sequence_id.is_some()) {
1313 self.wait_for_registration_visible(VisibilityConfirmationOptions::default())
1314 .await?;
1315 }
1316 Ok(())
1317 }
1318
1319 pub async fn wait_for_registration_visible(
1322 &self,
1323 options: VisibilityConfirmationOptions,
1324 ) -> Result<(), ClientError> {
1325 use xmtp_common::time::{Duration, sleep, timeout};
1326 if !self.identity().is_ready() {
1327 return Err(ClientError::RegistrationNotVisible);
1328 }
1329 let stored: Option<StoredIdentity> = self.context.db().fetch(&())?;
1330 let stored = stored.ok_or(ClientError::RegistrationNotVisible)?;
1331 let Some(receipt) = stored.registration_cursor_sequence_id else {
1332 return Ok(());
1333 };
1334 self.context.server_configuration().check()?;
1335 let sequence_id = u64::try_from(receipt)
1336 .ok()
1337 .filter(|sequence_id| *sequence_id != 0)
1338 .ok_or(ClientError::RegistrationNotVisible)?;
1339 timeout(Duration::from_millis(options.timeout_ms), async {
1340 let mut delay = REGISTRATION_INITIAL_BACKOFF;
1341 let inbox = hex::decode(self.inbox_id())
1342 .map_err(|_| xmtp_api::ApiError::InvalidRequest("registration inbox id"))?;
1343 let topic = xmtp_proto::types::Topic::new_identity_update(inbox);
1344 xmtp_proto::types::Topic::parse(&topic)?;
1345 loop {
1346 let heads = self
1347 .context
1348 .api()
1349 .newest_topic_cursors(vec![topic.clone()])
1350 .await?;
1351 let head = heads
1352 .get(&topic)
1353 .ok_or(xmtp_api::ApiError::InvalidResponse(
1354 "registration identity head",
1355 ))?;
1356 if head.0 >= sequence_id {
1358 return Ok::<(), ClientError>(());
1359 }
1360 sleep(delay).await;
1361 delay = (delay * 2).min(REGISTRATION_MAX_BACKOFF);
1362 }
1363 })
1364 .await
1365 .map_err(|_| ClientError::RegistrationNotVisible)??;
1366 if self
1367 .context
1368 .registration_event_pending()
1369 .swap(false, Ordering::AcqRel)
1370 {
1371 xmtp_events::EventWriter::emit(
1372 self.context.events(),
1373 Some(xmtp_events::ClientEvent::IdentityRegistered(
1374 xmtp_events::IdentityRegistered {
1375 inbox_id: self.inbox_id().to_string(),
1376 installation_key: self.installation_id.to_vec(),
1377 },
1378 )),
1379 None,
1380 );
1381 }
1382 self.context.db().clear_registration_cursor(receipt)?;
1383 Ok(())
1384 }
1385
1386 pub fn queue_key_rotation(&self) -> Result<(), ClientError> {
1388 crate::worker::key_package_maintenance::queue_key_rotation(&self.context)?;
1389 Ok(())
1390 }
1391
1392 pub async fn rotate_and_upload_key_package(&self) -> Result<(), ClientError> {
1395 self.ensure_identity_ready()?;
1399 self.identity()
1400 .rotate_and_upload_key_package(
1401 self.context.api(),
1402 self.context.mls_storage(),
1403 CREATE_PQ_KEY_PACKAGE_EXTENSION,
1404 )
1405 .await?;
1406 crate::worker::key_package_maintenance::nudge_deletion(&self.context)?;
1409
1410 Ok(())
1411 }
1412
1413 #[tracing::instrument(skip_all)]
1415 pub async fn get_key_packages_for_installation_ids(
1416 &self,
1417 installation_ids: Vec<Vec<u8>>,
1418 ) -> Result<
1419 HashMap<Vec<u8>, Result<VerifiedKeyPackageV2, KeyPackageVerificationError>>,
1420 ClientError,
1421 > {
1422 MlsStore::new(self.context.clone())
1423 .get_key_packages_for_installation_ids(installation_ids)
1424 .await
1425 .map_err(Into::into)
1426 }
1427
1428 #[tracing::instrument(skip_all)]
1431 pub async fn sync_welcomes(&self) -> Result<Vec<MlsGroup<Context>>, GroupError> {
1432 self.ensure_identity_ready()?;
1433 WelcomeService::new(self.context.clone())
1434 .sync_welcomes()
1435 .await
1436 .map_err(|error| self.context.server_configuration().blocked_or(error))
1437 }
1438
1439 #[tracing::instrument(err, skip_all, fields(operation = "sync_all_groups"))]
1442 pub async fn sync_all_groups(
1443 &self,
1444 groups: Vec<MlsGroup<Context>>,
1445 ) -> Result<GroupSyncSummary, GroupError> {
1446 self.ensure_identity_ready()?;
1447 WelcomeService::new(self.context.clone())
1448 .sync_all_groups(groups)
1449 .await
1450 .map_err(|error| self.context.server_configuration().blocked_or(error))
1451 }
1452
1453 #[xmtp_common::mls_span]
1456 pub async fn sync_all_welcomes_and_groups(
1457 &self,
1458 consent_states: Option<Vec<ConsentState>>,
1459 ) -> Result<GroupSyncSummary, GroupError> {
1460 self.ensure_identity_ready()?;
1461 WelcomeService::new(self.context.clone())
1462 .sync_all_welcomes_and_groups(consent_states)
1463 .await
1464 .map_err(|error| self.context.server_configuration().blocked_or(error))
1465 }
1466
1467 pub async fn unstick_paused_groups(&self) -> Result<usize, GroupError> {
1477 self.ensure_identity_ready()?;
1478 WelcomeService::new(self.context.clone())
1479 .unstick_paused_groups()
1480 .await
1481 }
1482
1483 pub async fn sync_all_welcomes_and_device_sync_groups(
1484 &self,
1485 ) -> Result<GroupSyncSummary, ClientError> {
1486 self.sync_welcomes().await?;
1487 self.sync_all_device_sync_groups().await
1488 }
1489
1490 pub async fn sync_all_device_sync_groups(&self) -> Result<GroupSyncSummary, ClientError> {
1491 let groups = self
1492 .context
1493 .db()
1494 .all_sync_groups()?
1495 .into_iter()
1496 .map(|g| {
1497 MlsGroup::new(
1498 self.context.clone(),
1499 g.id,
1500 g.dm_id,
1501 g.conversation_type,
1502 g.created_at_ns,
1503 )
1504 })
1505 .collect();
1506
1507 Ok(self.sync_all_groups(groups).await?)
1508 }
1509
1510 pub async fn validate_credential_against_network(
1517 &self,
1518 conn: &DbConnection<<Context::Db as XmtpDb>::Connection>,
1519 credential: &[u8],
1520 installation_pub_key: Vec<u8>,
1521 ) -> Result<InboxId, ClientError> {
1522 let inbox_id = parse_credential(credential)?;
1523 let association_state = IdentityUpdates::new(&self.context)
1524 .get_latest_association_state(conn, &inbox_id)
1525 .await?;
1526 let ident = MemberIdentifier::installation(installation_pub_key);
1527
1528 match association_state.get(&ident) {
1529 Some(_) => Ok(inbox_id),
1530 None => Err(IdentityError::InstallationIdNotFound(inbox_id).into()),
1531 }
1532 }
1533
1534 pub async fn can_message(
1542 &self,
1543 account_identifiers: &[Identifier],
1544 ) -> Result<HashMap<Identifier, bool>, ClientError> {
1545 self.context.server_configuration().check()?;
1549 let requests = account_identifiers.iter().map(Into::into).collect();
1550
1551 let results = self.context.api().get_inbox_ids(requests).await?;
1552 Ok(account_identifiers
1553 .iter()
1554 .cloned()
1555 .zip(results.into_iter().map(|inbox_id| inbox_id.is_some()))
1556 .collect())
1557 }
1558}
1559
1560#[cfg(test)]
1561pub(crate) mod tests;
1562
1563#[cfg(all(test, not(target_arch = "wasm32")))]
1564pub(crate) mod test_hooks {
1565 use std::sync::Arc;
1566 use tokio::sync::Barrier;
1567
1568 type PauseHook = (Vec<u8>, Arc<Barrier>, Arc<Barrier>);
1569 pub(crate) static PAUSE_RECONNECT: parking_lot::Mutex<Option<PauseHook>> =
1572 parking_lot::Mutex::new(None);
1573
1574 pub(super) fn pause_reconnect(installation_id: &[u8]) {
1575 let paused = {
1576 let mut hook = PAUSE_RECONNECT.lock();
1577 if hook
1578 .as_ref()
1579 .is_some_and(|(installation, _, _)| installation.as_slice() == installation_id)
1580 {
1581 hook.take()
1582 } else {
1583 None
1584 }
1585 };
1586 if let Some((_, entered, release)) = paused {
1587 tokio::runtime::Handle::current().block_on(async {
1588 entered.wait().await;
1589 release.wait().await;
1590 });
1591 }
1592 }
1593}