diff --git a/docs/site/content/en/docs/how-to/config/index.md b/docs/site/content/en/docs/how-to/config/index.md
index b5ed46ac..ec30dcc7 100644
--- a/docs/site/content/en/docs/how-to/config/index.md
+++ b/docs/site/content/en/docs/how-to/config/index.md
@@ -114,6 +114,14 @@ you'll get a webhook event with `hasMedia: True` field, but with no `media.url`.
}
```
+### Health Check
+Health check is available in [WAHA Plus ]({{< relref "/docs/how-to/plus-version" >}}) only.
+
+The following environment variables can be used to configure the [Health Check ->]({{< relref "/docs/how-to/other" >}}):
+- `WHATSAPP_HEALTH_MEDIA_FILES_THRESHOLD_MB` - the threshold in MB for the media files storage. The default value is `100`.
+- `WHATSAPP_HEALTH_SESSIONS_FILES_THRESHOLD_MB` - the threshold in MB for the sessions files storage. The default value is `100`.
+- `WHATSAPP_HEALTH_MONGODB_TIMEOUT` - the timeout in milliseconds for the MongoDB health check. The default value is `5000`.
+
## Examples
#### Debug Mode
diff --git a/docs/site/content/en/docs/how-to/engines/index.md b/docs/site/content/en/docs/how-to/engines/index.md
index e7d3f1a3..2ef955e0 100644
--- a/docs/site/content/en/docs/how-to/engines/index.md
+++ b/docs/site/content/en/docs/how-to/engines/index.md
@@ -193,7 +193,8 @@ please [create an issue](https://github.com/devlikeapro/whatsapp-http-api/issues
| `GET /api/{session}/presence/{chatId}` | ➖ | ✔️ | ➖ |
| `POST /api/{session}/presence/{chatId}/subscribe` | ➖ | ✔️ | ➖ |
| **Other** | | | |
-| `POST /api/version` | ➖ | ✔️ | ➖ |
+| `GET /api/version` | ➖ | ✔️ | ➖ |
+| `GET /health`  | ✔️ | ✔️ | ✔️ |
| **Webhooks** | WEBJS | NOWEB | VENOM |
|-----------------------------------------------------|:-----:|:-----:|:-----:|
diff --git a/docs/site/content/en/docs/how-to/other/index.md b/docs/site/content/en/docs/how-to/other/index.md
new file mode 100644
index 00000000..5e7a64ec
--- /dev/null
+++ b/docs/site/content/en/docs/how-to/other/index.md
@@ -0,0 +1,192 @@
+---
+title: "Other"
+description: "Other features and API"
+lead: ""
+date: 2020-10-06T08:48:45+00:00
+lastmod: 2020-10-06T08:48:45+00:00
+draft: false
+images: [ ]
+weight: 802
+---
+
+This page provides useful information about other features and API that are not covered in the other sections.
+
+## Health Check
+Health check is available in [WAHA Plus ]({{< relref "/docs/how-to/plus-version" >}}) only.
+
+The health check endpoint is used to determine the health of the service.
+
+```
+GET /health
+```
+
+It returns a **200 OK** status code if the service is healthy.
+
+The response format:
+```json
+{
+ "status": "ok",
+ "info": {
+ "metric1": {
+ "field": "value"
+ },
+ "metric2": {
+ "field": "value"
+ }
+ },
+ "error": {},
+ "details": {}
+}
+```
+
+Where:
+- `status`: `'error' | 'ok' | 'shutting_down'` - If any health indicator failed the status will be `'error'`. If the app is shutting down but still accepting HTTP requests, the health check will have the `'shutting_down'` status.
+- `info`: Object containing information of each health indicator which is of status `'up'`, or in other words "healthy".
+- `error`: Object containing information of each health indicator which is of status `'down'`, or in other words "unhealthy".
+- `details`: Object containing detailed information of each health indicator.
+
+### Health Check Indicators
+Few things we check in the health check:
+- Media files storage space - `mediaFiles.space`
+- Sessions files storage space - `sessionsFiles.space`
+- MongoDB connection - `mongodb`
+
+### Configuration
+The following environment variables can be used to configure the health check:
+- `WHATSAPP_HEALTH_MEDIA_FILES_THRESHOLD_MB` - the threshold in MB for the media files storage. The default value is `100`.
+- `WHATSAPP_HEALTH_SESSIONS_FILES_THRESHOLD_MB` - the threshold in MB for the sessions files storage. The default value is `100`.
+- `WHATSAPP_HEALTH_MONGODB_TIMEOUT` - the timeout in milliseconds for the MongoDB health check. The default value is `5000`.
+
+### Examples
+**Healthy response** when you use [Local Storage]({{< relref "/docs/how-to/storages#sessions" >}}) for session authentication:
+
+**200 OK**
+```json
+{
+ "status": "ok",
+ "info": {
+ "mediaFiles.space": {
+ "status": "up",
+ "path": "/tmp/whatsapp-files",
+ "diskPath": "/",
+ "free": 132979355648,
+ "threshold": 104857600
+ },
+ "sessionsFiles.space": {
+ "status": "up",
+ "path": "/app/.sessions",
+ "diskPath": "/",
+ "free": 132979355648,
+ "threshold": 104857600
+ }
+ },
+ "error": {},
+ "details": {
+ "mediaFiles.space": {
+ "status": "up",
+ "path": "/tmp/whatsapp-files",
+ "diskPath": "/",
+ "free": 132979355648,
+ "threshold": 104857600
+ },
+ "sessionsFiles.space": {
+ "status": "up",
+ "path": "/app/.sessions",
+ "diskPath": "/",
+ "free": 132979355648,
+ "threshold": 104857600
+ }
+ }
+}
+```
+
+**Healthy response** when you use [MongoDB Storage]({{< relref "/docs/how-to/storages#sessions" >}}) for session authentication:
+
+**200 OK**
+```json
+{
+ "status": "ok",
+ "info": {
+ "mediaFiles.space": {
+ "status": "up",
+ "path": "/tmp/whatsapp-files",
+ "diskPath": "/",
+ "free": 132977496064,
+ "threshold": 104857600
+ },
+ "mongodb": {
+ "status": "up",
+ "message": "Up and running"
+ }
+ },
+ "error": {},
+ "details": {
+ "mediaFiles.space": {
+ "status": "up",
+ "path": "/tmp/whatsapp-files",
+ "diskPath": "/",
+ "free": 132977496064,
+ "threshold": 104857600
+ },
+ "mongodb": {
+ "status": "up",
+ "message": "Up and running"
+ }
+ }
+}
+```
+
+**Unhealthy response example**
+
+**503 Service Unavailable**
+```json
+{
+ "status": "error",
+ "info": {
+ "mediaFiles.space": {
+ "status": "up",
+ "path": "/tmp/whatsapp-files",
+ "diskPath": "/",
+ "free": 132976623616,
+ "threshold": 104857600
+ }
+ },
+ "error": {
+ "mongodb": {
+ "status": "down",
+ "error": "Timeout"
+ }
+ },
+ "details": {
+ "mediaFiles.space": {
+ "status": "up",
+ "path": "/tmp/whatsapp-files",
+ "diskPath": "/",
+ "free": 132976623616,
+ "threshold": 104857600
+ },
+ "mongodb": {
+ "status": "down",
+ "error": "Timeout"
+ }
+ }
+}
+```
+
+
+
+## Get version
+Returns the version of the installed docker image.
+```
+GET /api/version
+```
+
+```json
+{
+ "version": "2024.2.3",
+ "engine": "NOWEB",
+ "tier": "PLUS",
+ "browser": "/usr/bin/google-chrome-stable"
+}
+```
+
diff --git a/docs/site/content/en/docs/how-to/storages/index.md b/docs/site/content/en/docs/how-to/storages/index.md
index cae2eac5..d2f2d4a3 100644
--- a/docs/site/content/en/docs/how-to/storages/index.md
+++ b/docs/site/content/en/docs/how-to/storages/index.md
@@ -61,6 +61,8 @@ Under the hood, the WAHA stores the session data in the following directory stru
when you "logout" the session using [POST /api/sessions/logout]({{< relref "/docs/how-to/sessions#logout">}}) or providing `logout: True` in [POST /api/sessions/stop]({{< relref "/docs/how-to/sessions#stop">}})
it removes the directory with the session data.
+### Health Check
+The [WAHA Plus ]({{< relref "/docs/how-to/plus-version" >}}) provides [the health check endpoint]({{< relref "/docs/how-to/other" >}}) that checks the local storage.
## Sessions - MongoDB
If you want to use the MongoDB to store the session data, you need to:
@@ -103,6 +105,9 @@ If you use the [MongoDB Atlas](https://www.mongodb.com/atlas/database) you must
For production please consider running the MongoDB server close to the WAHA server for the best performance and security reasons.
+### Health Check
+The [WAHA Plus ]({{< relref "/docs/how-to/plus-version" >}}) provides [the health check endpoint]({{< relref "/docs/how-to/other" >}}) that checks the MongoDB connection.
+
## Media
When your WhatsApp instance receives media files, it stores them in the media storage.
@@ -136,6 +141,9 @@ Here's all the steps in one command:
docker run -v /path/to/on/host/.media:/app/.media -e WHATSAPP_FILES_FOLDER=/app/.media -e WHATSAPP_FILES_LIFETIME=0 -p 3000:3000/tcp devlikeapro/whatsapp-http-api-plus
```
+### Health Check
+The [WAHA Plus ]({{< relref "/docs/how-to/plus-version" >}}) provides [the health check endpoint]({{< relref "/docs/how-to/other" >}}) that checks the local storage.
+
## Media - S3
If you're interested in using the S3 storage or any other cloud storage (like [self-hosted S3 - Minio](https://min.io/)),
please create an issue or vote for the S3 issue in [the GitHub repository](https://github.com/devlikeapro/whatsapp-http-api/issues?q=is%3Aissue+is%3Aopen+S3).
diff --git a/docs/site/content/en/docs/overview/changelog.md b/docs/site/content/en/docs/overview/changelog.md
index f5467efc..c0d34d2a 100644
--- a/docs/site/content/en/docs/overview/changelog.md
+++ b/docs/site/content/en/docs/overview/changelog.md
@@ -23,6 +23,7 @@ You even can **subscribe to get new updates** there!
- Add support for [MongoDB as storage for Session data]({{< relref "/docs/how-to/storages" >}})
- Support persistent file storage for media files - [now you can save media files between container restarts]({{< relref "/docs/how-to/storages#media" >}})
- If you set `WHATSAPP_FILES_LIFETIME=0` environment variable - media files will be never deleted.
+- Add `GET /api/health` endpoint to [check the health of the service](https://waha.devlike.pro/docs/how-to/other/)
## 2024.1
- Implement [Patron Portal](https://portal.devlike.pro/) where you can get your personal API key and manage your perks.
diff --git a/package.json b/package.json
index 28f2cb78..48fda473 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
"dependencies": {
"@adiwajshing/baileys": "github:WhiskeySockets/Baileys",
"@adiwajshing/keyed-db": "^0.2.4",
+ "@nestjs/axios": "^3.0.2",
"@nestjs/common": "^9.0.9",
"@nestjs/config": "^0.5.0",
"@nestjs/core": "^9.0.9",
@@ -31,8 +32,10 @@
"@nestjs/platform-express": "^9.0.9",
"@nestjs/serve-static": "^2.1.3",
"@nestjs/swagger": "^7.1.11",
+ "@nestjs/terminus": "^10.2.3",
"@types/lodash": "^4.14.194",
"@types/ws": "^8.5.4",
+ "check-disk-space": "^3.4.0",
"class-validator": "^0.12.2",
"del": "^6.0.0",
"express-basic-auth": "^1.2.1",
diff --git a/src/api/health.controller.ts b/src/api/health.controller.ts
new file mode 100644
index 00000000..6dd2dea2
--- /dev/null
+++ b/src/api/health.controller.ts
@@ -0,0 +1,17 @@
+import { Controller, Get } from '@nestjs/common';
+import { ApiTags } from '@nestjs/swagger';
+import { HealthCheck } from '@nestjs/terminus';
+
+import { WAHAHealthCheckService } from '../core/abc/WAHAHealthCheckService';
+
+@Controller('health')
+@ApiTags('other')
+export class HealthController {
+ constructor(private wahaHealth: WAHAHealthCheckService) {}
+
+ @Get()
+ @HealthCheck()
+ async check() {
+ return this.wahaHealth.check();
+ }
+}
diff --git a/src/config.service.ts b/src/config.service.ts
index 4e414910..802947db 100644
--- a/src/config.service.ts
+++ b/src/config.service.ts
@@ -155,4 +155,25 @@ export class WhatsappConfigService {
getSwaggerAdvancedConfigEnabled(): boolean {
return this.configService.get('WHATSAPP_SWAGGER_CONFIG_ADVANCED', false);
}
+
+ getHealthMediaFilesThreshold(): number {
+ return this.configService.get(
+ 'WHATSAPP_HEALTH_MEDIA_FILES_THRESHOLD_MB',
+ 100,
+ );
+ }
+
+ getHealthSessionFilesThreshold(): number {
+ return this.configService.get(
+ 'WHATSAPP_HEALTH_SESSION_FILES_THRESHOLD_MB',
+ 100,
+ );
+ }
+
+ getHealthMongoTimeout(): number {
+ return this.configService.get(
+ 'WHATSAPP_HEALTH_MONGO_TIMEOUT_MS',
+ 3000,
+ );
+ }
}
diff --git a/src/core/abc/WAHAHealthCheckService.ts b/src/core/abc/WAHAHealthCheckService.ts
new file mode 100644
index 00000000..fed59124
--- /dev/null
+++ b/src/core/abc/WAHAHealthCheckService.ts
@@ -0,0 +1,18 @@
+import { ConsoleLogger, Injectable } from '@nestjs/common';
+import { DiskHealthIndicator, HealthCheckService } from '@nestjs/terminus';
+import type { HealthCheckResult } from '@nestjs/terminus/dist/health-check/health-check-result.interface';
+
+import { WhatsappConfigService } from '../../config.service';
+import { SessionManager } from './manager.abc';
+
+@Injectable()
+export abstract class WAHAHealthCheckService {
+ constructor(
+ protected sessionManager: SessionManager,
+ protected health: HealthCheckService,
+ protected log: ConsoleLogger,
+ protected config: WhatsappConfigService,
+ ) {}
+
+ abstract check(): Promise;
+}
diff --git a/src/core/app.module.core.ts b/src/core/app.module.core.ts
index e4b6eb50..fabe41df 100644
--- a/src/core/app.module.core.ts
+++ b/src/core/app.module.core.ts
@@ -2,12 +2,14 @@ import { ConsoleLogger, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { PassportModule } from '@nestjs/passport';
import { ServeStaticModule } from '@nestjs/serve-static';
+import { TerminusModule } from '@nestjs/terminus';
import { AuthController } from '../api/auth.controller';
import { ChatsController } from '../api/chats.controller';
import { ChattingController } from '../api/chatting.controller';
import { ContactsController } from '../api/contacts.controller';
import { GroupsController } from '../api/groups.controller';
+import { HealthController } from '../api/health.controller';
import { PresenceController } from '../api/presence.controller';
import { ScreenshotController } from '../api/screenshot.controller';
import {
@@ -18,6 +20,8 @@ import { StatusController } from '../api/status.controller';
import { VersionController } from '../api/version.controller';
import { WhatsappConfigService } from '../config.service';
import { SessionManager } from './abc/manager.abc';
+import { WAHAHealthCheckService } from './abc/WAHAHealthCheckService';
+import { WAHAHealthCheckServiceCore } from './health/WAHAHealthCheckServiceCore';
import { SessionManagerCore } from './manager.core';
export const IMPORTS = [
@@ -38,6 +42,7 @@ export const IMPORTS = [
},
}),
PassportModule,
+ TerminusModule,
];
export const CONTROLLERS = [
AuthController,
@@ -51,12 +56,17 @@ export const CONTROLLERS = [
PresenceController,
ScreenshotController,
VersionController,
+ HealthController,
];
const PROVIDERS = [
{
provide: SessionManager,
useClass: SessionManagerCore,
},
+ {
+ provide: WAHAHealthCheckService,
+ useClass: WAHAHealthCheckServiceCore,
+ },
WhatsappConfigService,
ConsoleLogger,
];
diff --git a/src/core/health/WAHAHealthCheckServiceCore.ts b/src/core/health/WAHAHealthCheckServiceCore.ts
new file mode 100644
index 00000000..8d30233a
--- /dev/null
+++ b/src/core/health/WAHAHealthCheckServiceCore.ts
@@ -0,0 +1,12 @@
+import { Injectable } from '@nestjs/common';
+import { HealthCheckResult } from '@nestjs/terminus';
+
+import { WAHAHealthCheckService } from '../abc/WAHAHealthCheckService';
+import { AvailableInPlusVersion } from '../exceptions';
+
+@Injectable()
+export class WAHAHealthCheckServiceCore extends WAHAHealthCheckService {
+ check(): Promise {
+ throw new AvailableInPlusVersion();
+ }
+}
diff --git a/src/utils/promiseTimeout.ts b/src/utils/promiseTimeout.ts
new file mode 100644
index 00000000..2daf133c
--- /dev/null
+++ b/src/utils/promiseTimeout.ts
@@ -0,0 +1,34 @@
+/**
+ * An errors which gets raised when the timeout
+ * exceeded
+ *
+ * @internal
+ */
+export class TimeoutError extends Error {}
+
+/**
+ * Executes a promise in the given timeout. If the promise
+ * does not finish in the given timeout, it will
+ * raise a TimeoutError
+ *
+ * @param {number} ms The timeout in milliseconds
+ * @param {Promise} promise The promise which should get executed
+ *
+ * @internal
+ */
+export const promiseTimeout = function (
+ ms: number,
+ promise: Promise,
+): Promise {
+ let timer: NodeJS.Timeout;
+ return Promise.race([
+ promise,
+ new Promise(
+ (_, reject) =>
+ (timer = setTimeout(
+ () => reject(new TimeoutError(`Timed out in ${ms}ms.`)),
+ ms,
+ )),
+ ),
+ ]).finally(() => clearTimeout(timer));
+};
diff --git a/yarn.lock b/yarn.lock
index 43046b81..74067c0c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -955,6 +955,17 @@ __metadata:
languageName: node
linkType: hard
+"@nestjs/axios@npm:^3.0.2":
+ version: 3.0.2
+ resolution: "@nestjs/axios@npm:3.0.2"
+ peerDependencies:
+ "@nestjs/common": ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0
+ axios: ^1.3.1
+ rxjs: ^6.0.0 || ^7.0.0
+ checksum: 285a735fb5db602b63aa4a37e161f609b2cec05b69f4bffe983617c2136ac29c0a33bb96e6276d22a656907bed5d53460e740310bc05c043dcd39c37db7cda29
+ languageName: node
+ linkType: hard
+
"@nestjs/cli@npm:^9.0.0":
version: 9.5.0
resolution: "@nestjs/cli@npm:9.5.0"
@@ -1153,6 +1164,61 @@ __metadata:
languageName: node
linkType: hard
+"@nestjs/terminus@npm:^10.2.3":
+ version: 10.2.3
+ resolution: "@nestjs/terminus@npm:10.2.3"
+ dependencies:
+ boxen: 5.1.2
+ check-disk-space: 3.4.0
+ peerDependencies:
+ "@grpc/grpc-js": "*"
+ "@grpc/proto-loader": "*"
+ "@mikro-orm/core": "*"
+ "@mikro-orm/nestjs": "*"
+ "@nestjs/axios": ^1.0.0 || ^2.0.0 || ^3.0.0
+ "@nestjs/common": ^9.0.0 || ^10.0.0
+ "@nestjs/core": ^9.0.0 || ^10.0.0
+ "@nestjs/microservices": ^9.0.0 || ^10.0.0
+ "@nestjs/mongoose": ^9.0.0 || ^10.0.0
+ "@nestjs/sequelize": ^9.0.0 || ^10.0.0
+ "@nestjs/typeorm": ^9.0.0 || ^10.0.0
+ "@prisma/client": "*"
+ mongoose: "*"
+ reflect-metadata: 0.1.x || 0.2.x
+ rxjs: 7.x
+ sequelize: "*"
+ typeorm: "*"
+ peerDependenciesMeta:
+ "@grpc/grpc-js":
+ optional: true
+ "@grpc/proto-loader":
+ optional: true
+ "@mikro-orm/core":
+ optional: true
+ "@mikro-orm/nestjs":
+ optional: true
+ "@nestjs/axios":
+ optional: true
+ "@nestjs/microservices":
+ optional: true
+ "@nestjs/mongoose":
+ optional: true
+ "@nestjs/sequelize":
+ optional: true
+ "@nestjs/typeorm":
+ optional: true
+ "@prisma/client":
+ optional: true
+ mongoose:
+ optional: true
+ sequelize:
+ optional: true
+ typeorm:
+ optional: true
+ checksum: 7a758c845c0a07a75230731d3b638919aae0b503b6cef97fa7f63d4cd23c6c6db0e8d39d5ecf4d8fda6e3f0aa7d4aa95fddcf5868758cf85d201db6acc43ac9d
+ languageName: node
+ linkType: hard
+
"@nestjs/testing@npm:^9.0.9":
version: 9.4.3
resolution: "@nestjs/testing@npm:9.4.3"
@@ -3216,7 +3282,7 @@ __metadata:
languageName: node
linkType: hard
-"boxen@npm:^5.1.1":
+"boxen@npm:5.1.2, boxen@npm:^5.1.1":
version: 5.1.2
resolution: "boxen@npm:5.1.2"
dependencies:
@@ -3592,6 +3658,13 @@ __metadata:
languageName: node
linkType: hard
+"check-disk-space@npm:3.4.0, check-disk-space@npm:^3.4.0":
+ version: 3.4.0
+ resolution: "check-disk-space@npm:3.4.0"
+ checksum: 0154c149c34a1233fe542f2a9f9ea2526bb90169489259eda8de1cf6870f765e8f5299c3b085085cc1ef33d9559f589fd10609c7e52d2e624ef6b5720dd9743c
+ languageName: node
+ linkType: hard
+
"cheerio-select@npm:^2.1.0":
version: 2.1.0
resolution: "cheerio-select@npm:2.1.0"
@@ -13216,6 +13289,7 @@ __metadata:
dependencies:
"@adiwajshing/baileys": "github:WhiskeySockets/Baileys"
"@adiwajshing/keyed-db": ^0.2.4
+ "@nestjs/axios": ^3.0.2
"@nestjs/cli": ^9.0.0
"@nestjs/common": ^9.0.9
"@nestjs/config": ^0.5.0
@@ -13225,6 +13299,7 @@ __metadata:
"@nestjs/schematics": ^9.0.1
"@nestjs/serve-static": ^2.1.3
"@nestjs/swagger": ^7.1.11
+ "@nestjs/terminus": ^10.2.3
"@nestjs/testing": ^9.0.9
"@types/express": ^4.17.3
"@types/jest": 26.0.10
@@ -13234,6 +13309,7 @@ __metadata:
"@types/ws": ^8.5.4
"@typescript-eslint/eslint-plugin": 3.9.1
"@typescript-eslint/parser": 3.9.1
+ check-disk-space: ^3.4.0
class-validator: ^0.12.2
del: ^6.0.0
eslint: 7.7.0