Skip to content

feat(push): add experimental support for MSC3768 (in-app-only notifications) #5441

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions bindings/matrix-sdk-ffi/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ All notable changes to this project will be documented in this file.
This is primarily for Element X to give a dedicated error message in case
it connects a homeserver with only this method available.
([#5222](https://github.com/matrix-org/matrix-rust-sdk/pull/5222))
- Add `Action::NotifyInApp` and `RoomNotificationMode::PushMentionsAndKeywordsOnly` behind
a new feature `unstable-msc3768`.
([#5441](https://github.com/matrix-org/matrix-rust-sdk/pull/5441))

### Breaking changes:

Expand Down
1 change: 1 addition & 0 deletions bindings/matrix-sdk-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ crate-type = [
[features]
default = ["bundled-sqlite", "unstable-msc4274"]
bundled-sqlite = ["matrix-sdk/bundled-sqlite"]
unstable-msc3768 = ["matrix-sdk-ui/unstable-msc3768"]
unstable-msc4274 = ["matrix-sdk-ui/unstable-msc4274"]
# Required when targeting a Javascript environment, like Wasm in a browser.
js = ["matrix-sdk-ui/js"]
Expand Down
25 changes: 22 additions & 3 deletions bindings/matrix-sdk-ffi/src/notification_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,13 @@ impl TryFrom<Tweak> for SdkTweak {
#[derive(Clone, uniffi::Enum)]
/// Enum representing the push notification actions for a rule.
pub enum Action {
/// Causes matching events to generate a notification.
/// Causes matching events to generate a notification (both in-app and
/// remote / push).
Notify,
/// Causes matching events to generate an in-app notification but no remote
/// (push) notification.
#[cfg(feature = "unstable-msc3768")]
NotifyInApp,
/// Sets an entry in the 'tweaks' dictionary sent to the push gateway.
SetTweak { value: Tweak },
}
Expand All @@ -334,6 +339,8 @@ impl TryFrom<SdkAction> for Action {
fn try_from(value: SdkAction) -> Result<Self, Self::Error> {
Ok(match value {
SdkAction::Notify => Self::Notify,
#[cfg(feature = "unstable-msc3768")]
SdkAction::NotifyInApp => Self::NotifyInApp,
SdkAction::SetTweak(tweak) => Self::SetTweak {
value: tweak.try_into().map_err(|e| format!("Failed to convert tweak: {e}"))?,
},
Expand All @@ -348,6 +355,8 @@ impl TryFrom<Action> for SdkAction {
fn try_from(value: Action) -> Result<Self, Self::Error> {
Ok(match value {
Action::Notify => Self::Notify,
#[cfg(feature = "unstable-msc3768")]
Action::NotifyInApp => Self::NotifyInApp,
Action::SetTweak { value } => Self::SetTweak(
value.try_into().map_err(|e| format!("Failed to convert tweak: {e}"))?,
),
Expand All @@ -358,10 +367,14 @@ impl TryFrom<Action> for SdkAction {
/// Enum representing the push notification modes for a room.
#[derive(Clone, uniffi::Enum)]
pub enum RoomNotificationMode {
/// Receive notifications for all messages.
/// Receive remote and in-app notifications for all messages.
AllMessages,
/// Receive notifications for mentions and keywords only.
/// Receive remote and in-app notifications for mentions and keywords only.
MentionsAndKeywordsOnly,
/// Receive remote and in-app notifications for mentions and keywords and
/// in-app notifications only for other room messages.
#[cfg(feature = "unstable-msc3768")]
PushMentionsAndKeywordsOnly,
/// Do not receive any notifications.
Mute,
}
Expand All @@ -371,6 +384,10 @@ impl From<SdkRoomNotificationMode> for RoomNotificationMode {
match value {
SdkRoomNotificationMode::AllMessages => Self::AllMessages,
SdkRoomNotificationMode::MentionsAndKeywordsOnly => Self::MentionsAndKeywordsOnly,
#[cfg(feature = "unstable-msc3768")]
SdkRoomNotificationMode::PushMentionsAndKeywordsOnly => {
Self::PushMentionsAndKeywordsOnly
}
SdkRoomNotificationMode::Mute => Self::Mute,
}
}
Expand All @@ -381,6 +398,8 @@ impl From<RoomNotificationMode> for SdkRoomNotificationMode {
match value {
RoomNotificationMode::AllMessages => Self::AllMessages,
RoomNotificationMode::MentionsAndKeywordsOnly => Self::MentionsAndKeywordsOnly,
#[cfg(feature = "unstable-msc3768")]
RoomNotificationMode::PushMentionsAndKeywordsOnly => Self::PushMentionsAndKeywordsOnly,
RoomNotificationMode::Mute => Self::Mute,
}
}
Expand Down
3 changes: 3 additions & 0 deletions crates/matrix-sdk-base/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ All notable changes to this project will be documented in this file.
`RoomInfo::invite_details` method returns both the timestamp and the
inviter.
([#5390](https://github.com/matrix-org/matrix-rust-sdk/pull/5390))
- Add `RoomNotificationMode::PushMentionsAndKeywordsOnly` behind a new
feature `unstable-msc3768`.
([#5441](https://github.com/matrix-org/matrix-rust-sdk/pull/5441

### Refactor
- [**breaking**] `RelationalLinkedChunk::items` now takes a `RoomId` instead of an
Expand Down
1 change: 1 addition & 0 deletions crates/matrix-sdk-base/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ qrcode = ["matrix-sdk-crypto?/qrcode"]
automatic-room-key-forwarding = ["matrix-sdk-crypto?/automatic-room-key-forwarding"]
experimental-send-custom-to-device = ["matrix-sdk-crypto?/experimental-send-custom-to-device"]
uniffi = ["dep:uniffi", "matrix-sdk-crypto?/uniffi", "matrix-sdk-common/uniffi"]
unstable-msc3768 = ["ruma/unstable-msc3768"]

# Private feature, see
# https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823 for the gory
Expand Down
8 changes: 6 additions & 2 deletions crates/matrix-sdk-base/src/notification_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@ use serde::{Deserialize, Serialize};
/// Enum representing the push notification modes for a room.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub enum RoomNotificationMode {
/// Receive notifications for all messages.
/// Receive remote and in-app notifications for all messages.
AllMessages,
/// Receive notifications for mentions and keywords only.
/// Receive remote and in-app notifications for mentions and keywords only.
MentionsAndKeywordsOnly,
/// Receive remote and in-app notifications for mentions and keywords and
/// in-app notifications only for other room messages.
#[cfg(feature = "unstable-msc3768")]
PushMentionsAndKeywordsOnly,
/// Do not receive any notifications.
Mute,
}
11 changes: 11 additions & 0 deletions crates/matrix-sdk-base/src/read_receipts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,17 @@ mod tests {
assert_eq!(receipts.num_mentions, 1);
assert_eq!(receipts.num_notifications, 1);

// NotifyInApp is treated like Notify
#[cfg(feature = "unstable-msc3768")]
{
let event = make_event(user_id!("@bob:example.org"), vec![Action::NotifyInApp]);
let mut receipts = RoomReadReceipts::default();
receipts.process_event(&event, user_id, ThreadingSupport::Disabled);
assert_eq!(receipts.num_unread, 1);
assert_eq!(receipts.num_mentions, 0);
assert_eq!(receipts.num_notifications, 1);
}

// Technically this `push_actions` set would be a bug somewhere else, but let's
// make sure to resist against it.
let event = make_event(user_id!("@bob:example.org"), vec![Action::Notify, Action::Notify]);
Expand Down
4 changes: 4 additions & 0 deletions crates/matrix-sdk-ui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ rustls-tls = ["matrix-sdk/rustls-tls"]
js = ["matrix-sdk/js"]
uniffi = ["dep:uniffi", "matrix-sdk/uniffi", "matrix-sdk-base/uniffi"]

# Add support for in-app only notifications
unstable-msc3768 = ["matrix-sdk/unstable-msc3768"]

# Add support for encrypted extensible events.
unstable-msc3956 = ["ruma/unstable-msc3956"]

Expand Down Expand Up @@ -58,6 +61,7 @@ tracing = { workspace = true, features = ["attributes"] }
unicode-normalization.workspace = true
uniffi = { workspace = true, optional = true }

cfg-if = "1.0.0"
emojis = "0.6.4"
unicode-segmentation = "1.12.0"

Expand Down
16 changes: 14 additions & 2 deletions crates/matrix-sdk-ui/src/notification_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use std::{
time::Duration,
};

use cfg_if::cfg_if;
use futures_util::{StreamExt as _, pin_mut};
use matrix_sdk::{
Client, ClientBuildError, SlidingSyncList, SlidingSyncMode, room::Room, sleep::sleep,
Expand Down Expand Up @@ -677,7 +678,7 @@ impl NotificationClient {

let should_notify = push_actions
.as_ref()
.is_some_and(|actions| actions.iter().any(|a| a.should_notify()));
.is_some_and(|actions| actions.iter().any(should_action_notify_remote));

if !should_notify {
// The event has been filtered out by the user's push rules.
Expand Down Expand Up @@ -749,7 +750,7 @@ impl NotificationClient {
}

if let Some(actions) = timeline_event.push_actions()
&& !actions.iter().any(|a| a.should_notify())
&& !actions.iter().any(should_action_notify_remote)
{
return Ok(NotificationStatus::EventFilteredOut);
}
Expand All @@ -771,6 +772,17 @@ impl NotificationClient {
}
}

fn should_action_notify_remote(action: &Action) -> bool {
cfg_if! {
if #[cfg(feature = "unstable-msc3768")] {
action.should_notify_remote()
} else {
// Before MSC3768 only combined remote/local notifications existed
action.should_notify()
}
}
}

fn is_event_encrypted(event_type: TimelineEventType) -> bool {
let is_still_encrypted = matches!(event_type, TimelineEventType::RoomEncrypted);

Expand Down
3 changes: 3 additions & 0 deletions crates/matrix-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ experimental-widgets = ["dep:uuid", "experimental-send-custom-to-device"]

docsrs = ["e2e-encryption", "sqlite", "indexeddb", "sso-login", "qrcode"]

# Add support for in-app only notifications
unstable-msc3768 = ["matrix-sdk-base/unstable-msc3768"]

# Add support for inline media galleries via msgtypes
unstable-msc4274 = ["ruma/unstable-msc4274", "matrix-sdk-base/unstable-msc4274"]

Expand Down
43 changes: 30 additions & 13 deletions crates/matrix-sdk/src/notification_settings/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ use crate::NotificationSettingsError;
#[derive(Clone, Debug)]
pub(crate) enum Command {
/// Set a new `Room` push rule
SetRoomPushRule { room_id: OwnedRoomId, notify: bool },
SetRoomPushRule { room_id: OwnedRoomId, notify: Notify },
/// Set a new `Override` push rule matching a `RoomId`
SetOverridePushRule { rule_id: String, room_id: OwnedRoomId, notify: bool },
SetOverridePushRule { rule_id: String, room_id: OwnedRoomId, notify: Notify },
/// Set a new push rule for a keyword.
SetKeywordPushRule { keyword: String },
/// Set whether a push rule is enabled
Expand All @@ -29,21 +29,13 @@ pub(crate) enum Command {
SetCustomPushRule { rule: NewPushRule },
}

fn get_notify_actions(notify: bool) -> Vec<Action> {
if notify {
vec![Action::Notify, Action::SetTweak(Tweak::Sound("default".into()))]
} else {
vec![]
}
}

impl Command {
/// Tries to create a push rule corresponding to this command
pub(crate) fn to_push_rule(&self) -> Result<NewPushRule, NotificationSettingsError> {
match self {
Self::SetRoomPushRule { room_id, notify } => {
// `Room` push rule for this `room_id`
let new_rule = NewSimplePushRule::new(room_id.clone(), get_notify_actions(*notify));
let new_rule = NewSimplePushRule::new(room_id.clone(), notify.get_actions());
Ok(NewPushRule::Room(new_rule))
}

Expand All @@ -55,7 +47,7 @@ impl Command {
key: "room_id".to_owned(),
pattern: room_id.to_string(),
}],
get_notify_actions(*notify),
notify.get_actions(),
);
Ok(NewPushRule::Override(new_rule))
}
Expand All @@ -65,7 +57,7 @@ impl Command {
let new_rule = NewPatternedPushRule::new(
keyword.clone(),
keyword.clone(),
get_notify_actions(true),
Notify::All.get_actions(),
);
Ok(NewPushRule::Content(new_rule))
}
Expand All @@ -80,3 +72,28 @@ impl Command {
}
}
}

/// Enum describing if and how to deliver a notification.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum Notify {
/// Generate a notification both in-app and remote / push.
All,

/// Only generate an in-app notification but no remote / push notification.
#[cfg(feature = "unstable-msc3768")]
InAppOnly,

/// Don't notify at all.
None,
}

impl Notify {
fn get_actions(&self) -> Vec<Action> {
match self {
Self::All => vec![Action::Notify, Action::SetTweak(Tweak::Sound("default".into()))],
#[cfg(feature = "unstable-msc3768")]
Self::InAppOnly => vec![Action::NotifyInApp],
Self::None => Vec::new(),
}
}
}
15 changes: 11 additions & 4 deletions crates/matrix-sdk/src/notification_settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub use matrix_sdk_base::notification_settings::RoomNotificationMode;

use crate::{
config::RequestConfig, error::NotificationSettingsError, event_handler::EventHandlerDropGuard,
Client, Result,
notification_settings::command::Notify, Client, Result,
};

/// Whether or not a room is encrypted
Expand Down Expand Up @@ -320,15 +320,20 @@ impl NotificationSettings {
let (new_rule_kind, notify) = match mode {
RoomNotificationMode::AllMessages => {
// insert a `Room` rule which notifies
(RuleKind::Room, true)
(RuleKind::Room, Notify::All)
}
RoomNotificationMode::MentionsAndKeywordsOnly => {
// insert a `Room` rule which doesn't notify
(RuleKind::Room, false)
(RuleKind::Room, Notify::None)
}
#[cfg(feature = "unstable-msc3768")]
RoomNotificationMode::PushMentionsAndKeywordsOnly => {
// insert a `Room` rule which notifies in-app only
(RuleKind::Room, Notify::InAppOnly)
}
RoomNotificationMode::Mute => {
// insert an `Override` rule which doesn't notify
(RuleKind::Override, false)
(RuleKind::Override, Notify::None)
}
};

Expand Down Expand Up @@ -923,6 +928,8 @@ mod tests {
let new_modes = [
RoomNotificationMode::AllMessages,
RoomNotificationMode::MentionsAndKeywordsOnly,
#[cfg(feature = "unstable-msc3768")]
RoomNotificationMode::PushMentionsAndKeywordsOnly,
RoomNotificationMode::Mute,
];
for new_mode in new_modes {
Expand Down
Loading