Skip to main content

livekit_api/services/
mod.rs

1// Copyright 2025 LiveKit, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::HashMap;
16use std::fmt::{Debug, Display};
17
18use http::header::{HeaderMap, HeaderValue, AUTHORIZATION};
19use thiserror::Error;
20
21use livekit_token::{AccessToken, AccessTokenError, SIPGrants, VideoGrants};
22
23pub use crate::public_api::public_api_url;
24pub use livekit_api::LiveKitApi;
25pub use twirp_client::{
26    ServerError,
27    ServerErrorCode,
28    ServerResult,
29    // Deprecated aliases, kept for backwards compatibility.
30    TwirpError,
31    TwirpErrorCode,
32    TwirpResult,
33};
34
35pub mod agent_dispatch;
36pub mod connector;
37pub mod databases;
38pub mod egress;
39pub mod ingress;
40pub mod room;
41pub mod sip;
42pub mod threads;
43
44mod dial_timeout;
45mod failover;
46mod livekit_api;
47mod twirp_client;
48
49#[cfg(all(test, feature = "services"))]
50mod api_test;
51
52pub const LIVEKIT_PACKAGE: &str = "livekit";
53
54#[derive(Debug, Error)]
55pub enum ServiceError {
56    #[error("invalid environment: {0}")]
57    Env(#[from] std::env::VarError),
58    #[error("invalid access token: {0}")]
59    AccessToken(#[from] AccessTokenError),
60    #[error("server error: {0}")]
61    Twirp(#[from] ServerError),
62}
63
64pub type ServiceResult<T> = Result<T, ServiceError>;
65
66// The two authentication modes are mutually exclusive, so they're distinct
67// variants rather than a struct where an invalid combination is representable.
68#[derive(Clone)]
69pub(crate) enum ServiceBase {
70    /// Sign a short-lived token per request from an API key and secret.
71    ApiKeySecret { api_key: String, api_secret: String },
72    /// Send a caller-supplied token verbatim; grants are ignored (the caller,
73    /// typically a browser client, signed it out of band).
74    Token(String),
75}
76
77impl Debug for ServiceBase {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        // Never print the API secret or token.
80        match self {
81            Self::ApiKeySecret { api_key, .. } => {
82                f.debug_struct("ServiceBase").field("api_key", api_key).finish_non_exhaustive()
83            }
84            Self::Token(_) => f.debug_struct("ServiceBase").field("token", &"<redacted>").finish(),
85        }
86    }
87}
88
89impl ServiceBase {
90    pub fn with_api_key(api_key: &str, api_secret: &str) -> Self {
91        Self::ApiKeySecret { api_key: api_key.to_owned(), api_secret: api_secret.to_owned() }
92    }
93
94    pub fn with_token(token: &str) -> Self {
95        Self::Token(token.to_owned())
96    }
97
98    pub fn auth_header(
99        &self,
100        grants: VideoGrants,
101        sip: Option<SIPGrants>,
102    ) -> Result<HeaderMap, AccessTokenError> {
103        let token = match self {
104            Self::Token(token) => token.clone(),
105            Self::ApiKeySecret { api_key, api_secret } => {
106                let mut tok = AccessToken::with_api_key(api_key, api_secret).with_grants(grants);
107                if let Some(sip_grants) = sip {
108                    tok = tok.with_sip_grants(sip_grants);
109                }
110                tok.to_jwt()?
111            }
112        };
113
114        let mut headers = HeaderMap::new();
115        headers.insert(AUTHORIZATION, HeaderValue::from_str(&format!("Bearer {}", token)).unwrap());
116        Ok(headers)
117    }
118}
119
120/// A failed SIP call (e.g. the callee was busy or declined), decoded from the
121/// SIP status the server attaches to the error metadata. Extract one from a
122/// [`ServiceError`] with [`SipCallError::from_error`].
123#[derive(Debug, Clone)]
124pub struct SipCallError {
125    code: String,
126    sip_status_code: Option<i32>,
127    sip_status: Option<String>,
128    metadata: HashMap<String, String>,
129}
130
131impl SipCallError {
132    /// Returns a `SipCallError` if `err` is a server error carrying a SIP status,
133    /// otherwise `None`.
134    pub fn from_error(err: &ServiceError) -> Option<Self> {
135        let ServiceError::Twirp(ServerError::Twirp(code)) = err else {
136            return None;
137        };
138        if !code.meta.contains_key("sip_status_code") && !code.meta.contains_key("sip_status") {
139            return None;
140        }
141        Some(Self {
142            code: code.code.clone(),
143            sip_status_code: code.meta.get("sip_status_code").and_then(|v| v.parse().ok()),
144            sip_status: code.meta.get("sip_status").cloned(),
145            metadata: code.meta.clone(),
146        })
147    }
148
149    /// The server error code (e.g. `resource_exhausted` for a busy callee).
150    pub fn code(&self) -> &str {
151        &self.code
152    }
153
154    /// The SIP status code (e.g. 486 for Busy Here), if present.
155    pub fn sip_status_code(&self) -> Option<i32> {
156        self.sip_status_code
157    }
158
159    /// The SIP status reason (e.g. "Busy Here"), if present.
160    pub fn sip_status(&self) -> Option<&str> {
161        self.sip_status.as_deref()
162    }
163
164    /// Any additional metadata the server attached to the error.
165    pub fn metadata(&self) -> &HashMap<String, String> {
166        &self.metadata
167    }
168}
169
170impl Display for SipCallError {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        write!(f, "SIP call failed: {}", self.sip_status_code.unwrap_or_default())?;
173        if let Some(reason) = &self.sip_status {
174            write!(f, " {}", reason)?;
175        }
176        write!(f, " ({})", self.code)?;
177        let mut extra: Vec<_> = self
178            .metadata
179            .iter()
180            .filter(|(k, _)| {
181                !matches!(k.as_str(), "sip_status_code" | "sip_status" | "error_details")
182            })
183            .map(|(k, v)| format!("{}={}", k, v))
184            .collect();
185        if !extra.is_empty() {
186            extra.sort();
187            write!(f, " [{}]", extra.join(", "))?;
188        }
189        Ok(())
190    }
191}
192
193impl std::error::Error for SipCallError {}