Skip to content
8 changes: 8 additions & 0 deletions bindings/matrix-sdk-ffi/src/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ pub fn get_element_call_required_permissions(
requires_client: true,
update_delayed_event: true,
send_delayed_event: true,
download_files: true,
}
}

Expand Down Expand Up @@ -331,6 +332,8 @@ pub struct WidgetCapabilities {
pub update_delayed_event: bool,
/// This allows the widget to send events with a delay.
pub send_delayed_event: bool,
/// This allows the widget to download files (avatars)
pub download_files: bool,
}

impl From<WidgetCapabilities> for matrix_sdk::widget::Capabilities {
Expand All @@ -341,6 +344,7 @@ impl From<WidgetCapabilities> for matrix_sdk::widget::Capabilities {
requires_client: value.requires_client,
update_delayed_event: value.update_delayed_event,
send_delayed_event: value.send_delayed_event,
download_file: value.download_files,
}
}
}
Expand All @@ -353,6 +357,7 @@ impl From<matrix_sdk::widget::Capabilities> for WidgetCapabilities {
requires_client: value.requires_client,
update_delayed_event: value.update_delayed_event,
send_delayed_event: value.send_delayed_event,
download_files: value.download_file,
}
}
}
Expand Down Expand Up @@ -553,5 +558,8 @@ mod tests {
// RTC decline
cap_assert("org.matrix.msc2762.receive.event:org.matrix.msc4310.rtc.decline");
cap_assert("org.matrix.msc2762.send.event:org.matrix.msc4310.rtc.decline");

// Download avatars
cap_assert("org.matrix.msc4039.download_file");
}
}
3 changes: 3 additions & 0 deletions crates/matrix-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ All notable changes to this project will be documented in this file.
to true will now trigger a download of all historical keys for the room in
question from the client's key backup.
([#6017](https://github.com/matrix-org/matrix-rust-sdk/pull/6017))
- Add widget partial support for MSC4039. Allows widgets to download non-encrypted files from the
content repository (like avatars).
([#6354](https://github.com/matrix-org/matrix-rust-sdk/pull/6354))

### Bugfix

Expand Down
18 changes: 17 additions & 1 deletion crates/matrix-sdk/src/widget/capabilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ pub struct Capabilities {
pub update_delayed_event: bool,
/// This allows the widget to send events with a delay.
pub send_delayed_event: bool,

/// This allows the widget to download files as per MSC4039.
pub download_file: bool,
}

impl Capabilities {
Expand Down Expand Up @@ -114,6 +117,8 @@ pub(super) const REQUIRES_CLIENT: &str = "io.element.requires_client";
pub(super) const SEND_DELAYED_EVENT: &str = "org.matrix.msc4157.send.delayed_event";
pub(super) const UPDATE_DELAYED_EVENT: &str = "org.matrix.msc4157.update_delayed_event";

pub(super) const DOWNLOAD_FILE: &str = "org.matrix.msc4039.download_file";

impl Serialize for Capabilities {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
Expand Down Expand Up @@ -174,6 +179,9 @@ impl Serialize for Capabilities {
if self.send_delayed_event {
seq.serialize_element(SEND_DELAYED_EVENT)?;
}
if self.download_file {
seq.serialize_element(DOWNLOAD_FILE)?;
}
for filter in &self.read {
let name = match filter {
Filter::MessageLike(_) => READ_EVENT,
Expand Down Expand Up @@ -204,6 +212,7 @@ impl<'de> Deserialize<'de> for Capabilities {
RequiresClient,
UpdateDelayedEvent,
SendDelayedEvent,
DownloadFile,
Read(Filter),
Send(Filter),
Unknown,
Expand All @@ -224,6 +233,9 @@ impl<'de> Deserialize<'de> for Capabilities {
if s == SEND_DELAYED_EVENT {
return Ok(Self::SendDelayedEvent);
}
if s == DOWNLOAD_FILE {
return Ok(Self::DownloadFile);
}

match s.split_once(':') {
Some((READ_EVENT, filter_s)) => Ok(Permission::Read(Filter::MessageLike(
Expand Down Expand Up @@ -284,6 +296,7 @@ impl<'de> Deserialize<'de> for Capabilities {
Permission::Unknown => {}
Permission::UpdateDelayedEvent => capabilities.update_delayed_event = true,
Permission::SendDelayedEvent => capabilities.send_delayed_event = true,
Permission::DownloadFile => capabilities.download_file = true,
}
}

Expand Down Expand Up @@ -321,7 +334,8 @@ mod tests {
"org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#@user:matrix.server",
"org.matrix.msc3819.send.to_device:io.element.call.encryption_keys",
"org.matrix.msc4157.send.delayed_event",
"org.matrix.msc4157.update_delayed_event"
"org.matrix.msc4157.update_delayed_event",
"org.matrix.msc4039.download_file"
]"#;

let parsed = serde_json::from_str::<Capabilities>(capabilities_str).unwrap();
Expand Down Expand Up @@ -351,6 +365,7 @@ mod tests {
requires_client: true,
update_delayed_event: true,
send_delayed_event: true,
download_file: true,
};

assert_eq!(parsed, expected);
Expand Down Expand Up @@ -381,6 +396,7 @@ mod tests {
requires_client: true,
update_delayed_event: false,
send_delayed_event: false,
download_file: false,
};

let capabilities_str = serde_json::to_string(&capabilities).unwrap();
Expand Down
23 changes: 21 additions & 2 deletions crates/matrix-sdk/src/widget/machine/driver_req.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
use std::{collections::BTreeMap, marker::PhantomData};

use ruma::{
OwnedUserId,
OwnedMxcUri, OwnedUserId,
api::client::{account::request_openid_token, delayed_events::update_delayed_event},
events::{AnyStateEvent, AnyTimelineEvent, AnyToDeviceEventContent},
serde::Raw,
Expand All @@ -31,7 +31,7 @@ use super::{
Action, MatrixDriverRequestMeta, SendToDeviceEventResponse, WidgetMachine,
from_widget::SendEventResponse, incoming::MatrixDriverResponse,
};
use crate::widget::{Capabilities, StateKeySelector};
use crate::widget::{Capabilities, StateKeySelector, machine::from_widget::DownloadFileResponse};

#[derive(Clone, Debug)]
pub(crate) enum MatrixDriverRequestData {
Expand Down Expand Up @@ -59,6 +59,9 @@ pub(crate) enum MatrixDriverRequestData {

/// Data for sending a UpdateDelayedEvent client server api request.
UpdateDelayedEvent(UpdateDelayedEventRequest),

/// Request a download of a file.
DownloadFile(DownloadFileRequest),
}

/// A handle to a pending `toWidget` request.
Expand Down Expand Up @@ -339,3 +342,19 @@ impl FromMatrixDriverResponse for update_delayed_event::unstable::Response {
}
}
}

#[derive(Deserialize, Debug, Clone)]
pub(crate) struct DownloadFileRequest {
// The MXC url of the file to download.
pub(crate) content_uri: OwnedMxcUri,
}

impl MatrixDriverRequest for DownloadFileRequest {
type Response = DownloadFileResponse;
}

impl From<DownloadFileRequest> for MatrixDriverRequestData {
fn from(req: DownloadFileRequest) -> Self {
MatrixDriverRequestData::DownloadFile(req)
}
}
37 changes: 35 additions & 2 deletions crates/matrix-sdk/src/widget/machine/from_widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use ruma::{
error::{ErrorBody, StandardErrorBody},
},
events::AnyTimelineEvent,
serde::Raw,
serde::{Base64, Raw},
};
use serde::{Deserialize, Serialize};
use tracing::error;
Expand All @@ -33,7 +33,10 @@ use super::{
};
use crate::{
Error, HttpError, RumaApiError,
widget::{StateKeySelector, machine::driver_req::FromMatrixDriverResponse},
widget::{
StateKeySelector,
machine::driver_req::{DownloadFileRequest, FromMatrixDriverResponse},
},
};

#[derive(Deserialize, Debug)]
Expand All @@ -49,6 +52,8 @@ pub(super) enum FromWidgetRequest {
SendToDevice(SendToDeviceRequest),
#[serde(rename = "org.matrix.msc4157.update_delayed_event")]
DelayedEventUpdate(UpdateDelayedEventRequest),
#[serde(rename = "org.matrix.msc4039.download_file")]
DownloadFile(DownloadFileRequest),
}

/// The full response a client sends to a [`FromWidgetRequest`] in case of an
Expand Down Expand Up @@ -145,6 +150,7 @@ impl SupportedApiVersionsResponse {
ApiVersion::MSC2762UpdateState,
ApiVersion::MSC2871,
ApiVersion::MSC3819,
ApiVersion::MSC4039,
],
}
}
Expand Down Expand Up @@ -192,6 +198,9 @@ pub(super) enum ApiVersion {
/// Supports access to the TURN servers.
#[serde(rename = "town.robin.msc3846")]
MSC3846,

#[serde(rename = "org.matrix.msc4039")]
MSC4039,
}

#[derive(Deserialize, Debug)]
Expand Down Expand Up @@ -276,3 +285,27 @@ impl FromMatrixDriverResponse for SendToDeviceEventResponse {
}
}
}

/// Response for a download file request.
/// https://github.com/matrix-org/matrix-spec-proposals/pull/4039
#[derive(Serialize, Debug)]
pub(crate) struct DownloadFileResponse {
// The binary file content in a format that can cross the
// widget-driver-api boundary.
#[serde(rename = "file")]
pub(crate) file_data_base64: Base64,
}

impl FromMatrixDriverResponse for DownloadFileResponse {
fn from_response(matrix_driver_response: MatrixDriverResponse) -> Option<Self> {
match matrix_driver_response {
MatrixDriverResponse::FileDownloaded(resp) => {
Some(Self { file_data_base64: resp.file_data_base64 })
}
_ => {
error!("bug in MatrixDriver, received wrong event response");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should be a full on panic as it's a developer error.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, can this be a separate PR? because it is now the common pattern in the crate

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing 👍

None
}
}
}
}
59 changes: 58 additions & 1 deletion crates/matrix-sdk/src/widget/machine/incoming.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use super::{
from_widget::{FromWidgetRequest, SendEventResponse},
to_widget::ToWidgetResponse,
};
use crate::widget::Capabilities;
use crate::widget::{Capabilities, machine::from_widget::DownloadFileResponse};

/// Incoming message for the widget client side module that it must process.
pub(crate) enum IncomingMessage {
Expand Down Expand Up @@ -87,6 +87,8 @@ pub(crate) enum MatrixDriverResponse {
/// Client updated a delayed event.
/// A response to a [`MatrixDriverRequestData::UpdateDelayedEvent`] command.
DelayedEventUpdated(delayed_events::update_delayed_event::unstable::Response),
/// The client successfully downloaded a file from a widget action.
FileDownloaded(DownloadFileResponse),
}

pub(super) struct IncomingWidgetMessage {
Expand Down Expand Up @@ -136,3 +138,58 @@ impl<'de> Deserialize<'de> for IncomingWidgetMessage {
Ok(Self { widget_id, request_id, kind })
}
}

#[cfg(test)]
mod tests {
use assert_matches2::assert_let;

use crate::widget::machine::{
from_widget::FromWidgetRequest,
incoming::{IncomingWidgetMessage, IncomingWidgetMessageKind},
};

#[test]
fn parse_download_file_widget_action() {
let raw = r#"
{
"api": "fromWidget",
"widgetId": "aGNStSuL3hhIISSCXgpt15j2",
"requestId": "generated-id-1234",
"action": "org.matrix.msc4039.download_file",
"data": {
"content_uri": "mxc://server/id"
}
}
"#;

assert_let!(
IncomingWidgetMessageKind::Request(incoming_request) =
serde_json::from_str::<IncomingWidgetMessage>(raw).unwrap().kind
);
assert_let!(FromWidgetRequest::DownloadFile(req) = incoming_request.deserialize().unwrap());

assert_eq!(req.content_uri, "mxc://server/id");
}
#[test]
fn parse_download_file_request_with_non_mxc_url() {
let raw = r#"
{
"api": "fromWidget",
"widgetId": "aGNStSuL3hhIISSCXgpt15j2",
"requestId": "generated-id-1234",
"action": "org.matrix.msc4039.download_file",
"data": {
"content_uri": "https://server/id"
}
}
"#;

assert_let!(
IncomingWidgetMessageKind::Request(incoming_request) =
serde_json::from_str::<IncomingWidgetMessage>(raw).unwrap().kind
);
assert_let!(FromWidgetRequest::DownloadFile(req) = incoming_request.deserialize().unwrap());

assert!(!req.content_uri.is_valid());
}
}
Loading
Loading