Skip to content

Commit 8e286f9

Browse files
committed
feat: experimental notification client that retries decryption if it failed the first time
1 parent ec76dd6 commit 8e286f9

File tree

3 files changed

+204
-2
lines changed

3 files changed

+204
-2
lines changed

crates/matrix-sdk-ui/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@ version = "0.6.0"
44
edition = "2021"
55

66
[features]
7-
default = ["e2e-encryption", "native-tls", "experimental-room-list", "experimental-encryption-sync", "experimental-app"]
7+
default = ["e2e-encryption", "native-tls", "experimental-room-list", "experimental-encryption-sync", "experimental-app", "experimental-notification-client"]
88

99
e2e-encryption = ["matrix-sdk/e2e-encryption"]
1010

1111
native-tls = ["matrix-sdk/native-tls"]
1212
rustls-tls = ["matrix-sdk/rustls-tls"]
1313

1414
experimental-app = ["experimental-room-list", "experimental-encryption-sync"]
15-
experimental-room-list = ["experimental-sliding-sync", "dep:async-stream", "dep:eyeball-im-util"]
1615
experimental-encryption-sync = ["experimental-sliding-sync", "dep:async-stream"]
16+
experimental-notification-client = ["experimental-encryption-sync"]
17+
experimental-room-list = ["experimental-sliding-sync", "dep:async-stream", "dep:eyeball-im-util"]
1718
experimental-sliding-sync = ["matrix-sdk/experimental-sliding-sync"]
1819

1920
testing = ["dep:eyeball-im-util"]

