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, avatar_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 avatar_cache,
122 &mut new_user_ids,
123 state_store,
124 #[cfg(feature = "experimental-encrypted-state-events")]
125 &e2ee,
126 )
127 .await?;
128
129 if let Some(raw_state_events) = raw_invite_state_events {
131 state_events::stripped::dispatch_invite_or_knock(
132 context,
133 raw_state_events,
134 &room,
135 &mut room_info,
136 user_id,
137 notification::Notification::new(
138 notification.push_rules,
139 notification.notifications,
140 notification.state_store,
141 ),
142 )
143 .await?;
144 }
145
146 properties(context, room_id, room_response, &mut room_info, is_new_room);
147
148 let timeline = timeline::build(
149 context,
150 &room,
151 &mut room_info,
152 timeline::builder::Timeline::from(room_response),
153 notification,
154 #[cfg(feature = "e2e-encryption")]
155 &e2ee,
156 )
157 .await?;
158
159 #[cfg(feature = "e2e-encryption")]
160 e2ee::tracked_users::update_or_set_if_room_is_newly_encrypted(
161 e2ee.olm_machine,
162 &new_user_ids,
163 room_info.encryption_state(),
164 room.encryption_state(),
165 room_id,
166 state_store,
167 )
168 .await?;
169
170 let notification_count = room_response.unread_notifications.clone().into();
171 room_info.update_notification_count(notification_count);
172
173 let ambiguity_changes = ambiguity_cache.changes.remove(room_id).unwrap_or_default();
174 let avatar_changes = avatar_cache.remove_changes(room_id);
175 let room_account_data = rooms_account_data.get(room_id);
176
177 match (room_info.state(), maybe_room_update_kind) {
178 (RoomState::Joined, None) => {
179 let ephemeral = Vec::new();
183
184 Ok(Some((
185 room_info,
186 RoomUpdateKind::Joined(JoinedRoomUpdate::new(
187 timeline,
188 state,
189 room_account_data.cloned().unwrap_or_default(),
190 ephemeral,
191 notification_count,
192 ambiguity_changes,
193 avatar_changes,
194 )),
195 )))
196 }
197
198 (RoomState::Left, None) | (RoomState::Banned, None) => Ok(Some((
199 room_info,
200 RoomUpdateKind::Left(LeftRoomUpdate::new(
201 timeline,
202 state,
203 room_account_data.cloned().unwrap_or_default(),
204 ambiguity_changes,
205 )),
206 ))),
207
208 (RoomState::Invited, Some(update @ RoomUpdateKind::Invited(_)))
209 | (RoomState::Knocked, Some(update @ RoomUpdateKind::Knocked(_))) => {
210 Ok(Some((room_info, update)))
211 }
212
213 (RoomState::Invited, None) => {
214 Ok(Some((room_info, RoomUpdateKind::Invited(InvitedRoom::default()))))
215 }
216 (RoomState::Knocked, None) => {
217 Ok(Some((room_info, RoomUpdateKind::Knocked(KnockedRoom::default()))))
218 }
219
220 _ => Ok(None),
221 }
222}
223
224fn membership(
230 context: &mut Context,
231 state_events: &mut [RawStateEventWithKeys<AnySyncStateEvent>],
232 invite_state_events: Option<&mut [RawStateEventWithKeys<AnyStrippedStateEvent>]>,
233 store: &BaseStateStore,
234 user_id: &UserId,
235 room_id: &RoomId,
236) -> (Room, RoomInfo, Option<RoomUpdateKind>) {
237 if let Some(state_events) = invite_state_events {
244 let own_membership = state_events.iter_mut().find_map(|raw_event| {
247 if raw_event.event_type == StateEventType::RoomMember
248 && raw_event.state_key == user_id.as_str()
249 {
250 raw_event
251 .deserialize_as(|any_event| {
252 as_variant!(any_event, AnyStrippedStateEvent::RoomMember)
253 })
254 .map(|event| event.content.membership.clone())
255 } else {
256 None
257 }
258 });
259
260 let raw_events = state_events.iter().map(|event| event.raw.clone()).collect();
261
262 match own_membership {
263 Some(MembershipState::Knock) => {
265 let room = store.get_or_create_room(room_id, RoomState::Knocked);
266 let mut room_info = room.clone_info();
267 room_info.mark_as_knocked();
269
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(room_id, RoomState::Invited);
279 let mut room_info = room.clone_info();
280 room_info.mark_as_invited();
282
283 let invited_room = InvitedRoom::from(InviteState::from(raw_events));
284
285 (room, room_info, Some(RoomUpdateKind::Invited(invited_room)))
286 }
287 }
288 }
289 else {
292 let room = store.get_or_create_room(room_id, RoomState::Joined);
293 let mut room_info = room.clone_info();
294
295 room_info.mark_as_joined();
300
301 state_events::sync::own_membership_and_update_room_state(
307 context,
308 user_id,
309 state_events,
310 &mut room_info,
311 );
312
313 (room, room_info, None)
314 }
315}
316
317fn properties(
318 context: &mut Context,
319 room_id: &RoomId,
320 room_response: &http::response::Room,
321 room_info: &mut RoomInfo,
322 is_new_room: bool,
323) {
324 match &room_response.avatar {
330 JsOption::Some(avatar_uri) => room_info.update_avatar(Some(avatar_uri.to_owned())),
332 JsOption::Null => room_info.update_avatar(None),
334 JsOption::Undefined => {}
336 }
337
338 if let Some(count) = room_response.joined_count {
341 room_info.update_joined_member_count(count.into());
342 }
343 if let Some(count) = room_response.invited_count {
344 room_info.update_invited_member_count(count.into());
345 }
346
347 if let Some(heroes) = &room_response.heroes {
348 room_info.update_heroes(
349 heroes
350 .iter()
351 .map(|hero| RoomHero {
352 user_id: hero.user_id.clone(),
353 display_name: hero.name.clone(),
354 avatar_url: hero.avatar.clone(),
355 })
356 .collect(),
357 );
358 }
359
360 room_info.set_prev_batch(room_response.prev_batch.as_deref());
361
362 if room_response.limited {
363 room_info.mark_members_missing();
364 }
365
366 if let Some(recency_stamp) = &room_response.bump_stamp {
367 let recency_stamp = u64::from(*recency_stamp).into();
368
369 if room_info.recency_stamp.as_ref() != Some(&recency_stamp) {
370 room_info.update_recency_stamp(recency_stamp);
371
372 if !is_new_room {
376 context
377 .room_info_notable_updates
378 .entry(room_id.to_owned())
379 .or_default()
380 .insert(RoomInfoNotableUpdateReasons::RECENCY_STAMP);
381 }
382 }
383 }
384}
385
386impl State {
387 fn from_msc4186(events: Vec<Raw<AnySyncStateEvent>>) -> Self {
390 Self::After(events)
391 }
392}