Skip to content

Commit fcce02f

Browse files
authored
feat(widget): Support for avatars in calls via msc4039 (#6354)
Allows to get the avatars in Element Call widget ! Using [MSC4039](https://github.com/nordeck/matrix-spec-proposals/blob/nic/feat/widgetapi-upload-files/proposals/4039-widget-api-media.md) Counter part of web support: - element-hq/element-web#32755 - element-hq/element-call#3780 Needs a EC PR to support passing data as base64 via the widget json API - element-hq/element-call#3818
1 parent 7cea5be commit fcce02f

File tree

11 files changed

+301
-12
lines changed

11 files changed

+301
-12
lines changed

bindings/matrix-sdk-ffi/src/widget.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ pub fn get_element_call_required_permissions(
266266
requires_client: true,
267267
update_delayed_event: true,
268268
send_delayed_event: true,
269+
download_files: true,
269270
}
270271
}
271272

@@ -331,6 +332,8 @@ pub struct WidgetCapabilities {
331332
pub update_delayed_event: bool,
332333
/// This allows the widget to send events with a delay.
333334
pub send_delayed_event: bool,
335+
/// This allows the widget to download files (avatars)
336+
pub download_files: bool,
334337
}
335338

336339
impl From<WidgetCapabilities> for matrix_sdk::widget::Capabilities {
@@ -341,6 +344,7 @@ impl From<WidgetCapabilities> for matrix_sdk::widget::Capabilities {
341344
requires_client: value.requires_client,
342345
update_delayed_event: value.update_delayed_event,
343346
send_delayed_event: value.send_delayed_event,
347+
download_file: value.download_files,
344348
}
345349
}
346350
}
@@ -353,6 +357,7 @@ impl From<matrix_sdk::widget::Capabilities> for WidgetCapabilities {
353357
requires_client: value.requires_client,
354358
update_delayed_event: value.update_delayed_event,
355359
send_delayed_event: value.send_delayed_event,
360+
download_files: value.download_file,
356361
}
357362
}
358363
}
@@ -553,5 +558,8 @@ mod tests {
553558
// RTC decline
554559
cap_assert("org.matrix.msc2762.receive.event:org.matrix.msc4310.rtc.decline");
555560
cap_assert("org.matrix.msc2762.send.event:org.matrix.msc4310.rtc.decline");
561+
562+
// Download avatars
563+
cap_assert("org.matrix.msc4039.download_file");
556564
}
557565
}

