matrix_sdk_base/response_processors/room/msc4186/
mod.rs1pub mod extensions;
16
17use std::collections::BTreeMap;
18#[cfg(feature = "e2e-encryption")]
19use std::collections::BTreeSet;
20
21#[cfg(feature = "e2e-encryption")]
22use matrix_sdk_common::deserialized_responses::TimelineEvent;
23use matrix_sdk_common::timer;
24use ruma::{
25 JsOption, OwnedRoomId, RoomId, UserId,
26 api::client::sync::sync_events::{
27 v3::{InviteState, InvitedRoom, KnockState, KnockedRoom},
28 v5 as http,
29 },
30 assign,
31 events::{
32 AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncStateEvent,
33 room::member::{MembershipState, RoomMemberEventContent},
34 },
35 serde::Raw,
36};
37use tokio::sync::broadcast::Sender;
38
39#[cfg(feature = "e2e-encryption")]
40use super::super::e2ee;
41use super::{
42 super::{Context, notification, state_events, timeline},
43 RoomCreationData,
44};
45#[cfg(feature = "e2e-encryption")]
46use crate::StateChanges;
47use crate::{
48 Result, Room, RoomHero, RoomInfo, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons,
49 RoomState,
50 store::BaseStateStore,
51 sync::{InvitedRoomUpdate, JoinedRoomUpdate, KnockedRoomUpdate, LeftRoomUpdate, State},
52};
53
54pub enum RoomUpdateKind {
56 Joined(JoinedRoomUpdate),
57 Left(LeftRoomUpdate),
58 Invited(InvitedRoomUpdate),
59 Knocked(KnockedRoomUpdate),
60}
61
62pub async fn update_any_room(
63 context: &mut Context,
64 user_id: &UserId,
65 room_creation_data: RoomCreationData<'_>,
66 room_response: &http::response::Room,
67 rooms_account_data: &BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
68 #[cfg(feature = "e2e-encryption")] e2ee: e2ee::E2EE<'_>,
69 notification: notification::Notification<'_>,
70) -> Result<Option<(RoomInfo, RoomUpdateKind)>> {
71 let _timer = timer!(tracing::Level::TRACE, "update_any_room");
72
73 let RoomCreationData {
74 room_id,
75 room_info_notable_update_sender,
76 requested_required_states,
77 ambiguity_cache,
78 } = room_creation_data;
79
80 let state = State::from_msc4186(room_response.required_state.clone());
86 let (raw_state_events, state_events) = state.collect(&[]);
87
88 let state_store = notification.state_store;
89
90 let is_new_room = !state_store.room_exists(room_id);
92
93 let invite_state_events =
94 room_response.invite_state.as_ref().map(|events| state_events::stripped::collect(events));
95
96 #[allow(unused_mut)] let (mut room, mut room_info, maybe_room_update_kind) = membership(
98 context,
99 &state_events,
100 &invite_state_events,
101 state_store,
102 user_id,
103 room_id,
104 room_info_notable_update_sender,
105 );
106
107 room_info.mark_state_partially_synced();
108 room_info.handle_encryption_state(requested_required_states.for_room(room_id));
109
110 #[cfg(feature = "e2e-encryption")]
111 let mut new_user_ids = BTreeSet::new();
112
113 #[cfg(not(feature = "e2e-encryption"))]
114 let mut new_user_ids = ();
115
116 state_events::sync::dispatch(
117 context,
118 (&raw_state_events, &state_events),
119 &mut room_info,
120 ambiguity_cache,
121 &mut new_user_ids,
122 state_store,
123 #[cfg(feature = "experimental-encrypted-state-events")]
124 e2ee.clone(),
125 )
126 .await?;
127
128 if let Some((raw_events, events)) = invite_state_events {
130 state_events::stripped::dispatch_invite_or_knock(
131 context,
132 (&raw_events, &events),
133 &room,
134 &mut room_info,
135 notification::Notification::new(
136 notification.push_rules,
137 notification.notifications,
138 notification.state_store,
139 ),
140 )
141 .await?;
142 }
143
144 properties(context, room_id, room_response, &mut room_info, is_new_room);
145
146 let timeline = timeline::build(
147 context,
148 &room,
149 &mut room_info,
150 timeline::builder::Timeline::from(room_response),
151 notification,
152 #[cfg(feature = "e2e-encryption")]
153 e2ee.clone(),
154 )
155 .await?;
156
157 #[cfg(feature = "e2e-encryption")]
160 cache_latest_events(
161 &room,
162 &mut room_info,
163 &timeline.events,
164 Some(&context.state_changes),
165 Some(state_store),
166 )
167 .await;
168
169 #[cfg(feature = "e2e-encryption")]
170 e2ee::tracked_users::update_or_set_if_room_is_newly_encrypted(
171 e2ee.olm_machine,
172 &new_user_ids,
173 room_info.encryption_state(),
174 room.encryption_state(),
175 room_id,
176 state_store,
177 )
178 .await?;
179
180 let notification_count = room_response.unread_notifications.clone().into();
181 room_info.update_notification_count(notification_count);
182
183 let ambiguity_changes = ambiguity_cache.changes.remove(room_id).unwrap_or_default();
184 let room_account_data = rooms_account_data.get(room_id);
185
186 match (room_info.state(), maybe_room_update_kind) {
187 (RoomState::Joined, None) => {
188 let ephemeral = Vec::new();
192
193 Ok(Some((
194 room_info,
195 RoomUpdateKind::Joined(JoinedRoomUpdate::new(
196 timeline,
197 state,
198 room_account_data.cloned().unwrap_or_default(),
199 ephemeral,
200 notification_count,
201 ambiguity_changes,
202 )),
203 )))
204 }
205
206 (RoomState::Left, None) | (RoomState::Banned, None) => Ok(Some((
207 room_info,
208 RoomUpdateKind::Left(LeftRoomUpdate::new(
209 timeline,
210 state,
211 room_account_data.cloned().unwrap_or_default(),
212 ambiguity_changes,
213 )),
214 ))),
215
216 (RoomState::Invited, Some(update @ RoomUpdateKind::Invited(_)))
217 | (RoomState::Knocked, Some(update @ RoomUpdateKind::Knocked(_))) => {
218 Ok(Some((room_info, update)))
219 }
220
221 _ => Ok(None),
222 }
223}
224
225fn membership(
231 context: &mut Context,
232 state_events: &[AnySyncStateEvent],
233 invite_state_events: &Option<(Vec<Raw<AnyStrippedStateEvent>>, Vec<AnyStrippedStateEvent>)>,
234 store: &BaseStateStore,
235 user_id: &UserId,
236 room_id: &RoomId,
237 room_info_notable_update_sender: Sender<RoomInfoNotableUpdate>,
238) -> (Room, RoomInfo, Option<RoomUpdateKind>) {
239 if let Some(state_events) = invite_state_events {
246 let membership_event = state_events.1.iter().find_map(|event| {
249 if let AnyStrippedStateEvent::RoomMember(membership_event) = event
250 && membership_event.state_key == user_id
251 {
252 return Some(membership_event.content.clone());
253 }
254 None
255 });
256
257 match membership_event {
258 Some(RoomMemberEventContent { membership: MembershipState::Knock, .. }) => {
260 let room = store.get_or_create_room(
261 room_id,
262 RoomState::Knocked,
263 room_info_notable_update_sender,
264 );
265 let mut room_info = room.clone_info();
266 room_info.mark_as_knocked();
268
269 let raw_events = state_events.0.clone();
270 let knock_state = assign!(KnockState::default(), { events: raw_events });
271 let knocked_room = assign!(KnockedRoom::default(), { knock_state: knock_state });
272
273 (room, room_info, Some(RoomUpdateKind::Knocked(knocked_room)))
274 }
275
276 _ => {
278 let room = store.get_or_create_room(
279 room_id,
280 RoomState::Invited,
281 room_info_notable_update_sender,
282 );
283 let mut room_info = room.clone_info();
284 room_info.mark_as_invited();
286
287 let raw_events = state_events.0.clone();
288 let invited_room = InvitedRoom::from(InviteState::from(raw_events));
289
290 (room, room_info, Some(RoomUpdateKind::Invited(invited_room)))
291 }
292 }
293 }
294 else {
297 let room =
298 store.get_or_create_room(room_id, RoomState::Joined, room_info_notable_update_sender);
299 let mut room_info = room.clone_info();
300
301 room_info.mark_as_joined();
306
307 own_membership(context, user_id, state_events, &mut room_info);
313
314 (room, room_info, None)
315 }
316}
317
318fn own_membership(
321 context: &mut Context,
322 user_id: &UserId,
323 state_events: &[AnySyncStateEvent],
324 room_info: &mut RoomInfo,
325) {
326 for event in state_events.iter().rev() {
330 if let AnySyncStateEvent::RoomMember(member) = &event {
331 if member.state_key() == user_id.as_str() {
334 let new_state: RoomState = member.membership().into();
335
336 if new_state != room_info.state() {
337 room_info.set_state(new_state);
338 context
340 .room_info_notable_updates
341 .entry(room_info.room_id.to_owned())
342 .or_default()
343 .insert(RoomInfoNotableUpdateReasons::MEMBERSHIP);
344 }
345
346 break;
347 }
348 }
349 }
350}
351
352fn properties(
353 context: &mut Context,
354 room_id: &RoomId,
355 room_response: &http::response::Room,
356 room_info: &mut RoomInfo,
357 is_new_room: bool,
358) {
359 match &room_response.avatar {
365 JsOption::Some(avatar_uri) => room_info.update_avatar(Some(avatar_uri.to_owned())),
367 JsOption::Null => room_info.update_avatar(None),
369 JsOption::Undefined => {}
371 }
372
373 if let Some(count) = room_response.joined_count {
376 room_info.update_joined_member_count(count.into());
377 }
378 if let Some(count) = room_response.invited_count {
379 room_info.update_invited_member_count(count.into());
380 }
381
382 if let Some(heroes) = &room_response.heroes {
383 room_info.update_heroes(
384 heroes
385 .iter()
386 .map(|hero| RoomHero {
387 user_id: hero.user_id.clone(),
388 display_name: hero.name.clone(),
389 avatar_url: hero.avatar.clone(),
390 })
391 .collect(),
392 );
393 }
394
395 room_info.set_prev_batch(room_response.prev_batch.as_deref());
396
397 if room_response.limited {
398 room_info.mark_members_missing();
399 }
400
401 if let Some(recency_stamp) = &room_response.bump_stamp {
402 let recency_stamp = u64::from(*recency_stamp).into();
403
404 if room_info.recency_stamp.as_ref() != Some(&recency_stamp) {
405 room_info.update_recency_stamp(recency_stamp);
406
407 if !is_new_room {
411 context
412 .room_info_notable_updates
413 .entry(room_id.to_owned())
414 .or_default()
415 .insert(RoomInfoNotableUpdateReasons::RECENCY_STAMP);
416 }
417 }
418 }
419}
420
421#[cfg(feature = "e2e-encryption")]
429pub(crate) async fn cache_latest_events(
430 room: &Room,
431 room_info: &mut RoomInfo,
432 events: &[TimelineEvent],
433 changes: Option<&StateChanges>,
434 store: Option<&BaseStateStore>,
435) {
436 use tracing::warn;
437
438 use crate::{
439 deserialized_responses::DisplayName,
440 latest_event::{LatestEvent, PossibleLatestEvent, is_suitable_for_latest_event},
441 store::ambiguity_map::is_display_name_ambiguous,
442 };
443
444 let _timer = timer!(tracing::Level::TRACE, "cache_latest_events");
445
446 let mut encrypted_events =
447 Vec::with_capacity(room.latest_encrypted_events.read().unwrap().capacity());
448
449 let power_levels = match changes.and_then(|changes| changes.power_levels(room_info.room_id())) {
452 Some(power_levels) => Some(power_levels),
453 None => room.power_levels().await.ok(),
454 };
455
456 let power_levels_info = Some(room.own_user_id()).zip(power_levels.as_ref());
457
458 for event in events.iter().rev() {
459 if let Ok(timeline_event) = event.raw().deserialize() {
460 match is_suitable_for_latest_event(&timeline_event, power_levels_info) {
461 PossibleLatestEvent::YesRoomMessage(_)
462 | PossibleLatestEvent::YesPoll(_)
463 | PossibleLatestEvent::YesCallInvite(_)
464 | PossibleLatestEvent::YesRtcNotification(_)
465 | PossibleLatestEvent::YesSticker(_)
466 | PossibleLatestEvent::YesKnockedStateEvent(_) => {
467 let mut sender_profile = None;
475 let mut sender_name_is_ambiguous = None;
476
477 if let Some(changes) = changes {
480 sender_profile = changes
481 .profiles
482 .get(room.room_id())
483 .and_then(|profiles_by_user| {
484 profiles_by_user.get(timeline_event.sender())
485 })
486 .cloned();
487
488 if let Some(sender_profile) = sender_profile.as_ref() {
489 sender_name_is_ambiguous = sender_profile
490 .as_original()
491 .and_then(|profile| profile.content.displayname.as_ref())
492 .and_then(|display_name| {
493 let display_name = DisplayName::new(display_name);
494
495 changes.ambiguity_maps.get(room.room_id()).and_then(
496 |map_for_room| {
497 map_for_room.get(&display_name).map(|users| {
498 is_display_name_ambiguous(&display_name, users)
499 })
500 },
501 )
502 });
503 }
504 }
505
506 if sender_profile.is_none()
508 && let Some(store) = store
509 {
510 sender_profile = store
511 .get_profile(room.room_id(), timeline_event.sender())
512 .await
513 .ok()
514 .flatten();
515
516 }
519
520 let latest_event = Box::new(LatestEvent::new_with_sender_details(
521 event.clone(),
522 sender_profile,
523 sender_name_is_ambiguous,
524 ));
525
526 room_info.latest_event = Some(latest_event);
528 room.latest_encrypted_events.write().unwrap().clear();
531 break;
534 }
535 PossibleLatestEvent::NoEncrypted => {
536 if encrypted_events.len() < encrypted_events.capacity() {
542 encrypted_events.push(event.raw().clone());
543 }
544 }
545 _ => {
546 }
548 }
549 } else {
550 warn!(
551 event_id = ?event.event_id(),
552 "Failed to deserialize event as `AnySyncTimelineEvent`",
553 );
554 }
555 }
556
557 room.latest_encrypted_events.write().unwrap().extend(encrypted_events.into_iter().rev());
560}
561
562impl State {
563 fn from_msc4186(events: Vec<Raw<AnySyncStateEvent>>) -> Self {
566 Self::After(events)
567 }
568}