# WebRTC Architecture — ShiftBridge V-Connect PBX

## Overview

The WebRTC subsystem provides browser-based calling through the webphone interface. It uses JsSIP 3.11.1 as the SIP/WebRTC library, connecting via WSS (WebSocket Secure) to Asterisk 22's PJSIP WebSocket transport.

## Architecture Diagram

```
┌────────────────────────────────────────────────────────────────────────┐
│  BROWSER (Webphone)                                                     │
│                                                                          │
│  JsSIP ──WSS──► Apache (TLS proxy :443/ws) ──► Asterisk (WSS :8089)   │
│                                                                          │
│  WebRTC ──DTLS/SRTP──► Asterisk (RTP :10000-40000)                     │
│                                                                          │
│  ICE: host → (srflx via STUN) → (relay via TURN)                       │
└────────────────────────────────────────────────────────────────────────┘
```

## Call Flow Timing

When user clicks "Call":

```
T+0ms    : makeCall() invoked
T+5ms    : getUserMedia() — browser prompts for mic access (cached after first use)
T+10ms   : PeerConnection created, ICE gathering starts
T+15ms   : First HOST candidate discovered (browser's own IP)
T+50ms   : STUN response received (srflx candidate — user's public IP)
           OR
T+500ms  : ICE timeout — send INVITE with host candidate only (STUN slow/blocked)
T+50-500ms: ★ SIP INVITE sent to Asterisk
T+55-505ms: Asterisk receives INVITE, processes dialplan
T+100-600ms: Destination extension starts ringing (180 Ringing)
```

**Expected total: 100-600ms from click to ringing** (was 20-30 seconds before fix)

## The 30-Second Delay — Root Cause & Fix

### Root Cause (TWO issues working together)

1. **Server-side STUN (`stunaddr` in rtp.conf)**
   - Asterisk was configured with `stunaddr=stun.l.google.com:19302`
   - On a server with a PUBLIC IP (41.221.10.104), STUN is completely unnecessary
   - Asterisk was making STUN requests during ICE candidate generation for the ANSWER
   - If STUN was slow/unreachable, Asterisk waited up to 20-30s before sending media

2. **Browser-side ICE gathering (no trickle ICE)**
   - JsSIP by default waits for ALL ICE candidates before sending the INVITE
   - If STUN was configured in `iceServers`, the browser waited for the STUN response
   - Chrome's ICE gathering timeout is ~30 seconds (waits for all servers to respond/timeout)
   - This blocked the INVITE from being sent until gathering completed

### Fix Applied

1. **Server: Disabled `stunaddr`** in `/etc/asterisk/rtp.conf`
   - ICE support remains ON (required for WebRTC)
   - Server knows its own public IP via `external_media_address` on transport
   - No STUN query needed — advertises 41.221.10.104 directly as host candidate

2. **Server: Added NAT settings to WSS transport** in `/etc/asterisk/pjsip.conf`
   ```ini
   [transport-wss]
   external_media_address=41.221.10.104
   external_signaling_address=41.221.10.104
   local_net=41.221.10.0/28
   local_net=127.0.0.0/8
   ```

3. **Browser: Implemented smart ICE gathering with timeout**
   - On first host candidate, starts a 500ms timer
   - If STUN responds quickly (srflx candidate), sends INVITE immediately
   - If STUN is slow/blocked, sends INVITE after 500ms with host candidate only
   - Result: INVITE is ALWAYS sent within 500ms maximum

## ICE Strategy

### Internal Users (Same LAN)
- Browser IP: `41.221.10.x`
- ICE candidate: host (direct IP)
- Media path: Direct to Asterisk on same subnet
- STUN: Not needed, but attempted (no delay due to trickle ICE)
- Result: Instant call setup (<100ms)

### External Users (Home/Office Fibre)
- Browser IP: `192.168.x.x` (behind NAT)
- ICE candidates: host (private) + srflx (public via STUN)
- Media path: Browser's public IP ↔ Asterisk's public IP
- STUN: Responds in <100ms typically
- Result: Fast call setup (<200ms)

### Mobile/LTE Users
- Browser IP: Carrier-grade NAT (100.x.x.x)
- ICE candidates: host + srflx (via STUN)
- Media path: Phone's carrier IP ↔ Asterisk's public IP
- STUN: Responds in <200ms typically
- Result: Fast call setup (<300ms)

