tuwunel_api_client/
events.rs1use std::iter::once;
2
3use axum::extract::State;
4use futures::{Stream, StreamExt, future::ok, pin_mut};
5use ruma::{
6 UserId,
7 api::client::peeking::listen_to_new_events::v3::{Request, Response},
8};
9use tokio::time::timeout_at;
10use tuwunel_core::{
11 Err, Result, err,
12 utils::{
13 BoolExt, OptionExt,
14 future::OptionFutureExt,
15 stream::{IterStream, ReadyExt, WidebandExt},
16 },
17};
18use tuwunel_matrix::{Event, PduCount};
19use tuwunel_service::{Services, rooms::timeline::PdusIterItem};
20
21use super::visibility_filter;
22use crate::{Ruma, utils::sync_deadline};
23
24const EVENT_LIMIT: usize = 50;
25
26struct Listen<'a> {
31 services: &'a Services,
32 sender_user: &'a UserId,
33 peeking: bool,
34}
35
36pub(crate) async fn events_route(
38 State(services): State<crate::State>,
39 body: Ruma<Request>,
40) -> Result<Response> {
41 let sender_user = body.sender_user();
42
43 let room_id = body.room_id.as_ref();
44
45 let peeking = services
46 .state_cache
47 .is_joined(sender_user, room_id)
48 .await
49 .is_false();
50
51 if peeking
52 && services
53 .state_accessor
54 .is_world_readable(room_id)
55 .await
56 .is_false()
57 {
58 return Err!(Request(Forbidden("No room preview available.")));
59 }
60
61 let from = body
63 .body
64 .from
65 .as_deref()
66 .map(str::parse)
67 .transpose()
68 .map_err(|_| err!(Request(InvalidParam("Invalid `from` token."))))?
69 .map_async(ok)
70 .unwrap_or_else_async(async || {
71 services
72 .globals
73 .wait_pending()
74 .await
75 .map(PduCount::Normal)
76 })
77 .await?;
78
79 let listen = Listen {
80 services: &services,
81 sender_user,
82 peeking,
83 };
84
85 let stop_at = sync_deadline(&services, body.body.timeout);
86
87 loop {
88 let watchers = services
89 .sync
90 .watch(sender_user, body.sender_device.as_deref(), once(room_id).stream())
91 .await;
92
93 let next_batch = services.globals.wait_pending().await?;
94
95 let window = services
96 .timeline
97 .pdus(Some(sender_user), room_id, Some(from))
98 .ready_filter_map(Result::ok)
99 .ready_take_while(|(count, _)| PduCount::Normal(next_batch).ge(count));
100
101 if let Some(response) = window_page(&listen, window, from, next_batch).await {
103 return Ok(response);
104 }
105
106 if timeout_at(stop_at, watchers).await.is_err() || services.server.is_stopping() {
107 return Ok(Response {
108 chunk: Default::default(),
109 start: from.to_string().into(),
110 end: services
111 .server
112 .is_stopping()
113 .is_false()
114 .then_some(next_batch)
115 .as_ref()
116 .map(ToString::to_string),
117 });
118 }
119 }
120}
121
122async fn window_page<Window>(
126 listen: &Listen<'_>,
127 window: Window,
128 from: PduCount,
129 next_batch: u64,
130) -> Option<Response>
131where
132 Window: Stream<Item = PdusIterItem> + Send,
133{
134 let window = window.peekable();
135
136 pin_mut!(window);
137 window.as_mut().peek().await?;
138
139 visible_page(listen, window, from, next_batch)
140 .await
141 .into()
142}
143
144async fn visible_page<Window>(
150 listen: &Listen<'_>,
151 window: Window,
152 from: PduCount,
153 next_batch: u64,
154) -> Response
155where
156 Window: Stream<Item = PdusIterItem> + Send,
157{
158 let (first, last, chunk) = window
159 .wide_filter_map(|item| listen_filter(listen, item))
160 .take(EVENT_LIMIT)
161 .ready_fold((None, None, Vec::new()), |(first, _, mut chunk), (count, pdu)| {
162 chunk.push(pdu.into_format());
163 (first.or(Some(count)), Some(count), chunk)
164 })
165 .await;
166
167 let start = first.unwrap_or(from).to_string().into();
168
169 let end = last
170 .filter(|_| chunk.len().eq(&EVENT_LIMIT))
171 .unwrap_or(PduCount::Normal(next_batch))
172 .to_string()
173 .into();
174
175 Response { start, end, chunk }
176}
177
178async fn listen_filter(listen: &Listen<'_>, item: PdusIterItem) -> Option<PdusIterItem> {
184 if !listen.peeking {
185 return visibility_filter(listen.services, item, listen.sender_user).await;
186 }
187
188 let (_, pdu) = &item;
189
190 listen
191 .services
192 .state_accessor
193 .is_world_readable_at(pdu)
194 .await
195 .then_some(item)
196}