synpad 0.1.0

A full-featured Matrix chat client built with Dioxus
pub mod config;
pub mod session;
pub mod sliding_sync;
pub mod timeline;

use anyhow::Result;
use matrix_sdk::Client;
use matrix_sdk::authentication::matrix::MatrixSession;
use matrix_sdk::authentication::SessionTokens;

use crate::client::config::ClientConfig;
use crate::persistence::matrix_state::SessionData;

/// Build a new Matrix client with the given homeserver URL.
pub async fn build_client(homeserver: &str) -> Result<Client> {
    let config = ClientConfig::default();
    let db_path = config.data_dir().join("matrix-sdk-state");

    // Ensure the data directory exists
    std::fs::create_dir_all(&db_path)?;

    let client = Client::builder()
        .homeserver_url(homeserver)
        .sqlite_store(&db_path, None)
        .build()
        .await?;

    Ok(client)
}

/// Restore a previously saved session.
pub async fn restore_session(session_data: SessionData) -> Result<Client> {
    let client = build_client(&session_data.homeserver_url).await?;

    // Reconstruct the MatrixSession from our saved data
    let user_id = matrix_sdk::ruma::OwnedUserId::try_from(session_data.user_id.as_str())
        .map_err(|e| anyhow::anyhow!("Invalid user ID: {e}"))?;
    let device_id = matrix_sdk::ruma::OwnedDeviceId::from(session_data.device_id.as_str());

    let meta = matrix_sdk::SessionMeta { user_id, device_id };
    let tokens = SessionTokens {
        access_token: session_data.access_token,
        refresh_token: None,
    };
    let session = MatrixSession { meta, tokens };

    client.restore_session(session).await?;
    tracing::info!("Session restored for {}", session_data.homeserver_url);
    Ok(client)
}

/// Login with username and password.
pub async fn login_with_password(
    homeserver: &str,
    username: &str,
    password: &str,
) -> Result<Client> {
    // Clear any stale crypto store from a previous device session.
    // A fresh login creates a new device, which will conflict with
    // crypto keys stored for the old device.
    let config = ClientConfig::default();
    let store_path = config.data_dir().join("matrix-sdk-state");
    if store_path.exists() {
        tracing::info!("Clearing old SDK store before fresh login");
        std::fs::remove_dir_all(&store_path).ok();
    }

    let client = build_client(homeserver).await?;

    // Login using the Matrix authentication API
    client
        .matrix_auth()
        .login_username(username, password)
        .initial_device_display_name("Netrix")
        .await?;

    // Save session with actual access token for future restoration
    let session = client.matrix_auth().session()
        .ok_or_else(|| anyhow::anyhow!("No session after login"))?;

    crate::persistence::matrix_state::save_session(&SessionData {
        homeserver_url: homeserver.to_string(),
        user_id: session.meta.user_id.to_string(),
        device_id: session.meta.device_id.to_string(),
        access_token: session.tokens.access_token.clone(),
    })
    .await?;

    Ok(client)
}