1use livekit_protocol as proto;
16use std::collections::HashMap;
17use std::time::Duration;
18
19use super::{ServiceBase, ServiceResult, LIVEKIT_PACKAGE};
20use crate::services::dial_timeout::DEFAULT_RINGING_TIMEOUT;
21use crate::services::twirp_client::TwirpClient;
22use livekit_token::{get_env_keys, VideoGrants};
23
24const SVC: &str = "Connector";
25
26#[derive(Default, Clone, Debug)]
28pub struct DialWhatsAppCallOptions {
29 pub biz_opaque_callback_data: Option<String>,
31 pub room_name: Option<String>,
33 pub agents: Option<Vec<proto::RoomAgentDispatch>>,
35 pub participant_identity: Option<String>,
37 pub participant_name: Option<String>,
39 pub participant_metadata: Option<String>,
41 pub participant_attributes: Option<HashMap<String, String>>,
43 pub destination_country: Option<String>,
45}
46
47#[derive(Default, Clone, Debug)]
49pub struct AcceptWhatsAppCallOptions {
50 pub biz_opaque_callback_data: Option<String>,
52 pub room_name: Option<String>,
54 pub agents: Option<Vec<proto::RoomAgentDispatch>>,
56 pub participant_identity: Option<String>,
58 pub participant_name: Option<String>,
60 pub participant_metadata: Option<String>,
62 pub participant_attributes: Option<HashMap<String, String>>,
64 pub destination_country: Option<String>,
66 pub wait_until_answered: Option<bool>,
68 pub timeout: Option<Duration>,
71}
72
73#[derive(Default, Clone, Debug)]
75pub struct ConnectTwilioCallOptions {
76 pub agents: Option<Vec<proto::RoomAgentDispatch>>,
78 pub participant_identity: Option<String>,
80 pub participant_name: Option<String>,
82 pub participant_metadata: Option<String>,
84 pub participant_attributes: Option<HashMap<String, String>>,
86 pub destination_country: Option<String>,
88}
89
90#[derive(Debug)]
91pub struct ConnectorClient {
92 base: ServiceBase,
93 client: TwirpClient,
94}
95
96impl ConnectorClient {
97 pub fn with_api_key(host: &str, api_key: &str, api_secret: &str) -> Self {
99 Self::build(
100 host,
101 ServiceBase::with_api_key(api_key, api_secret),
102 crate::http_client::Client::new(),
103 )
104 }
105
106 pub fn with_token(host: &str, token: &str) -> Self {
108 Self::build(host, ServiceBase::with_token(token), crate::http_client::Client::new())
109 }
110
111 pub(crate) fn build(host: &str, base: ServiceBase, client: crate::http_client::Client) -> Self {
114 Self { base, client: TwirpClient::with_client(host, LIVEKIT_PACKAGE, None, client) }
115 }
116
117 #[cfg(test)]
118 pub(crate) fn with_default_headers(mut self, headers: http::HeaderMap) -> Self {
119 self.client = self.client.with_default_headers(headers);
120 self
121 }
122
123 pub fn new(host: &str) -> ServiceResult<Self> {
126 let (api_key, api_secret) = get_env_keys()?;
127 Ok(Self::with_api_key(host, &api_key, &api_secret))
128 }
129
130 pub fn with_failover(mut self, enabled: bool) -> Self {
133 self.client = self.client.with_failover(enabled);
134 self
135 }
136
137 pub fn with_request_timeout(mut self, timeout: std::time::Duration) -> Self {
139 self.client = self.client.with_request_timeout(timeout);
140 self
141 }
142
143 pub async fn dial_whatsapp_call(
155 &self,
156 phone_number_id: impl Into<String>,
157 to_phone_number: impl Into<String>,
158 api_key: impl Into<String>,
159 cloud_api_version: impl Into<String>,
160 options: DialWhatsAppCallOptions,
161 ) -> ServiceResult<proto::DialWhatsAppCallResponse> {
162 self.client
163 .request(
164 SVC,
165 "DialWhatsAppCall",
166 proto::DialWhatsAppCallRequest {
167 whatsapp_phone_number_id: phone_number_id.into(),
168 whatsapp_to_phone_number: to_phone_number.into(),
169 whatsapp_api_key: api_key.into(),
170 whatsapp_cloud_api_version: cloud_api_version.into(),
171 whatsapp_biz_opaque_callback_data: options
172 .biz_opaque_callback_data
173 .unwrap_or_default(),
174 room_name: options.room_name.unwrap_or_default(),
175 agents: options.agents.unwrap_or_default(),
176 participant_identity: options.participant_identity.unwrap_or_default(),
177 participant_name: options.participant_name.unwrap_or_default(),
178 participant_metadata: options.participant_metadata.unwrap_or_default(),
179 participant_attributes: options.participant_attributes.unwrap_or_default(),
180 destination_country: options.destination_country.unwrap_or_default(),
181 ringing_timeout: Default::default(),
182 },
183 self.base
184 .auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?,
185 )
186 .await
187 .map_err(Into::into)
188 }
189
190 pub async fn disconnect_whatsapp_call(
205 &self,
206 call_id: impl Into<String>,
207 api_key: impl Into<String>,
208 ) -> ServiceResult<proto::DisconnectWhatsAppCallResponse> {
209 self.disconnect_whatsapp_call_with_reason(
210 call_id,
211 api_key,
212 proto::disconnect_whats_app_call_request::DisconnectReason::BusinessInitiated,
213 )
214 .await
215 }
216
217 pub async fn disconnect_whatsapp_call_with_reason(
228 &self,
229 call_id: impl Into<String>,
230 api_key: impl Into<String>,
231 reason: proto::disconnect_whats_app_call_request::DisconnectReason,
232 ) -> ServiceResult<proto::DisconnectWhatsAppCallResponse> {
233 self.client
234 .request(
235 SVC,
236 "DisconnectWhatsAppCall",
237 proto::DisconnectWhatsAppCallRequest {
238 whatsapp_call_id: call_id.into(),
239 whatsapp_api_key: api_key.into(),
240 disconnect_reason: reason as i32,
241 },
242 self.base
243 .auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?,
244 )
245 .await
246 .map_err(Into::into)
247 }
248
249 pub async fn connect_whatsapp_call(
258 &self,
259 call_id: impl Into<String>,
260 sdp: proto::SessionDescription,
261 ) -> ServiceResult<proto::ConnectWhatsAppCallResponse> {
262 self.client
263 .request(
264 SVC,
265 "ConnectWhatsAppCall",
266 proto::ConnectWhatsAppCallRequest {
267 whatsapp_call_id: call_id.into(),
268 sdp: Some(sdp),
269 ..Default::default()
270 },
271 self.base
272 .auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?,
273 )
274 .await
275 .map_err(Into::into)
276 }
277
278 pub async fn accept_whatsapp_call(
291 &self,
292 phone_number_id: impl Into<String>,
293 api_key: impl Into<String>,
294 cloud_api_version: impl Into<String>,
295 call_id: impl Into<String>,
296 sdp: proto::SessionDescription,
297 options: AcceptWhatsAppCallOptions,
298 ) -> ServiceResult<proto::AcceptWhatsAppCallResponse> {
299 let wait_until_answered = options.wait_until_answered.unwrap_or(false);
300 let request = proto::AcceptWhatsAppCallRequest {
301 whatsapp_phone_number_id: phone_number_id.into(),
302 whatsapp_api_key: api_key.into(),
303 whatsapp_cloud_api_version: cloud_api_version.into(),
304 whatsapp_call_id: call_id.into(),
305 whatsapp_biz_opaque_callback_data: options.biz_opaque_callback_data.unwrap_or_default(),
306 sdp: Some(sdp),
307 room_name: options.room_name.unwrap_or_default(),
308 agents: options.agents.unwrap_or_default(),
309 participant_identity: options.participant_identity.unwrap_or_default(),
310 participant_name: options.participant_name.unwrap_or_default(),
311 participant_metadata: options.participant_metadata.unwrap_or_default(),
312 participant_attributes: options.participant_attributes.unwrap_or_default(),
313 destination_country: options.destination_country.unwrap_or_default(),
314 ringing_timeout: None,
315 wait_until_answered,
316 };
317 let headers =
318 self.base.auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?;
319
320 let timeout = if wait_until_answered {
324 Some(options.timeout.unwrap_or(DEFAULT_RINGING_TIMEOUT))
325 } else {
326 options.timeout
327 };
328 match timeout {
329 Some(timeout) => self
330 .client
331 .request_with_timeout(SVC, "AcceptWhatsAppCall", request, headers, timeout)
332 .await
333 .map_err(Into::into),
334 None => self
335 .client
336 .request(SVC, "AcceptWhatsAppCall", request, headers)
337 .await
338 .map_err(Into::into),
339 }
340 }
341
342 pub async fn connect_twilio_call(
352 &self,
353 direction: proto::connect_twilio_call_request::TwilioCallDirection,
354 room_name: impl Into<String>,
355 options: ConnectTwilioCallOptions,
356 ) -> ServiceResult<proto::ConnectTwilioCallResponse> {
357 self.client
358 .request(
359 SVC,
360 "ConnectTwilioCall",
361 proto::ConnectTwilioCallRequest {
362 twilio_call_direction: direction as i32,
363 room_name: room_name.into(),
364 agents: options.agents.unwrap_or_default(),
365 participant_identity: options.participant_identity.unwrap_or_default(),
366 participant_name: options.participant_name.unwrap_or_default(),
367 participant_metadata: options.participant_metadata.unwrap_or_default(),
368 participant_attributes: options.participant_attributes.unwrap_or_default(),
369 destination_country: options.destination_country.unwrap_or_default(),
370 },
371 self.base
372 .auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?,
373 )
374 .await
375 .map_err(Into::into)
376 }
377}