1use axum::extract::State;
2use futures::{
3 FutureExt, TryFutureExt, TryStreamExt,
4 future::{ok, try_join, try_join4},
5};
6use ruma::{
7 RoomId,
8 api::client::room::initial_sync::v3::{PaginationChunk, Request, Response},
9 events::{
10 AnyRawAccountDataEvent,
11 StateEventType::RoomMember,
12 room::member::{MembershipState, RoomMemberEventContent},
13 },
14};
15use tuwunel_core::{
16 Event, Result, at, err, extract_variant,
17 matrix::{Pdu, PduCount},
18 utils::{
19 BoolExt, TryReadyExt,
20 result::NotFound,
21 stream::{TryTools, TryWidebandExt},
22 },
23};
24use tuwunel_service::rooms::short::ShortStateHash;
25
26use crate::{Ruma, client::visibility_filter};
27
28const LIMIT_MAX: usize = 50;
29
30pub(crate) async fn room_initial_sync_route(
32 State(services): State<crate::State>,
33 body: Ruma<Request>,
34) -> Result<Response> {
35 let room_id = &body.room_id;
36 let sender_user = body.sender_user();
37
38 let cached_membership = services
40 .state_cache
41 .user_membership(sender_user, room_id)
42 .await;
43
44 matches!(cached_membership.as_ref(), Some(MembershipState::Ban))
45 .is_false()
46 .ok_or_else(|| err!(Request(Forbidden("No room preview available."))))?;
47
48 services
49 .state_accessor
50 .user_can_see_state_events(sender_user, room_id)
51 .await
52 .ok_or_else(|| err!(Request(Forbidden("No room preview available."))))?;
53
54 let current_shortstatehash = services
55 .state
56 .get_room_shortstatehash(room_id)
57 .await?;
58
59 let member = services
60 .state_accessor
61 .state_get(current_shortstatehash, &RoomMember, sender_user.as_str())
62 .await
63 .optional()?;
64
65 let membership = member
66 .as_ref()
67 .map(Event::get_content)
68 .transpose()?
69 .map(|content: RoomMemberEventContent| content.membership);
70
71 let next_batch = services.globals.current_count();
72 let departure = membership
73 .as_ref()
74 .filter(|membership| matches!(membership, MembershipState::Leave | MembershipState::Ban))
75 .zip(member.as_ref())
76 .map(|(membership, pdu)| {
77 departure_snapshot(
78 &services,
79 room_id,
80 pdu,
81 membership.to_owned(),
82 current_shortstatehash,
83 )
84 .left_future()
85 });
86
87 let current_snapshot = ok((PduCount::Normal(next_batch), current_shortstatehash, membership));
88 let (timeline_end, shortstatehash, membership) = departure
89 .unwrap_or_else(|| current_snapshot.right_future())
90 .await?;
91
92 let visibility = services.directory.visibility(room_id).map(Ok);
93 let limit = body.limit.unwrap_or(LIMIT_MAX).min(LIMIT_MAX);
94 let state = services
95 .state_accessor
96 .state_full_pdus_strict(shortstatehash)
97 .map_ok(Event::into_format)
98 .try_collect::<Vec<_>>();
99
100 let events = services
101 .timeline
102 .pdus_rev(Some(sender_user), room_id, Some(timeline_end.saturating_add(1)))
103 .wide_and_then(|item| visibility_filter(&services, item, sender_user).map(Ok))
104 .ready_try_filter_map(Ok)
105 .try_take(limit)
106 .try_collect()
107 .map_ok(|mut vec: Vec<_>| {
108 vec.reverse();
109 vec
110 });
111
112 let account_data = services
113 .account_data
114 .changes_since_fallible(
115 Some(room_id),
116 sender_user,
117 0,
118 Some(timeline_end.into_normal().into_unsigned()),
119 )
120 .ready_try_filter_map(|e| Ok(extract_variant!(e, AnyRawAccountDataEvent::Room)))
121 .try_collect::<Vec<_>>();
122
123 let (visibility, state, events, account_data) = try_join4(visibility, state, events, account_data)
124 .boxed() .await?;
126
127 Ok(Response {
128 room_id: room_id.to_owned(),
129 membership,
130 visibility: visibility.into(),
131 account_data: Some(account_data),
132 state: state.into(),
133 messages: PaginationChunk {
134 start: events
135 .first()
136 .map(at!(0))
137 .as_ref()
138 .map(ToString::to_string),
139
140 end: events
141 .last()
142 .map(at!(0))
143 .as_ref()
144 .map_or_else(|| timeline_end.to_string(), ToString::to_string),
145
146 chunk: events
147 .into_iter()
148 .map(at!(1))
149 .map(Event::into_format)
150 .collect(),
151 }
152 .into(),
153 })
154}
155
156async fn departure_snapshot(
157 services: &crate::State,
158 room_id: &RoomId,
159 pdu: &Pdu,
160 membership: MembershipState,
161 current_shortstatehash: ShortStateHash,
162) -> Result<(PduCount, ShortStateHash, Option<MembershipState>)> {
163 let timeline_end = services.timeline.get_pdu_count(pdu.event_id());
164 let latest_count = services
165 .timeline
166 .last_timeline_count(None, room_id, None);
167
168 let (timeline_end, latest_count) = try_join(timeline_end, latest_count).await?;
169
170 let shortstatehash = (latest_count == timeline_end)
171 .then(|| ok(current_shortstatehash).left_future())
172 .unwrap_or_else(|| {
173 services
174 .timeline
175 .next_shortstatehash(room_id, timeline_end)
176 .right_future()
177 })
178 .await?;
179
180 Ok((timeline_end, shortstatehash, Some(membership)))
181}