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/CHANGELOG.md b/crates/matrix-sdk/CHANGELOG.md index 33d9aa014c8..55c9dcefc88 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: 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 a4c2550ca81..58b7dd64061 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,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 event. + #[serde(flatten)] + pub(crate) future_event_parameters: Option, } impl From for MatrixDriverRequestData { @@ -226,10 +231,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..78425711071 100644 --- a/crates/matrix-sdk/src/widget/machine/from_widget.rs +++ b/crates/matrix-sdk/src/widget/machine/from_widget.rs @@ -15,16 +15,17 @@ use std::fmt; use ruma::{ + api::client::future, events::{AnyTimelineEvent, MessageLikeEventType, StateEventType}, serde::Raw, - OwnedEventId, RoomId, + OwnedEventId, OwnedRoomId, }; 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 {}, @@ -111,7 +112,7 @@ pub(super) enum ApiVersion { MSC3846, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] #[serde(untagged)] pub(super) enum ReadEventRequest { ReadStateEvent { @@ -132,8 +133,68 @@ 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(crate) struct SendEventResponse { + /// The room id for the send event. + pub(crate) room_id: Option, + /// 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 event into the DAG. + pub(crate) send_token: Option, + /// 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 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 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 event does not have a timeout this will be `None`. + pub(crate) refresh_token: Option, +} + +impl SendEventResponse { + pub(crate) 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(crate) fn set_room_id(&mut self, room_id: OwnedRoomId) { + self.room_id = Some(room_id); + } +} + +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(val.send_token), + cancel_token: Some(val.cancel_token), + future_group_id: Some(val.future_group_id), + refresh_token: val.refresh_token, + } + } +} + +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(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/src/widget/machine/incoming.rs b/crates/matrix-sdk/src/widget/machine/incoming.rs index 4d365fefefc..db9d8e1248f 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 { @@ -65,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/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/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..a29ab3f73a0 --- /dev/null +++ b/crates/matrix-sdk/src/widget/machine/tests/send_event.rs @@ -0,0 +1,43 @@ +use std::time::Duration; + +use assert_matches2::assert_let; +use ruma::{api::client::future::FutureParameters, events::TimelineEventType}; + +use super::WIDGET_ID; +use crate::widget::machine::{ + from_widget::FromWidgetRequest, + incoming::{IncomingWidgetMessage, IncomingWidgetMessageKind}, +}; + +#[test] +fn parse_future_event_widget_action() { + let raw = 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", + }, + }); + 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_event_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. diff --git a/crates/matrix-sdk/src/widget/matrix.rs b/crates/matrix-sdk/src/widget/matrix.rs index 7c2ea7ddb6f..ce23f2acf87 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_event_parameters: 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_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_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_event_parameters, + Raw::::from_json(content), + ); + self.room.client.send(r, None).await.map(|r| r.into())? + } + (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_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 db0ac1f0b02..96cd60d59dc 100644 --- a/crates/matrix-sdk/src/widget/mod.rs +++ b/crates/matrix-sdk/src/widget/mod.rs @@ -225,9 +225,14 @@ 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_event_parameters, + } = req; self.matrix_driver - .send(event_type, state_key, content) + .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 9ff2387d28f..441fdbe88b3 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_event() { + 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_event() { + 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