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
21use as_variant::as_variant;
22use matrix_sdk_common::timer;
23use ruma::{
24 JsOption, OwnedRoomId, RoomId, UserId,
25 api::client::sync::sync_events::{
26 v3::{InviteState, InvitedRoom, KnockState, KnockedRoom},
27 v5 as http,
28 },
29 assign,
30 events::{
31 AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncStateEvent, StateEventType,
32 room::member::MembershipState,
33 },
34 serde::Raw,
35};
36
37#[cfg(feature = "e2e-encryption")]
38use super::super::e2ee;
39use super::{
40 super::{Context, notification, state_events, timeline},
41 RoomCreationData,
42};
43use crate::{
44 Result, Room, RoomHero, RoomInfo, RoomInfoNotableUpdateReasons, RoomState,
45 store::BaseStateStore,
46 sync::{InvitedRoomUpdate, JoinedRoomUpdate, KnockedRoomUpdate, LeftRoomUpdate, State},
47 utils::RawStateEventWithKeys,
48};
49
50pub enum RoomUpdateKind {
52 Joined(JoinedRoomUpdate),
53 Left(LeftRoomUpdate),
54 Invited(InvitedRoomUpdate),
55 Knocked(KnockedRoomUpdate),
56}
57
58pub async fn update_any_room(
59 context: &mut Context,
60 user_id: &UserId,
61 room_creation_data: RoomCreationData<'_>,
62 room_response: &http::response::Room,
63 rooms_account_data: &BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
64 #[cfg(feature = "e2e-encryption")] e2ee: e2ee::E2EE<'_>,
65 notification: notification::Notification<'_>,
66) -> Result<Option<(RoomInfo, RoomUpdateKind)>> {
67 let _timer = timer!(tracing::Level::TRACE, "update_any_room");
68
69 let RoomCreationData { room_id, requested_required_states, ambiguity_cache } =
70 room_creation_data;
71
72 let state = State::from_msc4186(room_response.required_state.clone());
78 let mut raw_state_events = state.collect(&[]);
79
80 let state_store = notification.state_store;
81
82 let is_new_room = !state_store.room_exists(room_id);
84
85 let mut raw_invite_state_events =
86 room_response.invite_state.as_ref().map(|events| state_events::stripped::collect(events));
87
88 #[allow(unused_mut)] let (mut room, mut room_info, maybe_room_update_kind) = membership(
90 context,
91 &mut raw_state_events,
92 raw_invite_state_events.as_deref_mut(),
93 state_store,
94 user_id,
95 room_id,
96 );
97
98 #[cfg(feature = "e2e-encryption")]
101 if room_info.state() != RoomState::Joined
102 && let Some(olm) = e2ee.olm_machine
103 {
104 olm.store().clear_room_pending_key_bundle(room_info.room_id()).await?
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,
119 &mut room_info,
120 ambiguity_cache,
121 &mut new_user_ids,
122 state_store,
123 #[cfg(feature = "experimental-encrypted-state-events")]
124 &e2ee,
125 )
126 .await?;
127
128 if let Some(raw_state_events) = raw_invite_state_events {
130 state_events::stripped::dispatch_invite_or_knock(
131 context,
132 raw_state_events,
133 &room,
134 &mut room_info,
135 user_id,
136 notification::Notification::new(
137 notification.push_rules,
138 notification.notifications,
139 notification.state_store,
140 ),
141 )
142 .await?;
143 }
144
145 properties(context, room_id, room_response, &mut room_info, is_new_room);
146
147 let timeline = timeline::build(
148 context,
149 &room,
150 &mut room_info,
151 timeline::builder::Timeline::from(room_response),
152 notification,
153 #[cfg(feature = "e2e-encryption")]
154 &e2ee,
155 )
156 .await?;
157
158 #[cfg(feature = "e2e-encryption")]
159 e2ee::tracked_users::update_or_set_if_room_is_newly_encrypted(
160 e2ee.olm_machine,
161 &new_user_ids,
162 room_info.encryption_state(),
163 room.encryption_state(),
164 room_id,
165 state_store,
166 )
167 .await?;
168
169 let notification_count = room_response.unread_notifications.clone().into();
170 room_info.update_notification_count(notification_count);
171
172 let ambiguity_changes = ambiguity_cache.changes.remove(room_id).unwrap_or_default();
173 let room_account_data = rooms_account_data.get(room_id);
174
175 match (room_info.state(), maybe_room_update_kind) {
176 (RoomState::Joined, None) => {
177 let ephemeral = Vec::new();
181
182 Ok(Some((
183 room_info,
184 RoomUpdateKind::Joined(JoinedRoomUpdate::new(
185 timeline,
186 state,
187 room_account_data.cloned().unwrap_or_default(),
188 ephemeral,
189 notification_count,
190 ambiguity_changes,
191 )),
192 )))
193 }
194
195 (RoomState::Left, None) | (RoomState::Banned, None) => Ok(Some((
196 room_info,
197 RoomUpdateKind::Left(LeftRoomUpdate::new(
198 timeline,
199 state,
200 room_account_data.cloned().unwrap_or_default(),
201 ambiguity_changes,
202 )),
203 ))),
204
205 (RoomState::Invited, Some(update @ RoomUpdateKind::Invited(_)))
206 | (RoomState::Knocked, Some(update @ RoomUpdateKind::Knocked(_))) => {
207 Ok(Some((room_info, update)))
208 }
209
210 (RoomState::Invited, None) => {
211 Ok(Some((room_info, RoomUpdateKind::Invited(InvitedRoom::default()))))
212 }
213 (RoomState::Knocked, None) => {
214 Ok(Some((room_info, RoomUpdateKind::Knocked(KnockedRoom::default()))))
215 }
216
217 _ => Ok(None),
218 }
219}
220
221fn membership(
227 context: &mut Context,
228 state_events: &mut [RawStateEventWithKeys<AnySyncStateEvent>],
229 invite_state_events: Option<&mut [RawStateEventWithKeys<AnyStrippedStateEvent>]>,
230 store: &BaseStateStore,
231 user_id: &UserId,
232 room_id: &RoomId,
233) -> (Room, RoomInfo, Option<RoomUpdateKind>) {
234 if let Some(state_events) = invite_state_events {
241 let own_membership = state_events.iter_mut().find_map(|raw_event| {
244 if raw_event.event_type == StateEventType::RoomMember
245 && raw_event.state_key == user_id.as_str()
246 {
247 raw_event
248 .deserialize_as(|any_event| {
249 as_variant!(any_event, AnyStrippedStateEvent::RoomMember)
250 })
251 .map(|event| event.content.membership.clone())
252 } else {
253 None
254 }
255 });
256
257 let raw_events = state_events.iter().map(|event| event.raw.clone()).collect();
258
259 match own_membership {
260 Some(MembershipState::Knock) => {
262 let room = store.get_or_create_room(room_id, RoomState::Knocked);
263 let mut room_info = room.clone_info();
264 room_info.mark_as_knocked();
266
267 let knock_state = assign!(KnockState::default(), { events: raw_events });
268 let knocked_room = assign!(KnockedRoom::default(), { knock_state: knock_state });
269
270 (room, room_info, Some(RoomUpdateKind::Knocked(knocked_room)))
271 }
272
273 _ => {
275 let room = store.get_or_create_room(room_id, RoomState::Invited);
276 let mut room_info = room.clone_info();
277 room_info.mark_as_invited();
279
280 let invited_room = InvitedRoom::from(InviteState::from(raw_events));
281
282 (room, room_info, Some(RoomUpdateKind::Invited(invited_room)))
283 }
284 }
285 }
286 else {
289 let room = store.get_or_create_room(room_id, RoomState::Joined);
290 let mut room_info = room.clone_info();
291
292 room_info.mark_as_joined();
297
298 state_events::sync::own_membership_and_update_room_state(
304 context,
305 user_id,
306 state_events,
307 &mut room_info,
308 );
309
310 (room, room_info, None)
311 }
312}
313
314fn properties(
315 context: &mut Context,
316 room_id: &RoomId,
317 room_response: &http::response::Room,
318 room_info: &mut RoomInfo,
319 is_new_room: bool,
320) {
321 match &room_response.avatar {
327 JsOption::Some(avatar_uri) => room_info.update_avatar(Some(avatar_uri.to_owned())),
329 JsOption::Null => room_info.update_avatar(None),
331 JsOption::Undefined => {}
333 }
334
335 if let Some(count) = room_response.joined_count {
338 room_info.update_joined_member_count(count.into());
339 }
340 if let Some(count) = room_response.invited_count {
341 room_info.update_invited_member_count(count.into());
342 }
343
344 if let Some(heroes) = &room_response.heroes {
345 room_info.update_heroes(
346 heroes
347 .iter()
348 .map(|hero| RoomHero {
349 user_id: hero.user_id.clone(),
350 display_name: hero.name.clone(),
351 avatar_url: hero.avatar.clone(),
352 })
353 .collect(),
354 );
355 }
356
357 room_info.set_prev_batch(room_response.prev_batch.as_deref());
358
359 if room_response.limited {
360 room_info.mark_members_missing();
361 }
362
363 if let Some(recency_stamp) = &room_response.bump_stamp {
364 let recency_stamp = u64::from(*recency_stamp).into();
365
366 if room_info.recency_stamp.as_ref() != Some(&recency_stamp) {
367 room_info.update_recency_stamp(recency_stamp);
368
369 if !is_new_room {
373 context
374 .room_info_notable_updates
375 .entry(room_id.to_owned())
376 .or_default()
377 .insert(RoomInfoNotableUpdateReasons::RECENCY_STAMP);
378 }
379 }
380 }
381}
382
383impl State {
384 fn from_msc4186(events: Vec<Raw<AnySyncStateEvent>>) -> Self {
387 Self::After(events)
388 }
389}