Skip to main content

matrix_sdk/widget/
mod.rs

1// Copyright 2023 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![allow(rustdoc::private_intra_doc_links)]
16#![doc = include_str!("README.md")]
17
18use std::{fmt, time::Duration};
19
20use futures_util::StreamExt;
21use matrix_sdk_common::executor::spawn;
22use ruma::api::client::delayed_events::DelayParameters;
23use serde::de::{self, Deserialize, Deserializer, Visitor};
24use tokio::sync::{
25    Mutex,
26    mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel},
27};
28use tokio_stream::wrappers::UnboundedReceiverStream;
29use tokio_util::sync::{CancellationToken, DropGuard};
30
31use self::{
32    machine::{
33        Action, IncomingMessage, MatrixDriverRequestData, MatrixDriverResponse, SendEventRequest,
34        WidgetMachine,
35    },
36    matrix::MatrixDriver,
37};
38use crate::{Result, room::Room, widget::machine::DownloadFileResponse};
39
40mod capabilities;
41mod filter;
42mod machine;
43mod matrix;
44mod settings;
45
46pub use self::{
47    capabilities::{Capabilities, CapabilitiesProvider},
48    filter::{Filter, MessageLikeEventFilter, StateEventFilter, ToDeviceEventFilter},
49    settings::{
50        ClientProperties, EncryptionSystem, Intent, VirtualElementCallWidgetConfig,
51        VirtualElementCallWidgetProperties, WidgetSettings,
52    },
53};
54
55/// An object that handles all interactions of a widget living inside a webview
56/// or iframe with the Matrix world.
57#[derive(Debug)]
58pub struct WidgetDriver {
59    settings: WidgetSettings,
60
61    /// Raw incoming messages from the widget (normally formatted as JSON).
62    ///
63    /// These can be both requests and responses.
64    from_widget_rx: UnboundedReceiver<String>,
65
66    /// Raw outgoing messages from the client (SDK) to the widget (normally
67    /// formatted as JSON).
68    ///
69    /// These can be both requests and responses.
70    to_widget_tx: UnboundedSender<String>,
71
72    /// Drop guard for an event handler forwarding all events from the Matrix
73    /// room to the widget.
74    ///
75    /// Only set if a subscription happened ([`Action::Subscribe`]).
76    event_forwarding_guard: Option<DropGuard>,
77}
78
79/// A handle that encapsulates the communication between a widget driver and the
80/// corresponding widget (inside a webview or iframe).
81#[derive(Debug)]
82pub struct WidgetDriverHandle {
83    /// Raw incoming messages from the widget driver to the widget (normally
84    /// formatted as JSON).
85    ///
86    /// These can be both requests and responses. Users of this API should not
87    /// care what's what though because they are only supposed to forward
88    /// messages between the webview / iframe, and the SDK's widget driver.
89    to_widget_rx: Mutex<UnboundedReceiver<String>>,
90
91    /// Raw outgoing messages from the widget to the widget driver (normally
92    /// formatted as JSON).
93    ///
94    /// These can be both requests and responses. Users of this API should not
95    /// care what's what though because they are only supposed to forward
96    /// messages between the webview / iframe, and the SDK's widget driver.
97    from_widget_tx: UnboundedSender<String>,
98}
99
100impl WidgetDriverHandle {
101    /// Receive a message from the widget driver.
102    ///
103    /// The message must be passed on to the widget.
104    ///
105    /// Returns `None` if the widget driver is no longer running.
106    ///
107    /// Despite this method takes a shared reference to `self` with `&self`,
108    /// there is an inner lock around the receiver. They can be only one
109    /// receiver at a time. Be aware if `recv` is waiting in a loop for example.
110    /// This design addresses one particular need where `WidgetDriverHandle`
111    /// lives inside an `Arc` to be cloned and passed in 2 tasks: one calling
112    /// `send`, one calling `recv`. In that case, there is no conflict.
113    pub async fn recv(&self) -> Option<String> {
114        self.to_widget_rx.lock().await.recv().await
115    }
116
117    /// Send a message from the widget to the widget driver.
118    ///
119    /// Returns `false` if the widget driver is no longer running.
120    pub fn send(&self, message: String) -> bool {
121        self.from_widget_tx.send(message).is_ok()
122    }
123}
124
125impl WidgetDriver {
126    /// Creates a new `WidgetDriver` and a corresponding set of channels to let
127    /// the widget (inside a webview or iframe) communicate with it.
128    pub fn new(settings: WidgetSettings) -> (Self, WidgetDriverHandle) {
129        let (from_widget_tx, from_widget_rx) = unbounded_channel();
130        let (to_widget_tx, to_widget_rx) = unbounded_channel();
131
132        let driver = Self { settings, from_widget_rx, to_widget_tx, event_forwarding_guard: None };
133        let channels =
134            WidgetDriverHandle { from_widget_tx, to_widget_rx: Mutex::new(to_widget_rx) };
135
136        (driver, channels)
137    }
138
139    /// Run client widget API state machine in a given joined `room` forever.
140    ///
141    /// The function returns once the widget is disconnected or any terminal
142    /// error occurs.
143    #[expect(clippy::result_unit_err)]
144    pub async fn run(
145        self,
146        room: Room,
147        capabilities_provider: impl CapabilitiesProvider,
148    ) -> Result<(), ()> {
149        // Create a channel so that we can conveniently send all messages to it.
150        //
151        // It will receive:
152        // - all incoming messages from the widget
153        // - all responses from the Matrix driver
154        // - all events from the Matrix driver, if subscribed
155        let (incoming_msg_tx, incoming_msg_rx) = unbounded_channel();
156
157        // Forward all of the incoming messages from the widget.
158        // TODO: This spawns a detached task, it would be nice to have an owner for this
159        // task. One way to achieve this if `WidgetDriver::run()` returns a handle that
160        // we can drop which will clean up the task and the channels. It's not too bad,
161        // since canelling `run()` will drop the sender this task listens which finishes
162        // the task.
163        spawn({
164            let incoming_msg_tx = incoming_msg_tx.clone();
165            let mut from_widget_rx = self.from_widget_rx;
166
167            async move {
168                while let Some(msg) = from_widget_rx.recv().await {
169                    let _ = incoming_msg_tx.send(IncomingMessage::WidgetMessage(msg));
170                }
171            }
172        });
173
174        // Create the widget API machine. The widget machine will process messages it
175        // receives from the widget and convert it into actions the `MatrixDriver` will
176        // then execute on.
177        let (mut widget_machine, initial_actions) = WidgetMachine::new(
178            self.settings.widget_id().to_owned(),
179            room.room_id().to_owned(),
180            self.settings.init_on_content_load(),
181        );
182
183        let matrix_driver = MatrixDriver::new(room.clone());
184
185        // Convert the incoming message receiver into a stream of actions.
186        let stream = UnboundedReceiverStream::new(incoming_msg_rx)
187            .flat_map(|message| tokio_stream::iter(widget_machine.process(message)));
188
189        // Let's combine our set of initial actions with the stream of received actions.
190        let mut combined = tokio_stream::iter(initial_actions).chain(stream);
191
192        let to_widget_tx = self.to_widget_tx;
193        let mut event_forwarding_guard = self.event_forwarding_guard;
194
195        // Let's now process all actions we receive forever.
196        while let Some(action) = combined.next().await {
197            Self::process_action(
198                &to_widget_tx,
199                &mut event_forwarding_guard,
200                &matrix_driver,
201                &incoming_msg_tx,
202                &capabilities_provider,
203                action,
204            )
205            .await?;
206        }
207
208        Ok(())
209    }
210
211    /// Process a single [`Action`].
212    async fn process_action(
213        to_widget_tx: &UnboundedSender<String>,
214        event_forwarding_guard: &mut Option<DropGuard>,
215        matrix_driver: &MatrixDriver,
216        incoming_msg_tx: &UnboundedSender<IncomingMessage>,
217        capabilities_provider: &impl CapabilitiesProvider,
218        action: Action,
219    ) -> Result<(), ()> {
220        match action {
221            Action::SendToWidget(msg) => {
222                to_widget_tx.send(msg).map_err(|_| ())?;
223            }
224
225            Action::MatrixDriverRequest { request_id, data } => {
226                let response = match data {
227                    MatrixDriverRequestData::AcquireCapabilities(cmd) => {
228                        let obtained = capabilities_provider
229                            .acquire_capabilities(cmd.desired_capabilities)
230                            .await;
231                        Ok(MatrixDriverResponse::CapabilitiesAcquired(obtained))
232                    }
233
234                    MatrixDriverRequestData::GetOpenId => {
235                        matrix_driver.get_open_id().await.map(MatrixDriverResponse::OpenIdReceived)
236                    }
237
238                    MatrixDriverRequestData::ReadEvents(cmd) => matrix_driver
239                        .read_events(cmd.event_type.into(), cmd.state_key, cmd.limit)
240                        .await
241                        .map(MatrixDriverResponse::EventsRead),
242
243                    MatrixDriverRequestData::ReadState(cmd) => matrix_driver
244                        .read_state(cmd.event_type.into(), &cmd.state_key)
245                        .await
246                        .map(MatrixDriverResponse::StateRead),
247
248                    MatrixDriverRequestData::SendEvent(req) => {
249                        let SendEventRequest { event_type, state_key, content, delay } = req;
250                        // The widget api action does not use the unstable prefix:
251                        // `org.matrix.msc4140.delay` so we
252                        // cannot use the `DelayParameters` here and need to convert
253                        // manually.
254                        let delay_event_parameter = delay.map(|d| DelayParameters::Timeout {
255                            timeout: Duration::from_millis(d),
256                        });
257                        matrix_driver
258                            .send(event_type.into(), state_key, content, delay_event_parameter)
259                            .await
260                            .map(MatrixDriverResponse::EventSent)
261                    }
262
263                    MatrixDriverRequestData::UpdateDelayedEvent(req) => matrix_driver
264                        .update_delayed_event(req.delay_id, req.action)
265                        .await
266                        .map(MatrixDriverResponse::DelayedEventUpdated),
267
268                    MatrixDriverRequestData::SendToDeviceEvent(send_to_device_request) => {
269                        matrix_driver
270                            .send_to_device(
271                                send_to_device_request.event_type.into(),
272                                send_to_device_request.messages,
273                            )
274                            .await
275                            .map(MatrixDriverResponse::ToDeviceSent)
276                    }
277                    MatrixDriverRequestData::DownloadFile(req) => matrix_driver
278                        .download_attachment(req.content_uri)
279                        .await
280                        .map(|file_data_base64| {
281                            MatrixDriverResponse::FileDownloaded(DownloadFileResponse {
282                                file_data_base64,
283                            })
284                        }),
285
286                    MatrixDriverRequestData::GetRtcTransports => matrix_driver
287                        .get_rtc_transports()
288                        .await
289                        .map(MatrixDriverResponse::RtcTransportsReceived),
290                };
291
292                // Forward the Matrix driver response to the incoming message stream.
293                incoming_msg_tx
294                    .send(IncomingMessage::MatrixDriverResponse { request_id, response })
295                    .map_err(|_| ())?;
296            }
297
298            Action::Subscribe => {
299                // Only subscribe if we are not already subscribed.
300                if event_forwarding_guard.is_some() {
301                    return Ok(());
302                }
303
304                let (stop_forwarding, guard) = {
305                    let token = CancellationToken::new();
306                    (token.child_token(), token.drop_guard())
307                };
308
309                event_forwarding_guard.replace(guard);
310
311                let mut events = matrix_driver.events();
312                let mut state_updates = matrix_driver.state_updates();
313                let mut to_device_events = matrix_driver.to_device_events();
314                let incoming_msg_tx = incoming_msg_tx.clone();
315
316                spawn(async move {
317                    loop {
318                        tokio::select! {
319                            _ = stop_forwarding.cancelled() => {
320                                // Upon cancellation, stop this task.
321                                return;
322                            }
323
324                            Some(event) = events.recv() => {
325                                // Forward all events to the incoming messages stream.
326                                let _ = incoming_msg_tx.send(IncomingMessage::MatrixEventReceived(event));
327                            }
328
329                            Ok(state) = state_updates.recv() => {
330                                // Forward all state updates to the incoming messages stream.
331                                let _ = incoming_msg_tx.send(IncomingMessage::StateUpdateReceived(state));
332                            }
333
334                            Some(event) = to_device_events.recv() => {
335                                // Forward all events to the incoming messages stream.
336                                let _ = incoming_msg_tx.send(IncomingMessage::ToDeviceReceived(event));
337                            }
338                        }
339                    }
340                });
341            }
342
343            Action::Unsubscribe => {
344                event_forwarding_guard.take();
345            }
346        }
347
348        Ok(())
349    }
350}
351
352// TODO: Decide which module this type should live in
353#[derive(Clone, Debug)]
354pub(crate) enum StateKeySelector {
355    Key(String),
356    Any,
357}
358
359impl<'de> Deserialize<'de> for StateKeySelector {
360    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
361    where
362        D: Deserializer<'de>,
363    {
364        struct StateKeySelectorVisitor;
365
366        impl Visitor<'_> for StateKeySelectorVisitor {
367            type Value = StateKeySelector;
368
369            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
370                write!(f, "a string or `true`")
371            }
372
373            fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
374            where
375                E: de::Error,
376            {
377                if v {
378                    Ok(StateKeySelector::Any)
379                } else {
380                    Err(E::invalid_value(de::Unexpected::Bool(v), &self))
381                }
382            }
383
384            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
385            where
386                E: de::Error,
387            {
388                self.visit_string(v.to_owned())
389            }
390
391            fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
392            where
393                E: de::Error,
394            {
395                Ok(StateKeySelector::Key(v))
396            }
397        }
398
399        deserializer.deserialize_any(StateKeySelectorVisitor)
400    }
401}
402
403#[cfg(test)]
404mod tests {
405    use assert_matches::assert_matches;
406    use serde_json::json;
407
408    use super::StateKeySelector;
409
410    #[test]
411    fn state_key_selector_from_true() {
412        let state_key = serde_json::from_value(json!(true)).unwrap();
413        assert_matches!(state_key, StateKeySelector::Any);
414    }
415
416    #[test]
417    fn state_key_selector_from_string() {
418        let state_key = serde_json::from_value(json!("test")).unwrap();
419        assert_matches!(state_key, StateKeySelector::Key(k) if k == "test");
420    }
421
422    #[test]
423    fn state_key_selector_from_false() {
424        let result = serde_json::from_value::<StateKeySelector>(json!(false));
425        assert_matches!(result, Err(e) if e.is_data());
426    }
427
428    #[test]
429    fn state_key_selector_from_number() {
430        let result = serde_json::from_value::<StateKeySelector>(json!(5));
431        assert_matches!(result, Err(e) if e.is_data());
432    }
433}