feat: add reverse proxy support with configurable base path and trust proxy settings (#47)
This commit is contained in:
parent
70154165fa
commit
62860a658c
6 changed files with 204 additions and 9 deletions
|
|
@ -24,4 +24,8 @@ AUTO_START_SESSIONS=TRUE # OPTIONAL, AUTO START SESSIONS ON SERVER STARTUP(TRUE
|
|||
|
||||
## Session File Storage ##
|
||||
SESSIONS_PATH=./sessions # OPTIONAL
|
||||
ENABLE_SWAGGER_ENDPOINT=TRUE # OPTIONAL, ENABLE SWAGGER ENDPOINT FOR API DOCUMENTATION
|
||||
ENABLE_SWAGGER_ENDPOINT=TRUE # OPTIONAL, ENABLE SWAGGER ENDPOINT FOR API DOCUMENTATION
|
||||
|
||||
## Reverse Proxy / Load Balancer ##
|
||||
BASE_PATH= # OPTIONAL, BASE PATH FOR MOUNTING ROUTES (e.g., /api/v1/whatsapp)
|
||||
TRUST_PROXY=FALSE # OPTIONAL, ENABLE WHEN BEHIND REVERSE PROXY/LOAD BALANCER
|
||||
169
REVERSE_PROXY_SETUP.md
Normal file
169
REVERSE_PROXY_SETUP.md
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
# Reverse Proxy / Kong Setup Guide
|
||||
|
||||
This document provides configuration examples for deploying WWebJS API behind reverse proxies like Kong, Nginx, or other load balancers.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The following environment variables have been added to support reverse proxy deployments:
|
||||
|
||||
```bash
|
||||
# Base path for mounting all routes (optional)
|
||||
BASE_PATH=/api/v1/whatsapp
|
||||
|
||||
# Enable trust proxy for proper IP forwarding (required for reverse proxy)
|
||||
TRUST_PROXY=true
|
||||
```
|
||||
|
||||
## Kong Configuration
|
||||
|
||||
### Basic Kong Route Setup
|
||||
|
||||
```yaml
|
||||
# Kong route configuration
|
||||
routes:
|
||||
- name: wwebjs-api
|
||||
paths: ["/api/v1/whatsapp"]
|
||||
strip_path: true # Important: removes the prefix before forwarding
|
||||
preserve_host: false
|
||||
protocols: ["http", "https"]
|
||||
service: wwebjs-service
|
||||
|
||||
services:
|
||||
- name: wwebjs-service
|
||||
url: http://wwebjs-api:3000
|
||||
connect_timeout: 60000
|
||||
write_timeout: 60000
|
||||
read_timeout: 60000
|
||||
```
|
||||
|
||||
### Kong with WebSocket Support
|
||||
|
||||
```yaml
|
||||
# Kong route for WebSocket connections
|
||||
routes:
|
||||
- name: wwebjs-websocket
|
||||
paths: ["/api/v1/whatsapp/ws"]
|
||||
strip_path: true
|
||||
protocols: ["ws", "wss"]
|
||||
service: wwebjs-websocket-service
|
||||
|
||||
services:
|
||||
- name: wwebjs-websocket-service
|
||||
url: http://wwebjs-api:3000
|
||||
```
|
||||
|
||||
## Nginx Configuration
|
||||
|
||||
```nginx
|
||||
upstream wwebjs_backend {
|
||||
server wwebjs-api:3000;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name api.yourdomain.com;
|
||||
|
||||
location /api/v1/whatsapp/ {
|
||||
proxy_pass http://wwebjs_backend/;
|
||||
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;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
|
||||
# WebSocket support
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
# Timeouts for long-running operations
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Docker Compose with Reverse Proxy
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
wwebjs-api:
|
||||
image: avoylenko/wwebjs-api:latest
|
||||
container_name: wwebjs-api
|
||||
restart: always
|
||||
environment:
|
||||
# Reverse proxy configuration
|
||||
- BASE_PATH=/api/v1/whatsapp
|
||||
- TRUST_PROXY=true
|
||||
|
||||
# Other configurations
|
||||
- BASE_WEBHOOK_URL=https://api.yourdomain.com/api/v1/whatsapp/localCallbackExample
|
||||
- API_KEY=your_secure_api_key
|
||||
- ENABLE_LOCAL_CALLBACK_EXAMPLE=false
|
||||
- ENABLE_SWAGGER_ENDPOINT=true
|
||||
volumes:
|
||||
- ./sessions:/usr/src/app/sessions
|
||||
networks:
|
||||
- api-network
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: nginx-proxy
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||
depends_on:
|
||||
- wwebjs-api
|
||||
networks:
|
||||
- api-network
|
||||
|
||||
networks:
|
||||
api-network:
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
## API Endpoint Examples
|
||||
|
||||
With `BASE_PATH=/api/v1/whatsapp` configured:
|
||||
|
||||
### Original endpoints:
|
||||
- `GET /session/start/ABCD`
|
||||
- `GET /client/getContacts/ABCD`
|
||||
- `WebSocket: ws://localhost:3000/ws/ABCD`
|
||||
|
||||
### Behind reverse proxy:
|
||||
- `GET https://api.yourdomain.com/api/v1/whatsapp/session/start/ABCD`
|
||||
- `GET https://api.yourdomain.com/api/v1/whatsapp/client/getContacts/ABCD`
|
||||
- `WebSocket: wss://api.yourdomain.com/api/v1/whatsapp/ws/ABCD`
|
||||
|
||||
## Important Notes
|
||||
|
||||
1. **Strip Path**: Always configure your reverse proxy to strip the base path before forwarding to the application
|
||||
2. **Trust Proxy**: Set `TRUST_PROXY=true` to ensure proper IP detection for rate limiting
|
||||
3. **WebSocket Headers**: Ensure `X-Forwarded-Host` header is properly forwarded for WebSocket connections
|
||||
4. **Timeouts**: Configure appropriate timeouts for WhatsApp operations which can take time
|
||||
5. **HTTPS**: Use HTTPS in production and update `BASE_WEBHOOK_URL` accordingly
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues:
|
||||
|
||||
1. **404 Errors**: Check if `strip_path` is enabled in your reverse proxy
|
||||
2. **WebSocket Connection Failed**: Ensure WebSocket upgrade headers are properly forwarded
|
||||
3. **Rate Limiting Issues**: Verify `TRUST_PROXY=true` is set and `X-Forwarded-For` header is forwarded
|
||||
4. **Webhook Callbacks**: Update `BASE_WEBHOOK_URL` to use the external domain with base path
|
||||
|
||||
### Testing:
|
||||
|
||||
```bash
|
||||
# Test API endpoint
|
||||
curl https://api.yourdomain.com/api/v1/whatsapp/ping
|
||||
|
||||
# Test WebSocket connection
|
||||
wscat -c wss://api.yourdomain.com/api/v1/whatsapp/ws/test
|
||||
```
|
||||
13
src/app.js
13
src/app.js
|
|
@ -1,14 +1,23 @@
|
|||
require('./routes')
|
||||
const express = require('express')
|
||||
const { routes } = require('./routes')
|
||||
const { maxAttachmentSize } = require('./config')
|
||||
const { maxAttachmentSize, basePath, trustProxy } = require('./config')
|
||||
|
||||
const app = express()
|
||||
|
||||
// Initialize Express app
|
||||
app.disable('x-powered-by')
|
||||
|
||||
// Configure trust proxy for reverse proxy compatibility
|
||||
if (trustProxy) {
|
||||
app.set('trust proxy', true)
|
||||
}
|
||||
|
||||
app.use(express.json({ limit: maxAttachmentSize + 1000000 }))
|
||||
app.use(express.urlencoded({ limit: maxAttachmentSize + 1000000, extended: true }))
|
||||
app.use('/', routes)
|
||||
|
||||
// Mount routes with configurable base path
|
||||
const mountPath = basePath || '/'
|
||||
app.use(mountPath, routes)
|
||||
|
||||
module.exports = app
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ const logLevel = process.env.LOG_LEVEL || 'info'
|
|||
const enableWebHook = process.env.ENABLE_WEBHOOK ? (process.env.ENABLE_WEBHOOK).toLowerCase() === 'true' : true
|
||||
const enableWebSocket = process.env.ENABLE_WEBSOCKET ? (process.env.ENABLE_WEBSOCKET).toLowerCase() === 'true' : false
|
||||
const autoStartSessions = process.env.AUTO_START_SESSIONS ? (process.env.AUTO_START_SESSIONS).toLowerCase() === 'true' : true
|
||||
const basePath = process.env.BASE_PATH || ''
|
||||
const trustProxy = process.env.TRUST_PROXY ? (process.env.TRUST_PROXY).toLowerCase() === 'true' : false
|
||||
|
||||
module.exports = {
|
||||
sessionFolderPath,
|
||||
|
|
@ -43,5 +45,7 @@ module.exports = {
|
|||
logLevel,
|
||||
enableWebHook,
|
||||
enableWebSocket,
|
||||
autoStartSessions
|
||||
autoStartSessions,
|
||||
basePath,
|
||||
trustProxy
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,7 +72,11 @@ const sessionValidation = async (req, res, next) => {
|
|||
const rateLimiter = rateLimiting({
|
||||
limit: rateLimitMax,
|
||||
windowMs: rateLimitWindowMs,
|
||||
message: "You can't make any more requests at the moment. Try again later"
|
||||
message: "You can't make any more requests at the moment. Try again later",
|
||||
// Use real client IP when behind reverse proxy
|
||||
keyGenerator: (req) => {
|
||||
return req.ip || req.connection.remoteAddress
|
||||
}
|
||||
})
|
||||
|
||||
const sessionSwagger = async (req, res, next) => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
const { WebSocketServer } = require('ws')
|
||||
const { enableWebSocket } = require('./config')
|
||||
const { enableWebSocket, basePath } = require('./config')
|
||||
const { logger } = require('./logger')
|
||||
const wssMap = new Map()
|
||||
|
||||
|
|
@ -52,10 +52,15 @@ const triggerWebSocket = (sessionId, dataType, data) => {
|
|||
}
|
||||
|
||||
const handleUpgrade = (request, socket, head) => {
|
||||
const baseUrl = 'ws://' + request.headers.host + '/'
|
||||
const host = request.headers['x-forwarded-host'] || request.headers.host
|
||||
const baseUrl = 'ws://' + host + '/'
|
||||
const { pathname } = new URL(request.url, baseUrl)
|
||||
if (pathname.startsWith('/ws/')) {
|
||||
const sessionId = pathname.split('/')[2]
|
||||
|
||||
// Handle base path for WebSocket connections
|
||||
const wsPath = basePath ? `${basePath}/ws/` : '/ws/'
|
||||
if (pathname.startsWith(wsPath)) {
|
||||
const pathParts = pathname.split('/')
|
||||
const sessionId = basePath ? pathParts[3] : pathParts[2]
|
||||
const server = wssMap.get(sessionId)
|
||||
if (server) {
|
||||
server.handleUpgrade(request, socket, head, (ws) => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue