From 21e40095257f9cafa44a570c4bea6ed815865390 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Tue, 14 May 2024 13:09:18 +0300 Subject: [PATCH] feat(calls): add support for sending Matrix RTC call notifications --- Cargo.toml | 1 + bindings/matrix-sdk-ffi/src/event.rs | 13 +++++- bindings/matrix-sdk-ffi/src/room.rs | 57 ++++++++++++++++++++++++- bindings/matrix-sdk-ffi/src/ruma.rs | 26 ++++++++++++ crates/matrix-sdk/CHANGELOG.md | 1 + crates/matrix-sdk/src/room/mod.rs | 62 ++++++++++++++++++++++++++-- 6 files changed, 154 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b3ab4fbf687..67b4722e126 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ ruma = { version = "0.10.0", features = [ "compat-tag-info", "unstable-msc3401", "unstable-msc3266", + "unstable-msc4075" ] } ruma-common = { version = "0.13.0" } once_cell = "1.16.0" diff --git a/bindings/matrix-sdk-ffi/src/event.rs b/bindings/matrix-sdk-ffi/src/event.rs index ebddc176cd2..fe141b5028d 100644 --- a/bindings/matrix-sdk-ffi/src/event.rs +++ b/bindings/matrix-sdk-ffi/src/event.rs @@ -5,7 +5,11 @@ use ruma::events::{ RedactedStateEventContent, StaticStateEventContent, SyncMessageLikeEvent, SyncStateEvent, }; -use crate::{room_member::MembershipState, ruma::MessageType, ClientError}; +use crate::{ + room_member::MembershipState, + ruma::{MessageType, NotifyType}, + ClientError, +}; #[derive(uniffi::Object)] pub struct TimelineEvent(pub(crate) AnySyncTimelineEvent); @@ -119,6 +123,7 @@ pub enum MessageLikeEventContent { CallInvite, CallHangup, CallCandidates, + CallNotify { notify_type: NotifyType }, KeyVerificationReady, KeyVerificationStart, KeyVerificationCancel, @@ -143,6 +148,12 @@ impl TryFrom for MessageLikeEventContent { AnySyncMessageLikeEvent::CallInvite(_) => MessageLikeEventContent::CallInvite, AnySyncMessageLikeEvent::CallHangup(_) => MessageLikeEventContent::CallHangup, AnySyncMessageLikeEvent::CallCandidates(_) => MessageLikeEventContent::CallCandidates, + AnySyncMessageLikeEvent::CallNotify(content) => { + let original_content = get_message_like_event_original_content(content)?; + MessageLikeEventContent::CallNotify { + notify_type: original_content.notify_type.into(), + } + } AnySyncMessageLikeEvent::KeyVerificationReady(_) => { MessageLikeEventContent::KeyVerificationReady } diff --git a/bindings/matrix-sdk-ffi/src/room.rs b/bindings/matrix-sdk-ffi/src/room.rs index 44bfe86468a..bf90d8f0fa2 100644 --- a/bindings/matrix-sdk-ffi/src/room.rs +++ b/bindings/matrix-sdk-ffi/src/room.rs @@ -12,6 +12,7 @@ use ruma::{ api::client::room::report_content, assign, events::{ + call::notify, room::{ avatar::ImageInfo as RumaAvatarImageInfo, power_levels::RoomPowerLevels as RumaPowerLevels, MediaSource, @@ -30,7 +31,7 @@ use crate::{ event::{MessageLikeEventType, StateEventType}, room_info::RoomInfo, room_member::RoomMember, - ruma::ImageInfo, + ruma::{ImageInfo, Mentions, NotifyType}, timeline::{EventTimelineItem, FocusEventError, ReceiptType, Timeline}, utils::u64_to_uint, TaskHandle, @@ -644,6 +645,48 @@ impl Room { let event_id = EventId::parse(event_id)?; Ok(self.inner.matrix_to_event_permalink(event_id).await?.to_string()) } + + /// This will only send a call notification event if appropriate. + /// + /// This function is supposed to be called whenever the user creates a room + /// call. It will send a `m.call.notify` event if: + /// - there is not yet a running call. + /// It will configure the notify type: ring or notify based on: + /// - is this a DM room -> ring + /// - is this a group with more than one other member -> notify + pub async fn send_call_notification_if_needed(&self) -> Result<(), ClientError> { + self.inner.send_call_notification_if_needed().await?; + Ok(()) + } + + /// Send a call notification event in the current room. + /// + /// This is only supposed to be used in **custom** situations where the user + /// explicitly chooses to send a `m.call.notify` event to invite/notify + /// someone explicitly in unusual conditions. The default should be to + /// use `send_call_notification_if_necessary` just before a new room call is + /// created/joined. + /// + /// One example could be that the UI allows to start a call with a subset of + /// users of the room members first. And then later on the user can + /// invite more users to the call. + pub async fn send_call_notification( + &self, + call_id: String, + application: RtcApplicationType, + notify_type: NotifyType, + mentions: Mentions, + ) -> Result<(), ClientError> { + self.inner + .send_call_notification( + call_id, + application.into(), + notify_type.into(), + mentions.into(), + ) + .await?; + Ok(()) + } } /// Generates a `matrix.to` permalink to the given room alias. @@ -770,3 +813,15 @@ impl TryFrom for RumaAvatarImageInfo { })) } } + +#[derive(uniffi::Enum)] +pub enum RtcApplicationType { + Call, +} +impl From for notify::ApplicationType { + fn from(value: RtcApplicationType) -> Self { + match value { + RtcApplicationType::Call => notify::ApplicationType::Call, + } + } +} diff --git a/bindings/matrix-sdk-ffi/src/ruma.rs b/bindings/matrix-sdk-ffi/src/ruma.rs index cd49ca24e96..153fdfbe131 100644 --- a/bindings/matrix-sdk-ffi/src/ruma.rs +++ b/bindings/matrix-sdk-ffi/src/ruma.rs @@ -21,6 +21,7 @@ use matrix_sdk::attachment::{ use ruma::{ assign, events::{ + call::notify::NotifyType as RumaNotifyType, location::AssetType as RumaAssetType, poll::start::PollKind as RumaPollKind, room::{ @@ -375,6 +376,31 @@ impl From for MessageType { } } +#[derive(Clone, uniffi::Enum)] +pub enum NotifyType { + Ring, + Notify, +} + +impl From for NotifyType { + fn from(val: RumaNotifyType) -> Self { + match val { + RumaNotifyType::Notify => Self::Notify, + RumaNotifyType::Ring => Self::Ring, + _ => Self::Notify, + } + } +} + +impl From for RumaNotifyType { + fn from(value: NotifyType) -> Self { + match value { + NotifyType::Ring => RumaNotifyType::Ring, + NotifyType::Notify => RumaNotifyType::Notify, + } + } +} + #[derive(Clone, uniffi::Record)] pub struct EmoteMessageContent { pub body: String, diff --git a/crates/matrix-sdk/CHANGELOG.md b/crates/matrix-sdk/CHANGELOG.md index 16e8f112e6d..11b3988323a 100644 --- a/crates/matrix-sdk/CHANGELOG.md +++ b/crates/matrix-sdk/CHANGELOG.md @@ -21,6 +21,7 @@ Additions: outbound session for that room. Can be used by clients as a dev tool like the `/discardsession` command. - Add a new `LinkedChunk` data structure to represents all events per room ([#3166](https://github.com/matrix-org/matrix-rust-sdk/pull/3166)). - Add new methods for tracking (on device only) the user's recently visited rooms called `Account::track_recently_visited_room(roomId)` and `Account::get_recently_visited_rooms()` +- Add `send_call_notification` and `send_call_notification_if_needed` methods. This allows to implement sending ring events on call start. # 0.7.0 diff --git a/crates/matrix-sdk/src/room/mod.rs b/crates/matrix-sdk/src/room/mod.rs index cccb16a5d41..731181007d1 100644 --- a/crates/matrix-sdk/src/room/mod.rs +++ b/crates/matrix-sdk/src/room/mod.rs @@ -51,6 +51,7 @@ use ruma::{ }, assign, events::{ + call::notify::{ApplicationType, CallNotifyEventContent, NotifyType}, direct::DirectEventContent, marked_unread::MarkedUnreadEventContent, receipt::{Receipt, ReceiptThread, ReceiptType}, @@ -68,10 +69,11 @@ use ruma::{ space::{child::SpaceChildEventContent, parent::SpaceParentEventContent}, tag::{TagInfo, TagName}, typing::SyncTypingEvent, - AnyRoomAccountDataEvent, AnyTimelineEvent, EmptyStateKey, MessageLikeEventContent, - MessageLikeEventType, RedactContent, RedactedStateEventContent, RoomAccountDataEvent, - RoomAccountDataEventContent, RoomAccountDataEventType, StateEventContent, StateEventType, - StaticEventContent, StaticStateEventContent, SyncStateEvent, + AnyRoomAccountDataEvent, AnyTimelineEvent, EmptyStateKey, Mentions, + MessageLikeEventContent, MessageLikeEventType, RedactContent, RedactedStateEventContent, + RoomAccountDataEvent, RoomAccountDataEventContent, RoomAccountDataEventType, + StateEventContent, StateEventType, StaticEventContent, StaticStateEventContent, + SyncStateEvent, }, push::{Action, PushConditionRoomCtx}, serde::Raw, @@ -2619,6 +2621,58 @@ impl Room { (maybe_room.unwrap(), drop_handles) }) } + + /// This will only send a call notification event if appropriate. + /// + /// This function is supposed to be called whenever the user creates a room + /// call. It will send a `m.call.notify` event if: + /// - there is not yet a running call. + /// It will configure the notify type: ring or notify based on: + /// - is this a DM room -> ring + /// - is this a group with more than one other member -> notify + pub async fn send_call_notification_if_needed(&self) -> Result<()> { + if self.has_active_room_call() { + return Ok(()); + } + + self.send_call_notification( + self.room_id().to_string().to_owned(), + ApplicationType::Call, + if self.is_direct().await.unwrap_or(false) { + NotifyType::Ring + } else { + NotifyType::Notify + }, + Mentions::with_room_mention(), + ) + .await?; + + Ok(()) + } + + /// Send a call notification event in the current room. + /// + /// This is only supposed to be used in **custom** situations where the user + /// explicitly chooses to send a `m.call.notify` event to invite/notify + /// someone explicitly in unusual conditions. The default should be to + /// use `send_call_notification_if_needed` just before a new room call is + /// created/joined. + /// + /// One example could be that the UI allows to start a call with a subset of + /// users of the room members first. And then later on the user can + /// invite more users to the call. + pub async fn send_call_notification( + &self, + call_id: String, + application: ApplicationType, + notify_type: NotifyType, + mentions: Mentions, + ) -> Result<()> { + let call_notify_event_content = + CallNotifyEventContent::new(call_id, application, notify_type, mentions); + self.send(call_notify_event_content).await?; + Ok(()) + } } /// Details of the (latest) invite.