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::{access_token::VideoGrants, get_env_keys, services::twirp_client::TwirpClient};
22
23const SVC: &str = "Connector";
24
25#[derive(Default, Clone, Debug)]
27pub struct DialWhatsAppCallOptions {
28 pub biz_opaque_callback_data: Option<String>,
30 pub room_name: Option<String>,
32 pub agents: Option<Vec<proto::RoomAgentDispatch>>,
34 pub participant_identity: Option<String>,
36 pub participant_name: Option<String>,
38 pub participant_metadata: Option<String>,
40 pub participant_attributes: Option<HashMap<String, String>>,
42 pub destination_country: Option<String>,
44}
45
46#[derive(Default, Clone, Debug)]
48pub struct AcceptWhatsAppCallOptions {
49 pub biz_opaque_callback_data: Option<String>,
51 pub room_name: Option<String>,
53 pub agents: Option<Vec<proto::RoomAgentDispatch>>,
55 pub participant_identity: Option<String>,
57 pub participant_name: Option<String>,
59 pub participant_metadata: Option<String>,
61 pub participant_attributes: Option<HashMap<String, String>>,
63 pub destination_country: Option<String>,
65 pub wait_until_answered: Option<bool>,
67 pub timeout: Option<Duration>,
70}
71
72#[derive(Default, Clone, Debug)]
74pub struct ConnectTwilioCallOptions {
75 pub agents: Option<Vec<proto::RoomAgentDispatch>>,
77 pub participant_identity: Option<String>,
79 pub participant_name: Option<String>,
81 pub participant_metadata: Option<String>,
83 pub participant_attributes: Option<HashMap<String, String>>,
85 pub destination_country: Option<String>,
87}
88
89#[derive(Debug)]
90pub struct ConnectorClient {
91 base: ServiceBase,
92 client: TwirpClient,
93}
94
95impl ConnectorClient {
96 pub fn with_api_key(host: &str, api_key: &str, api_secret: &str) -> Self {
98 Self::build(
99 host,
100 ServiceBase::with_api_key(api_key, api_secret),
101 crate::http_client::Client::new(),
102 )
103 }
104
105 pub fn with_token(host: &str, token: &str) -> Self {
107 Self::build(host, ServiceBase::with_token(token), crate::http_client::Client::new())
108 }
109
110 pub(crate) fn build(host: &str, base: ServiceBase, client: crate::http_client::Client) -> Self {
113 Self { base, client: TwirpClient::with_client(host, LIVEKIT_PACKAGE, None, client) }
114 }
115
116 #[cfg(test)]
117 pub(crate) fn with_default_headers(mut self, headers: http::HeaderMap) -> Self {
118 self.client = self.client.with_default_headers(headers);
119 self
120 }
121
122 pub fn new(host: &str) -> ServiceResult<Self> {
125 let (api_key, api_secret) = get_env_keys()?;
126 Ok(Self::with_api_key(host, &api_key, &api_secret))
127 }
128
129 pub fn with_failover(mut self, enabled: bool) -> Self {
132 self.client = self.client.with_failover(enabled);
133 self
134 }
135
136 pub fn with_request_timeout(mut self, timeout: std::time::Duration) -> Self {
138 self.client = self.client.with_request_timeout(timeout);
139 self
140 }
141
142 pub async fn dial_whatsapp_call(
154 &self,
155 phone_number_id: impl Into<String>,
156 to_phone_number: impl Into<String>,
157 api_key: impl Into<String>,
158 cloud_api_version: impl Into<String>,
159 options: DialWhatsAppCallOptions,
160 ) -> ServiceResult<proto::DialWhatsAppCallResponse> {
161 self.client
162 .request(
163 SVC,
164 "DialWhatsAppCall",
165 proto::DialWhatsAppCallRequest {
166 whatsapp_phone_number_id: phone_number_id.into(),
167 whatsapp_to_phone_number: to_phone_number.into(),
168 whatsapp_api_key: api_key.into(),
169 whatsapp_cloud_api_version: cloud_api_version.into(),
170 whatsapp_biz_opaque_callback_data: options
171 .biz_opaque_callback_data
172 .unwrap_or_default(),
173 room_name: options.room_name.unwrap_or_default(),
174 agents: options.agents.unwrap_or_default(),
175 participant_identity: options.participant_identity.unwrap_or_default(),
176 participant_name: options.participant_name.unwrap_or_default(),
177 participant_metadata: options.participant_metadata.unwrap_or_default(),
178 participant_attributes: options.participant_attributes.unwrap_or_default(),
179 destination_country: options.destination_country.unwrap_or_default(),
180 ringing_timeout: Default::default(),
181 },
182 self.base
183 .auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?,
184 )
185 .await
186 .map_err(Into::into)
187 }
188
189 pub async fn disconnect_whatsapp_call(
204 &self,
205 call_id: impl Into<String>,
206 api_key: impl Into<String>,
207 ) -> ServiceResult<proto::DisconnectWhatsAppCallResponse> {
208 self.disconnect_whatsapp_call_with_reason(
209 call_id,
210 api_key,
211 proto::disconnect_whats_app_call_request::DisconnectReason::BusinessInitiated,
212 )
213 .await
214 }
215
216 pub async fn disconnect_whatsapp_call_with_reason(
227 &self,
228 call_id: impl Into<String>,
229 api_key: impl Into<String>,
230 reason: proto::disconnect_whats_app_call_request::DisconnectReason,
231 ) -> ServiceResult<proto::DisconnectWhatsAppCallResponse> {
232 self.client
233 .request(
234 SVC,
235 "DisconnectWhatsAppCall",
236 proto::DisconnectWhatsAppCallRequest {
237 whatsapp_call_id: call_id.into(),
238 whatsapp_api_key: api_key.into(),
239 disconnect_reason: reason as i32,
240 },
241 self.base
242 .auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?,
243 )
244 .await
245 .map_err(Into::into)
246 }
247
248 pub async fn connect_whatsapp_call(
257 &self,
258 call_id: impl Into<String>,
259 sdp: proto::SessionDescription,
260 ) -> ServiceResult<proto::ConnectWhatsAppCallResponse> {
261 self.client
262 .request(
263 SVC,
264 "ConnectWhatsAppCall",
265 proto::ConnectWhatsAppCallRequest {
266 whatsapp_call_id: call_id.into(),
267 sdp: Some(sdp),
268 ..Default::default()
269 },
270 self.base
271 .auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?,
272 )
273 .await
274 .map_err(Into::into)
275 }
276
277 pub async fn accept_whatsapp_call(
290 &self,
291 phone_number_id: impl Into<String>,
292 api_key: impl Into<String>,
293 cloud_api_version: impl Into<String>,
294 call_id: impl Into<String>,
295 sdp: proto::SessionDescription,
296 options: AcceptWhatsAppCallOptions,
297 ) -> ServiceResult<proto::AcceptWhatsAppCallResponse> {
298 let wait_until_answered = options.wait_until_answered.unwrap_or(false);
299 let request = proto::AcceptWhatsAppCallRequest {
300 whatsapp_phone_number_id: phone_number_id.into(),
301 whatsapp_api_key: api_key.into(),
302 whatsapp_cloud_api_version: cloud_api_version.into(),
303 whatsapp_call_id: call_id.into(),
304 whatsapp_biz_opaque_callback_data: options.biz_opaque_callback_data.unwrap_or_default(),
305 sdp: Some(sdp),
306 room_name: options.room_name.unwrap_or_default(),
307 agents: options.agents.unwrap_or_default(),
308 participant_identity: options.participant_identity.unwrap_or_default(),
309 participant_name: options.participant_name.unwrap_or_default(),
310 participant_metadata: options.participant_metadata.unwrap_or_default(),
311 participant_attributes: options.participant_attributes.unwrap_or_default(),
312 destination_country: options.destination_country.unwrap_or_default(),
313 ringing_timeout: None,
314 wait_until_answered,
315 };
316 let headers =
317 self.base.auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?;
318
319 let timeout = if wait_until_answered {
323 Some(options.timeout.unwrap_or(DEFAULT_RINGING_TIMEOUT))
324 } else {
325 options.timeout
326 };
327 match timeout {
328 Some(timeout) => self
329 .client
330 .request_with_timeout(SVC, "AcceptWhatsAppCall", request, headers, timeout)
331 .await
332 .map_err(Into::into),
333 None => self
334 .client
335 .request(SVC, "AcceptWhatsAppCall", request, headers)
336 .await
337 .map_err(Into::into),
338 }
339 }
340
341 pub async fn connect_twilio_call(
351 &self,
352 direction: proto::connect_twilio_call_request::TwilioCallDirection,
353 room_name: impl Into<String>,
354 options: ConnectTwilioCallOptions,
355 ) -> ServiceResult<proto::ConnectTwilioCallResponse> {
356 self.client
357 .request(
358 SVC,
359 "ConnectTwilioCall",
360 proto::ConnectTwilioCallRequest {
361 twilio_call_direction: direction as i32,
362 room_name: room_name.into(),
363 agents: options.agents.unwrap_or_default(),
364 participant_identity: options.participant_identity.unwrap_or_default(),
365 participant_name: options.participant_name.unwrap_or_default(),
366 participant_metadata: options.participant_metadata.unwrap_or_default(),
367 participant_attributes: options.participant_attributes.unwrap_or_default(),
368 destination_country: options.destination_country.unwrap_or_default(),
369 },
370 self.base
371 .auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?,
372 )
373 .await
374 .map_err(Into::into)
375 }
376}