tuwunel_service/sending/sender/dispatch/push/
mod.rs1mod suppressed;
2
3use futures::{FutureExt, TryFutureExt, future::try_join3};
4use ruma::{OwnedUserId, api::error::ErrorKind};
5use tuwunel_core::{
6 Error, Event, debug, error,
7 error::error_chain,
8 extract_variant, implement,
9 smallvec::SmallVec,
10 utils::{BoolExt, IterStream, ReadyExt, stream::WidebandExt},
11 warn,
12};
13
14use super::SendingResult;
15use crate::{
16 rooms::timeline::RawPduId,
17 sending::{Destination, SendingEvent, Service},
18};
19
20type FailedIds = SmallVec<[RawPduId; 1]>;
21
22pub(super) const PUSH_WIDTH: usize = 4;
24
25#[derive(Default)]
30struct Failures {
31 ids: FailedIds,
32 error: Option<Error>,
33}
34
35#[implement(Service)]
36#[tracing::instrument(
37 name = "push",
38 level = "info",
39 skip(self, events),
40 fields(
41 events = events.len(),
42 ),
43)]
44pub(super) async fn send_events_dest_push(
45 &self,
46 user_id: OwnedUserId,
47 pushkey: String,
48 events: Vec<SendingEvent>,
49) -> SendingResult {
50 let has_pdu = events
51 .iter()
52 .any(|event| matches!(event, SendingEvent::Pdu(_)));
53
54 let destination = || Destination::Push(user_id.clone(), pushkey.clone());
55 let suppressed = self.pushing_suppressed(&user_id).map(Ok);
56 let pusher = self
57 .services
58 .pusher
59 .get_pusher(&user_id, &pushkey)
60 .map(|result| match result {
61 | Ok(pusher) => Ok(Some(pusher)),
62 | Err(error) if error.is_not_found() => {
63 error!(%user_id, %pushkey, "Pusher disappeared before delivery");
64
65 Ok(None)
66 },
67 | Err(error) => Err((destination(), error)),
68 });
69
70 let ruleset = has_pdu
71 .then_async(|| self.services.pusher.ruleset(&user_id))
72 .map(Ok);
73
74 let (pusher, ruleset, suppressed) = try_join3(pusher, ruleset, suppressed).await?;
75
76 let Some(pusher) = pusher else {
77 return Ok(Destination::Push(user_id, pushkey));
78 };
79
80 if events.contains(&SendingEvent::BadgeRefresh) {
82 let result = self
83 .services
84 .pusher
85 .send_badge_notice(&user_id, &pusher)
86 .await;
87
88 match result {
89 | Ok(()) => (),
90 | Err(error) if is_permanent_error(&error) => warn!(
91 %user_id,
92 %pushkey,
93 chain = %error_chain(&error),
94 "Dropping a badge push with a permanent local error",
95 ),
96 | Err(error) => return Err((destination(), error)),
97 }
98 }
99
100 if suppressed {
101 let queued = self
102 .enqueue_suppressed_push_events(&user_id, &pushkey, &events)
103 .await;
104
105 debug!(
106 %user_id,
107 %pushkey,
108 queued,
109 events = events.len(),
110 "Push suppressed; queued events"
111 );
112
113 return Ok(Destination::Push(user_id, pushkey));
114 }
115
116 self.schedule_flush_suppressed_for_pushkey(
117 user_id.clone(),
118 pushkey.clone(),
119 "non-suppressed push",
120 );
121
122 let Some(ruleset) = ruleset else {
123 return Ok(Destination::Push(user_id, pushkey));
124 };
125
126 let pdu_ids = || {
127 events
128 .iter()
129 .filter_map(|event| extract_variant!(event, SendingEvent::Pdu))
130 };
131
132 let failures = pdu_ids()
133 .stream()
134 .wide_filter_map(async |pdu_id| {
135 self.services
136 .timeline
137 .get_pdu_from_id(pdu_id)
138 .map_ok(|pdu| (*pdu_id, pdu))
139 .await
140 .ok()
141 })
142 .ready_filter(|(_, pdu)| !pdu.is_redacted())
143 .widen_then(Some(PUSH_WIDTH), async |(pdu_id, pdu)| {
144 let result = self
145 .services
146 .pusher
147 .send_push_notice(&user_id, &pusher, &ruleset, &pdu)
148 .await;
149
150 (pdu_id, result)
151 })
152 .ready_fold(Failures::default(), |failures, (pdu_id, result)| match result {
153 | Ok(()) => failures,
154 | Err(error) if is_permanent_error(&error) => {
155 warn!(
156 %user_id,
157 %pushkey,
158 ?pdu_id,
159 chain = %error_chain(&error),
160 "Dropping a push with a permanent local error",
161 );
162
163 failures
164 },
165 | Err(error) => failures.retain(pdu_id, error),
166 })
167 .await;
168
169 let Failures { ids, error: Some(error) } = failures else {
170 return Ok(Destination::Push(user_id, pushkey));
171 };
172
173 let destination = Destination::Push(user_id, pushkey);
174
175 pdu_ids()
176 .filter(|pdu_id| !ids.contains(*pdu_id))
177 .for_each(|pdu_id| {
178 self.db
179 .delete_active_request(&destination.event_key(pdu_id));
180 });
181
182 Err((destination, error))
183}
184
185impl Failures {
186 fn retain(mut self, pdu_id: RawPduId, error: Error) -> Self {
187 self.ids.push(pdu_id);
188 self.error = self.error.or(Some(error));
189
190 self
191 }
192}
193
194#[inline]
195fn is_permanent_error(error: &Error) -> bool {
196 matches!(error, Error::Request(ErrorKind::InvalidParam, ..))
197}