docs: refresh readme and legal info

This commit is contained in:
2026-05-07 20:22:12 +00:00
parent 33b4ae5dd0
commit 980a1500f5
+167 -351
View File
@@ -1,140 +1,104 @@
# @serve.zone/siprouter # siprouter
A production-grade **SIP B2BUA + WebRTC bridge** built with TypeScript and Rust. Routes calls between SIP providers, SIP hardware devices, and browser softphones — with real-time codec transcoding, adaptive jitter buffering, ML noise suppression, neural TTS, voicemail, IVR menus, and a slick web dashboard. siprouter is a TypeScript control plane plus Rust media/data plane for SIP routing, SIP device registration, SIP trunk calls, browser WebRTC softphones, voicemail/fax storage, and a live operations dashboard. It is intentionally split so TypeScript owns configuration, REST/WebSocket APIs, and UI glue while the Rust `proxy-engine` owns SIP, RTP, WebRTC media, codecs, mixing, jitter handling, fax transport, and real-time call state.
## Issue Reporting and Security ## Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly. For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
--- ## Current Capabilities
## 🔥 What It Does - SIP B2BUA behavior for SIP providers and LAN SIP devices, including dialog state, provider registration, digest auth retry, SDP negotiation, BYE/CANCEL handling, and routing decisions in Rust.
- Browser WebRTC softphone signaling through the TypeScript dashboard, with WebRTC media sessions implemented in the Rust engine.
- A call hub model: each call owns multiple legs and a 20 ms tick mix-minus mixer at a 48 kHz f32 internal bus.
- Codec handling for Opus, G.722, PCMU, and PCMA through `codec-lib`, including per-leg transcoding, resampling, packet loss concealment, and jitter buffering.
- Device registration push events from Rust into TypeScript, then into the dashboard status stream.
- Route configuration for inbound and outbound calls with provider/device matching, number patterns, failover provider fields, browser notification flags, voicemail, fax, and IVR-related action fields.
- Voicemail metadata and WAV storage through `VoiceboxManager` under `.nogit/voicemail/{boxId}/`.
- Fax box and fax job metadata storage under `.nogit/fax/`, backed by Rust fax handling that includes audio fax and T.38-related code paths.
- Web dashboard and REST API on the configured `webUiPort`, served over HTTPS if `.nogit/cert.pem` and `.nogit/key.pem` exist, otherwise HTTP.
siprouter sits between your SIP trunk providers and your endpoints — hardware phones, ATAs, browser softphones — and handles **everything** in between: ## Important Accuracy Notes
- 📞 **SIP B2BUA** — Terminates and re-originates calls with full RFC 3261 dialog state management, digest auth, and SDP negotiation - TypeScript does not handle raw SIP or RTP. It sends high-level commands to the Rust engine over `@push.rocks/smartrust` and receives high-level events back.
- 🌐 **WebRTC Bridge** — Browser-based softphone with bidirectional Opus audio to the SIP network - Browser WebRTC calls use a strict two-stage link flow. The browser first creates a standalone WebRTC session with `webrtc-offer`; only after `webrtc-accept`/linking can Rust attach that session to the call mixer.
- 🎛️ **Multi-Provider Trunking** — Register with multiple SIP providers simultaneously (sipgate, easybell, etc.) with automatic failover - Inbound route resolution is wired in Rust, but multi-target inbound forking is not implemented yet. Only the first registered target device from an inbound route is rung.
- 🎧 **48kHz f32 Audio Engine** — High-fidelity internal audio bus at 48kHz/32-bit float with native Opus float encode/decode, FFT-based resampling, and per-leg ML noise suppression - `ringBrowsers` currently controls browser notifications. It is not first-answer-wins call racing against SIP devices.
- 🔀 **N-Leg Mix-Minus Mixer** — Conference-grade mixing with dynamic leg add/remove, transfer, and per-source audio separation - `voicemailBox`, `ivrMenuId`, and `noAnswerTimeout` are part of resolved inbound route data, but the project notes mark downstream honoring of those fields as not complete yet.
- 🎯 **Adaptive Jitter Buffer** — Per-leg jitter buffering with sequence-based reordering, adaptive depth (60120ms), Opus PLC for lost packets, and hold/resume detection - The `/api/transfer` HTTP endpoint currently returns `501 not yet implemented`.
- 📧 **Voicemail** — Configurable voicemail boxes with TTS greetings, recording, and web playback
- 🔢 **IVR Menus** — DTMF-navigable interactive voice response with nested menus, routing actions, and custom prompts
- 🗣️ **Neural TTS** — Kokoro-powered greetings and IVR prompts with 25+ voice presets
- 🎙️ **Call Recording** — Per-source separated WAV recording at 48kHz via tool legs
- 🖥️ **Web Dashboard** — Real-time SPA with 9 views: live calls, browser phone, routing, voicemail, IVR, contacts, providers, and streaming logs
--- ## Architecture
## 🏗️ Architecture ```text
Browser dashboard and softphone
```mermaid |
flowchart TB | HTTP + WebSocket signaling
Browser["🌐 Browser Softphone<br/>(WebRTC via WebSocket signaling)"] v
Devices["📞 SIP Devices<br/>(HT801, desk phones, ATAs)"] TypeScript control plane
Trunks["☎️ SIP Trunk Providers<br/>(sipgate, easybell, …)"] ts/sipproxy.ts
ts/frontend.ts
subgraph Router["siprouter"] ts/webrtcbridge.ts
direction TB ts/config.ts
subgraph TS["TypeScript Control Plane"] ts/proxybridge.ts
TSBits["Config · WebRTC Signaling<br/>REST API · Web Dashboard<br/>Voicebox Manager · TTS Cache"] |
end | JSON-over-stdio via @push.rocks/smartrust
subgraph Rust["Rust proxy-engine (data plane)"] v
RustBits["SIP Stack · Dialog SM · Auth<br/>Call Manager · N-Leg Mixer<br/>48kHz f32 Bus · Jitter Buffer<br/>Codec Engine · RTP Port Pool<br/>WebRTC Engine · Kokoro TTS<br/>Voicemail · IVR · Recording"] Rust proxy-engine
end SIP transport and dialog state
TS <-->|"JSON-over-stdio IPC"| Rust Call manager and call hub
end RTP port pool and RTP I/O
48 kHz f32 mix-minus mixer
Browser <-->|"Opus / WebRTC"| TS WebRTC sessions
Rust <-->|"SIP / RTP"| Devices Fax, voicemail, TTS, recorder, tool legs
Rust <-->|"SIP / RTP"| Trunks |
| SIP/RTP/UDPTL/WebRTC media
v
SIP providers, SIP devices, and browser clients
``` ```
### 🧠 Key Design Decisions ## Key Files
- **Hub Model** — Every call is a hub with N legs. Each leg is a `SipLeg` (device/provider) or `WebRtcLeg` (browser). Legs can be dynamically added, removed, or transferred without tearing down the call. | Path | Role |
- **Rust Data Plane** — All SIP protocol handling, codec transcoding, mixing, and RTP I/O runs in native Rust for real-time performance. TypeScript handles config, signaling, REST API, and dashboard. | --- | --- |
- **48kHz f32 Internal Bus** — Audio is processed at maximum quality internally. Encoding/decoding to wire format (G.722, PCMU, Opus) happens solely at the leg boundary. | `ts/sipproxy.ts` | Process entry point. Loads config, starts web UI, starts Rust, wires event handlers, and handles shutdown. |
- **Per-Session Codec Isolation** — Each call leg gets its own encoder/decoder/resampler/denoiser state — no cross-call corruption. | `ts/config.ts` | `.nogit/config.json` schema, defaults, and validation. |
- **SDP Codec Negotiation** — Outbound encoding uses the codec actually negotiated in SDP answers, not just the first offered codec. | `ts/proxybridge.ts` | Typed command bridge to the Rust `proxy-engine` binary. |
| `ts/frontend.ts` | HTTP API, static dashboard serving, status WebSocket, and WebRTC message routing. |
| `ts/webrtcbridge.ts` | Browser device registration and WebSocket-to-device mapping. |
| `ts/voicebox.ts` | Voicemail box config, WAV metadata, unheard counts, and message CRUD. |
| `ts/faxbox.ts` | Fax inbox metadata and TIFF file tracking. |
| `ts/faxjobs.ts` | Outbound/inbound fax job state persistence. |
| `rust/crates/proxy-engine/src/call_manager.rs` | Central call registry, SIP routing, B2BUA state, route resolution, fax metadata, and call orchestration. |
| `rust/crates/proxy-engine/src/mixer.rs` | 20 ms mix-minus engine with 48 kHz f32 processing, codec boundaries, jitter, PLC, DTMF, and tool-leg audio. |
| `rust/crates/proxy-engine/src/webrtc_engine.rs` | Browser WebRTC sessions. |
| `rust/crates/proxy-engine/src/fax_engine.rs` | Fax transfer engine using `spandsp` and `udptl`. |
| `rust/crates/sip-proto/` | Zero-dependency SIP data library for parsing, serializing, dialogs, SDP helpers, digest auth, and URI rewriting. |
| `ts_web/` | Lit/dees-element dashboard views and WebRTC browser client state. |
### 📲 WebRTC Browser Call Flow ## Configuration
Browser calls are set up in a strict three-step dance — the WebRTC leg cannot be attached at call-creation time because the browser's session ID is only known once the SDP offer arrives: Create `.nogit/config.json` in the repository root before starting the service.
```mermaid
sequenceDiagram
participant B as Browser
participant TS as TypeScript (sipproxy.ts)
participant R as Rust proxy-engine
participant P as SIP Provider
B->>TS: POST /api/call
TS->>R: make_call (pending call, no WebRTC leg yet)
R-->>TS: call_created
TS-->>B: webrtc-incoming (callId)
B->>TS: webrtc-offer (sessionId, SDP)
TS->>R: handle_webrtc_offer
R-->>TS: webrtc-answer (SDP)
TS-->>B: webrtc-answer
Note over R: Standalone WebRTC session<br/>(not yet attached to call)
B->>TS: webrtc_link (callId + sessionId)
TS->>R: link session → call
R->>R: wire WebRTC leg through mixer
R->>P: SIP INVITE
P-->>R: 200 OK + SDP
R-->>TS: call_answered
Note over B,P: Bidirectional Opus ↔ codec-transcoded<br/>audio flows through the mixer
```
---
## 🚀 Getting Started
### Prerequisites
- **Node.js** ≥ 20 with `tsx` globally available
- **pnpm** for package management
- **Rust** toolchain (for building the proxy engine)
### Install & Build
```bash
# Clone and install dependencies
pnpm install
# Build the Rust proxy-engine binary
pnpm run buildRust
# Bundle the web frontend
pnpm run bundle
```
### Configuration
Create `.nogit/config.json`:
```jsonc ```jsonc
{ {
"proxy": { "proxy": {
"lanIp": "192.168.1.100", // Your server's LAN IP "lanIp": "192.168.1.100",
"lanPort": 5070, // SIP signaling port "lanPort": 5070,
"publicIpSeed": "stun.example.com", // STUN server for public IP discovery "publicIpSeed": null,
"rtpPortRange": { "min": 20000, "max": 20200 }, // RTP port pool (even ports) "rtpPortRange": { "min": 20000, "max": 20200 },
"webUiPort": 3060 // Dashboard + REST API port "webUiPort": 3060
}, },
"providers": [ "providers": [
{ {
"id": "my-trunk", "id": "main-trunk",
"displayName": "My SIP Provider", "displayName": "Main SIP trunk",
"domain": "sip.provider.com", "domain": "sip.example.net",
"outboundProxy": { "address": "sip.provider.com", "port": 5060 }, "outboundProxy": { "address": "sip.example.net", "port": 5060 },
"username": "user", "username": "trunk-user",
"password": "pass", "password": "trunk-password",
"codecs": [9, 0, 8, 101], // G.722, PCMU, PCMA, telephone-event "registerIntervalSec": 300,
"registerIntervalSec": 300 "codecs": [9, 0, 8, 101],
"quirks": { "earlyMediaSilence": false }
} }
], ],
"devices": [ "devices": [
@@ -148,13 +112,21 @@ Create `.nogit/config.json`:
"routing": { "routing": {
"routes": [ "routes": [
{ {
"id": "inbound-main-did", "id": "outbound-default",
"name": "Main DID", "name": "Outbound via main trunk",
"priority": 100,
"enabled": true,
"match": { "direction": "outbound" },
"action": { "provider": "main-trunk" }
},
{
"id": "inbound-main",
"name": "Inbound main number",
"priority": 200, "priority": 200,
"enabled": true, "enabled": true,
"match": { "match": {
"direction": "inbound", "direction": "inbound",
"sourceProvider": "my-trunk", "sourceProvider": "main-trunk",
"numberPattern": "+49421219694" "numberPattern": "+49421219694"
}, },
"action": { "action": {
@@ -162,267 +134,111 @@ Create `.nogit/config.json`:
"ringBrowsers": true, "ringBrowsers": true,
"voicemailBox": "main" "voicemailBox": "main"
} }
},
{
"id": "inbound-support-did",
"name": "Support DID",
"priority": 190,
"enabled": true,
"match": {
"direction": "inbound",
"sourceProvider": "my-trunk",
"numberPattern": "+49421219695"
},
"action": {
"ivrMenuId": "support-menu"
}
},
{
"id": "outbound-default",
"name": "Route via trunk",
"priority": 100,
"enabled": true,
"match": { "direction": "outbound" },
"action": { "provider": "my-trunk" }
} }
] ]
}, },
"contacts": [],
"voiceboxes": [ "voiceboxes": [
{ {
"id": "main", "id": "main",
"enabled": true, "enabled": true,
"greetingText": "Please leave a message after the beep.", "greetingText": "Please leave a message after the tone.",
"greetingVoice": "af_bella", "greetingVoice": "af_bella",
"noAnswerTimeoutSec": 25, "noAnswerTimeoutSec": 25,
"maxRecordingSec": 120, "maxRecordingSec": 120,
"maxMessages": 50 "maxMessages": 50
} }
], ],
"contacts": [ "faxboxes": [],
{ "id": "1", "name": "Alice", "number": "+491234567890", "starred": true } "ivr": {
] "enabled": false,
"entryMenuId": "main-menu",
"menus": []
}
} }
``` ```
Inbound number ownership is explicit: add one inbound route per DID (or DID prefix) and scope it with `sourceProvider` when a provider delivers multiple external numbers. ## Persistent Files
### TTS Setup (Optional) | Path | Purpose |
| --- | --- |
| `.nogit/config.json` | Main app config. |
| `.nogit/cert.pem` and `.nogit/key.pem` | Optional HTTPS certificate for the dashboard. |
| `.nogit/voicemail/{boxId}/` | Voicemail WAV files and `messages.json`. |
| `.nogit/fax/inboxes/{boxId}/` | Fax inbox files and metadata. |
| `.nogit/fax/jobs.json` | Fax job state. |
| `.nogit/prompts/` | Cached prompt/TTS assets used by call flows. |
| `sip_trace.log` | Runtime log written by `ts/sipproxy.ts`. |
For neural voicemail greetings and IVR prompts, download the Kokoro TTS model: ## HTTP and WebSocket API
| Endpoint | Purpose |
| --- | --- |
| `GET /api/status` | Full status snapshot for providers, devices, calls, and dashboard state. |
| `POST /api/call` | Originate an outbound call. |
| `POST /api/hangup` | Hang up a call. |
| `POST /api/fax` | Start an outbound fax. |
| `GET /api/fax/jobs` | List fax jobs. |
| `GET /api/fax/inboxes/:boxId` | List fax inbox messages. |
| `GET /api/fax/inboxes/:boxId/:messageId/file` | Stream a fax TIFF. |
| `DELETE /api/fax/inboxes/:boxId/:messageId` | Delete a fax message. |
| `POST /api/call/:id/addleg` | Add a registered SIP device leg to an active call. |
| `POST /api/call/:id/addexternal` | Add an external dial-out leg to an active call. |
| `POST /api/call/:id/removeleg` | Remove a leg from a call. |
| `POST /api/transfer` | Present but returns `501 not yet implemented`. |
| `GET /api/config` | Read sanitized config. |
| `POST /api/config` | Update config and trigger runtime reload where possible. |
| `GET /api/voicemail/:boxId` | List voicemail messages. |
| `GET /api/voicemail/:boxId/unheard` | Get unheard voicemail count. |
| `GET /api/voicemail/:boxId/:messageId/audio` | Stream voicemail WAV audio. |
| `POST /api/voicemail/:boxId/:messageId/heard` | Mark voicemail as heard. |
| `DELETE /api/voicemail/:boxId/:messageId` | Delete voicemail metadata and WAV file. |
| `WS /ws` | Status updates, logs, WebRTC signaling, and browser phone events. |
## Build and Run
```bash ```bash
mkdir -p .nogit/tts pnpm install
curl -L -o .nogit/tts/kokoro-v1.0.onnx \
https://github.com/mzdk100/kokoro/releases/download/V1.0/kokoro-v1.0.onnx
curl -L -o .nogit/tts/voices.bin \
https://github.com/mzdk100/kokoro/releases/download/V1.0/voices.bin
```
Without the model files, TTS prompts (IVR menus, voicemail greetings) are skipped — everything else works fine.
### Run
```bash
pnpm start
```
The SIP proxy starts on the configured port and the web dashboard is available at `https://<your-ip>:3060`.
### HTTPS (Optional)
Place `cert.pem` and `key.pem` in `.nogit/` for TLS on the dashboard.
---
## 📂 Project Structure
```
siprouter/
├── ts/ # TypeScript control plane
│ ├── sipproxy.ts # Main entry — bootstraps everything
│ ├── config.ts # Config loader & validation
│ ├── proxybridge.ts # Rust proxy-engine IPC bridge (smartrust)
│ ├── frontend.ts # Web dashboard HTTP/WS server + REST API
│ ├── webrtcbridge.ts # WebRTC signaling layer
│ ├── registrar.ts # Browser softphone registration
│ ├── voicebox.ts # Voicemail box management
│ └── call/
│ └── prompt-cache.ts # Named audio prompt WAV management
├── ts_web/ # Web frontend (Lit-based SPA)
│ ├── elements/ # Web components (9 dashboard views)
│ └── state/ # App state, WebRTC client, notifications
├── rust/ # Rust workspace (the data plane)
│ └── crates/
│ ├── codec-lib/ # Audio codec library (Opus/G.722/PCMU/PCMA)
│ ├── sip-proto/ # Zero-dependency SIP protocol library
│ └── proxy-engine/ # Main binary — SIP engine + mixer + RTP
├── html/ # Static HTML shell
├── .nogit/ # Secrets, config, TTS models (gitignored)
└── dist_rust/ # Compiled Rust binary (gitignored)
```
---
## 🎧 Audio Engine (Rust)
The `proxy-engine` binary handles all real-time audio processing with a **48kHz f32 internal bus** — encoding and decoding happens only at leg boundaries.
### Supported Codecs
| Codec | PT | Native Rate | Use Case |
|-------|:--:|:-----------:|----------|
| **Opus** | 111 | 48 kHz | WebRTC browsers (native float encode/decode — zero i16 quantization) |
| **G.722** | 9 | 16 kHz | HD SIP devices & providers |
| **PCMU** (G.711 µ-law) | 0 | 8 kHz | Legacy SIP |
| **PCMA** (G.711 A-law) | 8 | 8 kHz | Legacy SIP |
### Audio Pipeline
```mermaid
flowchart LR
subgraph Inbound["Inbound path (per leg)"]
direction LR
IN_RTP["Wire RTP"] --> IN_JB["Jitter Buffer"] --> IN_DEC["Decode"] --> IN_RS["Resample → 48 kHz"] --> IN_DN["Denoise (RNNoise)"] --> IN_BUS["Mix Bus"]
end
subgraph Outbound["Outbound path (per leg)"]
direction LR
OUT_BUS["Mix Bus"] --> OUT_MM["Mix-Minus"] --> OUT_RS["Resample → codec rate"] --> OUT_ENC["Encode"] --> OUT_RTP["Wire RTP"]
end
```
- **Adaptive jitter buffer** — per-leg `BTreeMap`-based buffer keyed by RTP sequence number. Delivers exactly one frame per 20ms mixer tick in sequence order. Adaptive target depth starts at 3 frames (60ms) and adjusts between 26 frames based on observed network jitter. Handles hold/resume by detecting large forward sequence jumps and resetting cleanly.
- **Packet loss concealment (PLC)** — on missing packets, Opus legs invoke the decoder's built-in PLC (`decode(None)`) to synthesize a smooth fill frame. Non-Opus legs (G.722, PCMU) apply exponential fade (0.85×) toward silence to avoid hard discontinuities.
- **FFT-based resampling** via `rubato` — high-quality sinc interpolation with canonical 20ms chunk sizes to ensure consistent resampler state across frames, preventing filter discontinuities
- **ML noise suppression** via `nnnoiseless` (RNNoise) — per-leg inbound denoising with SIMD acceleration (AVX/SSE). Skipped for WebRTC legs (browsers already denoise via getUserMedia)
- **Mix-minus mixing** — each participant hears everyone except themselves, accumulated in f64 precision
- **RFC 3550 compliant header parsing** — properly handles CSRC lists and header extensions
---
## 🗣️ Neural TTS
Voicemail greetings and IVR prompts are synthesized using [Kokoro TTS](https://github.com/mzdk100/kokoro) — an 82M parameter neural model running via ONNX Runtime directly in the Rust process:
- **24 kHz, 16-bit mono** output
- **25+ voice presets** — American/British, male/female (e.g., `af_bella`, `am_adam`, `bf_emma`, `bm_george`)
- **~800ms** synthesis time for a 3-second phrase
- Lazy-loaded on first use — no startup cost if TTS is unused
---
## 📧 Voicemail
- Configurable voicemail boxes with custom TTS greetings (text + voice) or uploaded WAV
- Automatic routing on no-answer timeout (configurable, default 25s)
- Recording with configurable max duration (default 120s) and message count limit (default 50)
- Unheard message tracking for MWI (message waiting indication)
- Web dashboard playback and management
- WAV storage in `.nogit/voicemail/`
---
## 🔢 IVR (Interactive Voice Response)
- DTMF-navigable menus with configurable entries
- Actions: route to extension, route to voicemail, transfer, submenu, hangup, repeat prompt
- Custom TTS prompts per menu
- Nested menu support
---
## 🌐 Web Dashboard & REST API
### Dashboard Views
| View | Description |
|------|-------------|
| 📊 **Overview** | Stats tiles — uptime, providers, devices, active calls |
| 📞 **Calls** | Active calls with leg details, codec info, add/remove legs, transfer, hangup |
| ☎️ **Phone** | Browser softphone — mic/speaker selection, audio meters, dial pad, incoming call popup |
| 🔀 **Routes** | Routing rule management — match/action model with priority |
| 📧 **Voicemail** | Voicemail box management + message playback |
| 🔢 **IVR** | IVR menu builder — DTMF entries, TTS prompts, nested menus |
| 👤 **Contacts** | Contact management with click-to-call |
| 🔌 **Providers** | SIP trunk configuration and registration status |
| 📋 **Log** | Live streaming log viewer |
### REST API
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/status` | GET | Full system status (providers, devices, calls, history) |
| `/api/call` | POST | Originate a call |
| `/api/hangup` | POST | Hang up a call |
| `/api/call/:id/addleg` | POST | Add a device leg to an active call |
| `/api/call/:id/addexternal` | POST | Add an external participant via provider |
| `/api/call/:id/removeleg` | POST | Remove a leg from a call |
| `/api/transfer` | POST | Transfer a call |
| `/api/config` | GET | Read current configuration |
| `/api/config` | POST | Update configuration (hot-reload) |
| `/api/voicemail/:box` | GET | List voicemail messages |
| `/api/voicemail/:box/unheard` | GET | Get unheard message count |
| `/api/voicemail/:box/:id/audio` | GET | Stream voicemail audio |
| `/api/voicemail/:box/:id/heard` | POST | Mark a voicemail message as heard |
| `/api/voicemail/:box/:id` | DELETE | Delete a voicemail message |
### WebSocket Events
Connect to `/ws` for real-time push:
```jsonc
{ "type": "status", "data": { ... } } // Full status snapshot (1s interval)
{ "type": "log", "data": { "message": "..." } } // Log lines in real-time
{ "type": "call-update", "data": { ... } } // Call state change notification
{ "type": "webrtc-answer", "data": { ... } } // WebRTC SDP answer for browser calls
{ "type": "webrtc-error", "data": { ... } } // WebRTC signaling error
```
Browser → server signaling:
```jsonc
{ "type": "webrtc-offer", "data": { ... } } // Browser sends SDP offer
{ "type": "webrtc-accept", "data": { ... } } // Browser accepts incoming call
{ "type": "webrtc-ice", "data": { ... } } // ICE candidate exchange
{ "type": "webrtc-hangup", "data": { ... } } // Browser hangs up
```
---
## 🔌 Ports
| Port | Protocol | Purpose |
|------|----------|---------|
| 5070 (configurable) | UDP | SIP signaling |
| 2000020200 (configurable) | UDP | RTP media (even ports, per-call allocation) |
| 3060 (configurable) | TCP | Web dashboard + WebSocket + REST API |
---
## 🛠️ Development
```bash
# Start in dev mode
pnpm start
# Build Rust proxy-engine
pnpm run buildRust pnpm run buildRust
# Bundle web frontend
pnpm run bundle pnpm run bundle
pnpm start
# Build + bundle + restart background server
pnpm run restartBackground
``` ```
--- Full build:
```bash
pnpm build
```
Docker build scripts are also present:
```bash
pnpm run build:docker
pnpm run release:docker
```
`pnpm run buildRust` uses `tsrust`. Per the project notes, do not replace that with a direct `cargo build` when validating the packaged Rust output. The configured build path cross-compiles the Rust engine for Linux amd64 and arm64 targets.
## Project Map
```text
siprouter/
├── ts/ # TypeScript control plane
├── ts_web/ # Browser dashboard
├── rust/
│ └── crates/
│ ├── codec-lib/ # Codec and transcoding helpers
│ ├── proxy-engine/ # Rust SIP/RTP/WebRTC/fax engine
│ └── sip-proto/ # SIP message/dialog/SDP library
├── html/ # Dashboard HTML shell
├── dist_rust/ # Built Rust binaries
├── dist_ts_web/ # Bundled web UI
└── .nogit/ # Local config, secrets, voicemail, fax, prompts
```
## License and Legal Information ## License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file. This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license) file.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file. **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.