feat: support outbound proxy for Chromium sessions (#131)
* feat: support outbound proxy for Chromium sessions Routes WhatsApp Web traffic through an HTTP/HTTPS/SOCKS5 proxy when PROXY_URL is set. Optional PROXY_API_KEY enables auth by passing the key as the basic-auth username (the format API-key proxies expect). No behavior change when unset. Useful for deployments behind egress filters or where outbound WhatsApp traffic must originate from a specific IP. * docs: 📝 rewrite proxy auth part * fix: 🐛 move api key value to passwd * fix: 💄 change var naming * fix: 🎨 change var ref in docs and data passing
This commit is contained in:
parent
7a0cd227fb
commit
132bcf6ee3
5 changed files with 54 additions and 4 deletions
11
.env.example
11
.env.example
|
|
@ -55,4 +55,13 @@ TRUST_PROXY=FALSE
|
||||||
|
|
||||||
## CORS ##
|
## CORS ##
|
||||||
# OPTIONAL, CORS ALLOWED ORIGINS (comma separated), DEFAULT ALLOWS ALL
|
# OPTIONAL, CORS ALLOWED ORIGINS (comma separated), DEFAULT ALLOWS ALL
|
||||||
# ALLOWED_ORIGINS=http://localhost:3000,http://example.com
|
# ALLOWED_ORIGINS=http://localhost:3000,http://example.com
|
||||||
|
|
||||||
|
## Outbound Proxy ##
|
||||||
|
# OPTIONAL, ROUTES CHROMIUM (WHATSAPP WEB) TRAFFIC THROUGH AN OUTBOUND PROXY.
|
||||||
|
# ACCEPTED FORMATS: http://host:port, https://host:port, socks5://host:port
|
||||||
|
# PROXY_URL=http://10.0.0.10:8118
|
||||||
|
# OPTIONAL, PROXY BASIC-AUTH USERNAME
|
||||||
|
# PROXY_USERNAME=
|
||||||
|
# OPTIONAL, PROXY BASIC-AUTH PASSWORD
|
||||||
|
# PROXY_PASSWORD=
|
||||||
|
|
@ -163,6 +163,11 @@ By setting the `ENABLE_WEBHOOK` environment to `FALSE` you can disable webhook d
|
||||||
|
|
||||||
In order to validate a new WhatsApp Web instance you need to scan the QR code using your mobile phone. Official documentation can be found at (https://faq.whatsapp.com/1079327266110265/?cms_platform=android) page. The service itself delivers the QR code content as a webhook event or you can use the REST endpoints (`/session/qr/:sessionId` or `/session/qr/:sessionId/image` to get the QR code as a png image).
|
In order to validate a new WhatsApp Web instance you need to scan the QR code using your mobile phone. Official documentation can be found at (https://faq.whatsapp.com/1079327266110265/?cms_platform=android) page. The service itself delivers the QR code content as a webhook event or you can use the REST endpoints (`/session/qr/:sessionId` or `/session/qr/:sessionId/image` to get the QR code as a png image).
|
||||||
|
|
||||||
|
### Outbound Proxy
|
||||||
|
The Chromium instance that powers each session can be routed through an outbound proxy by setting the `PROXY_URL` environment variable (e.g. `http://10.0.0.10:8118`, `https://...`, or `socks5://...`). When `PROXY_URL` is empty, sessions connect directly with no behavior change.
|
||||||
|
|
||||||
|
If the proxy requires authentication, set `PROXY_USERNAME` and `PROXY_PASSWORD`. When both are defined they are forwarded to Chromium as HTTP Basic auth via puppeteer's `proxyAuthentication`. For API-key vendor proxies, use the vendor-specified username (often `api-key`) and the key itself as the password.
|
||||||
|
|
||||||
### WebSocket mode
|
### WebSocket mode
|
||||||
The service can dispatch realtime events through websocket connection. By default, the websocket is not activated, so you need manually set the `ENABLE_WEBSOCKET` environment variable to activate it. The server activates a new websocket instance per each active session. The websocket path is `/ws/:sessionId`, where sessionId is your configured session name. The websocket supports ping/pong scheme to keep the socket running.
|
The service can dispatch realtime events through websocket connection. By default, the websocket is not activated, so you need manually set the `ENABLE_WEBSOCKET` environment variable to activate it. The server activates a new websocket instance per each active session. The websocket path is `/ws/:sessionId`, where sessionId is your configured session name. The websocket supports ping/pong scheme to keep the socket running.
|
||||||
The below example shows how to receive the events for **test** session.
|
The below example shows how to receive the events for **test** session.
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,16 @@ services:
|
||||||
env_file: .env
|
env_file: .env
|
||||||
volumes:
|
volumes:
|
||||||
- ./sessions:/usr/src/app/sessions
|
- ./sessions:/usr/src/app/sessions
|
||||||
|
# Optional: route Chromium (WhatsApp Web) traffic through an outbound proxy.
|
||||||
|
# Set PROXY_URL (and optionally PROXY_USERNAME / PROXY_PASSWORD for HTTP
|
||||||
|
# Basic auth) in .env, or uncomment the block below to point at the
|
||||||
|
# bundled tinyproxy service.
|
||||||
|
# environment:
|
||||||
|
# PROXY_URL: http://tinyproxy:8888
|
||||||
|
# # PROXY_USERNAME: api-key
|
||||||
|
# # PROXY_PASSWORD: your-api-key
|
||||||
|
# depends_on:
|
||||||
|
# - tinyproxy
|
||||||
# Optional healthcheck
|
# Optional healthcheck
|
||||||
# healthcheck:
|
# healthcheck:
|
||||||
# test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
# test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
||||||
|
|
@ -20,3 +30,17 @@ services:
|
||||||
# timeout: 10s
|
# timeout: 10s
|
||||||
# retries: 3
|
# retries: 3
|
||||||
# start_period: 40s
|
# start_period: 40s
|
||||||
|
|
||||||
|
# Optional: local HTTP proxy for testing the PROXY_URL feature end-to-end.
|
||||||
|
# Uncomment along with the api `environment` / `depends_on` blocks above.
|
||||||
|
# tinyproxy:
|
||||||
|
# image: vimagick/tinyproxy
|
||||||
|
# container_name: wwebjs-tinyproxy
|
||||||
|
# ports:
|
||||||
|
# - "8888:8888"
|
||||||
|
# environment:
|
||||||
|
# ALLOWED_NETWORKS: 0.0.0.0/0
|
||||||
|
# # Uncomment to test the PROXY_USERNAME / PROXY_PASSWORD path.
|
||||||
|
# # BASIC_AUTH_USER: "api-key"
|
||||||
|
# # BASIC_AUTH_PASSWORD: "your-api-key"
|
||||||
|
# restart: unless-stopped
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,9 @@ const enableWebSocket = process.env.ENABLE_WEBSOCKET ? (process.env.ENABLE_WEBSO
|
||||||
const autoStartSessions = process.env.AUTO_START_SESSIONS ? (process.env.AUTO_START_SESSIONS).toLowerCase() === 'true' : true
|
const autoStartSessions = process.env.AUTO_START_SESSIONS ? (process.env.AUTO_START_SESSIONS).toLowerCase() === 'true' : true
|
||||||
const basePath = process.env.BASE_PATH || '/'
|
const basePath = process.env.BASE_PATH || '/'
|
||||||
const trustProxy = process.env.TRUST_PROXY ? (process.env.TRUST_PROXY).toLowerCase() === 'true' : false
|
const trustProxy = process.env.TRUST_PROXY ? (process.env.TRUST_PROXY).toLowerCase() === 'true' : false
|
||||||
|
const proxyUrl = process.env.PROXY_URL || null
|
||||||
|
const proxyUsername = process.env.PROXY_USERNAME ?? null
|
||||||
|
const proxyPassword = process.env.PROXY_PASSWORD ?? null
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
servicePort,
|
servicePort,
|
||||||
|
|
@ -49,5 +52,8 @@ module.exports = {
|
||||||
enableWebSocket,
|
enableWebSocket,
|
||||||
autoStartSessions,
|
autoStartSessions,
|
||||||
basePath,
|
basePath,
|
||||||
trustProxy
|
trustProxy,
|
||||||
|
proxyUrl,
|
||||||
|
proxyUsername,
|
||||||
|
proxyPassword
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ const { Client, LocalAuth } = require('whatsapp-web.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const sessions = new Map()
|
const sessions = new Map()
|
||||||
const { baseWebhookURL, sessionFolderPath, maxAttachmentSize, setMessagesAsSeen, webVersion, webVersionCacheType, recoverSessions, chromeBin, headless, releaseBrowserLock } = require('./config')
|
const { baseWebhookURL, sessionFolderPath, maxAttachmentSize, setMessagesAsSeen, webVersion, webVersionCacheType, recoverSessions, chromeBin, headless, releaseBrowserLock, proxyUrl, proxyUsername, proxyPassword } = require('./config')
|
||||||
const { triggerWebhook, waitForNestedObject, isEventEnabled, sendMessageSeenStatus, sleep, patchWWebLibrary } = require('./utils')
|
const { triggerWebhook, waitForNestedObject, isEventEnabled, sendMessageSeenStatus, sleep, patchWWebLibrary } = require('./utils')
|
||||||
const { logger } = require('./logger')
|
const { logger } = require('./logger')
|
||||||
const { initWebSocketServer, terminateWebSocketServer, triggerWebSocket } = require('./websocket')
|
const { initWebSocketServer, terminateWebSocketServer, triggerWebSocket } = require('./websocket')
|
||||||
|
|
@ -138,12 +138,18 @@ const setupSession = async (sessionId) => {
|
||||||
'--use-mock-keychain',
|
'--use-mock-keychain',
|
||||||
'--disable-setuid-sandbox',
|
'--disable-setuid-sandbox',
|
||||||
'--no-sandbox',
|
'--no-sandbox',
|
||||||
'--disable-blink-features=AutomationControlled'
|
'--disable-blink-features=AutomationControlled',
|
||||||
|
// Route Chromium outbound traffic through PROXY_URL when configured.
|
||||||
|
...(proxyUrl ? [`--proxy-server=${proxyUrl}`] : [])
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
authStrategy: localAuth
|
authStrategy: localAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (proxyUrl && proxyUsername != null && proxyPassword != null) {
|
||||||
|
clientOptions.proxyAuthentication = { username: proxyUsername, password: proxyPassword }
|
||||||
|
}
|
||||||
|
|
||||||
if (webVersion) {
|
if (webVersion) {
|
||||||
clientOptions.webVersion = webVersion
|
clientOptions.webVersion = webVersion
|
||||||
switch (webVersionCacheType.toLowerCase()) {
|
switch (webVersionCacheType.toLowerCase()) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue