diff --git a/Cargo.lock b/Cargo.lock index bb87c26f3..c1d20b902 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -497,7 +497,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -799,7 +799,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -1958,7 +1958,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ "rustix 1.1.2", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -2124,6 +2124,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.3.1", + "indexmap 2.11.4", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.7.1" @@ -2308,7 +2327,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.27", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -2332,6 +2351,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2 0.4.12", "http 1.3.1", "http-body 1.0.1", "httparse", @@ -2408,9 +2428,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.1", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -2879,7 +2901,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -2985,6 +3007,7 @@ dependencies = [ "log", "parking_lot", "prost 0.12.6", + "reqwest", "semver", "serde", "serde_json", @@ -3867,7 +3890,7 @@ dependencies = [ "petgraph 0.6.5", "redox_syscall 0.5.18", "smallvec", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -4555,9 +4578,11 @@ checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", "futures-channel", "futures-core", "futures-util", + "h2 0.4.12", "http 1.3.1", "http-body 1.0.1", "http-body-util", @@ -4567,6 +4592,7 @@ dependencies = [ "hyper-util", "js-sys", "log", + "mime", "native-tls", "percent-encoding", "pin-project-lite", @@ -5228,6 +5254,27 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.4", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.23.0" @@ -5415,6 +5462,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "token_source" +version = "0.1.0" +dependencies = [ + "env_logger 0.10.2", + "livekit", + "livekit-api", + "log", + "tokio", +] + [[package]] name = "tokio" version = "1.48.0" @@ -5568,7 +5626,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "h2", + "h2 0.3.27", "http 0.2.12", "http-body 0.4.6", "hyper 0.14.32", @@ -6548,7 +6606,7 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement 0.60.2", "windows-interface 0.59.3", - "windows-link", + "windows-link 0.2.1", "windows-result 0.4.1", "windows-strings 0.5.1", ] @@ -6597,12 +6655,29 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + [[package]] name = "windows-result" version = "0.1.2" @@ -6621,13 +6696,22 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -6640,13 +6724,22 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-strings" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -6700,7 +6793,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -6755,7 +6848,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link", + "windows-link 0.2.1", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", diff --git a/Cargo.toml b/Cargo.toml index c1808ff0a..f270243b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "examples/send_bytes", "examples/webhooks", "examples/wgpu_room", + "examples/token_source", ] [workspace.dependencies] diff --git a/examples/token_source/Cargo.toml b/examples/token_source/Cargo.toml new file mode 100644 index 000000000..46f80d768 --- /dev/null +++ b/examples/token_source/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "token_source" +version = "0.1.0" +edition = "2021" + +[dependencies] +tokio = { version = "1", features = ["full"] } +env_logger = "0.10" +livekit = { path = "../../livekit", features = ["rustls-tls-native-roots"]} +livekit-api = { path = "../../livekit-api", features = ["rustls-tls-native-roots"]} +log = "0.4" diff --git a/examples/token_source/src/main.rs b/examples/token_source/src/main.rs new file mode 100644 index 000000000..7a95fe9fc --- /dev/null +++ b/examples/token_source/src/main.rs @@ -0,0 +1,95 @@ +use std::future; + +use livekit::prelude::*; +use livekit::token_source::{ + MinterCredentials, MinterCredentialsEnvironment, TokenSourceCustom, TokenSourceCustomMinter, + TokenSourceEndpoint, TokenSourceFetchOptions, TokenSourceLiteral, TokenSourceMinter, + TokenSourceResponse, TokenSourceSandboxTokenServer, +}; +use livekit_api::access_token; + +#[tokio::main] +async fn main() { + env_logger::init(); + + let fetch_options = TokenSourceFetchOptions::default().with_agent_name("voice ai quickstart"); + + let literal = TokenSourceLiteral::new(TokenSourceResponse { + server_url: "...".into(), + participant_token: "...".into(), + }); + let literal_response = literal.fetch().await; + log::info!("Example TokenSourceLiteral response: {:?}", literal_response); + + let minter = TokenSourceMinter::default(); + let minter_response = minter.fetch(&fetch_options).await; + log::info!("Example TokenSourceMinter::default() result: {:?}", minter_response); + + let minter_literal_credentials = + TokenSourceMinter::new(MinterCredentials::new("server url", "api key", "api secret")); + let minter_literal_credentials_response = + minter_literal_credentials.fetch(&fetch_options).await; + log::info!( + "Example TokenSourceMinter / literal MinterCredentials result: {:?}", + minter_literal_credentials_response + ); + + let minter_env = TokenSourceMinter::new(MinterCredentialsEnvironment::new( + "SERVER_URL", + "API_KEY", + "API_SECRET", + )); + let minter_env_response = minter_env.fetch(&fetch_options).await; + log::info!( + "Example TokenSourceMinter / MinterCredentialsEnvironment result: {:?}", + minter_env_response + ); + + let minter_literal_custom = + TokenSourceMinter::new(|| MinterCredentials::new("server url", "api key", "api secret")); + let minter_literal_custom_response = minter_literal_custom.fetch(&fetch_options).await; + log::info!( + "Example TokenSourceMinter / custom credentials result: {:?}", + minter_literal_custom_response + ); + + let custom_minter = + TokenSourceCustomMinter::<_, MinterCredentialsEnvironment>::new(|access_token| { + access_token + .with_identity("rust-bot") + .with_name("Rust Bot") + .with_grants(access_token::VideoGrants { + room_join: true, + room: "my-room".to_string(), + ..Default::default() + }) + .to_jwt() + }); + let custom_minter_response = custom_minter.fetch().await; + log::info!("Example TokenSourceCustomMinter response: {:?}", custom_minter_response); + + let endpoint = TokenSourceEndpoint::new("https://example.com/my/example/auth/endpoint"); + let endpoint_response = endpoint.fetch(&fetch_options).await; + log::info!("Example TokenSourceMinter response: {:?}", endpoint_response); + + let sandbox_token_server = TokenSourceSandboxTokenServer::new("SANDBOX ID HERE"); + let sandbox_token_server_response = sandbox_token_server.fetch(&fetch_options).await; + log::info!("Example TokenSourceSandboxTokenServer: {:?}", sandbox_token_server_response); + + // let foo = Box::pin(async |options: &TokenSourceFetchOptions| { + // Ok(TokenSourceResponse::new("...", "... _options should be encoded in here ...")) + // }); + + // // TODO: custom + // let custom = TokenSourceCustom::new(foo); + // let _ = custom.fetch(&fetch_options).await; + + let custom = TokenSourceCustom::new(|_options| { + Box::pin(future::ready(Ok(TokenSourceResponse { + server_url: "...".into(), + participant_token: "... _options should be encoded in here ...".into(), + }))) + }); + let custom_response = custom.fetch(&fetch_options).await; + log::info!("Example TokenSourceCustomResponse: {:?}", endpoint_response); +} diff --git a/livekit-api/src/access_token.rs b/livekit-api/src/access_token.rs index 1c9d18d0c..d9d3c6acd 100644 --- a/livekit-api/src/access_token.rs +++ b/livekit-api/src/access_token.rs @@ -316,6 +316,29 @@ impl TokenVerifier { } } +/// Given a token, determine if it is currently valid based off data in the claims. +/// +/// NOTE: No token signature validation is being done! To do this, see [TokenVerifier]. +pub fn is_token_valid(token: &str) -> Result { + let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256); + validation.validate_exp = true; + validation.validate_nbf = true; + validation.insecure_disable_signature_validation(); + + let result = jsonwebtoken::decode::( + token, + &DecodingKey::from_secret(&[]), + &validation, + ); + + match result { + Ok(_) => Ok(true), + Err(err) if err.kind() == &jsonwebtoken::errors::ErrorKind::ExpiredSignature => Ok(false), + Err(err) if err.kind() == &jsonwebtoken::errors::ErrorKind::ImmatureSignature => Ok(false), + Err(err) => Err(err.into()), + } +} + #[cfg(test)] mod tests { use std::time::Duration; diff --git a/livekit-ffi/protocol/participant.proto b/livekit-ffi/protocol/participant.proto index 5e7ab34ba..23a0903e4 100644 --- a/livekit-ffi/protocol/participant.proto +++ b/livekit-ffi/protocol/participant.proto @@ -40,6 +40,7 @@ enum ParticipantKind { PARTICIPANT_KIND_EGRESS = 2; PARTICIPANT_KIND_SIP = 3; PARTICIPANT_KIND_AGENT = 4; + PARTICIPANT_KIND_CONNECTOR = 5; } enum DisconnectReason { diff --git a/livekit-ffi/src/conversion/participant.rs b/livekit-ffi/src/conversion/participant.rs index c7cbc9b97..dacf17a1e 100644 --- a/livekit-ffi/src/conversion/participant.rs +++ b/livekit-ffi/src/conversion/participant.rs @@ -46,6 +46,7 @@ impl From for proto::ParticipantKind { ParticipantKind::Ingress => proto::ParticipantKind::Ingress, ParticipantKind::Egress => proto::ParticipantKind::Egress, ParticipantKind::Agent => proto::ParticipantKind::Agent, + ParticipantKind::Connector => proto::ParticipantKind::Connector, } } } diff --git a/livekit-protocol/generate_proto.sh b/livekit-protocol/generate_proto.sh index d8b5c19c7..68d942d27 100755 --- a/livekit-protocol/generate_proto.sh +++ b/livekit-protocol/generate_proto.sh @@ -32,4 +32,5 @@ protoc \ $PROTOCOL/livekit_room.proto \ $PROTOCOL/livekit_webhook.proto \ $PROTOCOL/livekit_sip.proto \ - $PROTOCOL/livekit_models.proto + $PROTOCOL/livekit_models.proto \ + $PROTOCOL/livekit_token_source.proto diff --git a/livekit-protocol/protocol b/livekit-protocol/protocol index 2bc93ddc2..357f7686d 160000 --- a/livekit-protocol/protocol +++ b/livekit-protocol/protocol @@ -1 +1 @@ -Subproject commit 2bc93ddc27ccfa66ee8d270a1bcd115586fb601d +Subproject commit 357f7686de5bab17e1d3e3f1b8b805dba5a0ff56 diff --git a/livekit-protocol/src/livekit.rs b/livekit-protocol/src/livekit.rs index 676becb62..73219bbe4 100644 --- a/livekit-protocol/src/livekit.rs +++ b/livekit-protocol/src/livekit.rs @@ -12,7 +12,7 @@ pub struct MetricsBatch { /// This is useful for storing participant identities, track names, etc. /// There is also a predefined list of labels that can be used to reference common metrics. /// They have reserved indices from 0 to (METRIC_LABEL_PREDEFINED_MAX_VALUE - 1). - /// Indexes pointing at str_data should start from METRIC_LABEL_PREDEFINED_MAX_VALUE, + /// Indexes pointing at str_data should start from METRIC_LABEL_PREDEFINED_MAX_VALUE, /// such that str_data\[0\] == index of METRIC_LABEL_PREDEFINED_MAX_VALUE. #[prost(string, repeated, tag="3")] pub str_data: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, @@ -78,6 +78,14 @@ pub struct EventMetric { #[prost(uint32, tag="9")] pub rid: u32, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MetricsRecordingHeader { + #[prost(string, tag="1")] + pub room_id: ::prost::alloc::string::String, + #[prost(bool, optional, tag="2")] + pub enable_user_data_training: ::core::option::Option, +} // // Protocol used to record metrics for a specific session. // @@ -217,7 +225,7 @@ pub struct ListUpdate { pub add: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, /// delete items from a list #[prost(string, repeated, tag="3")] - pub del: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + pub remove: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, /// sets the list to an empty list #[prost(bool, tag="4")] pub clear: bool, @@ -398,6 +406,10 @@ pub mod participant_info { Sip = 3, /// LiveKit agents Agent = 4, + /// Connectors participants + /// + /// NEXT_ID: 8 + Connector = 7, } impl Kind { /// String value of the enum field names used in the ProtoBuf definition. @@ -411,6 +423,7 @@ pub mod participant_info { Kind::Egress => "EGRESS", Kind::Sip => "SIP", Kind::Agent => "AGENT", + Kind::Connector => "CONNECTOR", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -421,6 +434,7 @@ pub mod participant_info { "EGRESS" => Some(Self::Egress), "SIP" => Some(Self::Sip), "AGENT" => Some(Self::Agent), + "CONNECTOR" => Some(Self::Connector), _ => None, } } @@ -521,11 +535,9 @@ pub struct TrackInfo { pub muted: bool, /// original width of video (unset for audio) /// clients may receive a lower resolution version with simulcast - #[deprecated] #[prost(uint32, tag="5")] pub width: u32, /// original height of video (unset for audio) - #[deprecated] #[prost(uint32, tag="6")] pub height: u32, /// true if track is simulcasted @@ -601,6 +613,7 @@ pub mod video_layer { Unused = 0, OneSpatialLayerPerStream = 1, MultipleSpatialLayersPerStream = 2, + OneSpatialLayerPerStreamIncompleteRtcpSr = 3, } impl Mode { /// String value of the enum field names used in the ProtoBuf definition. @@ -612,6 +625,7 @@ pub mod video_layer { Mode::Unused => "MODE_UNUSED", Mode::OneSpatialLayerPerStream => "ONE_SPATIAL_LAYER_PER_STREAM", Mode::MultipleSpatialLayersPerStream => "MULTIPLE_SPATIAL_LAYERS_PER_STREAM", + Mode::OneSpatialLayerPerStreamIncompleteRtcpSr => "ONE_SPATIAL_LAYER_PER_STREAM_INCOMPLETE_RTCP_SR", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -620,6 +634,7 @@ pub mod video_layer { "MODE_UNUSED" => Some(Self::Unused), "ONE_SPATIAL_LAYER_PER_STREAM" => Some(Self::OneSpatialLayerPerStream), "MULTIPLE_SPATIAL_LAYERS_PER_STREAM" => Some(Self::MultipleSpatialLayersPerStream), + "ONE_SPATIAL_LAYER_PER_STREAM_INCOMPLETE_RTCP_SR" => Some(Self::OneSpatialLayerPerStreamIncompleteRtcpSr), _ => None, } } @@ -715,7 +730,7 @@ pub struct EncryptedPacket { pub iv: ::prost::alloc::vec::Vec, #[prost(uint32, tag="3")] pub key_index: u32, - /// This is an encrypted EncryptedPacketPayload message representation + /// This is an encrypted EncryptedPacketPayload message representation #[prost(bytes="vec", tag="4")] pub encrypted_value: ::prost::alloc::vec::Vec, } @@ -1440,11 +1455,29 @@ pub mod data_stream { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct FilterParams { + #[prost(string, repeated, tag="1")] + pub include_events: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, repeated, tag="2")] + pub exclude_events: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct WebhookConfig { #[prost(string, tag="1")] pub url: ::prost::alloc::string::String, #[prost(string, tag="2")] pub signing_key: ::prost::alloc::string::String, + #[prost(message, optional, tag="3")] + pub filter_params: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SubscribedAudioCodec { + #[prost(string, tag="1")] + pub codec: ::prost::alloc::string::String, + #[prost(bool, tag="2")] + pub enabled: bool, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] @@ -1452,6 +1485,7 @@ pub enum AudioCodec { DefaultAc = 0, Opus = 1, Aac = 2, + AcMp3 = 3, } impl AudioCodec { /// String value of the enum field names used in the ProtoBuf definition. @@ -1463,6 +1497,7 @@ impl AudioCodec { AudioCodec::DefaultAc => "DEFAULT_AC", AudioCodec::Opus => "OPUS", AudioCodec::Aac => "AAC", + AudioCodec::AcMp3 => "AC_MP3", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1471,6 +1506,7 @@ impl AudioCodec { "DEFAULT_AC" => Some(Self::DefaultAc), "OPUS" => Some(Self::Opus), "AAC" => Some(Self::Aac), + "AC_MP3" => Some(Self::AcMp3), _ => None, } } @@ -2703,6 +2739,7 @@ pub enum EncodedFileType { DefaultFiletype = 0, Mp4 = 1, Ogg = 2, + Mp3 = 3, } impl EncodedFileType { /// String value of the enum field names used in the ProtoBuf definition. @@ -2714,6 +2751,7 @@ impl EncodedFileType { EncodedFileType::DefaultFiletype => "DEFAULT_FILETYPE", EncodedFileType::Mp4 => "MP4", EncodedFileType::Ogg => "OGG", + EncodedFileType::Mp3 => "MP3", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2722,6 +2760,7 @@ impl EncodedFileType { "DEFAULT_FILETYPE" => Some(Self::DefaultFiletype), "MP4" => Some(Self::Mp4), "OGG" => Some(Self::Ogg), + "MP3" => Some(Self::Mp3), _ => None, } } @@ -3056,7 +3095,7 @@ pub mod signal_request { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SignalResponse { - #[prost(oneof="signal_response::Message", tags="1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25")] + #[prost(oneof="signal_response::Message", tags="1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27")] pub message: ::core::option::Option, } /// Nested message and enum types in `SignalResponse`. @@ -3139,6 +3178,12 @@ pub mod signal_response { /// notify number of required media sections to satisfy subscribed tracks #[prost(message, tag="25")] MediaSectionsRequirement(super::MediaSectionsRequirement), + /// when audio subscription changes, used to enable simulcasting of audio codecs based on subscriptions + #[prost(message, tag="26")] + SubscribedAudioCodecUpdate(super::SubscribedAudioCodecUpdate), + /// sent when server answers publisher, includes the mid -> trackID mapping + #[prost(message, tag="27")] + MappedAnswer(super::MappedSessionDescription), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -3302,6 +3347,14 @@ pub struct SessionDescription { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct MappedSessionDescription { + #[prost(message, optional, tag="1")] + pub session_description: ::core::option::Option, + #[prost(map="string, string", tag="2")] + pub mid_to_track_id: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantUpdate { #[prost(message, repeated, tag="1")] pub participants: ::prost::alloc::vec::Vec, @@ -3520,6 +3573,14 @@ pub struct SubscribedQualityUpdate { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct SubscribedAudioCodecUpdate { + #[prost(string, tag="1")] + pub track_sid: ::prost::alloc::string::String, + #[prost(message, repeated, tag="2")] + pub subscribed_audio_codecs: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackPermission { /// permission could be granted either by participant sid or identity #[prost(string, tag="1")] @@ -3697,6 +3758,8 @@ pub struct RequestResponse { pub reason: i32, #[prost(string, tag="3")] pub message: ::prost::alloc::string::String, + #[prost(oneof="request_response::Request", tags="4, 5, 6, 7, 8, 9")] + pub request: ::core::option::Option, } /// Nested message and enum types in `RequestResponse`. pub mod request_response { @@ -3707,6 +3770,9 @@ pub mod request_response { NotFound = 1, NotAllowed = 2, LimitExceeded = 3, + Queued = 4, + UnsupportedType = 5, + UnclassifiedError = 6, } impl Reason { /// String value of the enum field names used in the ProtoBuf definition. @@ -3719,6 +3785,9 @@ pub mod request_response { Reason::NotFound => "NOT_FOUND", Reason::NotAllowed => "NOT_ALLOWED", Reason::LimitExceeded => "LIMIT_EXCEEDED", + Reason::Queued => "QUEUED", + Reason::UnsupportedType => "UNSUPPORTED_TYPE", + Reason::UnclassifiedError => "UNCLASSIFIED_ERROR", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3728,10 +3797,29 @@ pub mod request_response { "NOT_FOUND" => Some(Self::NotFound), "NOT_ALLOWED" => Some(Self::NotAllowed), "LIMIT_EXCEEDED" => Some(Self::LimitExceeded), + "QUEUED" => Some(Self::Queued), + "UNSUPPORTED_TYPE" => Some(Self::UnsupportedType), + "UNCLASSIFIED_ERROR" => Some(Self::UnclassifiedError), _ => None, } } } + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Request { + #[prost(message, tag="4")] + Trickle(super::TrickleRequest), + #[prost(message, tag="5")] + AddTrack(super::AddTrackRequest), + #[prost(message, tag="6")] + Mute(super::MuteTrackRequest), + #[prost(message, tag="7")] + UpdateMetadata(super::UpdateParticipantMetadata), + #[prost(message, tag="8")] + UpdateAudioTrack(super::UpdateLocalAudioTrack), + #[prost(message, tag="9")] + UpdateVideoTrack(super::UpdateLocalVideoTrack), + } } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3928,6 +4016,8 @@ pub struct Job { pub agent_name: ::prost::alloc::string::String, #[prost(message, optional, tag="8")] pub state: ::core::option::Option, + #[prost(bool, tag="10")] + pub enable_recording: bool, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4571,6 +4661,26 @@ pub struct MoveParticipantResponse { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct PerformRpcRequest { + #[prost(string, tag="1")] + pub room: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub destination_identity: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub method: ::prost::alloc::string::String, + #[prost(string, tag="4")] + pub payload: ::prost::alloc::string::String, + #[prost(uint32, tag="5")] + pub response_timeout_ms: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PerformRpcResponse { + #[prost(string, tag="1")] + pub payload: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateIngressRequest { #[prost(enumeration="IngressInput", tag="1")] pub input_type: i32, @@ -5058,6 +5168,16 @@ pub struct CreateSipTrunkRequest { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct ProviderInfo { + #[prost(string, tag="1")] + pub id: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub name: ::prost::alloc::string::String, + #[prost(enumeration="ProviderType", tag="3")] + pub r#type: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SipTrunkInfo { #[prost(string, tag="1")] pub sip_trunk_id: ::prost::alloc::string::String, @@ -5715,10 +5835,18 @@ pub struct CreateSipParticipantRequest { #[prost(enumeration="SipMediaEncryption", tag="18")] pub media_encryption: i32, /// Wait for the answer for the call before returning. - /// - /// NEXT ID: 21 #[prost(bool, tag="19")] pub wait_until_answered: bool, + /// Optional display name for the 'From' SIP header. + /// + /// Cases: + /// 1) Unspecified: Use legacy behavior - display name will be set to be the caller's number. + /// 2) Empty string: Do not send a display name, which will result in a CNAM lookup downstream. + /// 3) Non-empty: Use the specified value as the display name. + /// + /// NEXT ID: 22 + #[prost(string, optional, tag="21")] + pub display_name: ::core::option::Option<::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -5806,6 +5934,12 @@ pub struct SipCallInfo { pub audio_codec: ::prost::alloc::string::String, #[prost(string, tag="21")] pub media_encryption: ::prost::alloc::string::String, + #[prost(string, tag="25")] + pub pcap_file_link: ::prost::alloc::string::String, + #[prost(message, repeated, tag="26")] + pub call_context: ::prost::alloc::vec::Vec<::pbjson_types::Any>, + #[prost(message, optional, tag="27")] + pub provider_info: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -6106,6 +6240,37 @@ impl SipMediaEncryption { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] +pub enum ProviderType { + Unknown = 0, + /// Internally implemented + Internal = 1, + /// Vendor provided + External = 2, +} +impl ProviderType { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + ProviderType::Unknown => "PROVIDER_TYPE_UNKNOWN", + ProviderType::Internal => "PROVIDER_TYPE_INTERNAL", + ProviderType::External => "PROVIDER_TYPE_EXTERNAL", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "PROVIDER_TYPE_UNKNOWN" => Some(Self::Unknown), + "PROVIDER_TYPE_INTERNAL" => Some(Self::Internal), + "PROVIDER_TYPE_EXTERNAL" => Some(Self::External), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] pub enum SipCallStatus { /// Incoming call is being handled by the SIP service. The SIP participant hasn't joined a LiveKit room yet ScsCallIncoming = 0, @@ -6228,5 +6393,37 @@ impl SipCallDirection { } } } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TokenSourceRequest { + /// The name of the room being requested when generating credentials + #[prost(string, optional, tag="1")] + pub room_name: ::core::option::Option<::prost::alloc::string::String>, + /// The name of the participant being requested for this client when generating credentials + #[prost(string, optional, tag="2")] + pub participant_name: ::core::option::Option<::prost::alloc::string::String>, + /// The identity of the participant being requested for this client when generating credentials + #[prost(string, optional, tag="3")] + pub participant_identity: ::core::option::Option<::prost::alloc::string::String>, + /// Any participant metadata being included along with the credentials generation operation + #[prost(string, optional, tag="4")] + pub participant_metadata: ::core::option::Option<::prost::alloc::string::String>, + /// Any participant attributes being included along with the credentials generation operation + #[prost(map="string, string", tag="5")] + pub participant_attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + /// A RoomConfiguration object can be passed to request extra parameters should be included when + /// generating connection credentials - dispatching agents, defining egress settings, etc + /// More info: + #[prost(message, optional, tag="6")] + pub room_config: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TokenSourceResponse { + #[prost(string, tag="1")] + pub server_url: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub participant_token: ::prost::alloc::string::String, +} include!("livekit.serde.rs"); // @@protoc_insertion_point(module) diff --git a/livekit-protocol/src/livekit.serde.rs b/livekit-protocol/src/livekit.serde.rs index 3c79f6c7a..2decf9d14 100644 --- a/livekit-protocol/src/livekit.serde.rs +++ b/livekit-protocol/src/livekit.serde.rs @@ -959,6 +959,7 @@ impl serde::Serialize for AudioCodec { Self::DefaultAc => "DEFAULT_AC", Self::Opus => "OPUS", Self::Aac => "AAC", + Self::AcMp3 => "AC_MP3", }; serializer.serialize_str(variant) } @@ -973,6 +974,7 @@ impl<'de> serde::Deserialize<'de> for AudioCodec { "DEFAULT_AC", "OPUS", "AAC", + "AC_MP3", ]; struct GeneratedVisitor; @@ -1016,6 +1018,7 @@ impl<'de> serde::Deserialize<'de> for AudioCodec { "DEFAULT_AC" => Ok(AudioCodec::DefaultAc), "OPUS" => Ok(AudioCodec::Opus), "AAC" => Ok(AudioCodec::Aac), + "AC_MP3" => Ok(AudioCodec::AcMp3), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -4759,6 +4762,9 @@ impl serde::Serialize for CreateSipParticipantRequest { if self.wait_until_answered { len += 1; } + if self.display_name.is_some() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.CreateSIPParticipantRequest", len)?; if !self.sip_trunk_id.is_empty() { struct_ser.serialize_field("sipTrunkId", &self.sip_trunk_id)?; @@ -4824,6 +4830,9 @@ impl serde::Serialize for CreateSipParticipantRequest { if self.wait_until_answered { struct_ser.serialize_field("waitUntilAnswered", &self.wait_until_answered)?; } + if let Some(v) = self.display_name.as_ref() { + struct_ser.serialize_field("displayName", v)?; + } struct_ser.end() } } @@ -4871,6 +4880,8 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { "mediaEncryption", "wait_until_answered", "waitUntilAnswered", + "display_name", + "displayName", ]; #[allow(clippy::enum_variant_names)] @@ -4895,6 +4906,7 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { KrispEnabled, MediaEncryption, WaitUntilAnswered, + DisplayName, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -4937,6 +4949,7 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { "krispEnabled" | "krisp_enabled" => Ok(GeneratedField::KrispEnabled), "mediaEncryption" | "media_encryption" => Ok(GeneratedField::MediaEncryption), "waitUntilAnswered" | "wait_until_answered" => Ok(GeneratedField::WaitUntilAnswered), + "displayName" | "display_name" => Ok(GeneratedField::DisplayName), _ => Ok(GeneratedField::__SkipField__), } } @@ -4976,6 +4989,7 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { let mut krisp_enabled__ = None; let mut media_encryption__ = None; let mut wait_until_answered__ = None; + let mut display_name__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::SipTrunkId => { @@ -5102,6 +5116,12 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { } wait_until_answered__ = Some(map_.next_value()?); } + GeneratedField::DisplayName => { + if display_name__.is_some() { + return Err(serde::de::Error::duplicate_field("displayName")); + } + display_name__ = map_.next_value()?; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -5128,6 +5148,7 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { krisp_enabled: krisp_enabled__.unwrap_or_default(), media_encryption: media_encryption__.unwrap_or_default(), wait_until_answered: wait_until_answered__.unwrap_or_default(), + display_name: display_name__, }) } } @@ -8911,6 +8932,7 @@ impl serde::Serialize for EncodedFileType { Self::DefaultFiletype => "DEFAULT_FILETYPE", Self::Mp4 => "MP4", Self::Ogg => "OGG", + Self::Mp3 => "MP3", }; serializer.serialize_str(variant) } @@ -8925,6 +8947,7 @@ impl<'de> serde::Deserialize<'de> for EncodedFileType { "DEFAULT_FILETYPE", "MP4", "OGG", + "MP3", ]; struct GeneratedVisitor; @@ -8968,6 +8991,7 @@ impl<'de> serde::Deserialize<'de> for EncodedFileType { "DEFAULT_FILETYPE" => Ok(EncodedFileType::DefaultFiletype), "MP4" => Ok(EncodedFileType::Mp4), "OGG" => Ok(EncodedFileType::Ogg), + "MP3" => Ok(EncodedFileType::Mp3), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -10334,6 +10358,120 @@ impl<'de> serde::Deserialize<'de> for FileInfo { deserializer.deserialize_struct("livekit.FileInfo", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for FilterParams { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.include_events.is_empty() { + len += 1; + } + if !self.exclude_events.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.FilterParams", len)?; + if !self.include_events.is_empty() { + struct_ser.serialize_field("includeEvents", &self.include_events)?; + } + if !self.exclude_events.is_empty() { + struct_ser.serialize_field("excludeEvents", &self.exclude_events)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for FilterParams { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "include_events", + "includeEvents", + "exclude_events", + "excludeEvents", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + IncludeEvents, + ExcludeEvents, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "includeEvents" | "include_events" => Ok(GeneratedField::IncludeEvents), + "excludeEvents" | "exclude_events" => Ok(GeneratedField::ExcludeEvents), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = FilterParams; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.FilterParams") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut include_events__ = None; + let mut exclude_events__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::IncludeEvents => { + if include_events__.is_some() { + return Err(serde::de::Error::duplicate_field("includeEvents")); + } + include_events__ = Some(map_.next_value()?); + } + GeneratedField::ExcludeEvents => { + if exclude_events__.is_some() { + return Err(serde::de::Error::duplicate_field("excludeEvents")); + } + exclude_events__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(FilterParams { + include_events: include_events__.unwrap_or_default(), + exclude_events: exclude_events__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.FilterParams", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for ForwardParticipantRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -13656,6 +13794,9 @@ impl serde::Serialize for Job { if self.state.is_some() { len += 1; } + if self.enable_recording { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.Job", len)?; if !self.id.is_empty() { struct_ser.serialize_field("id", &self.id)?; @@ -13686,6 +13827,9 @@ impl serde::Serialize for Job { if let Some(v) = self.state.as_ref() { struct_ser.serialize_field("state", v)?; } + if self.enable_recording { + struct_ser.serialize_field("enableRecording", &self.enable_recording)?; + } struct_ser.end() } } @@ -13707,6 +13851,8 @@ impl<'de> serde::Deserialize<'de> for Job { "agent_name", "agentName", "state", + "enable_recording", + "enableRecording", ]; #[allow(clippy::enum_variant_names)] @@ -13720,6 +13866,7 @@ impl<'de> serde::Deserialize<'de> for Job { Metadata, AgentName, State, + EnableRecording, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -13751,6 +13898,7 @@ impl<'de> serde::Deserialize<'de> for Job { "metadata" => Ok(GeneratedField::Metadata), "agentName" | "agent_name" => Ok(GeneratedField::AgentName), "state" => Ok(GeneratedField::State), + "enableRecording" | "enable_recording" => Ok(GeneratedField::EnableRecording), _ => Ok(GeneratedField::__SkipField__), } } @@ -13779,6 +13927,7 @@ impl<'de> serde::Deserialize<'de> for Job { let mut metadata__ = None; let mut agent_name__ = None; let mut state__ = None; + let mut enable_recording__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Id => { @@ -13835,6 +13984,12 @@ impl<'de> serde::Deserialize<'de> for Job { } state__ = map_.next_value()?; } + GeneratedField::EnableRecording => { + if enable_recording__.is_some() { + return Err(serde::de::Error::duplicate_field("enableRecording")); + } + enable_recording__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -13850,6 +14005,7 @@ impl<'de> serde::Deserialize<'de> for Job { metadata: metadata__.unwrap_or_default(), agent_name: agent_name__.unwrap_or_default(), state: state__, + enable_recording: enable_recording__.unwrap_or_default(), }) } } @@ -17209,7 +17365,7 @@ impl serde::Serialize for ListUpdate { if !self.add.is_empty() { len += 1; } - if !self.del.is_empty() { + if !self.remove.is_empty() { len += 1; } if self.clear { @@ -17222,8 +17378,8 @@ impl serde::Serialize for ListUpdate { if !self.add.is_empty() { struct_ser.serialize_field("add", &self.add)?; } - if !self.del.is_empty() { - struct_ser.serialize_field("del", &self.del)?; + if !self.remove.is_empty() { + struct_ser.serialize_field("remove", &self.remove)?; } if self.clear { struct_ser.serialize_field("clear", &self.clear)?; @@ -17240,7 +17396,7 @@ impl<'de> serde::Deserialize<'de> for ListUpdate { const FIELDS: &[&str] = &[ "set", "add", - "del", + "remove", "clear", ]; @@ -17248,7 +17404,7 @@ impl<'de> serde::Deserialize<'de> for ListUpdate { enum GeneratedField { Set, Add, - Del, + Remove, Clear, __SkipField__, } @@ -17274,7 +17430,7 @@ impl<'de> serde::Deserialize<'de> for ListUpdate { match value { "set" => Ok(GeneratedField::Set), "add" => Ok(GeneratedField::Add), - "del" => Ok(GeneratedField::Del), + "remove" => Ok(GeneratedField::Remove), "clear" => Ok(GeneratedField::Clear), _ => Ok(GeneratedField::__SkipField__), } @@ -17297,7 +17453,7 @@ impl<'de> serde::Deserialize<'de> for ListUpdate { { let mut set__ = None; let mut add__ = None; - let mut del__ = None; + let mut remove__ = None; let mut clear__ = None; while let Some(k) = map_.next_key()? { match k { @@ -17313,11 +17469,11 @@ impl<'de> serde::Deserialize<'de> for ListUpdate { } add__ = Some(map_.next_value()?); } - GeneratedField::Del => { - if del__.is_some() { - return Err(serde::de::Error::duplicate_field("del")); + GeneratedField::Remove => { + if remove__.is_some() { + return Err(serde::de::Error::duplicate_field("remove")); } - del__ = Some(map_.next_value()?); + remove__ = Some(map_.next_value()?); } GeneratedField::Clear => { if clear__.is_some() { @@ -17333,7 +17489,7 @@ impl<'de> serde::Deserialize<'de> for ListUpdate { Ok(ListUpdate { set: set__.unwrap_or_default(), add: add__.unwrap_or_default(), - del: del__.unwrap_or_default(), + remove: remove__.unwrap_or_default(), clear: clear__.unwrap_or_default(), }) } @@ -17341,6 +17497,122 @@ impl<'de> serde::Deserialize<'de> for ListUpdate { deserializer.deserialize_struct("livekit.ListUpdate", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for MappedSessionDescription { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.session_description.is_some() { + len += 1; + } + if !self.mid_to_track_id.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.MappedSessionDescription", len)?; + if let Some(v) = self.session_description.as_ref() { + struct_ser.serialize_field("sessionDescription", v)?; + } + if !self.mid_to_track_id.is_empty() { + struct_ser.serialize_field("midToTrackId", &self.mid_to_track_id)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for MappedSessionDescription { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "session_description", + "sessionDescription", + "mid_to_track_id", + "midToTrackId", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + SessionDescription, + MidToTrackId, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "sessionDescription" | "session_description" => Ok(GeneratedField::SessionDescription), + "midToTrackId" | "mid_to_track_id" => Ok(GeneratedField::MidToTrackId), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = MappedSessionDescription; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.MappedSessionDescription") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut session_description__ = None; + let mut mid_to_track_id__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::SessionDescription => { + if session_description__.is_some() { + return Err(serde::de::Error::duplicate_field("sessionDescription")); + } + session_description__ = map_.next_value()?; + } + GeneratedField::MidToTrackId => { + if mid_to_track_id__.is_some() { + return Err(serde::de::Error::duplicate_field("midToTrackId")); + } + mid_to_track_id__ = Some( + map_.next_value::>()? + ); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(MappedSessionDescription { + session_description: session_description__, + mid_to_track_id: mid_to_track_id__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.MappedSessionDescription", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for MediaSectionsRequirement { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -17895,6 +18167,120 @@ impl<'de> serde::Deserialize<'de> for MetricsBatch { deserializer.deserialize_struct("livekit.MetricsBatch", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for MetricsRecordingHeader { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.room_id.is_empty() { + len += 1; + } + if self.enable_user_data_training.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.MetricsRecordingHeader", len)?; + if !self.room_id.is_empty() { + struct_ser.serialize_field("roomId", &self.room_id)?; + } + if let Some(v) = self.enable_user_data_training.as_ref() { + struct_ser.serialize_field("enableUserDataTraining", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for MetricsRecordingHeader { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "room_id", + "roomId", + "enable_user_data_training", + "enableUserDataTraining", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + RoomId, + EnableUserDataTraining, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "roomId" | "room_id" => Ok(GeneratedField::RoomId), + "enableUserDataTraining" | "enable_user_data_training" => Ok(GeneratedField::EnableUserDataTraining), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = MetricsRecordingHeader; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.MetricsRecordingHeader") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut room_id__ = None; + let mut enable_user_data_training__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::RoomId => { + if room_id__.is_some() { + return Err(serde::de::Error::duplicate_field("roomId")); + } + room_id__ = Some(map_.next_value()?); + } + GeneratedField::EnableUserDataTraining => { + if enable_user_data_training__.is_some() { + return Err(serde::de::Error::duplicate_field("enableUserDataTraining")); + } + enable_user_data_training__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(MetricsRecordingHeader { + room_id: room_id__.unwrap_or_default(), + enable_user_data_training: enable_user_data_training__, + }) + } + } + deserializer.deserialize_struct("livekit.MetricsRecordingHeader", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for MigrateJobRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -19307,6 +19693,7 @@ impl serde::Serialize for participant_info::Kind { Self::Egress => "EGRESS", Self::Sip => "SIP", Self::Agent => "AGENT", + Self::Connector => "CONNECTOR", }; serializer.serialize_str(variant) } @@ -19323,6 +19710,7 @@ impl<'de> serde::Deserialize<'de> for participant_info::Kind { "EGRESS", "SIP", "AGENT", + "CONNECTOR", ]; struct GeneratedVisitor; @@ -19368,6 +19756,7 @@ impl<'de> serde::Deserialize<'de> for participant_info::Kind { "EGRESS" => Ok(participant_info::Kind::Egress), "SIP" => Ok(participant_info::Kind::Sip), "AGENT" => Ok(participant_info::Kind::Agent), + "CONNECTOR" => Ok(participant_info::Kind::Connector), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -19973,6 +20362,268 @@ impl<'de> serde::Deserialize<'de> for ParticipantUpdate { deserializer.deserialize_struct("livekit.ParticipantUpdate", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for PerformRpcRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.room.is_empty() { + len += 1; + } + if !self.destination_identity.is_empty() { + len += 1; + } + if !self.method.is_empty() { + len += 1; + } + if !self.payload.is_empty() { + len += 1; + } + if self.response_timeout_ms != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.PerformRpcRequest", len)?; + if !self.room.is_empty() { + struct_ser.serialize_field("room", &self.room)?; + } + if !self.destination_identity.is_empty() { + struct_ser.serialize_field("destinationIdentity", &self.destination_identity)?; + } + if !self.method.is_empty() { + struct_ser.serialize_field("method", &self.method)?; + } + if !self.payload.is_empty() { + struct_ser.serialize_field("payload", &self.payload)?; + } + if self.response_timeout_ms != 0 { + struct_ser.serialize_field("responseTimeoutMs", &self.response_timeout_ms)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for PerformRpcRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "room", + "destination_identity", + "destinationIdentity", + "method", + "payload", + "response_timeout_ms", + "responseTimeoutMs", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Room, + DestinationIdentity, + Method, + Payload, + ResponseTimeoutMs, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "room" => Ok(GeneratedField::Room), + "destinationIdentity" | "destination_identity" => Ok(GeneratedField::DestinationIdentity), + "method" => Ok(GeneratedField::Method), + "payload" => Ok(GeneratedField::Payload), + "responseTimeoutMs" | "response_timeout_ms" => Ok(GeneratedField::ResponseTimeoutMs), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = PerformRpcRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.PerformRpcRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut room__ = None; + let mut destination_identity__ = None; + let mut method__ = None; + let mut payload__ = None; + let mut response_timeout_ms__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Room => { + if room__.is_some() { + return Err(serde::de::Error::duplicate_field("room")); + } + room__ = Some(map_.next_value()?); + } + GeneratedField::DestinationIdentity => { + if destination_identity__.is_some() { + return Err(serde::de::Error::duplicate_field("destinationIdentity")); + } + destination_identity__ = Some(map_.next_value()?); + } + GeneratedField::Method => { + if method__.is_some() { + return Err(serde::de::Error::duplicate_field("method")); + } + method__ = Some(map_.next_value()?); + } + GeneratedField::Payload => { + if payload__.is_some() { + return Err(serde::de::Error::duplicate_field("payload")); + } + payload__ = Some(map_.next_value()?); + } + GeneratedField::ResponseTimeoutMs => { + if response_timeout_ms__.is_some() { + return Err(serde::de::Error::duplicate_field("responseTimeoutMs")); + } + response_timeout_ms__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(PerformRpcRequest { + room: room__.unwrap_or_default(), + destination_identity: destination_identity__.unwrap_or_default(), + method: method__.unwrap_or_default(), + payload: payload__.unwrap_or_default(), + response_timeout_ms: response_timeout_ms__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.PerformRpcRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for PerformRpcResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.payload.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.PerformRpcResponse", len)?; + if !self.payload.is_empty() { + struct_ser.serialize_field("payload", &self.payload)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for PerformRpcResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "payload", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Payload, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "payload" => Ok(GeneratedField::Payload), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = PerformRpcResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.PerformRpcResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut payload__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Payload => { + if payload__.is_some() { + return Err(serde::de::Error::duplicate_field("payload")); + } + payload__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(PerformRpcResponse { + payload: payload__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.PerformRpcResponse", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for Ping { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -20347,6 +20998,211 @@ impl<'de> serde::Deserialize<'de> for Pong { deserializer.deserialize_struct("livekit.Pong", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for ProviderInfo { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.id.is_empty() { + len += 1; + } + if !self.name.is_empty() { + len += 1; + } + if self.r#type != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.ProviderInfo", len)?; + if !self.id.is_empty() { + struct_ser.serialize_field("id", &self.id)?; + } + if !self.name.is_empty() { + struct_ser.serialize_field("name", &self.name)?; + } + if self.r#type != 0 { + let v = ProviderType::try_from(self.r#type) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.r#type)))?; + struct_ser.serialize_field("type", &v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for ProviderInfo { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "id", + "name", + "type", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Id, + Name, + Type, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "id" => Ok(GeneratedField::Id), + "name" => Ok(GeneratedField::Name), + "type" => Ok(GeneratedField::Type), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ProviderInfo; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.ProviderInfo") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut id__ = None; + let mut name__ = None; + let mut r#type__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Id => { + if id__.is_some() { + return Err(serde::de::Error::duplicate_field("id")); + } + id__ = Some(map_.next_value()?); + } + GeneratedField::Name => { + if name__.is_some() { + return Err(serde::de::Error::duplicate_field("name")); + } + name__ = Some(map_.next_value()?); + } + GeneratedField::Type => { + if r#type__.is_some() { + return Err(serde::de::Error::duplicate_field("type")); + } + r#type__ = Some(map_.next_value::()? as i32); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(ProviderInfo { + id: id__.unwrap_or_default(), + name: name__.unwrap_or_default(), + r#type: r#type__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.ProviderInfo", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for ProviderType { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::Unknown => "PROVIDER_TYPE_UNKNOWN", + Self::Internal => "PROVIDER_TYPE_INTERNAL", + Self::External => "PROVIDER_TYPE_EXTERNAL", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for ProviderType { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "PROVIDER_TYPE_UNKNOWN", + "PROVIDER_TYPE_INTERNAL", + "PROVIDER_TYPE_EXTERNAL", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ProviderType; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "PROVIDER_TYPE_UNKNOWN" => Ok(ProviderType::Unknown), + "PROVIDER_TYPE_INTERNAL" => Ok(ProviderType::Internal), + "PROVIDER_TYPE_EXTERNAL" => Ok(ProviderType::External), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} impl serde::Serialize for ProxyConfig { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -23217,6 +24073,9 @@ impl serde::Serialize for RequestResponse { if !self.message.is_empty() { len += 1; } + if self.request.is_some() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.RequestResponse", len)?; if self.request_id != 0 { struct_ser.serialize_field("requestId", &self.request_id)?; @@ -23229,6 +24088,28 @@ impl serde::Serialize for RequestResponse { if !self.message.is_empty() { struct_ser.serialize_field("message", &self.message)?; } + if let Some(v) = self.request.as_ref() { + match v { + request_response::Request::Trickle(v) => { + struct_ser.serialize_field("trickle", v)?; + } + request_response::Request::AddTrack(v) => { + struct_ser.serialize_field("addTrack", v)?; + } + request_response::Request::Mute(v) => { + struct_ser.serialize_field("mute", v)?; + } + request_response::Request::UpdateMetadata(v) => { + struct_ser.serialize_field("updateMetadata", v)?; + } + request_response::Request::UpdateAudioTrack(v) => { + struct_ser.serialize_field("updateAudioTrack", v)?; + } + request_response::Request::UpdateVideoTrack(v) => { + struct_ser.serialize_field("updateVideoTrack", v)?; + } + } + } struct_ser.end() } } @@ -23243,6 +24124,16 @@ impl<'de> serde::Deserialize<'de> for RequestResponse { "requestId", "reason", "message", + "trickle", + "add_track", + "addTrack", + "mute", + "update_metadata", + "updateMetadata", + "update_audio_track", + "updateAudioTrack", + "update_video_track", + "updateVideoTrack", ]; #[allow(clippy::enum_variant_names)] @@ -23250,6 +24141,12 @@ impl<'de> serde::Deserialize<'de> for RequestResponse { RequestId, Reason, Message, + Trickle, + AddTrack, + Mute, + UpdateMetadata, + UpdateAudioTrack, + UpdateVideoTrack, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -23275,6 +24172,12 @@ impl<'de> serde::Deserialize<'de> for RequestResponse { "requestId" | "request_id" => Ok(GeneratedField::RequestId), "reason" => Ok(GeneratedField::Reason), "message" => Ok(GeneratedField::Message), + "trickle" => Ok(GeneratedField::Trickle), + "addTrack" | "add_track" => Ok(GeneratedField::AddTrack), + "mute" => Ok(GeneratedField::Mute), + "updateMetadata" | "update_metadata" => Ok(GeneratedField::UpdateMetadata), + "updateAudioTrack" | "update_audio_track" => Ok(GeneratedField::UpdateAudioTrack), + "updateVideoTrack" | "update_video_track" => Ok(GeneratedField::UpdateVideoTrack), _ => Ok(GeneratedField::__SkipField__), } } @@ -23297,6 +24200,7 @@ impl<'de> serde::Deserialize<'de> for RequestResponse { let mut request_id__ = None; let mut reason__ = None; let mut message__ = None; + let mut request__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::RequestId => { @@ -23319,6 +24223,48 @@ impl<'de> serde::Deserialize<'de> for RequestResponse { } message__ = Some(map_.next_value()?); } + GeneratedField::Trickle => { + if request__.is_some() { + return Err(serde::de::Error::duplicate_field("trickle")); + } + request__ = map_.next_value::<::std::option::Option<_>>()?.map(request_response::Request::Trickle) +; + } + GeneratedField::AddTrack => { + if request__.is_some() { + return Err(serde::de::Error::duplicate_field("addTrack")); + } + request__ = map_.next_value::<::std::option::Option<_>>()?.map(request_response::Request::AddTrack) +; + } + GeneratedField::Mute => { + if request__.is_some() { + return Err(serde::de::Error::duplicate_field("mute")); + } + request__ = map_.next_value::<::std::option::Option<_>>()?.map(request_response::Request::Mute) +; + } + GeneratedField::UpdateMetadata => { + if request__.is_some() { + return Err(serde::de::Error::duplicate_field("updateMetadata")); + } + request__ = map_.next_value::<::std::option::Option<_>>()?.map(request_response::Request::UpdateMetadata) +; + } + GeneratedField::UpdateAudioTrack => { + if request__.is_some() { + return Err(serde::de::Error::duplicate_field("updateAudioTrack")); + } + request__ = map_.next_value::<::std::option::Option<_>>()?.map(request_response::Request::UpdateAudioTrack) +; + } + GeneratedField::UpdateVideoTrack => { + if request__.is_some() { + return Err(serde::de::Error::duplicate_field("updateVideoTrack")); + } + request__ = map_.next_value::<::std::option::Option<_>>()?.map(request_response::Request::UpdateVideoTrack) +; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -23328,6 +24274,7 @@ impl<'de> serde::Deserialize<'de> for RequestResponse { request_id: request_id__.unwrap_or_default(), reason: reason__.unwrap_or_default(), message: message__.unwrap_or_default(), + request: request__, }) } } @@ -23345,6 +24292,9 @@ impl serde::Serialize for request_response::Reason { Self::NotFound => "NOT_FOUND", Self::NotAllowed => "NOT_ALLOWED", Self::LimitExceeded => "LIMIT_EXCEEDED", + Self::Queued => "QUEUED", + Self::UnsupportedType => "UNSUPPORTED_TYPE", + Self::UnclassifiedError => "UNCLASSIFIED_ERROR", }; serializer.serialize_str(variant) } @@ -23360,6 +24310,9 @@ impl<'de> serde::Deserialize<'de> for request_response::Reason { "NOT_FOUND", "NOT_ALLOWED", "LIMIT_EXCEEDED", + "QUEUED", + "UNSUPPORTED_TYPE", + "UNCLASSIFIED_ERROR", ]; struct GeneratedVisitor; @@ -23404,6 +24357,9 @@ impl<'de> serde::Deserialize<'de> for request_response::Reason { "NOT_FOUND" => Ok(request_response::Reason::NotFound), "NOT_ALLOWED" => Ok(request_response::Reason::NotAllowed), "LIMIT_EXCEEDED" => Ok(request_response::Reason::LimitExceeded), + "QUEUED" => Ok(request_response::Reason::Queued), + "UNSUPPORTED_TYPE" => Ok(request_response::Reason::UnsupportedType), + "UNCLASSIFIED_ERROR" => Ok(request_response::Reason::UnclassifiedError), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -26056,6 +27012,15 @@ impl serde::Serialize for SipCallInfo { if !self.media_encryption.is_empty() { len += 1; } + if !self.pcap_file_link.is_empty() { + len += 1; + } + if !self.call_context.is_empty() { + len += 1; + } + if self.provider_info.is_some() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.SIPCallInfo", len)?; if !self.call_id.is_empty() { struct_ser.serialize_field("callId", &self.call_id)?; @@ -26151,6 +27116,15 @@ impl serde::Serialize for SipCallInfo { if !self.media_encryption.is_empty() { struct_ser.serialize_field("mediaEncryption", &self.media_encryption)?; } + if !self.pcap_file_link.is_empty() { + struct_ser.serialize_field("pcapFileLink", &self.pcap_file_link)?; + } + if !self.call_context.is_empty() { + struct_ser.serialize_field("callContext", &self.call_context)?; + } + if let Some(v) = self.provider_info.as_ref() { + struct_ser.serialize_field("providerInfo", v)?; + } struct_ser.end() } } @@ -26207,6 +27181,12 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { "audioCodec", "media_encryption", "mediaEncryption", + "pcap_file_link", + "pcapFileLink", + "call_context", + "callContext", + "provider_info", + "providerInfo", ]; #[allow(clippy::enum_variant_names)] @@ -26235,6 +27215,9 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { CallStatusCode, AudioCodec, MediaEncryption, + PcapFileLink, + CallContext, + ProviderInfo, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -26281,6 +27264,9 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { "callStatusCode" | "call_status_code" => Ok(GeneratedField::CallStatusCode), "audioCodec" | "audio_codec" => Ok(GeneratedField::AudioCodec), "mediaEncryption" | "media_encryption" => Ok(GeneratedField::MediaEncryption), + "pcapFileLink" | "pcap_file_link" => Ok(GeneratedField::PcapFileLink), + "callContext" | "call_context" => Ok(GeneratedField::CallContext), + "providerInfo" | "provider_info" => Ok(GeneratedField::ProviderInfo), _ => Ok(GeneratedField::__SkipField__), } } @@ -26324,6 +27310,9 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { let mut call_status_code__ = None; let mut audio_codec__ = None; let mut media_encryption__ = None; + let mut pcap_file_link__ = None; + let mut call_context__ = None; + let mut provider_info__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::CallId => { @@ -26484,6 +27473,24 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { } media_encryption__ = Some(map_.next_value()?); } + GeneratedField::PcapFileLink => { + if pcap_file_link__.is_some() { + return Err(serde::de::Error::duplicate_field("pcapFileLink")); + } + pcap_file_link__ = Some(map_.next_value()?); + } + GeneratedField::CallContext => { + if call_context__.is_some() { + return Err(serde::de::Error::duplicate_field("callContext")); + } + call_context__ = Some(map_.next_value()?); + } + GeneratedField::ProviderInfo => { + if provider_info__.is_some() { + return Err(serde::de::Error::duplicate_field("providerInfo")); + } + provider_info__ = map_.next_value()?; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -26514,6 +27521,9 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { call_status_code: call_status_code__, audio_codec: audio_codec__.unwrap_or_default(), media_encryption: media_encryption__.unwrap_or_default(), + pcap_file_link: pcap_file_link__.unwrap_or_default(), + call_context: call_context__.unwrap_or_default(), + provider_info: provider_info__, }) } } @@ -32506,6 +33516,12 @@ impl serde::Serialize for SignalResponse { signal_response::Message::MediaSectionsRequirement(v) => { struct_ser.serialize_field("mediaSectionsRequirement", v)?; } + signal_response::Message::SubscribedAudioCodecUpdate(v) => { + struct_ser.serialize_field("subscribedAudioCodecUpdate", v)?; + } + signal_response::Message::MappedAnswer(v) => { + struct_ser.serialize_field("mappedAnswer", v)?; + } } } struct_ser.end() @@ -32557,6 +33573,10 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { "roomMoved", "media_sections_requirement", "mediaSectionsRequirement", + "subscribed_audio_codec_update", + "subscribedAudioCodecUpdate", + "mapped_answer", + "mappedAnswer", ]; #[allow(clippy::enum_variant_names)] @@ -32585,6 +33605,8 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { TrackSubscribed, RoomMoved, MediaSectionsRequirement, + SubscribedAudioCodecUpdate, + MappedAnswer, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -32631,6 +33653,8 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { "trackSubscribed" | "track_subscribed" => Ok(GeneratedField::TrackSubscribed), "roomMoved" | "room_moved" => Ok(GeneratedField::RoomMoved), "mediaSectionsRequirement" | "media_sections_requirement" => Ok(GeneratedField::MediaSectionsRequirement), + "subscribedAudioCodecUpdate" | "subscribed_audio_codec_update" => Ok(GeneratedField::SubscribedAudioCodecUpdate), + "mappedAnswer" | "mapped_answer" => Ok(GeneratedField::MappedAnswer), _ => Ok(GeneratedField::__SkipField__), } } @@ -32817,6 +33841,20 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { return Err(serde::de::Error::duplicate_field("mediaSectionsRequirement")); } message__ = map_.next_value::<::std::option::Option<_>>()?.map(signal_response::Message::MediaSectionsRequirement) +; + } + GeneratedField::SubscribedAudioCodecUpdate => { + if message__.is_some() { + return Err(serde::de::Error::duplicate_field("subscribedAudioCodecUpdate")); + } + message__ = map_.next_value::<::std::option::Option<_>>()?.map(signal_response::Message::SubscribedAudioCodecUpdate) +; + } + GeneratedField::MappedAnswer => { + if message__.is_some() { + return Err(serde::de::Error::duplicate_field("mappedAnswer")); + } + message__ = map_.next_value::<::std::option::Option<_>>()?.map(signal_response::Message::MappedAnswer) ; } GeneratedField::__SkipField__ => { @@ -34822,7 +35860,225 @@ impl<'de> serde::Deserialize<'de> for StreamStateUpdate { E: serde::de::Error, { match value { - "streamStates" | "stream_states" => Ok(GeneratedField::StreamStates), + "streamStates" | "stream_states" => Ok(GeneratedField::StreamStates), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = StreamStateUpdate; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.StreamStateUpdate") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut stream_states__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::StreamStates => { + if stream_states__.is_some() { + return Err(serde::de::Error::duplicate_field("streamStates")); + } + stream_states__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(StreamStateUpdate { + stream_states: stream_states__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.StreamStateUpdate", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for SubscribedAudioCodec { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.codec.is_empty() { + len += 1; + } + if self.enabled { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.SubscribedAudioCodec", len)?; + if !self.codec.is_empty() { + struct_ser.serialize_field("codec", &self.codec)?; + } + if self.enabled { + struct_ser.serialize_field("enabled", &self.enabled)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SubscribedAudioCodec { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "codec", + "enabled", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Codec, + Enabled, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "codec" => Ok(GeneratedField::Codec), + "enabled" => Ok(GeneratedField::Enabled), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SubscribedAudioCodec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.SubscribedAudioCodec") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut codec__ = None; + let mut enabled__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Codec => { + if codec__.is_some() { + return Err(serde::de::Error::duplicate_field("codec")); + } + codec__ = Some(map_.next_value()?); + } + GeneratedField::Enabled => { + if enabled__.is_some() { + return Err(serde::de::Error::duplicate_field("enabled")); + } + enabled__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(SubscribedAudioCodec { + codec: codec__.unwrap_or_default(), + enabled: enabled__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.SubscribedAudioCodec", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for SubscribedAudioCodecUpdate { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.track_sid.is_empty() { + len += 1; + } + if !self.subscribed_audio_codecs.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.SubscribedAudioCodecUpdate", len)?; + if !self.track_sid.is_empty() { + struct_ser.serialize_field("trackSid", &self.track_sid)?; + } + if !self.subscribed_audio_codecs.is_empty() { + struct_ser.serialize_field("subscribedAudioCodecs", &self.subscribed_audio_codecs)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SubscribedAudioCodecUpdate { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "track_sid", + "trackSid", + "subscribed_audio_codecs", + "subscribedAudioCodecs", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + TrackSid, + SubscribedAudioCodecs, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "trackSid" | "track_sid" => Ok(GeneratedField::TrackSid), + "subscribedAudioCodecs" | "subscribed_audio_codecs" => Ok(GeneratedField::SubscribedAudioCodecs), _ => Ok(GeneratedField::__SkipField__), } } @@ -34832,36 +36088,44 @@ impl<'de> serde::Deserialize<'de> for StreamStateUpdate { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = StreamStateUpdate; + type Value = SubscribedAudioCodecUpdate; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.StreamStateUpdate") + formatter.write_str("struct livekit.SubscribedAudioCodecUpdate") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut stream_states__ = None; + let mut track_sid__ = None; + let mut subscribed_audio_codecs__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::StreamStates => { - if stream_states__.is_some() { - return Err(serde::de::Error::duplicate_field("streamStates")); + GeneratedField::TrackSid => { + if track_sid__.is_some() { + return Err(serde::de::Error::duplicate_field("trackSid")); } - stream_states__ = Some(map_.next_value()?); + track_sid__ = Some(map_.next_value()?); + } + GeneratedField::SubscribedAudioCodecs => { + if subscribed_audio_codecs__.is_some() { + return Err(serde::de::Error::duplicate_field("subscribedAudioCodecs")); + } + subscribed_audio_codecs__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(StreamStateUpdate { - stream_states: stream_states__.unwrap_or_default(), + Ok(SubscribedAudioCodecUpdate { + track_sid: track_sid__.unwrap_or_default(), + subscribed_audio_codecs: subscribed_audio_codecs__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.StreamStateUpdate", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.SubscribedAudioCodecUpdate", FIELDS, GeneratedVisitor) } } impl serde::Serialize for SubscribedCodec { @@ -36244,6 +37508,308 @@ impl<'de> serde::Deserialize<'de> for TokenPagination { deserializer.deserialize_struct("livekit.TokenPagination", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for TokenSourceRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.room_name.is_some() { + len += 1; + } + if self.participant_name.is_some() { + len += 1; + } + if self.participant_identity.is_some() { + len += 1; + } + if self.participant_metadata.is_some() { + len += 1; + } + if !self.participant_attributes.is_empty() { + len += 1; + } + if self.room_config.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.TokenSourceRequest", len)?; + if let Some(v) = self.room_name.as_ref() { + struct_ser.serialize_field("roomName", v)?; + } + if let Some(v) = self.participant_name.as_ref() { + struct_ser.serialize_field("participantName", v)?; + } + if let Some(v) = self.participant_identity.as_ref() { + struct_ser.serialize_field("participantIdentity", v)?; + } + if let Some(v) = self.participant_metadata.as_ref() { + struct_ser.serialize_field("participantMetadata", v)?; + } + if !self.participant_attributes.is_empty() { + struct_ser.serialize_field("participantAttributes", &self.participant_attributes)?; + } + if let Some(v) = self.room_config.as_ref() { + struct_ser.serialize_field("roomConfig", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for TokenSourceRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "room_name", + "roomName", + "participant_name", + "participantName", + "participant_identity", + "participantIdentity", + "participant_metadata", + "participantMetadata", + "participant_attributes", + "participantAttributes", + "room_config", + "roomConfig", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + RoomName, + ParticipantName, + ParticipantIdentity, + ParticipantMetadata, + ParticipantAttributes, + RoomConfig, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "roomName" | "room_name" => Ok(GeneratedField::RoomName), + "participantName" | "participant_name" => Ok(GeneratedField::ParticipantName), + "participantIdentity" | "participant_identity" => Ok(GeneratedField::ParticipantIdentity), + "participantMetadata" | "participant_metadata" => Ok(GeneratedField::ParticipantMetadata), + "participantAttributes" | "participant_attributes" => Ok(GeneratedField::ParticipantAttributes), + "roomConfig" | "room_config" => Ok(GeneratedField::RoomConfig), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = TokenSourceRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.TokenSourceRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut room_name__ = None; + let mut participant_name__ = None; + let mut participant_identity__ = None; + let mut participant_metadata__ = None; + let mut participant_attributes__ = None; + let mut room_config__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::RoomName => { + if room_name__.is_some() { + return Err(serde::de::Error::duplicate_field("roomName")); + } + room_name__ = map_.next_value()?; + } + GeneratedField::ParticipantName => { + if participant_name__.is_some() { + return Err(serde::de::Error::duplicate_field("participantName")); + } + participant_name__ = map_.next_value()?; + } + GeneratedField::ParticipantIdentity => { + if participant_identity__.is_some() { + return Err(serde::de::Error::duplicate_field("participantIdentity")); + } + participant_identity__ = map_.next_value()?; + } + GeneratedField::ParticipantMetadata => { + if participant_metadata__.is_some() { + return Err(serde::de::Error::duplicate_field("participantMetadata")); + } + participant_metadata__ = map_.next_value()?; + } + GeneratedField::ParticipantAttributes => { + if participant_attributes__.is_some() { + return Err(serde::de::Error::duplicate_field("participantAttributes")); + } + participant_attributes__ = Some( + map_.next_value::>()? + ); + } + GeneratedField::RoomConfig => { + if room_config__.is_some() { + return Err(serde::de::Error::duplicate_field("roomConfig")); + } + room_config__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(TokenSourceRequest { + room_name: room_name__, + participant_name: participant_name__, + participant_identity: participant_identity__, + participant_metadata: participant_metadata__, + participant_attributes: participant_attributes__.unwrap_or_default(), + room_config: room_config__, + }) + } + } + deserializer.deserialize_struct("livekit.TokenSourceRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for TokenSourceResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.server_url.is_empty() { + len += 1; + } + if !self.participant_token.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.TokenSourceResponse", len)?; + if !self.server_url.is_empty() { + struct_ser.serialize_field("serverUrl", &self.server_url)?; + } + if !self.participant_token.is_empty() { + struct_ser.serialize_field("participantToken", &self.participant_token)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for TokenSourceResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "server_url", + "serverUrl", + "participant_token", + "participantToken", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + ServerUrl, + ParticipantToken, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "serverUrl" | "server_url" => Ok(GeneratedField::ServerUrl), + "participantToken" | "participant_token" => Ok(GeneratedField::ParticipantToken), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = TokenSourceResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.TokenSourceResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut server_url__ = None; + let mut participant_token__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::ServerUrl => { + if server_url__.is_some() { + return Err(serde::de::Error::duplicate_field("serverUrl")); + } + server_url__ = Some(map_.next_value()?); + } + GeneratedField::ParticipantToken => { + if participant_token__.is_some() { + return Err(serde::de::Error::duplicate_field("participantToken")); + } + participant_token__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(TokenSourceResponse { + server_url: server_url__.unwrap_or_default(), + participant_token: participant_token__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.TokenSourceResponse", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for TrackCompositeEgressRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -41842,6 +43408,7 @@ impl serde::Serialize for video_layer::Mode { Self::Unused => "MODE_UNUSED", Self::OneSpatialLayerPerStream => "ONE_SPATIAL_LAYER_PER_STREAM", Self::MultipleSpatialLayersPerStream => "MULTIPLE_SPATIAL_LAYERS_PER_STREAM", + Self::OneSpatialLayerPerStreamIncompleteRtcpSr => "ONE_SPATIAL_LAYER_PER_STREAM_INCOMPLETE_RTCP_SR", }; serializer.serialize_str(variant) } @@ -41856,6 +43423,7 @@ impl<'de> serde::Deserialize<'de> for video_layer::Mode { "MODE_UNUSED", "ONE_SPATIAL_LAYER_PER_STREAM", "MULTIPLE_SPATIAL_LAYERS_PER_STREAM", + "ONE_SPATIAL_LAYER_PER_STREAM_INCOMPLETE_RTCP_SR", ]; struct GeneratedVisitor; @@ -41899,6 +43467,7 @@ impl<'de> serde::Deserialize<'de> for video_layer::Mode { "MODE_UNUSED" => Ok(video_layer::Mode::Unused), "ONE_SPATIAL_LAYER_PER_STREAM" => Ok(video_layer::Mode::OneSpatialLayerPerStream), "MULTIPLE_SPATIAL_LAYERS_PER_STREAM" => Ok(video_layer::Mode::MultipleSpatialLayersPerStream), + "ONE_SPATIAL_LAYER_PER_STREAM_INCOMPLETE_RTCP_SR" => Ok(video_layer::Mode::OneSpatialLayerPerStreamIncompleteRtcpSr), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -42319,6 +43888,9 @@ impl serde::Serialize for WebhookConfig { if !self.signing_key.is_empty() { len += 1; } + if self.filter_params.is_some() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.WebhookConfig", len)?; if !self.url.is_empty() { struct_ser.serialize_field("url", &self.url)?; @@ -42326,6 +43898,9 @@ impl serde::Serialize for WebhookConfig { if !self.signing_key.is_empty() { struct_ser.serialize_field("signingKey", &self.signing_key)?; } + if let Some(v) = self.filter_params.as_ref() { + struct_ser.serialize_field("filterParams", v)?; + } struct_ser.end() } } @@ -42339,12 +43914,15 @@ impl<'de> serde::Deserialize<'de> for WebhookConfig { "url", "signing_key", "signingKey", + "filter_params", + "filterParams", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { Url, SigningKey, + FilterParams, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -42369,6 +43947,7 @@ impl<'de> serde::Deserialize<'de> for WebhookConfig { match value { "url" => Ok(GeneratedField::Url), "signingKey" | "signing_key" => Ok(GeneratedField::SigningKey), + "filterParams" | "filter_params" => Ok(GeneratedField::FilterParams), _ => Ok(GeneratedField::__SkipField__), } } @@ -42390,6 +43969,7 @@ impl<'de> serde::Deserialize<'de> for WebhookConfig { { let mut url__ = None; let mut signing_key__ = None; + let mut filter_params__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Url => { @@ -42404,6 +43984,12 @@ impl<'de> serde::Deserialize<'de> for WebhookConfig { } signing_key__ = Some(map_.next_value()?); } + GeneratedField::FilterParams => { + if filter_params__.is_some() { + return Err(serde::de::Error::duplicate_field("filterParams")); + } + filter_params__ = map_.next_value()?; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -42412,6 +43998,7 @@ impl<'de> serde::Deserialize<'de> for WebhookConfig { Ok(WebhookConfig { url: url__.unwrap_or_default(), signing_key: signing_key__.unwrap_or_default(), + filter_params: filter_params__, }) } } diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index e41c726f7..b36bc6ec1 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -45,6 +45,7 @@ libloading = { version = "0.8.6" } bytes = "1.10.1" bmrng = "0.5.2" test-log = "0.2.18" +reqwest = { version = "0.12", features = ["json"] } [dev-dependencies] anyhow = "1.0.99" diff --git a/livekit/src/prelude.rs b/livekit/src/prelude.rs index 505cf7e2a..ed375b44e 100644 --- a/livekit/src/prelude.rs +++ b/livekit/src/prelude.rs @@ -21,6 +21,7 @@ pub use crate::{ RemoteParticipant, RpcError, RpcErrorCode, RpcInvocationData, }, publication::{LocalTrackPublication, RemoteTrackPublication, TrackPublication}, + token_source::{TokenSourceConfigurable, TokenSourceFixed}, track::{ AudioTrack, LocalAudioTrack, LocalTrack, LocalVideoTrack, RemoteAudioTrack, RemoteTrack, RemoteVideoTrack, StreamState, Track, TrackDimension, TrackKind, TrackSource, VideoTrack, diff --git a/livekit/src/proto.rs b/livekit/src/proto.rs index cd274f1ab..5a97f0629 100644 --- a/livekit/src/proto.rs +++ b/livekit/src/proto.rs @@ -154,6 +154,7 @@ impl From for participant::ParticipantKind { participant_info::Kind::Egress => participant::ParticipantKind::Egress, participant_info::Kind::Sip => participant::ParticipantKind::Sip, participant_info::Kind::Agent => participant::ParticipantKind::Agent, + participant_info::Kind::Connector => participant::ParticipantKind::Connector, } } } diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 3ee94088d..48a969d53 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -60,6 +60,7 @@ pub mod id; pub mod options; pub mod participant; pub mod publication; +pub mod token_source; pub mod track; pub(crate) mod utils; diff --git a/livekit/src/room/participant/mod.rs b/livekit/src/room/participant/mod.rs index d3105d5e8..751d7f2fc 100644 --- a/livekit/src/room/participant/mod.rs +++ b/livekit/src/room/participant/mod.rs @@ -44,6 +44,7 @@ pub enum ParticipantKind { Egress, Sip, Agent, + Connector, } #[derive(Debug, Clone, Copy, Eq, PartialEq)] diff --git a/livekit/src/room/token_source/cache.rs b/livekit/src/room/token_source/cache.rs new file mode 100644 index 000000000..c35a10d91 --- /dev/null +++ b/livekit/src/room/token_source/cache.rs @@ -0,0 +1,255 @@ +use livekit_api::access_token; +use parking_lot::RwLock; +use std::sync::Arc; + +use crate::token_source::{ + TokenResponseCache, TokenResponseCacheValue, TokenResponseInMemoryCache, + TokenSourceConfigurable, TokenSourceFetchOptions, TokenSourceFixed, TokenSourceResponse, + TokenSourceResult, +}; + +pub trait TokenSourceFixedCached { + fn get_response_cache(&self) -> Arc>>; + + async fn update(&self) -> TokenSourceResult; + + async fn fetch_cached(&self) -> TokenSourceResult { + let cache = self.get_response_cache(); + + let cached_response_to_return = { + let cache_read = cache.read(); + let cached_value = cache_read.get(); + + if let Some(cached_response) = cached_value { + if access_token::is_token_valid(&cached_response.participant_token)? { + Some(cached_response.clone()) + } else { + None + } + } else { + None + } + }; + + if let Some(cached_response) = cached_response_to_return { + Ok(cached_response) + } else { + let response = self.update().await?; + cache.write().set(response.clone()); + Ok(response) + } + } +} + +pub trait TokenSourceConfigurableCached { + fn get_response_cache( + &self, + ) -> Arc>>; + + async fn update( + &self, + options: &TokenSourceFetchOptions, + ) -> TokenSourceResult; + + async fn fetch_cached( + &self, + options: &TokenSourceFetchOptions, + ) -> TokenSourceResult { + let cache = self.get_response_cache(); + + let cached_response_to_return = { + let cache_read = cache.read(); + let cached_value = cache_read.get(); + + if let Some((cached_options, cached_response)) = cached_value { + if options == cached_options + && access_token::is_token_valid(&cached_response.participant_token)? + { + Some(cached_response.clone()) + } else { + None + } + } else { + None + } + }; + + if let Some(cached_response) = cached_response_to_return { + Ok(cached_response) + } else { + let response = self.update(options).await?; + cache.write().set((options.clone(), response.clone())); + Ok(response) + } + } +} + +// FIXME: Why doesn't this work? +// impl TokenSourceConfigurable for T { +// async fn fetch( +// &self, +// options: &TokenSourceFetchOptions, +// ) -> TokenSourceResult { +// self.fetch_cached(options).await +// } +// } + +trait CacheType {} + +pub struct CacheConfigurable(T); +impl CacheType for CacheConfigurable {} + +pub struct CacheFixed(T); +impl CacheType for CacheFixed {} + +/// A conmposable TokenSource which can wrap either a [TokenSourceFixed] or a [TokenSourceConfigurable] and +/// caches the intermediate value in a [TokenResponseCache]. +pub struct TokenSourceCache< + Type: CacheType, + Value: TokenResponseCacheValue, + Cache: TokenResponseCache, +> { + inner: Type, + cache: Arc>, + _v: Value, // FIXME: how do I remove this? `Value` needs to be used in here or I get an error. +} + +impl + TokenSourceCache< + CacheConfigurable, + (TokenSourceFetchOptions, TokenSourceResponse), + TokenResponseInMemoryCache<(TokenSourceFetchOptions, TokenSourceResponse)>, + > +{ + // FIXME: Is there some way I can make this `new` without requiring something like the below? + // TokenSourceCache::, _, _>::new(...) + fn new_configurable(inner_token_source: Inner) -> Self { + TokenSourceCache::new_configurable_with_cache( + inner_token_source, + TokenResponseInMemoryCache::new(), + ) + } +} + +impl + TokenSourceCache< + CacheFixed, + TokenSourceResponse, + TokenResponseInMemoryCache, + > +{ + // FIXME: Is there some way I can make this `new` without requiring something like the below? + // TokenSourceCache::, _, _>::new(...) + fn new_fixed(inner_token_source: Inner) -> Self { + TokenSourceCache::new_fixed_with_cache( + inner_token_source, + TokenResponseInMemoryCache::new(), + ) + } +} + +impl< + Inner: TokenSourceConfigurable, + Cache: TokenResponseCache<(TokenSourceFetchOptions, TokenSourceResponse)>, + > + TokenSourceCache< + CacheConfigurable, + (TokenSourceFetchOptions, TokenSourceResponse), + Cache, + > +{ + fn new_configurable_with_cache(inner_token_source: Inner, token_cache: Cache) -> Self { + Self { + inner: CacheConfigurable(inner_token_source), + cache: Arc::new(RwLock::new(token_cache)), + + // FIXME: remove this! + _v: ( + TokenSourceFetchOptions::default(), + TokenSourceResponse { server_url: "".into(), participant_token: "".into() }, + ), + } + } +} + +impl> + TokenSourceCache, TokenSourceResponse, Cache> +{ + fn new_fixed_with_cache(inner_token_source: Inner, token_cache: Cache) -> Self { + Self { + inner: CacheFixed(inner_token_source), + cache: Arc::new(RwLock::new(token_cache)), + + // FIXME: remove this! + _v: TokenSourceResponse { server_url: "".into(), participant_token: "".into() }, + } + } +} + +impl> + TokenSourceCache +{ + fn clear_cache(&mut self) { + self.cache.write().clear(); + } +} + +impl< + Inner: TokenSourceConfigurable, + Cache: TokenResponseCache<(TokenSourceFetchOptions, TokenSourceResponse)>, + > TokenSourceConfigurableCached + for TokenSourceCache< + CacheConfigurable, + (TokenSourceFetchOptions, TokenSourceResponse), + Cache, + > +{ + fn get_response_cache( + &self, + ) -> Arc>> { + self.cache.clone() + } + async fn update( + &self, + options: &TokenSourceFetchOptions, + ) -> TokenSourceResult { + self.inner.0.fetch(options).await + } +} + +impl> TokenSourceFixedCached + for TokenSourceCache, TokenSourceResponse, Cache> +{ + fn get_response_cache(&self) -> Arc>> { + self.cache.clone() + } + async fn update(&self) -> TokenSourceResult { + self.inner.0.fetch().await + } +} + +impl< + Inner: TokenSourceConfigurable, + Cache: TokenResponseCache<(TokenSourceFetchOptions, TokenSourceResponse)>, + > TokenSourceConfigurable + for TokenSourceCache< + CacheConfigurable, + (TokenSourceFetchOptions, TokenSourceResponse), + Cache, + > +{ + async fn fetch( + &self, + options: &TokenSourceFetchOptions, + ) -> TokenSourceResult { + self.fetch_cached(options).await + } +} + +impl> TokenSourceFixed + for TokenSourceCache, TokenSourceResponse, Cache> +{ + async fn fetch(&self) -> TokenSourceResult { + self.fetch_cached().await + } +} diff --git a/livekit/src/room/token_source/error.rs b/livekit/src/room/token_source/error.rs new file mode 100644 index 000000000..24905be57 --- /dev/null +++ b/livekit/src/room/token_source/error.rs @@ -0,0 +1,19 @@ +use std::error::Error; + +use livekit_api::access_token::AccessTokenError; + +#[derive(Debug, thiserror::Error)] +pub enum TokenSourceError { + #[error("network error: {0}")] + Reqwest(#[from] reqwest::Error), + #[error( + "Error generating token from endpoint {url}: received {status_code:?} / {raw_body_text}" + )] + TokenGenerationFailed { url: String, status_code: reqwest::StatusCode, raw_body_text: String }, + #[error("access token error: {0}")] + AccessToken(#[from] AccessTokenError), + #[error("Other error: {0}")] + Other(#[from] Box), +} + +pub type TokenSourceResult = Result; diff --git a/livekit/src/room/token_source/fetch_options.rs b/livekit/src/room/token_source/fetch_options.rs new file mode 100644 index 000000000..d591def0a --- /dev/null +++ b/livekit/src/room/token_source/fetch_options.rs @@ -0,0 +1,123 @@ +// Copyright 2025 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use livekit_protocol as proto; +use std::collections::HashMap; + +use crate::token_source::TokenSourceRequest; + +/// Options that can be used when fetching new credentials from a TokenSourceConfigurable +/// +/// Example: +/// ```rust +/// use livekit::token_source::TokenSourceFetchOptions; +/// let _ = TokenSourceFetchOptions::default().with_agent_name("my agent name"); +/// ``` +#[derive(Clone, Default, PartialEq, Eq)] +pub struct TokenSourceFetchOptions { + room_name: Option, + participant_name: Option, + participant_identity: Option, + participant_metadata: Option, + participant_attributes: Option>, + + agent_name: Option, + agent_metadata: Option, +} + +impl TokenSourceFetchOptions { + pub fn with_room_name(mut self, room_name: &str) -> Self { + self.room_name = Some(room_name.into()); + self + } + pub fn with_participant_name(mut self, participant_name: &str) -> Self { + self.participant_name = Some(participant_name.into()); + self + } + pub fn with_participant_identity(mut self, participant_identity: &str) -> Self { + self.participant_identity = Some(participant_identity.into()); + self + } + pub fn with_participant_metadata(mut self, participant_metadata: &str) -> Self { + self.participant_metadata = Some(participant_metadata.into()); + self + } + + fn ensure_participant_attributes_defined(&mut self) -> &mut HashMap { + if self.participant_attributes.is_none() { + self.participant_attributes = Some(HashMap::new()); + }; + + let Some(participant_attribute_mut) = self.participant_attributes.as_mut() else { + unreachable!(); + }; + participant_attribute_mut + } + + pub fn with_participant_attribute( + mut self, + attribute_key: &str, + attribute_value: &str, + ) -> Self { + self.ensure_participant_attributes_defined() + .insert(attribute_key.into(), attribute_value.into()); + self + } + + pub fn with_participant_attributes( + mut self, + participant_attributes: HashMap, + ) -> Self { + self.ensure_participant_attributes_defined().extend(participant_attributes); + self + } + + pub fn with_agent_name(mut self, agent_name: &str) -> Self { + self.agent_name = Some(agent_name.into()); + self + } + pub fn with_agent_metadata(mut self, agent_metadata: &str) -> Self { + self.agent_metadata = Some(agent_metadata.into()); + self + } +} + +impl Into for TokenSourceFetchOptions { + fn into(self) -> TokenSourceRequest { + let mut agent_dispatch = proto::RoomAgentDispatch::default(); + if let Some(agent_name) = self.agent_name { + agent_dispatch.agent_name = agent_name; + } + if let Some(agent_metadata) = self.agent_metadata { + agent_dispatch.metadata = agent_metadata; + } + + let room_config = if agent_dispatch != proto::RoomAgentDispatch::default() { + let mut room_config = proto::RoomConfiguration::default(); + room_config.agents.push(agent_dispatch); + Some(room_config) + } else { + None + }; + + TokenSourceRequest { + room_name: self.room_name, + participant_name: self.participant_name, + participant_identity: self.participant_identity, + participant_metadata: self.participant_metadata, + participant_attributes: self.participant_attributes.unwrap_or_default(), + room_config, + } + } +} diff --git a/livekit/src/room/token_source/minter_credentials.rs b/livekit/src/room/token_source/minter_credentials.rs new file mode 100644 index 000000000..b75acf6e6 --- /dev/null +++ b/livekit/src/room/token_source/minter_credentials.rs @@ -0,0 +1,78 @@ +// Copyright 2025 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +const LIVEKIT_URL_ENV_NAME: &'static str = "LIVEKIT_URL"; +const LIVEKIT_API_KEY_ENV_NAME: &'static str = "LIVEKIT_API_KEY"; +const LIVEKIT_API_SECRET_ENV_NAME: &'static str = "LIVEKIT_API_SECRET"; + +/// MinterCredentials provides a way to configure a TokenSourceMinter with a `LIVEKIT_URL`, +/// `LIVEKIT_API_KEY`, and `LIVEKIT_API_SECRET` value. +pub trait MinterCredentialsSource { + fn get(&self) -> MinterCredentials; +} + +#[derive(Debug, Clone)] +pub struct MinterCredentials { + pub url: String, + pub api_key: String, + pub api_secret: String, +} +impl MinterCredentials { + pub fn new(server_url: &str, api_key: &str, api_secret: &str) -> Self { + Self { url: server_url.into(), api_key: api_key.into(), api_secret: api_secret.into() } + } +} + +impl MinterCredentialsSource for MinterCredentials { + fn get(&self) -> MinterCredentials { + self.clone() + } +} + +// FIXME: maybe add dotenv source too? Or have this look there too? +pub struct MinterCredentialsEnvironment(String, String, String); + +impl MinterCredentialsEnvironment { + pub fn new(url_variable: &str, api_key_variable: &str, api_secret_variable: &str) -> Self { + Self(url_variable.into(), api_key_variable.into(), api_secret_variable.into()) + } +} + +impl MinterCredentialsSource for MinterCredentialsEnvironment { + fn get(&self) -> MinterCredentials { + let (url_variable, api_key_variable, api_secret_variable) = (&self.0, &self.1, &self.2); + let url = std::env::var(url_variable).expect(format!("{url_variable} is not set").as_str()); + let api_key = std::env::var(api_key_variable) + .expect(format!("{api_key_variable} is not set").as_str()); + let api_secret = std::env::var(api_secret_variable) + .expect(format!("{api_secret_variable} is not set").as_str()); + MinterCredentials { url, api_key, api_secret } + } +} + +impl Default for MinterCredentialsEnvironment { + fn default() -> Self { + Self( + LIVEKIT_URL_ENV_NAME.into(), + LIVEKIT_API_KEY_ENV_NAME.into(), + LIVEKIT_API_SECRET_ENV_NAME.into(), + ) + } +} + +impl MinterCredentials> MinterCredentialsSource for F { + fn get(&self) -> MinterCredentials { + self() + } +} diff --git a/livekit/src/room/token_source/mod.rs b/livekit/src/room/token_source/mod.rs new file mode 100644 index 000000000..8a99753bf --- /dev/null +++ b/livekit/src/room/token_source/mod.rs @@ -0,0 +1,249 @@ +// Copyright 2025 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use livekit_api::access_token::{self, AccessTokenError}; +use livekit_protocol as proto; +use std::{future::Future, pin::Pin}; + +mod cache; +mod error; +mod fetch_options; +mod minter_credentials; +mod request_response; +mod token_response_cache; +mod traits; + +pub use cache::{CacheConfigurable, CacheFixed, TokenSourceCache}; +pub use error::{TokenSourceError, TokenSourceResult}; +pub use fetch_options::TokenSourceFetchOptions; +pub use minter_credentials::{ + MinterCredentials, MinterCredentialsEnvironment, MinterCredentialsSource, +}; +pub use request_response::{TokenSourceRequest, TokenSourceResponse}; +pub use token_response_cache::{ + TokenResponseCache, TokenResponseCacheValue, TokenResponseInMemoryCache, +}; +pub use traits::{ + TokenSourceConfigurable, TokenSourceConfigurableSynchronous, TokenSourceFixed, + TokenSourceFixedSynchronous, +}; + +pub trait TokenLiteralGenerator { + fn apply(&self) -> TokenSourceResponse; +} + +impl TokenLiteralGenerator for TokenSourceResponse { + fn apply(&self) -> TokenSourceResponse { + self.clone() + } +} + +impl TokenSourceResponse> TokenLiteralGenerator for F { + // FIXME: allow this to be an async fn! + fn apply(&self) -> TokenSourceResponse { + self() + } +} + +pub struct TokenSourceLiteral { + generator: Generator, +} +impl TokenSourceLiteral { + pub fn new(generator: G) -> Self { + Self { generator } + } +} + +impl TokenSourceFixedSynchronous for TokenSourceLiteral { + fn fetch_synchronous(&self) -> TokenSourceResult { + Ok(self.generator.apply()) + } +} + +pub struct TokenSourceMinter { + credentials_source: CredentialsSource, +} + +impl TokenSourceMinter { + pub fn new(credentials_source: CS) -> Self { + Self { credentials_source } + } +} + +impl TokenSourceConfigurableSynchronous for TokenSourceMinter { + fn fetch_synchronous( + &self, + options: &TokenSourceFetchOptions, + ) -> TokenSourceResult { + let MinterCredentials { url: server_url, api_key, api_secret } = + self.credentials_source.get(); + + // FIXME: apply options in the below code! + let participant_token = access_token::AccessToken::with_api_key(&api_key, &api_secret) + .with_identity("rust-bot") + .with_name("Rust Bot") + .with_grants(access_token::VideoGrants { + room_join: true, + room: "my-room".to_string(), + ..Default::default() + }) + .to_jwt()?; + + Ok(TokenSourceResponse { server_url, participant_token }) + } +} + +impl Default for TokenSourceMinter { + fn default() -> Self { + Self::new(MinterCredentialsEnvironment::default()) + } +} + +pub struct TokenSourceCustomMinter< + MintFn: Fn(access_token::AccessToken) -> Result, + Credentials: MinterCredentialsSource, +> { + mint_fn: MintFn, + credentials_source: Credentials, +} + +impl< + MF: Fn(access_token::AccessToken) -> Result, + CS: MinterCredentialsSource, + > TokenSourceCustomMinter +{ + pub fn new_with_credentials(mint_fn: MF, credentials_source: CS) -> Self { + Self { mint_fn, credentials_source } + } + pub fn new(mint_fn: MF) -> TokenSourceCustomMinter { + TokenSourceCustomMinter::new_with_credentials( + mint_fn, + MinterCredentialsEnvironment::default(), + ) + } +} + +impl< + MF: Fn(access_token::AccessToken) -> Result, + C: MinterCredentialsSource, + > TokenSourceFixedSynchronous for TokenSourceCustomMinter +{ + fn fetch_synchronous(&self) -> TokenSourceResult { + let MinterCredentials { url: server_url, api_key, api_secret } = + self.credentials_source.get(); + + let participant_token = + (self.mint_fn)(access_token::AccessToken::with_api_key(&api_key, &api_secret))?; + + Ok(TokenSourceResponse { server_url, participant_token }) + } +} + +use reqwest::{header::HeaderMap, Method}; + +pub struct TokenSourceEndpoint { + url: String, + method: Method, + headers: HeaderMap, +} + +impl TokenSourceEndpoint { + pub fn new(url: &str) -> Self { + Self { url: url.into(), method: Method::POST, headers: HeaderMap::new() } + } +} + +impl TokenSourceConfigurable for TokenSourceEndpoint { + async fn fetch( + &self, + options: &TokenSourceFetchOptions, + ) -> TokenSourceResult { + let client = reqwest::Client::new(); + + let request: TokenSourceRequest = options.clone().into(); + let request_proto: proto::TokenSourceRequest = request.into(); + + let response = client + .request(self.method.clone(), &self.url) + .json(&request_proto) + .headers(self.headers.clone()) + .send() + .await?; + + if !response.status().is_success() { + return Err(TokenSourceError::TokenGenerationFailed { + url: self.url.clone(), + status_code: response.status(), + raw_body_text: response.text().await?, + }); + } + + let response_proto = response.json::().await?; + Ok(response_proto.into()) + } +} + +pub struct TokenSourceSandboxTokenServer(TokenSourceEndpoint); + +impl TokenSourceSandboxTokenServer { + pub fn new(sandbox_id: &str) -> Self { + Self::new_with_base_url(sandbox_id, "https://cloud-api.livekit.io") + } + pub fn new_with_base_url(sandbox_id: &str, base_url: &str) -> Self { + let mut endpoint = + TokenSourceEndpoint::new(&format!("{base_url}/api/v2/sandbox/connection-details")); + endpoint.headers.insert("X-Sandbox-ID", sandbox_id.parse().unwrap()); + Self(endpoint) + } +} + +impl TokenSourceConfigurable for TokenSourceSandboxTokenServer { + async fn fetch( + &self, + options: &TokenSourceFetchOptions, + ) -> TokenSourceResult { + self.0.fetch(options).await + } +} + +pub struct TokenSourceCustom< + CustomFn: Fn( + &TokenSourceFetchOptions, + ) -> Pin>>>, +>(CustomFn); + +impl< + CustomFn: Fn( + &TokenSourceFetchOptions, + ) -> Pin>>>, + > TokenSourceCustom +{ + pub fn new(custom_fn: CustomFn) -> Self { + Self(custom_fn) + } +} + +impl< + CustomFn: Fn( + &TokenSourceFetchOptions, + ) -> Pin>>>, + > TokenSourceConfigurable for TokenSourceCustom +{ + async fn fetch( + &self, + options: &TokenSourceFetchOptions, + ) -> TokenSourceResult { + (self.0)(options).await + } +} diff --git a/livekit/src/room/token_source/request_response.rs b/livekit/src/room/token_source/request_response.rs new file mode 100644 index 000000000..e23f1cb60 --- /dev/null +++ b/livekit/src/room/token_source/request_response.rs @@ -0,0 +1,67 @@ +// Copyright 2025 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use livekit_protocol as proto; +use std::collections::HashMap; + +#[derive(Debug, Clone, PartialEq)] +pub struct TokenSourceRequest { + /// The name of the room being requested when generating credentials + pub room_name: Option, + + /// The name of the participant being requested for this client when generating credentials + pub participant_name: Option, + + /// The identity of the participant being requested for this client when generating credentials + pub participant_identity: Option, + + /// Any participant metadata being included along with the credentials generation operation + pub participant_metadata: Option, + + /// Any participant attributes being included along with the credentials generation operation + pub participant_attributes: HashMap, + + /// A RoomConfiguration object can be passed to request extra parameters should be included when + /// generating connection credentials - dispatching agents, defining egress settings, etc + /// More info: + pub room_config: Option, +} + +impl From for proto::TokenSourceRequest { + fn from(value: TokenSourceRequest) -> Self { + proto::TokenSourceRequest { + room_name: value.room_name, + participant_name: value.participant_name, + participant_identity: value.participant_identity, + participant_metadata: value.participant_metadata, + participant_attributes: value.participant_attributes, + room_config: value.room_config, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct TokenSourceResponse { + pub server_url: String, + pub participant_token: String, +} + +impl From for TokenSourceResponse { + fn from(value: proto::TokenSourceResponse) -> Self { + TokenSourceResponse { + server_url: value.server_url, + participant_token: value.participant_token, + } + } +} diff --git a/livekit/src/room/token_source/token_response_cache.rs b/livekit/src/room/token_source/token_response_cache.rs new file mode 100644 index 000000000..0a8e5aaa9 --- /dev/null +++ b/livekit/src/room/token_source/token_response_cache.rs @@ -0,0 +1,38 @@ +use crate::token_source::{TokenSourceFetchOptions, TokenSourceResponse}; + +pub trait TokenResponseCacheValue {} + +impl TokenResponseCacheValue for TokenSourceResponse {} +impl TokenResponseCacheValue for (TokenSourceFetchOptions, TokenSourceResponse) {} + +/// Represents a mechanism by which token responses can be cached +/// +/// When used with a TokenSourceFixed, `Value` is `TokenSourceResponse` +/// When used with a TokenSourceConfigurable, `Value` is `(TokenSourceFetchOptions, TokenSourceResponse)` +pub trait TokenResponseCache { + fn get(&self) -> Option<&Value>; + fn set(&mut self, value: Value); + fn clear(&mut self); +} + +/// In-memory implementation of [TokenResponseCache] +pub struct TokenResponseInMemoryCache(Option); +impl TokenResponseInMemoryCache { + pub fn new() -> Self { + Self(None) + } +} + +impl TokenResponseCache + for TokenResponseInMemoryCache +{ + fn get(&self) -> Option<&Value> { + self.0.as_ref() + } + fn set(&mut self, value: Value) { + self.0 = Some(value); + } + fn clear(&mut self) { + self.0 = None; + } +} diff --git a/livekit/src/room/token_source/traits.rs b/livekit/src/room/token_source/traits.rs new file mode 100644 index 000000000..748b5ee52 --- /dev/null +++ b/livekit/src/room/token_source/traits.rs @@ -0,0 +1,69 @@ +// Copyright 2025 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::future::{ready, Future}; + +use crate::token_source::{TokenSourceFetchOptions, TokenSourceResponse, TokenSourceResult}; + +/// A Fixed TokenSource is a token source that takes no parameters and returns a completely +/// independently derived value on each fetch() call. +/// +/// The most common downstream implementer is TokenSourceLiteral. +pub trait TokenSourceFixed { + fn fetch(&self) -> impl Future>; +} + +/// A helper trait to more easily implement a TokenSourceFixed which not async. +pub trait TokenSourceFixedSynchronous { + fn fetch_synchronous(&self) -> TokenSourceResult; +} + +impl TokenSourceFixed for T { + fn fetch(&self) -> impl Future> { + ready(self.fetch_synchronous()) + } +} + +/// A Configurable TokenSource is a token source that takes a +/// TokenSourceFetchOptions object as input and returns a deterministic +/// TokenSourceResponseObject output based on the options specified. +/// +/// For example, if options.participantName is set, it should be expected that +/// all tokens that are generated will have participant name field set to the +/// provided value. +/// +/// A few common downstream implementers are TokenSourceEndpoint and TokenSourceCustom. +pub trait TokenSourceConfigurable { + fn fetch( + &self, + options: &TokenSourceFetchOptions, + ) -> impl Future>; +} + +/// A helper trait to more easily implement a TokenSourceConfigurable which not async. +pub trait TokenSourceConfigurableSynchronous { + fn fetch_synchronous( + &self, + options: &TokenSourceFetchOptions, + ) -> TokenSourceResult; +} + +impl TokenSourceConfigurable for T { + fn fetch( + &self, + options: &TokenSourceFetchOptions, + ) -> impl Future> { + ready(self.fetch_synchronous(options)) + } +}