feat(routing): add rule-based SIP routing for inbound and outbound calls with dashboard route management
This commit is contained in:
@@ -25,7 +25,7 @@ import {
|
||||
} from '../sip/index.ts';
|
||||
import type { IEndpoint } from '../sip/index.ts';
|
||||
import type { IAppConfig, IProviderConfig } from '../config.ts';
|
||||
import { getProvider, getProviderForOutbound, getDevicesForInbound } from '../config.ts';
|
||||
import { getProvider, getDevice, resolveOutboundRoute, resolveInboundRoute } from '../config.ts';
|
||||
import { RtpPortPool } from './rtp-port-pool.ts';
|
||||
import { Call } from './call.ts';
|
||||
import { SipLeg } from './sip-leg.ts';
|
||||
@@ -211,10 +211,27 @@ export class CallManager {
|
||||
* Dials the device first (leg A), then the provider (leg B) when device answers.
|
||||
*/
|
||||
createOutboundCall(number: string, deviceId?: string, providerId?: string): Call | null {
|
||||
// Resolve provider.
|
||||
const provider = providerId
|
||||
? getProvider(this.config.appConfig, providerId)
|
||||
: getProviderForOutbound(this.config.appConfig);
|
||||
// Resolve provider via routing (or explicit providerId override).
|
||||
let provider: IProviderConfig | null;
|
||||
let dialNumber = number;
|
||||
|
||||
if (providerId) {
|
||||
provider = getProvider(this.config.appConfig, providerId);
|
||||
} else {
|
||||
const routeResult = resolveOutboundRoute(
|
||||
this.config.appConfig,
|
||||
number,
|
||||
deviceId,
|
||||
(pid) => !!this.config.getProviderState(pid)?.registeredAor,
|
||||
);
|
||||
if (routeResult) {
|
||||
provider = routeResult.provider;
|
||||
dialNumber = routeResult.transformedNumber;
|
||||
} else {
|
||||
provider = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!provider) {
|
||||
this.config.log('[call-mgr] no provider found');
|
||||
return null;
|
||||
@@ -259,7 +276,7 @@ export class CallManager {
|
||||
// then webrtc-accept (which links the leg to this call and starts the provider).
|
||||
this.pendingBrowserCalls.set(callId, {
|
||||
provider,
|
||||
number,
|
||||
number: dialNumber,
|
||||
ps,
|
||||
rtpPort: rtpA.port,
|
||||
rtpSock: rtpA.sock,
|
||||
@@ -312,7 +329,7 @@ export class CallManager {
|
||||
}
|
||||
|
||||
// Start dialing provider in parallel with announcement.
|
||||
this.startProviderLeg(call, provider, number, ps);
|
||||
this.startProviderLeg(call, provider, dialNumber, ps);
|
||||
};
|
||||
|
||||
legA.onTerminated = (leg) => {
|
||||
@@ -505,9 +522,13 @@ export class CallManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Resolve target device.
|
||||
const deviceConfigs = getDevicesForInbound(this.config.appConfig, provider.id);
|
||||
const deviceTarget = this.resolveFirstDevice(deviceConfigs.map((d) => d.id));
|
||||
// Resolve inbound routing — determine target devices and browser ring.
|
||||
const calledNumber = SipMessage.extractUri(invite.requestUri || '') || '';
|
||||
const routeResult = resolveInboundRoute(this.config.appConfig, provider.id, calledNumber, call.callerNumber);
|
||||
const targetDeviceIds = routeResult.deviceIds.length
|
||||
? routeResult.deviceIds
|
||||
: this.config.appConfig.devices.map((d) => d.id);
|
||||
const deviceTarget = this.resolveFirstDevice(targetDeviceIds);
|
||||
if (!deviceTarget) {
|
||||
this.config.log('[call-mgr] cannot handle inbound — no device target');
|
||||
this.portPool.release(rtpAlloc.port);
|
||||
@@ -563,8 +584,8 @@ export class CallManager {
|
||||
|
||||
this.config.sendSip(fwdInvite.serialize(), deviceTarget);
|
||||
|
||||
// Notify browsers if configured.
|
||||
if (this.config.appConfig.routing.ringBrowsers?.[provider.id]) {
|
||||
// Notify browsers if route says so.
|
||||
if (routeResult.ringBrowsers) {
|
||||
const ids = this.config.getAllBrowserDeviceIds();
|
||||
for (const deviceIdBrowser of ids) {
|
||||
this.config.sendToBrowserDevice(deviceIdBrowser, {
|
||||
@@ -868,10 +889,27 @@ export class CallManager {
|
||||
const call = this.calls.get(callId);
|
||||
if (!call) return false;
|
||||
|
||||
// Resolve provider.
|
||||
const provider = providerId
|
||||
? getProvider(this.config.appConfig, providerId)
|
||||
: getProviderForOutbound(this.config.appConfig);
|
||||
// Resolve provider via routing (or explicit providerId override).
|
||||
let provider: IProviderConfig | null;
|
||||
let dialNumber = number;
|
||||
|
||||
if (providerId) {
|
||||
provider = getProvider(this.config.appConfig, providerId);
|
||||
} else {
|
||||
const routeResult = resolveOutboundRoute(
|
||||
this.config.appConfig,
|
||||
number,
|
||||
undefined,
|
||||
(pid) => !!this.config.getProviderState(pid)?.registeredAor,
|
||||
);
|
||||
if (routeResult) {
|
||||
provider = routeResult.provider;
|
||||
dialNumber = routeResult.transformedNumber;
|
||||
} else {
|
||||
provider = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!provider) {
|
||||
this.config.log(`[call-mgr] addExternalToCall: no provider`);
|
||||
return false;
|
||||
@@ -915,7 +953,7 @@ export class CallManager {
|
||||
call.addLeg(newLeg);
|
||||
|
||||
const sipCallId = `${callId}-${legId}`;
|
||||
const destUri = `sip:${number}@${provider.domain}`;
|
||||
const destUri = `sip:${dialNumber}@${provider.domain}`;
|
||||
newLeg.sendInvite({
|
||||
fromUri: ps.registeredAor,
|
||||
toUri: destUri,
|
||||
@@ -923,7 +961,7 @@ export class CallManager {
|
||||
});
|
||||
this.sipCallIdIndex.set(sipCallId, call);
|
||||
|
||||
this.config.log(`[call-mgr] ${callId} dialing external ${number} via ${provider.displayName}`);
|
||||
this.config.log(`[call-mgr] ${callId} dialing external ${dialNumber} via ${provider.displayName}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user