### Strict Firewall Users (Hotel/Corporate)
- UDP may be blocked entirely
- STUN won't help (UDP blocked)
- TURN server required (relays media via TCP/TLS)
- Without TURN: Call setup within 500ms but NO AUDIO (candidates can't connect)
- With TURN: Full media via relay

## Configuration

### Environment Variables (`.env`)

```bash
# STUN server (helps remote users discover their public IP)
PBX_STUN_SERVER=stun:stun.l.google.com:19302

# TURN server (relay for users behind strict firewalls)
PBX_TURN_SERVER=turn:turn.example.com:3478
PBX_TURN_USERNAME=username
PBX_TURN_PASSWORD=password

# Local networks (comma-separated CIDRs)
PBX_LOCAL_NETS=41.221.10.0/28
```

### Asterisk Configuration

**`/etc/asterisk/rtp.conf`** — Server-side RTP/ICE:
```ini
[general]
rtpstart=10000
rtpend=40000
icesupport=true
; stunaddr is DISABLED — server has public IP, doesn't need STUN
;stunaddr=stun.l.google.com:19302
strictrtp=no
```

**`/etc/asterisk/pjsip.conf`** — WSS Transport:
```ini
[transport-wss]
type=transport
protocol=wss
bind=0.0.0.0:8089
external_media_address=41.221.10.104
external_signaling_address=41.221.10.104
local_net=41.221.10.0/28
local_net=127.0.0.0/8
```

### WebRTC Endpoint Settings (Database)

Each WebRTC endpoint (`{ext}w`) has:
| Setting | Value | Purpose |
|---------|-------|---------|
| webrtc | yes | Enables all WebRTC defaults |
| transport | transport-wss | Uses WebSocket transport |
| ice_support | yes | ICE required for WebRTC |
| dtls_auto_generate_cert | yes | Auto-generates DTLS cert |
| media_encryption | dtls | DTLS-SRTP for media |
| rtcp_mux | yes | Single port for RTP+RTCP |
| use_avpf | yes | AVPF profile for WebRTC |
| bundle | yes | Bundle audio/video on single port |
| direct_media | no | Media always through Asterisk |
| rtp_symmetric | yes | Use same port for send/receive |
| force_rport | yes | Use source port from request |
| rewrite_contact | yes | Update contact with real IP |
| media_use_received_transport | yes | Keep media on same transport |

### Normal SIP Endpoint (for comparison)

| Setting | Value | Purpose |
|---------|-------|---------|
| webrtc | no | Standard SIP |
| transport | transport-udp | UDP transport |
| ice_support | no | No ICE |
| media_encryption | no | No SRTP |
| direct_media | no | Media through Asterisk |
| rtp_symmetric | yes | NAT traversal |

## Diagnostics

### Browser Console Timing

Open browser DevTools (F12) → Console tab. When you make a call, you'll see:

```
[WP] ──── CALL START ────
[WP] T+0ms: makeCall -> target: 1350
[WP] T+8ms: PeerConnection created, ICE gathering starting
[WP] T+12ms: ICE candidate [host]
[WP] T+45ms: Got srflx candidate, sending INVITE now
[WP] T+46ms: ★ SIP INVITE SENT
[WP] T+120ms: ★ Progress/Ringing (180)
[WP] T+5200ms: ★ ANSWERED
[WP] ──── TIMING SUMMARY ────
[WP]   Call setup → INVITE sent: 46ms
[WP]   INVITE sent → Ringing: 74ms
[WP]   Total call setup: 5200ms
[WP]   ICE candidate type: host
[WP] ────────────────────────
```

### Key Timing Checkpoints

| Gap | Normal | Problem If |
|-----|--------|-----------|
| Click → INVITE sent | <500ms | >1000ms (ICE gathering blocked) |
| INVITE sent → Ringing | <200ms | >2000ms (dialplan/DB issue) |
| ICE gathering time | <200ms | >1000ms (STUN unreachable) |

### Asterisk CLI Debugging

```bash
# Enable SIP tracing
asterisk -rx "pjsip set logger on"

# Watch call flow
asterisk -rx "core set verbose 5"

# Check endpoint status
asterisk -rx "pjsip show endpoints"

# Check contacts (look for stale entries)
asterisk -rx "pjsip show contacts"

# Check transport
asterisk -rx "pjsip show transport transport-wss"

# Check RTP settings
asterisk -rx "rtp show settings"
```

## TURN Server Recommendations

For a production PBX product, TURN is recommended for:
- Hotel WiFi (UDP often blocked)
- Corporate firewalls (only 443/TCP allowed)
- Mobile networks with strict NAT
- Government/banking networks

### Options:
1. **coturn** (self-hosted, free, open-source)
   - Install on same or separate server
   - Uses ports 3478 (TURN) and 443 (TURN over TLS)
   - Requires valid SSL cert for TLS mode

2. **Twilio Network Traversal** (paid, managed)
   - ~$0.0004/minute
   - No server to manage
   - Global edge network

3. **Xirsys** (paid, managed)
   - WebRTC-focused TURN provider
   - Per-GB pricing

### Future GUI Settings (Planned)

The WebRTC settings page should expose:
- STUN server URL
- TURN server URL
- TURN over TCP/TLS toggle
- TURN username/password
- Force TURN for testing
- Internal network ranges
- External PBX FQDN
- RTP port range
- ICE gathering timeout
- WebRTC diagnostics panel

## Test Matrix

| Scenario | Expected Setup Time | Requires |
|----------|-------------------|----------|
| Internal → Internal (LAN) | <100ms | Nothing extra |
| Internal → Internal (Fibre) | <200ms | STUN |
| Internal → Internal (LTE) | <300ms | STUN |
| Webphone → Trunk (LAN) | <200ms | Nothing extra |
| Webphone → Trunk (External) | <300ms | STUN |
| Strict firewall → Internal | <500ms | TURN |
| STUN unavailable | <500ms (timeout) | Falls back to host |
| TURN forced | <400ms | TURN server configured |
| Desk phone → Internal | <50ms | Nothing (no ICE) |

## Files Modified

| File | Change |
|------|--------|
| `/etc/asterisk/rtp.conf` | Commented out `stunaddr` (server doesn't need it) |
| `/etc/asterisk/pjsip.conf` | Added `external_media_address`, `local_net` to WSS transport |
| `config/pbx.php` | Added `local_nets` config, documented ICE strategy |
| `.env` | Added `PBX_STUN_SERVER`, `PBX_TURN_*`, `PBX_LOCAL_NETS` |
| `resources/views/webphone/phone.blade.php` | Trickle ICE, smart timeout, timing diagnostics |
