Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Widget API: initial skeleton #2420

Merged
merged 14 commits into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/matrix-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ experimental-sliding-sync = [

docsrs = ["e2e-encryption", "sqlite", "sso-login", "qrcode", "image-proc"]

widget-api = []
jplatte marked this conversation as resolved.
Show resolved Hide resolved

[dependencies]
anyhow = { workspace = true, optional = true }
anymap2 = "0.13.0"
Expand Down
3 changes: 3 additions & 0 deletions crates/matrix-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ pub mod sync;
#[cfg(feature = "experimental-sliding-sync")]
pub mod sliding_sync;

#[cfg(feature = "widget-api")]
pub mod widget_api;

#[cfg(feature = "e2e-encryption")]
pub mod encryption;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove the blank lines between all of the module declarations, and instead have just one to separate the mods from the pub uses?

Copy link
Contributor Author

@daniel-abramov daniel-abramov Aug 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. One question though: should I also remove the empty lines around the sliding_sync. Rationale: I added pub mod widget_api there since it was following the experimental sliding sync module which also was separated by spaces.

I.e. it was:

#[cfg(feature = "experimental-oidc")]
pub mod oidc;
pub mod room;
pub mod sync;

#[cfg(feature = "experimental-sliding-sync")]
pub mod sliding_sync;

#[cfg(feature = "e2e-encryption")]
pub mod encryption;

So I placed the pub mod widget_api right after the pub mod sliding_sync. I've pushed a change, hopefully I got it right.

pub use account::Account;
Expand Down
16 changes: 16 additions & 0 deletions crates/matrix-sdk/src/widget_api/mod.rs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rename this module widget instead of widget_api. Just a personal taste compared to the rest of the SDK.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though in that case, we should not have a widget sub-module. Which is fine by me really, the items from that could be part of this module just as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also prefer short and concise names, I think the only reason we chose widget_api originally was that we thought that if we call it widget, it might be confused with the widget implementation (not the API, in our case the client widget API), but I'm totally fine with renaming it to widget if that's your preference folks!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The client widget API couldn't live in this crate though, right? In that case, let's do the rename as suggested by Ivan.

Copy link
Contributor Author

@daniel-abramov daniel-abramov Aug 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

The client widget API couldn't live in this crate though, right?

The actual implementation of the run() function and its internals. Originally I thought that it would live there, but maybe you're right and it's better to place the client widget API in a separate module or something like this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, then I guess I misunderstood what you meant by "client widget API". Anyways, let's figure this out when it comes to the next PR.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//! Client widget API implementation.

use crate::room::Room as JoinedRoom;

pub mod permissions;
pub mod widget;
daniel-abramov marked this conversation as resolved.
Show resolved Hide resolved

/// 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.
daniel-abramov marked this conversation as resolved.
Show resolved Hide resolved
pub async fn run_widget_api(
_room: JoinedRoom,
_widget: widget::Widget,
_permissions_provider: impl permissions::PermissionsProvider,
) -> Result<(), ()> {
todo!()
}
24 changes: 24 additions & 0 deletions crates/matrix-sdk/src/widget_api/permissions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//! Types and traits related to the permissions that a widget can request from a client.

use async_trait::async_trait;

/// A trait that must be implemented by an entity that performs the validation / authorization
/// of the widget's permissions. Typically, it's a native client that "hosts" the widget.
daniel-abramov marked this conversation as resolved.
Show resolved Hide resolved
#[async_trait]
pub trait PermissionsProvider: Send + Sync + 'static {
/// Receives a request for given permissions and returns the actual permissions that
/// the user (client) granted to a given widget.
daniel-abramov marked this conversation as resolved.
Show resolved Hide resolved
async fn acquire_permissions(&self, permissions: Permissions) -> Permissions;
}

/// Permissions that a widget can request from a client.
#[derive(Debug)]
pub struct Permissions {
/// Types of the messages that a widget wants to be able to fetch.
pub read: Vec<MessageType>,
/// Types of the messages that a widget wants to be able to send.
pub send: Vec<MessageType>,
}

/// An alias for an actual type of the messages that is going to be used with a permission request.
pub type MessageType = String;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for a Matrix event type, and widgets might interact with both message events and state events, right? If yes, this type alias doesn't need to exist, we can use TimelineEventType from ruma::events.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right (if I remember correctly). I actually wanted to also use stronger types here and had a discussion with @toger5, but as far as I can remember, @toger5's suggestion was that it can be any event type in theory and that there was no simple way to map them (no enum with a function f: EventType -> String and f: String -> EventType transformation). @toger5, could you chime in? 🙂

Copy link
Collaborator

@jplatte jplatte Aug 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are other "event" types that aren't part of TimelineEventType, but I'm 99.5% certain that those are out of scope for the widget API. They are ephemeral or mutable things like typing and receipt events, account data and presence. Often when people talk about events, those are implicitly excluded (see also matrix-org/matrix-spec#897).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Widgets are allowed to introduce new unspecced message types. Just arbitrary strings for their own usecases. Does TimelineEevntType have a "other" enum case. If so this would very much be a better solution. If not it would be a shame if a widget is working with its own timeline event types but the widget driver is giving a parsing error.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does TimelineEevntType have a "other" enum case.

Yes,any string can be converted .into() that type. The variant is hidden / opaque, but to check for a custom type, one can also convert the other way using .to_string().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Updated.

31 changes: 31 additions & 0 deletions crates/matrix-sdk/src/widget_api/widget.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//! The description of a widget.

use tokio::sync::mpsc::{Receiver, Sender};

/// Describes a widget.
#[derive(Debug)]
pub struct Widget {
/// Information about the widget.
pub info: Info,
/// Communication channels with a widget.
pub comm: Comm,
}

/// Information about a widget.
#[derive(Debug)]
pub struct Info {
/// Widget's unique identifier.
pub id: String,
/// Whether or not the widget should be initialized on load message (`ContentLoad` message),
/// or upon creation/attaching of the widget to the SDK's state machine that drives the API.
pub init_on_load: bool,
daniel-abramov marked this conversation as resolved.
Show resolved Hide resolved
}

/// Communication "pipes" with a widget.
#[derive(Debug)]
pub struct Comm {
/// Raw incoming messages from the widget (normally, formatted as JSON).
pub from: Receiver<String>,
daniel-abramov marked this conversation as resolved.
Show resolved Hide resolved
/// Raw outgoing messages from the client (SDK) to the widget (normally, formatted as JSON).
daniel-abramov marked this conversation as resolved.
Show resolved Hide resolved
pub to: Sender<String>,
}