245 lines
7.7 KiB
Markdown
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.
|