diff --git a/src/api/client/sync/mod.rs b/src/api/client/sync/mod.rs index 3b679091..679c6f9e 100644 --- a/src/api/client/sync/mod.rs +++ b/src/api/client/sync/mod.rs @@ -1,5 +1,4 @@ mod v3; -mod v4; mod v5; use conduwuit::{ @@ -17,9 +16,7 @@ use ruma::{ }, }; -pub(crate) use self::{ - v3::sync_events_route, v4::sync_events_v4_route, v5::sync_events_v5_route, -}; +pub(crate) use self::{v3::sync_events_route, v5::sync_events_v5_route}; pub(crate) const DEFAULT_BUMP_TYPES: &[TimelineEventType; 6] = &[CallInvite, PollStart, Beacon, RoomEncrypted, RoomMessage, Sticker]; diff --git a/src/api/client/sync/v4.rs b/src/api/client/sync/v4.rs deleted file mode 100644 index 5ded1a9f..00000000 --- a/src/api/client/sync/v4.rs +++ /dev/null @@ -1,853 +0,0 @@ -use std::{ - cmp::{self, Ordering}, - collections::{BTreeMap, BTreeSet, HashMap, HashSet}, - time::Duration, -}; - -use axum::extract::State; -use conduwuit::{ - Err, Error, Event, PduCount, Result, at, debug, error, extract_variant, - matrix::TypeStateKey, - utils::{ - BoolExt, IterStream, ReadyExt, TryFutureExtExt, - math::{ruma_from_usize, usize_from_ruma, usize_from_u64_truncated}, - stream::WidebandExt, - }, - warn, -}; -use conduwuit_service::{ - Services, - rooms::read_receipt::pack_receipts, - sync::{into_db_key, into_snake_key}, -}; -use futures::{FutureExt, StreamExt, TryFutureExt}; -use ruma::{ - MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, RoomId, UInt, UserId, - api::client::sync::sync_events::{ - self, DeviceLists, UnreadNotificationsCount, - v4::{SlidingOp, SlidingSyncRoomHero}, - }, - directory::RoomTypeFilter, - events::{ - AnyRawAccountDataEvent, AnySyncEphemeralRoomEvent, StateEventType, - TimelineEventType::*, - room::member::{MembershipState, RoomMemberEventContent}, - }, - serde::Raw, - uint, -}; - -use super::{load_timeline, share_encrypted_room}; -use crate::{ - Ruma, - client::{DEFAULT_BUMP_TYPES, TimelinePdus, ignored_filter, is_ignored_invite}, -}; - -type TodoRooms = BTreeMap, usize, u64)>; -const SINGLE_CONNECTION_SYNC: &str = "single_connection_sync"; - -#[allow(clippy::cognitive_complexity)] -/// POST `/_matrix/client/unstable/org.matrix.msc3575/sync` -/// -/// Sliding Sync endpoint (future endpoint: `/_matrix/client/v4/sync`) -pub(crate) async fn sync_events_v4_route( - State(services): State, - body: Ruma, -) -> Result { - debug_assert!(DEFAULT_BUMP_TYPES.is_sorted(), "DEFAULT_BUMP_TYPES is not sorted"); - let sender_user = body.sender_user.as_ref().expect("user is authenticated"); - let sender_device = body.sender_device.as_ref().expect("user is authenticated"); - let mut body = body.body; - - // Setup watchers, so if there's no response, we can wait for them - let watcher = services.sync.watch(sender_user, sender_device); - - let next_batch = services.globals.next_count()?; - - let conn_id = body - .conn_id - .clone() - .unwrap_or_else(|| SINGLE_CONNECTION_SYNC.to_owned()); - - let globalsince = body - .pos - .as_ref() - .and_then(|string| string.parse().ok()) - .unwrap_or(0); - - let db_key = into_db_key(sender_user, sender_device, conn_id.clone()); - if globalsince != 0 && !services.sync.remembered(&db_key) { - debug!("Restarting sync stream because it was gone from the database"); - return Err!(Request(UnknownPos("Connection data lost since last time"))); - } - - if globalsince == 0 { - services.sync.forget_sync_request_connection(&db_key); - } - - // Get sticky parameters from cache - let snake_key = into_snake_key(sender_user, sender_device, conn_id.clone()); - let known_rooms = services - .sync - .update_sync_request_with_cache(&snake_key, &mut body); - - let all_joined_rooms: Vec<_> = services - .rooms - .state_cache - .rooms_joined(sender_user) - .map(ToOwned::to_owned) - .collect() - .await; - - let all_invited_rooms: Vec<_> = services - .rooms - .state_cache - .rooms_invited(sender_user) - .wide_filter_map(async |(room_id, invite_state)| { - if is_ignored_invite(&services, sender_user, &room_id).await { - None - } else { - Some((room_id, invite_state)) - } - }) - .map(|r| r.0) - .collect() - .await; - - let all_knocked_rooms: Vec<_> = services - .rooms - .state_cache - .rooms_knocked(sender_user) - .map(|r| r.0) - .collect() - .await; - - let all_invited_rooms: Vec<&RoomId> = all_invited_rooms.iter().map(AsRef::as_ref).collect(); - let all_knocked_rooms: Vec<&RoomId> = all_knocked_rooms.iter().map(AsRef::as_ref).collect(); - - let all_rooms: Vec<&RoomId> = all_joined_rooms - .iter() - .map(AsRef::as_ref) - .chain(all_invited_rooms.iter().map(AsRef::as_ref)) - .chain(all_knocked_rooms.iter().map(AsRef::as_ref)) - .collect(); - - let all_joined_rooms = all_joined_rooms.iter().map(AsRef::as_ref).collect(); - let all_invited_rooms = all_invited_rooms.iter().map(AsRef::as_ref).collect(); - - if body.extensions.to_device.enabled.unwrap_or(false) { - services - .users - .remove_to_device_events(sender_user, sender_device, globalsince) - .await; - } - - let mut left_encrypted_users = HashSet::new(); // Users that have left any encrypted rooms the sender was in - let mut device_list_changes = HashSet::new(); - let mut device_list_left = HashSet::new(); - - let mut receipts = sync_events::v4::Receipts { rooms: BTreeMap::new() }; - - let mut account_data = sync_events::v4::AccountData { - global: Vec::new(), - rooms: BTreeMap::new(), - }; - if body.extensions.account_data.enabled.unwrap_or(false) { - account_data.global = services - .account_data - .changes_since(None, sender_user, Some(globalsince), Some(next_batch)) - .ready_filter_map(|e| extract_variant!(e, AnyRawAccountDataEvent::Global)) - .collect() - .await; - - if let Some(rooms) = body.extensions.account_data.rooms { - for room in rooms { - account_data.rooms.insert( - room.clone(), - services - .account_data - .changes_since( - Some(&room), - sender_user, - Some(globalsince), - Some(next_batch), - ) - .ready_filter_map(|e| extract_variant!(e, AnyRawAccountDataEvent::Room)) - .collect() - .await, - ); - } - } - } - - if body.extensions.e2ee.enabled.unwrap_or(false) { - // Look for device list updates of this account - device_list_changes.extend( - services - .users - .keys_changed(sender_user, Some(globalsince), None) - .map(ToOwned::to_owned) - .collect::>() - .await, - ); - - for room_id in &all_joined_rooms { - let room_id: &&RoomId = room_id; - let Ok(current_shortstatehash) = - services.rooms.state.get_room_shortstatehash(room_id).await - else { - error!("Room {room_id} has no state"); - continue; - }; - - let since_shortstatehash = services - .rooms - .user - .get_token_shortstatehash(room_id, globalsince) - .await - .ok(); - - let encrypted_room = services - .rooms - .state_accessor - .state_get(current_shortstatehash, &StateEventType::RoomEncryption, "") - .await - .is_ok(); - - if let Some(since_shortstatehash) = since_shortstatehash { - // Skip if there are only timeline changes - if since_shortstatehash == current_shortstatehash { - continue; - } - - let since_encryption = services - .rooms - .state_accessor - .state_get(since_shortstatehash, &StateEventType::RoomEncryption, "") - .await; - - let since_sender_member: Option = services - .rooms - .state_accessor - .state_get_content( - since_shortstatehash, - &StateEventType::RoomMember, - sender_user.as_str(), - ) - .ok() - .await; - - let joined_since_last_sync = since_sender_member - .as_ref() - .is_none_or(|member| member.membership != MembershipState::Join); - - let new_encrypted_room = encrypted_room && since_encryption.is_err(); - - if encrypted_room { - let current_state_ids: HashMap<_, OwnedEventId> = services - .rooms - .state_accessor - .state_full_ids(current_shortstatehash) - .collect() - .await; - - let since_state_ids: HashMap<_, _> = services - .rooms - .state_accessor - .state_full_ids(since_shortstatehash) - .collect() - .await; - - for (key, id) in current_state_ids { - if since_state_ids.get(&key) != Some(&id) { - let Ok(pdu) = services.rooms.timeline.get_pdu(&id).await else { - error!("Pdu in state not found: {id}"); - continue; - }; - if pdu.kind == RoomMember { - if let Some(Ok(user_id)) = - pdu.state_key.as_deref().map(UserId::parse) - { - if user_id == sender_user { - continue; - } - - let content: RoomMemberEventContent = pdu.get_content()?; - match content.membership { - | MembershipState::Join => { - // A new user joined an encrypted room - if !share_encrypted_room( - &services, - sender_user, - user_id, - Some(room_id), - ) - .await - { - device_list_changes.insert(user_id.to_owned()); - } - }, - | MembershipState::Leave => { - // Write down users that have left encrypted rooms we - // are in - left_encrypted_users.insert(user_id.to_owned()); - }, - | _ => {}, - } - } - } - } - } - if joined_since_last_sync || new_encrypted_room { - // If the user is in a new encrypted room, give them all joined users - device_list_changes.extend( - services - .rooms - .state_cache - .room_members(room_id) - // Don't send key updates from the sender to the sender - .ready_filter(|&user_id| sender_user != user_id) - // Only send keys if the sender doesn't share an encrypted room with the target - // already - .filter_map(|user_id| { - share_encrypted_room(&services, sender_user, user_id, Some(room_id)) - .map(|res| res.or_some(user_id.to_owned())) - }) - .collect::>() - .await, - ); - } - } - } - // Look for device list updates in this room - device_list_changes.extend( - services - .users - .room_keys_changed(room_id, Some(globalsince), None) - .map(|(user_id, _)| user_id) - .map(ToOwned::to_owned) - .collect::>() - .await, - ); - } - - for user_id in left_encrypted_users { - let dont_share_encrypted_room = - !share_encrypted_room(&services, sender_user, &user_id, None).await; - - // If the user doesn't share an encrypted room with the target anymore, we need - // to tell them - if dont_share_encrypted_room { - device_list_left.insert(user_id); - } - } - } - - let mut lists = BTreeMap::new(); - let mut todo_rooms: TodoRooms = BTreeMap::new(); // and required state - - for (list_id, list) in &body.lists { - let active_rooms = match list.filters.clone().and_then(|f| f.is_invite) { - | Some(true) => &all_invited_rooms, - | Some(false) => &all_joined_rooms, - | None => &all_rooms, - }; - - let active_rooms = match list.filters.clone().map(|f| f.not_room_types) { - | Some(filter) if filter.is_empty() => active_rooms.clone(), - | Some(value) => filter_rooms(&services, active_rooms, &value, true).await, - | None => active_rooms.clone(), - }; - - let active_rooms = match list.filters.clone().map(|f| f.room_types) { - | Some(filter) if filter.is_empty() => active_rooms.clone(), - | Some(value) => filter_rooms(&services, &active_rooms, &value, false).await, - | None => active_rooms, - }; - - let mut new_known_rooms: BTreeSet = BTreeSet::new(); - - let ranges = list.ranges.clone(); - lists.insert(list_id.clone(), sync_events::v4::SyncList { - ops: ranges - .into_iter() - .map(|mut r| { - r.0 = r.0.clamp( - uint!(0), - UInt::try_from(active_rooms.len().saturating_sub(1)).unwrap_or(UInt::MAX), - ); - r.1 = r.1.clamp( - r.0, - UInt::try_from(active_rooms.len().saturating_sub(1)).unwrap_or(UInt::MAX), - ); - - let room_ids = if !active_rooms.is_empty() { - active_rooms[usize_from_ruma(r.0)..=usize_from_ruma(r.1)].to_vec() - } else { - Vec::new() - }; - - new_known_rooms.extend(room_ids.clone().into_iter().map(ToOwned::to_owned)); - for room_id in &room_ids { - let todo_room = todo_rooms.entry((*room_id).to_owned()).or_insert(( - BTreeSet::new(), - 0_usize, - u64::MAX, - )); - - let limit: usize = list - .room_details - .timeline_limit - .map(u64::from) - .map_or(10, usize_from_u64_truncated) - .min(100); - - todo_room.0.extend( - list.room_details - .required_state - .iter() - .map(|(ty, sk)| (ty.clone(), sk.as_str().into())), - ); - - todo_room.1 = todo_room.1.max(limit); - // 0 means unknown because it got out of date - todo_room.2 = todo_room.2.min( - known_rooms - .get(list_id.as_str()) - .and_then(|k| k.get(*room_id)) - .copied() - .unwrap_or(0), - ); - } - sync_events::v4::SyncOp { - op: SlidingOp::Sync, - range: Some(r), - index: None, - room_ids: room_ids.into_iter().map(ToOwned::to_owned).collect(), - room_id: None, - } - }) - .collect(), - count: ruma_from_usize(active_rooms.len()), - }); - - if let Some(conn_id) = &body.conn_id { - let db_key = into_db_key(sender_user, sender_device, conn_id); - services.sync.update_sync_known_rooms( - &db_key, - list_id.clone(), - new_known_rooms, - globalsince, - ); - } - } - - let mut known_subscription_rooms = BTreeSet::new(); - for (room_id, room) in &body.room_subscriptions { - if !services.rooms.metadata.exists(room_id).await - || services.rooms.metadata.is_disabled(room_id).await - || services.rooms.metadata.is_banned(room_id).await - { - continue; - } - let todo_room = - todo_rooms - .entry(room_id.clone()) - .or_insert((BTreeSet::new(), 0_usize, u64::MAX)); - - let limit: usize = room - .timeline_limit - .map(u64::from) - .map_or(10, usize_from_u64_truncated) - .min(100); - - todo_room.0.extend( - room.required_state - .iter() - .map(|(ty, sk)| (ty.clone(), sk.as_str().into())), - ); - todo_room.1 = todo_room.1.max(limit); - // 0 means unknown because it got out of date - todo_room.2 = todo_room.2.min( - known_rooms - .get("subscriptions") - .and_then(|k| k.get(room_id)) - .copied() - .unwrap_or(0), - ); - known_subscription_rooms.insert(room_id.clone()); - } - - for r in body.unsubscribe_rooms { - known_subscription_rooms.remove(&r); - body.room_subscriptions.remove(&r); - } - - if let Some(conn_id) = &body.conn_id { - let db_key = into_db_key(sender_user, sender_device, conn_id); - services.sync.update_sync_known_rooms( - &db_key, - "subscriptions".to_owned(), - known_subscription_rooms, - globalsince, - ); - } - - if let Some(conn_id) = body.conn_id.clone() { - let db_key = into_db_key(sender_user, sender_device, conn_id); - services - .sync - .update_sync_subscriptions(&db_key, body.room_subscriptions); - } - - let mut rooms = BTreeMap::new(); - for (room_id, (required_state_request, timeline_limit, roomsince)) in &todo_rooms { - let roomsincecount = PduCount::Normal(*roomsince); - - let mut timestamp: Option<_> = None; - let mut invite_state = None; - let (timeline_pdus, limited); - let new_room_id: &RoomId = (*room_id).as_ref(); - if all_invited_rooms.contains(&new_room_id) { - // TODO: figure out a timestamp we can use for remote invites - invite_state = services - .rooms - .state_cache - .invite_state(sender_user, room_id) - .await - .ok(); - - (timeline_pdus, limited) = (Vec::new(), true); - } else { - TimelinePdus { pdus: timeline_pdus, limited } = match load_timeline( - &services, - sender_user, - room_id, - Some(roomsincecount), - None, - *timeline_limit, - ) - .await - { - | Ok(value) => value, - | Err(err) => { - warn!("Encountered missing timeline in {}, error {}", room_id, err); - continue; - }, - }; - } - - account_data.rooms.insert( - room_id.to_owned(), - services - .account_data - .changes_since(Some(room_id), sender_user, Some(*roomsince), Some(next_batch)) - .ready_filter_map(|e| extract_variant!(e, AnyRawAccountDataEvent::Room)) - .collect() - .await, - ); - - let last_privateread_update = services - .rooms - .read_receipt - .last_privateread_update(sender_user, room_id) - .await > *roomsince; - - let private_read_event = if last_privateread_update { - services - .rooms - .read_receipt - .private_read_get(room_id, sender_user) - .await - .ok() - } else { - None - }; - - let mut vector: Vec> = services - .rooms - .read_receipt - .readreceipts_since(room_id, Some(*roomsince)) - .filter_map(|(read_user, _ts, v)| async move { - services - .users - .user_is_ignored(read_user, sender_user) - .await - .or_some(v) - }) - .collect() - .await; - - if let Some(private_read_event) = private_read_event { - vector.push(private_read_event); - } - - let receipt_size = vector.len(); - receipts - .rooms - .insert(room_id.clone(), pack_receipts(Box::new(vector.into_iter()))); - - if roomsince != &0 - && timeline_pdus.is_empty() - && account_data.rooms.get(room_id).is_some_and(Vec::is_empty) - && receipt_size == 0 - { - continue; - } - - let prev_batch = timeline_pdus - .first() - .map_or(Ok::<_, Error>(None), |(pdu_count, _)| { - Ok(Some(match pdu_count { - | PduCount::Backfilled(_) => { - error!("timeline in backfill state?!"); - "0".to_owned() - }, - | PduCount::Normal(c) => c.to_string(), - })) - })? - .or_else(|| { - if roomsince != &0 { - Some(roomsince.to_string()) - } else { - None - } - }); - - let room_events: Vec<_> = timeline_pdus - .iter() - .stream() - .filter_map(|item| ignored_filter(&services, item.clone(), sender_user)) - .map(at!(1)) - .map(Event::into_format) - .collect() - .await; - - for (_, pdu) in timeline_pdus { - let ts = MilliSecondsSinceUnixEpoch(pdu.origin_server_ts); - if DEFAULT_BUMP_TYPES.binary_search(&pdu.kind).is_ok() - && timestamp.is_none_or(|time| time <= ts) - { - timestamp = Some(ts); - } - } - - let required_state = required_state_request - .iter() - .stream() - .filter_map(|state| async move { - services - .rooms - .state_accessor - .room_state_get(room_id, &state.0, &state.1) - .await - .map(Event::into_format) - .ok() - }) - .collect() - .await; - - // Heroes - let heroes: Vec<_> = services - .rooms - .state_cache - .room_members(room_id) - .ready_filter(|&member| member != sender_user) - .filter_map(|user_id| { - services - .rooms - .state_accessor - .get_member(room_id, user_id) - .map_ok(|memberevent| SlidingSyncRoomHero { - user_id: user_id.into(), - name: memberevent.displayname, - avatar: memberevent.avatar_url, - }) - .ok() - }) - .take(5) - .collect() - .await; - - let name = match heroes.len().cmp(&(1_usize)) { - | Ordering::Greater => { - let firsts = heroes[1..] - .iter() - .map(|h| h.name.clone().unwrap_or_else(|| h.user_id.to_string())) - .collect::>() - .join(", "); - - let last = heroes[0] - .name - .clone() - .unwrap_or_else(|| heroes[0].user_id.to_string()); - - Some(format!("{firsts} and {last}")) - }, - | Ordering::Equal => Some( - heroes[0] - .name - .clone() - .unwrap_or_else(|| heroes[0].user_id.to_string()), - ), - | Ordering::Less => None, - }; - - let heroes_avatar = if heroes.len() == 1 { - heroes[0].avatar.clone() - } else { - None - }; - - rooms.insert(room_id.clone(), sync_events::v4::SlidingSyncRoom { - name: services - .rooms - .state_accessor - .get_name(room_id) - .await - .ok() - .or(name), - avatar: match heroes_avatar { - | Some(heroes_avatar) => ruma::JsOption::Some(heroes_avatar), - | _ => match services.rooms.state_accessor.get_avatar(room_id).await { - | ruma::JsOption::Some(avatar) => ruma::JsOption::from_option(avatar.url), - | ruma::JsOption::Null => ruma::JsOption::Null, - | ruma::JsOption::Undefined => ruma::JsOption::Undefined, - }, - }, - initial: Some(roomsince == &0), - is_dm: None, - invite_state, - unread_notifications: UnreadNotificationsCount { - highlight_count: Some( - services - .rooms - .user - .highlight_count(sender_user, room_id) - .await - .try_into() - .expect("notification count can't go that high"), - ), - notification_count: Some( - services - .rooms - .user - .notification_count(sender_user, room_id) - .await - .try_into() - .expect("notification count can't go that high"), - ), - }, - timeline: room_events, - required_state, - prev_batch, - limited, - joined_count: Some( - services - .rooms - .state_cache - .room_joined_count(room_id) - .await - .unwrap_or(0) - .try_into() - .unwrap_or_else(|_| uint!(0)), - ), - invited_count: Some( - services - .rooms - .state_cache - .room_invited_count(room_id) - .await - .unwrap_or(0) - .try_into() - .unwrap_or_else(|_| uint!(0)), - ), - num_live: None, // Count events in timeline greater than global sync counter - timestamp, - heroes: Some(heroes), - }); - } - - if rooms.iter().all(|(id, r)| { - r.timeline.is_empty() && r.required_state.is_empty() && !receipts.rooms.contains_key(id) - }) { - // Hang a few seconds so requests are not spammed - // Stop hanging if new info arrives - let default = Duration::from_secs(30); - let duration = cmp::min(body.timeout.unwrap_or(default), default); - _ = tokio::time::timeout(duration, watcher).await; - } - - Ok(sync_events::v4::Response { - initial: globalsince == 0, - txn_id: body.txn_id.clone(), - pos: next_batch.to_string(), - lists, - rooms, - extensions: sync_events::v4::Extensions { - to_device: if body.extensions.to_device.enabled.unwrap_or(false) { - Some(sync_events::v4::ToDevice { - events: services - .users - .get_to_device_events( - sender_user, - sender_device, - Some(globalsince), - Some(next_batch), - ) - .collect() - .await, - next_batch: next_batch.to_string(), - }) - } else { - None - }, - e2ee: sync_events::v4::E2EE { - device_lists: DeviceLists { - changed: device_list_changes.into_iter().collect(), - left: device_list_left.into_iter().collect(), - }, - device_one_time_keys_count: services - .users - .count_one_time_keys(sender_user, sender_device) - .await, - // Fallback keys are not yet supported - device_unused_fallback_key_types: None, - }, - account_data, - receipts, - typing: sync_events::v4::Typing { rooms: BTreeMap::new() }, - }, - delta_token: None, - }) -} - -async fn filter_rooms<'a>( - services: &Services, - rooms: &[&'a RoomId], - filter: &[RoomTypeFilter], - negate: bool, -) -> Vec<&'a RoomId> { - rooms - .iter() - .stream() - .filter_map(|r| async move { - let room_type = services.rooms.state_accessor.get_room_type(r).await; - - if room_type.as_ref().is_err_and(|e| !e.is_not_found()) { - return None; - } - - let room_type_filter = RoomTypeFilter::from(room_type.ok()); - - let include = if negate { - !filter.contains(&room_type_filter) - } else { - filter.is_empty() || filter.contains(&room_type_filter) - }; - - include.then_some(r) - }) - .collect() - .await -} diff --git a/src/api/client/unversioned.rs b/src/api/client/unversioned.rs index 3d4e468a..f377ed54 100644 --- a/src/api/client/unversioned.rs +++ b/src/api/client/unversioned.rs @@ -52,7 +52,6 @@ pub(crate) async fn get_supported_versions_route( ("org.matrix.msc3026.busy_presence".to_owned(), true), /* busy presence status (https://github.com/matrix-org/matrix-spec-proposals/pull/3026) */ ("org.matrix.msc3827".to_owned(), true), /* filtering of /publicRooms by room type (https://github.com/matrix-org/matrix-spec-proposals/pull/3827) */ ("org.matrix.msc3952_intentional_mentions".to_owned(), true), /* intentional mentions (https://github.com/matrix-org/matrix-spec-proposals/pull/3952) */ - ("org.matrix.msc3575".to_owned(), true), /* sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/3575/files#r1588877046) */ ("org.matrix.msc3916.stable".to_owned(), true), /* authenticated media (https://github.com/matrix-org/matrix-spec-proposals/pull/3916) */ ("org.matrix.msc4180".to_owned(), true), /* stable flag for 3916 (https://github.com/matrix-org/matrix-spec-proposals/pull/4180) */ ("uk.tcpip.msc4133".to_owned(), true), /* Extending User Profile API with Key:Value Pairs (https://github.com/matrix-org/matrix-spec-proposals/pull/4133) */ diff --git a/src/api/router.rs b/src/api/router.rs index 399489e0..7d4e7118 100644 --- a/src/api/router.rs +++ b/src/api/router.rs @@ -143,7 +143,6 @@ pub fn build(router: Router, server: &Server) -> Router { .put(client::send_state_event_for_empty_key_route), ) .ruma_route(&client::sync_events_route) - .ruma_route(&client::sync_events_v4_route) .ruma_route(&client::sync_events_v5_route) .ruma_route(&client::get_context_route) .ruma_route(&client::get_message_events_route)