Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 169 additions & 0 deletions REVERSE_PROXY_SETUP.md
Original file line number Diff line number Diff line change
@@ -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 changes: 11 additions & 2 deletions src/app.js
Original file line number Diff line number Diff line change
@@ -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
6 changes: 5 additions & 1 deletion src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -43,5 +45,7 @@ module.exports = {
logLevel,
enableWebHook,
enableWebSocket,
autoStartSessions
autoStartSessions,
basePath,
trustProxy
}
6 changes: 5 additions & 1 deletion src/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
avoylenko marked this conversation as resolved.
}
})

const sessionSwagger = async (req, res, next) => {
Expand Down
13 changes: 9 additions & 4 deletions src/websocket.js
Original file line number Diff line number Diff line change
@@ -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()

Expand Down Expand Up @@ -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) => {
Expand Down