openrtc 1.0.10

OpenRTC: a Rust-first P2P runtime for device discovery, signaling, and iroh/QUIC networking.
Documentation
#![cfg(not(target_arch = "wasm32"))]

use anyhow::{Context, Result};
use openrtc::client::{AuthMode, Client};
use openrtc::native_auth::{FirebaseAuthConfig, NativeAuthState};
use serde_json::{json, Value};
use std::time::{Duration, SystemTime, UNIX_EPOCH};

const DEFAULT_PROJECT_ID: &str = "pluto-rtc-prod";
const DEFAULT_API_KEY: &str = "pk_test_integration_test";
const DEFAULT_SECRET_KEY: &str = "sk_test_integration_plutonium";
const DEFAULT_APP_TAG: &str = "app_integration_test";

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

fn env_or(name: &str, fallback: &str) -> String {
    std::env::var(name)
        .ok()
        .map(|value| value.trim().trim_end_matches('/').to_string())
        .filter(|value| !value.is_empty())
        .unwrap_or_else(|| fallback.to_string())
}

fn now_millis() -> u128 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("system time after epoch")
        .as_millis()
}

async fn mint_custom_token(project_id: &str, user_id: &str) -> Result<String> {
    let functions_host = env_or("OPENRTC_FUNCTIONS_EMULATOR_HOST", "127.0.0.1:5002");
    let response = reqwest::Client::new()
        .post(format!(
            "http://{functions_host}/{project_id}/us-central1/v1Tokens"
        ))
        .bearer_auth(env_or("OPENRTC_EMULATOR_SECRET_KEY", DEFAULT_SECRET_KEY))
        .header(
            "x-openrtc-idempotency-key",
            format!("native-auth-gateway-boundary:{user_id}"),
        )
        .json(&json!({ "userId": user_id, "ttl": 3600 }))
        .send()
        .await
        .context("v1Tokens request failed")?;
    let status = response.status();
    let body: Value = response.json().await.unwrap_or(Value::Null);
    if !status.is_success() {
        anyhow::bail!("v1Tokens failed status={status} body={body}");
    }
    body.get("token")
        .and_then(Value::as_str)
        .map(ToOwned::to_owned)
        .context("v1Tokens response missing token")
}

#[tokio::test]
async fn native_auth_emulator_requires_host_gateway_projection() -> Result<()> {
    if !enabled() {
        eprintln!("skipping native auth emulator test: set OPENRTC_NATIVE_AUTH_EMULATOR=1");
        return Ok(());
    }

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

    let project_id = env_or("OPENRTC_EMULATOR_PROJECT_ID", DEFAULT_PROJECT_ID);
    let api_key = env_or("OPENRTC_EMULATOR_API_KEY", DEFAULT_API_KEY);
    let auth_host = env_or("FIREBASE_AUTH_EMULATOR_HOST", "127.0.0.1:9100");
    let config = FirebaseAuthConfig::new(&api_key)
        .identity_toolkit_base_url(format!("http://{auth_host}/identitytoolkit.googleapis.com"))
        .secure_token_base_url(format!("http://{auth_host}/securetoken.googleapis.com"))
        .refresh_margin(Duration::from_secs(30));

    let user_id = format!("emulator_native_gateway_{}", now_millis());
    let custom_token = mint_custom_token(&project_id, &user_id).await?;
    let auth = NativeAuthState::new();
    let session = auth
        .sign_in_with_custom_token(config, &custom_token)
        .await
        .context("native custom-token exchange failed")?;
    let firebase_uid = session
        .user_id
        .as_deref()
        .context("managed custom-token exchange did not return a Firebase uid")?;
    assert!(
        firebase_uid.starts_with("pluto_app_integration_test_"),
        "managed custom-token exchange must retain the app-scoped Firebase uid; actual={firebase_uid}"
    );

    let client = Client::builder_with_native_auth(project_id, api_key, auth)
        .auth_mode(AuthMode::External)
        .build();
    assert_eq!(client.app_tag(), DEFAULT_APP_TAG);

    let error = client
        .search_devices(session.user_id.as_deref().unwrap_or(&user_id))
        .await
        .expect_err("standalone native clients must fail closed without a gateway provider");
    assert!(
        error
            .to_string()
            .contains("managed coordination gateway is required"),
        "unexpected provider-boundary error: {error:#}"
    );
    Ok(())
}