active_call/useragent/
invitation.rs1use 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 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>>;