Kobler kommunikasjonsnoder til LiveKit for sanntidslyd. Bruker sender join_communication-intensjon, maskinrommet validerer tilgang og returnerer signert LiveKit JWT-token + rom-URL. Nye komponenter: - maskinrommet/src/livekit.rs: JWT token-generering (HS256-signert med LIVEKIT_API_SECRET, 1-times TTL, publisher/subscriber-roller) - POST /intentions/join_communication: validerer deltaker-edge, genererer token, oppretter rom i STDB, oppdaterer node-metadata - POST /intentions/leave_communication: fjerner deltaker fra STDB - POST /intentions/close_communication: stenger rom (krever owner) - SpacetimeDB: live_room + room_participant tabeller for sanntids deltakerliste (frontend abonnerer via WebSocket) SpacetimeDB-modul publisert som synops-v2 (ny identitet etter at den opprinnelige ikke lenger var tilgjengelig). .env oppdatert. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
133 lines
3.8 KiB
Rust
133 lines
3.8 KiB
Rust
// 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<LiveKitToken, String> {
|
|
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<bool, String> {
|
|
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}")),
|
|
}
|
|
}
|