Skip to main content

tuwunel_api_client/
events.rs

1use 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
26/// One user's listen on one room's event stream.
27///
28/// A non-member's listen is a peek, which sees only what a room preview may
29/// show.
30struct Listen<'a> {
31	services: &'a Services,
32	sender_user: &'a UserId,
33	peeking: bool,
34}
35
36/// GET `/_matrix/client/v3/events`
37pub(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	// The endpoint listens for new events, so a stream without a token starts now.
62	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		// Any new event answers, hidden or not, so no later wake rescans the window.
102		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
122/// The page for a window of new events, or `None` while the window is empty.
123///
124/// The peek keeps the event it looked at, so the page scans the window once.
125async 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
144/// The events of a window the user may see, as one page of the stream.
145///
146/// A full page ends at its last event. A short one scanned the whole window,
147/// hidden events included, so it ends at `next_batch`. An empty page starts
148/// where the stream did.
149async 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
178/// Keeps an event the listener may see.
179///
180/// A member gets the general history-visibility rule. A peek gets only what a
181/// room preview may show: events sent while the room was world-readable, and the
182/// event that made it so.
183async 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}