active_call/useragent/
invitation.rs

1use std::sync::Arc;
2
3use crate::{
4    call::{RoutingState, sip::Invitation},
5    config::InviteHandlerConfig,
6    useragent::{playbook_handler::PlaybookInvitationHandler, webhook::WebhookInvitationHandler},
7};
8use anyhow::{Result, anyhow};
9use async_trait::async_trait;
10use rsipstack::dialog::{
11    DialogId,
12    dialog::{Dialog, DialogStateReceiver},
13    server_dialog::ServerInviteDialog,
14};
15use tokio_util::sync::CancellationToken;
16use tracing::info;
17
18pub struct PendingDialog {
19    pub token: CancellationToken,
20    pub dialog: ServerInviteDialog,
21    pub state_receiver: DialogStateReceiver,
22}
23pub struct PendingDialogGuard {
24    pub id: DialogId,
25    pub invitation: Invitation,
26}
27
28impl PendingDialogGuard {
29    pub fn new(invitation: Invitation, id: DialogId, pending_dialog: PendingDialog) -> Self {
30        invitation.add_pending(id.clone(), pending_dialog);
31        info!(%id, "added pending dialog");
32        Self { id, invitation }
33    }
34
35    fn take_dialog(&self) -> Option<Dialog> {
36        if let Some(pending) = self.invitation.get_pending_call(&self.id) {
37            let dialog_id = pending.dialog.id();
38            match self.invitation.dialog_layer.get_dialog(&dialog_id) {
39                Some(dialog) => {
40                    self.invitation.dialog_layer.remove_dialog(&dialog_id);
41                    return Some(dialog);
42                }
43                None => {}
44            }
45        }
46        None
47    }
48    pub async fn drop_async(&self) {
49        if let Some(dialog) = self.take_dialog() {
50            dialog.hangup().await.ok();
51        }
52    }
53}
54
55impl Drop for PendingDialogGuard {
56    fn drop(&mut self) {
57        if let Some(dialog) = self.take_dialog() {
58            info!(%self.id, "removing pending dialog on drop");
59
60            crate::spawn(async move {
61                dialog.hangup().await.ok();
62            });
63        }
64    }
65}
66
67#[async_trait]
68pub trait InvitationHandler: Send + Sync {
69    async fn on_invite(
70        &self,
71        _session_id: String,
72        _cancel_token: CancellationToken,
73        _dialog: ServerInviteDialog,
74        _routing_state: Arc<RoutingState>,
75    ) -> Result<()> {
76        return Err(anyhow!("invite not handled"));
77    }
78}
79
80pub fn default_create_invite_handler(
81    config: Option<&InviteHandlerConfig>,
82    app_state: Option<crate::app::AppState>,
83) -> Option<Box<dyn InvitationHandler>> {
84    match config {
85        Some(InviteHandlerConfig::Webhook {
86            url,
87            urls,
88            method,
89            headers,
90        }) => {
91            let all_urls = if let Some(urls) = urls {
92                urls.clone()
93            } else if let Some(url) = url {
94                vec![url.clone()]
95            } else {
96                vec![]
97            };
98            Some(Box::new(WebhookInvitationHandler::new(
99                all_urls,
100                method.clone(),
101                headers.clone(),
102            )))
103        }
104        Some(InviteHandlerConfig::Playbook { rules, default }) => {
105            let app_state = match app_state {
106                Some(s) => s,
107                None => {
108                    tracing::error!("app_state required for playbook invitation handler");
109                    return None;
110                }
111            };
112            let rules = rules.clone().unwrap_or_default();
113            match PlaybookInvitationHandler::new(rules, default.clone(), app_state) {
114                Ok(handler) => Some(Box::new(handler)),
115                Err(e) => {
116                    tracing::error!("failed to create playbook invitation handler: {}", e);
117                    None
118                }
119            }
120        }
121        _ => None,
122    }
123}
124
125pub type FnCreateInvitationHandler =
126    fn(config: Option<&InviteHandlerConfig>) -> Result<Box<dyn InvitationHandler>>;