fix(proxy-engine): respect explicit inbound route targets and store voicemail in the configured mailbox
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-04-14 - 1.25.1 - fix(proxy-engine)
|
||||
respect explicit inbound route targets and store voicemail in the configured mailbox
|
||||
|
||||
- Prevent inbound routes with an explicit empty target list from ringing arbitrary registered devices by distinguishing omitted targets from empty targets.
|
||||
- Route unrouted or no-target inbound calls to voicemail with a generated unrouted greeting instead of falling back to random devices.
|
||||
- Pass voicemail box identifiers through proxy events and runtime handling so recordings are saved and indexed under the correct mailbox instead of always using default.
|
||||
|
||||
## 2026-04-14 - 1.25.0 - feat(proxy-engine)
|
||||
add live TTS streaming interactions and incoming number range support
|
||||
|
||||
|
||||
@@ -863,13 +863,48 @@ impl CallManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Pick the first registered device from the matched targets, or fall
|
||||
// back to any-registered-device if the route has no resolved targets.
|
||||
let device_addr = route
|
||||
.device_ids
|
||||
.iter()
|
||||
.find_map(|id| registrar.get_device_contact(id))
|
||||
.or_else(|| self.resolve_first_device(config, registrar));
|
||||
// Explicit no-target inbound routes do not fall back to random devices.
|
||||
// They either go to the configured voicemail box or play the unrouted
|
||||
// greeting via the default voicemail flow.
|
||||
if !route.ring_all_devices && route.device_ids.is_empty() && !route.ring_browsers {
|
||||
let greeting_wav = if route.voicemail_box.is_some() {
|
||||
resolve_greeting_wav(config, route.voicemail_box.as_deref(), &tts_engine).await
|
||||
} else {
|
||||
resolve_unrouted_greeting_wav(&tts_engine).await
|
||||
};
|
||||
let call_id = self
|
||||
.route_to_voicemail(
|
||||
&call_id,
|
||||
invite,
|
||||
from_addr,
|
||||
&caller_number,
|
||||
provider_id,
|
||||
provider_config,
|
||||
config,
|
||||
rtp_pool,
|
||||
socket,
|
||||
public_ip,
|
||||
route.voicemail_box.clone(),
|
||||
greeting_wav,
|
||||
)
|
||||
.await?;
|
||||
return Some(InboundCallCreated {
|
||||
call_id,
|
||||
ring_browsers,
|
||||
});
|
||||
}
|
||||
|
||||
// Device targeting is explicit: omitted targets ring any registered
|
||||
// device, an empty target list rings nobody, and a populated list rings
|
||||
// only those device IDs.
|
||||
let device_addr = if route.ring_all_devices {
|
||||
self.resolve_first_device(config, registrar)
|
||||
} else {
|
||||
route
|
||||
.device_ids
|
||||
.iter()
|
||||
.find_map(|id| registrar.get_device_contact(id))
|
||||
};
|
||||
|
||||
let device_addr = match device_addr {
|
||||
Some(addr) => addr,
|
||||
@@ -890,6 +925,7 @@ impl CallManager {
|
||||
rtp_pool,
|
||||
socket,
|
||||
public_ip,
|
||||
route.voicemail_box.clone(),
|
||||
greeting_wav,
|
||||
)
|
||||
.await?;
|
||||
@@ -1715,6 +1751,7 @@ impl CallManager {
|
||||
rtp_pool: &mut RtpPortPool,
|
||||
socket: &UdpSocket,
|
||||
public_ip: Option<&str>,
|
||||
voicebox_id: Option<String>,
|
||||
greeting_wav: Option<String>,
|
||||
) -> Option<String> {
|
||||
let lan_ip = &config.proxy.lan_ip;
|
||||
@@ -1810,17 +1847,22 @@ impl CallManager {
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis();
|
||||
let recording_dir = "nogit/voicemail/default".to_string();
|
||||
let recording_dir = format!(
|
||||
".nogit/voicemail/{}",
|
||||
voicebox_id.as_deref().unwrap_or("default")
|
||||
);
|
||||
let recording_path = format!("{recording_dir}/msg-{timestamp}.wav");
|
||||
let out_tx = self.out_tx.clone();
|
||||
let call_id_owned = call_id.to_string();
|
||||
let caller_owned = caller_number.to_string();
|
||||
let voicebox_id_owned = voicebox_id.clone();
|
||||
let rtp_socket = rtp_alloc.socket;
|
||||
tokio::spawn(async move {
|
||||
crate::voicemail::run_voicemail_session(
|
||||
rtp_socket,
|
||||
provider_media,
|
||||
codec_pt,
|
||||
voicebox_id_owned,
|
||||
greeting_wav,
|
||||
recording_path,
|
||||
120_000,
|
||||
@@ -2109,3 +2151,22 @@ async fn resolve_greeting_wav(
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
async fn resolve_unrouted_greeting_wav(tts_engine: &Arc<Mutex<TtsEngine>>) -> Option<String> {
|
||||
let output = ".nogit/tts/unrouted-number.wav";
|
||||
let params = serde_json::json!({
|
||||
"model": crate::tts::DEFAULT_MODEL_PATH,
|
||||
"voices": crate::tts::DEFAULT_VOICES_PATH,
|
||||
"voice": "af_bella",
|
||||
"text": "This number is currently not being routed by siprouter.",
|
||||
"output": output,
|
||||
"cacheable": true,
|
||||
});
|
||||
|
||||
let mut tts = tts_engine.lock().await;
|
||||
if tts.generate(¶ms).await.is_ok() {
|
||||
Some(output.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,12 +373,14 @@ pub struct OutboundRouteResult {
|
||||
|
||||
/// Result of resolving an inbound route.
|
||||
//
|
||||
// `device_ids` and `ring_browsers` are consumed by create_inbound_call.
|
||||
// `device_ids`, `ring_all_devices`, and `ring_browsers` are consumed by
|
||||
// create_inbound_call.
|
||||
// The remaining fields (voicemail_box, ivr_menu_id, no_answer_timeout)
|
||||
// are resolved but not yet acted upon — see the multi-target TODO.
|
||||
#[allow(dead_code)]
|
||||
pub struct InboundRouteResult {
|
||||
pub device_ids: Vec<String>,
|
||||
pub ring_all_devices: bool,
|
||||
pub ring_browsers: bool,
|
||||
pub voicemail_box: Option<String>,
|
||||
pub ivr_menu_id: Option<String>,
|
||||
@@ -485,8 +487,10 @@ impl AppConfig {
|
||||
continue;
|
||||
}
|
||||
|
||||
let explicit_targets = route.action.targets.clone();
|
||||
return Some(InboundRouteResult {
|
||||
device_ids: route.action.targets.clone().unwrap_or_default(),
|
||||
device_ids: explicit_targets.clone().unwrap_or_default(),
|
||||
ring_all_devices: explicit_targets.is_none(),
|
||||
ring_browsers: route.action.ring_browsers.unwrap_or(false),
|
||||
voicemail_box: route.action.voicemail_box.clone(),
|
||||
ivr_menu_id: route.action.ivr_menu_id.clone(),
|
||||
|
||||
@@ -19,6 +19,7 @@ pub async fn run_voicemail_session(
|
||||
rtp_socket: Arc<UdpSocket>,
|
||||
provider_media: SocketAddr,
|
||||
codec_pt: u8,
|
||||
voicebox_id: Option<String>,
|
||||
greeting_wav: Option<String>,
|
||||
recording_path: String,
|
||||
max_recording_ms: u64,
|
||||
@@ -33,6 +34,7 @@ pub async fn run_voicemail_session(
|
||||
"voicemail_started",
|
||||
serde_json::json!({
|
||||
"call_id": call_id,
|
||||
"voicebox_id": voicebox_id,
|
||||
"caller_number": caller_number,
|
||||
}),
|
||||
);
|
||||
@@ -102,6 +104,7 @@ pub async fn run_voicemail_session(
|
||||
"recording_done",
|
||||
serde_json::json!({
|
||||
"call_id": call_id,
|
||||
"voicebox_id": voicebox_id,
|
||||
"file_path": result.file_path,
|
||||
"duration_ms": result.duration_ms,
|
||||
"caller_number": caller_number,
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: 'siprouter',
|
||||
version: '1.25.0',
|
||||
version: '1.25.1',
|
||||
description: 'undefined'
|
||||
}
|
||||
|
||||
@@ -168,12 +168,13 @@ export function registerProxyEventHandlers(options: IRegisterProxyEventHandlersO
|
||||
});
|
||||
|
||||
onProxyEvent('voicemail_started', (data) => {
|
||||
log(`[voicemail] started for call ${data.call_id} caller=${data.caller_number}`);
|
||||
log(`[voicemail] started for call ${data.call_id} box=${data.voicebox_id || 'default'} caller=${data.caller_number}`);
|
||||
});
|
||||
|
||||
onProxyEvent('recording_done', (data) => {
|
||||
log(`[voicemail] recording done: ${data.file_path} (${data.duration_ms}ms) caller=${data.caller_number}`);
|
||||
voiceboxManager.addMessage('default', {
|
||||
const boxId = data.voicebox_id || 'default';
|
||||
log(`[voicemail] recording done: ${data.file_path} (${data.duration_ms}ms) box=${boxId} caller=${data.caller_number}`);
|
||||
voiceboxManager.addMessage(boxId, {
|
||||
callerNumber: data.caller_number || 'Unknown',
|
||||
callerName: null,
|
||||
fileName: data.file_path,
|
||||
|
||||
@@ -108,10 +108,13 @@ export interface IWebRtcAudioRxEvent {
|
||||
|
||||
export interface IVoicemailStartedEvent {
|
||||
call_id: string;
|
||||
voicebox_id?: string;
|
||||
caller_number?: string;
|
||||
}
|
||||
|
||||
export interface IRecordingDoneEvent {
|
||||
call_id?: string;
|
||||
voicebox_id?: string;
|
||||
file_path: string;
|
||||
duration_ms: number;
|
||||
caller_number?: string;
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: 'siprouter',
|
||||
version: '1.25.0',
|
||||
version: '1.25.1',
|
||||
description: 'undefined'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user