use livekit_protocol as proto;
use std::collections::HashMap;
use std::time::Duration;
use crate::access_token::{SIPGrants, VideoGrants};
use crate::get_env_keys;
use crate::services::dial_timeout::{dial_timeout, DEFAULT_RINGING_TIMEOUT};
use crate::services::twirp_client::TwirpClient;
use crate::services::{ServiceBase, ServiceResult, LIVEKIT_PACKAGE};
use pbjson_types::Duration as ProtoDuration;
const SVC: &str = "SIP";
#[derive(Debug)]
pub struct SIPClient {
base: ServiceBase,
client: TwirpClient,
}
#[deprecated]
#[derive(Default, Clone, Debug)]
pub struct CreateSIPTrunkOptions {
pub name: String,
pub metadata: String,
pub inbound_addresses: Vec<String>,
pub inbound_numbers: Vec<String>,
pub inbound_username: String,
pub inbound_password: String,
pub outbound_address: String,
pub outbound_username: String,
pub outbound_password: String,
}
#[derive(Default, Clone, Debug)]
pub struct CreateSIPInboundTrunkOptions {
pub metadata: Option<String>,
pub allowed_addresses: Option<Vec<String>>,
pub allowed_numbers: Option<Vec<String>>,
pub auth_username: Option<String>,
pub auth_password: Option<String>,
pub headers: Option<HashMap<String, String>>,
pub headers_to_attributes: Option<HashMap<String, String>>,
pub attributes_to_headers: Option<HashMap<String, String>>,
pub max_call_duration: Option<Duration>,
pub ringing_timeout: Option<Duration>,
pub krisp_enabled: Option<bool>,
pub auth_realm: Option<String>,
}
#[derive(Default, Clone, Debug)]
pub struct CreateSIPOutboundTrunkOptions {
pub transport: proto::SipTransport,
pub metadata: String,
pub auth_username: String,
pub auth_password: String,
pub headers: Option<HashMap<String, String>>,
pub headers_to_attributes: Option<HashMap<String, String>>,
pub attributes_to_headers: Option<HashMap<String, String>>,
}
#[deprecated]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ListSIPTrunkFilter {
All,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ListSIPInboundTrunkFilter {
All,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ListSIPOutboundTrunkFilter {
All,
}
#[derive(Default, Clone, Debug)]
pub struct CreateSIPDispatchRuleOptions {
pub name: String,
pub metadata: String,
pub attributes: HashMap<String, String>,
pub trunk_ids: Vec<String>,
pub allowed_numbers: Vec<String>,
pub hide_phone_number: bool,
pub room_config: Option<proto::RoomConfiguration>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ListSIPDispatchRuleFilter {
All,
}
#[derive(Default, Clone, Debug)]
pub struct CreateSIPParticipantOptions {
pub participant_identity: String,
pub participant_name: Option<String>,
pub participant_metadata: Option<String>,
pub participant_attributes: Option<HashMap<String, String>>,
pub display_name: Option<String>,
pub sip_number: Option<String>,
pub dtmf: Option<String>,
pub wait_until_answered: Option<bool>,
pub play_dialtone: Option<bool>,
pub hide_phone_number: Option<bool>,
pub ringing_timeout: Option<Duration>,
pub max_call_duration: Option<Duration>,
pub enable_krisp: Option<bool>,
pub headers: Option<HashMap<String, String>>,
pub include_headers: Option<proto::SipHeaderOptions>,
pub media_encryption: Option<proto::SipMediaEncryption>,
pub timeout: Option<Duration>,
}
#[derive(Default, Clone, Debug)]
pub struct TransferSIPParticipantOptions {
pub play_dialtone: Option<bool>,
pub ringing_timeout: Option<Duration>,
pub headers: Option<HashMap<String, String>>,
pub timeout: Option<Duration>,
}
impl SIPClient {
pub fn with_api_key(host: &str, api_key: &str, api_secret: &str) -> Self {
Self::build(
host,
ServiceBase::with_api_key(api_key, api_secret),
crate::http_client::Client::new(),
)
}
pub fn with_token(host: &str, token: &str) -> Self {
Self::build(host, ServiceBase::with_token(token), crate::http_client::Client::new())
}
pub(crate) fn build(host: &str, base: ServiceBase, client: crate::http_client::Client) -> Self {
Self { base, client: TwirpClient::with_client(host, LIVEKIT_PACKAGE, None, client) }
}
#[cfg(test)]
pub(crate) fn with_default_headers(mut self, headers: http::HeaderMap) -> Self {
self.client = self.client.with_default_headers(headers);
self
}
pub fn new(host: &str) -> ServiceResult<Self> {
let (api_key, api_secret) = get_env_keys()?;
Ok(Self::with_api_key(host, &api_key, &api_secret))
}
pub fn with_failover(mut self, enabled: bool) -> Self {
self.client = self.client.with_failover(enabled);
self
}
pub fn with_request_timeout(mut self, timeout: Duration) -> Self {
self.client = self.client.with_request_timeout(timeout);
self
}
fn duration_to_proto(d: Option<Duration>) -> Option<ProtoDuration> {
d.map(|d| ProtoDuration { seconds: d.as_secs() as i64, nanos: d.subsec_nanos() as i32 })
}
pub async fn create_sip_inbound_trunk(
&self,
name: String,
numbers: Vec<String>,
options: CreateSIPInboundTrunkOptions,
) -> ServiceResult<proto::SipInboundTrunkInfo> {
self.client
.request(
SVC,
"CreateSIPInboundTrunk",
proto::CreateSipInboundTrunkRequest {
trunk: Some(proto::SipInboundTrunkInfo {
sip_trunk_id: Default::default(),
name,
numbers,
metadata: options.metadata.unwrap_or_default(),
allowed_numbers: options.allowed_numbers.unwrap_or_default(),
allowed_addresses: options.allowed_addresses.unwrap_or_default(),
auth_username: options.auth_username.unwrap_or_default(),
auth_password: options.auth_password.unwrap_or_default(),
auth_realm: options.auth_realm.unwrap_or_default(),
headers: options.headers.unwrap_or_default(),
headers_to_attributes: options.headers_to_attributes.unwrap_or_default(),
attributes_to_headers: options.attributes_to_headers.unwrap_or_default(),
krisp_enabled: options.krisp_enabled.unwrap_or(false),
max_call_duration: Self::duration_to_proto(options.max_call_duration),
ringing_timeout: Self::duration_to_proto(options.ringing_timeout),
include_headers: Default::default(),
media_encryption: Default::default(),
created_at: Default::default(),
updated_at: Default::default(),
media: Default::default(),
}),
},
self.base.auth_header(
Default::default(),
Some(SIPGrants { admin: true, ..Default::default() }),
)?,
)
.await
.map_err(Into::into)
}
pub async fn create_sip_outbound_trunk(
&self,
name: String,
address: String,
numbers: Vec<String>,
options: CreateSIPOutboundTrunkOptions,
) -> ServiceResult<proto::SipOutboundTrunkInfo> {
self.client
.request(
SVC,
"CreateSIPOutboundTrunk",
proto::CreateSipOutboundTrunkRequest {
trunk: Some(proto::SipOutboundTrunkInfo {
sip_trunk_id: Default::default(),
name,
address,
numbers,
transport: options.transport as i32,
metadata: options.metadata,
auth_username: options.auth_username.to_owned(),
auth_password: options.auth_password.to_owned(),
headers: options.headers.unwrap_or_default(),
headers_to_attributes: options.headers_to_attributes.unwrap_or_default(),
attributes_to_headers: options.attributes_to_headers.unwrap_or_default(),
include_headers: Default::default(),
media_encryption: Default::default(),
destination_country: Default::default(),
created_at: Default::default(),
updated_at: Default::default(),
from_host: Default::default(),
media: Default::default(),
}),
},
self.base.auth_header(
Default::default(),
Some(SIPGrants { admin: true, ..Default::default() }),
)?,
)
.await
.map_err(Into::into)
}
pub async fn update_sip_inbound_trunk(
&self,
trunk_id: String,
update: proto::SipInboundTrunkUpdate,
) -> ServiceResult<proto::SipInboundTrunkInfo> {
self.client
.request(
SVC,
"UpdateSIPInboundTrunk",
proto::UpdateSipInboundTrunkRequest {
sip_trunk_id: trunk_id,
action: Some(proto::update_sip_inbound_trunk_request::Action::Update(update)),
},
self.base.auth_header(
Default::default(),
Some(SIPGrants { admin: true, ..Default::default() }),
)?,
)
.await
.map_err(Into::into)
}
pub async fn update_sip_inbound_trunk_replace(
&self,
trunk_id: String,
trunk: proto::SipInboundTrunkInfo,
) -> ServiceResult<proto::SipInboundTrunkInfo> {
self.client
.request(
SVC,
"UpdateSIPInboundTrunk",
proto::UpdateSipInboundTrunkRequest {
sip_trunk_id: trunk_id,
action: Some(proto::update_sip_inbound_trunk_request::Action::Replace(trunk)),
},
self.base.auth_header(
Default::default(),
Some(SIPGrants { admin: true, ..Default::default() }),
)?,
)
.await
.map_err(Into::into)
}
pub async fn update_sip_outbound_trunk(
&self,
trunk_id: String,
update: proto::SipOutboundTrunkUpdate,
) -> ServiceResult<proto::SipOutboundTrunkInfo> {
self.client
.request(
SVC,
"UpdateSIPOutboundTrunk",
proto::UpdateSipOutboundTrunkRequest {
sip_trunk_id: trunk_id,
action: Some(proto::update_sip_outbound_trunk_request::Action::Update(update)),
},
self.base.auth_header(
Default::default(),
Some(SIPGrants { admin: true, ..Default::default() }),
)?,
)
.await
.map_err(Into::into)
}
pub async fn update_sip_outbound_trunk_replace(
&self,
trunk_id: String,
trunk: proto::SipOutboundTrunkInfo,
) -> ServiceResult<proto::SipOutboundTrunkInfo> {
self.client
.request(
SVC,
"UpdateSIPOutboundTrunk",
proto::UpdateSipOutboundTrunkRequest {
sip_trunk_id: trunk_id,
action: Some(proto::update_sip_outbound_trunk_request::Action::Replace(trunk)),
},
self.base.auth_header(
Default::default(),
Some(SIPGrants { admin: true, ..Default::default() }),
)?,
)
.await
.map_err(Into::into)
}
#[deprecated]
pub async fn list_sip_trunk(
&self,
filter: ListSIPTrunkFilter,
) -> ServiceResult<Vec<proto::SipTrunkInfo>> {
let resp: proto::ListSipTrunkResponse = self
.client
.request(
SVC,
"ListSIPTrunk",
proto::ListSipTrunkRequest {
page: Default::default(),
},
self.base.auth_header(
Default::default(),
Some(SIPGrants { admin: true, ..Default::default() }),
)?,
)
.await?;
Ok(resp.items)
}
pub async fn list_sip_inbound_trunk(
&self,
filter: ListSIPInboundTrunkFilter,
) -> ServiceResult<Vec<proto::SipInboundTrunkInfo>> {
let resp: proto::ListSipInboundTrunkResponse = self
.client
.request(
SVC,
"ListSIPInboundTrunk",
proto::ListSipInboundTrunkRequest {
page: Default::default(),
trunk_ids: Default::default(),
numbers: Default::default(),
},
self.base.auth_header(
Default::default(),
Some(SIPGrants { admin: true, ..Default::default() }),
)?,
)
.await?;
Ok(resp.items)
}
pub async fn list_sip_outbound_trunk(
&self,
filter: ListSIPOutboundTrunkFilter,
) -> ServiceResult<Vec<proto::SipOutboundTrunkInfo>> {
let resp: proto::ListSipOutboundTrunkResponse = self
.client
.request(
SVC,
"ListSIPOutboundTrunk",
proto::ListSipOutboundTrunkRequest {
page: Default::default(),
trunk_ids: Default::default(),
numbers: Default::default(),
},
self.base.auth_header(
Default::default(),
Some(SIPGrants { admin: true, ..Default::default() }),
)?,
)
.await?;
Ok(resp.items)
}
pub async fn delete_sip_trunk(&self, sip_trunk_id: &str) -> ServiceResult<proto::SipTrunkInfo> {
self.client
.request(
SVC,
"DeleteSIPTrunk",
proto::DeleteSipTrunkRequest { sip_trunk_id: sip_trunk_id.to_owned() },
self.base.auth_header(
Default::default(),
Some(SIPGrants { admin: true, ..Default::default() }),
)?,
)
.await
.map_err(Into::into)
}
pub async fn create_sip_dispatch_rule(
&self,
rule: proto::sip_dispatch_rule::Rule,
options: CreateSIPDispatchRuleOptions,
) -> ServiceResult<proto::SipDispatchRuleInfo> {
self.client
.request(
SVC,
"CreateSIPDispatchRule",
proto::CreateSipDispatchRuleRequest {
dispatch_rule: Some(proto::SipDispatchRuleInfo {
rule: Some(proto::SipDispatchRule { rule: Some(rule) }),
name: options.name,
metadata: options.metadata,
attributes: options.attributes,
trunk_ids: options.trunk_ids,
inbound_numbers: options.allowed_numbers,
hide_phone_number: options.hide_phone_number,
room_config: options.room_config,
..Default::default()
}),
..Default::default()
},
self.base.auth_header(
Default::default(),
Some(SIPGrants { admin: true, ..Default::default() }),
)?,
)
.await
.map_err(Into::into)
}
pub async fn update_sip_dispatch_rule(
&self,
dispatch_rule_id: String,
update: proto::SipDispatchRuleUpdate,
) -> ServiceResult<proto::SipDispatchRuleInfo> {
self.client
.request(
SVC,
"UpdateSIPDispatchRule",
proto::UpdateSipDispatchRuleRequest {
sip_dispatch_rule_id: dispatch_rule_id,
action: Some(proto::update_sip_dispatch_rule_request::Action::Update(update)),
},
self.base.auth_header(
Default::default(),
Some(SIPGrants { admin: true, ..Default::default() }),
)?,
)
.await
.map_err(Into::into)
}
pub async fn update_sip_dispatch_rule_replace(
&self,
dispatch_rule_id: String,
rule: proto::SipDispatchRuleInfo,
) -> ServiceResult<proto::SipDispatchRuleInfo> {
self.client
.request(
SVC,
"UpdateSIPDispatchRule",
proto::UpdateSipDispatchRuleRequest {
sip_dispatch_rule_id: dispatch_rule_id,
action: Some(proto::update_sip_dispatch_rule_request::Action::Replace(rule)),
},
self.base.auth_header(
Default::default(),
Some(SIPGrants { admin: true, ..Default::default() }),
)?,
)
.await
.map_err(Into::into)
}
pub async fn list_sip_dispatch_rule(
&self,
filter: ListSIPDispatchRuleFilter,
) -> ServiceResult<Vec<proto::SipDispatchRuleInfo>> {
let resp: proto::ListSipDispatchRuleResponse = self
.client
.request(
SVC,
"ListSIPDispatchRule",
proto::ListSipDispatchRuleRequest {
page: Default::default(),
dispatch_rule_ids: Default::default(),
trunk_ids: Default::default(),
},
self.base.auth_header(
Default::default(),
Some(SIPGrants { admin: true, ..Default::default() }),
)?,
)
.await?;
Ok(resp.items)
}
pub async fn delete_sip_dispatch_rule(
&self,
sip_dispatch_rule_id: &str,
) -> ServiceResult<proto::SipDispatchRuleInfo> {
self.client
.request(
SVC,
"DeleteSIPDispatchRule",
proto::DeleteSipDispatchRuleRequest {
sip_dispatch_rule_id: sip_dispatch_rule_id.to_owned(),
},
self.base.auth_header(
Default::default(),
Some(SIPGrants { admin: true, ..Default::default() }),
)?,
)
.await
.map_err(Into::into)
}
pub async fn create_sip_participant(
&self,
sip_trunk_id: String,
call_to: String,
room_name: String,
options: CreateSIPParticipantOptions,
outbound_trunk_config: Option<proto::SipOutboundConfig>,
) -> ServiceResult<proto::SipParticipantInfo> {
let wait_until_answered = options.wait_until_answered.unwrap_or(false);
let user_timeout = options.timeout;
let ringing_timeout =
options.ringing_timeout.or(wait_until_answered.then_some(DEFAULT_RINGING_TIMEOUT));
let request = proto::CreateSipParticipantRequest {
sip_trunk_id: sip_trunk_id.to_owned(),
trunk: outbound_trunk_config,
sip_call_to: call_to.to_owned(),
sip_number: options.sip_number.to_owned().unwrap_or_default(),
room_name: room_name.to_owned(),
participant_identity: options.participant_identity.to_owned(),
participant_name: options.participant_name.to_owned().unwrap_or_default(),
participant_metadata: options.participant_metadata.to_owned().unwrap_or_default(),
participant_attributes: options.participant_attributes.to_owned().unwrap_or_default(),
display_name: options.display_name.to_owned(),
dtmf: options.dtmf.to_owned().unwrap_or_default(),
wait_until_answered,
play_ringtone: options.play_dialtone.unwrap_or(false),
play_dialtone: options.play_dialtone.unwrap_or(false),
hide_phone_number: options.hide_phone_number.unwrap_or(false),
max_call_duration: Self::duration_to_proto(options.max_call_duration),
ringing_timeout: Self::duration_to_proto(ringing_timeout),
krisp_enabled: options.enable_krisp.unwrap_or(false),
headers: options.headers.unwrap_or_default(),
include_headers: options.include_headers.map(|h| h as i32).unwrap_or_default(),
media_encryption: options.media_encryption.map(|e| e as i32).unwrap_or_default(),
..Default::default()
};
let headers = self.base.auth_header(
Default::default(),
Some(SIPGrants { call: true, ..Default::default() }),
)?;
if wait_until_answered {
self.client
.request_with_timeout(
SVC,
"CreateSIPParticipant",
request,
headers,
dial_timeout(user_timeout, ringing_timeout),
)
.await
.map_err(Into::into)
} else if let Some(timeout) = user_timeout {
self.client
.request_with_timeout(SVC, "CreateSIPParticipant", request, headers, timeout)
.await
.map_err(Into::into)
} else {
self.client
.request(SVC, "CreateSIPParticipant", request, headers)
.await
.map_err(Into::into)
}
}
pub async fn transfer_sip_participant(
&self,
room_name: String,
participant_identity: String,
transfer_to: String,
options: TransferSIPParticipantOptions,
) -> ServiceResult<()> {
let ringing_timeout = options.ringing_timeout.or(Some(DEFAULT_RINGING_TIMEOUT));
let request = proto::TransferSipParticipantRequest {
participant_identity,
room_name: room_name.to_owned(),
transfer_to,
play_dialtone: options.play_dialtone.unwrap_or(false),
headers: options.headers.unwrap_or_default(),
ringing_timeout: Self::duration_to_proto(ringing_timeout),
};
let headers = self.base.auth_header(
VideoGrants { room_admin: true, room: room_name, ..Default::default() },
Some(SIPGrants { call: true, ..Default::default() }),
)?;
self.client
.request_with_timeout(
SVC,
"TransferSIPParticipant",
request,
headers,
dial_timeout(options.timeout, ringing_timeout),
)
.await
.map_err(Into::into)
}
}