From 6887f8ce4387d273dddf643c6ee99bcaf85f8230 Mon Sep 17 00:00:00 2001 From: Timo Date: Fri, 21 Jun 2024 19:09:56 +0200 Subject: [PATCH 1/7] widget-driver: Support for sending futures events through the widget api. --- Cargo.lock | 18 ++--- Cargo.toml | 7 +- .../src/widget/machine/driver_req.rs | 14 ++-- .../src/widget/machine/from_widget.rs | 68 +++++++++++++++++-- .../matrix-sdk/src/widget/machine/incoming.rs | 11 +-- crates/matrix-sdk/src/widget/machine/mod.rs | 12 ++-- crates/matrix-sdk/src/widget/matrix.rs | 42 +++++++++--- crates/matrix-sdk/src/widget/mod.rs | 5 +- 8 files changed, 135 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27318477b35..88786c92805 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5010,7 +5010,7 @@ dependencies = [ [[package]] name = "ruma" version = "0.10.1" -source = "git+https://github.com/ruma/ruma?rev=e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0#e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0" +source = "git+https://github.com/ruma/ruma?rev=c37843e9be619ffac8c4d33ad3a6a175cc32610c#c37843e9be619ffac8c4d33ad3a6a175cc32610c" dependencies = [ "assign", "js_int", @@ -5027,7 +5027,7 @@ dependencies = [ [[package]] name = "ruma-client-api" version = "0.18.0" -source = "git+https://github.com/ruma/ruma?rev=e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0#e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0" +source = "git+https://github.com/ruma/ruma?rev=c37843e9be619ffac8c4d33ad3a6a175cc32610c#c37843e9be619ffac8c4d33ad3a6a175cc32610c" dependencies = [ "as_variant", "assign", @@ -5050,7 +5050,7 @@ dependencies = [ [[package]] name = "ruma-common" version = "0.13.0" -source = "git+https://github.com/ruma/ruma?rev=e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0#e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0" +source = "git+https://github.com/ruma/ruma?rev=c37843e9be619ffac8c4d33ad3a6a175cc32610c#c37843e9be619ffac8c4d33ad3a6a175cc32610c" dependencies = [ "as_variant", "base64 0.22.1", @@ -5082,7 +5082,7 @@ dependencies = [ [[package]] name = "ruma-events" version = "0.28.1" -source = "git+https://github.com/ruma/ruma?rev=e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0#e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0" +source = "git+https://github.com/ruma/ruma?rev=c37843e9be619ffac8c4d33ad3a6a175cc32610c#c37843e9be619ffac8c4d33ad3a6a175cc32610c" dependencies = [ "as_variant", "indexmap 2.2.6", @@ -5106,7 +5106,7 @@ dependencies = [ [[package]] name = "ruma-federation-api" version = "0.9.0" -source = "git+https://github.com/ruma/ruma?rev=e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0#e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0" +source = "git+https://github.com/ruma/ruma?rev=c37843e9be619ffac8c4d33ad3a6a175cc32610c#c37843e9be619ffac8c4d33ad3a6a175cc32610c" dependencies = [ "js_int", "ruma-common", @@ -5118,7 +5118,7 @@ dependencies = [ [[package]] name = "ruma-html" version = "0.2.0" -source = "git+https://github.com/ruma/ruma?rev=e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0#e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0" +source = "git+https://github.com/ruma/ruma?rev=c37843e9be619ffac8c4d33ad3a6a175cc32610c#c37843e9be619ffac8c4d33ad3a6a175cc32610c" dependencies = [ "as_variant", "html5ever", @@ -5130,7 +5130,7 @@ dependencies = [ [[package]] name = "ruma-identifiers-validation" version = "0.9.5" -source = "git+https://github.com/ruma/ruma?rev=e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0#e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0" +source = "git+https://github.com/ruma/ruma?rev=c37843e9be619ffac8c4d33ad3a6a175cc32610c#c37843e9be619ffac8c4d33ad3a6a175cc32610c" dependencies = [ "js_int", "thiserror", @@ -5139,7 +5139,7 @@ dependencies = [ [[package]] name = "ruma-macros" version = "0.13.0" -source = "git+https://github.com/ruma/ruma?rev=e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0#e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0" +source = "git+https://github.com/ruma/ruma?rev=c37843e9be619ffac8c4d33ad3a6a175cc32610c#c37843e9be619ffac8c4d33ad3a6a175cc32610c" dependencies = [ "once_cell", "proc-macro-crate", @@ -5154,7 +5154,7 @@ dependencies = [ [[package]] name = "ruma-push-gateway-api" version = "0.9.0" -source = "git+https://github.com/ruma/ruma?rev=e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0#e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0" +source = "git+https://github.com/ruma/ruma?rev=c37843e9be619ffac8c4d33ad3a6a175cc32610c#c37843e9be619ffac8c4d33ad3a6a175cc32610c" dependencies = [ "js_int", "ruma-common", diff --git a/Cargo.toml b/Cargo.toml index 48bab8ad893..c27232da9f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ once_cell = "1.16.0" pin-project-lite = "0.2.9" rand = "0.8.5" reqwest = { version = "0.12.4", default-features = false } -ruma = { git = "https://github.com/ruma/ruma", rev = "e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0", features = [ +ruma = { git = "https://github.com/ruma/ruma", rev = "c37843e9be619ffac8c4d33ad3a6a175cc32610c", features = [ "client-api-c", "compat-upload-signatures", "compat-user-id", @@ -53,9 +53,10 @@ ruma = { git = "https://github.com/ruma/ruma", rev = "e5a370f7e5fcebb0da6e4945e5 "compat-encrypted-stickers", "unstable-msc3401", "unstable-msc3266", - "unstable-msc4075" + "unstable-msc4075", + "unstable-msc4140", ] } -ruma-common = { git = "https://github.com/ruma/ruma", rev = "e5a370f7e5fcebb0da6e4945e51c5fafba9aa5f0" } +ruma-common = { git = "https://github.com/ruma/ruma", rev = "c37843e9be619ffac8c4d33ad3a6a175cc32610c" } serde = "1.0.151" serde_html_form = "0.2.0" serde_json = "1.0.91" diff --git a/crates/matrix-sdk/src/widget/machine/driver_req.rs b/crates/matrix-sdk/src/widget/machine/driver_req.rs index a4c2550ca81..2a4c436898d 100644 --- a/crates/matrix-sdk/src/widget/machine/driver_req.rs +++ b/crates/matrix-sdk/src/widget/machine/driver_req.rs @@ -17,16 +17,18 @@ use std::marker::PhantomData; use ruma::{ - api::client::account::request_openid_token, + api::client::{account::request_openid_token, future::FutureParameters}, events::{AnyTimelineEvent, MessageLikeEventType, StateEventType, TimelineEventType}, serde::Raw, - OwnedEventId, }; use serde::Deserialize; use serde_json::value::RawValue as RawJsonValue; use tracing::error; -use super::{incoming::MatrixDriverResponse, Action, MatrixDriverRequestMeta, WidgetMachine}; +use super::{ + from_widget::SendEventResponse, incoming::MatrixDriverResponse, Action, + MatrixDriverRequestMeta, WidgetMachine, +}; use crate::widget::{Capabilities, StateKeySelector}; #[derive(Clone, Debug)] @@ -217,6 +219,8 @@ pub(crate) struct SendEventRequest { pub(crate) state_key: Option, /// Raw content of an event. pub(crate) content: Box, + /// Addition send event parameters to send a future + pub(crate) future_parameters: Option, } impl From for MatrixDriverRequestData { @@ -226,10 +230,10 @@ impl From for MatrixDriverRequestData { } impl MatrixDriverRequest for SendEventRequest { - type Response = OwnedEventId; + type Response = SendEventResponse; } -impl FromMatrixDriverResponse for OwnedEventId { +impl FromMatrixDriverResponse for SendEventResponse { fn from_response(ev: MatrixDriverResponse) -> Option { match ev { MatrixDriverResponse::MatrixEventSent(response) => Some(response), diff --git a/crates/matrix-sdk/src/widget/machine/from_widget.rs b/crates/matrix-sdk/src/widget/machine/from_widget.rs index 2094bbbeb3f..b2efbb92600 100644 --- a/crates/matrix-sdk/src/widget/machine/from_widget.rs +++ b/crates/matrix-sdk/src/widget/machine/from_widget.rs @@ -15,9 +15,10 @@ use std::fmt; use ruma::{ + api::client::future, events::{AnyTimelineEvent, MessageLikeEventType, StateEventType}, serde::Raw, - OwnedEventId, RoomId, + OwnedEventId, OwnedRoomId, }; use serde::{Deserialize, Serialize}; @@ -132,8 +133,65 @@ pub(super) struct ReadEventResponse { pub(super) events: Vec>, } -#[derive(Serialize)] -pub(super) struct SendEventResponse<'a> { - pub(super) room_id: &'a RoomId, - pub(super) event_id: OwnedEventId, +#[derive(Serialize, Debug)] +pub struct SendEventResponse { + /// The room id for the send event. + pub room_id: Option, + /// The event id of the send event. Its optional because if its a future one does not get + /// the event_id at this point. + pub event_id: Option, + /// A token to send/insert the future into the DAG. + pub send_token: Option, + /// A token to cancel this future. It will never be send if this is called. + pub cancel_token: Option, + /// The `future_group_id` generated for this future. Used to connect multiple futures + /// only one of the connected futures will be sent and inserted into the DAG. + pub future_group_id: Option, + /// A token used to refresh the timer of the future. This allows + /// to implement heartbeat like capabilities. An event is only sent once + /// a refresh in the timeout interval is missed. + /// + /// If the future does not have a timeout this will be `None`. + pub refresh_token: Option, +} + +impl SendEventResponse { + pub fn from_event_id(event_id: OwnedEventId) -> Self { + SendEventResponse { + room_id: None, + event_id: Some(event_id), + send_token: None, + cancel_token: None, + future_group_id: None, + refresh_token: None, + } + } + pub fn set_room_id(&mut self, room_id: OwnedRoomId) { + self.room_id = Some(room_id); + } +} +impl Into for future::send_future_message_event::unstable::Response { + fn into(self) -> SendEventResponse { + SendEventResponse { + room_id: None, + event_id: None, + send_token: Some(self.send_token), + cancel_token: Some(self.cancel_token), + future_group_id: Some(self.future_group_id), + refresh_token: self.refresh_token, + } + } +} + +impl Into for future::send_future_state_event::unstable::Response { + fn into(self) -> SendEventResponse { + SendEventResponse { + room_id: None, + event_id: None, + send_token: Some(self.send_token), + cancel_token: Some(self.cancel_token), + future_group_id: Some(self.future_group_id), + refresh_token: self.refresh_token, + } + } } diff --git a/crates/matrix-sdk/src/widget/machine/incoming.rs b/crates/matrix-sdk/src/widget/machine/incoming.rs index 4d365fefefc..661efae29a2 100644 --- a/crates/matrix-sdk/src/widget/machine/incoming.rs +++ b/crates/matrix-sdk/src/widget/machine/incoming.rs @@ -12,14 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use ruma::{ - api::client::account::request_openid_token, events::AnyTimelineEvent, serde::Raw, OwnedEventId, -}; +use ruma::{api::client::account::request_openid_token, events::AnyTimelineEvent, serde::Raw}; use serde::{de, Deserialize, Deserializer}; use serde_json::value::RawValue as RawJsonValue; use uuid::Uuid; -use super::{from_widget::FromWidgetRequest, to_widget::ToWidgetResponse}; +use super::{ + from_widget::{FromWidgetRequest, SendEventResponse}, + to_widget::ToWidgetResponse, +}; use crate::widget::Capabilities; /// Incoming event that the client API must process. @@ -56,7 +57,7 @@ pub(crate) enum MatrixDriverResponse { MatrixEventRead(Vec>), /// Client sent some matrix event. The response contains the event ID. /// A response to an `Action::SendMatrixEvent` command. - MatrixEventSent(OwnedEventId), + MatrixEventSent(SendEventResponse), } pub(super) struct IncomingWidgetMessage { diff --git a/crates/matrix-sdk/src/widget/machine/mod.rs b/crates/matrix-sdk/src/widget/machine/mod.rs index 17c93fa5c9f..c371a2dbb6c 100644 --- a/crates/matrix-sdk/src/widget/machine/mod.rs +++ b/crates/matrix-sdk/src/widget/machine/mod.rs @@ -35,7 +35,7 @@ use self::{ }, from_widget::{ FromWidgetErrorResponse, FromWidgetRequest, ReadEventRequest, ReadEventResponse, - SendEventResponse, SupportedApiVersionsResponse, + SupportedApiVersionsResponse, }, incoming::{IncomingWidgetMessage, IncomingWidgetMessageKind}, openid::{OpenIdResponse, OpenIdState}, @@ -64,6 +64,7 @@ mod to_widget; pub(crate) use self::{ driver_req::{MatrixDriverRequestData, ReadStateEventRequest, SendEventRequest}, + from_widget::SendEventResponse, incoming::{IncomingMessage, MatrixDriverResponse}, }; @@ -343,10 +344,11 @@ impl WidgetMachine { } let (request, action) = self.send_matrix_driver_request(request); - request.then(|result, machine| { - let room_id = &machine.room_id; - let response = result.map(|event_id| SendEventResponse { event_id, room_id }); - vec![machine.send_from_widget_result_response(raw_request, response)] + request.then(|mut result, machine| { + if let Ok(r) = result.as_mut() { + r.set_room_id(machine.room_id.clone().to_owned()); + } + vec![machine.send_from_widget_result_response(raw_request, result)] }); action } diff --git a/crates/matrix-sdk/src/widget/matrix.rs b/crates/matrix-sdk/src/widget/matrix.rs index 7c2ea7ddb6f..647d58d2dfb 100644 --- a/crates/matrix-sdk/src/widget/matrix.rs +++ b/crates/matrix-sdk/src/widget/matrix.rs @@ -22,20 +22,21 @@ use ruma::{ api::client::{ account::request_openid_token::v3::{Request as OpenIdRequest, Response as OpenIdResponse}, filter::RoomEventFilter, + future, }, assign, events::{ - AnySyncTimelineEvent, AnyTimelineEvent, MessageLikeEventType, StateEventType, - TimelineEventType, + AnyMessageLikeEventContent, AnyStateEventContent, AnySyncTimelineEvent, AnyTimelineEvent, + MessageLikeEventType, StateEventType, TimelineEventType, }, serde::Raw, - OwnedEventId, RoomId, + RoomId, TransactionId, }; use serde_json::value::RawValue as RawJsonValue; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; use tracing::error; -use super::StateKeySelector; +use super::{machine::SendEventResponse, StateKeySelector}; use crate::{ event_handler::EventHandlerDropGuard, room::MessagesOptions, HttpResult, Result, Room, }; @@ -112,11 +113,36 @@ impl MatrixDriver { event_type: TimelineEventType, state_key: Option, content: Box, - ) -> Result { + future: Option, + ) -> Result { let type_str = event_type.to_string(); - Ok(match state_key { - Some(key) => self.room.send_state_event_raw(&type_str, &key, content).await?.event_id, - None => self.room.send_raw(&type_str, content).await?.event_id, + Ok(match (state_key, future) { + (None, None) => SendEventResponse::from_event_id( + self.room.send_raw(&type_str, content).await?.event_id, + ), + (Some(key), None) => SendEventResponse::from_event_id( + self.room.send_state_event_raw(&type_str, &key, content).await?.event_id, + ), + (None, Some(future)) => { + let r = future::send_future_message_event::unstable::Request::new_raw( + self.room.room_id().to_owned(), + TransactionId::new().to_owned(), + MessageLikeEventType::from(type_str), + future, + Raw::::from_json(content), + ); + self.room.client.send(r, None).await.map(|r| r.into())? + } + (Some(key), Some(future)) => { + let r = future::send_future_state_event::unstable::Request::new_raw( + self.room.room_id().to_owned(), + key, + StateEventType::from(type_str), + future, + Raw::::from_json(content), + ); + self.room.client.send(r, None).await.map(|r| r.into())? + } }) } diff --git a/crates/matrix-sdk/src/widget/mod.rs b/crates/matrix-sdk/src/widget/mod.rs index db0ac1f0b02..b3bc49e93b7 100644 --- a/crates/matrix-sdk/src/widget/mod.rs +++ b/crates/matrix-sdk/src/widget/mod.rs @@ -225,9 +225,10 @@ impl ProcessingContext { .map_err(|e| e.to_string()), MatrixDriverRequestData::SendMatrixEvent(req) => { - let SendEventRequest { event_type, state_key, content } = req; + let SendEventRequest { event_type, state_key, content, future_parameters } = + req; self.matrix_driver - .send(event_type, state_key, content) + .send(event_type, state_key, content, future_parameters) .await .map(MatrixDriverResponse::MatrixEventSent) .map_err(|e| e.to_string()) From 027ecb5041ddf9540ba6db0c4aefa86d2e96d3ab Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 4 Jul 2024 18:30:27 +0200 Subject: [PATCH 2/7] widget-driver: add tests for future events in widget send action. --- .../src/widget/machine/tests/mod.rs | 1 + .../src/widget/machine/tests/send_event.rs | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 crates/matrix-sdk/src/widget/machine/tests/send_event.rs diff --git a/crates/matrix-sdk/src/widget/machine/tests/mod.rs b/crates/matrix-sdk/src/widget/machine/tests/mod.rs index ab6f13383d6..e9e94bc9a34 100644 --- a/crates/matrix-sdk/src/widget/machine/tests/mod.rs +++ b/crates/matrix-sdk/src/widget/machine/tests/mod.rs @@ -26,6 +26,7 @@ mod api_versions; mod capabilities; mod error; mod openid; +mod send_event; const WIDGET_ID: &str = "test-widget"; diff --git a/crates/matrix-sdk/src/widget/machine/tests/send_event.rs b/crates/matrix-sdk/src/widget/machine/tests/send_event.rs new file mode 100644 index 00000000000..242c9b94ad8 --- /dev/null +++ b/crates/matrix-sdk/src/widget/machine/tests/send_event.rs @@ -0,0 +1,30 @@ +use ruma::owned_room_id; + +use crate::widget::machine::{IncomingMessage, WidgetMachine}; + +use super::WIDGET_ID; + +#[test] +fn process_send_event() { + let (mut machine, _) = WidgetMachine::new( + WIDGET_ID.to_owned(), + owned_room_id!("!a98sd12bjh:example.org"), + true, + None, + ); + + let actions = machine.process(IncomingMessage::WidgetMessage(json_string!({ + "api": "fromWidget", + "widgetId": WIDGET_ID, + "requestId": "send_event-request-id", + "action": "send_event", + "data": { + "content": {}, + "future_timeout": 10000, + "room_id": "!rXAYvblqYaGiJmeRdR:matrix.org", + "state_key": "_@abc:example.org_VFKPEKYWMP", + "type": "org.matrix.msc3401.call.member", + }, + }))); + println!("{:?}", actions); +} From 95b753f4b0854a5d73f9f35ba7e6331cfa0df4ce Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 4 Jul 2024 19:38:14 +0200 Subject: [PATCH 3/7] widget-driver: Fix widget action format and add test. --- .../src/widget/machine/driver_req.rs | 1 + .../src/widget/machine/from_widget.rs | 9 ++--- .../src/widget/machine/tests/send_event.rs | 36 ++++++++++++------- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/crates/matrix-sdk/src/widget/machine/driver_req.rs b/crates/matrix-sdk/src/widget/machine/driver_req.rs index 2a4c436898d..e11fd93b255 100644 --- a/crates/matrix-sdk/src/widget/machine/driver_req.rs +++ b/crates/matrix-sdk/src/widget/machine/driver_req.rs @@ -220,6 +220,7 @@ pub(crate) struct SendEventRequest { /// Raw content of an event. pub(crate) content: Box, /// Addition send event parameters to send a future + #[serde(flatten)] pub(crate) future_parameters: Option, } diff --git a/crates/matrix-sdk/src/widget/machine/from_widget.rs b/crates/matrix-sdk/src/widget/machine/from_widget.rs index b2efbb92600..1644cd5c15a 100644 --- a/crates/matrix-sdk/src/widget/machine/from_widget.rs +++ b/crates/matrix-sdk/src/widget/machine/from_widget.rs @@ -137,15 +137,16 @@ pub(super) struct ReadEventResponse { pub struct SendEventResponse { /// The room id for the send event. pub room_id: Option, - /// The event id of the send event. Its optional because if its a future one does not get - /// the event_id at this point. + /// The event id of the send event. Its optional because if its a future one + /// does not get the event_id at this point. pub event_id: Option, /// A token to send/insert the future into the DAG. pub send_token: Option, /// A token to cancel this future. It will never be send if this is called. pub cancel_token: Option, - /// The `future_group_id` generated for this future. Used to connect multiple futures - /// only one of the connected futures will be sent and inserted into the DAG. + /// The `future_group_id` generated for this future. Used to connect + /// multiple futures only one of the connected futures will be sent and + /// inserted into the DAG. pub future_group_id: Option, /// A token used to refresh the timer of the future. This allows /// to implement heartbeat like capabilities. An event is only sent once diff --git a/crates/matrix-sdk/src/widget/machine/tests/send_event.rs b/crates/matrix-sdk/src/widget/machine/tests/send_event.rs index 242c9b94ad8..c0c6b72aeb2 100644 --- a/crates/matrix-sdk/src/widget/machine/tests/send_event.rs +++ b/crates/matrix-sdk/src/widget/machine/tests/send_event.rs @@ -1,19 +1,16 @@ -use ruma::owned_room_id; +use std::time::Duration; -use crate::widget::machine::{IncomingMessage, WidgetMachine}; +use ruma::{api::client::future::FutureParameters, events::TimelineEventType}; use super::WIDGET_ID; +use crate::widget::machine::{ + from_widget::FromWidgetRequest, + incoming::{IncomingWidgetMessage, IncomingWidgetMessageKind}, +}; #[test] -fn process_send_event() { - let (mut machine, _) = WidgetMachine::new( - WIDGET_ID.to_owned(), - owned_room_id!("!a98sd12bjh:example.org"), - true, - None, - ); - - let actions = machine.process(IncomingMessage::WidgetMessage(json_string!({ +fn parse_future_action() { + let raw = json_string!({ "api": "fromWidget", "widgetId": WIDGET_ID, "requestId": "send_event-request-id", @@ -25,6 +22,19 @@ fn process_send_event() { "state_key": "_@abc:example.org_VFKPEKYWMP", "type": "org.matrix.msc3401.call.member", }, - }))); - println!("{:?}", actions); + }); + if let IncomingWidgetMessageKind::Request(a) = + serde_json::from_str::(&raw).unwrap().kind + { + if let FromWidgetRequest::SendEvent(b) = a.deserialize().unwrap() { + let FutureParameters::Timeout { timeout, group_id } = b.future_parameters.unwrap() + else { + panic!() + }; + assert_eq!(timeout, Duration::from_millis(10000)); + assert_eq!(group_id, None); + assert_eq!(b.event_type, TimelineEventType::CallMember); + assert_eq!(b.state_key.unwrap(), "_@abc:example.org_VFKPEKYWMP".to_owned()); + } + } } From f6edcc40146dfa8d5a183ef400d59f05339f4b6d Mon Sep 17 00:00:00 2001 From: Timo Date: Fri, 5 Jul 2024 13:53:15 +0200 Subject: [PATCH 4/7] widget-driver: add integration test for future events. --- .../src/widget/machine/from_widget.rs | 43 +++---- crates/matrix-sdk/tests/integration/widget.rs | 109 ++++++++++++++++++ 2 files changed, 131 insertions(+), 21 deletions(-) diff --git a/crates/matrix-sdk/src/widget/machine/from_widget.rs b/crates/matrix-sdk/src/widget/machine/from_widget.rs index 1644cd5c15a..4aa383c3dfa 100644 --- a/crates/matrix-sdk/src/widget/machine/from_widget.rs +++ b/crates/matrix-sdk/src/widget/machine/from_widget.rs @@ -134,30 +134,30 @@ pub(super) struct ReadEventResponse { } #[derive(Serialize, Debug)] -pub struct SendEventResponse { +pub(crate) struct SendEventResponse { /// The room id for the send event. - pub room_id: Option, + pub(crate) room_id: Option, /// The event id of the send event. Its optional because if its a future one /// does not get the event_id at this point. - pub event_id: Option, + pub(crate) event_id: Option, /// A token to send/insert the future into the DAG. - pub send_token: Option, + pub(crate) send_token: Option, /// A token to cancel this future. It will never be send if this is called. - pub cancel_token: Option, + pub(crate) cancel_token: Option, /// The `future_group_id` generated for this future. Used to connect /// multiple futures only one of the connected futures will be sent and /// inserted into the DAG. - pub future_group_id: Option, + pub(crate) future_group_id: Option, /// A token used to refresh the timer of the future. This allows /// to implement heartbeat like capabilities. An event is only sent once /// a refresh in the timeout interval is missed. /// /// If the future does not have a timeout this will be `None`. - pub refresh_token: Option, + pub(crate) refresh_token: Option, } impl SendEventResponse { - pub fn from_event_id(event_id: OwnedEventId) -> Self { + pub(crate) fn from_event_id(event_id: OwnedEventId) -> Self { SendEventResponse { room_id: None, event_id: Some(event_id), @@ -167,32 +167,33 @@ impl SendEventResponse { refresh_token: None, } } - pub fn set_room_id(&mut self, room_id: OwnedRoomId) { + pub(crate) fn set_room_id(&mut self, room_id: OwnedRoomId) { self.room_id = Some(room_id); } } -impl Into for future::send_future_message_event::unstable::Response { - fn into(self) -> SendEventResponse { + +impl From for SendEventResponse { + fn from(val: future::send_future_message_event::unstable::Response) -> Self { SendEventResponse { room_id: None, event_id: None, - send_token: Some(self.send_token), - cancel_token: Some(self.cancel_token), - future_group_id: Some(self.future_group_id), - refresh_token: self.refresh_token, + send_token: Some(val.send_token), + cancel_token: Some(val.cancel_token), + future_group_id: Some(val.future_group_id), + refresh_token: val.refresh_token, } } } -impl Into for future::send_future_state_event::unstable::Response { - fn into(self) -> SendEventResponse { +impl From for SendEventResponse { + fn from(val: future::send_future_state_event::unstable::Response) -> Self { SendEventResponse { room_id: None, event_id: None, - send_token: Some(self.send_token), - cancel_token: Some(self.cancel_token), - future_group_id: Some(self.future_group_id), - refresh_token: self.refresh_token, + send_token: Some(val.send_token), + cancel_token: Some(val.cancel_token), + future_group_id: Some(val.future_group_id), + refresh_token: val.refresh_token, } } } diff --git a/crates/matrix-sdk/tests/integration/widget.rs b/crates/matrix-sdk/tests/integration/widget.rs index 9ff2387d28f..6e140530434 100644 --- a/crates/matrix-sdk/tests/integration/widget.rs +++ b/crates/matrix-sdk/tests/integration/widget.rs @@ -600,6 +600,115 @@ async fn send_room_name() { mock_server.verify().await; } +#[async_test] +async fn send_future_room_message() { + let (_, mock_server, driver_handle) = run_test_driver(false).await; + + negotiate_capabilities(&driver_handle, json!(["org.matrix.msc2762.send.event:m.room.message"])) + .await; + + Mock::given(method("PUT")) + .and(path_regex( + r"^/_matrix/client/unstable/org.matrix.msc4140/rooms/.*/send_future/m.room.message/.*$", + )) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "future_group_id": "1234", + "send_token": "my_send_token", + "refresh_token":"my_refresh_token", + "cancel_token": "my_cancel_token" + }))) + .expect(1) + .mount(&mock_server) + .await; + + send_request( + &driver_handle, + "send-room-message", + "send_event", + json!({ + "type": "m.room.message", + "content": { + "msgtype": "m.text", + "body": "Message from a widget!", + }, + "future_timeout":1000, + }), + ) + .await; + + // Receive the response + let msg = recv_message(&driver_handle).await; + assert_eq!(msg["api"], "fromWidget"); + assert_eq!(msg["action"], "send_event"); + let future_group_id = msg["response"]["future_group_id"].as_str().unwrap(); + assert_eq!(future_group_id, "1234"); + let cancel_token = msg["response"]["cancel_token"].as_str().unwrap(); + assert_eq!(cancel_token, "my_cancel_token"); + let refresh_token = msg["response"]["refresh_token"].as_str().unwrap(); + assert_eq!(refresh_token, "my_refresh_token"); + let send_token = msg["response"]["send_token"].as_str().unwrap(); + assert_eq!(send_token, "my_send_token"); + + // Make sure the event-sending endpoint was hit exactly once + mock_server.verify().await; +} + +#[async_test] +async fn send_future_state() { + let (_, mock_server, driver_handle) = run_test_driver(false).await; + + negotiate_capabilities( + &driver_handle, + json!(["org.matrix.msc2762.send.state_event:m.room.name#"]), + ) + .await; + + Mock::given(method("PUT")) + .and(path_regex( + r"^/_matrix/client/unstable/org.matrix.msc4140/rooms/.*/state_future/m.room.name/?$", + )) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "future_group_id": "1234", + "send_token": "my_send_token", + "refresh_token":"my_refresh_token", + "cancel_token": "my_cancel_token" + }))) + .expect(1) + .mount(&mock_server) + .await; + + send_request( + &driver_handle, + "send-room-message", + "send_event", + json!({ + "type": "m.room.name", + "state_key": "", + "content": { + "name": "Room Name set by Widget", + }, + "future_timeout":1000, + }), + ) + .await; + + // Receive the response + let msg = recv_message(&driver_handle).await; + assert_eq!(msg["api"], "fromWidget"); + assert_eq!(msg["action"], "send_event"); + let future_group_id = msg["response"]["future_group_id"].as_str().unwrap(); + assert_eq!(future_group_id, "1234"); + let cancel_token = msg["response"]["cancel_token"].as_str().unwrap(); + assert_eq!(cancel_token, "my_cancel_token"); + let refresh_token = msg["response"]["refresh_token"].as_str().unwrap(); + assert_eq!(refresh_token, "my_refresh_token"); + let send_token = msg["response"]["send_token"].as_str().unwrap(); + assert_eq!(send_token, "my_send_token"); + + // Make sure the event-sending endpoint was hit exactly once + mock_server.verify().await; +} + async fn negotiate_capabilities(driver_handle: &WidgetDriverHandle, caps: JsonValue) { { // Receive toWidget capabilities request From ac878244346befdcf18bdfc69afeea4036973e62 Mon Sep 17 00:00:00 2001 From: Timo Date: Fri, 5 Jul 2024 14:53:22 +0200 Subject: [PATCH 5/7] changelog: add future events to the widget-driver/api. --- crates/matrix-sdk/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/matrix-sdk/CHANGELOG.md b/crates/matrix-sdk/CHANGELOG.md index 33d9aa014c8..92236aa2dcc 100644 --- a/crates/matrix-sdk/CHANGELOG.md +++ b/crates/matrix-sdk/CHANGELOG.md @@ -35,6 +35,8 @@ Additions: - Add `send_call_notification` and `send_call_notification_if_needed` methods. This allows to implement sending ring events on call start. - The `get_media_content`, `get_media_file` and `get_file` methods of the `Media` api now support the new authenticated media endpoints. +- WidgetDriver: Allow the `"future_timeout"` and `"future_group_id"` fields in the `send_event` widget actions. + As defined in [MSC4157](https://github.com/matrix-org/matrix-spec-proposals/pull/4157) # 0.7.0 From b2caec37668745429938e98a2761f1e36b0fdd54 Mon Sep 17 00:00:00 2001 From: Timo Date: Wed, 10 Jul 2024 11:56:32 +0200 Subject: [PATCH 6/7] widget_driver: doc and test changes (review) --- .../src/widget/machine/driver_req.rs | 2 +- .../src/widget/machine/from_widget.rs | 19 ++++++------ .../matrix-sdk/src/widget/machine/incoming.rs | 1 + .../src/widget/machine/tests/send_event.rs | 31 ++++++++++--------- .../src/widget/machine/to_widget.rs | 2 +- 5 files changed, 30 insertions(+), 25 deletions(-) diff --git a/crates/matrix-sdk/src/widget/machine/driver_req.rs b/crates/matrix-sdk/src/widget/machine/driver_req.rs index e11fd93b255..aadea1b46a2 100644 --- a/crates/matrix-sdk/src/widget/machine/driver_req.rs +++ b/crates/matrix-sdk/src/widget/machine/driver_req.rs @@ -219,7 +219,7 @@ pub(crate) struct SendEventRequest { pub(crate) state_key: Option, /// Raw content of an event. pub(crate) content: Box, - /// Addition send event parameters to send a future + /// Additional send event parameters to send a future #[serde(flatten)] pub(crate) future_parameters: Option, } diff --git a/crates/matrix-sdk/src/widget/machine/from_widget.rs b/crates/matrix-sdk/src/widget/machine/from_widget.rs index 4aa383c3dfa..e592469c883 100644 --- a/crates/matrix-sdk/src/widget/machine/from_widget.rs +++ b/crates/matrix-sdk/src/widget/machine/from_widget.rs @@ -25,7 +25,7 @@ use serde::{Deserialize, Serialize}; use super::SendEventRequest; use crate::widget::StateKeySelector; -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] #[serde(tag = "action", rename_all = "snake_case", content = "data")] pub(super) enum FromWidgetRequest { SupportedApiVersions {}, @@ -112,7 +112,7 @@ pub(super) enum ApiVersion { MSC3846, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] #[serde(untagged)] pub(super) enum ReadEventRequest { ReadStateEvent { @@ -137,19 +137,20 @@ pub(super) struct ReadEventResponse { pub(crate) struct SendEventResponse { /// The room id for the send event. pub(crate) room_id: Option, - /// The event id of the send event. Its optional because if its a future one - /// does not get the event_id at this point. + /// The event id of the send event. It's optional because if it's a future + /// event, it does not get the event_id at this point. pub(crate) event_id: Option, /// A token to send/insert the future into the DAG. pub(crate) send_token: Option, - /// A token to cancel this future. It will never be send if this is called. + /// A token to cancel this future event. It will never be seny if this is + /// called. pub(crate) cancel_token: Option, - /// The `future_group_id` generated for this future. Used to connect - /// multiple futures only one of the connected futures will be sent and - /// inserted into the DAG. + /// The `future_group_id` generated for this future event. Used to connect + /// multiple future events. Only one of the connected future event will be + /// sent and inserted into the DAG. pub(crate) future_group_id: Option, /// A token used to refresh the timer of the future. This allows - /// to implement heartbeat like capabilities. An event is only sent once + /// to implement heartbeat-like capabilities. An event is only sent once /// a refresh in the timeout interval is missed. /// /// If the future does not have a timeout this will be `None`. diff --git a/crates/matrix-sdk/src/widget/machine/incoming.rs b/crates/matrix-sdk/src/widget/machine/incoming.rs index 661efae29a2..db9d8e1248f 100644 --- a/crates/matrix-sdk/src/widget/machine/incoming.rs +++ b/crates/matrix-sdk/src/widget/machine/incoming.rs @@ -66,6 +66,7 @@ pub(super) struct IncomingWidgetMessage { pub(super) kind: IncomingWidgetMessageKind, } +#[derive(Debug)] pub(super) enum IncomingWidgetMessageKind { Request(Raw), Response(ToWidgetResponse), diff --git a/crates/matrix-sdk/src/widget/machine/tests/send_event.rs b/crates/matrix-sdk/src/widget/machine/tests/send_event.rs index c0c6b72aeb2..8467b7a726f 100644 --- a/crates/matrix-sdk/src/widget/machine/tests/send_event.rs +++ b/crates/matrix-sdk/src/widget/machine/tests/send_event.rs @@ -1,5 +1,6 @@ use std::time::Duration; +use assert_matches2::assert_let; use ruma::{api::client::future::FutureParameters, events::TimelineEventType}; use super::WIDGET_ID; @@ -23,18 +24,20 @@ fn parse_future_action() { "type": "org.matrix.msc3401.call.member", }, }); - if let IncomingWidgetMessageKind::Request(a) = - serde_json::from_str::(&raw).unwrap().kind - { - if let FromWidgetRequest::SendEvent(b) = a.deserialize().unwrap() { - let FutureParameters::Timeout { timeout, group_id } = b.future_parameters.unwrap() - else { - panic!() - }; - assert_eq!(timeout, Duration::from_millis(10000)); - assert_eq!(group_id, None); - assert_eq!(b.event_type, TimelineEventType::CallMember); - assert_eq!(b.state_key.unwrap(), "_@abc:example.org_VFKPEKYWMP".to_owned()); - } - } + assert_let!( + IncomingWidgetMessageKind::Request(incoming_request) = + serde_json::from_str::(&raw).unwrap().kind + ); + assert_let!( + FromWidgetRequest::SendEvent(send_event_request) = incoming_request.deserialize().unwrap() + ); + assert_let!( + FutureParameters::Timeout { timeout, group_id } = + send_event_request.future_parameters.unwrap() + ); + + assert_eq!(timeout, Duration::from_millis(10000)); + assert_eq!(group_id, None); + assert_eq!(send_event_request.event_type, TimelineEventType::CallMember); + assert_eq!(send_event_request.state_key.unwrap(), "_@abc:example.org_VFKPEKYWMP".to_owned()); } diff --git a/crates/matrix-sdk/src/widget/machine/to_widget.rs b/crates/matrix-sdk/src/widget/machine/to_widget.rs index f002077c36e..5315ffb5850 100644 --- a/crates/matrix-sdk/src/widget/machine/to_widget.rs +++ b/crates/matrix-sdk/src/widget/machine/to_widget.rs @@ -58,7 +58,7 @@ where } } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub(super) struct ToWidgetResponse { /// The action from the original request. From 48ca2407bb023b4d97c903429e1ccf775456332b Mon Sep 17 00:00:00 2001 From: Timo Date: Wed, 10 Jul 2024 12:11:27 +0200 Subject: [PATCH 7/7] widget-driver: rename all mentions of `future` in the context of future events. We need to disambigute future events and rust futures. --- crates/matrix-sdk/CHANGELOG.md | 4 ++-- crates/matrix-sdk/src/widget/machine/driver_req.rs | 4 ++-- crates/matrix-sdk/src/widget/machine/from_widget.rs | 10 +++++----- .../src/widget/machine/tests/send_event.rs | 4 ++-- crates/matrix-sdk/src/widget/matrix.rs | 12 ++++++------ crates/matrix-sdk/src/widget/mod.rs | 10 +++++++--- crates/matrix-sdk/tests/integration/widget.rs | 4 ++-- 7 files changed, 26 insertions(+), 22 deletions(-) diff --git a/crates/matrix-sdk/CHANGELOG.md b/crates/matrix-sdk/CHANGELOG.md index 92236aa2dcc..55c9dcefc88 100644 --- a/crates/matrix-sdk/CHANGELOG.md +++ b/crates/matrix-sdk/CHANGELOG.md @@ -35,8 +35,8 @@ Additions: - Add `send_call_notification` and `send_call_notification_if_needed` methods. This allows to implement sending ring events on call start. - The `get_media_content`, `get_media_file` and `get_file` methods of the `Media` api now support the new authenticated media endpoints. -- WidgetDriver: Allow the `"future_timeout"` and `"future_group_id"` fields in the `send_event` widget actions. - As defined in [MSC4157](https://github.com/matrix-org/matrix-spec-proposals/pull/4157) +- WidgetDriver: Support the `"future_timeout"` and `"future_group_id"` fields in the `send_event` widget actions. +This allows to send future events, as defined in [MSC4157](https://github.com/matrix-org/matrix-spec-proposals/pull/4157) # 0.7.0 diff --git a/crates/matrix-sdk/src/widget/machine/driver_req.rs b/crates/matrix-sdk/src/widget/machine/driver_req.rs index aadea1b46a2..58b7dd64061 100644 --- a/crates/matrix-sdk/src/widget/machine/driver_req.rs +++ b/crates/matrix-sdk/src/widget/machine/driver_req.rs @@ -219,9 +219,9 @@ pub(crate) struct SendEventRequest { pub(crate) state_key: Option, /// Raw content of an event. pub(crate) content: Box, - /// Additional send event parameters to send a future + /// Additional send event parameters to send a future event. #[serde(flatten)] - pub(crate) future_parameters: Option, + pub(crate) future_event_parameters: Option, } impl From for MatrixDriverRequestData { diff --git a/crates/matrix-sdk/src/widget/machine/from_widget.rs b/crates/matrix-sdk/src/widget/machine/from_widget.rs index e592469c883..78425711071 100644 --- a/crates/matrix-sdk/src/widget/machine/from_widget.rs +++ b/crates/matrix-sdk/src/widget/machine/from_widget.rs @@ -140,20 +140,20 @@ pub(crate) struct SendEventResponse { /// The event id of the send event. It's optional because if it's a future /// event, it does not get the event_id at this point. pub(crate) event_id: Option, - /// A token to send/insert the future into the DAG. + /// A token to send/insert the future event into the DAG. pub(crate) send_token: Option, - /// A token to cancel this future event. It will never be seny if this is + /// A token to cancel this future event. It will never be sent if this is /// called. pub(crate) cancel_token: Option, /// The `future_group_id` generated for this future event. Used to connect - /// multiple future events. Only one of the connected future event will be + /// multiple future events. Only one of the connected future events will be /// sent and inserted into the DAG. pub(crate) future_group_id: Option, - /// A token used to refresh the timer of the future. This allows + /// A token used to refresh the timer of the future event. This allows /// to implement heartbeat-like capabilities. An event is only sent once /// a refresh in the timeout interval is missed. /// - /// If the future does not have a timeout this will be `None`. + /// If the future event does not have a timeout this will be `None`. pub(crate) refresh_token: Option, } diff --git a/crates/matrix-sdk/src/widget/machine/tests/send_event.rs b/crates/matrix-sdk/src/widget/machine/tests/send_event.rs index 8467b7a726f..a29ab3f73a0 100644 --- a/crates/matrix-sdk/src/widget/machine/tests/send_event.rs +++ b/crates/matrix-sdk/src/widget/machine/tests/send_event.rs @@ -10,7 +10,7 @@ use crate::widget::machine::{ }; #[test] -fn parse_future_action() { +fn parse_future_event_widget_action() { let raw = json_string!({ "api": "fromWidget", "widgetId": WIDGET_ID, @@ -33,7 +33,7 @@ fn parse_future_action() { ); assert_let!( FutureParameters::Timeout { timeout, group_id } = - send_event_request.future_parameters.unwrap() + send_event_request.future_event_parameters.unwrap() ); assert_eq!(timeout, Duration::from_millis(10000)); diff --git a/crates/matrix-sdk/src/widget/matrix.rs b/crates/matrix-sdk/src/widget/matrix.rs index 647d58d2dfb..ce23f2acf87 100644 --- a/crates/matrix-sdk/src/widget/matrix.rs +++ b/crates/matrix-sdk/src/widget/matrix.rs @@ -113,32 +113,32 @@ impl MatrixDriver { event_type: TimelineEventType, state_key: Option, content: Box, - future: Option, + future_event_parameters: Option, ) -> Result { let type_str = event_type.to_string(); - Ok(match (state_key, future) { + Ok(match (state_key, future_event_parameters) { (None, None) => SendEventResponse::from_event_id( self.room.send_raw(&type_str, content).await?.event_id, ), (Some(key), None) => SendEventResponse::from_event_id( self.room.send_state_event_raw(&type_str, &key, content).await?.event_id, ), - (None, Some(future)) => { + (None, Some(future_event_parameters)) => { let r = future::send_future_message_event::unstable::Request::new_raw( self.room.room_id().to_owned(), TransactionId::new().to_owned(), MessageLikeEventType::from(type_str), - future, + future_event_parameters, Raw::::from_json(content), ); self.room.client.send(r, None).await.map(|r| r.into())? } - (Some(key), Some(future)) => { + (Some(key), Some(future_event_parameters)) => { let r = future::send_future_state_event::unstable::Request::new_raw( self.room.room_id().to_owned(), key, StateEventType::from(type_str), - future, + future_event_parameters, Raw::::from_json(content), ); self.room.client.send(r, None).await.map(|r| r.into())? diff --git a/crates/matrix-sdk/src/widget/mod.rs b/crates/matrix-sdk/src/widget/mod.rs index b3bc49e93b7..96cd60d59dc 100644 --- a/crates/matrix-sdk/src/widget/mod.rs +++ b/crates/matrix-sdk/src/widget/mod.rs @@ -225,10 +225,14 @@ impl ProcessingContext { .map_err(|e| e.to_string()), MatrixDriverRequestData::SendMatrixEvent(req) => { - let SendEventRequest { event_type, state_key, content, future_parameters } = - req; + let SendEventRequest { + event_type, + state_key, + content, + future_event_parameters, + } = req; self.matrix_driver - .send(event_type, state_key, content, future_parameters) + .send(event_type, state_key, content, future_event_parameters) .await .map(MatrixDriverResponse::MatrixEventSent) .map_err(|e| e.to_string()) diff --git a/crates/matrix-sdk/tests/integration/widget.rs b/crates/matrix-sdk/tests/integration/widget.rs index 6e140530434..441fdbe88b3 100644 --- a/crates/matrix-sdk/tests/integration/widget.rs +++ b/crates/matrix-sdk/tests/integration/widget.rs @@ -601,7 +601,7 @@ async fn send_room_name() { } #[async_test] -async fn send_future_room_message() { +async fn send_future_room_message_event() { let (_, mock_server, driver_handle) = run_test_driver(false).await; negotiate_capabilities(&driver_handle, json!(["org.matrix.msc2762.send.event:m.room.message"])) @@ -654,7 +654,7 @@ async fn send_future_room_message() { } #[async_test] -async fn send_future_state() { +async fn send_future_state_event() { let (_, mock_server, driver_handle) = run_test_driver(false).await; negotiate_capabilities(