synpad 0.1.0

A full-featured Matrix chat client built with Dioxus
use matrix_sdk::Client;

/// Device verification state machine.
#[derive(Clone, Debug, PartialEq)]
pub enum VerificationState {
    /// No verification in progress
    Idle,
    /// Verification requested
    Requested {
        flow_id: String,
        from_user: String,
    },
    /// Keys exchanged, waiting for emoji comparison
    KeysExchanged {
        flow_id: String,
        emojis: Vec<(String, String)>,
    },
    /// User confirmed the emojis match
    Confirmed {
        flow_id: String,
    },
    /// Verification completed successfully
    Done {
        flow_id: String,
    },
    /// Verification was cancelled
    Cancelled {
        flow_id: String,
        reason: String,
    },
}

impl Default for VerificationState {
    fn default() -> Self {
        Self::Idle
    }
}

/// Accept an incoming verification request.
pub async fn accept_verification_request(
    client: &Client,
    flow_id: &str,
    from_user: &str,
) -> Result<(), String> {
    let user_id = matrix_sdk::ruma::OwnedUserId::try_from(from_user)
        .map_err(|e| format!("Invalid user ID: {e}"))?;

    let encryption = client.encryption();
    let request = encryption
        .get_verification_request(&user_id, flow_id)
        .await
        .ok_or_else(|| "Verification request not found".to_string())?;

    request
        .accept()
        .await
        .map_err(|e| format!("Failed to accept verification: {e}"))?;

    tracing::info!("Accepted verification request {flow_id} from {from_user}");
    Ok(())
}

/// Confirm that the SAS emoji match.
pub async fn confirm_sas_verification(
    client: &Client,
    flow_id: &str,
    from_user: &str,
) -> Result<(), String> {
    let user_id = matrix_sdk::ruma::OwnedUserId::try_from(from_user)
        .map_err(|e| format!("Invalid user ID: {e}"))?;

    let encryption = client.encryption();
    if let Some(verification) = encryption.get_verification(&user_id, flow_id).await {
        if let Some(sas) = verification.sas() {
            sas.confirm()
                .await
                .map_err(|e| format!("Failed to confirm SAS: {e}"))?;
            tracing::info!("SAS verification confirmed for {flow_id}");
            return Ok(());
        }
    }

    Err("SAS verification not found".to_string())
}

/// Cancel an ongoing verification.
pub async fn cancel_verification(
    client: &Client,
    flow_id: &str,
    from_user: &str,
) -> Result<(), String> {
    let user_id = matrix_sdk::ruma::OwnedUserId::try_from(from_user)
        .map_err(|e| format!("Invalid user ID: {e}"))?;

    let encryption = client.encryption();
    if let Some(request) = encryption.get_verification_request(&user_id, flow_id).await {
        request
            .cancel()
            .await
            .map_err(|e| format!("Failed to cancel verification: {e}"))?;
        tracing::info!("Cancelled verification {flow_id}");
        return Ok(());
    }

    Err("Verification request not found".to_string())
}

/// Request verification of another user's device.
pub async fn request_verification(
    client: &Client,
    user_id_str: &str,
) -> Result<String, String> {
    let user_id = matrix_sdk::ruma::OwnedUserId::try_from(user_id_str)
        .map_err(|e| format!("Invalid user ID: {e}"))?;

    let encryption = client.encryption();
    let identity = encryption
        .get_user_identity(&user_id)
        .await
        .map_err(|e| format!("Failed to get user identity: {e}"))?
        .ok_or_else(|| "User identity not found".to_string())?;

    let request = identity
        .request_verification()
        .await
        .map_err(|e| format!("Failed to request verification: {e}"))?;

    let flow_id = request.flow_id().to_string();
    tracing::info!("Requested verification with {user_id}, flow_id: {flow_id}");
    Ok(flow_id)
}

/// Extract SAS emojis from a verification.
pub async fn get_sas_emojis(
    client: &Client,
    flow_id: &str,
    from_user: &str,
) -> Result<Vec<(String, String)>, String> {
    let user_id = matrix_sdk::ruma::OwnedUserId::try_from(from_user)
        .map_err(|e| format!("Invalid user ID: {e}"))?;

    let encryption = client.encryption();
    if let Some(verification) = encryption.get_verification(&user_id, flow_id).await {
        if let Some(sas) = verification.sas() {
            if let Some(emojis) = sas.emoji() {
                let emoji_list: Vec<(String, String)> = emojis
                    .iter()
                    .map(|e| (e.symbol.to_string(), e.description.to_string()))
                    .collect();
                return Ok(emoji_list);
            }
        }
    }

    Err("SAS emojis not available yet".to_string())
}