### Example: read_app_service_config() usage Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/config.md Example usage of read_app_service_config(). On error, prints the error and exits. After successful retrieval, checks if the configuration is set up and logs the server name. ```rust let app_service_config = match read_app_service_config() { Ok(cfg) => cfg, Err(err) => { eprintln!("{err}"); std::process::exit(1); } }; if app_service_config.is_set_up() { info!("running as application service for {}", app_service_config.hs_server_name); } ``` -------------------------------- ### Constructing and driving a DelayedEventJob Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/delayed_event_manager.md Demonstrates how to initialize a new job and start its execution loop, similar to the Handler::run_loop implementation. ```rust let job = DelayedEventJob::new( &self.cancel, params, self.deps.clone(), self.make_lookup(), self.job_done_tx.clone(), self.job_restarted_tx.clone(), self.app_service_config.as_token.clone(), ) .expect("job creation failed"); start_participant_lookup(&job, self.deps.clone(), self.livekit_auth.clone(), sanity_interval); job.spawn_loop(); ``` -------------------------------- ### Run lk-jwt-service with full option set Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/configuration.md Full configuration example showing all available environment variables: JWT bind address, sanity check interval, CS API URL overrides, Redis connection, appservice tokens, and log level. ```shell LIVEKIT_URL="wss://matrix-rtc.m.localhost/livekit/sfu" \ LIVEKIT_KEY=devkey LIVEKIT_SECRET=secret \ LIVEKIT_JWT_BIND=":8080" \ LIVEKIT_FULL_ACCESS_HOMESERVERS="synapse.m.localhost" \ LIVEKIT_SANITY_CHECK_INTERVAL_SECONDS=30 \ LIVEKIT_CS_API_URL_OVERRIDES="example.com=https://matrix-client.example.com" \ LIVEKIT_REDIS_URL="redis://localhost:6379" \ LIVEKIT_REDIS_PASSWORD="secret" \ LIVEKIT_AS_TOKEN="as_token" LIVEKIT_HS_TOKEN="hs_token" LIVEKIT_HS_SERVER_NAME="example.com" \ LIVEKIT_LOG_LEVEL=info ``` -------------------------------- ### Using new_redis_store in main.rs Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/store.md Example usage from src/main.rs. Falls back to an in-memory store when LIVEKIT_REDIS_URL is empty, otherwise attempts to create a Redis store and exits the process on connection failure. ```rust let store: Option> = if config.redis_url.is_empty() { warn!("LIVEKIT_REDIS_URL not set. Using in-memory store."); None } else { match new_redis_store(&config.redis_url).await { Ok(store) => Some(store), Err(err) => { eprintln!("Could not connect Redis store: {err}"); std::process::exit(1); } } }; ``` -------------------------------- ### Example call to add_delayed_event_job Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/handler.md Shows the exact call used by the process_* methods, constructing DelayedEventJobParams and mapping errors with matrix_error_for_add_job. ```rust self.add_delayed_event_job(DelayedEventJobParams { server_name: req.openid_token.matrix_server_name.clone(), delay_id: req.delay_id.clone(), delay_timeout: Duration::from_millis(req.delay_timeout.max(0) as u64), livekit_room: lk_room_alias.clone(), livekit_identity: lk_identity.clone(), owner_user_id: String::new(), }) .await .map_err(matrix_error_for_add_job)?; ``` -------------------------------- ### healthcheck probing example using bind_addresses Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/config.md Example usage of bind_addresses by the healthcheck binary to probe every address. It iterates over the returned addresses, sending a GET request to each healthz URL. If any returns a status other than 200, it returns an error; otherwise it returns Ok. ```rust let lk_jwt_bind = parse_bind()?; let addrs = bind_addresses(&lk_jwt_bind); for addr in &addrs { if let Ok(resp) = reqwest::get(get_healthz_url(addr)).await { if resp.status().as_u16() != 200 { return Err("Healthcheck failed..."); } return Ok(()); } } ``` -------------------------------- ### Run lk-jwt-service with minimum configuration Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/configuration.md Minimal configuration to run the lk-jwt-service standalone. Sets the LiveKit server URL, key, secret, and the homeserver that gets full access. ```shell LIVEKIT_URL="ws://somewhere" LIVEKIT_KEY=devkey LIVEKIT_SECRET=secret \ LIVEKIT_FULL_ACCESS_HOMESERVERS=example.com ./lk-jwt-service ``` -------------------------------- ### Start the MatrixRTC Authorization Service locally Source: https://github.com/element-hq/lk-jwt-service/blob/main/README.md Clones the lk-jwt-service repository and runs it locally with environment variables for LiveKit and homeserver configuration. Requires the MatrixRTC stack to be running and TLS verification disabled via LIVEKIT_INSECURE_SKIP_VERIFY_TLS. ```sh git clone https://github.com/element-hq/lk-jwt-service cd lk-jwt-service LIVEKIT_INSECURE_SKIP_VERIFY_TLS="YES_I_KNOW_WHAT_I_AM_DOING" \ LIVEKIT_URL="wss://matrix-rtc.m.localhost/livekit/sfu" \ LIVEKIT_KEY=devkey \ LIVEKIT_SECRET=secret \ LIVEKIT_JWT_PORT=6080 \ LIVEKIT_FULL_ACCESS_HOMESERVERS=synapse.m.localhost \ cargo run ``` -------------------------------- ### Recovering jobs after restart with all_jobs Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/store.md Advanced example for recovering jobs after a restart. Loads all stored jobs, drops expired ones, and rebuilds the rest. Simplified from the source's Handler::run_loop startup path. ```rust // Handler::run_loop, startup path (simplified from source) match store.all_jobs().await { Ok(stored_jobs) => { for stored_job in stored_jobs { let key = JobKey { room: stored_job.params.livekit_room.clone(), identity: stored_job.params.livekit_identity.clone(), }; let expires_at = stored_job.restarted_at + chrono::Duration::from_std(stored_job.params.delay_timeout).unwrap_or_default(); if expires_at <= Utc::now() { store.delete_job(&key).await // expired — drop continue; } // rebuild the job, then start_participant_lookup + spawn_loop } } Err(err) => error!("Handler: failed to load stored jobs"), } ``` -------------------------------- ### Start MatrixRTC stack without the MatrixRTC Authorization Service Source: https://github.com/element-hq/lk-jwt-service/blob/main/README.md Clones the Element Call repository and starts the MatrixRTC stack (nginx, livekit, synapse, redis) using Docker Compose. This is the first step to set up a local development environment without the MatrixRTC Authorization Service. ```sh git clone https://github.com/element-hq/element-call.git cd element-call docker-compose -f ./dev-backend-docker-compose.yml -f ./playwright-backend-docker-compose.override.yml up nginx livekit synapse redis ``` -------------------------------- ### Basic call to get_join_token Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/handler.md Basic call to `get_join_token` with example values. The helper functions `livekit_room_alias_for` and `livekit_identity_for` are used to construct the room alias and identity arguments. ```rust let token = get_join_token( "devkey", "secret", &livekit_room_alias_for("!roomid:example.com", "m.call#ROOM"), &livekit_identity_for("@alice:example.com", "DEVICE123", "memberABC"), true, ); ``` -------------------------------- ### read_key_secret error handling example Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/config.md Error-handling example for `read_key_secret()`: on error, prints the error to stderr and exits with a non-zero code, mirroring the behavior in `src/main.rs`. ```rust // Non-zero exit on failure, mirroring src/main.rs let (key, secret) = match read_key_secret() { Ok(kv) => kv, Err(err) => { eprintln!("{err}"); std::process::exit(1); } }; ``` -------------------------------- ### new_unique_id() usage example Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/helper.md Example of generating a unique ID and converting it to a string for use in appservice pings. ```rust let job_id = new_unique_id(); let transaction_id = new_unique_id().to_string(); // used for appservice pings ``` -------------------------------- ### Develop inside container (docker, podman) Source: https://github.com/element-hq/lk-jwt-service/blob/main/README.md Develops inside a container using Docker or Podman. Runs a Rust Alpine container, installs build dependencies, and builds the service in release mode. ```sh docker run --rm -it -w /proj -v .:/proj docker.io/rust:1-alpine sh apk add --no-cache musl-dev cmake make gcc g++ perl linux-headers cargo build --release # The service binary is target/release/lk-jwt-service, # the healthcheck binary is target/release/healthcheck ``` -------------------------------- ### Basic retry of a transient network call Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/retry.md Example demonstrating how to use the retry function with the default service backoff schedule for a network operation. ```rust let result = retry( &cancel_token, ExponentialBackoff::service_default(), Duration::from_secs(30), || async { deps.execute_delayed_event_action(&cs_api_url, &delay_id, DelayEventAction::Send, None) .await }, ) .await; ``` -------------------------------- ### AppServiceConfig::is_set_up Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/config.md Returns `true` when all three fields are non-empty. Gates the application-service routes in `Handler::prepare_router` (see handler.md). ```rust pub fn is_set_up(&self) -> bool ``` -------------------------------- ### Download and make lk-jwt-service binary executable Source: https://github.com/element-hq/lk-jwt-service/blob/main/README.md Downloads the amd64 release binary and makes it executable. Replace `amd64` with `arm64` if needed. ```shell wget https://github.com/element-hq/lk-jwt-service/releases/latest/download/lk-jwt-service_linux_amd64 chmod +x lk-jwt-service_linux_amd64 ``` -------------------------------- ### Handler::prepare_router method Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/handler.md Builds the axum Router and attaches the handler as state. ```rust pub fn prepare_router(self: &Arc) -> Router ``` -------------------------------- ### DelayedEventJob::spawn_loop Method Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/delayed_event_manager.md Starts the actor loop for the job. Must be called exactly once. ```rust pub fn spawn_loop(self: &Arc) ``` -------------------------------- ### DelayedEventJob::new Constructor Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/delayed_event_manager.md Initializes a new DelayedEventJob instance. The job is not started until spawn_loop is called. ```rust pub fn new( parent_cancel: &CancellationToken, params: DelayedEventJobParams, deps: Arc, lookup_cs_api_url: LookupCsApiUrlFn, done_tx: mpsc::Sender>, restarted_tx: mpsc::Sender, as_token: String, ) -> Result, String> ``` -------------------------------- ### Implementing Classify for a custom error Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/retry.md Example showing how to implement the Classify trait for a custom error type. ```rust #[derive(Debug)] struct MyError(ErrorClass); impl Classify for MyError { fn classify(&self) -> ErrorClass { self.0 } } ``` -------------------------------- ### new_redis_store(redis_url) Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/store.md Documentation for the `new_redis_store` function, which creates a Redis-backed store instance. ```APIDOC ## `new_redis_store(redis_url)` ### Description Opens a `redis::Client` for `redis_url`, obtains its auto-reconnecting `ConnectionManager`, and verifies connectivity with `PING`. ### Signature ```rust pub async fn new_redis_store(redis_url: &str) -> Result, String> ``` ### Parameters - `redis_url` (`&str`) - Required - Redis connection URL (e.g. `redis://localhost:6379`), optionally with embedded password ### Returns `Arc` — a `RedisStore` behind the trait. ### Errors - `Err(format!("store: invalid Redis URL {redis_url:?}: {e}"))` on URL parse failure - `Err(...connection failed...)` if the connection manager cannot be acquired - `Err(...Redis ping failed...)` if PING fails ### Example (as used by `src/main.rs`) ```rust let store: Option> = if config.redis_url.is_empty() { warn!("LIVEKIT_REDIS_URL not set. Using in-memory store."); None } else { match new_redis_store(&config.redis_url).await { Ok(store) => Some(store), Err(err) => { eprintln!("Could not connect Redis store: {err}"); std::process::exit(1); } } }; ``` ### Advanced example — recovering jobs after a restart ```rust // Handler::run_loop, startup path (simplified from source) match store.all_jobs().await { Ok(stored_jobs) => { for stored_job in stored_jobs { let key = JobKey { room: stored_job.params.livekit_room.clone(), identity: stored_job.params.livekit_identity.clone(), }; let expires_at = stored_job.restarted_at + chrono::Duration::from_std(stored_job.params.delay_timeout).unwrap_or_default(); if expires_at <= Utc::now() { store.delete_job(&key).await // expired — drop continue; } // rebuild the job, then start_participant_lookup + spawn_loop } } Err(err) => error!("Handler: failed to load stored jobs"), } ``` ``` -------------------------------- ### POST /sfu/get Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/endpoints.md Documentation for the legacy SFU token endpoint POST /sfu/get. ```APIDOC ## POST /sfu/get ### Description Legacy SFU token endpoint (deprecated). Exchanges an OpenID token for a LiveKit JWT and URL. ### Method POST ### Endpoint /sfu/get ### Parameters #### Request Body - **room** (string) - Required - The room name. - **openid_token** (object) - Required - OpenID token object containing access_token, token_type, matrix_server_name, expires_in. - **device_id** (string) - Required - The device ID. - **delay_id** (string) - Optional - Delay ID for delayed events. - **delay_timeout** (number) - Optional - Delay timeout. - **delay_cs_api_url** (string) - Optional - Client-Server API URL for delegation. ### Request Example { "room": "example-room", "openid_token": { "access_token": "...", "token_type": "Bearer", "matrix_server_name": "example.org", "expires_in": 3600 }, "device_id": "device123" } ### Response #### Success Response (200) - **url** (string) - The configured LiveKit URL. - **jwt** (string) - The JWT token. #### Response Example { "url": "wss://livekit.example.com", "jwt": "eyJhbGciOi..." } ### Error Responses - **400 M_BAD_JSON** - Validation failure, delegation requested by non-full-access user, or C-S API unresolvable. - **401 M_UNAUTHORIZED** - OpenID token invalid or exchange failed. - **500 M_UNKNOWN** - Room creation or token minting failed. ``` -------------------------------- ### Run lk-jwt-service binary locally Source: https://github.com/element-hq/lk-jwt-service/blob/main/README.md Runs the downloaded lk-jwt-service binary locally with the required environment variables set. ```shell LIVEKIT_URL="ws://somewhere" LIVEKIT_KEY=devkey LIVEKIT_SECRET=secret LIVEKIT_FULL_ACCESS_HOMESERVERS=example.com ./lk-jwt-service_linux_amd64 ``` -------------------------------- ### Handler::new_without_loop constructor Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/handler.md Constructs a Handler without starting its actor loop, returning the loop's receiving ends. ```rust pub(crate) fn new_without_loop( lk_auth: LiveKitAuth, full_access_homeservers: Vec, app_service_config: AppServiceConfig, sanity_check_interval: Duration, cs_api_url_overrides: HashMap, store: Option>, deps: Arc, ) -> (Arc, LoopReceivers) ``` -------------------------------- ### Distinguishing error kinds in retry Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/retry.md Example showing how to handle the result of a retry operation, including distinguishing between success, permanent errors, and cancellation. ```rust match retry(&cancel, schedule, Duration::from_secs(10), || async { op() }).await { Ok(v) => info!("succeeded: {v}"), Err(RetryError::Error(err)) => { // err.classify() tells you why it stopped: // Permanent — never retried; Transient — budget exhausted. warn!(err = %err, "retry loop ended"); } Err(RetryError::Cancelled) => { debug!("retry cancelled during shutdown"); } } ``` -------------------------------- ### create_livekit_room Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/helper.md Creates a LiveKit room with the given alias, empty timeout, departure timeout, and max participants. Creating an existing room is not an error. ```APIDOC ## create_room (LiveKit) ### Description Creates a LiveKit room with the given alias, empty timeout, departure timeout, and max participants. Creating an existing room is not an error. ### Method POST (LiveKit API) ### Endpoint `create_room` (LiveKit API) ### Parameters #### Request Body - **name** (string) - Required - The room alias. - **empty_timeout** (integer) - Required - 300 seconds. - **departure_timeout** (integer) - Required - 20 seconds. - **max_participants** (integer) - Required - 0 (unlimited). ### Response #### Success Response - Logs "created" or "reused" based on `creation_time`. ``` -------------------------------- ### GET/HEAD /healthz Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/endpoints.md Container health check endpoint. Returns 200 OK for GET and HEAD requests, 405 otherwise. Used by the healthcheck binary to verify service availability. ```APIDOC ## GET/HEAD /healthz ### Description Container health check. Returns 200 OK for GET/HEAD, 405 otherwise. ### Method GET, HEAD ### Endpoint /healthz ### Response #### Success Response (200) Returns 200 OK for GET and HEAD requests. #### Error Response (405) Returns 405 for other HTTP methods. ### Healthcheck Binary Behavior The healthcheck binary (src/bin/healthcheck.rs) GETs http://{addr}/healthz for each address returned by bind_addresses (IPv6 wildcard first, IPv4 fallback) and exits: - Ok(()) when a response arrives with status 200 - Err("Healthcheck failed with status code {status}") for other statuses - Err("No configured address ({addrs}) could be accessed") when all addresses failed ``` -------------------------------- ### Run full e2e suite Source: https://github.com/element-hq/lk-jwt-service/blob/main/e2e-tests/README.md Runs the full end-to-end test suite. It brings up the Docker stack with `docker compose up -d --build`, waits for all components to be healthy, runs the tests, and tears down with `docker compose down -v`. ```sh cargo xtask e2e ``` -------------------------------- ### Initialization flow with parse_config() Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/config.md Initialization flow as used by src/main.rs. Calls parse_config() and exits with the error message if parsing fails, otherwise continues with the resulting config. ```rust let config = match parse_config() { Ok(config) => config, Err(err) => { eprintln!("{err}"); std::process::exit(1); } }; ``` -------------------------------- ### Define Config and AppServiceConfig in src/config.rs Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/types.md Configuration structs from `src/config.rs`. `Config` is produced by `parse_config()` and consumed by `Handler::new` and `src/main.rs`. `AppServiceConfig` is empty by default; `is_set_up()` gates AS routes. ```rust #[derive(Debug, Clone, PartialEq)] pub struct Config { pub key: String, pub secret: String, pub lk_url: String, pub skip_verify_tls: bool, pub full_access_homeservers: Vec, pub lk_jwt_bind: String, pub sanity_check_interval: Duration, pub cs_api_url_overrides: HashMap, pub redis_url: String, pub app_service_config: AppServiceConfig, } ``` ```rust #[derive(Debug, Clone, PartialEq, Default)] pub struct AppServiceConfig { pub as_token: String, pub hs_token: String, pub hs_server_name: String, } ``` -------------------------------- ### LiveKit SFU webhook config for /sfu_webhook Source: https://github.com/element-hq/lk-jwt-service/blob/main/README.md LiveKit SFU webhook configuration pointing at the service's `/sfu_webhook` endpoint. The `api_key` must match the `LIVEKIT_KEY` used by this service and be one of the SFU's known keys; the URL is the public reverse-proxied path (e.g. `https://matrix-rtc.m.localhost/livekit/jwt/sfu_webhook` for local dev). Without this wiring, the service cannot observe participant disconnects and cannot send delegated leave events. ```yaml webhook: api_key: devkey # must match LIVEKIT_KEY used by this service — # the SFU signs webhooks with it, this service verifies urls: - https://matrix-rtc.domain.tld/livekit/jwt/sfu_webhook ``` -------------------------------- ### Integration test architecture diagram Source: https://github.com/element-hq/lk-jwt-service/blob/main/integration-tests/README.md Architecture diagram showing the integration test setup: a #[tokio::test] function communicates over HTTP with the lk-jwt-service child process, which uses C-S API, Twirp, and RESP2 protocols to interact with FakeHomeserver, FakeSfu, and FakeRedis. The fakes are used to configure the environment and verify test results. ```text ┌───────────────────────────────────────────────┐ │ #[tokio::test] │───────────┐ └──────────────────────┬────────────────────────┘ │ │ HTTP │ ▼ │ ┌────────────────────────────┐ │ │ lk-jwt-service │ │ │ (child process) │ │ └───┬──────────┬─────────┬───┘ │ │ │ │ │ C-S API Twirp RESP2 │ │ │ │ │ ▼ ▼ ▼ │ ┌────────────────┐ ┌─────────┐ ┌───────────┐ │ │ FakeHomeserver │ │ FakeSfu │ │ FakeRedis │ │ └────────────────┘ └─────────┘ └───────────┘ │ ▲ ▲ ▲ │ └──────────┴─────────┴──── configure / verify ─┘ ``` -------------------------------- ### DTO struct definitions (UserInfo, ClientWellKnown, CreateRoomParams, RoomInfo) Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/helper.md DTOs used by the service: UserInfo (response of the Matrix federation OpenID userinfo endpoint), ClientWellKnown (relevant subset of a .well-known/matrix/client response), CreateRoomParams (room creation parameters passed to RoomServiceClient::create_room), and RoomInfo (subset of LiveKit room data). CreateRoomParams values used by Deps::create_livekit_room: empty_timeout: 5 * 60 (300 s), departure_timeout: 20 (20 s), max_participants: 0 (0 = no limitation). ```rust /// The response of the Matrix federation OpenID userinfo endpoint. #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct UserInfo { pub sub: String; } /// The relevant subset of a .well-known/matrix/client response. #[derive(Debug, Clone, Default)] pub struct ClientWellKnown { pub homeserver_base_url: String; } /// Room creation parameters passed to [`RoomServiceClient::create_room`]. #[derive(Debug, Clone, Default)] pub struct CreateRoomParams { pub name: String, pub empty_timeout: u32, pub departure_timeout: u32, pub max_participants: u32, } /// The subset of LiveKit room data this service inspects. #[derive(Debug, Clone, Default)] pub struct RoomInfo { pub sid: String, pub creation_time: i64 } ``` -------------------------------- ### Hash derivations for LiveKit room alias and identity (MSC4195) Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/helper.md Functions that JSON-encode their string slice arguments (serde_json::to_vec) and return sha256_unpadded_base64 (SHA-256 digest, unpadded standard Base64) of that encoding. Verified against MSC4195 test vectors: livekit_room_alias_for("!roomid:example.com", "slot1234") → "O8437W3+jmzMVjoIP3tNwbm+XxHQk2iKpOA7aqw3qSc" and livekit_identity_for("@alice:example.com", "DEVICE123", "memberABC") → "J+T45tGruxc+HrUOqJJlyQSV33m728Cme4+vt8/SWrU". The example shows how to call both functions with request fields. ```rust pub fn livekit_room_alias_for(matrix_room: &str, matrix_rtc_slot: &str) -> LiveKitRoomAlias pub fn livekit_identity_for(matrix_id: &str, device_id: &str, member_id: &str) -> LiveKitIdentity ``` ```rust let lk_identity = livekit_identity_for(&matrix_id, &req.member.claimed_device_id, &req.member.id); let lk_room_alias = livekit_room_alias_for(&req.room_id, &req.slot_id); ``` -------------------------------- ### Build from source with cargo build --release Source: https://github.com/element-hq/lk-jwt-service/blob/main/README.md Builds the service in release mode, producing the lk-jwt-service and healthcheck binaries. Requires a Rust toolchain. ```sh cargo build --release # Produces target/release/lk-jwt-service and target/release/healthcheck ``` -------------------------------- ### POST /get_token Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/endpoints.md Issues a JWT for a LiveKit room based on the provided SfuRequest. It verifies the OpenID token, checks full-access permissions, and optionally creates a LiveKit room and schedules a delayed-event job if delegation is requested. ```APIDOC ## POST /get_token ### Description Issues a JWT for a LiveKit room based on the provided SfuRequest. It verifies the OpenID token, checks full-access permissions, and optionally creates a LiveKit room and schedules a delayed-event job if delegation is requested. ### Method POST ### Endpoint /get_token ### Parameters #### Request Body - **room_id** (string) - Required - The room identifier. - **slot_id** (string) - Required - The slot identifier. - **openid_token** (object) - Required - The OpenID token object. - **member** (object) - Required - Contains `id`, `claimed_user_id`, and `claimed_device_id`. - **delay_id** (string) - Optional - The delay event identifier. - **delay_timeout** (number) - Optional - The delay timeout in seconds. - **delay_cs_api_url** (string) - Optional - The client-server API URL for the delayed event. ### Request Example { "room_id": "string", "slot_id": "string", "openid_token": {}, "member": { "id": "string", "claimed_user_id": "string", "claimed_device_id": "string" }, "delay_id": "string", "delay_timeout": 0, "delay_cs_api_url": "string" } ### Response #### Success Response (200) - **url** (string) - The LiveKit server URL. - **jwt** (string) - The JWT token. #### Response Example { "url": "string", "jwt": "string" } ### Error Responses | Condition | Status / errcode | |-----------|------------------| | `validate()` failure (including partial delay params) | 400 `M_BAD_JSON` | | OpenID token invalid / `member.claimed_user_id` ≠ token `sub` | 401 `M_UNAUTHORIZED` | | Delegation by non-full-access user | 400 `M_BAD_JSON` | | C-S API unresolvable / room creation / minting | 400/500 `M_BAD_JSON`/`M_UNKNOWN` | | `AddJobError::Shutdown` | 503 `M_UNKNOWN` ("Service is shutting down") | | `AddJobError::Creation` | 400 `M_BAD_JSON` | ``` -------------------------------- ### read_app_service_config() Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/config.md Reads application-service configuration from environment variables. Parses LIVEKIT_AS_REGISTRATION_FILE as YAML if set, extracting as_token and hs_token, with hs_server_name from LIVEKIT_HS_SERVER_NAME. Returns an empty AppServiceConfig if nothing is set. ```APIDOC ## read_app_service_config() ### Description Reads application-service configuration. When LIVEKIT_AS_REGISTRATION_FILE is set it is parsed as YAML and the as_token and hs_token root fields are extracted (taking precedence over LIVEKIT_AS_TOKEN / LIVEKIT_HS_TOKEN). hs_server_name always comes from LIVEKIT_HS_SERVER_NAME. When nothing is set, an empty AppServiceConfig is returned. ### Method Function call ### Signature pub fn read_app_service_config() -> Result ### Parameters None ### Returns - **Ok(AppServiceConfig)** - The parsed configuration. - **Err(String)** - Error message if configuration is invalid. ### Errors - Unreadable registration file (OS error string) - Invalid YAML: "Could not parse app service registration file: ..." - Root element is not a mapping - Missing as_token or hs_token property - as_token or hs_token present but not a string - Partially populated configuration: "The app service token(s) (LIVEKIT_AS_TOKEN/LIVEKIT_HS_TOKEN or LIVEKIT_AS_REGISTRATION_FILE) and LIVEKIT_HS_SERVER_NAME must either all be set or all be left empty" ### Example ```rust let app_service_config = match read_app_service_config() { Ok(cfg) => cfg, Err(err) => { eprintln!("{err}"); std::process::exit(1); } }; if app_service_config.is_set_up() { info!("running as application service for {}", app_service_config.hs_server_name); } ``` ``` -------------------------------- ### Run integration tests with cargo test Source: https://github.com/element-hq/lk-jwt-service/blob/main/integration-tests/README.md Commands to run the integration test suite. The harness builds the service from the repository root on first use. ```sh cd integration-tests cargo test ``` -------------------------------- ### Struct: LiveKitAuth Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/helper.md Authentication bundle for talking to LiveKit, containing API key, secret, and SFU WebSocket URL. ```rust #[derive(Debug, Clone, Default)] pub struct LiveKitAuth { pub key: String, pub secret: String, pub lk_url: String, } ``` -------------------------------- ### Run lk-jwt-service with Docker Source: https://github.com/element-hq/lk-jwt-service/blob/main/README.md Runs the lk-jwt-service from the official Docker image. Requires the LIVEKIT_URL, LIVEKIT_KEY, LIVEKIT_SECRET, and LIVEKIT_FULL_ACCESS_HOMESERVERS environment variables. The `latest` tag points to the most recent tagged release; `latest-ci` is a rolling build of `main` that may be unstable. ```shell docker run -e LIVEKIT_URL="ws://somewhere" -e LIVEKIT_KEY=devkey -e LIVEKIT_SECRET=secret -e LIVEKIT_FULL_ACCESS_HOMESERVERS=example.com -p 8080:8080 ghcr.io/element-hq/lk-jwt-service:latest ``` -------------------------------- ### read_cs_api_url_overrides(raw) Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/config.md Parses a comma-separated list of server_name=url entries from the LIVEKIT_CS_API_URL_OVERRIDES environment variable. Returns a HashMap of server names to CsApiUrl values. ```APIDOC ## read_cs_api_url_overrides(raw) ### Description Parses a comma-separated list of server_name=url entries. Entries are trimmed; empty keys or values are rejected. Server names may be DNS names, host:port, IPv4, or bracketed IPv6 literals. ### Method Function call ### Signature pub fn read_cs_api_url_overrides(raw: &str) -> Result, String> ### Parameters - **raw** (&str) - Required - Raw value of LIVEKIT_CS_API_URL_OVERRIDES (may be empty) ### Returns - **Ok(HashMap)** - Mapping of server names to CS API URLs. - **Err(String)** - Error message if an entry is invalid. ### Errors - Err(format!("invalid entry {entry:?}, expected server_name=url")) for entries without '=', empty keys, or empty values. ### Example No example provided in source. ``` -------------------------------- ### parse_config() function signature Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/config.md Signature for the primary configuration entry point. Validates environment variables including required LIVEKIT_KEY, LIVEKIT_SECRET, LIVEKIT_URL, and LIVEKIT_FULL_ACCESS_HOMESERVERS; checks the bind conflict; parses sanity check interval and CS-API overrides; and handles Redis URL and password. ```rust pub fn parse_config() -> Result ``` -------------------------------- ### start_participant_lookup Function Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/delayed_event_manager.md Spawns a task to perform presence checks for a participant, utilizing exponential backoff and periodic sanity intervals. ```rust pub fn start_participant_lookup( job: &Arc, deps: Arc, lk_auth: LiveKitAuth, sanity_interval: Duration, ) ``` -------------------------------- ### LiveKitRoomServiceClient struct Source: https://github.com/element-hq/lk-jwt-service/blob/main/_autodocs/api-reference/helper.md The internal implementation of RoomServiceClient, containing host, API credentials, and TLS verification settings. ```rust struct LiveKitRoomServiceClient { host: String, api_key: String, api_secret: String, skip_verify_tls: bool, } ``` -------------------------------- ### Application service registration YAML for LiveKit JWT service Source: https://github.com/element-hq/lk-jwt-service/blob/main/README.md Application service registration file for the LiveKit JWT service. Requires the `urn:matrix:client:rooms:is_joined` scope to cover all local users, sets `url` to `null` to disable event traffic, and enables request proxying for the `/rtc/livekit` subpath via `proxy_prefix` and `proxy_url`. ```yaml id: "LiveKit JWT service" as_token: "" hs_token: "" sender_localpart: "_lk_jwt_service" namespaces: users: - exclusive: false regex: ".*" # Cover all users url: null # No event traffic required # Stable scope for membership look-up scopes: [ "urn:matrix:client:rooms:is_joined" ] # Unstable scope for membership look-up io.element.msc4502.scope: [ "urn:matrix:client:io.element.msc4502:rooms:is_joined" ], proxy_prefix: "rtc/livekit" # Proxy /rtc/livekit requests on the C-S and S-S API proxy_url: "http://127.0.0.1:1234" # Forward proxied requests to this URL ```