crates/matrix-sdk-ui/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ mod events;
1818
pub mod app;
1919
#[cfg(feature = "experimental-encryption-sync")]
2020
pub mod encryption_sync;
21+
#[cfg(feature = "experimental-notification-client")]
22+
pub mod notification_client;
2123
#[cfg(feature = "experimental-room-list")]
2224
pub mod room_list;
2325
pub mod timeline;
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// Copyright 2023 The Matrix.org Foundation C.I.C.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for that specific language governing permissions and
13+
// limitations under the License.
14+
15+
use std::{sync::Arc, time::Duration};
16+
17+
use matrix_sdk::{room::Room, Client};
18+
use matrix_sdk_base::StoreError;
19+
use ruma::{events::AnySyncTimelineEvent, EventId, RoomId};
20+
use thiserror::Error;
21+
22+
use crate::encryption_sync::{self, EncryptionSync, WithLocking};
23+
24+
pub struct NotificationClient {
25+
client: Client,
26+
with_cross_process_lock: bool,
27+
retry_decryption: bool,
28+
filter_by_push_rules: bool,
29+
}
30+
31+
impl NotificationClient {
32+
pub fn builder(client: Client) -> NotificationClientBuilder {
33+
NotificationClientBuilder::new(client)
34+
}
35+
36+
pub async fn get_notification_item(
37+
&self,
38+
room_id: &RoomId,
39+
event_id: &EventId,
40+
) -> Result<Option<NotificationItem>, Error> {
41+
let Some(room) = self.client.get_room(&room_id) else { return Err(Error::UnknownRoom) };
42+
43+
let (ruma_event, event) = loop {
44+
let ruma_event = room.event(&event_id).await?;
45+
if self.filter_by_push_rules
46+
&& !ruma_event.push_actions.iter().any(|a| a.should_notify())
47+
{
48+
return Ok(None);
49+
}
50+
51+
let event: AnySyncTimelineEvent =
52+
ruma_event.event.deserialize().map_err(|_| Error::InvalidRumaEvent)?.into();
53+
54+
let event_type = event.event_type();
55+
56+
let is_still_encrypted =
57+
matches!(event_type, ruma::events::TimelineEventType::RoomEncrypted);
58+
59+
#[cfg(feature = "unstable-msc3956")]
60+
let is_still_encrypted = is_still_encrypted
61+
|| matches!(event_type, ruma::events::TimelineEventType::Encrypted);
62+
63+
if is_still_encrypted && self.retry_decryption {
64+
// The message is still encrypted, and the client is configured to retry
65+
// decryption.
66+
//
67+
// Spawn an `EncryptionSync` that runs two iterations of the sliding sync loop:
68+
// - the first iteration allows to get SS events as well as send e2ee requests.
69+
// - the second one let the SS proxy forward events triggered by the sending of
70+
// e2ee requests.
71+
//
72+
// Keep timeouts small for both, since we might be short on time.
73+
74+
let with_locking =
75+
if self.with_cross_process_lock { WithLocking::Yes } else { WithLocking::No };
76+
77+
let encryption_sync = EncryptionSync::new(
78+
"notifications".to_owned(),
79+
self.client.clone(),
80+
Some((Duration::from_secs(3), Duration::from_secs(1))),
81+
with_locking,
82+
)
83+
.await;
84+
85+
if let Ok(sync) = encryption_sync {
86+
if sync.run_fixed_iterations(2).await.is_ok() {
87+
continue;
88+
}
89+
}
90+
}
91+
92+
break (ruma_event, event);
93+
};
94+
95+
let sender = match &room {
96+
Room::Invited(invited) => invited.invite_details().await?.inviter,
97+
_ => room.get_member(event.sender()).await?,
98+
};
99+
let mut sender_display_name = None;
100+
let mut sender_avatar_url = None;
101+
if let Some(sender) = sender {
102+
sender_display_name = sender.display_name().map(|s| s.to_owned());
103+
sender_avatar_url = sender.avatar_url().map(|s| s.to_string());
104+
}
105+
106+
let is_noisy = ruma_event.push_actions.iter().any(|a| a.sound().is_some());
107+
108+
let item = NotificationItem {
109+
event: Arc::new(event),
110+
room_id: room.room_id().to_string(),
111+
sender_display_name,
112+
sender_avatar_url,
113+
room_display_name: room.display_name().await?.to_string(),
114+
room_avatar_url: room.avatar_url().map(|s| s.to_string()),
115+
room_canonical_alias: room.canonical_alias().map(|c| c.to_string()),
116+
is_noisy,
117+
is_direct: room.is_direct().await?,
118+
is_room_encrypted: room.is_encrypted().await.ok(),
119+
};
120+
121+
Ok(Some(item))
122+
}
123+
}
124+
125+
pub struct NotificationClientBuilder {
126+
with_cross_process_lock: bool,
127+
filter_by_push_rules: bool,
128+
retry_decryption: bool,
129+
client: Client,
130+
}
131+
132+
impl NotificationClientBuilder {
133+
fn new(client: Client) -> Self {
134+
Self {
135+
with_cross_process_lock: false,
136+
filter_by_push_rules: false,
137+
retry_decryption: false,
138+
client,
139+
}
140+
}
141+
142+
pub fn with_cross_process_lock(mut self) -> Self {
143+
self.with_cross_process_lock = true;
144+
self
145+
}
146+
147+
pub fn filter_by_push_rules(mut self) -> Self {
148+
self.filter_by_push_rules = true;
149+
self
150+
}
151+
152+
pub fn retry_decryption(mut self) -> Self {
153+
self.retry_decryption = true;
154+
self
155+
}
156+
157+
pub fn build(self) -> NotificationClient {
158+
NotificationClient {
159+
client: self.client,
160+
with_cross_process_lock: self.with_cross_process_lock,
161+
filter_by_push_rules: self.filter_by_push_rules,
162+
retry_decryption: self.retry_decryption,
163+
}
164+
}
165+
}
166+
167+
pub struct NotificationItem {
168+
pub event: Arc<AnySyncTimelineEvent>,
169+
pub room_id: String,
170+
171+
pub sender_display_name: Option<String>,
172+
pub sender_avatar_url: Option<String>,
173+
174+
pub room_display_name: String,
175+
pub room_avatar_url: Option<String>,
176+
pub room_canonical_alias: Option<String>,
177+
178+
pub is_noisy: bool,
179+
pub is_direct: bool,
180+
pub is_room_encrypted: Option<bool>,
181+
}
182+
183+
#[derive(Debug, Error)]
184+
pub enum Error {
185+
#[error("unknown room for a notification")]
186+
UnknownRoom,
187+
188+
#[error("invalid ruma event")]
189+
InvalidRumaEvent,
190+
191+
#[error(transparent)]
192+
SdkError(#[from] matrix_sdk::Error),
193+
194+
#[error(transparent)]
195+
StoreError(#[from] StoreError),
196+
197+
#[error(transparent)]
198+
EncryptionSync(#[from] encryption_sync::Error),
199+
}

0 commit comments

Comments
 (0)