matrix_ui_serializable/models/
async_requests.rs

1use matrix_sdk::{
2    OwnedServerName, RoomMemberships,
3    media::MediaRequestParameters,
4    room::{
5        RoomMember,
6        edit::EditedContent,
7        reply::{EnforceThread, Reply},
8    },
9    ruma::{
10        OwnedEventId, OwnedRoomAliasId, OwnedRoomId, OwnedUserId,
11        events::room::message::{ReplyWithinThread, RoomMessageEventContent},
12        matrix_uri::MatrixId,
13    },
14};
15use matrix_sdk_ui::timeline::TimelineEventItemId;
16use serde::{Deserialize, Deserializer};
17use serde_json::Value;
18use tokio::sync::oneshot;
19
20use crate::{events::timeline::PaginationDirection, init::singletons::REQUEST_SENDER};
21
22/// Submits a request to the worker thread to be executed asynchronously.
23pub(crate) fn submit_async_request(req: MatrixRequest) {
24    REQUEST_SENDER
25        .get()
26        .unwrap() // this is initialized
27        .send(req)
28        .expect("BUG: async worker task receiver has died!");
29}
30
31/// The set of requests for async work that can be made to the worker thread.
32#[allow(clippy::large_enum_variant)]
33pub enum MatrixRequest {
34    /// Request to paginate the older (or newer) events of a room's timeline.
35    PaginateRoomTimeline {
36        room_id: OwnedRoomId,
37        /// The maximum number of timeline events to fetch in each pagination batch.
38        num_events: u16,
39        direction: PaginationDirection,
40    },
41    /// Request to edit the content of an event in the given room's timeline.
42    EditMessage {
43        room_id: OwnedRoomId,
44        timeline_event_item_id: TimelineEventItemId,
45        edited_content: EditedContent,
46    },
47    /// Request to fetch the full details of the given event in the given room's timeline.
48    FetchDetailsForEvent {
49        room_id: OwnedRoomId,
50        event_id: OwnedEventId,
51    },
52    /// Request to fetch profile information for all members of a room.
53    /// This can be *very* slow depending on the number of members in the room.
54    SyncRoomMemberList {
55        room_id: OwnedRoomId,
56    },
57    /// Request to join the given room.
58    JoinRoom {
59        room_id: OwnedRoomId,
60    },
61    /// Request to leave the given room.
62    LeaveRoom {
63        room_id: OwnedRoomId,
64    },
65    /// Request to get the actual list of members in a room.
66    /// This returns the list of members that can be displayed in the UI.
67    GetRoomMembers {
68        room_id: OwnedRoomId,
69        memberships: RoomMemberships,
70        /// * If `true` (not recommended), only the local cache will be accessed.
71        /// * If `false` (recommended), details will be fetched from the server.
72        local_only: bool,
73    },
74    /// Request to fetch profile information for the given user ID.
75    GetUserProfile {
76        user_id: OwnedUserId,
77        /// * If `Some`, the user is known to be a member of a room, so this will
78        ///   fetch the user's profile from that room's membership info.
79        /// * If `None`, the user's profile info will be fetched from the server
80        ///   in a room-agnostic manner, and no room membership info will be returned.
81        room_id: Option<OwnedRoomId>,
82        /// * If `true` (not recommended), only the local cache will be accessed.
83        /// * If `false` (recommended), details will be fetched from the server.
84        local_only: bool,
85    },
86    /// Request to fetch the number of unread messages in the given room.
87    GetNumberUnreadMessages {
88        room_id: OwnedRoomId,
89    },
90    /// Request to ignore/block or unignore/unblock a user.
91    IgnoreUser {
92        /// Whether to ignore (`true`) or unignore (`false`) the user.
93        ignore: bool,
94        /// The room membership info of the user to (un)ignore.
95        room_member: RoomMember,
96        /// The room ID of the room where the user is a member,
97        /// which is only needed because it isn't present in the `RoomMember` object.
98        room_id: OwnedRoomId,
99    },
100    /// Request to resolve a room alias into a room ID and the servers that know about that room.
101    ResolveRoomAlias(OwnedRoomAliasId),
102    /// Request to fetch media from the server.
103    /// Upon completion of the async media request, the `on_fetched` function
104    /// will be invoked with four arguments: the `destination`, the `media_request`,
105    /// the result of the media fetch, and the `update_sender`.
106    FetchMedia {
107        media_request: MediaRequestParameters,
108        content_sender: oneshot::Sender<Result<Vec<u8>, matrix_sdk::Error>>,
109    },
110    /// Request to send a message to the given room.
111    SendMessage {
112        room_id: OwnedRoomId,
113        message: RoomMessageEventContent,
114        replied_to: Option<Reply>,
115    },
116    /// Sends a notice to the given room that the current user is or is not typing.
117    ///
118    /// This request does not return a response or notify the UI thread, and
119    /// furthermore, there is no need to send a follow-up request to stop typing
120    /// (though you certainly can do so).
121    SendTypingNotice {
122        room_id: OwnedRoomId,
123        typing: bool,
124    },
125    /// Subscribe to typing notices for the given room.
126    ///
127    /// This request does not return a response or notify the UI thread.
128    SubscribeToTypingNotices {
129        room_id: OwnedRoomId,
130        /// Whether to subscribe or unsubscribe from typing notices for this room.
131        subscribe: bool,
132    },
133    /// Subscribe to changes in the read receipts of our own user.
134    ///
135    /// This request does not return a response or notify the UI thread.
136    SubscribeToOwnUserReadReceiptsChanged {
137        room_id: OwnedRoomId,
138        /// Whether to subscribe or unsubscribe to changes in the read receipts of our own user for this room
139        subscribe: bool,
140    },
141    /// Sends a read receipt for the given event in the given room.
142    ReadReceipt {
143        room_id: OwnedRoomId,
144        event_id: OwnedEventId,
145    },
146    /// Sends a fully-read receipt for the given event in the given room.
147    FullyReadReceipt {
148        room_id: OwnedRoomId,
149        event_id: OwnedEventId,
150    },
151    /// Sends a request to obtain the power levels for this room.
152    ///
153    /// The response is delivered back to the main UI thread via [`TimelineUpdate::UserPowerLevels`].
154    GetRoomPowerLevels {
155        room_id: OwnedRoomId,
156    },
157    /// Toggles the given reaction to the given event in the given room.
158    ToggleReaction {
159        room_id: OwnedRoomId,
160        timeline_event_id: TimelineEventItemId,
161        reaction: String,
162    },
163    /// Redacts (deletes) the given event in the given room.
164    #[doc(alias("delete"))]
165    RedactMessage {
166        room_id: OwnedRoomId,
167        timeline_event_id: TimelineEventItemId,
168        reason: Option<String>,
169    },
170    /// Sends a request to obtain the room's pill link info for the given Matrix ID.
171    ///
172    /// The MatrixLinkPillInfo::Loaded variant is sent back to the main UI thread via.
173    GetMatrixRoomLinkPillInfo {
174        matrix_id: MatrixId,
175        via: Vec<OwnedServerName>,
176    },
177    CreateDMRoom {
178        user_id: OwnedUserId,
179    },
180}
181// Deserialize trait is implemented in models/async_requests.rs
182
183impl<'de> Deserialize<'de> for MatrixRequest {
184    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
185    where
186        D: Deserializer<'de>,
187    {
188        // First deserialize into a generic Value to inspect the structure
189        let value = Value::deserialize(deserializer)?;
190
191        // Extract the "event" field to determine the variant
192        let event = value
193            .get("event")
194            .and_then(|v| v.as_str())
195            .ok_or_else(|| serde::de::Error::missing_field("event"))?;
196
197        // Extract the "payload" field containing the variant data
198        let payload = value
199            .get("payload")
200            .ok_or_else(|| serde::de::Error::missing_field("payload"))?;
201
202        // Match on the event type and deserialize the appropriate variant
203        match event {
204            "paginateRoomTimeline" => {
205                let data: PaginateRoomTimelinePayload =
206                    serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
207                Ok(MatrixRequest::PaginateRoomTimeline {
208                    room_id: data.room_id,
209                    num_events: data.num_events,
210                    direction: data.direction,
211                })
212            }
213            // "editMessage" => {
214            //     let data: EditMessagePayload =
215            //         serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
216            //     Ok(MatrixRequest::EditMessage {
217            //         room_id: data.room_id,
218            //         timeline_event_item_id: data.timeline_event_item_id,
219            //         edited_content: data.edited_content,
220            //     })
221            // }
222            "fetchDetailsForEvent" => {
223                let data: FetchDetailsForEventPayload =
224                    serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
225                Ok(MatrixRequest::FetchDetailsForEvent {
226                    room_id: data.room_id,
227                    event_id: data.event_id,
228                })
229            }
230            // "syncRoomMemberList" => {
231            //     let data: SyncRoomMemberListPayload =
232            //         serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
233            //     Ok(MatrixRequest::SyncRoomMemberList {
234            //         room_id: data.room_id,
235            //     })
236            // }
237            "joinRoom" => {
238                let data: JoinRoomPayload =
239                    serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
240                Ok(MatrixRequest::JoinRoom {
241                    room_id: data.room_id,
242                })
243            }
244            "leaveRoom" => {
245                let data: LeaveRoomPayload =
246                    serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
247                Ok(MatrixRequest::LeaveRoom {
248                    room_id: data.room_id,
249                })
250            }
251            // "getRoomMembers" => {
252            //     let data: GetRoomMembersPayload =
253            //         serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
254            //     Ok(MatrixRequest::GetRoomMembers {
255            //         room_id: data.room_id,
256            //         memberships: data.memberships,
257            //         local_only: data.local_only,
258            //     })
259            // }
260            "getUserProfile" => {
261                let data: GetUserProfilePayload =
262                    serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
263                Ok(MatrixRequest::GetUserProfile {
264                    user_id: data.user_id,
265                    room_id: data.room_id,
266                    local_only: data.local_only,
267                })
268            }
269            "getNumberUnreadMessages" => {
270                let data: GetNumberUnreadMessagesPayload =
271                    serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
272                Ok(MatrixRequest::GetNumberUnreadMessages {
273                    room_id: data.room_id,
274                })
275            }
276            // "ignoreUser" => {
277            //     let data: IgnoreUserPayload =
278            //         serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
279            //     Ok(MatrixRequest::IgnoreUser {
280            //         ignore: data.ignore,
281            //         room_member: data.room_member,
282            //         room_id: data.room_id,
283            //     })
284            // }
285            "resolveRoomAlias" => {
286                let alias =
287                    serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
288                Ok(MatrixRequest::ResolveRoomAlias(alias))
289            }
290            "sendMessage" => {
291                let data: SendMessagePayload =
292                    serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
293                let reply_option = match data.reply_to_event_id {
294                    Some(id) => Some(Reply {
295                        event_id: id,
296                        enforce_thread: EnforceThread::Threaded(ReplyWithinThread::Yes),
297                    }),
298                    None => None,
299                };
300                Ok(MatrixRequest::SendMessage {
301                    room_id: data.room_id,
302                    message: data.message,
303                    replied_to: reply_option,
304                })
305            }
306            "sendTypingNotice" => {
307                let data: SendTypingNoticePayload =
308                    serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
309                Ok(MatrixRequest::SendTypingNotice {
310                    room_id: data.room_id,
311                    typing: data.typing,
312                })
313            }
314            "subscribeToTypingNotices" => {
315                let data: SubscribeToTypingNoticesPayload =
316                    serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
317                Ok(MatrixRequest::SubscribeToTypingNotices {
318                    room_id: data.room_id,
319                    subscribe: data.subscribe,
320                })
321            }
322            "subscribeToOwnUserReadReceiptsChanged" => {
323                let data: SubscribeToOwnUserReadReceiptsChangedPayload =
324                    serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
325                Ok(MatrixRequest::SubscribeToOwnUserReadReceiptsChanged {
326                    room_id: data.room_id,
327                    subscribe: data.subscribe,
328                })
329            }
330            "readReceipt" => {
331                let data: ReadReceiptPayload =
332                    serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
333                Ok(MatrixRequest::ReadReceipt {
334                    room_id: data.room_id,
335                    event_id: data.event_id,
336                })
337            }
338            "fullyReadReceipt" => {
339                let data: FullyReadReceiptPayload =
340                    serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
341                Ok(MatrixRequest::FullyReadReceipt {
342                    room_id: data.room_id,
343                    event_id: data.event_id,
344                })
345            }
346            "getRoomPowerLevels" => {
347                let data: GetRoomPowerLevelsPayload =
348                    serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
349                Ok(MatrixRequest::GetRoomPowerLevels {
350                    room_id: data.room_id,
351                })
352            }
353            "toggleReaction" => {
354                let data: ToggleReactionPayload =
355                    serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
356                Ok(MatrixRequest::ToggleReaction {
357                    room_id: data.room_id,
358                    timeline_event_id: TimelineEventItemId::EventId(
359                        OwnedEventId::try_from(data.timeline_event_id)
360                            .expect("Frontend sent incorrect event id"),
361                    ), // We only use eventId, not transactions.
362                    reaction: data.reaction,
363                })
364            }
365            // "redactMessage" => {
366            //     let data: RedactMessagePayload =
367            //         serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
368            //     Ok(MatrixRequest::RedactMessage {
369            //         room_id: data.room_id,
370            //         timeline_event_id: data.timeline_event_id,
371            //         reason: data.reason,
372            //     })
373            // }
374            // "getMatrixRoomLinkPillInfo" => {
375            //     let data: GetMatrixRoomLinkPillInfoPayload =
376            //         serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
377            //     Ok(MatrixRequest::GetMatrixRoomLinkPillInfo {
378            //         matrix_id: data.matrix_id,
379            //         via: data.via,
380            //     })
381            // }
382            "createDMRoom" => {
383                let data: CreateDMRoomPayload =
384                    serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
385                Ok(MatrixRequest::CreateDMRoom {
386                    user_id: data.user_id,
387                })
388            }
389            _ => Err(serde::de::Error::unknown_variant(
390                event,
391                &[
392                    "paginateRoomTimeline",
393                    // "editMessage",
394                    "fetchDetailsForEvent",
395                    // "syncRoomMemberList",
396                    "joinRoom",
397                    "leaveRoom",
398                    // "getRoomMembers",
399                    "getUserProfile",
400                    "getNumberUnreadMessages",
401                    // "ignoreUser",
402                    "resolveRoomAlias",
403                    "sendMessage",
404                    "sendTypingNotice",
405                    "subscribeToTypingNotices",
406                    "subscribeToOwnUserReadReceiptsChanged",
407                    "readReceipt",
408                    "fullyReadReceipt",
409                    "getRoomPowerLevels",
410                    "toggleReaction",
411                    // "redactMessage",
412                    // "getMatrixRoomLinkPillInfo",
413                    "createDMRoom",
414                ],
415            )),
416        }
417    }
418}
419
420// Helper structs for deserializing payloads
421#[derive(Deserialize)]
422#[serde(rename_all = "camelCase")]
423struct PaginateRoomTimelinePayload {
424    room_id: OwnedRoomId,
425    num_events: u16,
426    direction: PaginationDirection,
427}
428
429// #[derive(Deserialize)]
430// #[serde(rename_all = "camelCase")]
431// struct EditMessagePayload {
432//     room_id: OwnedRoomId,
433//     timeline_event_item_id: TimelineEventItemId,
434//     edited_content: EditedContent,
435// }
436
437#[derive(Deserialize)]
438#[serde(rename_all = "camelCase")]
439struct FetchDetailsForEventPayload {
440    room_id: OwnedRoomId,
441    event_id: OwnedEventId,
442}
443
444// #[derive(Deserialize)]
445// #[serde(rename_all = "camelCase")]
446// struct SyncRoomMemberListPayload {
447//     room_id: OwnedRoomId,
448// }
449
450#[derive(Deserialize)]
451#[serde(rename_all = "camelCase")]
452struct JoinRoomPayload {
453    room_id: OwnedRoomId,
454}
455
456#[derive(Deserialize)]
457#[serde(rename_all = "camelCase")]
458struct LeaveRoomPayload {
459    room_id: OwnedRoomId,
460}
461
462// #[derive(Deserialize)]
463// #[serde(rename_all = "camelCase")]
464// struct GetRoomMembersPayload {
465//     room_id: OwnedRoomId,
466//     memberships: RoomMemberships,
467//     local_only: bool,
468// }
469
470#[derive(Deserialize)]
471#[serde(rename_all = "camelCase")]
472struct GetUserProfilePayload {
473    user_id: OwnedUserId,
474    room_id: Option<OwnedRoomId>,
475    local_only: bool,
476}
477
478#[derive(Deserialize)]
479#[serde(rename_all = "camelCase")]
480struct GetNumberUnreadMessagesPayload {
481    room_id: OwnedRoomId,
482}
483
484// #[derive(Deserialize)]
485// #[serde(rename_all = "camelCase")]
486// struct IgnoreUserPayload {
487//     ignore: bool,
488//     room_member: RoomMember,
489//     room_id: OwnedRoomId,
490// }
491
492#[derive(Deserialize)]
493#[serde(rename_all = "camelCase")]
494struct SendMessagePayload {
495    room_id: OwnedRoomId,
496    message: RoomMessageEventContent,
497    reply_to_event_id: Option<OwnedEventId>,
498}
499
500#[derive(Deserialize)]
501#[serde(rename_all = "camelCase")]
502struct SendTypingNoticePayload {
503    room_id: OwnedRoomId,
504    typing: bool,
505}
506
507#[derive(Deserialize)]
508#[serde(rename_all = "camelCase")]
509struct SubscribeToTypingNoticesPayload {
510    room_id: OwnedRoomId,
511    subscribe: bool,
512}
513
514#[derive(Deserialize)]
515#[serde(rename_all = "camelCase")]
516struct SubscribeToOwnUserReadReceiptsChangedPayload {
517    room_id: OwnedRoomId,
518    subscribe: bool,
519}
520
521#[derive(Deserialize)]
522#[serde(rename_all = "camelCase")]
523struct ReadReceiptPayload {
524    room_id: OwnedRoomId,
525    event_id: OwnedEventId,
526}
527
528#[derive(Deserialize)]
529#[serde(rename_all = "camelCase")]
530struct FullyReadReceiptPayload {
531    room_id: OwnedRoomId,
532    event_id: OwnedEventId,
533}
534
535#[derive(Deserialize)]
536#[serde(rename_all = "camelCase")]
537struct GetRoomPowerLevelsPayload {
538    room_id: OwnedRoomId,
539}
540
541#[derive(Deserialize)]
542#[serde(rename_all = "camelCase")]
543struct ToggleReactionPayload {
544    room_id: OwnedRoomId,
545    timeline_event_id: String,
546    reaction: String,
547}
548
549// #[derive(Deserialize)]
550// #[serde(rename_all = "camelCase")]
551// struct RedactMessagePayload {
552//     room_id: OwnedRoomId,
553//     timeline_event_id: TimelineEventItemId,
554//     reason: Option<String>,
555// }
556
557// #[derive(Deserialize)]
558// #[serde(rename_all = "camelCase")]
559// struct GetMatrixRoomLinkPillInfoPayload {
560//     matrix_id: MatrixId,
561//     via: Vec<OwnedServerName>,
562// }
563
564#[derive(Deserialize)]
565#[serde(rename_all = "camelCase")]
566struct CreateDMRoomPayload {
567    user_id: OwnedUserId,
568}