Skip to content

Commit 62860a6

Browse files
authored
feat: add reverse proxy support with configurable base path and trust proxy settings (#47)
1 parent 7015416 commit 62860a6

6 files changed

Lines changed: 204 additions & 9 deletions

File tree

.env.example

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,8 @@ AUTO_START_SESSIONS=TRUE # OPTIONAL, AUTO START SESSIONS ON SERVER STARTUP(TRUE
2424

2525
## Session File Storage ##
2626
SESSIONS_PATH=./sessions # OPTIONAL
27-
ENABLE_SWAGGER_ENDPOINT=TRUE # OPTIONAL, ENABLE SWAGGER ENDPOINT FOR API DOCUMENTATION
27+
ENABLE_SWAGGER_ENDPOINT=TRUE # OPTIONAL, ENABLE SWAGGER ENDPOINT FOR API DOCUMENTATION
28+
29+
## Reverse Proxy / Load Balancer ##
30+
BASE_PATH= # OPTIONAL, BASE PATH FOR MOUNTING ROUTES (e.g., /api/v1/whatsapp)
31+
TRUST_PROXY=FALSE # OPTIONAL, ENABLE WHEN BEHIND REVERSE PROXY/LOAD BALANCER

REVERSE_PROXY_SETUP.md

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# Reverse Proxy / Kong Setup Guide
2+
3+
This document provides configuration examples for deploying WWebJS API behind reverse proxies like Kong, Nginx, or other load balancers.
4+
5+
## Environment Variables
6+
7+
The following environment variables have been added to support reverse proxy deployments:
8+
9+
```bash
10+
# Base path for mounting all routes (optional)
11+
BASE_PATH=/api/v1/whatsapp
12+
13+
# Enable trust proxy for proper IP forwarding (required for reverse proxy)
14+
TRUST_PROXY=true
15+
```
16+
17+
## Kong Configuration
18+
19+
### Basic Kong Route Setup
20+
21+
```yaml
22+
# Kong route configuration
23+
routes:
24+
- name: wwebjs-api
25+
paths: ["/api/v1/whatsapp"]
26+
strip_path: true # Important: removes the prefix before forwarding
27+
preserve_host: false
28+
protocols: ["http", "https"]
29+
service: wwebjs-service
30+
31+
services:
32+
- name: wwebjs-service
33+
url: http://wwebjs-api:3000
34+
connect_timeout: 60000
35+
write_timeout: 60000
36+
read_timeout: 60000
37+
```
38+
39+
### Kong with WebSocket Support
40+
41+
```yaml
42+
# Kong route for WebSocket connections
43+
routes:
44+
- name: wwebjs-websocket
45+
paths: ["/api/v1/whatsapp/ws"]
46+
strip_path: true
47+
protocols: ["ws", "wss"]
48+
service: wwebjs-websocket-service
49+
50+
services:
51+
- name: wwebjs-websocket-service
52+
url: http://wwebjs-api:3000
53+
```
54+
55+
## Nginx Configuration
56+
57+
```nginx
58+
upstream wwebjs_backend {
59+
server wwebjs-api:3000;
60+
}
61+
62+
server {
63+
listen 80;
64+
server_name api.yourdomain.com;
65+
66+
location /api/v1/whatsapp/ {
67+
proxy_pass http://wwebjs_backend/;
68+
proxy_set_header Host $host;
69+
proxy_set_header X-Real-IP $remote_addr;
70+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
71+
proxy_set_header X-Forwarded-Proto $scheme;
72+
proxy_set_header X-Forwarded-Host $host;
73+
74+
# WebSocket support
75+
proxy_http_version 1.1;
76+
proxy_set_header Upgrade $http_upgrade;
77+
proxy_set_header Connection "upgrade";
78+
79+
# Timeouts for long-running operations
80+
proxy_connect_timeout 60s;
81+
proxy_send_timeout 60s;
82+
proxy_read_timeout 60s;
83+
}
84+
}
85+
```
86+
87+
## Docker Compose with Reverse Proxy
88+
89+
```yaml
90+
version: '3.8'
91+
92+
services:
93+
wwebjs-api:
94+
image: avoylenko/wwebjs-api:latest
95+
container_name: wwebjs-api
96+
restart: always
97+
environment:
98+
# Reverse proxy configuration
99+
- BASE_PATH=/api/v1/whatsapp
100+
- TRUST_PROXY=true
101+
102+
# Other configurations
103+
- BASE_WEBHOOK_URL=https://api.yourdomain.com/api/v1/whatsapp/localCallbackExample
104+
- API_KEY=your_secure_api_key
105+
- ENABLE_LOCAL_CALLBACK_EXAMPLE=false
106+
- ENABLE_SWAGGER_ENDPOINT=true
107+
volumes:
108+
- ./sessions:/usr/src/app/sessions
109+
networks:
110+
- api-network
111+
112+
nginx:
113+
image: nginx:alpine
114+
container_name: nginx-proxy
115+
ports:
116+
- "80:80"
117+
- "443:443"
118+
volumes:
119+
- ./nginx.conf:/etc/nginx/nginx.conf
120+
depends_on:
121+
- wwebjs-api
122+
networks:
123+
- api-network
124+
125+
networks:
126+
api-network:
127+
driver: bridge
128+
```
129+
130+
## API Endpoint Examples
131+
132+
With `BASE_PATH=/api/v1/whatsapp` configured:
133+
134+
### Original endpoints:
135+
- `GET /session/start/ABCD`
136+
- `GET /client/getContacts/ABCD`
137+
- `WebSocket: ws://localhost:3000/ws/ABCD`
138+
139+
### Behind reverse proxy:
140+
- `GET https://api.yourdomain.com/api/v1/whatsapp/session/start/ABCD`
141+
- `GET https://api.yourdomain.com/api/v1/whatsapp/client/getContacts/ABCD`
142+
- `WebSocket: wss://api.yourdomain.com/api/v1/whatsapp/ws/ABCD`
143+
144+
## Important Notes
145+
146+
1. **Strip Path**: Always configure your reverse proxy to strip the base path before forwarding to the application
147+
2. **Trust Proxy**: Set `TRUST_PROXY=true` to ensure proper IP detection for rate limiting
148+
3. **WebSocket Headers**: Ensure `X-Forwarded-Host` header is properly forwarded for WebSocket connections
149+
4. **Timeouts**: Configure appropriate timeouts for WhatsApp operations which can take time
150+
5. **HTTPS**: Use HTTPS in production and update `BASE_WEBHOOK_URL` accordingly
151+
152+
## Troubleshooting
153+
154+
### Common Issues:
155+
156+
1. **404 Errors**: Check if `strip_path` is enabled in your reverse proxy
157+
2. **WebSocket Connection Failed**: Ensure WebSocket upgrade headers are properly forwarded
158+
3. **Rate Limiting Issues**: Verify `TRUST_PROXY=true` is set and `X-Forwarded-For` header is forwarded
159+
4. **Webhook Callbacks**: Update `BASE_WEBHOOK_URL` to use the external domain with base path
160+
161+
### Testing:
162+
163+
```bash
164+
# Test API endpoint
165+
curl https://api.yourdomain.com/api/v1/whatsapp/ping
166+
167+
# Test WebSocket connection
168+
wscat -c wss://api.yourdomain.com/api/v1/whatsapp/ws/test
169+
```

src/app.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
require('./routes')
22
const express = require('express')
33
const { routes } = require('./routes')
4-
const { maxAttachmentSize } = require('./config')
4+
const { maxAttachmentSize, basePath, trustProxy } = require('./config')
55

66
const app = express()
77

88
// Initialize Express app
99
app.disable('x-powered-by')
10+
11+
// Configure trust proxy for reverse proxy compatibility
12+
if (trustProxy) {
13+
app.set('trust proxy', true)
14+
}
15+
1016
app.use(express.json({ limit: maxAttachmentSize + 1000000 }))
1117
app.use(express.urlencoded({ limit: maxAttachmentSize + 1000000, extended: true }))
12-
app.use('/', routes)
18+
19+
// Mount routes with configurable base path
20+
const mountPath = basePath || '/'
21+
app.use(mountPath, routes)
1322

1423
module.exports = app

src/config.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ const logLevel = process.env.LOG_LEVEL || 'info'
2222
const enableWebHook = process.env.ENABLE_WEBHOOK ? (process.env.ENABLE_WEBHOOK).toLowerCase() === 'true' : true
2323
const enableWebSocket = process.env.ENABLE_WEBSOCKET ? (process.env.ENABLE_WEBSOCKET).toLowerCase() === 'true' : false
2424
const autoStartSessions = process.env.AUTO_START_SESSIONS ? (process.env.AUTO_START_SESSIONS).toLowerCase() === 'true' : true
25+
const basePath = process.env.BASE_PATH || ''
26+
const trustProxy = process.env.TRUST_PROXY ? (process.env.TRUST_PROXY).toLowerCase() === 'true' : false
2527

2628
module.exports = {
2729
sessionFolderPath,
@@ -43,5 +45,7 @@ module.exports = {
4345
logLevel,
4446
enableWebHook,
4547
enableWebSocket,
46-
autoStartSessions
48+
autoStartSessions,
49+
basePath,
50+
trustProxy
4751
}

src/middleware.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@ const sessionValidation = async (req, res, next) => {
7272
const rateLimiter = rateLimiting({
7373
limit: rateLimitMax,
7474
windowMs: rateLimitWindowMs,
75-
message: "You can't make any more requests at the moment. Try again later"
75+
message: "You can't make any more requests at the moment. Try again later",
76+
// Use real client IP when behind reverse proxy
77+
keyGenerator: (req) => {
78+
return req.ip || req.connection.remoteAddress
79+
}
7680
})
7781

7882
const sessionSwagger = async (req, res, next) => {

src/websocket.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const { WebSocketServer } = require('ws')
2-
const { enableWebSocket } = require('./config')
2+
const { enableWebSocket, basePath } = require('./config')
33
const { logger } = require('./logger')
44
const wssMap = new Map()
55

@@ -52,10 +52,15 @@ const triggerWebSocket = (sessionId, dataType, data) => {
5252
}
5353

5454
const handleUpgrade = (request, socket, head) => {
55-
const baseUrl = 'ws://' + request.headers.host + '/'
55+
const host = request.headers['x-forwarded-host'] || request.headers.host
56+
const baseUrl = 'ws://' + host + '/'
5657
const { pathname } = new URL(request.url, baseUrl)
57-
if (pathname.startsWith('/ws/')) {
58-
const sessionId = pathname.split('/')[2]
58+
59+
// Handle base path for WebSocket connections
60+
const wsPath = basePath ? `${basePath}/ws/` : '/ws/'
61+
if (pathname.startsWith(wsPath)) {
62+
const pathParts = pathname.split('/')
63+
const sessionId = basePath ? pathParts[3] : pathParts[2]
5964
const server = wssMap.get(sessionId)
6065
if (server) {
6166
server.handleUpgrade(request, socket, head, (ws) => {

0 commit comments

Comments
 (0)