Skip to main content

matrix_sdk/room/
futures.rs

1// Copyright 2023 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Named futures returned from methods on types in [the `room` module][super].
16
17#![deny(unreachable_pub)]
18
19#[cfg(feature = "experimental-encrypted-state-events")]
20use std::borrow::Borrow;
21use std::{future::IntoFuture, time::Duration};
22
23use eyeball::SharedObservable;
24use matrix_sdk_base::deserialized_responses::EncryptionInfo;
25use matrix_sdk_common::boxed_into_future;
26use mime::Mime;
27#[cfg(feature = "e2e-encryption")]
28use ruma::events::MessageLikeEventType;
29#[cfg(feature = "unstable-msc4354")]
30use ruma::events::sticky::StickyDurationMs;
31#[cfg(doc)]
32use ruma::events::{MessageLikeUnsigned, SyncMessageLikeEvent};
33#[cfg(feature = "experimental-encrypted-state-events")]
34use ruma::events::{StateEventContent, StateEventType};
35use ruma::{
36    OwnedTransactionId, TransactionId,
37    api::client::{
38        delayed_events::send_delayed_event, message::send_message_event, state::send_state_event,
39    },
40    assign,
41    events::{AnyMessageLikeEventContent, AnyStateEventContent, MessageLikeEventContent},
42    serde::Raw,
43};
44use tracing::{Instrument, Span, info, trace};
45
46use super::Room;
47use crate::{
48    Result, TransmissionProgress,
49    attachment::AttachmentConfig,
50    config::RequestConfig,
51    utils::{IntoRawMessageLikeEventContent, IntoRawStateEventContent},
52};
53
54/// The result of the [`Room::send`] future
55#[derive(Debug)]
56pub struct SendMessageLikeEventResult {
57    /// The response
58    pub response: send_message_event::v3::Response,
59    /// The encryption info, if the event was encrypted
60    pub encryption_info: Option<EncryptionInfo>,
61}
62
63/// Future returned by [`Room::send`].
64#[allow(missing_debug_implementations)]
65pub struct SendMessageLikeEvent<'a> {
66    room: &'a Room,
67    event_type: String,
68    content: serde_json::Result<serde_json::Value>,
69    transaction_id: Option<OwnedTransactionId>,
70    request_config: Option<RequestConfig>,
71    #[cfg(feature = "unstable-msc4354")]
72    sticky_duration: Option<StickyDurationMs>,
73}
74
75impl<'a> SendMessageLikeEvent<'a> {
76    pub(crate) fn new(room: &'a Room, content: impl MessageLikeEventContent) -> Self {
77        let event_type = content.event_type().to_string();
78        let content = serde_json::to_value(&content);
79        Self {
80            room,
81            event_type,
82            content,
83            transaction_id: None,
84            request_config: None,
85            #[cfg(feature = "unstable-msc4354")]
86            sticky_duration: None,
87        }
88    }
89
90    /// Make this event sticky for `duration`, clamped to one hour.
91    ///
92    /// Note that if the homeserver doesn't support sticky events, it will
93    /// ignore the duration and send the event unsticky. Server support can
94    /// be checked with [`Client::supports_sticky_events`].
95    ///
96    /// [`Client::supports_sticky_events`]: crate::Client::supports_sticky_events
97    #[cfg(feature = "unstable-msc4354")]
98    pub fn with_sticky_duration(mut self, duration: Duration) -> Self {
99        self.sticky_duration = Some(crate::utils::sticky_duration_ms(duration));
100        self
101    }
102
103    /// Set a transaction ID for this event.
104    ///
105    /// Since sending message-like events always requires a transaction ID, one
106    /// is generated if this method is not called.
107    ///
108    /// The transaction ID is a locally-unique ID describing a message
109    /// transaction with the homeserver.
110    ///
111    /// - On the sending side, this field is used for re-trying earlier failed
112    ///   transactions. Subsequent messages _must never_ re-use an earlier
113    ///   transaction ID.
114    /// - On the receiving side, the field is used for recognizing our own
115    ///   messages when they arrive down the sync: the server includes the ID in
116    ///   the [`MessageLikeUnsigned`] field `transaction_id` of the
117    ///   corresponding [`SyncMessageLikeEvent`], but only for the _sending_
118    ///   device. Other devices will not see it. This is then used to ignore
119    ///   events sent by our own device and/or to implement local echo.
120    pub fn with_transaction_id(mut self, txn_id: OwnedTransactionId) -> Self {
121        self.transaction_id = Some(txn_id);
122        self
123    }
124
125    /// Assign a given [`RequestConfig`] to configure how this request should
126    /// behave with respect to the network.
127    pub fn with_request_config(mut self, request_config: RequestConfig) -> Self {
128        self.request_config = Some(request_config);
129        self
130    }
131}
132
133impl<'a> IntoFuture for SendMessageLikeEvent<'a> {
134    type Output = Result<SendMessageLikeEventResult>;
135    boxed_into_future!(extra_bounds: 'a);
136
137    fn into_future(self) -> Self::IntoFuture {
138        let Self {
139            room,
140            event_type,
141            content,
142            transaction_id,
143            request_config,
144            #[cfg(feature = "unstable-msc4354")]
145            sticky_duration,
146        } = self;
147        Box::pin(async move {
148            let content = content?;
149            let future =
150                assign!(room.send_raw(&event_type, content), { transaction_id, request_config });
151            #[cfg(feature = "unstable-msc4354")]
152            let future = assign!(future, { sticky_duration });
153            future.await
154        })
155    }
156}
157
158/// Future returned by [`Room::send_raw`].
159#[allow(missing_debug_implementations)]
160pub struct SendRawMessageLikeEvent<'a> {
161    room: &'a Room,
162    event_type: &'a str,
163    content: Raw<AnyMessageLikeEventContent>,
164    tracing_span: Span,
165    transaction_id: Option<OwnedTransactionId>,
166    request_config: Option<RequestConfig>,
167    #[cfg(feature = "unstable-msc4354")]
168    sticky_duration: Option<StickyDurationMs>,
169}
170
171impl<'a> SendRawMessageLikeEvent<'a> {
172    pub(crate) fn new(
173        room: &'a Room,
174        event_type: &'a str,
175        content: impl IntoRawMessageLikeEventContent,
176    ) -> Self {
177        let content = content.into_raw_message_like_event_content();
178        Self {
179            room,
180            event_type,
181            content,
182            tracing_span: Span::current(),
183            transaction_id: None,
184            request_config: None,
185            #[cfg(feature = "unstable-msc4354")]
186            sticky_duration: None,
187        }
188    }
189
190    /// Make this event sticky for `duration`, clamped to one hour.
191    ///
192    /// Note that if the homeserver doesn't support sticky events, it will
193    /// ignore the duration and send the event unsticky. Server support can
194    /// be checked with [`Client::supports_sticky_events`].
195    ///
196    /// [`Client::supports_sticky_events`]: crate::Client::supports_sticky_events
197    #[cfg(feature = "unstable-msc4354")]
198    pub fn with_sticky_duration(mut self, duration: Duration) -> Self {
199        self.sticky_duration = Some(crate::utils::sticky_duration_ms(duration));
200        self
201    }
202
203    /// Set a transaction ID for this event.
204    ///
205    /// Since sending message-like events always requires a transaction ID, one
206    /// is generated if this method is not called.
207    ///
208    /// - On the sending side, this field is used for re-trying earlier failed
209    ///   transactions. Subsequent messages _must never_ re-use an earlier
210    ///   transaction ID.
211    /// - On the receiving side, the field is used for recognizing our own
212    ///   messages when they arrive down the sync: the server includes the ID in
213    ///   the [`MessageLikeUnsigned`] field `transaction_id` of the
214    ///   corresponding [`SyncMessageLikeEvent`], but only for the _sending_
215    ///   device. Other devices will not see it. This is then used to ignore
216    ///   events sent by our own device and/or to implement local echo.
217    pub fn with_transaction_id(mut self, txn_id: &TransactionId) -> Self {
218        self.transaction_id = Some(txn_id.to_owned());
219        self
220    }
221
222    /// Assign a given [`RequestConfig`] to configure how this request should
223    /// behave with respect to the network.
224    pub fn with_request_config(mut self, request_config: RequestConfig) -> Self {
225        self.request_config = Some(request_config);
226        self
227    }
228
229    /// Send the event as a delayed event ([MSC4140]) instead of sending it
230    /// right away.
231    ///
232    /// The homeserver holds on to the event and only distributes it to the room
233    /// once `delay` elapses. Until then the event can be cancelled, restarted
234    /// or sent immediately with [`Client::update_delayed_event`], using the
235    /// `delay_id` from the response.
236    ///
237    /// The event is prepared exactly like an immediate one: in particular it
238    /// is encrypted if the room is encrypted. Note that this happens when the
239    /// event is *scheduled*: the room key is shared with the members of the
240    /// room at that point, so a member joining before the delay elapses won't
241    /// be able to decrypt the event, and restarting the delay does not
242    /// re-encrypt it.
243    ///
244    /// A sticky duration set beforehand with `with_sticky_duration`
245    /// ([MSC4354]) is kept: the event becomes sticky once the homeserver sends
246    /// it.
247    ///
248    /// [MSC4140]: https://github.com/matrix-org/matrix-spec-proposals/pull/4140
249    /// [MSC4354]: https://github.com/matrix-org/matrix-spec-proposals/pull/4354
250    /// [`Client::update_delayed_event`]: crate::Client::update_delayed_event
251    pub fn into_delayed(self, delay: Duration) -> SendDelayedRawMessageLikeEvent<'a> {
252        SendDelayedRawMessageLikeEvent { inner: self, delay }
253    }
254}
255
256impl<'a> IntoFuture for SendRawMessageLikeEvent<'a> {
257    type Output = Result<SendMessageLikeEventResult>;
258    boxed_into_future!(extra_bounds: 'a);
259
260    fn into_future(self) -> Self::IntoFuture {
261        let Self {
262            room,
263            event_type,
264            content,
265            tracing_span,
266            transaction_id,
267            request_config,
268            #[cfg(feature = "unstable-msc4354")]
269            sticky_duration,
270        } = self;
271
272        let fut = async move {
273            let PreparedMessageLikeEvent { txn_id, event_type, content, encryption_info } =
274                prepare_message_like_event(room, event_type, content, transaction_id).await?;
275
276            let request = send_message_event::v3::Request::new_raw(
277                room.room_id().to_owned(),
278                txn_id,
279                event_type.into(),
280                content,
281            );
282            #[cfg(feature = "unstable-msc4354")]
283            let request = assign!(request, { sticky_duration_ms: sticky_duration });
284
285            let response = room.client.send(request).with_request_config(request_config).await?;
286
287            Span::current().record("event_id", tracing::field::debug(&response.event_id));
288            info!("Sent event in room");
289
290            Ok(SendMessageLikeEventResult { response, encryption_info })
291        };
292
293        Box::pin(fut.instrument(tracing_span))
294    }
295}
296
297/// Future returned by [`SendRawMessageLikeEvent::into_delayed`].
298#[allow(missing_debug_implementations)]
299pub struct SendDelayedRawMessageLikeEvent<'a> {
300    inner: SendRawMessageLikeEvent<'a>,
301    delay: Duration,
302}
303
304impl<'a> SendDelayedRawMessageLikeEvent<'a> {
305    /// Set a transaction ID for this event.
306    ///
307    /// See [`SendRawMessageLikeEvent::with_transaction_id`].
308    pub fn with_transaction_id(mut self, txn_id: &TransactionId) -> Self {
309        self.inner = self.inner.with_transaction_id(txn_id);
310        self
311    }
312
313    /// Assign a given [`RequestConfig`] to configure how this request should
314    /// behave with respect to the network.
315    pub fn with_request_config(mut self, request_config: RequestConfig) -> Self {
316        self.inner = self.inner.with_request_config(request_config);
317        self
318    }
319}
320
321impl<'a> IntoFuture for SendDelayedRawMessageLikeEvent<'a> {
322    type Output = Result<send_delayed_event::msc4140::Response>;
323    boxed_into_future!(extra_bounds: 'a);
324
325    fn into_future(self) -> Self::IntoFuture {
326        let Self {
327            inner:
328                SendRawMessageLikeEvent {
329                    room,
330                    event_type,
331                    content,
332                    tracing_span,
333                    transaction_id,
334                    request_config,
335                    #[cfg(feature = "unstable-msc4354")]
336                    sticky_duration,
337                },
338            delay,
339        } = self;
340
341        let fut = async move {
342            let PreparedMessageLikeEvent { txn_id, event_type, content, encryption_info: _ } =
343                prepare_message_like_event(room, event_type, content, transaction_id).await?;
344
345            let request = send_delayed_event::msc4140::Request::new_raw(
346                event_type.into(),
347                room.room_id().to_owned(),
348                txn_id,
349                delay,
350                None,
351                content.cast(),
352            )?;
353            #[cfg(feature = "unstable-msc4354")]
354            let request = assign!(request, { sticky_duration_ms: sticky_duration });
355
356            let response = room.client.send(request).with_request_config(request_config).await?;
357
358            info!(delay_id = response.delay_id, "Scheduled delayed event in room");
359
360            Ok(response)
361        };
362
363        Box::pin(fut.instrument(tracing_span))
364    }
365}
366
367/// A message-like event that is ready to be handed to the homeserver.
368struct PreparedMessageLikeEvent<'a> {
369    txn_id: OwnedTransactionId,
370    /// The type to send, `m.room.encrypted` if the content was encrypted.
371    event_type: &'a str,
372    content: Raw<AnyMessageLikeEventContent>,
373    /// The encryption info, if the content was encrypted.
374    encryption_info: Option<EncryptionInfo>,
375}
376
377/// The steps shared by every way of sending a message-like event: check that
378/// the room is joined, pick a transaction ID, and encrypt the content if the
379/// room is encrypted.
380#[cfg_attr(not(feature = "e2e-encryption"), allow(unused_mut, clippy::unused_async))]
381async fn prepare_message_like_event<'a>(
382    room: &Room,
383    mut event_type: &'a str,
384    mut content: Raw<AnyMessageLikeEventContent>,
385    transaction_id: Option<OwnedTransactionId>,
386) -> Result<PreparedMessageLikeEvent<'a>> {
387    room.ensure_room_joined()?;
388
389    let txn_id = transaction_id.unwrap_or_else(TransactionId::new);
390    Span::current().record("transaction_id", tracing::field::debug(&txn_id));
391
392    #[cfg(not(feature = "e2e-encryption"))]
393    trace!("Sending plaintext event to room because we don't have encryption support.");
394
395    #[cfg(feature = "e2e-encryption")]
396    let mut encryption_info: Option<EncryptionInfo> = None;
397    #[cfg(not(feature = "e2e-encryption"))]
398    let encryption_info: Option<EncryptionInfo> = None;
399
400    #[cfg(feature = "e2e-encryption")]
401    if room.latest_encryption_state().await?.is_encrypted() {
402        Span::current().record("is_room_encrypted", true);
403        // Reactions are currently famously not encrypted, skip encrypting
404        // them until they are.
405        if MessageLikeEventType::from(event_type) == MessageLikeEventType::Reaction {
406            trace!("Sending plaintext event because of the event type.");
407        } else {
408            trace!(
409                room_id = ?room.room_id(),
410                "Sending encrypted event because the room is encrypted.",
411            );
412
413            ensure_room_encryption_ready(room).await?;
414
415            let olm = room.client.olm_machine().await;
416            let olm = olm.as_ref().expect("Olm machine wasn't started");
417
418            let result = olm.encrypt_room_event_raw(room.room_id(), event_type, &content).await?;
419            content = result.content.cast();
420            encryption_info = Some(result.encryption_info);
421            event_type = "m.room.encrypted";
422        }
423    } else {
424        Span::current().record("is_room_encrypted", false);
425        trace!("Sending plaintext event because the room is NOT encrypted.");
426    }
427
428    Ok(PreparedMessageLikeEvent { txn_id, event_type, content, encryption_info })
429}
430
431/// Future returned by [`Room::send_attachment`].
432#[allow(missing_debug_implementations)]
433pub struct SendAttachment<'a> {
434    room: &'a Room,
435    filename: String,
436    content_type: &'a Mime,
437    data: Vec<u8>,
438    config: AttachmentConfig,
439    tracing_span: Span,
440    send_progress: SharedObservable<TransmissionProgress>,
441    store_in_cache: bool,
442}
443
444impl<'a> SendAttachment<'a> {
445    pub(crate) fn new(
446        room: &'a Room,
447        filename: String,
448        content_type: &'a Mime,
449        data: Vec<u8>,
450        config: AttachmentConfig,
451    ) -> Self {
452        Self {
453            room,
454            filename,
455            content_type,
456            data,
457            config,
458            tracing_span: Span::current(),
459            send_progress: Default::default(),
460            store_in_cache: false,
461        }
462    }
463
464    /// Replace the default `SharedObservable` used for tracking upload
465    /// progress.
466    pub fn with_send_progress_observable(
467        mut self,
468        send_progress: SharedObservable<TransmissionProgress>,
469    ) -> Self {
470        self.send_progress = send_progress;
471        self
472    }
473
474    /// Whether the sent attachment should be stored in the cache or not.
475    ///
476    /// If set to true, then retrieving the data for the attachment will result
477    /// in a cache hit immediately after upload.
478    pub fn store_in_cache(mut self) -> Self {
479        self.store_in_cache = true;
480        self
481    }
482}
483
484impl<'a> IntoFuture for SendAttachment<'a> {
485    type Output = Result<send_message_event::v3::Response>;
486    boxed_into_future!(extra_bounds: 'a);
487
488    fn into_future(self) -> Self::IntoFuture {
489        let Self {
490            room,
491            filename,
492            content_type,
493            data,
494            config,
495            tracing_span,
496            send_progress,
497            store_in_cache,
498        } = self;
499        let fut = async move {
500            room.prepare_and_send_attachment(
501                filename,
502                content_type,
503                data,
504                config,
505                send_progress,
506                store_in_cache,
507            )
508            .await
509        };
510
511        Box::pin(fut.instrument(tracing_span))
512    }
513}
514
515/// Future returned by [`Room::send_state_event_raw`].
516#[allow(missing_debug_implementations)]
517pub struct SendRawStateEvent<'a> {
518    room: &'a Room,
519    event_type: &'a str,
520    state_key: &'a str,
521    content: Raw<AnyStateEventContent>,
522    tracing_span: Span,
523    request_config: Option<RequestConfig>,
524}
525
526impl<'a> SendRawStateEvent<'a> {
527    pub(crate) fn new(
528        room: &'a Room,
529        event_type: &'a str,
530        state_key: &'a str,
531        content: impl IntoRawStateEventContent,
532    ) -> Self {
533        let content = content.into_raw_state_event_content();
534        Self {
535            room,
536            event_type,
537            state_key,
538            content,
539            tracing_span: Span::current(),
540            request_config: None,
541        }
542    }
543
544    /// Assign a given [`RequestConfig`] to configure how this request should
545    /// behave with respect to the network.
546    pub fn with_request_config(mut self, request_config: RequestConfig) -> Self {
547        self.request_config = Some(request_config);
548        self
549    }
550
551    /// Send the state event as a delayed event ([MSC4140]) instead of sending
552    /// it right away.
553    ///
554    /// The homeserver holds on to the event and only distributes it to the room
555    /// once `delay` elapses. Until then the event can be cancelled, restarted
556    /// or sent immediately with [`Client::update_delayed_event`], using the
557    /// `delay_id` from the response.
558    ///
559    /// The event is prepared exactly like an immediate one: in particular,
560    /// with the `experimental-encrypted-state-events` feature, it is encrypted
561    /// if the room has encrypted state. Note that this happens when the event
562    /// is *scheduled*, and restarting the delay does not re-encrypt it.
563    ///
564    /// [MSC4140]: https://github.com/matrix-org/matrix-spec-proposals/pull/4140
565    /// [`Client::update_delayed_event`]: crate::Client::update_delayed_event
566    pub fn into_delayed(self, delay: Duration) -> SendDelayedRawStateEvent<'a> {
567        SendDelayedRawStateEvent { inner: self, delay }
568    }
569
570    /// Determines whether the inner state event should be encrypted before
571    /// sending.
572    ///
573    /// This method checks two conditions:
574    ///
575    /// 1. Whether the room supports encrypted state events, by inspecting the
576    ///    room's encryption state.
577    /// 2. Whether the event type is considered "critical" or excluded from
578    ///    encryption under MSC4362.
579    ///
580    /// # Returns
581    ///
582    /// Returns `true` if the event should be encrypted, otherwise returns
583    /// `false`.
584    #[cfg(feature = "experimental-encrypted-state-events")]
585    fn should_encrypt(room: &Room, event_type: &str) -> bool {
586        if !room.encryption_state().is_state_encrypted() {
587            trace!("Sending plaintext event as the room does NOT support encrypted state events.");
588            return false;
589        }
590
591        // Check the event is not critical.
592        if matches!(
593            StateEventType::from(event_type),
594            StateEventType::RoomCreate
595                | StateEventType::RoomMember
596                | StateEventType::RoomJoinRules
597                | StateEventType::RoomPowerLevels
598                | StateEventType::RoomThirdPartyInvite
599                | StateEventType::RoomHistoryVisibility
600                | StateEventType::RoomGuestAccess
601                | StateEventType::RoomEncryption
602                | StateEventType::SpaceChild
603                | StateEventType::SpaceParent
604        ) {
605            trace!("Sending plaintext event as its type is excluded from encryption.");
606            return false;
607        }
608
609        true
610    }
611}
612
613impl<'a> IntoFuture for SendRawStateEvent<'a> {
614    type Output = Result<send_state_event::v3::Response>;
615    boxed_into_future!(extra_bounds: 'a);
616
617    fn into_future(self) -> Self::IntoFuture {
618        let Self { room, event_type, state_key, content, tracing_span, request_config } = self;
619
620        let fut = async move {
621            let PreparedStateEvent { event_type, state_key, content } =
622                prepare_state_event(room, event_type, state_key, content).await?;
623
624            let request = send_state_event::v3::Request::new_raw(
625                room.room_id().to_owned(),
626                event_type.into(),
627                state_key,
628                content,
629            );
630
631            let response = room.client.send(request).with_request_config(request_config).await?;
632
633            Span::current().record("event_id", tracing::field::debug(&response.event_id));
634            info!("Sent event in room");
635
636            Ok(response)
637        };
638
639        Box::pin(fut.instrument(tracing_span))
640    }
641}
642
643/// Future returned by [`SendRawStateEvent::into_delayed`].
644#[allow(missing_debug_implementations)]
645pub struct SendDelayedRawStateEvent<'a> {
646    inner: SendRawStateEvent<'a>,
647    delay: Duration,
648}
649
650impl<'a> SendDelayedRawStateEvent<'a> {
651    /// Assign a given [`RequestConfig`] to configure how this request should
652    /// behave with respect to the network.
653    pub fn with_request_config(mut self, request_config: RequestConfig) -> Self {
654        self.inner = self.inner.with_request_config(request_config);
655        self
656    }
657}
658
659impl<'a> IntoFuture for SendDelayedRawStateEvent<'a> {
660    type Output = Result<send_delayed_event::msc4140::Response>;
661    boxed_into_future!(extra_bounds: 'a);
662
663    fn into_future(self) -> Self::IntoFuture {
664        let Self {
665            inner:
666                SendRawStateEvent { room, event_type, state_key, content, tracing_span, request_config },
667            delay,
668        } = self;
669
670        let fut = async move {
671            let PreparedStateEvent { event_type, state_key, content } =
672                prepare_state_event(room, event_type, state_key, content).await?;
673
674            let request = send_delayed_event::msc4140::Request::new_raw(
675                event_type.into(),
676                room.room_id().to_owned(),
677                TransactionId::new(),
678                delay,
679                Some(state_key),
680                content.cast(),
681            )?;
682
683            let response = room.client.send(request).with_request_config(request_config).await?;
684
685            info!(delay_id = response.delay_id, "Scheduled delayed state event in room");
686
687            Ok(response)
688        };
689
690        Box::pin(fut.instrument(tracing_span))
691    }
692}
693
694/// A state event that is ready to be handed to the homeserver.
695struct PreparedStateEvent<'a> {
696    /// The type to send, `m.room.encrypted` if the content was encrypted.
697    event_type: &'a str,
698    /// The state key to send, prefixed with the original event type if the
699    /// content was encrypted.
700    state_key: String,
701    content: Raw<AnyStateEventContent>,
702}
703
704/// The steps shared by every way of sending a state event: check that the
705/// room is joined, and encrypt the content if the room has encrypted state.
706#[cfg_attr(
707    not(feature = "experimental-encrypted-state-events"),
708    allow(unused_mut, clippy::unused_async)
709)]
710async fn prepare_state_event<'a>(
711    room: &Room,
712    mut event_type: &'a str,
713    state_key: &str,
714    mut content: Raw<AnyStateEventContent>,
715) -> Result<PreparedStateEvent<'a>> {
716    room.ensure_room_joined()?;
717
718    let mut state_key = state_key.to_owned();
719
720    #[cfg(feature = "experimental-encrypted-state-events")]
721    if SendRawStateEvent::should_encrypt(room, event_type) {
722        use tracing::debug;
723
724        Span::current().record("should_encrypt", true);
725        debug!(
726            room_id = ?room.room_id(),
727            "Sending encrypted event because the room is encrypted.",
728        );
729
730        ensure_room_encryption_ready(room).await?;
731
732        let olm = room.client.olm_machine().await;
733        let olm = olm.as_ref().expect("Olm machine wasn't started");
734
735        content = olm
736            .encrypt_state_event_raw(room.room_id(), event_type, &state_key, &content)
737            .await?
738            .cast_unchecked();
739
740        state_key = format!("{event_type}:{state_key}");
741        event_type = "m.room.encrypted";
742    } else {
743        Span::current().record("should_encrypt", false);
744    }
745
746    Ok(PreparedStateEvent { event_type, state_key, content })
747}
748
749/// Future returned by `Room::send_state_event`.
750#[allow(missing_debug_implementations)]
751#[cfg(feature = "experimental-encrypted-state-events")]
752pub struct SendStateEvent<'a> {
753    room: &'a Room,
754    event_type: String,
755    state_key: String,
756    content: serde_json::Result<serde_json::Value>,
757    request_config: Option<RequestConfig>,
758}
759
760#[cfg(feature = "experimental-encrypted-state-events")]
761impl<'a> SendStateEvent<'a> {
762    pub(crate) fn new<C, K>(room: &'a Room, state_key: &K, content: C) -> Self
763    where
764        C: StateEventContent,
765        C::StateKey: Borrow<K>,
766        K: AsRef<str> + ?Sized,
767    {
768        let event_type = content.event_type().to_string();
769        let state_key = state_key.as_ref().to_owned();
770        let content = serde_json::to_value(&content);
771        Self { room, event_type, state_key, content, request_config: None }
772    }
773
774    /// Assign a given [`RequestConfig`] to configure how this request should
775    /// behave with respect to the network.
776    pub fn with_request_config(mut self, request_config: RequestConfig) -> Self {
777        self.request_config = Some(request_config);
778        self
779    }
780}
781
782#[cfg(feature = "experimental-encrypted-state-events")]
783impl<'a> IntoFuture for SendStateEvent<'a> {
784    type Output = Result<send_state_event::v3::Response>;
785    boxed_into_future!(extra_bounds: 'a);
786
787    fn into_future(self) -> Self::IntoFuture {
788        let Self { room, state_key, event_type, content, request_config } = self;
789        Box::pin(async move {
790            let content = content?;
791            assign!(room.send_state_event_raw(&event_type, &state_key, content), { request_config })
792                .await
793        })
794    }
795}
796
797/// Ensures the room is ready for encrypted events to be sent.
798#[cfg(feature = "e2e-encryption")]
799async fn ensure_room_encryption_ready(room: &Room) -> Result<()> {
800    if !room.are_members_synced() {
801        room.sync_members().await?;
802    }
803
804    // Query keys in case we don't have them for newly synced members.
805    //
806    // Note we do it all the time, because we might have sync'd members before
807    // sending a message (so didn't enter the above branch), but could have not
808    // query their keys ever.
809    room.query_keys_for_untracked_or_dirty_users().await?;
810
811    room.preshare_room_key().await?;
812
813    Ok(())
814}