Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3cce2bb35f | ||
|
|
e080ce42ee | ||
|
|
5dbe41da78 | ||
|
|
eef3957d4f | ||
|
|
680f4596b9 | ||
|
|
8de344f220 | ||
|
|
8005846955 | ||
|
|
3edd60448b | ||
|
|
4a2fa5c076 | ||
|
|
96e9c2cc4e | ||
|
|
2089a1bb35 | ||
|
|
023c2718e2 | ||
|
|
f7aacb193a | ||
|
|
e710040904 | ||
|
|
fb12826801 | ||
|
|
833a70c6e0 | ||
|
|
c807f9021c | ||
|
|
46b66bcdf0 | ||
|
|
2ebade817e | ||
|
|
080342e619 | ||
|
|
75b06d3ba1 | ||
|
|
3b6fa3343d | ||
|
|
469cdc9b1d | ||
|
|
8a179e7194 | ||
|
|
c133795753 |
No files matched your search
+84
-168
@@ -1,191 +1,107 @@
|
||||
# WAHA Configuration
|
||||
# WAHA - WhatsApp HTTP API
|
||||
#
|
||||
# https://waha.devlike.pro/docs/how-to/config/
|
||||
#
|
||||
# Common
|
||||
#
|
||||
# If you know the right URL, you can use it here.
|
||||
# Use any port in WHATSAPP_API_PORT and domain in WHATSAPP_API_HOSTNAME
|
||||
# https://waha.devlike.pro/docs/how-to/config/#common
|
||||
|
||||
# ==================
|
||||
# ===== COMMON =====
|
||||
# ==================
|
||||
# Base URL for the API (used for webhooks, file URLs, etc.)
|
||||
WAHA_BASE_URL=http://localhost:3000
|
||||
|
||||
#WHATSAPP_API_SCHEMA=http
|
||||
#WHATSAPP_API_PORT=3000
|
||||
#WHATSAPP_API_HOSTNAME=localhost
|
||||
# Server configuration (if you need to customize hostname/port)
|
||||
# WHATSAPP_API_SCHEMA=http
|
||||
# WHATSAPP_API_PORT=3000
|
||||
# WHATSAPP_API_HOSTNAME=localhost
|
||||
|
||||
#
|
||||
# Set your timezone to see the right time on screenshots
|
||||
# Find your timezone name in the list
|
||||
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
#TZ=Europe/Dublin
|
||||
|
||||
#
|
||||
# Security
|
||||
#
|
||||
# https://waha.devlike.pro/docs/how-to/security/
|
||||
#
|
||||
WHATSAPP_API_KEY=admin
|
||||
# ====================
|
||||
# ===== SECURITY =====
|
||||
# ====================
|
||||
# "sha512:{SHA}" format - below is "admin" api key
|
||||
WAHA_API_KEY=sha512:c7ad44cbad762a5da0a452f9e854fdc1e0e7a52a38015f23f3eab1d80b931dd472634dfac71cd34ebc35d16ab7fb8a90c81f975113d6c7538dc69dd8de9077ec
|
||||
WAHA_DASHBOARD_ENABLED=True
|
||||
WAHA_DASHBOARD_USERNAME=admin
|
||||
WAHA_DASHBOARD_PASSWORD=admin
|
||||
#WHATSAPP_SWAGGER_USERNAME=admin
|
||||
#WHATSAPP_SWAGGER_PASSWORD=admin
|
||||
|
||||
#
|
||||
# Dashboard
|
||||
#
|
||||
# https://waha.devlike.pro/docs/how-to/waha-dashboard/#configuration
|
||||
# Enabled by default
|
||||
#WAHA_DASHBOARD_ENABLED=true
|
||||
#WAHA_DASHBOARD_USERNAME=admin
|
||||
#WAHA_DASHBOARD_PASSWORD=admin
|
||||
|
||||
#
|
||||
# Logging
|
||||
# https://waha.devlike.pro/docs/how-to/observability/#logging
|
||||
#
|
||||
# Set log format to JSON if you consume logs with log management systems
|
||||
WAHA_LOG_FORMAT=JSON
|
||||
WAHA_LOG_LEVEL=info
|
||||
# WAHA_LOG_FORMAT=PRETTY
|
||||
# If you want to see more logs, you can set the log level to debug
|
||||
#WAHA_LOG_LEVEL=debug
|
||||
# DEBUG=1 # shortcut for setting log level to debug
|
||||
|
||||
#
|
||||
# Engine
|
||||
# https://waha.devlike.pro/docs/how-to/engines/
|
||||
#
|
||||
# Choose the right engine for your needs (WEBJS by default)
|
||||
WHATSAPP_DEFAULT_ENGINE=WEBJS
|
||||
# Use NOWEB engine for the fastest performance
|
||||
#WHATSAPP_DEFAULT_ENGINE=NOWEB
|
||||
|
||||
# Worker
|
||||
# WAHA_WORKER_ID=waha1
|
||||
|
||||
#
|
||||
# Sessions
|
||||
# https://waha.devlike.pro/docs/how-to/config/#sessions
|
||||
#
|
||||
# Do not print QR codes in logs
|
||||
WAHA_PRINT_QR=False
|
||||
#
|
||||
# OR you can specify sessions by name, if you have handful amount of sessions
|
||||
#WHATSAPP_START_SESSION=session1,session2
|
||||
#
|
||||
# Restart all sessions when container starts
|
||||
# !!! Do not use it with multiple workers against one database !!!
|
||||
# WHATSAPP_RESTART_ALL_SESSIONS=True
|
||||
|
||||
#
|
||||
# Swagger
|
||||
# https://waha.devlike.pro/docs/how-to/swagger/
|
||||
#
|
||||
WHATSAPP_SWAGGER_ENABLED=True
|
||||
WHATSAPP_SWAGGER_USERNAME=admin
|
||||
WHATSAPP_SWAGGER_PASSWORD=admin
|
||||
# Disable Swagger
|
||||
#WHATSAPP_SWAGGER_ENABLED=false
|
||||
# Enable advanced Swagger configuration
|
||||
#WHATSAPP_SWAGGER_CONFIG_ADVANCED=true
|
||||
#
|
||||
# Swagger White Label customization
|
||||
# https://waha.devlike.pro/docs/how-to/swagger/#white-label
|
||||
#WHATSAPP_SWAGGER_TITLE=WAHA API
|
||||
#
|
||||
|
||||
#
|
||||
# PostgresSQL Configuration
|
||||
# https://waha.devlike.pro/docs/how-to/storages/#sessions---postgresql
|
||||
#WHATSAPP_SESSIONS_POSTGRESQL_URL=postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable
|
||||
#
|
||||
# Media - PostgresSQL Storage
|
||||
# https://waha.devlike.pro/docs/how-to/storages/#media---postgresql
|
||||
#WAHA_MEDIA_STORAGE=POSTGRESQL
|
||||
#WAHA_MEDIA_POSTGRESQL_URL=postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable
|
||||
#
|
||||
# WhatsApp engine (WEBJS is default, GOWS or NOWEB for better performance)
|
||||
WHATSAPP_DEFAULT_ENGINE=WEBJS
|
||||
|
||||
#
|
||||
# Global Proxy Settings
|
||||
# https://waha.devlike.pro/docs/how-to/config/#global-proxy-configuration
|
||||
#
|
||||
# You can also set proxy settings for each session later when starting it
|
||||
# https://waha.devlike.pro/docs/how-to/sessions/#configure-proxy
|
||||
#WHATSAPP_PROXY_SERVER=proxy.example.com:3128
|
||||
#WHATSAPP_PROXY_SERVER_USERNAME=user
|
||||
#WHATSAPP_PROXY_SERVER_PASSWORD=pass
|
||||
#
|
||||
# ===================
|
||||
# ===== LOGGING =====
|
||||
# ===================
|
||||
# Log format: JSON (for log management systems) or PRETTY (for development)
|
||||
WAHA_LOG_FORMAT=JSON
|
||||
|
||||
#
|
||||
# Media Configuration
|
||||
#
|
||||
# https://waha.devlike.pro/docs/how-to/config/#files
|
||||
#
|
||||
# Disable media (images, videos, files) download for incoming messages
|
||||
#WHATSAPP_DOWNLOAD_MEDIA=false
|
||||
#
|
||||
# Download only specific media types
|
||||
#WHATSAPP_FILES_MIMETYPES=image/jpeg,image/png
|
||||
#
|
||||
# Log level: info, debug, error, warn
|
||||
WAHA_LOG_LEVEL=info
|
||||
|
||||
# Media - Local Storage
|
||||
# https://waha.devlike.pro/docs/how-to/storages/#media---local
|
||||
#
|
||||
# Set to 0 to keep media files forever
|
||||
# https://waha.devlike.pro/docs/how-to/storages/#save-media-files-between-the-container-restarts
|
||||
# Don't print QR codes in logs
|
||||
WAHA_PRINT_QR=False
|
||||
|
||||
# =========================
|
||||
# ===== MEDIA STORAGE =====
|
||||
# =========================
|
||||
# Local storage (default)
|
||||
WAHA_MEDIA_STORAGE=LOCAL
|
||||
WHATSAPP_FILES_LIFETIME=0
|
||||
WHATSAPP_FILES_FOLDER=/app/.media
|
||||
# Keep media files for 180 seconds (3 minutes)
|
||||
#WHATSAPP_FILES_LIFETIME=180
|
||||
#
|
||||
|
||||
# Media - S3 Storage
|
||||
# https://waha.devlike.pro/docs/how-to/storages/#media---s3
|
||||
#WAHA_MEDIA_STORAGE=S3
|
||||
#WAHA_S3_REGION=eu-west-2
|
||||
#WAHA_S3_BUCKET=waha
|
||||
#WAHA_S3_ACCESS_KEY_ID=minioadmin
|
||||
#WAHA_S3_SECRET_ACCESS_KEY=minioadmin
|
||||
#WAHA_S3_ENDPOINT=http://minio:9000 # Not required if you're using AWS S3
|
||||
#WAHA_S3_FORCE_PATH_STYLE=True # Required for Minio
|
||||
#WAHA_S3_PROXY_FILES=True # Required for docker-compose setup
|
||||
# Media download settings
|
||||
# WHATSAPP_DOWNLOAD_MEDIA=true
|
||||
# WHATSAPP_FILES_MIMETYPES=image/jpeg,image/png
|
||||
|
||||
#
|
||||
# Global Webhooks
|
||||
#
|
||||
# https://waha.devlike.pro/docs/how-to/config/#webhooks
|
||||
# https://waha.devlike.pro/docs/how-to/webhooks/
|
||||
#WHATSAPP_HOOK_URL=https://webhook.site/11111111-1111-1111-1111-11111111
|
||||
#WHATSAPP_HOOK_EVENTS=session.status,message,message.reaction
|
||||
#
|
||||
# S3 storage (uncomment to use)
|
||||
# WAHA_MEDIA_STORAGE=S3
|
||||
# WAHA_S3_REGION=eu-west-2
|
||||
# WAHA_S3_BUCKET=waha
|
||||
# WAHA_S3_ACCESS_KEY_ID=minioadmin
|
||||
# WAHA_S3_SECRET_ACCESS_KEY=minioadmin
|
||||
# WAHA_S3_ENDPOINT=http://minio:9000
|
||||
# WAHA_S3_FORCE_PATH_STYLE=True
|
||||
# WAHA_S3_PROXY_FILES=True
|
||||
|
||||
#
|
||||
# Local Configuration
|
||||
#
|
||||
# WAHA_LOCAL_STORE_BASE_DIR=/app/sessions
|
||||
# Remember to map the volume to the host machine to the right direction in "volumes" field in docker-compose.yml
|
||||
# volumes:
|
||||
# - './.sessions:/app/sessions'
|
||||
# PostgreSQL storage (uncomment to use)
|
||||
# WAHA_MEDIA_STORAGE=POSTGRESQL
|
||||
# WAHA_MEDIA_POSTGRESQL_URL=postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable
|
||||
|
||||
# ===========================
|
||||
# ===== SESSION STORAGE =====
|
||||
# ===========================
|
||||
# PostgreSQL for sessions (uncomment to use)
|
||||
# WHATSAPP_SESSIONS_POSTGRESQL_URL=postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable
|
||||
|
||||
# MongoDB for sessions (uncomment to use)
|
||||
# WHATSAPP_SESSIONS_MONGO_URL=mongodb://mongouser:mongopassword@mongodb:27017
|
||||
|
||||
|
||||
#
|
||||
# MongoDB Configuration
|
||||
# https://waha.devlike.pro/docs/how-to/storages/#sessions---mongodb
|
||||
#WHATSAPP_SESSIONS_MONGO_URL=mongodb://mongouser:mongopassword@mongodb:27017
|
||||
#
|
||||
# ==================================
|
||||
# ===== ADVANCED CONFIGURATION =====
|
||||
# ==================================
|
||||
|
||||
#
|
||||
# HTTPS Configuration
|
||||
# https://waha.devlike.pro/docs/how-to/config/#https
|
||||
#
|
||||
# Consider using certbot for HTTPS
|
||||
# https://waha.devlike.pro/blog/setting-up-https-for-waha/#lets-encrypt-certbot--waha
|
||||
#
|
||||
#WAHA_HTTPS_ENABLED=true
|
||||
#WAHA_HTTPS_PATH_KEY=/etc/letsencrypt/live/waha.example.pro/privkey.pem
|
||||
#WAHA_HTTPS_PATH_CERT=/etc/letsencrypt/live/waha.example.pro/cert.pem \
|
||||
#WAHA_HTTPS_PATH_CA=/etc/letsencrypt/live/waha.example.pro/chain.pem
|
||||
#WHATSAPP_API_SCHEMA=https
|
||||
#WHATSAPP_API_PORT=3000
|
||||
#WHATSAPP_API_HOSTNAME=waha.example.pro
|
||||
#
|
||||
# Timezone for screenshots and logs
|
||||
# TZ=Europe/Dublin
|
||||
|
||||
# Session management
|
||||
# WHATSAPP_START_SESSION=session1,session2
|
||||
# WHATSAPP_RESTART_ALL_SESSIONS=False
|
||||
|
||||
# Webhooks
|
||||
# WHATSAPP_HOOK_URL=https://webhook.site/11111111-1111-1111-1111-11111111
|
||||
# WHATSAPP_HOOK_EVENTS=session.status,message,message.reaction
|
||||
|
||||
# Proxy configuration
|
||||
# WHATSAPP_PROXY_SERVER=proxy.example.com:3128
|
||||
# WHATSAPP_PROXY_SERVER_USERNAME=user
|
||||
# WHATSAPP_PROXY_SERVER_PASSWORD=pass
|
||||
|
||||
# HTTPS configuration
|
||||
# !DEPRECATED!
|
||||
# Setup nginx reverse proxy to handle TLS connection
|
||||
# using Let's encrypt or self-issued certificate
|
||||
# WAHA_HTTPS_ENABLED=true
|
||||
# WAHA_HTTPS_PATH_KEY=/etc/letsencrypt/live/waha.example.pro/privkey.pem
|
||||
# WAHA_HTTPS_PATH_CERT=/etc/letsencrypt/live/waha.example.pro/cert.pem
|
||||
# WAHA_HTTPS_PATH_CA=/etc/letsencrypt/live/waha.example.pro/chain.pem
|
||||
+1
-1
@@ -38,7 +38,7 @@ services:
|
||||
# POSTGRES_PASSWORD: postgres
|
||||
# POSTGRES_DB: postgres
|
||||
# ports:
|
||||
# - "5432:5432"
|
||||
# - "127.0.0.1:5432:5432"
|
||||
# volumes:
|
||||
# - pg_data:/var/lib/postgresql/data
|
||||
# command:
|
||||
|
||||
@@ -10,7 +10,7 @@ services:
|
||||
- './.sessions:/app/.sessions'
|
||||
- './.media:/app/.media'
|
||||
environment:
|
||||
- WHATSAPP_API_KEY=321
|
||||
- WAHA_API_KEY=321
|
||||
- WAHA_DASHBOARD_USERNAME=admin
|
||||
- WAHA_DASHBOARD_PASSWORD=admin
|
||||
- WHATSAPP_DEFAULT_ENGINE=WEBJS
|
||||
|
||||
@@ -29,7 +29,7 @@ services:
|
||||
# - WHATSAPP_HOOK_EVENTS=message
|
||||
# - WHATSAPP_API_HOSTNAME=localhost
|
||||
- WHATSAPP_DEFAULT_ENGINE=WEBJS
|
||||
- WHATSAPP_API_KEY=321
|
||||
- WAHA_API_KEY=321
|
||||
# Username and password for Swagger
|
||||
- WHATSAPP_SWAGGER_USERNAME=swagger
|
||||
- WHATSAPP_SWAGGER_PASSWORD=admin
|
||||
@@ -55,7 +55,7 @@ services:
|
||||
# Environment variables from https://waha.devlike.pro/docs/how-to/config/
|
||||
# - WHATSAPP_HOOK_URL=https://webhook.site/11111111-1111-1111-1111-11111111
|
||||
# - WHATSAPP_HOOK_EVENTS=message
|
||||
# - WHATSAPP_API_KEY=321
|
||||
# - WAHA_API_KEY=321
|
||||
# - WHATSAPP_SWAGGER_USERNAME=admin
|
||||
# - WHATSAPP_SWAGGER_PASSWORD=123
|
||||
# - WAHA_DASHBOARD_USERNAME=admin
|
||||
|
||||
+58
-4
@@ -1,16 +1,70 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Start virtual X server in the background
|
||||
Xvfb :99 -screen 0 1280x720x24 &
|
||||
export DISPLAY=:99
|
||||
sleep 2
|
||||
#
|
||||
# Run Xvfb if exists
|
||||
#
|
||||
if command -v Xvfb > /dev/null 2>&1; then
|
||||
# Start virtual X server in the background
|
||||
Xvfb :99 -screen 0 1280x720x24 &
|
||||
export DISPLAY=:99
|
||||
sleep 2
|
||||
else
|
||||
echo "Xvfb command not found, skipping virtual X server setup"
|
||||
fi
|
||||
|
||||
#
|
||||
# Calculate UV_THREADPOOL_SIZE based on number of CPUs
|
||||
#
|
||||
cpus=$(node -e "const os = require('os'); console.log(os.cpus().length);")
|
||||
uv_threadpool_size=$(($cpus * 1))
|
||||
|
||||
# Set UV_THREADPOOL_SIZE as an environment variable
|
||||
export UV_THREADPOOL_SIZE="${UV_THREADPOOL_SIZE:-$uv_threadpool_size}"
|
||||
|
||||
#
|
||||
# Handle API key hashing
|
||||
#
|
||||
# Save WHATSAPP_API_KEY or WAHA_API_KEY in a variable (WHATSAPP_API_KEY has priority)
|
||||
if [ -n "$WHATSAPP_API_KEY" ]; then
|
||||
key="$WHATSAPP_API_KEY"
|
||||
elif [ -n "$WAHA_API_KEY" ]; then
|
||||
key="$WAHA_API_KEY"
|
||||
fi
|
||||
|
||||
# Unset both environment variables
|
||||
unset WHATSAPP_API_KEY
|
||||
unset WAHA_API_KEY
|
||||
|
||||
# Process the key if it exists
|
||||
if [ -n "$key" ]; then
|
||||
# Check if key is already hashed
|
||||
if echo "$key" | grep -q "^sha512:"; then
|
||||
# If already hashed, use it as is
|
||||
export WAHA_API_KEY="$key"
|
||||
else
|
||||
# Display warning about using plain text API key
|
||||
echo "⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️"
|
||||
echo "WARNING: Plain text API key detected. Converting to hashed format for security."
|
||||
echo "For better security, use WAHA_API_KEY=sha512:{SHA512_HASH_FOR_YOUR_API_KEY}"
|
||||
echo "⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️"
|
||||
|
||||
# Start a background task to display another warning after 5 seconds
|
||||
(
|
||||
sleep 5
|
||||
echo "⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️"
|
||||
echo "SECURITY REMINDER: Your plain text API key has been hashed for this session."
|
||||
echo "In the future, please use WAHA_API_KEY=sha512:{SHA512_HASH_FOR_YOUR_API_KEY}"
|
||||
echo "to avoid exposing your API key in environment variables or process lists."
|
||||
echo "⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️"
|
||||
) &
|
||||
|
||||
# Hash the key using sha512sum
|
||||
HASHED_KEY=$(echo -n "$key" | sha512sum | awk '{print $1}')
|
||||
export WAHA_API_KEY="sha512:$HASHED_KEY"
|
||||
fi
|
||||
fi
|
||||
|
||||
#
|
||||
# Start your application using node with exec to ensure proper signal handling
|
||||
#
|
||||
exec node dist/main
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
WebSocketServer,
|
||||
} from '@nestjs/websockets';
|
||||
import { SessionManager } from '@waha/core/abc/manager.abc';
|
||||
import { WebSocketAuth } from '@waha/core/auth/WebSocketAuth';
|
||||
import { WebsocketHeartbeatJob } from '@waha/nestjs/ws/WebsocketHeartbeatJob';
|
||||
import { WebSocket } from '@waha/nestjs/ws/ws';
|
||||
import { WAHAEvents, WAHAEventsWild } from '@waha/structures/enums.dto';
|
||||
@@ -21,6 +22,15 @@ import { IncomingMessage } from 'http';
|
||||
import * as url from 'url';
|
||||
import { Server } from 'ws';
|
||||
|
||||
export enum WebSocketCloseCode {
|
||||
NORMAL = 1000,
|
||||
GOING_AWAY = 1001,
|
||||
PROTOCOL_ERROR = 1002,
|
||||
UNSUPPORTED_DATA = 1003,
|
||||
POLICY_VIOLATION = 1008,
|
||||
INTERNAL_ERROR = 1011,
|
||||
}
|
||||
|
||||
@WebSocketGateway({
|
||||
path: '/ws',
|
||||
cors: true,
|
||||
@@ -41,7 +51,10 @@ export class WebsocketGatewayCore
|
||||
private heartbeat: WebsocketHeartbeatJob;
|
||||
private eventUnmask = new EventWildUnmask(WAHAEvents, WAHAEventsWild);
|
||||
|
||||
constructor(private manager: SessionManager) {
|
||||
constructor(
|
||||
private manager: SessionManager,
|
||||
private auth: WebSocketAuth,
|
||||
) {
|
||||
this.logger = new Logger('WebsocketGateway');
|
||||
this.heartbeat = new WebsocketHeartbeatJob(
|
||||
this.logger,
|
||||
@@ -51,14 +64,23 @@ export class WebsocketGatewayCore
|
||||
|
||||
handleConnection(socket: WebSocket, request: IncomingMessage, ...args): any {
|
||||
// wsc - websocket client
|
||||
const id = generatePrefixedId('wsc');
|
||||
socket.id = id;
|
||||
this.logger.debug(`New client connected: ${request.url}`);
|
||||
socket.id = generatePrefixedId('wsc');
|
||||
|
||||
if (!this.auth.validateRequest(request)) {
|
||||
// Not authorized - close connection
|
||||
socket.close(WebSocketCloseCode.POLICY_VIOLATION, 'Unauthorized');
|
||||
this.logger.debug(
|
||||
`Unauthorized websocket connection attempt: ${request.url} - ${socket.id}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.debug(`New client connected: ${request.url} - ${socket.id}`);
|
||||
const params = this.getParams(request);
|
||||
const session: string = params.session;
|
||||
const events: WAHAEvents[] = params.events;
|
||||
this.logger.debug(
|
||||
`Client connected to session: '${session}', events: ${events}, ${id}`,
|
||||
`Client connected to session: '${session}', events: ${events}, ${socket.id}`,
|
||||
);
|
||||
|
||||
const sub = this.manager
|
||||
@@ -97,26 +119,11 @@ export class WebsocketGatewayCore
|
||||
async beforeApplicationShutdown(signal?: string) {
|
||||
this.logger.log('Shutting down websocket server');
|
||||
this.heartbeat?.stop();
|
||||
|
||||
// Allow pending messages to be sent, it can be even 1ms, just to release the event loop
|
||||
await sleep(100);
|
||||
// Close clients and server
|
||||
await this.close(this.server);
|
||||
this.logger.log('Websocket server is down');
|
||||
}
|
||||
|
||||
// Cherry-pick from nestjs new version
|
||||
// https://github.com/nestjs/nest/pull/13531/files
|
||||
private async close(server: any) {
|
||||
// const closeEventSignal = new Promise((resolve, reject) =>
|
||||
// server.close((err) => (err ? reject(err) : resolve(undefined))),
|
||||
// );
|
||||
for (const ws of server.clients) {
|
||||
ws.terminate();
|
||||
}
|
||||
// await closeEventSignal;
|
||||
}
|
||||
|
||||
afterInit(server: Server) {
|
||||
this.logger.debug('Websocket server initialized');
|
||||
|
||||
@@ -138,7 +138,10 @@ export class WhatsappConfigService implements OnApplicationBootstrap {
|
||||
}
|
||||
|
||||
getApiKey(): string | undefined {
|
||||
return this.configService.get('WHATSAPP_API_KEY', '');
|
||||
return (
|
||||
this.configService.get('WHATSAPP_API_KEY', '') ||
|
||||
this.configService.get('WAHA_API_KEY', '')
|
||||
);
|
||||
}
|
||||
|
||||
getExcludedPaths(): string[] {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { DocumentBuilder, OpenAPIObject, SwaggerModule } from '@nestjs/swagger';
|
||||
import { DECORATORS } from '@nestjs/swagger/dist/constants';
|
||||
import { BasicAuthFunction } from '@waha/core/auth/basicAuth';
|
||||
import { DashboardConfigServiceCore } from '@waha/core/config/DashboardConfigServiceCore';
|
||||
import { Logger } from 'nestjs-pino';
|
||||
|
||||
import { WhatsappConfigService } from '../config.service';
|
||||
@@ -9,16 +11,22 @@ import { SwaggerConfigServiceCore } from './config/SwaggerConfigServiceCore';
|
||||
|
||||
export class SwaggerConfiguratorCore {
|
||||
protected logger: any;
|
||||
private config: SwaggerConfigServiceCore;
|
||||
|
||||
constructor(protected app: INestApplication) {
|
||||
this.logger = app.get(Logger);
|
||||
this.config = app.get(SwaggerConfigServiceCore);
|
||||
}
|
||||
|
||||
get title() {
|
||||
return 'WAHA - WhatsApp HTTP API';
|
||||
return this.config.title || 'WAHA - WhatsApp HTTP API';
|
||||
}
|
||||
|
||||
get description() {
|
||||
if (this.config.description) {
|
||||
return this.config.description;
|
||||
}
|
||||
|
||||
return (
|
||||
'<b>WhatsApp HTTP API</b> that you can run in a click!<br/>' +
|
||||
'<a href="/dashboard"><b>📊 Dashboard</b></a><br/>' +
|
||||
@@ -43,10 +51,19 @@ export class SwaggerConfiguratorCore {
|
||||
}
|
||||
|
||||
get externalDocUrl() {
|
||||
return 'https://waha.devlike.pro/';
|
||||
return this.config.externalDocUrl || 'https://waha.devlike.pro/';
|
||||
}
|
||||
|
||||
configure(webhooks: any[]) {
|
||||
if (!this.config.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const credentials = this.config.credentials;
|
||||
if (credentials) {
|
||||
this.setUpAuth(credentials);
|
||||
}
|
||||
|
||||
const app = this.app;
|
||||
const builder = new DocumentBuilder();
|
||||
|
||||
@@ -163,4 +180,12 @@ export class SwaggerConfiguratorCore {
|
||||
document.webhooks = webhooks;
|
||||
return document;
|
||||
}
|
||||
|
||||
setUpAuth(credentials: [string, string]): void {
|
||||
const [username, password] = credentials;
|
||||
const dashboardConfig = this.app.get(DashboardConfigServiceCore);
|
||||
const exclude = ['/api/', dashboardConfig.dashboardUri, '/health', '/ws'];
|
||||
const authFunction = BasicAuthFunction(username, password, exclude);
|
||||
this.app.use(authFunction);
|
||||
}
|
||||
}
|
||||
+70
-17
@@ -1,4 +1,7 @@
|
||||
import { INestApplication, Module } from '@nestjs/common';
|
||||
import * as process from 'node:process';
|
||||
|
||||
import { INestApplication, MiddlewareConsumer, Module } from '@nestjs/common';
|
||||
import { Provider } from '@nestjs/common/interfaces/modules/provider.interface';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { APP_INTERCEPTOR } from '@nestjs/core';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
@@ -11,20 +14,29 @@ import {
|
||||
ServerController,
|
||||
ServerDebugController,
|
||||
} from '@waha/api/server.controller';
|
||||
import { WebsocketGatewayCore } from '@waha/core/api/websocket.gateway.core';
|
||||
import { WebsocketGatewayCore } from '@waha/api/websocket.gateway.core';
|
||||
import { ApiKeyStrategy } from '@waha/core/auth/apiKey.strategy';
|
||||
import { IApiKeyAuth } from '@waha/core/auth/auth';
|
||||
import { AuthMiddleware } from '@waha/core/auth/auth.middleware';
|
||||
import { BasicAuthFunction } from '@waha/core/auth/basicAuth';
|
||||
import { WebSocketAuth } from '@waha/core/auth/WebSocketAuth';
|
||||
import { GowsEngineConfigService } from '@waha/core/config/GowsEngineConfigService';
|
||||
import { WebJSEngineConfigService } from '@waha/core/config/WebJSEngineConfigService';
|
||||
import { MediaLocalStorageModule } from '@waha/core/media/local/media.local.storage.module';
|
||||
import { MediaLocalStorageConfig } from '@waha/core/media/local/MediaLocalStorageConfig';
|
||||
import { ChannelsInfoServiceCore } from '@waha/core/services/ChannelsInfoServiceCore';
|
||||
import { parseBool } from '@waha/helpers';
|
||||
import { BufferJsonReplacerInterceptor } from '@waha/nestjs/BufferJsonReplacerInterceptor';
|
||||
import { HttpsExpress } from '@waha/nestjs/HttpsExpress';
|
||||
import {
|
||||
getPinoHttpUseLevel,
|
||||
getPinoLogLevel,
|
||||
getPinoTransport,
|
||||
} from '@waha/utils/logging';
|
||||
import { noSlashAtTheEnd } from '@waha/utils/string';
|
||||
import * as Joi from 'joi';
|
||||
import { LoggerModule } from 'nestjs-pino';
|
||||
import { Logger as NestJSPinoLogger } from 'nestjs-pino';
|
||||
import { join } from 'path';
|
||||
import { Logger } from 'pino';
|
||||
|
||||
@@ -46,6 +58,7 @@ import { VersionController } from '../api/version.controller';
|
||||
import { WhatsappConfigService } from '../config.service';
|
||||
import { SessionManager } from './abc/manager.abc';
|
||||
import { WAHAHealthCheckService } from './abc/WAHAHealthCheckService';
|
||||
import { ApiKeyAuthFactory } from './auth/ApiKeyAuthFactory';
|
||||
import { DashboardConfigServiceCore } from './config/DashboardConfigServiceCore';
|
||||
import { EngineConfigService } from './config/EngineConfigService';
|
||||
import { SwaggerConfigServiceCore } from './config/SwaggerConfigServiceCore';
|
||||
@@ -144,6 +157,28 @@ export const CONTROLLERS = [
|
||||
VersionController,
|
||||
MediaController,
|
||||
];
|
||||
export const PROVIDERS_BASE: Provider[] = [
|
||||
{
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: BufferJsonReplacerInterceptor,
|
||||
},
|
||||
DashboardConfigServiceCore,
|
||||
SwaggerConfigServiceCore,
|
||||
WebJSEngineConfigService,
|
||||
GowsEngineConfigService,
|
||||
WhatsappConfigService,
|
||||
EngineConfigService,
|
||||
WebsocketGatewayCore,
|
||||
MediaLocalStorageConfig,
|
||||
WebSocketAuth,
|
||||
ApiKeyStrategy,
|
||||
{
|
||||
provide: IApiKeyAuth,
|
||||
useFactory: ApiKeyAuthFactory,
|
||||
inject: [WhatsappConfigService, NestJSPinoLogger],
|
||||
},
|
||||
];
|
||||
|
||||
const PROVIDERS = [
|
||||
{
|
||||
provide: SessionManager,
|
||||
@@ -153,19 +188,8 @@ const PROVIDERS = [
|
||||
provide: WAHAHealthCheckService,
|
||||
useClass: WAHAHealthCheckServiceCore,
|
||||
},
|
||||
{
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: BufferJsonReplacerInterceptor,
|
||||
},
|
||||
ChannelsInfoServiceCore,
|
||||
DashboardConfigServiceCore,
|
||||
SwaggerConfigServiceCore,
|
||||
WebJSEngineConfigService,
|
||||
GowsEngineConfigService,
|
||||
WhatsappConfigService,
|
||||
EngineConfigService,
|
||||
WebsocketGatewayCore,
|
||||
MediaLocalStorageConfig,
|
||||
...PROVIDERS_BASE,
|
||||
];
|
||||
|
||||
@Module({
|
||||
@@ -176,15 +200,44 @@ const PROVIDERS = [
|
||||
export class AppModuleCore {
|
||||
public startTimestamp: number;
|
||||
|
||||
constructor(protected config: WhatsappConfigService) {
|
||||
constructor(
|
||||
protected config: WhatsappConfigService,
|
||||
private dashboardConfig: DashboardConfigServiceCore,
|
||||
) {
|
||||
this.startTimestamp = Date.now();
|
||||
}
|
||||
|
||||
static getHttpsOptions(logger: Logger) {
|
||||
return undefined;
|
||||
const httpsEnabled = parseBool(process.env.WAHA_HTTPS_ENABLED);
|
||||
if (!httpsEnabled) {
|
||||
return undefined;
|
||||
}
|
||||
const httpsExpress = new HttpsExpress(logger);
|
||||
return httpsExpress.readSync();
|
||||
}
|
||||
|
||||
static appReady(app: INestApplication, logger: Logger) {
|
||||
return;
|
||||
const httpsEnabled = parseBool(process.env.WAHA_HTTPS_ENABLED);
|
||||
if (!httpsEnabled) {
|
||||
return;
|
||||
}
|
||||
const httpd = app.getHttpServer();
|
||||
const httpsExpress = new HttpsExpress(logger);
|
||||
httpsExpress.watchCertChanges(httpd);
|
||||
}
|
||||
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
const exclude = this.config.getExcludedPaths();
|
||||
consumer
|
||||
.apply(AuthMiddleware)
|
||||
.exclude(...exclude)
|
||||
.forRoutes('api', 'health', 'ws');
|
||||
const dashboardCredentials = this.dashboardConfig.credentials;
|
||||
if (dashboardCredentials) {
|
||||
const username = dashboardCredentials[0];
|
||||
const password = dashboardCredentials[1];
|
||||
const route = noSlashAtTheEnd(this.dashboardConfig.dashboardUri);
|
||||
consumer.apply(BasicAuthFunction(username, password)).forRoutes(route);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { LoggerService } from '@nestjs/common';
|
||||
import { WhatsappConfigService } from '@waha/config.service';
|
||||
import {
|
||||
HashAuth,
|
||||
IApiKeyAuth,
|
||||
NoAuth,
|
||||
PlainApiKeyAuth,
|
||||
} from '@waha/core/auth/auth';
|
||||
|
||||
export function ApiKeyAuthFactory(
|
||||
config: WhatsappConfigService,
|
||||
logger: LoggerService,
|
||||
): IApiKeyAuth {
|
||||
const apiKey = config.getApiKey();
|
||||
if (!apiKey) {
|
||||
setTimeout(() => {
|
||||
logger.warn('🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫');
|
||||
logger.warn('WARNING: No API key detected. This is a security risk.');
|
||||
logger.warn(
|
||||
'Your API is publicly accessible without any authentication.',
|
||||
);
|
||||
logger.warn(
|
||||
'To secure your API, set environment variable: WAHA_API_KEY=your_api_key',
|
||||
);
|
||||
logger.warn(
|
||||
'For better security, use WAHA_API_KEY=sha512:{SHA512_HASH_FOR_YOUR_API_KEY}',
|
||||
);
|
||||
logger.warn('🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫🚫');
|
||||
}, 3000);
|
||||
return new NoAuth();
|
||||
}
|
||||
|
||||
if (apiKey.startsWith('sha512:')) {
|
||||
const hash = apiKey.slice(7);
|
||||
return new HashAuth(hash, 'sha512');
|
||||
}
|
||||
|
||||
// Fallback to plain text
|
||||
setTimeout(() => {
|
||||
logger.warn('⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️');
|
||||
logger.warn(
|
||||
'WARNING: Plain text API key detected. This is a security risk.',
|
||||
);
|
||||
logger.warn(
|
||||
'Your API key can be exposed in environment variables or process lists.',
|
||||
);
|
||||
logger.warn(
|
||||
'For better security, use WAHA_API_KEY=sha512:{SHA512_HASH_FOR_YOUR_API_KEY}',
|
||||
);
|
||||
logger.warn('⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️');
|
||||
}, 2000);
|
||||
return new PlainApiKeyAuth(apiKey);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { IncomingMessage } from 'http';
|
||||
import * as url from 'url';
|
||||
|
||||
import { IApiKeyAuth } from './auth';
|
||||
|
||||
@Injectable()
|
||||
export class WebSocketAuth {
|
||||
constructor(private auth: IApiKeyAuth) {}
|
||||
|
||||
validateRequest(request: IncomingMessage) {
|
||||
if (this.auth.skipAuth()) {
|
||||
return true;
|
||||
}
|
||||
const provided = this.getKeyFromQueryParams(request);
|
||||
return this.auth.isValid(provided);
|
||||
}
|
||||
|
||||
private getKeyFromQueryParams(request: IncomingMessage) {
|
||||
let query = url.parse(request.url, true).query;
|
||||
// case-insensitive query params
|
||||
query = Object.keys(query).reduce((acc, key) => {
|
||||
acc[key.toLowerCase()] = query[key];
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const provided = query['x-api-key'];
|
||||
// Check if it's array - return first
|
||||
if (Array.isArray(provided)) {
|
||||
return provided[0];
|
||||
}
|
||||
return provided;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { IApiKeyAuth } from '@waha/core/auth/auth';
|
||||
import { HeaderAPIKeyStrategy } from 'passport-headerapikey';
|
||||
|
||||
@Injectable()
|
||||
export class ApiKeyStrategy extends PassportStrategy(HeaderAPIKeyStrategy) {
|
||||
constructor(private auth: IApiKeyAuth) {
|
||||
super({ header: 'X-Api-Key', prefix: '' }, true, (apikey, done) => {
|
||||
const isValid = this.auth.isValid(apikey);
|
||||
return done(isValid);
|
||||
});
|
||||
}
|
||||
|
||||
validate(apikey: string, done: (result: boolean) => void): void {
|
||||
const isValid = this.auth.isValid(apikey);
|
||||
return done(isValid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
Injectable,
|
||||
NestMiddleware,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import * as passport from 'passport';
|
||||
|
||||
import { IApiKeyAuth } from './auth';
|
||||
|
||||
@Injectable()
|
||||
export class AuthMiddleware implements NestMiddleware {
|
||||
constructor(private auth: IApiKeyAuth) {}
|
||||
|
||||
use(req: any, res: any, next: () => void) {
|
||||
// Skip authentication if auth says so
|
||||
if (this.auth.skipAuth()) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
passport.authenticate('headerapikey', { session: false }, (value) => {
|
||||
if (!value) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
next();
|
||||
})(req, res, next);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
export abstract class IApiKeyAuth {
|
||||
abstract isValid(plain: string): boolean;
|
||||
|
||||
abstract skipAuth(): boolean;
|
||||
}
|
||||
|
||||
export class NoAuth implements IApiKeyAuth {
|
||||
isValid(plain: string): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
skipAuth(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class PlainApiKeyAuth implements IApiKeyAuth {
|
||||
constructor(private key: string) {}
|
||||
|
||||
isValid(plain: string): boolean {
|
||||
return compare(plain, this.key);
|
||||
}
|
||||
|
||||
skipAuth(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class HashAuth implements IApiKeyAuth {
|
||||
constructor(
|
||||
private hash: string,
|
||||
private algorithm: string,
|
||||
) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
isValid(plain: string): boolean {
|
||||
if (!plain) {
|
||||
return false;
|
||||
}
|
||||
const hash = crypto.createHash(this.algorithm).update(plain).digest('hex');
|
||||
return compare(hash, this.hash);
|
||||
}
|
||||
|
||||
skipAuth(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Securely compare 2 strings
|
||||
*/
|
||||
export function compare(provided: string, stored: string | undefined): boolean {
|
||||
if (!stored || !provided) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Convert strings to buffers for constant-time comparison
|
||||
const providedBuffer = Buffer.from(provided);
|
||||
const storedBuffer = Buffer.from(stored);
|
||||
|
||||
// If lengths are different, return false but use a dummy comparison to prevent timing attacks
|
||||
if (providedBuffer.length !== storedBuffer.length) {
|
||||
// Create a dummy buffer of the same length as the provided key
|
||||
const dummyBuffer = Buffer.alloc(providedBuffer.length);
|
||||
// Perform comparison with dummy buffer to maintain constant time
|
||||
crypto.timingSafeEqual(providedBuffer, dummyBuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Perform constant-time comparison
|
||||
return crypto.timingSafeEqual(providedBuffer, storedBuffer);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import * as basicAuth from 'express-basic-auth';
|
||||
|
||||
export function BasicAuthFunction(username, password, exclude: string[] = []) {
|
||||
function authFunction(req, res, next) {
|
||||
const ignore = exclude.filter((url) => req.url.startsWith(url)).length > 0;
|
||||
if (ignore) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
const auth = basicAuth({
|
||||
challenge: true,
|
||||
users: {
|
||||
[username]: password,
|
||||
},
|
||||
});
|
||||
auth(req, res, next);
|
||||
}
|
||||
|
||||
return authFunction;
|
||||
}
|
||||
@@ -18,4 +18,20 @@ export class DashboardConfigServiceCore {
|
||||
const value = this.configService.get('WAHA_DASHBOARD_ENABLED', 'true');
|
||||
return parseBool(value);
|
||||
}
|
||||
|
||||
get credentials(): [string, string] | null {
|
||||
const user = this.configService.get('WAHA_DASHBOARD_USERNAME', '');
|
||||
const password = this.configService.get('WAHA_DASHBOARD_PASSWORD', '');
|
||||
if (!user && !password) {
|
||||
return null;
|
||||
}
|
||||
if ((user && !password) || (!user && password)) {
|
||||
this.logger.warn(
|
||||
'Set up both WAHA_DASHBOARD_USERNAME and WAHA_DASHBOARD_PASSWORD ' +
|
||||
'to enable dashboard authentication.',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return [user, password];
|
||||
}
|
||||
}
|
||||
@@ -19,4 +19,43 @@ export class SwaggerConfigServiceCore {
|
||||
);
|
||||
return parseBool(value);
|
||||
}
|
||||
|
||||
get enabled(): boolean {
|
||||
const value = this.configService.get('WHATSAPP_SWAGGER_ENABLED', 'true');
|
||||
return parseBool(value);
|
||||
}
|
||||
|
||||
get credentials(): [string, string] | undefined {
|
||||
const user = this.configService.get<string>(
|
||||
'WHATSAPP_SWAGGER_USERNAME',
|
||||
undefined,
|
||||
);
|
||||
const password = this.configService.get<string>(
|
||||
'WHATSAPP_SWAGGER_PASSWORD',
|
||||
undefined,
|
||||
);
|
||||
if (!user && !password) {
|
||||
return null;
|
||||
}
|
||||
if ((user && !password) || (!user && password)) {
|
||||
this.logger.warn(
|
||||
'Set up both WHATSAPP_SWAGGER_USERNAME and WHATSAPP_SWAGGER_PASSWORD ' +
|
||||
'to enable swagger authentication.',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return [user, password];
|
||||
}
|
||||
|
||||
get title() {
|
||||
return this.configService.get('WHATSAPP_SWAGGER_TITLE', '');
|
||||
}
|
||||
|
||||
get description() {
|
||||
return this.configService.get('WHATSAPP_SWAGGER_DESCRIPTION', '');
|
||||
}
|
||||
|
||||
get externalDocUrl() {
|
||||
return this.configService.get('WHATSAPP_SWAGGER_EXTERNAL_DOC_URL', '');
|
||||
}
|
||||
}
|
||||
@@ -79,15 +79,13 @@ export class SqlKVRepository<Entity> {
|
||||
}
|
||||
}
|
||||
|
||||
private async upsertBatch(entities: Entity[]): Promise<void> {
|
||||
protected async upsertBatch(entities: Entity[]): Promise<void> {
|
||||
const all = entities.map((entity) => this.dump(entity));
|
||||
// make it unique by .id
|
||||
const data = lodash.uniqBy(all, (d: any) => d.id);
|
||||
if (data.length != all.length) {
|
||||
console.warn(
|
||||
`WARNING - Duplicated entities for upsert batch: ${JSON.stringify(
|
||||
entities,
|
||||
)}`,
|
||||
`WARNING - Duplicated entities for upsert batch: all=${all.length}, data=${data.length}`,
|
||||
);
|
||||
}
|
||||
const columns = this.columns.map((c) => `"${c.fieldName}"`);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { SqlKVRepository } from '@waha/core/storage/sql/SqlKVRepository';
|
||||
import { Sqlite3Engine } from '@waha/core/storage/sqlite3/Sqlite3Engine';
|
||||
import { Sqlite3JsonQuery } from '@waha/core/storage/sqlite3/Sqlite3JsonQuery';
|
||||
import { sleep } from '@waha/utils/promiseTimeout';
|
||||
import { Database } from 'better-sqlite3';
|
||||
import Knex from 'knex';
|
||||
|
||||
@@ -18,4 +19,12 @@ export class Sqlite3KVRepository<Entity> extends SqlKVRepository<Entity> {
|
||||
super(engine, knex);
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
protected async upsertBatch(entities: Entity[]): Promise<void> {
|
||||
await super.upsertBatch(entities);
|
||||
// Give some time to the Node.js loop because we're using sync better-sqlite
|
||||
if (entities.length >= this.UPSERT_BATCH_SIZE) {
|
||||
await sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-15
@@ -47,31 +47,24 @@ process.on('SIGTERM', () => {
|
||||
logger.info('SIGTERM received');
|
||||
});
|
||||
|
||||
async function loadModules(): Promise<
|
||||
[typeof AppModuleCore, typeof SwaggerConfiguratorCore]
|
||||
> {
|
||||
async function loadModules(): Promise<typeof AppModuleCore> {
|
||||
const version = getWAHAVersion();
|
||||
|
||||
if (version === WAHAVersion.CORE) {
|
||||
const { AppModuleCore } = await import('./core/app.module.core');
|
||||
const { SwaggerConfiguratorCore } = await import(
|
||||
'./core/SwaggerConfiguratorCore'
|
||||
);
|
||||
return [AppModuleCore, SwaggerConfiguratorCore];
|
||||
return AppModuleCore;
|
||||
}
|
||||
// Ignore if it's core version - there's no plus module
|
||||
// Ignore if it's a core version - there's no plus module
|
||||
// @ts-ignore
|
||||
const { AppModulePlus } = await import('./plus/app.module.plus');
|
||||
// @ts-ignore
|
||||
const { SwaggerConfiguratorPlus } = await import('./plus/SwaggerConfiguratorPlus'); // prettier-ignore
|
||||
// @ts-ignore
|
||||
return [AppModulePlus, SwaggerConfiguratorPlus];
|
||||
return AppModulePlus;
|
||||
}
|
||||
|
||||
async function bootstrap() {
|
||||
const version = getWAHAVersion();
|
||||
logger.info(`WAHA (WhatsApp HTTP API) - Running ${version} version...`);
|
||||
const [AppModule, SwaggerModule] = await loadModules();
|
||||
const AppModule = await loadModules();
|
||||
const httpsOptions = AppModule.getHttpsOptions(logger);
|
||||
const app = await NestFactory.create(AppModule, {
|
||||
logger: getNestJSLogLevels(),
|
||||
@@ -81,7 +74,7 @@ async function bootstrap() {
|
||||
});
|
||||
app.useLogger(app.get(NestJSPinoLogger));
|
||||
|
||||
// Print original stack, not pino one
|
||||
// Print the original stack, not pino one
|
||||
// https://github.com/iamolegga/nestjs-pino?tab=readme-ov-file#expose-stack-trace-and-error-class-in-err-property
|
||||
app.useGlobalInterceptors(new LoggerErrorInterceptor());
|
||||
|
||||
@@ -91,13 +84,13 @@ async function bootstrap() {
|
||||
// but for now we added it ValidationPipe on Controller or endpoint level
|
||||
// app.useGlobalPipes(new ValidationPipe({ transform: true }));
|
||||
|
||||
// Allow to send big body - for images and attachments
|
||||
// Allow sending big body - for images and attachments
|
||||
app.use(json({ limit: '50mb' }));
|
||||
app.use(urlencoded({ limit: '50mb', extended: false }));
|
||||
app.useWebSocketAdapter(new WsAdapter(app));
|
||||
|
||||
// Configure swagger
|
||||
const swaggerConfigurator = new SwaggerModule(app);
|
||||
const swaggerConfigurator = new SwaggerConfiguratorCore(app);
|
||||
swaggerConfigurator.configure(WAHA_WEBHOOKS);
|
||||
|
||||
AppModule.appReady(app, logger);
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import * as fs from 'node:fs';
|
||||
|
||||
import { LoggerBuilder } from '@waha/utils/logging';
|
||||
import { Logger } from 'pino';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const chokidar = require('chokidar');
|
||||
|
||||
export class HttpsExpress {
|
||||
private readonly keyPath: string;
|
||||
private readonly certPath: string;
|
||||
private readonly caPath: string;
|
||||
|
||||
constructor(private logger: Logger) {
|
||||
//
|
||||
// Let's encrypt certificates default paths
|
||||
// cert.pem chain.pem fullchain.pem privkey.pem
|
||||
//
|
||||
this.keyPath = process.env.WAHA_HTTPS_PATH_KEY || './.secrets/privkey.pem';
|
||||
this.certPath = process.env.WAHA_HTTPS_PATH_CERT || './.secrets/cert.pem';
|
||||
this.caPath = process.env.WAHA_HTTPS_PATH_CA;
|
||||
if (this.caPath == null) {
|
||||
this.caPath = './.secrets/chain.pem';
|
||||
}
|
||||
}
|
||||
|
||||
readSync() {
|
||||
this.logger.info('Reading HTTPS certificates...');
|
||||
this.logger.info('HTTPS Key Path:', this.keyPath);
|
||||
const key = fs.readFileSync(this.keyPath);
|
||||
|
||||
this.logger.info('HTTPS Cert Path:', this.certPath);
|
||||
const cert = fs.readFileSync(this.certPath);
|
||||
|
||||
this.logger.info('HTTPS CA Path:', this.caPath);
|
||||
const ca = this.caPath ? fs.readFileSync(this.caPath) : undefined;
|
||||
|
||||
this.logger.info('HTTPS certificates read successfully');
|
||||
return { key: key, cert: cert, ca: ca };
|
||||
}
|
||||
|
||||
/**
|
||||
* https://stackoverflow.com/a/74076392
|
||||
*/
|
||||
watchCertChanges(httpd) {
|
||||
let waitForCertAndFullChainToGetUpdatedTooTimeout: any;
|
||||
const paths = [this.keyPath, this.certPath, this.caPath].filter(
|
||||
(path) => !!path,
|
||||
);
|
||||
const watcher = chokidar.watch(paths, {
|
||||
followSymlinks: false,
|
||||
persistent: true,
|
||||
ignoreInitial: true,
|
||||
disableGlobbing: true,
|
||||
});
|
||||
// IDK why, but it has few bugs:
|
||||
// 1. It issues 'add' event at the start, even tho ignoreInitial is set to true
|
||||
// 2. It issues additional 'add' for the same file, but without full path
|
||||
watcher.on('all', (eventName, path, stats) => {
|
||||
this.logger.info(`HTTPS file '${path}' has been '${eventName}'...`);
|
||||
clearTimeout(waitForCertAndFullChainToGetUpdatedTooTimeout);
|
||||
waitForCertAndFullChainToGetUpdatedTooTimeout = setTimeout(() => {
|
||||
this.logger.info('Updating HTTPS configuration...');
|
||||
httpd.setSecureContext(this.readSync());
|
||||
}, 1000);
|
||||
});
|
||||
process.on('SIGTERM', () => {
|
||||
this.logger.info('SIGTERM received, closing HTTP file watchers');
|
||||
clearTimeout(waitForCertAndFullChainToGetUpdatedTooTimeout);
|
||||
watcher.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -33,7 +33,7 @@ export function getEngineName(): string {
|
||||
}
|
||||
|
||||
export const VERSION: WAHAEnvironment = {
|
||||
version: '2025.6.3',
|
||||
version: '2025.6.5',
|
||||
engine: getEngineName(),
|
||||
tier: getWAHAVersion(),
|
||||
browser:
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"dashboard": {
|
||||
"repo": "devlikeapro/dashboard",
|
||||
"ref": "3fb6694d849ca0b567679dd59311a50b9d727f2f"
|
||||
"ref": "52750393583e215d212d22bc613d75c106add3a1"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user