Rewrite public widget API

This commit is contained in:
Jonas Platte
2023-09-27 12:31:53 +02:00
committed by Jonas Platte
parent ae524b3fa6
commit cbb7b24cb5
2 changed files with 174 additions and 100 deletions

View File

@@ -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<WidgetComm>,
pub struct WidgetDriverAndHandle {
pub driver: Arc<WidgetDriver>,
pub handle: Arc<WidgetDriverHandle>,
}
impl From<Widget> 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<Option<matrix_sdk::widget::WidgetDriver>>);
#[uniffi::export(async_runtime = "tokio")]
impl WidgetDriver {
pub async fn run(
&self,
room: Arc<Room>,
permissions_provider: Box<dyn WidgetPermissionsProvider>,
) {
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<WidgetSettings> 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<String> {
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<Room>,
widget: Widget,
permissions_provider: Box<dyn WidgetPermissionsProvider>,
) {
let permissions_provider = PermissionsProviderWrap(permissions_provider.into());
if let Err(()) =
matrix_sdk::widget::run_widget_api(room.inner.clone(), widget.into(), permissions_provider)
.await
{}
}

View File

@@ -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<String>,
/// 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<String>,
}
/// 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<String>,
/// 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<String>,
to_widget_rx: Receiver<String>,
/// 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<String>,
}
/// 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<T>` does not implement `Sink<T>`.
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<String> {
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<T>` does not implement `Sink<T>`.
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(())
}
}