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