mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-18 13:40:55 -04:00
feat(calls): add support for sending Matrix RTC call notifications
This commit is contained in:
committed by
Benjamin Bouvier
parent
13ceb3e745
commit
f672f17fcf
@@ -48,6 +48,7 @@ ruma = { version = "0.10.1", features = [
|
||||
"compat-tag-info",
|
||||
"unstable-msc3401",
|
||||
"unstable-msc3266",
|
||||
"unstable-msc4075"
|
||||
] }
|
||||
ruma-common = { version = "0.13.0" }
|
||||
once_cell = "1.16.0"
|
||||
|
||||
@@ -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<AnySyncMessageLikeEvent> 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
|
||||
}
|
||||
|
||||
@@ -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<ImageInfo> for RumaAvatarImageInfo {
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Enum)]
|
||||
pub enum RtcApplicationType {
|
||||
Call,
|
||||
}
|
||||
impl From<RtcApplicationType> for notify::ApplicationType {
|
||||
fn from(value: RtcApplicationType) -> Self {
|
||||
match value {
|
||||
RtcApplicationType::Call => notify::ApplicationType::Call,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,30 @@ impl From<RumaMessageType> for MessageType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Enum)]
|
||||
pub enum NotifyType {
|
||||
Ring,
|
||||
Notify,
|
||||
}
|
||||
|
||||
impl From<RumaNotifyType> for NotifyType {
|
||||
fn from(val: RumaNotifyType) -> Self {
|
||||
match val {
|
||||
RumaNotifyType::Ring => Self::Ring,
|
||||
_ => Self::Notify,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NotifyType> 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,
|
||||
|
||||
@@ -25,6 +25,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
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -2628,6 +2630,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(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper for a weak client and a room id that allows to lazily retrieve a
|
||||
|
||||
@@ -11,7 +11,7 @@ use matrix_sdk::{
|
||||
use matrix_sdk_base::RoomState;
|
||||
use matrix_sdk_test::{
|
||||
async_test, test_json, test_json::sync::CUSTOM_ROOM_POWER_LEVELS, EphemeralTestEvent,
|
||||
JoinedRoomBuilder, SyncResponseBuilder, DEFAULT_TEST_ROOM_ID,
|
||||
GlobalAccountDataTestEvent, JoinedRoomBuilder, SyncResponseBuilder, DEFAULT_TEST_ROOM_ID,
|
||||
};
|
||||
use ruma::{
|
||||
api::client::{membership::Invite3pidInit, receipt::create_receipt::v3::ReceiptType},
|
||||
@@ -19,7 +19,7 @@ use ruma::{
|
||||
events::{receipt::ReceiptThread, room::message::RoomMessageEventContent, TimelineEventType},
|
||||
int, mxc_uri, owned_event_id, room_id, thirdparty, user_id, OwnedUserId, TransactionId,
|
||||
};
|
||||
use serde_json::json;
|
||||
use serde_json::{json, Value};
|
||||
use wiremock::{
|
||||
matchers::{body_json, body_partial_json, header, method, path_regex},
|
||||
Mock, ResponseTemplate,
|
||||
@@ -628,3 +628,88 @@ async fn test_reset_power_levels() {
|
||||
|
||||
room.reset_power_levels().await.unwrap();
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_call_notifications_ring_for_dms() {
|
||||
let (client, server) = logged_in_client_with_server().await;
|
||||
|
||||
let mut sync_builder = SyncResponseBuilder::new();
|
||||
sync_builder.add_joined_room(JoinedRoomBuilder::default());
|
||||
sync_builder.add_global_account_data_event(GlobalAccountDataTestEvent::Direct);
|
||||
|
||||
mock_sync(&server, sync_builder.build_json_sync_response(), None).await;
|
||||
mock_encryption_state(&server, false).await;
|
||||
|
||||
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room = client.get_room(&DEFAULT_TEST_ROOM_ID).unwrap();
|
||||
assert!(room.is_direct().await.unwrap());
|
||||
assert!(!room.has_active_room_call());
|
||||
|
||||
Mock::given(method("PUT"))
|
||||
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*"))
|
||||
.and({
|
||||
move |request: &wiremock::Request| {
|
||||
let content: Value = request.body_json().expect("The body should be a JSON body");
|
||||
assert_eq!(
|
||||
content,
|
||||
json!({
|
||||
"application": "m.call",
|
||||
"call_id": DEFAULT_TEST_ROOM_ID.to_string(),
|
||||
"m.mentions": {"room" :true},
|
||||
"notify_type": "ring"
|
||||
}),
|
||||
);
|
||||
true
|
||||
}
|
||||
})
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({"event_id": "$event_id"})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
room.send_call_notification_if_needed().await.unwrap();
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_call_notifications_notify_for_rooms() {
|
||||
let (client, server) = logged_in_client_with_server().await;
|
||||
|
||||
let mut sync_builder = SyncResponseBuilder::new();
|
||||
sync_builder.add_joined_room(JoinedRoomBuilder::default());
|
||||
|
||||
mock_sync(&server, sync_builder.build_json_sync_response(), None).await;
|
||||
mock_encryption_state(&server, false).await;
|
||||
|
||||
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room = client.get_room(&DEFAULT_TEST_ROOM_ID).unwrap();
|
||||
assert!(!room.is_direct().await.unwrap());
|
||||
assert!(!room.has_active_room_call());
|
||||
|
||||
Mock::given(method("PUT"))
|
||||
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*"))
|
||||
.and({
|
||||
move |request: &wiremock::Request| {
|
||||
let content: Value = request.body_json().expect("The body should be a JSON body");
|
||||
assert_eq!(
|
||||
content,
|
||||
json!({
|
||||
"application": "m.call",
|
||||
"call_id": DEFAULT_TEST_ROOM_ID.to_string(),
|
||||
"m.mentions": {"room" :true},
|
||||
"notify_type": "notify"
|
||||
}),
|
||||
);
|
||||
true
|
||||
}
|
||||
})
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({"event_id": "$event_id"})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
room.send_call_notification_if_needed().await.unwrap();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user