openrtc 0.2.1

OpenRTC: a Rust-first P2P runtime for device discovery, signaling, and iroh/QUIC networking.
Documentation
use anyhow::Result;
use openrtc::client::Client;
use serde_json::json;
use tokio::time::{sleep, Duration};

fn live_enabled() -> bool {
    std::env::var("OPENRTC_LIVE_TESTS")
        .map(|value| value == "1" || value.eq_ignore_ascii_case("true"))
        .unwrap_or(false)
}

// Helper to get a Firebase ID Token for testing
async fn get_test_token(email: &str, password: &str) -> Result<(String, String)> {
    let api_key = "AIzaSyAxyDE1xSYNAk5Ohe8VvKWi3xHOxB4cNV8";
    let url = format!(
        "https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key={}",
        api_key
    );

    let client = reqwest::Client::new();
    let res = client
        .post(&url)
        .json(&json!({
            "email": email,
            "password": password,
            "returnSecureToken": true
        }))
        .send()
        .await?;

    if !res.status().is_success() {
        let err_text = res.text().await?;
        return Err(anyhow::anyhow!("Firebase auth failed: {}", err_text));
    }

    let json: serde_json::Value = res.json().await?;
    let token = json["idToken"]
        .as_str()
        .ok_or_else(|| anyhow::anyhow!("No idToken in response"))?;

    let uid = json["localId"]
        .as_str()
        .ok_or_else(|| anyhow::anyhow!("No localId in response"))?;

    Ok((token.to_string(), uid.to_string()))
}

async fn get_anonymous_token() -> Result<(String, String)> {
    let api_key = "AIzaSyAxyDE1xSYNAk5Ohe8VvKWi3xHOxB4cNV8";
    let url = format!(
        "https://identitytoolkit.googleapis.com/v1/accounts:signUp?key={}",
        api_key
    );

    let client = reqwest::Client::new();
    let res = client
        .post(&url)
        .json(&json!({
            "returnSecureToken": true
        }))
        .send()
        .await?;

    if !res.status().is_success() {
        let err_text = res.text().await?;
        return Err(anyhow::anyhow!("Firebase anon auth failed: {}", err_text));
    }

    let json: serde_json::Value = res.json().await?;
    let token = json["idToken"]
        .as_str()
        .ok_or_else(|| anyhow::anyhow!("No idToken in response"))?;

    let uid = json["localId"]
        .as_str()
        .ok_or_else(|| anyhow::anyhow!("No localId in response"))?;

    Ok((token.to_string(), uid.to_string()))
}

#[tokio::test]
async fn test_email_login_and_discovery() -> Result<()> {
    if !live_enabled() {
        eprintln!("skipping live test: set OPENRTC_LIVE_TESTS=1 to run");
        return Ok(());
    }

    rustls::crypto::ring::default_provider()
        .install_default()
        .ok();
    let (token, uid) = get_test_token("test@gmail.com", "testing").await?;

    let client = Client::new(
        "pluto-rtc-prod".into(),
        "pk_test_4444444444444444444444444444444444444444".into(),
        Box::new(move || Some(token.clone())),
    );
    client.set_node_id("test-node-123".to_string()).await;
    client.init_iroh(None, vec![]).await?;
    let ticket = client.endpoint_ticket().await?;

    // Test Updating Presence
    client
        .update_presence(&uid, "Rust Test Suite", &ticket, Some("meta"))
        .await?;

    // Search devices with a bounded propagation window.
    let mut found = false;
    for _ in 0..20 {
        let devices = client.search_devices(&uid).await?;
        if devices.iter().any(|d| d.device_name == "Rust Test Suite") {
            found = true;
            break;
        }
        sleep(Duration::from_millis(500)).await;
    }

    assert!(found, "Rust Test Suite presence did not propagate in time");

    Ok(())
}

#[tokio::test]
async fn test_anonymous_login_and_room_signaling() -> Result<()> {
    if !live_enabled() {
        eprintln!("skipping live test: set OPENRTC_LIVE_TESTS=1 to run");
        return Ok(());
    }

    rustls::crypto::ring::default_provider()
        .install_default()
        .ok();
    let (token_1, uid_1) = get_anonymous_token().await?;
    let (token_2, uid_2) = get_anonymous_token().await?;

    let client1 = Client::new(
        "pluto-rtc-prod".into(),
        "pk_test_5555555555555555555555555555555555555555".into(),
        Box::new(move || Some(token_1.clone())),
    );
    client1.set_node_id("anon-node-1".into()).await;
    client1.init_iroh(None, vec![]).await?;
    let ticket_1 = client1.endpoint_ticket().await?;

    let client2 = Client::new(
        "pluto-rtc-prod".into(),
        "pk_test_5555555555555555555555555555555555555555".into(),
        Box::new(move || Some(token_2.clone())),
    );
    client2.set_node_id("anon-node-2".into()).await;
    client2.init_iroh(None, vec![]).await?;
    let ticket_2 = client2.endpoint_ticket().await?;

    // Both update presence
    client1
        .update_presence(&uid_1, "Anon Client 1", &ticket_1, None)
        .await?;
    client2
        .update_presence(&uid_2, "Anon Client 2", &ticket_2, None)
        .await?;

    // Note: Cross-user discovery might fail due to security rules unless they are in the same room.
    // For now we just test that the functions don't panic or error out on invocation.

    Ok(())
}

#[tokio::test]
async fn test_room_management_and_signaling() -> Result<()> {
    if !live_enabled() {
        eprintln!("skipping live test: set OPENRTC_LIVE_TESTS=1 to run");
        return Ok(());
    }

    rustls::crypto::ring::default_provider()
        .install_default()
        .ok();

    let (token, uid) = get_test_token("test@gmail.com", "testing").await?;
    let client = Client::new(
        "pluto-rtc-prod".into(),
        "pk_test_4444444444444444444444444444444444444444".into(),
        Box::new(move || Some(token.clone())),
    );
    client.set_node_id("sig-node-1".into()).await;

    // Test Room Creation
    let room_id = format!("test-room-{}", uuid::Uuid::new_v4());
    let app_tag = openrtc::app_tag_from_api_key("pk_test_4444444444444444444444444444444444444444");
    let _ = client
        .room
        .create_room(&room_id, &uid, "ticket", "sig-node-1", &app_tag, None)
        .await;

    let members = client
        .room
        .get_members(&room_id, "sig-node-1", &app_tag)
        .await?;
    println!("Room has {} other members", members.len());

    // Test Signaling
    let doc_id = client
        .send_message("sig-node-2", "hello", Some("offer"), None)
        .await?;
    assert!(!doc_id.is_empty());

    Ok(())
}