atagen

matrix-rust-sdk

public
Star0
Watch0
ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk.git

Blame: bindings/matrix-sdk-ffi/src/widget.rs

7ff8adafe9e4atagen2026-10-22 12:30 UTC1
// Copyright 2025 The Matrix.org Foundation C.I.C.
7ff8adafe9e4atagen2026-10-22 12:30 UTC2
//
7ff8adafe9e4atagen2026-10-22 12:30 UTC3
// Licensed under the Apache License, Version 2.0 (the "License");
7ff8adafe9e4atagen2026-10-22 12:30 UTC4
// you may not use this file except in compliance with the License.
7ff8adafe9e4atagen2026-10-22 12:30 UTC5
// You may obtain a copy of the License at
7ff8adafe9e4atagen2026-10-22 12:30 UTC6
//
7ff8adafe9e4atagen2026-10-22 12:30 UTC7
//     http://www.apache.org/licenses/LICENSE-2.0
7ff8adafe9e4atagen2026-10-22 12:30 UTC8
//
7ff8adafe9e4atagen2026-10-22 12:30 UTC9
// Unless required by applicable law or agreed to in writing, software
7ff8adafe9e4atagen2026-10-22 12:30 UTC10
// distributed under the License is distributed on an "AS IS" BASIS,
7ff8adafe9e4atagen2026-10-22 12:30 UTC11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7ff8adafe9e4atagen2026-10-22 12:30 UTC12
// See the License for that specific language governing permissions and
7ff8adafe9e4atagen2026-10-22 12:30 UTC13
// limitations under the License.
7ff8adafe9e4atagen2026-10-22 12:30 UTC14
7ff8adafe9e4atagen2026-10-22 12:30 UTC15
use std::sync::{Arc, Mutex};
7ff8adafe9e4atagen2026-10-22 12:30 UTC16
7ff8adafe9e4atagen2026-10-22 12:30 UTC17
use language_tags::LanguageTag;
7ff8adafe9e4atagen2026-10-22 12:30 UTC18
use matrix_sdk::widget::{MessageLikeEventFilter, StateEventFilter, ToDeviceEventFilter};
7ff8adafe9e4atagen2026-10-22 12:30 UTC19
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
7ff8adafe9e4atagen2026-10-22 12:30 UTC20
use ruma::events::MessageLikeEventType;
7ff8adafe9e4atagen2026-10-22 12:30 UTC21
use tracing::error;
7ff8adafe9e4atagen2026-10-22 12:30 UTC22
7ff8adafe9e4atagen2026-10-22 12:30 UTC23
use crate::{room::Room, runtime::get_runtime_handle};
7ff8adafe9e4atagen2026-10-22 12:30 UTC24
7ff8adafe9e4atagen2026-10-22 12:30 UTC25
#[derive(uniffi::Record)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC26
pub struct WidgetDriverAndHandle {
7ff8adafe9e4atagen2026-10-22 12:30 UTC27
    pub driver: Arc<WidgetDriver>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC28
    pub handle: Arc<WidgetDriverHandle>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC29
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC30
7ff8adafe9e4atagen2026-10-22 12:30 UTC31
#[matrix_sdk_ffi_macros::export]
7ff8adafe9e4atagen2026-10-22 12:30 UTC32
pub fn make_widget_driver(settings: WidgetSettings) -> Result<WidgetDriverAndHandle, ParseError> {
7ff8adafe9e4atagen2026-10-22 12:30 UTC33
    let (driver, handle) = matrix_sdk::widget::WidgetDriver::new(settings.try_into()?);
7ff8adafe9e4atagen2026-10-22 12:30 UTC34
    Ok(WidgetDriverAndHandle {
7ff8adafe9e4atagen2026-10-22 12:30 UTC35
        driver: Arc::new(WidgetDriver(Mutex::new(Some(driver)))),
7ff8adafe9e4atagen2026-10-22 12:30 UTC36
        handle: Arc::new(WidgetDriverHandle(handle)),
7ff8adafe9e4atagen2026-10-22 12:30 UTC37
    })
7ff8adafe9e4atagen2026-10-22 12:30 UTC38
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC39
7ff8adafe9e4atagen2026-10-22 12:30 UTC40
/// An object that handles all interactions of a widget living inside a webview
7ff8adafe9e4atagen2026-10-22 12:30 UTC41
/// or IFrame with the Matrix world.
7ff8adafe9e4atagen2026-10-22 12:30 UTC42
#[derive(uniffi::Object)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC43
pub struct WidgetDriver(Mutex<Option<matrix_sdk::widget::WidgetDriver>>);
7ff8adafe9e4atagen2026-10-22 12:30 UTC44
7ff8adafe9e4atagen2026-10-22 12:30 UTC45
#[matrix_sdk_ffi_macros::export]
7ff8adafe9e4atagen2026-10-22 12:30 UTC46
impl WidgetDriver {
7ff8adafe9e4atagen2026-10-22 12:30 UTC47
    pub async fn run(
7ff8adafe9e4atagen2026-10-22 12:30 UTC48
        &self,
7ff8adafe9e4atagen2026-10-22 12:30 UTC49
        room: Arc<Room>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC50
        capabilities_provider: Box<dyn WidgetCapabilitiesProvider>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC51
    ) {
7ff8adafe9e4atagen2026-10-22 12:30 UTC52
        let Some(driver) = self.0.lock().unwrap().take() else {
7ff8adafe9e4atagen2026-10-22 12:30 UTC53
            error!("Can't call run multiple times on a WidgetDriver");
7ff8adafe9e4atagen2026-10-22 12:30 UTC54
            return;
7ff8adafe9e4atagen2026-10-22 12:30 UTC55
        };
7ff8adafe9e4atagen2026-10-22 12:30 UTC56
7ff8adafe9e4atagen2026-10-22 12:30 UTC57
        let capabilities_provider = CapabilitiesProviderWrap(capabilities_provider.into());
7ff8adafe9e4atagen2026-10-22 12:30 UTC58
        if let Err(()) = driver.run((*room.inner).clone(), capabilities_provider).await {
7ff8adafe9e4atagen2026-10-22 12:30 UTC59
            // TODO
7ff8adafe9e4atagen2026-10-22 12:30 UTC60
        }
7ff8adafe9e4atagen2026-10-22 12:30 UTC61
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC62
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC63
7ff8adafe9e4atagen2026-10-22 12:30 UTC64
/// Information about a widget.
7ff8adafe9e4atagen2026-10-22 12:30 UTC65
#[derive(uniffi::Record, Clone)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC66
pub struct WidgetSettings {
7ff8adafe9e4atagen2026-10-22 12:30 UTC67
    /// Widget's unique identifier.
7ff8adafe9e4atagen2026-10-22 12:30 UTC68
    pub widget_id: String,
7ff8adafe9e4atagen2026-10-22 12:30 UTC69
    /// Whether or not the widget should be initialized on load message
7ff8adafe9e4atagen2026-10-22 12:30 UTC70
    /// (`ContentLoad` message), or upon creation/attaching of the widget to
7ff8adafe9e4atagen2026-10-22 12:30 UTC71
    /// the SDK's state machine that drives the API.
7ff8adafe9e4atagen2026-10-22 12:30 UTC72
    pub init_after_content_load: bool,
7ff8adafe9e4atagen2026-10-22 12:30 UTC73
    /// This contains the url from the widget state event.
7ff8adafe9e4atagen2026-10-22 12:30 UTC74
    /// In this url placeholders can be used to pass information from the client
7ff8adafe9e4atagen2026-10-22 12:30 UTC75
    /// to the widget. Possible values are: `$widgetId`, `$parentUrl`,
7ff8adafe9e4atagen2026-10-22 12:30 UTC76
    /// `$userId`, `$lang`, `$fontScale`, `$analyticsID`.
7ff8adafe9e4atagen2026-10-22 12:30 UTC77
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC78
    /// # Examples
7ff8adafe9e4atagen2026-10-22 12:30 UTC79
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC80
    /// e.g `http://widget.domain?username=$userId`
7ff8adafe9e4atagen2026-10-22 12:30 UTC81
    /// will become: `http://widget.domain?username=@user_matrix_id:server.domain`.
7ff8adafe9e4atagen2026-10-22 12:30 UTC82
    raw_url: String,
7ff8adafe9e4atagen2026-10-22 12:30 UTC83
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC84
7ff8adafe9e4atagen2026-10-22 12:30 UTC85
impl TryFrom<WidgetSettings> for matrix_sdk::widget::WidgetSettings {
7ff8adafe9e4atagen2026-10-22 12:30 UTC86
    type Error = ParseError;
7ff8adafe9e4atagen2026-10-22 12:30 UTC87
7ff8adafe9e4atagen2026-10-22 12:30 UTC88
    fn try_from(value: WidgetSettings) -> Result<Self, Self::Error> {
7ff8adafe9e4atagen2026-10-22 12:30 UTC89
        let WidgetSettings { widget_id, init_after_content_load, raw_url } = value;
7ff8adafe9e4atagen2026-10-22 12:30 UTC90
        Ok(matrix_sdk::widget::WidgetSettings::new(widget_id, init_after_content_load, &raw_url)?)
7ff8adafe9e4atagen2026-10-22 12:30 UTC91
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC92
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC93
7ff8adafe9e4atagen2026-10-22 12:30 UTC94
impl From<matrix_sdk::widget::WidgetSettings> for WidgetSettings {
7ff8adafe9e4atagen2026-10-22 12:30 UTC95
    fn from(value: matrix_sdk::widget::WidgetSettings) -> Self {
7ff8adafe9e4atagen2026-10-22 12:30 UTC96
        WidgetSettings {
7ff8adafe9e4atagen2026-10-22 12:30 UTC97
            widget_id: value.widget_id().to_owned(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC98
            init_after_content_load: value.init_on_content_load(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC99
            raw_url: value.raw_url().to_string(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC100
        }
7ff8adafe9e4atagen2026-10-22 12:30 UTC101
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC102
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC103
7ff8adafe9e4atagen2026-10-22 12:30 UTC104
/// Create the actual url that can be used to setup the WebView or IFrame
7ff8adafe9e4atagen2026-10-22 12:30 UTC105
/// that contains the widget.
7ff8adafe9e4atagen2026-10-22 12:30 UTC106
///
7ff8adafe9e4atagen2026-10-22 12:30 UTC107
/// # Arguments
7ff8adafe9e4atagen2026-10-22 12:30 UTC108
/// * `widget_settings` - The widget settings to generate the url for.
7ff8adafe9e4atagen2026-10-22 12:30 UTC109
/// * `room` - A Matrix room which is used to query the logged in username
7ff8adafe9e4atagen2026-10-22 12:30 UTC110
/// * `props` - Properties from the client that can be used by a widget to adapt
7ff8adafe9e4atagen2026-10-22 12:30 UTC111
///   to the client. e.g. language, font-scale...
7ff8adafe9e4atagen2026-10-22 12:30 UTC112
#[matrix_sdk_ffi_macros::export]
7ff8adafe9e4atagen2026-10-22 12:30 UTC113
pub async fn generate_webview_url(
7ff8adafe9e4atagen2026-10-22 12:30 UTC114
    widget_settings: WidgetSettings,
7ff8adafe9e4atagen2026-10-22 12:30 UTC115
    room: Arc<Room>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC116
    props: ClientProperties,
7ff8adafe9e4atagen2026-10-22 12:30 UTC117
) -> Result<String, ParseError> {
7ff8adafe9e4atagen2026-10-22 12:30 UTC118
    Ok(matrix_sdk::widget::WidgetSettings::generate_webview_url(
7ff8adafe9e4atagen2026-10-22 12:30 UTC119
        &widget_settings.clone().try_into()?,
7ff8adafe9e4atagen2026-10-22 12:30 UTC120
        &room.inner,
7ff8adafe9e4atagen2026-10-22 12:30 UTC121
        props.into(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC122
    )
7ff8adafe9e4atagen2026-10-22 12:30 UTC123
    .await
7ff8adafe9e4atagen2026-10-22 12:30 UTC124
    .map(|url| url.to_string())?)
7ff8adafe9e4atagen2026-10-22 12:30 UTC125
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC126
7ff8adafe9e4atagen2026-10-22 12:30 UTC127
/// `WidgetSettings` are usually created from a state event.
7ff8adafe9e4atagen2026-10-22 12:30 UTC128
/// (currently unimplemented)
7ff8adafe9e4atagen2026-10-22 12:30 UTC129
///
7ff8adafe9e4atagen2026-10-22 12:30 UTC130
/// In some cases the client wants to create custom `WidgetSettings`
7ff8adafe9e4atagen2026-10-22 12:30 UTC131
/// for specific rooms based on other conditions.
7ff8adafe9e4atagen2026-10-22 12:30 UTC132
/// This function returns a `WidgetSettings` object which can be used
7ff8adafe9e4atagen2026-10-22 12:30 UTC133
/// to setup a widget using `run_client_widget_api`
7ff8adafe9e4atagen2026-10-22 12:30 UTC134
/// and to generate the correct url for the widget.
7ff8adafe9e4atagen2026-10-22 12:30 UTC135
///
7ff8adafe9e4atagen2026-10-22 12:30 UTC136
/// # Arguments
7ff8adafe9e4atagen2026-10-22 12:30 UTC137
///
7ff8adafe9e4atagen2026-10-22 12:30 UTC138
/// * `props` - A struct containing the configuration parameters for a element
7ff8adafe9e4atagen2026-10-22 12:30 UTC139
///   call widget.
7ff8adafe9e4atagen2026-10-22 12:30 UTC140
#[matrix_sdk_ffi_macros::export]
7ff8adafe9e4atagen2026-10-22 12:30 UTC141
pub fn new_virtual_element_call_widget(
7ff8adafe9e4atagen2026-10-22 12:30 UTC142
    props: matrix_sdk::widget::VirtualElementCallWidgetProperties,
7ff8adafe9e4atagen2026-10-22 12:30 UTC143
    config: matrix_sdk::widget::VirtualElementCallWidgetConfig,
7ff8adafe9e4atagen2026-10-22 12:30 UTC144
) -> Result<WidgetSettings, ParseError> {
7ff8adafe9e4atagen2026-10-22 12:30 UTC145
    Ok(matrix_sdk::widget::WidgetSettings::new_virtual_element_call_widget(props, config)
7ff8adafe9e4atagen2026-10-22 12:30 UTC146
        .map(|w| w.into())?)
7ff8adafe9e4atagen2026-10-22 12:30 UTC147
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC148
7ff8adafe9e4atagen2026-10-22 12:30 UTC149
/// The Capabilities required to run a element call widget.
7ff8adafe9e4atagen2026-10-22 12:30 UTC150
///
7ff8adafe9e4atagen2026-10-22 12:30 UTC151
/// This is intended to be used in combination with: `acquire_capabilities` of
7ff8adafe9e4atagen2026-10-22 12:30 UTC152
/// the `CapabilitiesProvider`.
7ff8adafe9e4atagen2026-10-22 12:30 UTC153
///
7ff8adafe9e4atagen2026-10-22 12:30 UTC154
/// `acquire_capabilities` can simply return the `WidgetCapabilities` from this
7ff8adafe9e4atagen2026-10-22 12:30 UTC155
/// function. Even if there are non intersecting permissions to what the widget
7ff8adafe9e4atagen2026-10-22 12:30 UTC156
/// requested.
7ff8adafe9e4atagen2026-10-22 12:30 UTC157
///
7ff8adafe9e4atagen2026-10-22 12:30 UTC158
/// Editing and extending the capabilities from this function is also possible,
7ff8adafe9e4atagen2026-10-22 12:30 UTC159
/// but should only be done as temporal workarounds until this function is
7ff8adafe9e4atagen2026-10-22 12:30 UTC160
/// adjusted
7ff8adafe9e4atagen2026-10-22 12:30 UTC161
#[matrix_sdk_ffi_macros::export]
7ff8adafe9e4atagen2026-10-22 12:30 UTC162
pub fn get_element_call_required_permissions(
7ff8adafe9e4atagen2026-10-22 12:30 UTC163
    own_user_id: String,
7ff8adafe9e4atagen2026-10-22 12:30 UTC164
    own_device_id: String,
7ff8adafe9e4atagen2026-10-22 12:30 UTC165
) -> WidgetCapabilities {
7ff8adafe9e4atagen2026-10-22 12:30 UTC166
    use ruma::events::StateEventType;
7ff8adafe9e4atagen2026-10-22 12:30 UTC167
7ff8adafe9e4atagen2026-10-22 12:30 UTC168
    let read_send = vec![
7ff8adafe9e4atagen2026-10-22 12:30 UTC169
        // To read and send rageshake requests from other room members
7ff8adafe9e4atagen2026-10-22 12:30 UTC170
        WidgetEventFilter::MessageLikeWithType {
7ff8adafe9e4atagen2026-10-22 12:30 UTC171
            event_type: "org.matrix.rageshake_request".to_owned(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC172
        },
7ff8adafe9e4atagen2026-10-22 12:30 UTC173
        // To read and send encryption keys
7ff8adafe9e4atagen2026-10-22 12:30 UTC174
        WidgetEventFilter::ToDevice { event_type: "io.element.call.encryption_keys".to_owned() },
7ff8adafe9e4atagen2026-10-22 12:30 UTC175
        // TODO change this to the appropriate to-device version once ready
7ff8adafe9e4atagen2026-10-22 12:30 UTC176
        // remove this once all matrixRTC call apps supports to-device encryption.
7ff8adafe9e4atagen2026-10-22 12:30 UTC177
        WidgetEventFilter::MessageLikeWithType {
7ff8adafe9e4atagen2026-10-22 12:30 UTC178
            event_type: "io.element.call.encryption_keys".to_owned(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC179
        },
7ff8adafe9e4atagen2026-10-22 12:30 UTC180
        // To read and send custom EC reactions. They are different to normal `m.reaction`
7ff8adafe9e4atagen2026-10-22 12:30 UTC181
        // because they can be send multiple times to the same event.
7ff8adafe9e4atagen2026-10-22 12:30 UTC182
        WidgetEventFilter::MessageLikeWithType {
7ff8adafe9e4atagen2026-10-22 12:30 UTC183
            event_type: "io.element.call.reaction".to_owned(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC184
        },
7ff8adafe9e4atagen2026-10-22 12:30 UTC185
        // This allows send raise hand reactions.
7ff8adafe9e4atagen2026-10-22 12:30 UTC186
        WidgetEventFilter::MessageLikeWithType {
7ff8adafe9e4atagen2026-10-22 12:30 UTC187
            event_type: MessageLikeEventType::Reaction.to_string(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC188
        },
7ff8adafe9e4atagen2026-10-22 12:30 UTC189
        // This allows to detect if someone does not raise their hand anymore.
7ff8adafe9e4atagen2026-10-22 12:30 UTC190
        WidgetEventFilter::MessageLikeWithType {
7ff8adafe9e4atagen2026-10-22 12:30 UTC191
            event_type: MessageLikeEventType::RoomRedaction.to_string(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC192
        },
7ff8adafe9e4atagen2026-10-22 12:30 UTC193
        // This allows declining an incoming call and detect if someone declines a call.
7ff8adafe9e4atagen2026-10-22 12:30 UTC194
        WidgetEventFilter::MessageLikeWithType {
7ff8adafe9e4atagen2026-10-22 12:30 UTC195
            event_type: MessageLikeEventType::RtcDecline.to_string(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC196
        },
7ff8adafe9e4atagen2026-10-22 12:30 UTC197
    ];
7ff8adafe9e4atagen2026-10-22 12:30 UTC198
7ff8adafe9e4atagen2026-10-22 12:30 UTC199
    WidgetCapabilities {
7ff8adafe9e4atagen2026-10-22 12:30 UTC200
        read: vec![
7ff8adafe9e4atagen2026-10-22 12:30 UTC201
            // To compute the current state of the matrixRTC session.
7ff8adafe9e4atagen2026-10-22 12:30 UTC202
            WidgetEventFilter::StateWithType { event_type: StateEventType::CallMember.to_string() },
7ff8adafe9e4atagen2026-10-22 12:30 UTC203
            // To display the name of the room.
7ff8adafe9e4atagen2026-10-22 12:30 UTC204
            WidgetEventFilter::StateWithType { event_type: StateEventType::RoomName.to_string() },
7ff8adafe9e4atagen2026-10-22 12:30 UTC205
            // To detect leaving/kicked room members during a call.
7ff8adafe9e4atagen2026-10-22 12:30 UTC206
            WidgetEventFilter::StateWithType { event_type: StateEventType::RoomMember.to_string() },
7ff8adafe9e4atagen2026-10-22 12:30 UTC207
            // To decide whether to encrypt the call streams based on the room encryption setting.
7ff8adafe9e4atagen2026-10-22 12:30 UTC208
            WidgetEventFilter::StateWithType {
7ff8adafe9e4atagen2026-10-22 12:30 UTC209
                event_type: StateEventType::RoomEncryption.to_string(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC210
            },
7ff8adafe9e4atagen2026-10-22 12:30 UTC211
            // This allows the widget to check the room version, so it can know about
7ff8adafe9e4atagen2026-10-22 12:30 UTC212
            // version-specific auth rules (namely MSC3779).
7ff8adafe9e4atagen2026-10-22 12:30 UTC213
            WidgetEventFilter::StateWithType { event_type: StateEventType::RoomCreate.to_string() },
7ff8adafe9e4atagen2026-10-22 12:30 UTC214
        ]
7ff8adafe9e4atagen2026-10-22 12:30 UTC215
        .into_iter()
7ff8adafe9e4atagen2026-10-22 12:30 UTC216
        .chain(read_send.clone())
7ff8adafe9e4atagen2026-10-22 12:30 UTC217
        .collect(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC218
        send: vec![
7ff8adafe9e4atagen2026-10-22 12:30 UTC219
            // To notify other users that a call has started.
7ff8adafe9e4atagen2026-10-22 12:30 UTC220
            WidgetEventFilter::MessageLikeWithType {
7ff8adafe9e4atagen2026-10-22 12:30 UTC221
                event_type: MessageLikeEventType::RtcNotification.to_string(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC222
            },
7ff8adafe9e4atagen2026-10-22 12:30 UTC223
            // Also for call notifications, except this is the deprecated fallback type which
7ff8adafe9e4atagen2026-10-22 12:30 UTC224
            // Element Call still sends.
7ff8adafe9e4atagen2026-10-22 12:30 UTC225
            // Deprecated for now, kept for backward compatibility as widgets will send both
7ff8adafe9e4atagen2026-10-22 12:30 UTC226
            // CallNotify and RtcNotification.
7ff8adafe9e4atagen2026-10-22 12:30 UTC227
            WidgetEventFilter::MessageLikeWithType {
7ff8adafe9e4atagen2026-10-22 12:30 UTC228
                event_type: MessageLikeEventType::CallNotify.to_string(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC229
            },
7ff8adafe9e4atagen2026-10-22 12:30 UTC230
            // To send the call participation state event (main MatrixRTC event).
7ff8adafe9e4atagen2026-10-22 12:30 UTC231
            // This is required for legacy state events (using only one event for all devices with
7ff8adafe9e4atagen2026-10-22 12:30 UTC232
            // a membership array). TODO: remove once legacy call member events are
7ff8adafe9e4atagen2026-10-22 12:30 UTC233
            // sunset.
7ff8adafe9e4atagen2026-10-22 12:30 UTC234
            WidgetEventFilter::StateWithTypeAndStateKey {
7ff8adafe9e4atagen2026-10-22 12:30 UTC235
                event_type: StateEventType::CallMember.to_string(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC236
                state_key: own_user_id.clone(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC237
            },
7ff8adafe9e4atagen2026-10-22 12:30 UTC238
            // `delayed_event`` version for session memberhips
7ff8adafe9e4atagen2026-10-22 12:30 UTC239
            // [MSC3779](https://github.com/matrix-org/matrix-spec-proposals/pull/3779), with no leading underscore.
7ff8adafe9e4atagen2026-10-22 12:30 UTC240
            WidgetEventFilter::StateWithTypeAndStateKey {
7ff8adafe9e4atagen2026-10-22 12:30 UTC241
                event_type: StateEventType::CallMember.to_string(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC242
                state_key: format!("{own_user_id}_{own_device_id}"),
7ff8adafe9e4atagen2026-10-22 12:30 UTC243
            },
7ff8adafe9e4atagen2026-10-22 12:30 UTC244
            // Same as above for [MSC3779] and [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143),
7ff8adafe9e4atagen2026-10-22 12:30 UTC245
            // with application suffix
7ff8adafe9e4atagen2026-10-22 12:30 UTC246
            WidgetEventFilter::StateWithTypeAndStateKey {
7ff8adafe9e4atagen2026-10-22 12:30 UTC247
                event_type: StateEventType::CallMember.to_string(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC248
                state_key: format!("{own_user_id}_{own_device_id}_m.call"),
7ff8adafe9e4atagen2026-10-22 12:30 UTC249
            },
7ff8adafe9e4atagen2026-10-22 12:30 UTC250
            // The same as above but with an underscore.
7ff8adafe9e4atagen2026-10-22 12:30 UTC251
            // To work around the issue that state events starting with `@` have to be Matrix id's
7ff8adafe9e4atagen2026-10-22 12:30 UTC252
            // but we use mxId+deviceId.
7ff8adafe9e4atagen2026-10-22 12:30 UTC253
            WidgetEventFilter::StateWithTypeAndStateKey {
7ff8adafe9e4atagen2026-10-22 12:30 UTC254
                event_type: StateEventType::CallMember.to_string(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC255
                state_key: format!("_{own_user_id}_{own_device_id}"),
7ff8adafe9e4atagen2026-10-22 12:30 UTC256
            },
7ff8adafe9e4atagen2026-10-22 12:30 UTC257
            // Same as above for [MSC4143], with application suffix
7ff8adafe9e4atagen2026-10-22 12:30 UTC258
            WidgetEventFilter::StateWithTypeAndStateKey {
7ff8adafe9e4atagen2026-10-22 12:30 UTC259
                event_type: StateEventType::CallMember.to_string(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC260
                state_key: format!("_{own_user_id}_{own_device_id}_m.call"),
7ff8adafe9e4atagen2026-10-22 12:30 UTC261
            },
7ff8adafe9e4atagen2026-10-22 12:30 UTC262
        ]
7ff8adafe9e4atagen2026-10-22 12:30 UTC263
        .into_iter()
7ff8adafe9e4atagen2026-10-22 12:30 UTC264
        .chain(read_send)
7ff8adafe9e4atagen2026-10-22 12:30 UTC265
        .collect(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC266
        requires_client: true,
7ff8adafe9e4atagen2026-10-22 12:30 UTC267
        update_delayed_event: true,
7ff8adafe9e4atagen2026-10-22 12:30 UTC268
        send_delayed_event: true,
7ff8adafe9e4atagen2026-10-22 12:30 UTC269
        download_files: true,
7ff8adafe9e4atagen2026-10-22 12:30 UTC270
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC271
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC272
7ff8adafe9e4atagen2026-10-22 12:30 UTC273
#[derive(uniffi::Record)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC274
pub struct ClientProperties {
7ff8adafe9e4atagen2026-10-22 12:30 UTC275
    /// The client_id provides the widget with the option to behave differently
7ff8adafe9e4atagen2026-10-22 12:30 UTC276
    /// for different clients. e.g org.example.ios.
7ff8adafe9e4atagen2026-10-22 12:30 UTC277
    client_id: String,
7ff8adafe9e4atagen2026-10-22 12:30 UTC278
    /// The language tag the client is set to e.g. en-us. (Undefined and invalid
7ff8adafe9e4atagen2026-10-22 12:30 UTC279
    /// becomes: `en-US`)
7ff8adafe9e4atagen2026-10-22 12:30 UTC280
    language_tag: Option<String>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC281
    /// A string describing the theme (dark, light) or org.example.dark.
7ff8adafe9e4atagen2026-10-22 12:30 UTC282
    /// (default: `light`)
7ff8adafe9e4atagen2026-10-22 12:30 UTC283
    theme: Option<String>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC284
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC285
7ff8adafe9e4atagen2026-10-22 12:30 UTC286
impl From<ClientProperties> for matrix_sdk::widget::ClientProperties {
7ff8adafe9e4atagen2026-10-22 12:30 UTC287
    fn from(value: ClientProperties) -> Self {
7ff8adafe9e4atagen2026-10-22 12:30 UTC288
        let ClientProperties { client_id, language_tag, theme } = value;
7ff8adafe9e4atagen2026-10-22 12:30 UTC289
        let language_tag = language_tag.and_then(|l| LanguageTag::parse(&l).ok());
7ff8adafe9e4atagen2026-10-22 12:30 UTC290
        Self::new(&client_id, language_tag, theme)
7ff8adafe9e4atagen2026-10-22 12:30 UTC291
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC292
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC293
7ff8adafe9e4atagen2026-10-22 12:30 UTC294
/// A handle that encapsulates the communication between a widget driver and the
7ff8adafe9e4atagen2026-10-22 12:30 UTC295
/// corresponding widget (inside a webview or IFrame).
7ff8adafe9e4atagen2026-10-22 12:30 UTC296
#[derive(uniffi::Object)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC297
pub struct WidgetDriverHandle(matrix_sdk::widget::WidgetDriverHandle);
7ff8adafe9e4atagen2026-10-22 12:30 UTC298
7ff8adafe9e4atagen2026-10-22 12:30 UTC299
#[matrix_sdk_ffi_macros::export]
7ff8adafe9e4atagen2026-10-22 12:30 UTC300
impl WidgetDriverHandle {
7ff8adafe9e4atagen2026-10-22 12:30 UTC301
    /// Receive a message from the widget driver.
7ff8adafe9e4atagen2026-10-22 12:30 UTC302
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC303
    /// The message must be passed on to the widget.
7ff8adafe9e4atagen2026-10-22 12:30 UTC304
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC305
    /// Returns `None` if the widget driver is no longer running.
7ff8adafe9e4atagen2026-10-22 12:30 UTC306
    pub async fn recv(&self) -> Option<String> {
7ff8adafe9e4atagen2026-10-22 12:30 UTC307
        self.0.recv().await
7ff8adafe9e4atagen2026-10-22 12:30 UTC308
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC309
7ff8adafe9e4atagen2026-10-22 12:30 UTC310
    //// Send a message from the widget to the widget driver.
7ff8adafe9e4atagen2026-10-22 12:30 UTC311
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC312
    /// Returns `false` if the widget driver is no longer running.
7ff8adafe9e4atagen2026-10-22 12:30 UTC313
    pub async fn send(&self, msg: String) -> bool {
7ff8adafe9e4atagen2026-10-22 12:30 UTC314
        self.0.send(msg).await
7ff8adafe9e4atagen2026-10-22 12:30 UTC315
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC316
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC317
7ff8adafe9e4atagen2026-10-22 12:30 UTC318
/// Capabilities that a widget can request from a client.
7ff8adafe9e4atagen2026-10-22 12:30 UTC319
#[derive(uniffi::Record)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC320
pub struct WidgetCapabilities {
7ff8adafe9e4atagen2026-10-22 12:30 UTC321
    /// Types of the messages that a widget wants to be able to fetch.
7ff8adafe9e4atagen2026-10-22 12:30 UTC322
    pub read: Vec<WidgetEventFilter>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC323
    /// Types of the messages that a widget wants to be able to send.
7ff8adafe9e4atagen2026-10-22 12:30 UTC324
    pub send: Vec<WidgetEventFilter>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC325
    /// If this capability is requested by the widget, it can not operate
7ff8adafe9e4atagen2026-10-22 12:30 UTC326
    /// separately from the Matrix client.
7ff8adafe9e4atagen2026-10-22 12:30 UTC327
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC328
    /// This means clients should not offer to open the widget in a separate
7ff8adafe9e4atagen2026-10-22 12:30 UTC329
    /// browser/tab/webview that is not connected to the postmessage widget-api.
7ff8adafe9e4atagen2026-10-22 12:30 UTC330
    pub requires_client: bool,
7ff8adafe9e4atagen2026-10-22 12:30 UTC331
    /// This allows the widget to ask the client to update delayed events.
7ff8adafe9e4atagen2026-10-22 12:30 UTC332
    pub update_delayed_event: bool,
7ff8adafe9e4atagen2026-10-22 12:30 UTC333
    /// This allows the widget to send events with a delay.
7ff8adafe9e4atagen2026-10-22 12:30 UTC334
    pub send_delayed_event: bool,
7ff8adafe9e4atagen2026-10-22 12:30 UTC335
    /// This allows the widget to download files (avatars)
7ff8adafe9e4atagen2026-10-22 12:30 UTC336
    pub download_files: bool,
7ff8adafe9e4atagen2026-10-22 12:30 UTC337
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC338
7ff8adafe9e4atagen2026-10-22 12:30 UTC339
impl From<WidgetCapabilities> for matrix_sdk::widget::Capabilities {
7ff8adafe9e4atagen2026-10-22 12:30 UTC340
    fn from(value: WidgetCapabilities) -> Self {
7ff8adafe9e4atagen2026-10-22 12:30 UTC341
        Self {
7ff8adafe9e4atagen2026-10-22 12:30 UTC342
            read: value.read.into_iter().map(Into::into).collect(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC343
            send: value.send.into_iter().map(Into::into).collect(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC344
            requires_client: value.requires_client,
7ff8adafe9e4atagen2026-10-22 12:30 UTC345
            update_delayed_event: value.update_delayed_event,
7ff8adafe9e4atagen2026-10-22 12:30 UTC346
            send_delayed_event: value.send_delayed_event,
7ff8adafe9e4atagen2026-10-22 12:30 UTC347
            download_file: value.download_files,
7ff8adafe9e4atagen2026-10-22 12:30 UTC348
        }
7ff8adafe9e4atagen2026-10-22 12:30 UTC349
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC350
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC351
7ff8adafe9e4atagen2026-10-22 12:30 UTC352
impl From<matrix_sdk::widget::Capabilities> for WidgetCapabilities {
7ff8adafe9e4atagen2026-10-22 12:30 UTC353
    fn from(value: matrix_sdk::widget::Capabilities) -> Self {
7ff8adafe9e4atagen2026-10-22 12:30 UTC354
        Self {
7ff8adafe9e4atagen2026-10-22 12:30 UTC355
            read: value.read.into_iter().map(Into::into).collect(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC356
            send: value.send.into_iter().map(Into::into).collect(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC357
            requires_client: value.requires_client,
7ff8adafe9e4atagen2026-10-22 12:30 UTC358
            update_delayed_event: value.update_delayed_event,
7ff8adafe9e4atagen2026-10-22 12:30 UTC359
            send_delayed_event: value.send_delayed_event,
7ff8adafe9e4atagen2026-10-22 12:30 UTC360
            download_files: value.download_file,
7ff8adafe9e4atagen2026-10-22 12:30 UTC361
        }
7ff8adafe9e4atagen2026-10-22 12:30 UTC362
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC363
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC364
7ff8adafe9e4atagen2026-10-22 12:30 UTC365
/// Different kinds of filters that could be applied to the timeline events.
7ff8adafe9e4atagen2026-10-22 12:30 UTC366
#[derive(uniffi::Enum, Clone)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC367
pub enum WidgetEventFilter {
7ff8adafe9e4atagen2026-10-22 12:30 UTC368
    /// Matches message-like events with the given `type`.
7ff8adafe9e4atagen2026-10-22 12:30 UTC369
    MessageLikeWithType { event_type: String },
7ff8adafe9e4atagen2026-10-22 12:30 UTC370
    /// Matches `m.room.message` events with the given `msgtype`.
7ff8adafe9e4atagen2026-10-22 12:30 UTC371
    RoomMessageWithMsgtype { msgtype: String },
7ff8adafe9e4atagen2026-10-22 12:30 UTC372
    /// Matches state events with the given `type`, regardless of `state_key`.
7ff8adafe9e4atagen2026-10-22 12:30 UTC373
    StateWithType { event_type: String },
7ff8adafe9e4atagen2026-10-22 12:30 UTC374
    /// Matches state events with the given `type` and `state_key`.
7ff8adafe9e4atagen2026-10-22 12:30 UTC375
    StateWithTypeAndStateKey { event_type: String, state_key: String },
7ff8adafe9e4atagen2026-10-22 12:30 UTC376
    /// Matches to-device events with the given `event_type`.
7ff8adafe9e4atagen2026-10-22 12:30 UTC377
    ToDevice { event_type: String },
7ff8adafe9e4atagen2026-10-22 12:30 UTC378
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC379
7ff8adafe9e4atagen2026-10-22 12:30 UTC380
impl From<WidgetEventFilter> for matrix_sdk::widget::Filter {
7ff8adafe9e4atagen2026-10-22 12:30 UTC381
    fn from(value: WidgetEventFilter) -> Self {
7ff8adafe9e4atagen2026-10-22 12:30 UTC382
        match value {
7ff8adafe9e4atagen2026-10-22 12:30 UTC383
            WidgetEventFilter::MessageLikeWithType { event_type } => {
7ff8adafe9e4atagen2026-10-22 12:30 UTC384
                Self::MessageLike(MessageLikeEventFilter::WithType(event_type.into()))
7ff8adafe9e4atagen2026-10-22 12:30 UTC385
            }
7ff8adafe9e4atagen2026-10-22 12:30 UTC386
            WidgetEventFilter::RoomMessageWithMsgtype { msgtype } => {
7ff8adafe9e4atagen2026-10-22 12:30 UTC387
                Self::MessageLike(MessageLikeEventFilter::RoomMessageWithMsgtype(msgtype))
7ff8adafe9e4atagen2026-10-22 12:30 UTC388
            }
7ff8adafe9e4atagen2026-10-22 12:30 UTC389
            WidgetEventFilter::StateWithType { event_type } => {
7ff8adafe9e4atagen2026-10-22 12:30 UTC390
                Self::State(StateEventFilter::WithType(event_type.into()))
7ff8adafe9e4atagen2026-10-22 12:30 UTC391
            }
7ff8adafe9e4atagen2026-10-22 12:30 UTC392
            WidgetEventFilter::StateWithTypeAndStateKey { event_type, state_key } => {
7ff8adafe9e4atagen2026-10-22 12:30 UTC393
                Self::State(StateEventFilter::WithTypeAndStateKey(event_type.into(), state_key))
7ff8adafe9e4atagen2026-10-22 12:30 UTC394
            }
7ff8adafe9e4atagen2026-10-22 12:30 UTC395
            WidgetEventFilter::ToDevice { event_type } => {
7ff8adafe9e4atagen2026-10-22 12:30 UTC396
                Self::ToDevice(ToDeviceEventFilter { event_type: event_type.into() })
7ff8adafe9e4atagen2026-10-22 12:30 UTC397
            }
7ff8adafe9e4atagen2026-10-22 12:30 UTC398
        }
7ff8adafe9e4atagen2026-10-22 12:30 UTC399
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC400
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC401
7ff8adafe9e4atagen2026-10-22 12:30 UTC402
impl From<matrix_sdk::widget::Filter> for WidgetEventFilter {
7ff8adafe9e4atagen2026-10-22 12:30 UTC403
    fn from(value: matrix_sdk::widget::Filter) -> Self {
7ff8adafe9e4atagen2026-10-22 12:30 UTC404
        use matrix_sdk::widget::Filter as F;
7ff8adafe9e4atagen2026-10-22 12:30 UTC405
7ff8adafe9e4atagen2026-10-22 12:30 UTC406
        match value {
7ff8adafe9e4atagen2026-10-22 12:30 UTC407
            F::MessageLike(MessageLikeEventFilter::WithType(event_type)) => {
7ff8adafe9e4atagen2026-10-22 12:30 UTC408
                Self::MessageLikeWithType { event_type: event_type.to_string() }
7ff8adafe9e4atagen2026-10-22 12:30 UTC409
            }
7ff8adafe9e4atagen2026-10-22 12:30 UTC410
            F::MessageLike(MessageLikeEventFilter::RoomMessageWithMsgtype(msgtype)) => {
7ff8adafe9e4atagen2026-10-22 12:30 UTC411
                Self::RoomMessageWithMsgtype { msgtype }
7ff8adafe9e4atagen2026-10-22 12:30 UTC412
            }
7ff8adafe9e4atagen2026-10-22 12:30 UTC413
            F::State(StateEventFilter::WithType(event_type)) => {
7ff8adafe9e4atagen2026-10-22 12:30 UTC414
                Self::StateWithType { event_type: event_type.to_string() }
7ff8adafe9e4atagen2026-10-22 12:30 UTC415
            }
7ff8adafe9e4atagen2026-10-22 12:30 UTC416
            F::State(StateEventFilter::WithTypeAndStateKey(event_type, state_key)) => {
7ff8adafe9e4atagen2026-10-22 12:30 UTC417
                Self::StateWithTypeAndStateKey { event_type: event_type.to_string(), state_key }
7ff8adafe9e4atagen2026-10-22 12:30 UTC418
            }
7ff8adafe9e4atagen2026-10-22 12:30 UTC419
            F::ToDevice(ToDeviceEventFilter { event_type }) => {
7ff8adafe9e4atagen2026-10-22 12:30 UTC420
                Self::ToDevice { event_type: event_type.to_string() }
7ff8adafe9e4atagen2026-10-22 12:30 UTC421
            }
7ff8adafe9e4atagen2026-10-22 12:30 UTC422
        }
7ff8adafe9e4atagen2026-10-22 12:30 UTC423
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC424
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC425
7ff8adafe9e4atagen2026-10-22 12:30 UTC426
#[matrix_sdk_ffi_macros::export(callback_interface)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC427
pub trait WidgetCapabilitiesProvider: SendOutsideWasm + SyncOutsideWasm {
7ff8adafe9e4atagen2026-10-22 12:30 UTC428
    fn acquire_capabilities(&self, capabilities: WidgetCapabilities) -> WidgetCapabilities;
7ff8adafe9e4atagen2026-10-22 12:30 UTC429
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC430
7ff8adafe9e4atagen2026-10-22 12:30 UTC431
struct CapabilitiesProviderWrap(Arc<dyn WidgetCapabilitiesProvider>);
7ff8adafe9e4atagen2026-10-22 12:30 UTC432
7ff8adafe9e4atagen2026-10-22 12:30 UTC433
impl matrix_sdk::widget::CapabilitiesProvider for CapabilitiesProviderWrap {
7ff8adafe9e4atagen2026-10-22 12:30 UTC434
    async fn acquire_capabilities(
7ff8adafe9e4atagen2026-10-22 12:30 UTC435
        &self,
7ff8adafe9e4atagen2026-10-22 12:30 UTC436
        capabilities: matrix_sdk::widget::Capabilities,
7ff8adafe9e4atagen2026-10-22 12:30 UTC437
    ) -> matrix_sdk::widget::Capabilities {
7ff8adafe9e4atagen2026-10-22 12:30 UTC438
        let this = self.0.clone();
7ff8adafe9e4atagen2026-10-22 12:30 UTC439
        // This could require a prompt to the user. Ideally the callback
7ff8adafe9e4atagen2026-10-22 12:30 UTC440
        // interface would just be async, but that's not supported yet so use
7ff8adafe9e4atagen2026-10-22 12:30 UTC441
        // one of tokio's blocking task threads instead.
7ff8adafe9e4atagen2026-10-22 12:30 UTC442
        get_runtime_handle()
7ff8adafe9e4atagen2026-10-22 12:30 UTC443
            .spawn_blocking(move || this.acquire_capabilities(capabilities.into()).into())
7ff8adafe9e4atagen2026-10-22 12:30 UTC444
            .await
7ff8adafe9e4atagen2026-10-22 12:30 UTC445
            // propagate panics from the blocking task
7ff8adafe9e4atagen2026-10-22 12:30 UTC446
            .unwrap()
7ff8adafe9e4atagen2026-10-22 12:30 UTC447
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC448
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC449
7ff8adafe9e4atagen2026-10-22 12:30 UTC450
#[derive(Debug, thiserror::Error, uniffi::Error)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC451
#[uniffi(flat_error)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC452
pub enum ParseError {
7ff8adafe9e4atagen2026-10-22 12:30 UTC453
    #[error("empty host")]
7ff8adafe9e4atagen2026-10-22 12:30 UTC454
    EmptyHost,
7ff8adafe9e4atagen2026-10-22 12:30 UTC455
    #[error("invalid international domain name")]
7ff8adafe9e4atagen2026-10-22 12:30 UTC456
    IdnaError,
7ff8adafe9e4atagen2026-10-22 12:30 UTC457
    #[error("invalid port number")]
7ff8adafe9e4atagen2026-10-22 12:30 UTC458
    InvalidPort,
7ff8adafe9e4atagen2026-10-22 12:30 UTC459
    #[error("invalid IPv4 address")]
7ff8adafe9e4atagen2026-10-22 12:30 UTC460
    InvalidIpv4Address,
7ff8adafe9e4atagen2026-10-22 12:30 UTC461
    #[error("invalid IPv6 address")]
7ff8adafe9e4atagen2026-10-22 12:30 UTC462
    InvalidIpv6Address,
7ff8adafe9e4atagen2026-10-22 12:30 UTC463
    #[error("invalid domain character")]
7ff8adafe9e4atagen2026-10-22 12:30 UTC464
    InvalidDomainCharacter,
7ff8adafe9e4atagen2026-10-22 12:30 UTC465
    #[error("relative URL without a base")]
7ff8adafe9e4atagen2026-10-22 12:30 UTC466
    RelativeUrlWithoutBase,
7ff8adafe9e4atagen2026-10-22 12:30 UTC467
    #[error("relative URL with a cannot-be-a-base base")]
7ff8adafe9e4atagen2026-10-22 12:30 UTC468
    RelativeUrlWithCannotBeABaseBase,
7ff8adafe9e4atagen2026-10-22 12:30 UTC469
    #[error("a cannot-be-a-base URL doesn’t have a host to set")]
7ff8adafe9e4atagen2026-10-22 12:30 UTC470
    SetHostOnCannotBeABaseUrl,
7ff8adafe9e4atagen2026-10-22 12:30 UTC471
    #[error("URLs more than 4 GB are not supported")]
7ff8adafe9e4atagen2026-10-22 12:30 UTC472
    Overflow,
7ff8adafe9e4atagen2026-10-22 12:30 UTC473
    #[error("unknown URL parsing error")]
7ff8adafe9e4atagen2026-10-22 12:30 UTC474
    Other,
7ff8adafe9e4atagen2026-10-22 12:30 UTC475
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC476
7ff8adafe9e4atagen2026-10-22 12:30 UTC477
impl From<url::ParseError> for ParseError {
7ff8adafe9e4atagen2026-10-22 12:30 UTC478
    fn from(value: url::ParseError) -> Self {
7ff8adafe9e4atagen2026-10-22 12:30 UTC479
        match value {
7ff8adafe9e4atagen2026-10-22 12:30 UTC480
            url::ParseError::EmptyHost => Self::EmptyHost,
7ff8adafe9e4atagen2026-10-22 12:30 UTC481
            url::ParseError::IdnaError => Self::IdnaError,
7ff8adafe9e4atagen2026-10-22 12:30 UTC482
            url::ParseError::InvalidPort => Self::InvalidPort,
7ff8adafe9e4atagen2026-10-22 12:30 UTC483
            url::ParseError::InvalidIpv4Address => Self::InvalidIpv4Address,
7ff8adafe9e4atagen2026-10-22 12:30 UTC484
            url::ParseError::InvalidIpv6Address => Self::InvalidIpv6Address,
7ff8adafe9e4atagen2026-10-22 12:30 UTC485
            url::ParseError::InvalidDomainCharacter => Self::InvalidDomainCharacter,
7ff8adafe9e4atagen2026-10-22 12:30 UTC486
            url::ParseError::RelativeUrlWithoutBase => Self::RelativeUrlWithoutBase,
7ff8adafe9e4atagen2026-10-22 12:30 UTC487
            url::ParseError::RelativeUrlWithCannotBeABaseBase => {
7ff8adafe9e4atagen2026-10-22 12:30 UTC488
                Self::RelativeUrlWithCannotBeABaseBase
7ff8adafe9e4atagen2026-10-22 12:30 UTC489
            }
7ff8adafe9e4atagen2026-10-22 12:30 UTC490
            url::ParseError::SetHostOnCannotBeABaseUrl => Self::SetHostOnCannotBeABaseUrl,
7ff8adafe9e4atagen2026-10-22 12:30 UTC491
            url::ParseError::Overflow => Self::Overflow,
7ff8adafe9e4atagen2026-10-22 12:30 UTC492
            _ => Self::Other,
7ff8adafe9e4atagen2026-10-22 12:30 UTC493
        }
7ff8adafe9e4atagen2026-10-22 12:30 UTC494
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC495
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC496
7ff8adafe9e4atagen2026-10-22 12:30 UTC497
#[cfg(test)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC498
mod tests {
7ff8adafe9e4atagen2026-10-22 12:30 UTC499
    use matrix_sdk::widget::Capabilities;
7ff8adafe9e4atagen2026-10-22 12:30 UTC500
7ff8adafe9e4atagen2026-10-22 12:30 UTC501
    use super::get_element_call_required_permissions;
7ff8adafe9e4atagen2026-10-22 12:30 UTC502
7ff8adafe9e4atagen2026-10-22 12:30 UTC503
    #[test]
7ff8adafe9e4atagen2026-10-22 12:30 UTC504
    fn element_call_permissions_are_correct() {
7ff8adafe9e4atagen2026-10-22 12:30 UTC505
        let widget_cap = get_element_call_required_permissions(
7ff8adafe9e4atagen2026-10-22 12:30 UTC506
            "@my_user:my_domain.org".to_owned(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC507
            "ABCDEFGHI".to_owned(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC508
        );
7ff8adafe9e4atagen2026-10-22 12:30 UTC509
7ff8adafe9e4atagen2026-10-22 12:30 UTC510
        // We test two things:
7ff8adafe9e4atagen2026-10-22 12:30 UTC511
7ff8adafe9e4atagen2026-10-22 12:30 UTC512
        // Converting the WidgetCapability (ffi struct) to Capabilities (rust sdk
7ff8adafe9e4atagen2026-10-22 12:30 UTC513
        // struct)
7ff8adafe9e4atagen2026-10-22 12:30 UTC514
        let cap = Into::<Capabilities>::into(widget_cap);
7ff8adafe9e4atagen2026-10-22 12:30 UTC515
        // Converting Capabilities (rust sdk struct) to a json list.
7ff8adafe9e4atagen2026-10-22 12:30 UTC516
        let cap_json_repr = serde_json::to_string(&cap).unwrap();
7ff8adafe9e4atagen2026-10-22 12:30 UTC517
7ff8adafe9e4atagen2026-10-22 12:30 UTC518
        // Converting to a Vec<String> allows to check if the required elements exist
7ff8adafe9e4atagen2026-10-22 12:30 UTC519
        // without breaking the test each time the order of permissions might
7ff8adafe9e4atagen2026-10-22 12:30 UTC520
        // change.
7ff8adafe9e4atagen2026-10-22 12:30 UTC521
        let permission_array: Vec<String> = serde_json::from_str(&cap_json_repr).unwrap();
7ff8adafe9e4atagen2026-10-22 12:30 UTC522
7ff8adafe9e4atagen2026-10-22 12:30 UTC523
        let cap_assert = |capability: &str| {
7ff8adafe9e4atagen2026-10-22 12:30 UTC524
            assert!(
7ff8adafe9e4atagen2026-10-22 12:30 UTC525
                permission_array.contains(&capability.to_owned()),
7ff8adafe9e4atagen2026-10-22 12:30 UTC526
                "The \"{capability}\" capability was missing from the element call capability list."
7ff8adafe9e4atagen2026-10-22 12:30 UTC527
            );
7ff8adafe9e4atagen2026-10-22 12:30 UTC528
        };
7ff8adafe9e4atagen2026-10-22 12:30 UTC529
7ff8adafe9e4atagen2026-10-22 12:30 UTC530
        cap_assert("io.element.requires_client");
7ff8adafe9e4atagen2026-10-22 12:30 UTC531
        cap_assert("org.matrix.msc4157.update_delayed_event");
7ff8adafe9e4atagen2026-10-22 12:30 UTC532
        cap_assert("org.matrix.msc4157.send.delayed_event");
7ff8adafe9e4atagen2026-10-22 12:30 UTC533
        cap_assert("org.matrix.msc2762.receive.state_event:org.matrix.msc3401.call.member");
7ff8adafe9e4atagen2026-10-22 12:30 UTC534
        cap_assert("org.matrix.msc2762.receive.state_event:m.room.name");
7ff8adafe9e4atagen2026-10-22 12:30 UTC535
        cap_assert("org.matrix.msc2762.receive.state_event:m.room.member");
7ff8adafe9e4atagen2026-10-22 12:30 UTC536
        cap_assert("org.matrix.msc2762.receive.state_event:m.room.encryption");
7ff8adafe9e4atagen2026-10-22 12:30 UTC537
        cap_assert("org.matrix.msc2762.receive.event:org.matrix.rageshake_request");
7ff8adafe9e4atagen2026-10-22 12:30 UTC538
        cap_assert("org.matrix.msc2762.receive.event:io.element.call.encryption_keys");
7ff8adafe9e4atagen2026-10-22 12:30 UTC539
        cap_assert("org.matrix.msc2762.receive.state_event:m.room.create");
7ff8adafe9e4atagen2026-10-22 12:30 UTC540
        cap_assert(
7ff8adafe9e4atagen2026-10-22 12:30 UTC541
            "org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#@my_user:my_domain.org",
7ff8adafe9e4atagen2026-10-22 12:30 UTC542
        );
7ff8adafe9e4atagen2026-10-22 12:30 UTC543
        cap_assert(
7ff8adafe9e4atagen2026-10-22 12:30 UTC544
            "org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#@my_user:my_domain.org_ABCDEFGHI",
7ff8adafe9e4atagen2026-10-22 12:30 UTC545
        );
7ff8adafe9e4atagen2026-10-22 12:30 UTC546
        cap_assert(
7ff8adafe9e4atagen2026-10-22 12:30 UTC547
            "org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#@my_user:my_domain.org_ABCDEFGHI_m.call",
7ff8adafe9e4atagen2026-10-22 12:30 UTC548
        );
7ff8adafe9e4atagen2026-10-22 12:30 UTC549
        cap_assert(
7ff8adafe9e4atagen2026-10-22 12:30 UTC550
            "org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#_@my_user:my_domain.org_ABCDEFGHI",
7ff8adafe9e4atagen2026-10-22 12:30 UTC551
        );
7ff8adafe9e4atagen2026-10-22 12:30 UTC552
        cap_assert(
7ff8adafe9e4atagen2026-10-22 12:30 UTC553
            "org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#_@my_user:my_domain.org_ABCDEFGHI_m.call",
7ff8adafe9e4atagen2026-10-22 12:30 UTC554
        );
7ff8adafe9e4atagen2026-10-22 12:30 UTC555
        cap_assert("org.matrix.msc2762.send.event:org.matrix.rageshake_request");
7ff8adafe9e4atagen2026-10-22 12:30 UTC556
        cap_assert("org.matrix.msc2762.send.event:io.element.call.encryption_keys");
7ff8adafe9e4atagen2026-10-22 12:30 UTC557
7ff8adafe9e4atagen2026-10-22 12:30 UTC558
        // RTC decline
7ff8adafe9e4atagen2026-10-22 12:30 UTC559
        cap_assert("org.matrix.msc2762.receive.event:org.matrix.msc4310.rtc.decline");
7ff8adafe9e4atagen2026-10-22 12:30 UTC560
        cap_assert("org.matrix.msc2762.send.event:org.matrix.msc4310.rtc.decline");
7ff8adafe9e4atagen2026-10-22 12:30 UTC561
7ff8adafe9e4atagen2026-10-22 12:30 UTC562
        // Download avatars
7ff8adafe9e4atagen2026-10-22 12:30 UTC563
        cap_assert("org.matrix.msc4039.download_file");
7ff8adafe9e4atagen2026-10-22 12:30 UTC564
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC565
}