From cbb7b24cb5355c26033d72bbb9ee27e7d9c7e075 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 27 Sep 2023 12:31:53 +0200 Subject: [PATCH] Rewrite public widget API --- bindings/matrix-sdk-ffi/src/widget.rs | 83 +++++++---- crates/matrix-sdk/src/widget/mod.rs | 191 ++++++++++++++++---------- 2 files changed, 174 insertions(+), 100 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/widget.rs b/bindings/matrix-sdk-ffi/src/widget.rs index b8f869fa0..4269fb2ca 100644 --- a/bindings/matrix-sdk-ffi/src/widget.rs +++ b/bindings/matrix-sdk-ffi/src/widget.rs @@ -1,26 +1,48 @@ -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use matrix_sdk::{ async_trait, widget::{MessageLikeEventFilter, StateEventFilter}, }; +use tracing::error; use crate::{room::Room, RUNTIME}; #[derive(uniffi::Record)] -pub struct Widget { - /// Settings for the widget. - pub settings: WidgetSettings, - /// Communication channels with a widget. - pub comm: Arc, +pub struct WidgetDriverAndHandle { + pub driver: Arc, + pub handle: Arc, } -impl From for matrix_sdk::widget::Widget { - fn from(value: Widget) -> Self { - let comm = &value.comm.0; - Self { - settings: value.settings.into(), - comm: matrix_sdk::widget::Comm { from: comm.from.clone(), to: comm.to.clone() }, +#[uniffi::export] +pub fn make_widget_driver(settings: WidgetSettings) -> WidgetDriverAndHandle { + let (driver, handle) = matrix_sdk::widget::WidgetDriver::new(settings.into()); + WidgetDriverAndHandle { + driver: Arc::new(WidgetDriver(Mutex::new(Some(driver)))), + handle: Arc::new(WidgetDriverHandle(handle)), + } +} + +/// An object that handles all interactions of a widget living inside a webview +/// or iframe with the Matrix world. +#[derive(uniffi::Object)] +pub struct WidgetDriver(Mutex>); + +#[uniffi::export(async_runtime = "tokio")] +impl WidgetDriver { + pub async fn run( + &self, + room: Arc, + permissions_provider: Box, + ) { + let Some(driver) = self.0.lock().unwrap().take() else { + error!("Can't call run multiple times on a WidgetDriver"); + return; + }; + + let permissions_provider = PermissionsProviderWrap(permissions_provider.into()); + if let Err(()) = driver.run(room.inner.clone(), permissions_provider).await { + // TODO } } } @@ -43,9 +65,29 @@ impl From for matrix_sdk::widget::WidgetSettings { } } -/// Communication "pipes" with a widget. +/// A handle that encapsulates the communication between a widget driver and the +/// corresponding widget (inside a webview or iframe). #[derive(uniffi::Object)] -pub struct WidgetComm(matrix_sdk::widget::Comm); +pub struct WidgetDriverHandle(matrix_sdk::widget::WidgetDriverHandle); + +#[uniffi::export(async_runtime = "tokio")] +impl WidgetDriverHandle { + /// Receive a message from the widget driver. + /// + /// The message must be passed on to the widget. + /// + /// Returns `None` if the widget driver is no longer running. + pub async fn recv(&self) -> Option { + self.0.recv().await + } + + //// Send a message from the widget to the widget driver. + /// + /// Returns `false` if the widget driver is no longer running. + pub async fn send(&self, msg: String) -> bool { + self.0.send(msg).await + } +} /// Permissions that a widget can request from a client. #[derive(uniffi::Record)] @@ -151,16 +193,3 @@ impl matrix_sdk::widget::PermissionsProvider for PermissionsProviderWrap { .unwrap() } } - -#[uniffi::export] -pub async fn run_widget_api( - room: Arc, - widget: Widget, - permissions_provider: Box, -) { - let permissions_provider = PermissionsProviderWrap(permissions_provider.into()); - if let Err(()) = - matrix_sdk::widget::run_widget_api(room.inner.clone(), widget.into(), permissions_provider) - .await - {} -} diff --git a/crates/matrix-sdk/src/widget/mod.rs b/crates/matrix-sdk/src/widget/mod.rs index 481802a9c..a460c0149 100644 --- a/crates/matrix-sdk/src/widget/mod.rs +++ b/crates/matrix-sdk/src/widget/mod.rs @@ -3,7 +3,7 @@ use async_channel::{Receiver, Sender}; use tokio::sync::mpsc::unbounded_channel; -use crate::room::Room as JoinedRoom; +use crate::room::Room; mod client; mod filter; @@ -15,13 +15,23 @@ pub use self::{ permissions::{Permissions, PermissionsProvider}, }; -/// Describes a widget. +/// An object that handles all interactions of a widget living inside a webview +/// or iframe with the Matrix world. #[derive(Debug)] -pub struct Widget { - /// Settings for the widget. - pub settings: WidgetSettings, - /// Communication channels with a widget. - pub comm: Comm, +pub struct WidgetDriver { + #[allow(dead_code)] + settings: WidgetSettings, + + /// Raw incoming messages from the widget (normally formatted as JSON). + /// + /// These can be both requests and responses. + from_widget_rx: Receiver, + + /// Raw outgoing messages from the client (SDK) to the widget (normally + /// formatted as JSON). + /// + /// These can be both requests and responses. + to_widget_tx: Sender, } /// Information about a widget. @@ -35,79 +45,114 @@ pub struct WidgetSettings { pub init_on_load: bool, } -/// Communication "pipes" with a widget. -#[derive(Debug)] -pub struct Comm { - /// Raw incoming messages from the widget (normally, formatted as JSON). - /// - /// These can be both requests and responses. Users of this API should not - /// care what's what though because they are only supposed to forward - /// messages between the webview / iframe, and the SDK's widget driver. - pub from: Receiver, - /// Raw outgoing messages from the client (SDK) to the widget (normally +/// A handle that encapsulates the communication between a widget driver and the +/// corresponding widget (inside a webview or iframe). +#[derive(Clone, Debug)] +pub struct WidgetDriverHandle { + /// Raw incoming messages from the widget driver to the widget (normally /// formatted as JSON). /// /// These can be both requests and responses. Users of this API should not /// care what's what though because they are only supposed to forward /// messages between the webview / iframe, and the SDK's widget driver. - pub to: Sender, + to_widget_rx: Receiver, + + /// Raw outgoing messages from the widget to the widget driver (normally + /// formatted as JSON). + /// + /// These can be both requests and responses. Users of this API should not + /// care what's what though because they are only supposed to forward + /// messages between the webview / iframe, and the SDK's widget driver. + from_widget_tx: Sender, } -/// Starts a client widget API state machine for a given `widget` in a given -/// joined `room`. The function returns once the widget is disconnected or any -/// terminal error occurs. -/// -/// Not implemented yet! Currently, it does not contain any useful -/// functionality, it only blindly forwards the messages and returns errors once -/// a non-implemented part is triggered. -pub async fn run_widget_api( - _room: JoinedRoom, - widget: Widget, - _permissions_provider: impl PermissionsProvider, -) -> Result<(), ()> { - let Comm { from, to } = widget.comm; - - // Create a channel so that we can conveniently send all events to it. - let (events_tx, mut events_rx) = unbounded_channel(); - - // Forward all incoming raw messages into events and send them to the sink. - // Equivalent of the: - // `from.map(|m| Ok(Event::MessageFromWidget(msg)).forward(events_tx)`, - // but apparently `UnboundedSender` does not implement `Sink`. - let tx = events_tx.clone(); - tokio::spawn(async move { - while let Ok(msg) = from.recv().await { - let _ = tx.send(Event::MessageFromWidget(msg)); - } - }); - - // Process events by passing them to the `ClientApi` implementation. - let mut client_api = ClientApi::new(); - while let Some(event) = events_rx.recv().await { - for action in client_api.process(event) { - match action { - Action::SendToWidget(msg) => to.send(msg).await.map_err(|_| ())?, - Action::AcquirePermissions(cmd) => { - let result = cmd.result(Err("not implemented".into())); - events_tx.send(Event::PermissionsAcquired(result)).map_err(|_| ())?; - } - Action::GetOpenId(cmd) => { - let result = cmd.result(Err("not implemented".into())); - events_tx.send(Event::OpenIdReceived(result)).map_err(|_| ())?; - } - Action::ReadMatrixEvent(cmd) => { - let result = cmd.result(Err("not implemented".into())); - events_tx.send(Event::MatrixEventRead(result)).map_err(|_| ())?; - } - Action::SendMatrixEvent(cmd) => { - let result = cmd.result(Err("not implemented".into())); - events_tx.send(Event::MatrixEventSent(result)).map_err(|_| ())?; - } - Action::Subscribe => {} - Action::Unsubscribe => {} - } - } +impl WidgetDriverHandle { + /// Receive a message from the widget driver. + /// + /// The message must be passed on to the widget. + /// + /// Returns `None` if the widget driver is no longer running. + pub async fn recv(&self) -> Option { + self.to_widget_rx.recv().await.ok() } - Ok(()) + /// Send a message from the widget to the widget driver. + /// + /// Returns `false` if the widget driver is no longer running. + pub async fn send(&self, message: String) -> bool { + self.from_widget_tx.send(message).await.is_ok() + } +} + +impl WidgetDriver { + /// Creates a new `WidgetDriver` and a corresponding set of channels to let + /// the widget (inside a webview or iframe) communicate with it. + pub fn new(settings: WidgetSettings) -> (Self, WidgetDriverHandle) { + let (from_widget_tx, from_widget_rx) = async_channel::unbounded(); + let (to_widget_tx, to_widget_rx) = async_channel::unbounded(); + + let driver = Self { settings, from_widget_rx, to_widget_tx }; + let channels = WidgetDriverHandle { from_widget_tx, to_widget_rx }; + + (driver, channels) + } + + /// Starts a client widget API state machine for a given `widget` in a given + /// joined `room`. The function returns once the widget is disconnected or + /// any terminal error occurs. + /// + /// Not implemented yet! Currently, it does not contain any useful + /// functionality, it only blindly forwards the messages and returns errors + /// once a non-implemented part is triggered. + pub async fn run( + self, + _room: Room, + _permissions_provider: impl PermissionsProvider, + ) -> Result<(), ()> { + // Create a channel so that we can conveniently send all events to it. + let (events_tx, mut events_rx) = unbounded_channel(); + + // Forward all incoming raw messages into events and send them to the sink. + // Equivalent of the: + // `from.map(|m| Ok(Event::MessageFromWidget(msg)).forward(events_tx)`, + // but apparently `UnboundedSender` does not implement `Sink`. + let tx = events_tx.clone(); + tokio::spawn(async move { + while let Ok(msg) = self.from_widget_rx.recv().await { + let _ = tx.send(Event::MessageFromWidget(msg)); + } + }); + + // Process events by passing them to the `ClientApi` implementation. + let mut client_api = ClientApi::new(); + while let Some(event) = events_rx.recv().await { + for action in client_api.process(event) { + match action { + Action::SendToWidget(msg) => { + self.to_widget_tx.send(msg).await.map_err(|_| ())? + } + Action::AcquirePermissions(cmd) => { + let result = cmd.result(Err("not implemented".into())); + events_tx.send(Event::PermissionsAcquired(result)).map_err(|_| ())?; + } + Action::GetOpenId(cmd) => { + let result = cmd.result(Err("not implemented".into())); + events_tx.send(Event::OpenIdReceived(result)).map_err(|_| ())?; + } + Action::ReadMatrixEvent(cmd) => { + let result = cmd.result(Err("not implemented".into())); + events_tx.send(Event::MatrixEventRead(result)).map_err(|_| ())?; + } + Action::SendMatrixEvent(cmd) => { + let result = cmd.result(Err("not implemented".into())); + events_tx.send(Event::MatrixEventSent(result)).map_err(|_| ())?; + } + Action::Subscribe => {} + Action::Unsubscribe => {} + } + } + } + + Ok(()) + } }