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 },
269 self.base
270 .auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?,
271 )
272 .await
273 .map_err(Into::into)
274 }
275
276 pub async fn accept_whatsapp_call(
289 &self,
290 phone_number_id: impl Into<String>,
291 api_key: impl Into<String>,
292 cloud_api_version: impl Into<String>,
293 call_id: impl Into<String>,
294 sdp: proto::SessionDescription,
295 options: AcceptWhatsAppCallOptions,
296 ) -> ServiceResult<proto::AcceptWhatsAppCallResponse> {
297 let wait_until_answered = options.wait_until_answered.unwrap_or(false);
298 let request = proto::AcceptWhatsAppCallRequest {
299 whatsapp_phone_number_id: phone_number_id.into(),
300 whatsapp_api_key: api_key.into(),
301 whatsapp_cloud_api_version: cloud_api_version.into(),
302 whatsapp_call_id: call_id.into(),
303 whatsapp_biz_opaque_callback_data: options.biz_opaque_callback_data.unwrap_or_default(),
304 sdp: Some(sdp),
305 room_name: options.room_name.unwrap_or_default(),
306 agents: options.agents.unwrap_or_default(),
307 participant_identity: options.participant_identity.unwrap_or_default(),
308 participant_name: options.participant_name.unwrap_or_default(),
309 participant_metadata: options.participant_metadata.unwrap_or_default(),
310 participant_attributes: options.participant_attributes.unwrap_or_default(),
311 destination_country: options.destination_country.unwrap_or_default(),
312 ringing_timeout: None,
313 wait_until_answered,
314 };
315 let headers =
316 self.base.auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?;
317
318 let timeout = if wait_until_answered {
322 Some(options.timeout.unwrap_or(DEFAULT_RINGING_TIMEOUT))
323 } else {
324 options.timeout
325 };
326 match timeout {
327 Some(timeout) => self
328 .client
329 .request_with_timeout(SVC, "AcceptWhatsAppCall", request, headers, timeout)
330 .await
331 .map_err(Into::into),
332 None => self
333 .client
334 .request(SVC, "AcceptWhatsAppCall", request, headers)
335 .await
336 .map_err(Into::into),
337 }
338 }
339
340 pub async fn connect_twilio_call(
350 &self,
351 direction: proto::connect_twilio_call_request::TwilioCallDirection,
352 room_name: impl Into<String>,
353 options: ConnectTwilioCallOptions,
354 ) -> ServiceResult<proto::ConnectTwilioCallResponse> {
355 self.client
356 .request(
357 SVC,
358 "ConnectTwilioCall",
359 proto::ConnectTwilioCallRequest {
360 twilio_call_direction: direction as i32,
361 room_name: room_name.into(),
362 agents: options.agents.unwrap_or_default(),
363 participant_identity: options.participant_identity.unwrap_or_default(),
364 participant_name: options.participant_name.unwrap_or_default(),
365 participant_metadata: options.participant_metadata.unwrap_or_default(),
366 participant_attributes: options.participant_attributes.unwrap_or_default(),
367 destination_country: options.destination_country.unwrap_or_default(),
368 },
369 self.base
370 .auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?,
371 )
372 .await
373 .map_err(Into::into)
374 }
375}