From 82c583b5bc9dd176f10f258285718ffbb74f7c0c Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Tue, 4 Nov 2025 15:55:41 +0100 Subject: [PATCH] feat(ffi): expose Client::register_notification_handler Signed-off-by: Johannes Marbach --- bindings/matrix-sdk-ffi/CHANGELOG.md | 2 + bindings/matrix-sdk-ffi/src/client.rs | 82 ++++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/bindings/matrix-sdk-ffi/CHANGELOG.md b/bindings/matrix-sdk-ffi/CHANGELOG.md index 728e8b26a..cfb128dc0 100644 --- a/bindings/matrix-sdk-ffi/CHANGELOG.md +++ b/bindings/matrix-sdk-ffi/CHANGELOG.md @@ -62,6 +62,8 @@ All notable changes to this project will be documented in this file. ### Features +- Add `Client::register_notification_handler` for observing notifications generated from sync responses. + ([#5831](https://github.com/matrix-org/matrix-rust-sdk/pull/5831)) - Add `Room::mark_as_fully_read_unchecked` so clients can mark a room as read without needing a `Timeline` instance. Note this method is not recommended as it can potentially cause incorrect read receipts, but it can needed in certain cases. - Add `Timeline::latest_event_id` to be able to fetch the event id of the latest event of the timeline. - Add `Room::load_or_fetch_event` so we can get a `TimelineEvent` given its event id ([#5678](https://github.com/matrix-org/matrix-rust-sdk/pull/5678)). diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index 5ed22d74c..ad39d52f0 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -16,6 +16,7 @@ use matrix_sdk::{ authentication::oauth::{ AccountManagementActionFull, ClientId, OAuthAuthorizationData, OAuthSession, }, + deserialized_responses::RawAnySyncOrStrippedTimelineEvent, media::{MediaFormat, MediaRequestParameters, MediaRetentionPolicy, MediaThumbnailSettings}, ruma::{ api::client::{ @@ -101,7 +102,7 @@ use crate::{ authentication::{HomeserverLoginDetails, OidcConfiguration, OidcError, SsoError, SsoHandler}, client, encryption::Encryption, - notification::NotificationClient, + notification::{NotificationClient, NotificationEvent}, notification_settings::NotificationSettings, qr_code::LoginWithQrCodeHandler, room::{RoomHistoryVisibility, RoomInfoListener, RoomSendQueueUpdate}, @@ -225,6 +226,25 @@ pub trait RoomAccountDataListener: SyncOutsideWasm + SendOutsideWasm { fn on_change(&self, event: RoomAccountDataEvent, room_id: String); } +/// A listener for notifications generated from sync responses. +/// +/// This is called during sync for each event that triggers a notification +/// based on the user's push rules. +#[matrix_sdk_ffi_macros::export(callback_interface)] +pub trait SyncNotificationListener: SyncOutsideWasm + SendOutsideWasm { + /// Called when a notifying event is received during sync. + fn on_notification(&self, notification: SyncNotification, room_id: String); +} + +/// A notification generated from a sync response. +#[derive(uniffi::Record)] +pub struct SyncNotification { + /// The push actions for this notification (notify, sound, highlight, etc.) + pub actions: Vec, + /// The event that triggered the notification + pub event: NotificationEvent, +} + #[derive(Clone, Copy, uniffi::Record)] pub struct TransmissionProgress { pub current: u64, @@ -818,6 +838,66 @@ impl Client { } } + /// Register a handler for notifications generated from sync responses. + /// + /// The handler will be called during sync for each event that triggers + /// a notification based on the user's push rules. + /// + /// The handler receives: + /// - The notification with push actions and event data + /// - The room ID where the notification occurred + /// + /// This is useful for implementing custom notification logic, such as + /// displaying local notifications or updating notification badges. + pub async fn register_notification_handler(&self, listener: Box) { + let listener = Arc::new(listener); + self.inner + .register_notification_handler(move |notification, room, _client| { + let listener = listener.clone(); + let room_id = room.room_id().to_string(); + + async move { + // Convert SDK actions to FFI type + let actions: Vec = notification + .actions + .into_iter() + .filter_map(|action| action.try_into().ok()) + .collect(); + + // Convert SDK event to FFI type + let event = match notification.event { + RawAnySyncOrStrippedTimelineEvent::Sync(raw) => match raw.deserialize() { + Ok(deserialized) => NotificationEvent::Timeline { + event: Arc::new(crate::event::TimelineEvent(Box::new( + deserialized, + ))), + }, + Err(err) => { + tracing::warn!("Failed to deserialize timeline event: {err}"); + return; + } + }, + RawAnySyncOrStrippedTimelineEvent::Stripped(raw) => { + match raw.deserialize() { + Ok(deserialized) => NotificationEvent::Invite { + sender: deserialized.sender().to_string(), + }, + Err(err) => { + tracing::warn!( + "Failed to deserialize stripped state event: {err}" + ); + return; + } + } + } + }; + + listener.on_notification(SyncNotification { actions, event }, room_id); + } + }) + .await; + } + /// Allows generic GET requests to be made through the SDK's internal HTTP /// client. This is useful when the caller's native HTTP client wouldn't /// have the same configuration (such as certificates, proxies, etc.) This