1use super::*;
4
5impl<Context> MlsGroup<Context>
6where
7 Context: XmtpSharedContext,
8{
9 #[xmtp_common::mls_span]
11 pub async fn send_message(
12 &self,
13 message: &[u8],
14 opts: send_message_opts::SendMessageOpts,
15 ) -> Result<Vec<u8>, GroupError> {
16 if !self.is_active()? {
17 tracing::warn!("Unable to send a message on an inactive group.");
18 return Err(GroupError::GroupInactive);
19 }
20
21 self.ensure_not_paused().await?;
22 let update_interval_ns = Some(SEND_MESSAGE_UPDATE_INSTALLATIONS_INTERVAL_NS);
23 self.maybe_update_installations(update_interval_ns).await?;
24
25 self.commit_pending_proposals_if_any().await?;
28
29 let message_id =
30 self.prepare_message(message, opts, |key| Self::into_envelope(message, key))?;
31
32 self.sync_until_last_intent_resolved().await?;
33
34 self.update_consent_state(ConsentState::Allowed)?;
36
37 Ok(message_id)
38 }
39
40 async fn commit_pending_proposals_if_any(&self) -> Result<(), GroupError> {
44 let has_pending = self.with_group_snapshot(|openmls_group| {
45 Ok::<bool, GroupError>(openmls_group.pending_proposals().next().is_some())
46 })?;
47
48 if has_pending {
49 tracing::debug!(
50 inbox_id = self.context.inbox_id(),
51 group_id = %self.group_id,
52 "Found pending proposals, committing before sending message"
53 );
54
55 let intent = intents::QueueIntent::commit_pending_proposals().queue(self)?;
57 self.sync_until_intent_resolved(intent.id).await?;
58 }
59
60 Ok(())
61 }
62
63 #[cfg_attr(any(test, feature = "test-utils"), tracing::instrument(level = "info", fields(inbox_id = self.context.inbox_id()), skip(self)))]
66 #[cfg_attr(not(any(test, feature = "test-utils")), xmtp_common::mls_span)]
67 pub async fn publish_messages(&self) -> Result<(), GroupError> {
68 self.ensure_not_paused().await?;
69 let update_interval_ns = Some(SEND_MESSAGE_UPDATE_INSTALLATIONS_INTERVAL_NS);
70 self.maybe_update_installations(update_interval_ns).await?;
71 self.sync_until_last_intent_resolved().await?;
72
73 self.update_consent_state(ConsentState::Allowed)?;
75
76 Ok(())
77 }
78
79 pub async fn update_installations(&self) -> Result<(), GroupError> {
84 self.ensure_not_paused().await?;
85 self.maybe_update_installations(Some(0)).await?;
86 Ok(())
87 }
88
89 pub fn send_message_optimistic(
91 &self,
92 message: &[u8],
93 opts: send_message_opts::SendMessageOpts,
94 ) -> Result<Vec<u8>, GroupError> {
95 let message_id =
96 self.prepare_message(message, opts, |key| Self::into_envelope(message, key))?;
97 Ok(message_id)
98 }
99
100 pub fn prepare_message_for_later_publish(
113 &self,
114 message: &[u8],
115 should_push: bool,
116 idempotency_key: Option<String>,
117 ) -> Result<Vec<u8>, GroupError> {
118 state_write(self.context.mls_storage(), |tx| {
119 let storage = tx.storage();
120 let message = self.store_message_for_later_publish(
121 &storage.db(),
122 message,
123 should_push,
124 idempotency_key,
125 )?;
126 Ok::<_, GroupError>(Continue(message.id))
127 })
128 .map(TransactionOutcome::into_continued)
129 }
130
131 fn store_message_for_later_publish(
133 &self,
134 db: &impl DbQuery,
135 message: &[u8],
136 should_push: bool,
137 idempotency_key: Option<String>,
138 ) -> Result<StoredGroupMessage, GroupError> {
139 let now = now_ns();
140 let idempotency_key = idempotency_key.unwrap_or_else(|| {
142 hex::encode(xmtp_common::rand_vec::<DEFAULT_IDEMPOTENCY_KEY_BYTES>())
143 });
144 let queryable_content_fields = Self::extract_queryable_content_fields(message);
145
146 let message_id = calculate_message_id(self.group_id, message, &idempotency_key);
147
148 if let Some(existing) = db.get_group_message(&message_id)? {
152 return Ok(existing);
153 }
154
155 let group_message = StoredGroupMessage {
156 id: message_id.clone(),
157 group_id: self.group_id,
158 decrypted_message_bytes: message.to_vec(),
159 sent_at_ns: now,
160 kind: GroupMessageKind::Application,
161 sender_installation_id: self.context.installation_id().into(),
162 sender_inbox_id: self.context.inbox_id().to_string(),
163 delivery_status: DeliveryStatus::Unpublished,
164 content_type: queryable_content_fields.content_type,
165 version_major: queryable_content_fields.version_major,
166 version_minor: queryable_content_fields.version_minor,
167 authority_id: queryable_content_fields.authority_id,
168 reference_id: queryable_content_fields.reference_id,
169 sequence_id: 0,
170 envelope_hash: None,
171 expiry_ns: None,
172 expire_at_ns: None,
173 inserted_at_ns: 0,
174 should_push,
175 idempotency_key,
176 };
177 group_message.store(db)?;
178 Ok(group_message)
179 }
180
181 #[xmtp_common::mls_span]
189 pub async fn publish_stored_message(&self, message_id: &[u8]) -> Result<(), GroupError> {
190 if !self.is_active()? {
191 return Err(GroupError::GroupInactive);
192 }
193 self.ensure_not_paused().await?;
194
195 let queued = state_write(self.context.mls_storage(), |tx| {
196 let storage = tx.storage();
197 let db = storage.db();
198 let message = db
199 .get_group_message(message_id)?
200 .filter(|message| message.group_id == self.group_id)
201 .ok_or_else(|| GroupError::NotFound(NotFound::MessageById(message_id.to_vec())))?;
202 if message.delivery_status == DeliveryStatus::Published {
203 return Ok(Continue(false));
204 }
205 let envelope =
206 Self::into_envelope(&message.decrypted_message_bytes, &message.idempotency_key);
207 let intent_data: Vec<u8> = SendMessageIntentData::new(envelope.encode_to_vec()).into();
208 QueueIntent::send_message()
209 .data(intent_data)
210 .should_push(message.should_push)
211 .queue_in(&db, self)?;
212 Ok::<_, GroupError>(Continue(true))
213 })?
214 .into_continued();
215 if !queued {
216 return Ok(());
217 }
218
219 self.maybe_update_installations(Some(SEND_MESSAGE_UPDATE_INSTALLATIONS_INTERVAL_NS))
221 .await?;
222 self.sync_until_last_intent_resolved().await?;
223
224 self.update_consent_state(ConsentState::Allowed)?;
226
227 Ok(())
228 }
229
230 pub fn delete_message(&self, message_id: Vec<u8>) -> Result<Vec<u8>, GroupError> {
247 use error::DeleteMessageError;
248
249 let conn = self.context.db();
250
251 let original_msg = conn
253 .get_group_message(&message_id)?
254 .ok_or_else(|| DeleteMessageError::MessageNotFound(hex::encode(&message_id)))?;
255
256 if original_msg.group_id.as_slice() != self.group_id.as_slice() {
258 return Err(DeleteMessageError::NotAuthorized.into());
259 }
260
261 if conn.is_message_deleted(&message_id)? {
263 return Err(DeleteMessageError::MessageAlreadyDeleted.into());
264 }
265
266 let sender_inbox_id = self.context.inbox_id();
267 let is_sender = original_msg.sender_inbox_id == sender_inbox_id;
268 let is_super_admin = self.is_super_admin(sender_inbox_id.to_string())?;
269
270 if !is_sender && !is_super_admin {
271 return Err(DeleteMessageError::NotAuthorized.into());
272 }
273
274 if !original_msg.kind.is_deletable() || !original_msg.content_type.is_deletable() {
275 return Err(DeleteMessageError::NonDeletableMessage.into());
276 }
277
278 let delete_msg = DeleteMessage {
279 message_id: hex::encode(&message_id),
280 };
281
282 let encoded_delete = DeleteMessageCodec::encode(delete_msg)?;
283 let mut buf = Vec::new();
284 encoded_delete.encode(&mut buf)?;
285
286 let deletion_message_id = self.send_message_optimistic(&buf, SendMessageOpts::default())?;
287
288 let is_super_admin_deletion = !is_sender && is_super_admin;
289
290 let deletion = StoredMessageDeletion {
291 id: deletion_message_id.clone(),
292 group_id: self.group_id,
293 deleted_message_id: message_id,
294 deleted_by_inbox_id: sender_inbox_id.to_string(),
295 is_super_admin_deletion,
296 deleted_at_ns: now_ns(),
297 };
298
299 deletion.store(&conn)?;
300
301 Ok(deletion_message_id)
302 }
303
304 pub(in crate::groups) fn extract_queryable_content_fields(
306 message: &[u8],
307 ) -> QueryableContentFields {
308 EncodedContent::decode(message)
310 .inspect_err(|_| {
311 tracing::debug!("No queryable content fields, msg not formatted as encoded content")
312 })
313 .and_then(|content| {
314 QueryableContentFields::try_from(content).inspect_err(|e| {
315 tracing::debug!(
316 "Failed to convert EncodedContent to QueryableContentFields: {}",
317 e
318 )
319 })
320 })
321 .unwrap_or_default()
322 }
323
324 #[tracing::instrument(skip_all, level = "trace")]
332 pub(crate) fn prepare_message<F>(
333 &self,
334 message: &[u8],
335 opts: send_message_opts::SendMessageOpts,
336 envelope: F,
337 ) -> Result<Vec<u8>, GroupError>
338 where
339 F: FnOnce(&str) -> PlaintextEnvelope,
340 {
341 state_write(self.context.mls_storage(), |tx| {
342 let storage = tx.storage();
343 let db = storage.db();
344 let stored_message = self.store_message_for_later_publish(
345 &db,
346 message,
347 opts.should_push,
348 opts.idempotency_key,
349 )?;
350 if stored_message.delivery_status == DeliveryStatus::Published {
351 return Ok(Continue(stored_message.id));
352 }
353 let plain_envelope = envelope(&stored_message.idempotency_key);
355 let mut encoded_envelope = vec![];
356 plain_envelope.encode(&mut encoded_envelope)?;
357
358 let intent_data: Vec<u8> = SendMessageIntentData::new(encoded_envelope).into();
360 QueueIntent::send_message()
361 .data(intent_data)
362 .should_push(stored_message.should_push)
363 .queue_in(&db, self)?;
364
365 Ok::<_, GroupError>(Continue(stored_message.id))
366 })
367 .map(TransactionOutcome::into_continued)
368 }
369
370 fn into_envelope(encoded_msg: &[u8], idempotency_key: &str) -> PlaintextEnvelope {
371 PlaintextEnvelope {
372 content: Some(Content::V1(V1 {
373 content: encoded_msg.to_vec(),
374 idempotency_key: idempotency_key.to_string(),
375 })),
376 }
377 }
378
379 pub fn find_messages(
382 &self,
383 args: &MsgQueryArgs,
384 ) -> Result<Vec<StoredGroupMessage>, GroupError> {
385 let conn = self.context.db();
386 let messages = conn.get_group_messages(&self.group_id, args)?;
387 Ok(messages)
388 }
389
390 pub fn count_messages(&self, args: &MsgQueryArgs) -> Result<i64, GroupError> {
392 let conn = self.context.db();
393 let count = conn.count_group_messages(&self.group_id, args)?;
394 Ok(count)
395 }
396
397 pub fn find_messages_with_reactions(
400 &self,
401 args: &MsgQueryArgs,
402 ) -> Result<Vec<StoredGroupMessageWithReactions>, GroupError> {
403 let conn = self.context.db();
404 let messages = conn.get_group_messages_with_reactions(&self.group_id, args)?;
405 Ok(messages)
406 }
407
408 #[xmtp_common::mls_span]
410 pub fn find_enriched_messages(
411 &self,
412 args: &MsgQueryArgs,
413 ) -> Result<Vec<crate::messages::decoded_message::DecodedMessage>, EnrichMessageError> {
414 let conn = self.context.db();
415 let messages = conn.get_group_messages(&self.group_id, args)?;
416 let enriched =
417 crate::messages::enrichment::enrich_messages(conn, &self.group_id, messages)?;
418 Ok(enriched)
419 }
420
421 pub fn get_last_read_times(&self) -> Result<LatestMessageTimeBySender, GroupError> {
422 let conn = self.context.db();
423 let latest_read_receipt =
424 conn.get_latest_message_times_by_sender(self.group_id, &[ContentType::ReadReceipt])?;
425 Ok(latest_read_receipt)
426 }
427
428 pub fn load(&self) -> Result<StoredGroup, StorageError> {
430 let conn = self.context.db();
431 if let Some(group) = conn.find_group(&self.group_id)? {
432 Ok(group)
433 } else {
434 tracing::error!("group {} does not exist", hex::encode(self.group_id));
435 Err(NotFound::GroupById(self.group_id).into())
436 }
437 }
438}