Files
siprouter/ts/config.ts
Juergen Kunz f3e1c96872 initial commit — SIP B2BUA + WebRTC bridge with Rust codec engine
Full-featured SIP router with multi-provider trunking, browser softphone
via WebRTC, real-time Opus/G.722/PCM transcoding in Rust, RNNoise ML
noise suppression, Kokoro neural TTS announcements, and a Lit-based
web dashboard with live call monitoring and REST API.
2026-04-09 23:03:55 +00:00

154 lines
4.4 KiB
TypeScript

/**
* Application configuration — loaded from .nogit/config.json.
*
* All network addresses, credentials, provider settings, device definitions,
* and routing rules come from this single config file. No hardcoded values
* in source.
*/
import fs from 'node:fs';
import path from 'node:path';
import type { IEndpoint } from './sip/index.ts';
// ---------------------------------------------------------------------------
// Config interfaces
// ---------------------------------------------------------------------------
export interface IQuirks {
earlyMediaSilence: boolean;
silencePayloadType?: number;
silenceMaxPackets?: number;
}
export interface IProviderConfig {
id: string;
displayName: string;
domain: string;
outboundProxy: IEndpoint;
username: string;
password: string;
registerIntervalSec: number;
codecs: number[];
quirks: IQuirks;
}
export interface IDeviceConfig {
id: string;
displayName: string;
expectedAddress: string;
extension: string;
}
export interface IRoutingConfig {
outbound: { default: string };
inbound: Record<string, string[]>;
ringBrowsers?: Record<string, boolean>;
}
export interface IProxyConfig {
lanIp: string;
lanPort: number;
publicIpSeed: string | null;
rtpPortRange: { min: number; max: number };
webUiPort: number;
}
export interface IContact {
id: string;
name: string;
number: string;
company?: string;
notes?: string;
starred?: boolean;
}
export interface IAppConfig {
proxy: IProxyConfig;
providers: IProviderConfig[];
devices: IDeviceConfig[];
routing: IRoutingConfig;
contacts: IContact[];
}
// ---------------------------------------------------------------------------
// Loader
// ---------------------------------------------------------------------------
const CONFIG_PATH = path.join(process.cwd(), '.nogit', 'config.json');
export function loadConfig(): IAppConfig {
let raw: string;
try {
raw = fs.readFileSync(CONFIG_PATH, 'utf8');
} catch {
throw new Error(`config not found at ${CONFIG_PATH} — create .nogit/config.json`);
}
const cfg = JSON.parse(raw) as IAppConfig;
// Basic validation.
if (!cfg.proxy) throw new Error('config: missing "proxy" section');
if (!cfg.proxy.lanIp) throw new Error('config: missing proxy.lanIp');
if (!cfg.proxy.lanPort) throw new Error('config: missing proxy.lanPort');
if (!cfg.proxy.rtpPortRange?.min || !cfg.proxy.rtpPortRange?.max) {
throw new Error('config: missing proxy.rtpPortRange.min/max');
}
cfg.proxy.webUiPort ??= 3060;
cfg.proxy.publicIpSeed ??= null;
cfg.providers ??= [];
for (const p of cfg.providers) {
if (!p.id || !p.domain || !p.outboundProxy || !p.username || !p.password) {
throw new Error(`config: provider "${p.id || '?'}" missing required fields`);
}
p.displayName ??= p.id;
p.registerIntervalSec ??= 300;
p.codecs ??= [9, 0, 8, 101];
p.quirks ??= { earlyMediaSilence: false };
}
if (!Array.isArray(cfg.devices) || !cfg.devices.length) {
throw new Error('config: need at least one device');
}
for (const d of cfg.devices) {
if (!d.id || !d.expectedAddress) {
throw new Error(`config: device "${d.id || '?'}" missing required fields`);
}
d.displayName ??= d.id;
d.extension ??= '100';
}
cfg.routing ??= { outbound: { default: cfg.providers[0].id }, inbound: {} };
cfg.routing.outbound ??= { default: cfg.providers[0].id };
cfg.contacts ??= [];
for (const c of cfg.contacts) {
c.starred ??= false;
}
return cfg;
}
// ---------------------------------------------------------------------------
// Lookup helpers
// ---------------------------------------------------------------------------
export function getProvider(cfg: IAppConfig, id: string): IProviderConfig | null {
return cfg.providers.find((p) => p.id === id) ?? null;
}
export function getDevice(cfg: IAppConfig, id: string): IDeviceConfig | null {
return cfg.devices.find((d) => d.id === id) ?? null;
}
export function getProviderForOutbound(cfg: IAppConfig): IProviderConfig | null {
const id = cfg.routing?.outbound?.default;
if (!id) return cfg.providers[0] || null;
return getProvider(cfg, id) || cfg.providers[0] || null;
}
export function getDevicesForInbound(cfg: IAppConfig, providerId: string): IDeviceConfig[] {
const ids = cfg.routing.inbound[providerId];
if (!ids?.length) return cfg.devices; // fallback: ring all devices
return ids.map((id) => getDevice(cfg, id)).filter(Boolean) as IDeviceConfig[];
}