Skip to main content

active_call/useragent/
invitation.rs

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