mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-07 23:44:53 -04:00
Rewrite public widget API
This commit is contained in:
committed by
Jonas Platte
parent
ae524b3fa6
commit
cbb7b24cb5
@@ -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
|
||||
{}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user