Files
voice-call-service/README.md
2026-05-29 01:06:47 +03:00

245 lines
7.7 KiB
Markdown

# Intaleq Voice Call Signaling Backend (API Key & Session Pre-creation)
Production-ready WebRTC signaling server in Node.js for the Intaleq ride-hailing application.
Coordinates secure peer-to-peer audio calls between driver and passenger during active rides only.
Sessions are pre-created by the main application backend over an authenticated HTTP endpoint, allowing mobile clients to connect using temporary session IDs without dealing with JWTs.
## Prerequisites
- **Node.js 20+** (for local development)
- **Docker** and **Docker Compose** (for production deployments)
- **Nginx** (acting as reverse proxy for WSS upgrade and SSL termination)
- **Let's Encrypt** (for SSL certificates)
## Local Development
1. **Clone and Navigate**
Ensure you are in the `voice-call-service` directory.
2. **Setup Configuration**
Copy the example environment file and configure variables:
```bash
cp .env.example .env
```
*Note: Ensure `API_KEY` is at least 32 characters long.*
3. **Install Dependencies**
```bash
npm install
```
4. **Run Server**
Since Node.js 20.6.0+, you can load `.env` files natively using the `--env-file` flag:
```bash
node --env-file=.env cmd/server/main.js
```
The signaling server will start and bind locally to `127.0.0.1:47880`.
## Docker Deployment
Deploying with Docker isolates dependencies and bounds resources to 128MB.
1. **Deploy Command**
Run the deployment automation script:
```bash
./deploy.sh
```
This script will verify your configuration, build a secure multi-stage container running under user `node` (UID 1000), deploy it in host networking mode, and verify the service's health.
2. **Manual Docker Compose Commands**
Alternatively, you can build and run it manually:
```bash
docker-compose up -d --build
```
## Nginx Configuration
An Nginx configuration block should be configured on your CloudPanel/hosting server for `calls.intaleqapp.com`. It proxies connections to localhost and processes WebSocket handshakes:
```nginx
server {
listen 80;
server_name calls.intaleqapp.com;
location / {
proxy_pass http://127.0.0.1:47880;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Read timeout management
proxy_read_timeout 120s;
proxy_send_timeout 120s;
}
}
```
## Environment Variables
| Variable | Description | Default |
| :--- | :--- | :--- |
| `SERVER_ADDR` | Local binding interface and port | `127.0.0.1:47880` |
| `API_KEY` | Secret key for authenticating HTTP session creation | None (min 32 chars) |
| `LOG_LEVEL` | Level of logging output (`debug`, `info`, `warn`, `error`) | `info` |
| `MAX_MESSAGE_BYTES` | Maximum payload size allowed for WebSocket frames | `4096` |
| `HEARTBEAT_INTERVAL` | Keepalive ping interval | `20s` |
| `HEARTBEAT_TIMEOUT` | Connection death timeout | `30s` |
| `SESSION_DURATION` | Hard limit on call length before auto-termination | `60s` |
| `RATE_LIMIT_PER_MIN` | Maximum connections allowed per IP per minute | `10` |
| `ENVIRONMENT` | Target environment designation | `production` |
| `DB_HOST` | MySQL database host address | `127.0.0.1` |
| `DB_PORT` | MySQL database connection port | `3306` |
| `DB_DATABASE` | MySQL database name | `callDB` |
| `DB_USERNAME` | MySQL database username | None |
| `DB_PASSWORD` | MySQL database password | None |
## Database Logging
The signaling server integrates with a MySQL database to log call connectivity metrics.
The system automatically creates a `call_logs` table on startup (defined in `database.sql`).
### Call Logs Schema
| Column | Type | Description |
| :--- | :--- | :--- |
| `id` | `INT` | Primary Key, Auto Increment |
| `session_id` | `VARCHAR(36)` | Unique Ephemeral Session ID |
| `ride_id` | `VARCHAR(255)` | Active Ride ID |
| `driver_id` | `VARCHAR(255)` | Registered Driver ID |
| `passenger_id` | `VARCHAR(255)` | Registered Passenger ID |
| `status` | `VARCHAR(50)` | Call state (`created`, `active`, `ended`) |
| `initiated_by` | `VARCHAR(255)` | User ID of the participant who started the connection (sent first offer) |
| `end_reason` | `VARCHAR(255)` | Call termination reason (e.g., `user_terminated`, `max_duration_reached`, `disconnect_driver`, `disconnect_passenger`) |
| `created_at` | `TIMESTAMP` | Time when session was pre-created |
| `connected_at` | `TIMESTAMP` | Time when both parties connected and call started |
| `ended_at` | `TIMESTAMP` | Time when call was terminated |
## HTTP Session Pre-creation API
The main application backend (PHP) registers a session before allowing call access.
### Create Call Session
* **Route**: `POST /sessions`
* **Headers**:
* `X-API-Key: YOUR_API_KEY_HERE`
* `Content-Type: application/json`
* **Request Body**:
```json
{
"ride_id": "ride_456",
"driver_id": "user_123",
"passenger_id": "user_789"
}
```
* **Response Body (200 OK)**:
```json
{
"session_id": "3b2e7c4f-95a2-4a0b-99f6-fc935d0a4461",
"ride_id": "ride_456",
"expires_in": 60
}
```
## WebSocket Protocol Reference
Clients communicate with the backend at `ws://calls.intaleqapp.com/ws`.
### Client-to-Server Messages
1. **Authenticate (MUST be sent first)**
```json
{
"type": "authenticate",
"session_id": "3b2e7c4f-95a2-4a0b-99f6-fc935d0a4461",
"user_id": "user_123"
}
```
2. **Offer WebRTC Payload**
```json
{"type": "offer", "sdp": "v=0\r\n..."}
```
3. **Answer WebRTC Payload**
```json
{"type": "answer", "sdp": "v=0\r\n..."}
```
4. **ICE Candidate WebRTC Payload**
```json
{"type": "ice_candidate", "candidate": {"candidate": "...", "sdpMid": "0", "sdpMLineIndex": 0}}
```
5. **Heartbeat**
```json
{"type": "heartbeat"}
```
6. **End Call**
```json
{"type": "end_call"}
```
### Server-to-Client Messages
1. **Authentication Success Alert**
```json
{"type": "authenticated", "user_id": "user_123"}
```
2. **Session Joined (Active status)**
```json
{"type": "session_joined", "session_id": "sess_abc", "ride_id": "ride_456"}
```
3. **Peer Joined Notification**
```json
{"type": "participant_joined", "role": "passenger"}
```
4. **Relayed WebRTC Offer**
```json
{"type": "offer", "sdp": "v=0\r\n..."}
```
5. **Relayed WebRTC Answer**
```json
{"type": "answer", "sdp": "v=0\r\n..."}
```
6. **Relayed ICE Candidate**
```json
{"type": "ice_candidate", "candidate": {"candidate": "...", "sdpMid": "0", "sdpMLineIndex": 0}}
```
7. **Peer Disconnected Alert**
```json
{"type": "participant_left", "role": "driver"}
```
8. **Call Duration Timeout Alert**
```json
{"type": "call_timeout", "reason": "max_duration_reached"}
```
9. **Call Terminated Alert**
```json
{"type": "call_ended", "reason": "user_terminated"}
```
10. **Heartbeat Reply (Pong)**
```json
{"type": "pong"}
```
11. **Unauthorized Connection Error**
```json
{"type": "unauthorized", "reason": "session_not_found"}
```
12. **General Error Message**
```json
{"type": "error", "code": "session_not_found", "message": "No active session for this ride"}
```
## Security Notes
1. **Access Control**
Only your trusted main backend can create call sessions, verified via the `X-API-Key` header.
Mobile clients only receive the ephemeral `session_id` and can only authenticate under their pre-registered `user_id` context.
2. **Encryption**
SSL (WSS) is terminated at Nginx. Internal network loops (from Nginx to 47880) run local loopbacks.