diff --git a/.env.example b/.env.example index aebf1f2..2d3b82e 100644 --- a/.env.example +++ b/.env.example @@ -55,4 +55,13 @@ TRUST_PROXY=FALSE ## CORS ## # OPTIONAL, CORS ALLOWED ORIGINS (comma separated), DEFAULT ALLOWS ALL -# ALLOWED_ORIGINS=http://localhost:3000,http://example.com \ No newline at end of file +# 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= \ No newline at end of file diff --git a/README.md b/README.md index bc596bf..dfa1e3e 100644 --- a/README.md +++ b/README.md @@ -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). +### 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 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. diff --git a/docker-compose.yml b/docker-compose.yml index 0cc4b29..c900c36 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,16 @@ services: env_file: .env volumes: - ./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 # healthcheck: # test: ["CMD", "curl", "-f", "http://localhost:3000/health"] @@ -20,3 +30,17 @@ services: # timeout: 10s # retries: 3 # 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 diff --git a/src/config.js b/src/config.js index da03b41..625dc6c 100644 --- a/src/config.js +++ b/src/config.js @@ -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 basePath = process.env.BASE_PATH || '/' 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 = { servicePort, @@ -49,5 +52,8 @@ module.exports = { enableWebSocket, autoStartSessions, basePath, - trustProxy + trustProxy, + proxyUrl, + proxyUsername, + proxyPassword } diff --git a/src/sessions.js b/src/sessions.js index 44c82c9..68973a0 100644 --- a/src/sessions.js +++ b/src/sessions.js @@ -2,7 +2,7 @@ const { Client, LocalAuth } = require('whatsapp-web.js') const fs = require('fs') const path = require('path') 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 { logger } = require('./logger') const { initWebSocketServer, terminateWebSocketServer, triggerWebSocket } = require('./websocket') @@ -138,12 +138,18 @@ const setupSession = async (sessionId) => { '--use-mock-keychain', '--disable-setuid-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 } + if (proxyUrl && proxyUsername != null && proxyPassword != null) { + clientOptions.proxyAuthentication = { username: proxyUsername, password: proxyPassword } + } + if (webVersion) { clientOptions.webVersion = webVersion switch (webVersionCacheType.toLowerCase()) {