crates/matrix-sdk/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ All notable changes to this project will be documented in this file.
8989
to true will now trigger a download of all historical keys for the room in
9090
question from the client's key backup.
9191
([#6017](https://github.com/matrix-org/matrix-rust-sdk/pull/6017))
92+
- Add widget partial support for MSC4039. Allows widgets to download non-encrypted files from the
93+
content repository (like avatars).
94+
([#6354](https://github.com/matrix-org/matrix-rust-sdk/pull/6354))
9295

9396
### Bugfix
9497

crates/matrix-sdk/src/widget/capabilities.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ pub struct Capabilities {
5757
pub update_delayed_event: bool,
5858
/// This allows the widget to send events with a delay.
5959
pub send_delayed_event: bool,
60+
61+
/// This allows the widget to download files as per MSC4039.
62+
pub download_file: bool,
6063
}
6164

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

120+
pub(super) const DOWNLOAD_FILE: &str = "org.matrix.msc4039.download_file";
121+
117122
impl Serialize for Capabilities {
118123
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
119124
where
@@ -174,6 +179,9 @@ impl Serialize for Capabilities {
174179
if self.send_delayed_event {
175180
seq.serialize_element(SEND_DELAYED_EVENT)?;
176181
}
182+
if self.download_file {
183+
seq.serialize_element(DOWNLOAD_FILE)?;
184+
}
177185
for filter in &self.read {
178186
let name = match filter {
179187
Filter::MessageLike(_) => READ_EVENT,
@@ -204,6 +212,7 @@ impl<'de> Deserialize<'de> for Capabilities {
204212
RequiresClient,
205213
UpdateDelayedEvent,
206214
SendDelayedEvent,
215+
DownloadFile,
207216
Read(Filter),
208217
Send(Filter),
209218
Unknown,
@@ -224,6 +233,9 @@ impl<'de> Deserialize<'de> for Capabilities {
224233
if s == SEND_DELAYED_EVENT {
225234
return Ok(Self::SendDelayedEvent);
226235
}
236+
if s == DOWNLOAD_FILE {
237+
return Ok(Self::DownloadFile);
238+
}
227239

228240
match s.split_once(':') {
229241
Some((READ_EVENT, filter_s)) => Ok(Permission::Read(Filter::MessageLike(
@@ -284,6 +296,7 @@ impl<'de> Deserialize<'de> for Capabilities {
284296
Permission::Unknown => {}
285297
Permission::UpdateDelayedEvent => capabilities.update_delayed_event = true,
286298
Permission::SendDelayedEvent => capabilities.send_delayed_event = true,
299+
Permission::DownloadFile => capabilities.download_file = true,
287300
}
288301
}
289302

@@ -321,7 +334,8 @@ mod tests {
321334
"org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#@user:matrix.server",
322335
"org.matrix.msc3819.send.to_device:io.element.call.encryption_keys",
323336
"org.matrix.msc4157.send.delayed_event",
324-
"org.matrix.msc4157.update_delayed_event"
337+
"org.matrix.msc4157.update_delayed_event",
338+
"org.matrix.msc4039.download_file"
325339
]"#;
326340

327341
let parsed = serde_json::from_str::<Capabilities>(capabilities_str).unwrap();
@@ -351,6 +365,7 @@ mod tests {
351365
requires_client: true,
352366
update_delayed_event: true,
353367
send_delayed_event: true,
368+
download_file: true,
354369
};
355370

356371
assert_eq!(parsed, expected);
@@ -381,6 +396,7 @@ mod tests {
381396
requires_client: true,
382397
update_delayed_event: false,
383398
send_delayed_event: false,
399+
download_file: false,
384400
};
385401

386402
let capabilities_str = serde_json::to_string(&capabilities).unwrap();

crates/matrix-sdk/src/widget/machine/driver_req.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
use std::{collections::BTreeMap, marker::PhantomData};
1818

1919
use ruma::{
20-
OwnedUserId,
20+
OwnedMxcUri, OwnedUserId,
2121
api::client::{account::request_openid_token, delayed_events::update_delayed_event},
2222
events::{AnyStateEvent, AnyTimelineEvent, AnyToDeviceEventContent},
2323
serde::Raw,
@@ -31,7 +31,7 @@ use super::{
3131
Action, MatrixDriverRequestMeta, SendToDeviceEventResponse, WidgetMachine,
3232
from_widget::SendEventResponse, incoming::MatrixDriverResponse,
3333
};
34-
use crate::widget::{Capabilities, StateKeySelector};
34+
use crate::widget::{Capabilities, StateKeySelector, machine::from_widget::DownloadFileResponse};
3535

3636
#[derive(Clone, Debug)]
3737
pub(crate) enum MatrixDriverRequestData {
@@ -59,6 +59,9 @@ pub(crate) enum MatrixDriverRequestData {
5959

6060
/// Data for sending a UpdateDelayedEvent client server api request.
6161
UpdateDelayedEvent(UpdateDelayedEventRequest),
62+
63+
/// Request a download of a file.
64+
DownloadFile(DownloadFileRequest),
6265
}
6366

6467
/// A handle to a pending `toWidget` request.
@@ -339,3 +342,19 @@ impl FromMatrixDriverResponse for update_delayed_event::unstable::Response {
339342
}
340343
}
341344
}
345+
346+
#[derive(Deserialize, Debug, Clone)]
347+
pub(crate) struct DownloadFileRequest {
348+
// The MXC url of the file to download.
349+
pub(crate) content_uri: OwnedMxcUri,
350+
}
351+
352+
impl MatrixDriverRequest for DownloadFileRequest {
353+
type Response = DownloadFileResponse;
354+
}
355+
356+
impl From<DownloadFileRequest> for MatrixDriverRequestData {
357+
fn from(req: DownloadFileRequest) -> Self {
358+
MatrixDriverRequestData::DownloadFile(req)
359+
}
360+
}

crates/matrix-sdk/src/widget/machine/from_widget.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use ruma::{
2222
error::{ErrorBody, StandardErrorBody},
2323
},
2424
events::AnyTimelineEvent,
25-
serde::Raw,
25+
serde::{Base64, Raw},
2626
};
2727
use serde::{Deserialize, Serialize};
2828
use tracing::error;
@@ -33,7 +33,10 @@ use super::{
3333
};
3434
use crate::{
3535
Error, HttpError, RumaApiError,
36-
widget::{StateKeySelector, machine::driver_req::FromMatrixDriverResponse},
36+
widget::{
37+
StateKeySelector,
38+
machine::driver_req::{DownloadFileRequest, FromMatrixDriverResponse},
39+
},
3740
};
3841

3942
#[derive(Deserialize, Debug)]
@@ -49,6 +52,8 @@ pub(super) enum FromWidgetRequest {
4952
SendToDevice(SendToDeviceRequest),
5053
#[serde(rename = "org.matrix.msc4157.update_delayed_event")]
5154
DelayedEventUpdate(UpdateDelayedEventRequest),
55+
#[serde(rename = "org.matrix.msc4039.download_file")]
56+
DownloadFile(DownloadFileRequest),
5257
}
5358

5459
/// The full response a client sends to a [`FromWidgetRequest`] in case of an
@@ -145,6 +150,7 @@ impl SupportedApiVersionsResponse {
145150
ApiVersion::MSC2762UpdateState,
146151
ApiVersion::MSC2871,
147152
ApiVersion::MSC3819,
153+
ApiVersion::MSC4039,
148154
],
149155
}
150156
}
@@ -192,6 +198,9 @@ pub(super) enum ApiVersion {
192198
/// Supports access to the TURN servers.
193199
#[serde(rename = "town.robin.msc3846")]
194200
MSC3846,
201+
202+
#[serde(rename = "org.matrix.msc4039")]
203+
MSC4039,
195204
}
196205

197206
#[derive(Deserialize, Debug)]
@@ -276,3 +285,27 @@ impl FromMatrixDriverResponse for SendToDeviceEventResponse {
276285
}
277286
}
278287
}
288+
289+
/// Response for a download file request.
290+
/// https://github.com/matrix-org/matrix-spec-proposals/pull/4039
291+
#[derive(Serialize, Debug)]
292+
pub(crate) struct DownloadFileResponse {
293+
// The binary file content in a format that can cross the
294+
// widget-driver-api boundary.
295+
#[serde(rename = "file")]
296+
pub(crate) file_data_base64: Base64,
297+
}
298+
299+
impl FromMatrixDriverResponse for DownloadFileResponse {
300+
fn from_response(matrix_driver_response: MatrixDriverResponse) -> Option<Self> {
301+
match matrix_driver_response {
302+
MatrixDriverResponse::FileDownloaded(resp) => {
303+
Some(Self { file_data_base64: resp.file_data_base64 })
304+
}
305+
_ => {
306+
error!("bug in MatrixDriver, received wrong event response");
307+
None
308+
}
309+
}
310+
}
311+
}

crates/matrix-sdk/src/widget/machine/incoming.rs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use super::{
2828
from_widget::{FromWidgetRequest, SendEventResponse},
2929
to_widget::ToWidgetResponse,
3030
};
31-
use crate::widget::Capabilities;
31+
use crate::widget::{Capabilities, machine::from_widget::DownloadFileResponse};
3232

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

9294
pub(super) struct IncomingWidgetMessage {
@@ -136,3 +138,58 @@ impl<'de> Deserialize<'de> for IncomingWidgetMessage {
136138
Ok(Self { widget_id, request_id, kind })
137139
}
138140
}
141+
142+
#[cfg(test)]
143+
mod tests {
144+
use assert_matches2::assert_let;
145+
146+
use crate::widget::machine::{
147+
from_widget::FromWidgetRequest,
148+
incoming::{IncomingWidgetMessage, IncomingWidgetMessageKind},
149+
};
150+
151+
#[test]
152+
fn parse_download_file_widget_action() {
153+
let raw = r#"
154+
{
155+
"api": "fromWidget",
156+
"widgetId": "aGNStSuL3hhIISSCXgpt15j2",
157+
"requestId": "generated-id-1234",
158+
"action": "org.matrix.msc4039.download_file",
159+
"data": {
160+
"content_uri": "mxc://server/id"
161+
}
162+
}
163+
"#;
164+
165+
assert_let!(
166+
IncomingWidgetMessageKind::Request(incoming_request) =
167+
serde_json::from_str::<IncomingWidgetMessage>(raw).unwrap().kind
168+
);
169+
assert_let!(FromWidgetRequest::DownloadFile(req) = incoming_request.deserialize().unwrap());
170+
171+
assert_eq!(req.content_uri, "mxc://server/id");
172+
}
173+
#[test]
174+
fn parse_download_file_request_with_non_mxc_url() {
175+
let raw = r#"
176+
{
177+
"api": "fromWidget",
178+
"widgetId": "aGNStSuL3hhIISSCXgpt15j2",
179+
"requestId": "generated-id-1234",
180+
"action": "org.matrix.msc4039.download_file",
181+
"data": {
182+
"content_uri": "https://server/id"
183+
}
184+
}
185+
"#;
186+
187+
assert_let!(
188+
IncomingWidgetMessageKind::Request(incoming_request) =
189+
serde_json::from_str::<IncomingWidgetMessage>(raw).unwrap().kind
190+
);
191+
assert_let!(FromWidgetRequest::DownloadFile(req) = incoming_request.deserialize().unwrap());
192+
193+
assert!(!req.content_uri.is_valid());
194+
}
195+
}

0 commit comments

Comments
 (0)