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