// LiveKit-integrasjon — token-generering for sanntidslyd. // // Genererer JWT access tokens som gir brukere tilgang til LiveKit-rom. // Tokens signeres med LIVEKIT_API_SECRET (HMAC-SHA256) og inneholder // grants som bestemmer hva deltakeren kan gjøre (publisere, lytte, etc.). // // Ref: docs/concepts/møterommet.md, docs/concepts/studioet.md use jsonwebtoken::{encode, EncodingKey, Header, Algorithm}; use serde::{Deserialize, Serialize}; use uuid::Uuid; /// LiveKit video grant — bestemmer hva en deltaker kan gjøre i et rom. #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct VideoGrant { room: String, room_join: bool, can_publish: bool, can_subscribe: bool, } /// JWT claims for LiveKit access token. #[derive(Serialize)] struct LiveKitClaims { /// API Key (issuer) iss: String, /// Participant identity sub: String, /// Participant name (display) name: String, /// Issued at (unix timestamp) iat: u64, /// Not before (unix timestamp) nbf: u64, /// Expiration (unix timestamp) exp: u64, /// LiveKit video grant video: VideoGrant, /// Metadata (JSON string) #[serde(skip_serializing_if = "String::is_empty")] metadata: String, } /// Rolle i et LiveKit-rom. Bestemmer publiserings-rettigheter. #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] #[serde(rename_all = "snake_case")] pub enum RoomRole { /// Kan publisere og lytte (host, deltaker) Publisher, /// Kan bare lytte (observatør) Subscriber, } /// Resultat fra token-generering. pub struct LiveKitToken { pub room_name: String, pub token: String, pub identity: String, } /// Generer et LiveKit access token for en deltaker. /// /// - `communication_id`: UUID for kommunikasjonsnoden (brukes til rom-navn) /// - `user_id`: Brukerens node_id (brukes som identity) /// - `display_name`: Visningsnavn for deltakeren /// - `role`: Publisher (kan sende lyd) eller Subscriber (bare lytte) /// - `ttl_secs`: Token-levetid i sekunder (typisk 3600 = 1 time) pub fn generate_token( communication_id: Uuid, user_id: Uuid, display_name: &str, role: RoomRole, ttl_secs: u64, ) -> Result { let api_key = std::env::var("LIVEKIT_API_KEY") .map_err(|_| "LIVEKIT_API_KEY ikke satt".to_string())?; let api_secret = std::env::var("LIVEKIT_API_SECRET") .map_err(|_| "LIVEKIT_API_SECRET ikke satt".to_string())?; let room_name = format!("communication_{communication_id}"); let identity = user_id.to_string(); let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs(); let can_publish = role == RoomRole::Publisher; let claims = LiveKitClaims { iss: api_key, sub: identity.clone(), name: display_name.to_string(), iat: now, nbf: now, exp: now + ttl_secs, video: VideoGrant { room: room_name.clone(), room_join: true, can_publish, can_subscribe: true, }, metadata: String::new(), }; let header = Header::new(Algorithm::HS256); let key = EncodingKey::from_secret(api_secret.as_bytes()); let token = encode(&header, &claims, &key) .map_err(|e| format!("Kunne ikke generere LiveKit-token: {e}"))?; Ok(LiveKitToken { room_name, token, identity, }) } /// Sjekk om LiveKit-serveren er tilgjengelig. pub async fn health_check() -> Result { let url = std::env::var("LIVEKIT_URL") .unwrap_or_else(|_| "http://localhost:7880".to_string()); match reqwest::Client::new() .get(&url) .timeout(std::time::Duration::from_secs(3)) .send() .await { Ok(_) => Ok(true), Err(e) => Err(format!("LiveKit utilgjengelig: {e}")), } }