diff --git a/.gitignore b/.gitignore index 63c1245..60ecdfd 100644 --- a/.gitignore +++ b/.gitignore @@ -66,6 +66,9 @@ uploads/ # Git worktrees .worktrees/ +# TypeScript +*.tsbuildinfo + # AI Agents .agent/ .claude/ diff --git a/CHANGELOG.md b/CHANGELOG.md index ba12a70..d9e029b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,7 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Lockfile compatibility**: Regenerated `package-lock.json` with npm 10 to match CI runtime - **TypeScript type conflicts**: Fixed `whatsapp-web.js` type mismatches after dependency update using `Omit<>` pattern - **ESLint peer dependency**: Pinned `@eslint/js` and `eslint` to v9 to resolve Dependabot-introduced peer conflict -- **CI npm audit**: Changed audit level from `high` to `critical` โ€” high-severity findings are all in unfixable transitive dependencies +- **CI npm audit**: Changed audit level from `high` to `critical` - high-severity findings are all in unfixable transitive dependencies ### Changed @@ -98,21 +98,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - **Dashboard React Query**: Migrate all 8 pages from manual `useState`/`useEffect` to `@tanstack/react-query` with automatic caching and deduplication -- **Dashboard code splitting**: Route-level lazy loading with `React.lazy` + `Suspense` โ€” main bundle reduced 36% +- **Dashboard code splitting**: Route-level lazy loading with `React.lazy` + `Suspense` - main bundle reduced 36% ### Added - **CI npm audit**: `npm audit --audit-level=high` in CI pipeline to catch vulnerabilities - **CI coverage threshold**: Jest coverage floor to prevent regression - **CI dashboard job**: Lint + build for React dashboard runs parallel with backend CI -- **Dependabot**: Automated dependency updates โ€” npm weekly, GitHub Actions monthly +- **Dependabot**: Automated dependency updates - npm weekly, GitHub Actions monthly ## [0.1.1] - 2026-02-17 ### Added - **Unit Tests**: 94 new tests across auth, session, message, and webhook modules (110 total, ~17% coverage) -- **Release Workflow**: `release.yml` GitHub Actions โ€” tag-triggered with test gate, GitHub Release, and Docker semver tagging +- **Release Workflow**: `release.yml` GitHub Actions - tag-triggered with test gate, GitHub Release, and Docker semver tagging - **SDK Scaffolds**: JavaScript/TypeScript and Python client libraries in `sdk/` directory - New hook events: `webhook:queued` (after queue add) and `webhook:delivered` (after actual delivery) @@ -126,7 +126,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.1.0] - 2026-02-05 -### ๐ŸŽ‰ Initial Release +### Initial Release OpenWA v0.1.0 is the first stable release featuring a complete WhatsApp API Gateway with all core functionality. diff --git a/README.md b/README.md index 3bcea7e..e4acd60 100644 --- a/README.md +++ b/README.md @@ -22,77 +22,80 @@ NestJS Docker TypeScript + Tests + Coverage + CI

--- -## โœจ Why OpenWA? +## Why OpenWA? -**OpenWA** is a free, open-source WhatsApp API Gateway designed for developers who need full control over their messaging infrastructureโ€”without vendor lock-in or hidden paywalls. +**OpenWA** is a free, open-source WhatsApp API Gateway designed for developers who need full control over their messaging infrastructure, without vendor lock-in or hidden paywalls. Built on a **pluggable architecture**, OpenWA lets you swap database engines (SQLite/PostgreSQL), storage backends (Local/S3), and cache layers (Memory/Redis) without changing a single line of application code. | | | | ----------------------------- | ------------------------------------------------------------ | -| ๐Ÿ”“ **100% Open Source** | No licensing fees, no feature locks, full source code access | -| ๐Ÿ—๏ธ **Pluggable Architecture** | Swap adapters for database, storage, and cache via config | -| ๐Ÿ–ฅ๏ธ **Full Dashboard** | Modern React UI for session, webhook, and API key management | -| ๐Ÿ”น **Multi-Session Ready** | Run multiple WhatsApp sessions concurrently on one instance | -| ๐Ÿณ **Docker Native** | Production-ready with zero configuration | -| ๐Ÿ”— **n8n Integration** | Community nodes for workflow automation | +| **100% Open Source** | No licensing fees, no feature locks, full source code access | +| **Pluggable Architecture** | Swap adapters for database, storage, and cache via config | +| **Full Dashboard** | Modern React UI for session, webhook, and API key management | +| **Multi-Session Ready** | Run multiple WhatsApp sessions concurrently on one instance | +| **Docker Native** | Production-ready with zero configuration | +| **n8n Integration** | Community nodes for workflow automation | --- -## ๐ŸŽฏ Features +## Features ### Core Features | Feature | Status | Description | | ------------- | ------ | ------------------------------------ | -| REST API | โœ… | Full WhatsApp API via HTTP endpoints | -| Multi-Session | โœ… | Manage multiple WhatsApp accounts | -| Webhooks | โœ… | Real-time events with HMAC signature | -| Web Dashboard | โœ… | Visual management interface | -| API Key Auth | โœ… | Secure API authentication | -| Swagger Docs | โœ… | Interactive API documentation | +| REST API | Yes | Full WhatsApp API via HTTP endpoints | +| Multi-Session | Yes | Manage multiple WhatsApp accounts | +| Webhooks | Yes | Real-time events with HMAC signature | +| Web Dashboard | Yes | Visual management interface | +| API Key Auth | Yes | Secure API authentication | +| Swagger Docs | Yes | Interactive API documentation | ### Messaging | Feature | Status | Description | | ----------------- | ------ | -------------------------------- | -| Text Messages | โœ… | Send/receive text messages | -| Media Messages | โœ… | Images, videos, documents, audio | -| Message Reactions | โœ… | React to messages with emoji | -| Bulk Messaging | โœ… | Send to multiple recipients | -| Message Status | โœ… | Track delivery and read receipts | +| Text Messages | Yes | Send/receive text messages | +| Media Messages | Yes | Images, videos, documents, audio | +| Message Reactions | Yes | React to messages with emoji | +| Bulk Messaging | Yes | Send to multiple recipients | +| Message Status | Yes | Track delivery and read receipts | ### Advanced | Feature | Status | Description | | ------------------- | ------ | ---------------------------------- | -| Groups API | โœ… | Create, manage, and message groups | -| Channels/Newsletter | โœ… | WhatsApp Channels support | -| Labels Management | โœ… | Organize chats with labels | -| Proxy Support | โœ… | Per-session proxy configuration | -| Rate Limiting | โœ… | Configurable request limits | -| CIDR Whitelisting | โœ… | IP-based access control | -| Audit Logging | โœ… | Track all API operations | +| Groups API | Yes | Create, manage, and message groups | +| Channels/Newsletter | Yes | WhatsApp Channels support | +| Labels Management | Yes | Organize chats with labels | +| Proxy Support | Yes | Per-session proxy configuration | +| Rate Limiting | Yes | Configurable request limits | +| CIDR Whitelisting | Yes | IP-based access control | +| Audit Logging | Yes | Track all API operations | ### Infrastructure | Feature | Status | Description | | ---------------- | ------ | ------------------------------ | -| SQLite | โœ… | Zero-config embedded database | -| PostgreSQL | โœ… | Production-grade database | -| Redis Cache | โœ… | Optional performance caching | -| S3/MinIO Storage | โœ… | Scalable media storage | -| Docker | โœ… | One-command deployment | -| Health Checks | โœ… | Kubernetes-ready probes | -| Data Migration | โœ… | Export/import between backends | +| SQLite | Yes | Zero-config embedded database | +| PostgreSQL | Yes | Production-grade database | +| Redis Cache | Yes | Optional performance caching | +| S3/MinIO Storage | Yes | Scalable media storage | +| Docker | Yes | One-command deployment | +| Health Checks | Yes | Kubernetes-ready probes | +| Data Migration | Yes | Export/import between backends | --- -## ๐Ÿš€ Quick Start +## Quick Start ### Option A: Docker (Recommended) @@ -129,7 +132,7 @@ npm run dev --- -## ๐Ÿญ Production Deployment +## Production Deployment For production, use the main `docker-compose.yml` with optional services: @@ -158,7 +161,7 @@ docker compose --profile full up -d > - Development (`docker-compose.dev.yml`): SQLite, local storage, both API & Dashboard included > - Production (`docker-compose.yml`): Configurable database, profiles for optional services -## ๐Ÿ”Œ Ports +## Ports | Service | Port | Description | | --------- | --------------- | ------------------------ | @@ -168,7 +171,7 @@ docker compose --profile full up -d --- -## ๐Ÿ“ก API Examples +## API Examples ### Create a Session @@ -218,7 +221,7 @@ curl -X POST http://localhost:2785/api/sessions/{sessionId}/webhooks \ --- -## ๐Ÿ›  Tech Stack +## Tech Stack | Layer | Technology | | ------------- | ----------------------- | @@ -234,7 +237,7 @@ curl -X POST http://localhost:2785/api/sessions/{sessionId}/webhooks \ --- -## ๐Ÿ“ Project Structure +## Project Structure ``` openwa/ @@ -267,7 +270,7 @@ openwa/ --- -## ๐Ÿ“š Documentation +## Documentation Comprehensive documentation is available in the `docs/` folder: @@ -284,7 +287,7 @@ Comprehensive documentation is available in the `docs/` folder: --- -## ๐Ÿค Contributing +## Contributing We welcome contributions! Here's how to get started: @@ -298,9 +301,9 @@ Please read our [Development Guidelines](./docs/08-development-guidelines.md) fo --- -## ๐Ÿ“„ License +## License -This project is licensed under the **MIT License** โ€“ free for personal and commercial use. +This project is licensed under the **MIT License** - free for personal and commercial use. See [LICENSE](./LICENSE) for details. @@ -308,12 +311,12 @@ See [LICENSE](./LICENSE) for details.
-**OpenWA** โ€“ Free, Open Source WhatsApp API Gateway +**OpenWA** - Free, Open Source WhatsApp API Gateway -[๐Ÿ“– Documentation](./docs/README.md) ยท [๐Ÿ”Œ API Docs](http://localhost:2785/api/docs) ยท [๐Ÿ› Report Bug](https://github.com/rmyndharis/OpenWA/issues) ยท [๐Ÿ’ก Request Feature](https://github.com/rmyndharis/OpenWA/issues) +[Documentation](./docs/README.md) ยท [API Docs](http://localhost:2785/api/docs) ยท [Report Bug](https://github.com/rmyndharis/OpenWA/issues) ยท [Request Feature](https://github.com/rmyndharis/OpenWA/issues)
-Made with โค๏ธ by Yudhi Armyndharis and the OpenWA Community +Made by Yudhi Armyndharis and the OpenWA Community
diff --git a/dashboard/README.md b/dashboard/README.md index f029b0d..94db091 100644 --- a/dashboard/README.md +++ b/dashboard/README.md @@ -6,7 +6,7 @@ Modern web dashboard for managing OpenWA WhatsApp API Gateway sessions, webhooks, and infrastructure. -## โœจ Features +## Features - **Session Management** - Create, monitor, and control WhatsApp sessions - **QR Code Authentication** - Real-time QR code display for device pairing @@ -15,7 +15,7 @@ Modern web dashboard for managing OpenWA WhatsApp API Gateway sessions, webhooks - **Infrastructure Monitoring** - View system health and storage status - **Real-time Updates** - Live session status via WebSocket -## ๐Ÿ› ๏ธ Tech Stack +## Tech Stack | Technology | Purpose | | ---------------- | ----------------------- | @@ -28,7 +28,7 @@ Modern web dashboard for managing OpenWA WhatsApp API Gateway sessions, webhooks | Socket.IO Client | Real-time Communication | | Lucide React | Icons | -## ๐Ÿš€ Getting Started +## Getting Started ### Prerequisites @@ -57,7 +57,7 @@ npm run build npm run preview ``` -## ๐Ÿ“ Project Structure +## Project Structure ``` dashboard/ @@ -76,7 +76,7 @@ dashboard/ โ””โ”€โ”€ vite.config.ts # Vite configuration ``` -## ๐Ÿ”— API Connection +## API Connection The dashboard connects to the OpenWA API backend. Configure the API URL in environment variables: @@ -84,6 +84,6 @@ The dashboard connects to the OpenWA API backend. Configure the API URL in envir VITE_API_URL=http://localhost:2785 ``` -## ๐Ÿ“„ License +## License MIT License - Part of the [OpenWA](https://github.com/rmyndharis/OpenWA) project. diff --git a/dashboard/src/i18n/locales/en.json b/dashboard/src/i18n/locales/en.json index 5304924..c0c25e9 100644 --- a/dashboard/src/i18n/locales/en.json +++ b/dashboard/src/i18n/locales/en.json @@ -78,7 +78,7 @@ "connectionError": "Unable to connect to server. Please try again.", "help": "Need help?", "viewDocs": "View Documentation", - "footer": "Made with โค๏ธ by Yudhi Armyndharis and the OpenWA Community", + "footer": "Made by Yudhi Armyndharis and the OpenWA Community", "version": "v{{version}} ยท {{date}}" }, "dashboard": { @@ -447,11 +447,11 @@ "disabled": "Disabled" }, "restart": { - "idleTitle": "โš™๏ธ Configuration Saved", - "restartingTitle": "๐Ÿ”„ Restarting Server...", - "waitingTitle": "โณ Please Wait...", - "successTitle": "โœ… Server Ready", - "errorTitle": "โŒ Restart Failed", + "idleTitle": "Configuration Saved", + "restartingTitle": "Restarting Server...", + "waitingTitle": "Please Wait...", + "successTitle": "Server Ready", + "errorTitle": "Restart Failed", "idleDesc": "Configuration has been saved to .env.generated.
A server restart is required to apply the changes.", "later": "Restart Later", "now": "Restart Now", diff --git a/dashboard/src/i18n/locales/he.json b/dashboard/src/i18n/locales/he.json index 6088a1d..da74e8a 100644 --- a/dashboard/src/i18n/locales/he.json +++ b/dashboard/src/i18n/locales/he.json @@ -447,11 +447,11 @@ "disabled": "ืžื•ืฉื‘ืช" }, "restart": { - "idleTitle": "โš™๏ธ ื”ืชืฆื•ืจื” ื ืฉืžืจื”", - "restartingTitle": "๐Ÿ”„ ืžืืชื—ืœ ืืช ื”ืฉืจืช...", - "waitingTitle": "โณ ืื ื ืœื”ืžืชื™ืŸ...", - "successTitle": "โœ… ื”ืฉืจืช ืžื•ื›ืŸ", - "errorTitle": "โŒ ื”ืืชื—ื•ืœ ื ื›ืฉืœ", + "idleTitle": "ื”ืชืฆื•ืจื” ื ืฉืžืจื”", + "restartingTitle": "ืžืืชื—ืœ ืืช ื”ืฉืจืช...", + "waitingTitle": "ืื ื ืœื”ืžืชื™ืŸ...", + "successTitle": "ื”ืฉืจืช ืžื•ื›ืŸ", + "errorTitle": "ื”ืืชื—ื•ืœ ื ื›ืฉืœ", "idleDesc": "ื”ืชืฆื•ืจื” ื ืฉืžืจื” ืœ-.env.generated.
ื ื“ืจืฉ ืืชื—ื•ืœ ืฉืจืช ื›ื“ื™ ืœื”ื—ื™ืœ ืืช ื”ืฉื™ื ื•ื™ื™ื.", "later": "ืืชื—ื•ืœ ืžืื•ื—ืจ ื™ื•ืชืจ", "now": "ืืชื—ื•ืœ ืขื›ืฉื™ื•", diff --git a/dashboard/src/index.css b/dashboard/src/index.css index 82bee6d..899dec0 100644 --- a/dashboard/src/index.css +++ b/dashboard/src/index.css @@ -341,7 +341,7 @@ code { text-align: right; } -/* URLs, emails, phones โ€” force LTR even inside RTL */ +/* URLs, emails, phones - force LTR even inside RTL */ [dir="rtl"] input[type="url"], [dir="rtl"] input[type="email"], [dir="rtl"] input[type="tel"] { @@ -353,7 +353,7 @@ code { flex-direction: row-reverse; } -/* Toast container โ€” slide in from the opposite side */ +/* Toast container - slide in from the opposite side */ [dir="rtl"] .toast-container { right: auto; left: 1rem; diff --git a/dashboard/src/pages/Dashboard.tsx b/dashboard/src/pages/Dashboard.tsx index b5d1ff3..d300ddb 100644 --- a/dashboard/src/pages/Dashboard.tsx +++ b/dashboard/src/pages/Dashboard.tsx @@ -38,9 +38,9 @@ export function Dashboard() { trend: `+${stats?.ready ?? 0}`, trendUp: true, }, - { label: t('dashboard.stats.messagesToday'), value: 'โ€”', icon: Send, trend: '0', trendUp: null }, + { label: t('dashboard.stats.messagesToday'), value: '-', icon: Send, trend: '0', trendUp: null }, { label: t('dashboard.stats.webhooksConfigured'), value: webhookCount, icon: Webhook, trend: '0', trendUp: null }, - { label: t('dashboard.stats.apiCalls'), value: 'โ€”', icon: Activity, trend: '0', trendUp: null }, + { label: t('dashboard.stats.apiCalls'), value: '-', icon: Activity, trend: '0', trendUp: null }, ]; const formatLastActive = (date?: string) => { @@ -135,7 +135,7 @@ export function Dashboard() { {session.name} - {session.phone || 'โ€”'} + {session.phone || '-'} {formatStatus(session.status)} {formatLastActive(session.lastActive)}
diff --git a/dashboard/src/pages/Infrastructure.tsx b/dashboard/src/pages/Infrastructure.tsx index eac28e5..b5e2f6e 100644 --- a/dashboard/src/pages/Infrastructure.tsx +++ b/dashboard/src/pages/Infrastructure.tsx @@ -246,7 +246,7 @@ export function Infrastructure() { const response = await infraApi.restart(pendingProfiles, profilesToRemove); if (response.estimatedTime) setRestartCountdown(response.estimatedTime); } catch { - // Expected โ€” server shutting down + // Expected - server shutting down } setRestartStatus('waiting'); @@ -305,7 +305,7 @@ export function Infrastructure() {

{t('infrastructure.server.title')}

- โ— {serverConfig.nodeEnv === 'production' ? t('infrastructure.server.production') : t('infrastructure.server.development')} + {serverConfig.nodeEnv === 'production' ? t('infrastructure.server.production') : t('infrastructure.server.development')} @@ -455,7 +455,7 @@ export function Infrastructure() {

{t('infrastructure.database.title')}

- โ— {dbConfig.type === 'postgres' ? 'PostgreSQL' : 'SQLite'} + {dbConfig.type === 'postgres' ? 'PostgreSQL' : 'SQLite'} @@ -616,7 +616,7 @@ export function Infrastructure() { - โ— {redisEnabled + {redisEnabled ? redisConfig.connected ? t('infrastructure.statusLabels.connected') : t('infrastructure.statusLabels.disconnected') diff --git a/dashboard/src/pages/Logs.tsx b/dashboard/src/pages/Logs.tsx index 0678d3f..77f1ca0 100644 --- a/dashboard/src/pages/Logs.tsx +++ b/dashboard/src/pages/Logs.tsx @@ -104,9 +104,9 @@ export function Logs() {
{formatTimestamp(log.createdAt)} {log.action} - {log.sessionName || log.sessionId || 'โ€”'} - {log.apiKeyName || 'โ€”'} - {log.ipAddress || 'โ€”'} + {log.sessionName || log.sessionId || '-'} + {log.apiKeyName || '-'} + {log.ipAddress || '-'} {log.severity.toUpperCase()} diff --git a/dashboard/src/pages/Sessions.tsx b/dashboard/src/pages/Sessions.tsx index ac448e5..8f2798c 100644 --- a/dashboard/src/pages/Sessions.tsx +++ b/dashboard/src/pages/Sessions.tsx @@ -459,7 +459,7 @@ export function Sessions() {
{t('sessions.card.phone')} - {session.phone || 'โ€”'} + {session.phone || '-'}
{t('sessions.card.sessionId')} diff --git a/docs/01-project-overview.md b/docs/01-project-overview.md index 0fb61a9..fbce380 100644 --- a/docs/01-project-overview.md +++ b/docs/01-project-overview.md @@ -10,11 +10,11 @@ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ OpenWA Values โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ ๐Ÿ†“ 100% Free โ”‚ No paywalled features โ”‚ -โ”‚ ๐Ÿ“– Open Source โ”‚ MIT License, fork friendly โ”‚ -โ”‚ ๐Ÿ”’ Self-Hosted โ”‚ Data stays on your own server โ”‚ -โ”‚ ๐Ÿš€ Production Ready โ”‚ Scalable & reliable โ”‚ -โ”‚ ๐ŸŽฏ Developer First โ”‚ Simple, intuitive API โ”‚ +โ”‚ 100% Free โ”‚ No paywalled features โ”‚ +โ”‚ Open Source โ”‚ MIT License, fork friendly โ”‚ +โ”‚ Self-Hosted โ”‚ Data stays on your own server โ”‚ +โ”‚ Production Ready โ”‚ Scalable & reliable โ”‚ +โ”‚ Developer First โ”‚ Simple, intuitive API โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` @@ -33,14 +33,14 @@ To become the most reliable open-source WhatsApp API gateway for production use. ```mermaid flowchart LR - subgraph Problems["โŒ Current Problems"] + subgraph Problems[" Current Problems"] P1[Existing solutions are paid] P2[Critical features are paywalled] P3[Vendor lock-in] P4[Data privacy concerns] end - subgraph Solution["โœ… OpenWA Solution"] + subgraph Solution[" OpenWA Solution"] S1[100% Free & Open Source] S2[All features included] S3[Self-hosted, no lock-in] @@ -99,7 +99,7 @@ mindmap ## 1.5 Project Scope -### In Scope โœ… +### In Scope ``` Phase 1 (MVP) @@ -139,7 +139,7 @@ Phase 3 (Advanced) โ””โ”€โ”€ Metrics & monitoring ``` -### Out of Scope โŒ +### Out of Scope - WhatsApp Business API (official Meta API) - Mobile app @@ -205,13 +205,13 @@ quadrantChart | Feature | OpenWA | WAHA Core | WAHA Plus | Whapi.cloud | |---------|--------|-----------|-----------|-------------| | Price | Free | Free | $50+/mo | $30+/mo | -| Open Source | โœ… | โŒ | โŒ | โŒ | -| Multi-session | โœ… | Limited | โœ… | โœ… | -| Dashboard | โœ… | โŒ | โœ… | โœ… | -| PostgreSQL | โœ… | โŒ | โœ… | N/A | -| Webhook UI | โœ… | โŒ | โœ… | โœ… | -| Self-hosted | โœ… | โœ… | โœ… | โŒ | -| Source code | โœ… | โŒ | โŒ | โŒ | +| Open Source | | | | | +| Multi-session | | Limited | | | +| Dashboard | | | | | +| PostgreSQL | | | | N/A | +| Webhook UI | | | | | +| Self-hosted | | | | | +| Source code | | | | | ## 1.8 Technology Decisions diff --git a/docs/02-requirements-specification.md b/docs/02-requirements-specification.md index cb952d6..9131b46 100644 --- a/docs/02-requirements-specification.md +++ b/docs/02-requirements-specification.md @@ -453,35 +453,35 @@ stateDiagram-v2 | Requirement ID | Use Case | Test Case | Status | |----------------|----------|-----------|--------| -| FR-SM-001 | UC-003 | TC-SM-001 | โœ… Implemented | -| FR-SM-002 | UC-003 | TC-SM-002 | โœ… Implemented | -| FR-MSG-001 | UC-001 | TC-MSG-001 | โœ… Implemented | -| FR-WH-001 | UC-002 | TC-WH-001 | โœ… Implemented | +| FR-SM-001 | UC-003 | TC-SM-001 | Implemented | +| FR-SM-002 | UC-003 | TC-SM-002 | Implemented | +| FR-MSG-001 | UC-001 | TC-MSG-001 | Implemented | +| FR-WH-001 | UC-002 | TC-WH-001 | Implemented | ## 2.6 Acceptance Criteria ### Phase 1 MVP Acceptance Criteria ``` -โœ“ A single session can be created and authenticated -โœ“ QR code can be scanned and session connected -โœ“ Text messages can be sent -โœ“ Image messages can be sent -โœ“ Incoming messages are forwarded to webhooks -โœ“ API documentation is available via Swagger -โœ“ Docker image can be built and run -โœ“ Basic health check endpoint works + A single session can be created and authenticated + QR code can be scanned and session connected + Text messages can be sent + Image messages can be sent + Incoming messages are forwarded to webhooks + API documentation is available via Swagger + Docker image can be built and run + Basic health check endpoint works ``` ### Phase 2 Acceptance Criteria ``` -โœ“ Multiple sessions can run concurrently -โœ“ Dashboard can display all sessions -โœ“ Webhooks can be managed via UI -โœ“ PostgreSQL can be used as storage -โœ“ API key authentication works -โœ“ Rate limiting is enabled + Multiple sessions can run concurrently + Dashboard can display all sessions + Webhooks can be managed via UI + PostgreSQL can be used as storage + API key authentication works + Rate limiting is enabled ``` --- diff --git a/docs/03-system-architecture.md b/docs/03-system-architecture.md index 4a79182..bcd09c5 100644 --- a/docs/03-system-architecture.md +++ b/docs/03-system-architecture.md @@ -1219,9 +1219,9 @@ flowchart TB | **Resource Usage** | High (~500MB/session) | Low (~50MB/session) | | **Stability** | Good | Good | | **Community** | Large | Large | -| **Multi-device** | โœ… | โœ… | -| **QR Code** | โœ… | โœ… | -| **Phone Link** | โŒ | โœ… | +| **Multi-device** | | | +| **QR Code** | | | +| **Phone Link** | | | | **Maintenance** | Active | Active | ### Benefits of Abstraction @@ -1539,8 +1539,8 @@ OpenWA supports SQLite for lightweight deployments and PostgreSQL for high-volum |---------|--------|------------| | **Setup** | Zero config | Requires server | | **Concurrent writes** | Limited (1 writer) | Excellent | -| **Horizontal scaling** | โŒ | โœ… | -| **Table partitioning** | โŒ | โœ… | +| **Horizontal scaling** | | | +| **Table partitioning** | | | | **Memory footprint** | ~10MB | ~100MB+ | | **Backup** | Copy file | pg_dump | | **Best for** | 1-5 sessions | 5+ sessions | @@ -1705,21 +1705,21 @@ OpenWA provides several deployment profiles for different needs: ```mermaid flowchart LR - subgraph Minimal["๐Ÿชถ Minimal Profile"] + subgraph Minimal[" Minimal Profile"] M1[SQLite] M2[Local Storage] M3[In-Memory Cache] M4[Single Session] end - subgraph Standard["โšก Standard Profile"] + subgraph Standard[" Standard Profile"] S1[PostgreSQL] S2[Local Storage] S3[Redis] S4[Multi Session] end - subgraph Enterprise["๐Ÿข Enterprise Profile"] + subgraph Enterprise[" Enterprise Profile"] E1[PostgreSQL Cluster] E2[S3/MinIO] E3[Redis Cluster] diff --git a/docs/04-security-design.md b/docs/04-security-design.md index ff3c246..cab3378 100644 --- a/docs/04-security-design.md +++ b/docs/04-security-design.md @@ -569,13 +569,13 @@ flowchart TB ### Environment Variables Security ```bash -# โŒ BAD: Secrets in code or docker-compose.yml +# BAD: Secrets in code or docker-compose.yml DATABASE_URL=postgresql://user:password123@localhost:5432/db -# โœ… GOOD: Use .env file (not committed) +# GOOD: Use .env file (not committed) DATABASE_URL=${DATABASE_URL} -# โœ… BETTER: Use Docker secrets or vault +# BETTER: Use Docker secrets or vault docker secret create db_password ./secret.txt ``` diff --git a/docs/05-database-design.md b/docs/05-database-design.md index e84c239..1d9cf39 100644 --- a/docs/05-database-design.md +++ b/docs/05-database-design.md @@ -16,8 +16,8 @@ OpenWA supports two database backends that can be selected at deployment time: | Database | Use Case | Sessions | Horizontal Scaling | | -------------- | ------------------------------------------- | -------- | ------------------ | -| **SQLite** | Development, personal bot, low-resource VPS | 1-5 | โŒ | -| **PostgreSQL** | Production, multi-session, high volume | 5+ | โœ… | +| **SQLite** | Development, personal bot, low-resource VPS | 1-5 | | +| **PostgreSQL** | Production, multi-session, high volume | 5+ | | > [!NOTE] > **SQLite as a Production Option** diff --git a/docs/06-api-specification.md b/docs/06-api-specification.md index 65bb002..29786c2 100644 --- a/docs/06-api-specification.md +++ b/docs/06-api-specification.md @@ -421,7 +421,7 @@ GET /api/sessions/:sessionId | `DISCONNECTED` | Connection lost or logged out | | `FAILED` | Fatal error, manual intervention required | -**Internal โ†” API Status Mapping:** +**Internal API Status Mapping:** | Internal Status (engine/db) | API Status | |-----------------------------|------------| @@ -1582,10 +1582,10 @@ http://localhost:2785/api/docs-json | Language | Package | Status | |----------|---------|--------| -| JavaScript/TypeScript | `@openwa/sdk` | ๐Ÿ”œ Planned | -| Python | `openwa-python` | ๐Ÿ”œ Planned | -| PHP | `openwa/php-sdk` | ๐Ÿ”œ Planned | -| Go | `openwa-go` | ๐Ÿ”œ Planned | +| JavaScript/TypeScript | `@openwa/sdk` | Planned | +| Python | `openwa-python` | Planned | +| PHP | `openwa/php-sdk` | Planned | +| Go | `openwa-go` | Planned | ### cURL Examples diff --git a/docs/07-api-collection.md b/docs/07-api-collection.md index 465257d..d01b68c 100644 --- a/docs/07-api-collection.md +++ b/docs/07-api-collection.md @@ -571,7 +571,7 @@ React to message. curl -X POST http://localhost:2785/api/sessions/default/messages/MSG_ABC123/react \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ - -d '{"emoji": "๐Ÿ‘"}' + -d '{"emoji": ""}' ``` ## 07.5 Contacts API diff --git a/docs/08-development-guidelines.md b/docs/08-development-guidelines.md index b5f3056..c7b5633 100644 --- a/docs/08-development-guidelines.md +++ b/docs/08-development-guidelines.md @@ -1011,18 +1011,18 @@ docker compose logs -f app ### Database Queries ```typescript -// โŒ Bad: N+1 query problem +// Bad: N+1 query problem const sessions = await sessionRepo.find(); for (const session of sessions) { session.webhooks = await webhookRepo.find({ where: { sessionId: session.id } }); } -// โœ… Good: Use relations +// Good: Use relations const sessions = await sessionRepo.find({ relations: ['webhooks'], }); -// โœ… Good: Use QueryBuilder for complex queries +// Good: Use QueryBuilder for complex queries const sessions = await sessionRepo .createQueryBuilder('session') .leftJoinAndSelect('session.webhooks', 'webhook') @@ -1071,19 +1071,19 @@ export class SessionService { ### Async Operations ```typescript -// โŒ Bad: Sequential execution +// Bad: Sequential execution const contact1 = await getContact('id1'); const contact2 = await getContact('id2'); const contact3 = await getContact('id3'); -// โœ… Good: Parallel execution +// Good: Parallel execution const [contact1, contact2, contact3] = await Promise.all([ getContact('id1'), getContact('id2'), getContact('id3'), ]); -// โœ… Good: Batch processing with concurrency limit +// Good: Batch processing with concurrency limit import pLimit from 'p-limit'; const limit = pLimit(5); // Max 5 concurrent diff --git a/docs/09-testing-strategy.md b/docs/09-testing-strategy.md index fe47b52..d9a66b3 100644 --- a/docs/09-testing-strategy.md +++ b/docs/09-testing-strategy.md @@ -49,7 +49,7 @@ flowchart TB | Integration Test Coverage | > 70% | 0% | High | | E2E Critical Paths | 100% | 0% | High | | Performance Benchmarks | Pass all | Not started | Medium | -| Security Scan | 0 Critical | โœ… Passing | High | +| Security Scan | 0 Critical | Passing | High | ## 09.2 Unit Testing @@ -709,8 +709,8 @@ jobs: | Test | Target | Actual | Status | |------|--------|--------|--------| -| API p95 latency | <500ms | 320ms | โœ… | -| Throughput | 100 req/s | 125 req/s | โœ… | +| API p95 latency | <500ms | 320ms | | +| Throughput | 100 req/s | 125 req/s | | ``` --- diff --git a/docs/10-devops-infrastructure.md b/docs/10-devops-infrastructure.md index 96d324f..3608348 100644 --- a/docs/10-devops-infrastructure.md +++ b/docs/10-devops-infrastructure.md @@ -787,14 +787,14 @@ receivers: slack_configs: - channel: '#openwa-critical' send_resolved: true - title: '๐Ÿšจ CRITICAL: {{ .GroupLabels.alertname }}' + title: ' CRITICAL: {{ .GroupLabels.alertname }}' text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}' - name: 'slack-warnings' slack_configs: - channel: '#openwa-alerts' send_resolved: true - title: 'โš ๏ธ WARNING: {{ .GroupLabels.alertname }}' + title: ' WARNING: {{ .GroupLabels.alertname }}' ``` ### Health Check Endpoint diff --git a/docs/14-migration-guide.md b/docs/14-migration-guide.md index f84cb8c..d0d0a31 100644 --- a/docs/14-migration-guide.md +++ b/docs/14-migration-guide.md @@ -143,7 +143,7 @@ curl -X POST 'http://localhost:2785/api/infra/import-data' \ } ``` -### Storage Migration (Local โ†” S3/MinIO) +### Storage Migration (Local S3/MinIO) OpenWA v0.2+ supports migrating media files between storage backends: @@ -175,10 +175,10 @@ curl -X POST 'http://localhost:2785/api/infra/storage/import' \ | Scenario | Support | Method | | ---------------------------- | ------- | ------------------------ | -| Local โ†’ Built-in MinIO | โœ… | Export โ†’ Config โ†’ Import | -| Local โ†’ External S3 | โœ… | Export โ†’ Config โ†’ Import | -| Built-in MinIO โ†’ External S3 | โœ… | Export โ†’ Config โ†’ Import | -| S3 โ†’ Local | โœ… | Export โ†’ Config โ†’ Import | +| Local โ†’ Built-in MinIO | | Export โ†’ Config โ†’ Import | +| Local โ†’ External S3 | | Export โ†’ Config โ†’ Import | +| Built-in MinIO โ†’ External S3 | | Export โ†’ Config โ†’ Import | +| S3 โ†’ Local | | Export โ†’ Config โ†’ Import | ### Redis Migration (Cache) @@ -197,10 +197,10 @@ REDIS_PASSWORD=optional | Scenario | Support | Notes | | ------------------------- | ------- | ---------------------------- | -| Built-in โ†’ External Redis | โœ… | Config change only | -| External โ†’ Built-in Redis | โœ… | Config change only | -| Enable โ†’ Disable Redis | โœ… | App uses memory fallback | -| Disable โ†’ Enable Redis | โœ… | Cache rebuilds automatically | +| Built-in โ†’ External Redis | | Config change only | +| External โ†’ Built-in Redis | | Config change only | +| Enable โ†’ Disable Redis | | App uses memory fallback | +| Disable โ†’ Enable Redis | | Cache rebuilds automatically | > [!TIP] > **Cache Warm-up**: After switching Redis instances, the cache will automatically rebuild as requests come in. No data migration is necessary. @@ -230,9 +230,9 @@ docker compose up -d | Scenario | Support | Notes | | ------------------------- | ------- | ----------------- | -| Queue Disabled โ†’ Enabled | โœ… | Config change | -| Queue Enabled โ†’ Disabled | โš ๏ธ | Drain queue first | -| Built-in โ†’ External Redis | โš ๏ธ | Drain queue first | +| Queue Disabled โ†’ Enabled | | Config change | +| Queue Enabled โ†’ Disabled | | Drain queue first | +| Built-in โ†’ External Redis | | Drain queue first | > [!WARNING] > **Job Loss Prevention**: Always ensure the MESSAGE and WEBHOOK queues are empty before switching Redis instances. Check `/admin/queues` dashboard. @@ -272,7 +272,7 @@ async function migrateSqliteToPostgres(config: MigrationConfig): Promise { - console.log('\n๐Ÿ“Š Migration Summary:'); + console.log('\n Migration Summary:'); console.table( results.map(r => ({ Table: r.table, @@ -514,32 +514,32 @@ if [ -z "$SESSION_ID" ]; then fi # Stop both instances -echo "โน๏ธ Stopping services..." +echo " Stopping services..." ssh old-server "docker compose down" ssh new-server "docker compose down" # Copy session data -echo "๐Ÿ“ฆ Copying session data..." +echo " Copying session data..." rsync -avz --progress \ "old-server:${SOURCE_DIR}/session-${SESSION_ID}/" \ "${TARGET_DIR}/session-${SESSION_ID}/" # Copy database record -echo "๐Ÿ“„ Exporting session record..." +echo " Exporting session record..." ssh old-server "sqlite3 /data/openwa.db \ \"SELECT * FROM sessions WHERE id='${SESSION_ID}'\" \ -csv" > session_record.csv # Import to new database -echo "๐Ÿ“ฅ Importing session record..." +echo " Importing session record..." ssh new-server "sqlite3 /data/openwa.db \ \".import session_record.csv sessions\"" # Start new server -echo "โ–ถ๏ธ Starting new server..." +echo " Starting new server..." ssh new-server "docker compose up -d" -echo "โœ… Session ${SESSION_ID} transferred successfully" +echo " Session ${SESSION_ID} transferred successfully" ``` #### Method 2: Export/Import via API @@ -651,11 +651,11 @@ async function bulkTransferSessions(config: TransferConfig): Promise { sessionIds = response.data.map((s: any) => s.id); } - console.log(`๐Ÿ“‹ Transferring ${sessionIds.length} sessions...`); + console.log(` Transferring ${sessionIds.length} sessions...`); for (const sessionId of sessionIds) { try { - console.log(`\n๐Ÿ”„ Processing session: ${sessionId}`); + console.log(`\n Processing session: ${sessionId}`); // 1. Stop session on source await axios.post( @@ -674,9 +674,9 @@ async function bulkTransferSessions(config: TransferConfig): Promise { headers: { 'X-API-Key': config.targetApiKey }, }); - console.log(`โœ… Session ${sessionId} transferred`); + console.log(` Session ${sessionId} transferred`); } catch (error: any) { - console.error(`โŒ Failed to transfer ${sessionId}: ${error.message}`); + console.error(` Failed to transfer ${sessionId}: ${error.message}`); } } } @@ -731,21 +731,21 @@ breaking_changes: set -e -echo "๐Ÿš€ Upgrading OpenWA v0.1.x โ†’ v0.2.x" +echo " Upgrading OpenWA v0.1.x โ†’ v0.2.x" # 1. Backup -echo "๐Ÿ“ฆ Creating backup..." +echo " Creating backup..." BACKUP_DIR="./backups/v01-$(date +%Y%m%d-%H%M%S)" mkdir -p "$BACKUP_DIR" cp -r ./data "$BACKUP_DIR/" cp .env "$BACKUP_DIR/" # 2. Stop current version -echo "โน๏ธ Stopping v0.1..." +echo " Stopping v0.1..." docker compose down # 3. Run database migrations -echo "๐Ÿ”„ Running migrations..." +echo " Running migrations..." docker run --rm \ -v $(pwd)/data:/app/data \ -e DATABASE_URL=sqlite:///app/data/openwa.db \ @@ -753,7 +753,7 @@ docker run --rm \ npm run migration:run # 4. Migrate configuration -echo "โš™๏ธ Migrating configuration..." +echo " Migrating configuration..." cat > .env.new << 'EOF' # OpenWA v0.2.x Configuration @@ -782,22 +782,22 @@ mv .env.migrated .env rm .env.new # 5. Start new version -echo "โ–ถ๏ธ Starting v0.2..." +echo " Starting v0.2..." docker compose pull docker compose up -d # 6. Wait for health -echo "โณ Waiting for health check..." +echo " Waiting for health check..." sleep 10 curl -f http://localhost:2785/health || exit 1 # 7. Create API key for existing integrations -echo "๐Ÿ”‘ Creating API key..." +echo " Creating API key..." docker exec openwa npm run cli -- create-api-key --name "migrated-key" -echo "โœ… Upgrade complete!" +echo " Upgrade complete!" echo "" -echo "โš ๏ธ IMPORTANT: Update your API clients:" +echo " IMPORTANT: Update your API clients:" echo " - Change /api/session to /api/sessions" echo " - Change /api/send to /api/sessions/{id}/messages" echo " - Add X-API-Key header to all requests" @@ -831,14 +831,14 @@ breaking_changes: set -e -echo "๐Ÿš€ Upgrading OpenWA v0.2.x โ†’ v1.0.0" +echo " Upgrading OpenWA v0.2.x โ†’ v1.0.0" # Pre-flight checks CURRENT_VERSION=$(docker inspect ghcr.io/rmyndharis/openwa --format '{{.Config.Labels.version}}' 2>/dev/null || echo "unknown") echo "Current version: $CURRENT_VERSION" # 1. Comprehensive backup -echo "๐Ÿ“ฆ Creating comprehensive backup..." +echo " Creating comprehensive backup..." BACKUP_DIR="./backups/v02-$(date +%Y%m%d-%H%M%S)" mkdir -p "$BACKUP_DIR" @@ -857,7 +857,7 @@ cp .env "$BACKUP_DIR/" cp docker-compose.yml "$BACKUP_DIR/" # 2. Export webhook configurations (per session, new format in v1.0) -echo "๐Ÿ“ค Exporting webhooks..." +echo " Exporting webhooks..." for sessionId in $(curl -s -H "X-API-Key: $API_KEY" \ http://localhost:2785/api/sessions | jq -r '.data[].id'); do curl -s -H "X-API-Key: $API_KEY" \ @@ -866,11 +866,11 @@ for sessionId in $(curl -s -H "X-API-Key: $API_KEY" \ done # 3. Stop services -echo "โน๏ธ Stopping v0.2..." +echo " Stopping v0.2..." docker compose down # 4. Update configuration -echo "โš™๏ธ Updating configuration..." +echo " Updating configuration..." cat >> .env << 'EOF' # New in v1.0 @@ -883,7 +883,7 @@ WEBHOOK_VERSION=2 EOF # 5. Run database migrations -echo "๐Ÿ”„ Running migrations..." +echo " Running migrations..." docker run --rm \ -v $(pwd)/data:/app/data \ --env-file .env \ @@ -891,7 +891,7 @@ docker run --rm \ npm run migration:run # 6. Migrate webhooks to new format -echo "๐Ÿ”„ Migrating webhooks..." +echo " Migrating webhooks..." docker run --rm \ -v $(pwd)/data:/app/data \ -v "$BACKUP_DIR/webhooks.json:/tmp/webhooks.json" \ @@ -900,30 +900,30 @@ docker run --rm \ npm run cli -- migrate-webhooks /tmp/webhooks.json # 7. Start new version -echo "โ–ถ๏ธ Starting v1.0..." +echo " Starting v1.0..." sed -i 's/:0.2./:1.0./g' docker-compose.yml docker compose pull docker compose up -d # 8. Health check -echo "โณ Waiting for health check..." +echo " Waiting for health check..." for i in {1..30}; do if curl -sf http://localhost:2785/health > /dev/null; then - echo "โœ… Health check passed" + echo " Health check passed" break fi sleep 2 done # 9. Verify sessions -echo "๐Ÿ” Verifying sessions..." +echo " Verifying sessions..." curl -s -H "X-API-Key: $API_KEY" \ http://localhost:2785/api/sessions | jq '.[] | {id, status}' echo "" -echo "โœ… Upgrade to v1.0.0 complete!" +echo " Upgrade to v1.0.0 complete!" echo "" -echo "๐Ÿ“ Post-upgrade tasks:" +echo " Post-upgrade tasks:" echo " 1. Update webhook consumers for v2 payload format" echo " 2. Test all active sessions" echo " 3. Review new rate limits" @@ -947,13 +947,13 @@ if [ -z "$BACKUP_DIR" ] || [ -z "$TARGET_VERSION" ]; then exit 1 fi -echo "๐Ÿ”„ Rolling back to v${TARGET_VERSION}..." +echo " Rolling back to v${TARGET_VERSION}..." # 1. Stop current docker compose down # 2. Restore database -echo "๐Ÿ“ฅ Restoring database..." +echo " Restoring database..." if [ -f "$BACKUP_DIR/database.sql" ]; then # PostgreSQL psql $DATABASE_URL < "$BACKUP_DIR/database.sql" @@ -963,23 +963,23 @@ else fi # 3. Restore auth sessions -echo "๐Ÿ“ฅ Restoring auth sessions..." +echo " Restoring auth sessions..." rm -rf ./data/.wwebjs_auth cp -r "$BACKUP_DIR/.wwebjs_auth" ./data/ # 4. Restore configuration -echo "๐Ÿ“ฅ Restoring configuration..." +echo " Restoring configuration..." cp "$BACKUP_DIR/.env" . cp "$BACKUP_DIR/docker-compose.yml" . # 5. Start old version -echo "โ–ถ๏ธ Starting v${TARGET_VERSION}..." +echo " Starting v${TARGET_VERSION}..." docker compose pull docker compose up -d # 6. Verify sleep 10 -curl -f http://localhost:2785/health && echo "โœ… Rollback successful" +curl -f http://localhost:2785/health && echo " Rollback successful" ``` ### Rollback Decision Tree @@ -1105,7 +1105,7 @@ async function fullExport(options: ExportOptions): Promise { await fs.ensureDir(exportDir); // 1. Export database tables - console.log('๐Ÿ“Š Exporting database...'); + console.log(' Exporting database...'); const tables = ['sessions', 'messages', 'contacts', 'webhooks', 'api_keys']; for (const table of tables) { @@ -1114,23 +1114,23 @@ async function fullExport(options: ExportOptions): Promise { } // 2. Export auth sessions - console.log('๐Ÿ” Exporting auth sessions...'); + console.log(' Exporting auth sessions...'); await fs.copy('./data/.wwebjs_auth', path.join(exportDir, 'auth')); // 3. Export media (optional) if (options.includeMedia) { - console.log('๐Ÿ“ Exporting media files...'); + console.log(' Exporting media files...'); await fs.copy('./data/media', path.join(exportDir, 'media')); } // 4. Export logs (optional) if (options.includeLogs) { - console.log('๐Ÿ“ Exporting logs...'); + console.log(' Exporting logs...'); await fs.copy('./logs', path.join(exportDir, 'logs')); } // 5. Export configuration (sanitized) - console.log('โš™๏ธ Exporting configuration...'); + console.log(' Exporting configuration...'); const config = { version: process.env.npm_package_version, exportedAt: new Date().toISOString(), @@ -1144,7 +1144,7 @@ async function fullExport(options: ExportOptions): Promise { // 6. Compress (optional) if (options.compress) { - console.log('๐Ÿ—œ๏ธ Compressing export...'); + console.log(' Compressing export...'); const output = fs.createWriteStream(`${exportDir}.tar.gz`); const archive = archiver('tar', { gzip: true }); @@ -1153,9 +1153,9 @@ async function fullExport(options: ExportOptions): Promise { await archive.finalize(); await fs.remove(exportDir); - console.log(`โœ… Export complete: ${exportDir}.tar.gz`); + console.log(` Export complete: ${exportDir}.tar.gz`); } else { - console.log(`โœ… Export complete: ${exportDir}`); + console.log(` Export complete: ${exportDir}`); } } ``` @@ -1193,11 +1193,11 @@ async function fullImport(options: ImportOptions): Promise { } const exportConfig = await fs.readJson(configPath); - console.log(`๐Ÿ“ฆ Importing from v${exportConfig.version}`); - console.log(`๐Ÿ“… Exported at: ${exportConfig.exportedAt}`); + console.log(` Importing from v${exportConfig.version}`); + console.log(` Exported at: ${exportConfig.exportedAt}`); if (options.dryRun) { - console.log('๐Ÿ” DRY RUN - No changes will be made'); + console.log(' DRY RUN - No changes will be made'); } // Import order matters (foreign keys) @@ -1208,7 +1208,7 @@ async function fullImport(options: ImportOptions): Promise { if (!(await fs.pathExists(dataPath))) continue; const data = await fs.readJson(dataPath); - console.log(`๐Ÿ“ฅ Importing ${table}: ${data.length} records`); + console.log(` Importing ${table}: ${data.length} records`); if (!options.dryRun) { await importTable(table, data, options.mergeStrategy); @@ -1218,7 +1218,7 @@ async function fullImport(options: ImportOptions): Promise { // Import auth sessions const authPath = path.join(importDir, 'auth'); if (await fs.pathExists(authPath)) { - console.log('๐Ÿ” Importing auth sessions...'); + console.log(' Importing auth sessions...'); if (!options.dryRun) { await fs.copy(authPath, './data/.wwebjs_auth', { overwrite: options.mergeStrategy === 'replace', @@ -1226,7 +1226,7 @@ async function fullImport(options: ImportOptions): Promise { } } - console.log('โœ… Import complete'); + console.log(' Import complete'); } ``` diff --git a/docs/15-project-roadmap.md b/docs/15-project-roadmap.md index 2410bcc..c0cab26 100644 --- a/docs/15-project-roadmap.md +++ b/docs/15-project-roadmap.md @@ -42,16 +42,16 @@ timeline | Version | Focus | Status | | ------- | --------------------------- | ----------- | -| v0.0.1 | MVP - Basic API | โœ… Released | -| v0.0.2 | Production Ready | โœ… Released | -| v0.1.0 | Initial Stable Release | โœ… Released | -| v0.2.0 | SDK & Developer Tools | ๐Ÿ“‹ Planned | -| v0.3.0 | Performance & Observability | ๐Ÿ“‹ Planned | -| v1.0.0 | Enterprise Ready | ๐Ÿ“‹ Planned | +| v0.0.1 | MVP - Basic API | Released | +| v0.0.2 | Production Ready | Released | +| v0.1.0 | Initial Stable Release | Released | +| v0.2.0 | SDK & Developer Tools | Planned | +| v0.3.0 | Performance & Observability | Planned | +| v1.0.0 | Enterprise Ready | Planned | ### Risk Buffer -Each phase includes a 2โ€“3 week buffer for: +Each phase includes a 2-3 week buffer for: - Bug fixing and stabilization - WhatsApp protocol changes @@ -149,18 +149,18 @@ gantt ```mermaid flowchart TB - subgraph HighRisk["โš ๏ธ High Complexity Areas"] + subgraph HighRisk[" High Complexity Areas"] WW[whatsapp-web.js Integration] RC[Reconnection Logic] QR[QR Code Lifecycle] end - subgraph MediumRisk["โšก Medium Complexity"] + subgraph MediumRisk[" Medium Complexity"] WH[Webhook Reliability] MD[Media Handling] end - subgraph LowRisk["โœ… Low Complexity"] + subgraph LowRisk[" Low Complexity"] CRUD[Basic CRUD APIs] DOC[Documentation] DOCKER[Docker Setup] @@ -182,38 +182,38 @@ flowchart TB | Feature | Priority | Status | | ------------------ | -------- | ------ | -| Create session | P0 | โœ… | -| Delete session | P0 | โœ… | -| Get session status | P0 | โœ… | -| Generate QR code | P0 | โœ… | -| Session reconnect | P1 | โœ… | +| Create session | P0 | | +| Delete session | P0 | | +| Get session status | P0 | | +| Generate QR code | P0 | | +| Session reconnect | P1 | | #### Basic Messaging | Feature | Priority | Status | | ----------------- | -------- | ------ | -| Send text message | P0 | โœ… | -| Send image | P0 | โœ… | -| Send video | P1 | โœ… | -| Send audio | P1 | โœ… | -| Send document | P1 | โœ… | -| Receive messages | P0 | โœ… | +| Send text message | P0 | | +| Send image | P0 | | +| Send video | P1 | | +| Send audio | P1 | | +| Send document | P1 | | +| Receive messages | P0 | | #### Basic Webhooks | Feature | Priority | Status | | ---------------- | -------- | ------ | -| Webhook delivery | P0 | โœ… | -| Webhook retry | P0 | โœ… | +| Webhook delivery | P0 | | +| Webhook retry | P0 | | #### Infrastructure | Feature | Priority | Status | | -------------- | -------- | ------ | -| SQLite storage | P0 | โœ… | -| Docker support | P0 | โœ… | -| Health check | P1 | โœ… | -| Swagger docs | P0 | โœ… | +| SQLite storage | P0 | | +| Docker support | P0 | | +| Health check | P1 | | +| Swagger docs | P0 | | ### Deliverables @@ -296,34 +296,34 @@ gantt | Feature | Priority | Status | | ------------------ | -------- | ------ | -| Multi-session | P0 | โœ… | -| Session isolation | P0 | โœ… | -| Proxy per session | P1 | โœ… | -| PostgreSQL support | P0 | โœ… | -| Redis cache | P1 | โœ… | -| Job queue (Bull) | P1 | โœ… | -| Connection pooling | P1 | โœ… | +| Multi-session | P0 | | +| Session isolation | P0 | | +| Proxy per session | P1 | | +| PostgreSQL support | P0 | | +| Redis cache | P1 | | +| Job queue (Bull) | P1 | | +| Connection pooling | P1 | | #### Security & Auth | Feature | Priority | Status | | ---------------------- | -------- | ------ | -| API key authentication | P0 | โœ… | -| Rate limiting | P0 | โœ… | -| Permission system | P1 | โœ… | -| IP whitelisting | P2 | โœ… | -| Audit logging | P2 | โœ… | +| API key authentication | P0 | | +| Rate limiting | P0 | | +| Permission system | P1 | | +| IP whitelisting | P2 | | +| Audit logging | P2 | | #### Dashboard | Feature | Priority | Status | | --------------------- | -------- | ------ | -| Web dashboard | P0 | โœ… | -| Session management UI | P0 | โœ… | -| QR code display | P0 | โœ… | -| Webhook management UI | P1 | โœ… | -| Logs viewer | P1 | โœ… | -| Test message sender | P2 | โœ… | +| Web dashboard | P0 | | +| Session management UI | P0 | | +| QR code display | P0 | | +| Webhook management UI | P1 | | +| Logs viewer | P1 | | +| Test message sender | P2 | | ### Deliverables @@ -398,37 +398,37 @@ gantt | Feature | Priority | Status | | ----------------- | -------- | ------ | -| Send location | P1 | โœ… | -| Send contact | P1 | โœ… | -| Send sticker | P2 | โœ… | -| Message reactions | P2 | โœ… | -| Reply to message | P1 | โœ… | -| Forward message | P1 | โœ… | -| Message history | P2 | โœ… | +| Send location | P1 | | +| Send contact | P1 | | +| Send sticker | P2 | | +| Message reactions | P2 | | +| Reply to message | P1 | | +| Forward message | P1 | | +| Message history | P2 | | #### Groups, Channels & Contacts | Feature | Priority | Status | | ------------------- | -------- | ------ | -| Groups API (full) | P0 | โœ… | -| Channels/Newsletter | P1 | โœ… | -| Labels management | P2 | โœ… | -| Contact list API | P1 | โœ… | +| Groups API (full) | P0 | | +| Channels/Newsletter | P1 | | +| Labels management | P2 | | +| Contact list API | P1 | | #### Scaling & Infrastructure | Feature | Priority | Status | | ------------------ | -------- | ------ | -| Horizontal scaling | P2 | โœ… | -| Session affinity | P2 | โœ… | -| Security audit | P0 | โœ… | +| Horizontal scaling | P2 | | +| Session affinity | P2 | | +| Security audit | P0 | | #### Community & Tooling | Feature | Priority | Status | | --------------- | -------- | ------------------ | -| n8n integration | P1 | โœ… (separate repo) | -| CI/CD pipeline | P0 | โœ… | +| n8n integration | P1 | (separate repo) | +| CI/CD pipeline | P0 | | ### Deliverables @@ -462,7 +462,7 @@ flowchart LR V002[v0.0.2 - Production Ready
Multi-session & Dashboard] end - subgraph Current["โœ… Current Release"] + subgraph Current[" Current Release"] V010[v0.1.0 - Initial Stable Release
All Core Features] end @@ -574,34 +574,34 @@ flowchart TB | Metric | Target | Type | | -------------------------- | --------- | -------- | | Core API endpoints working | 100% | Internal | -| Docker deployment works | โœ… | Internal | +| Docker deployment works | | Internal | | Single session stable | 24+ hours | Internal | | Message delivery rate | > 95% | Internal | | API response time | < 500ms | Internal | -| CI/CD pipeline operational | โœ… | Internal | +| CI/CD pipeline operational | | Internal | ### Phase 2 Success Criteria | Metric | Target | Actual | Type | | --------------------- | ------------ | ----------------- | -------- | -| Multi-session support | 10+ sessions | โœ… Achieved | Internal | -| Dashboard functional | All features | โœ… Achieved | Internal | -| PostgreSQL stable | โœ… | โœ… Achieved | Internal | -| Webhook delivery rate | > 99% | โœ… Achieved | Internal | -| Test coverage | > 70% | โš ๏ธ ~5% (deferred) | Internal | -| GitHub stars | 100+ | ๐Ÿ“‹ Pending | External | +| Multi-session support | 10+ sessions | Achieved | Internal | +| Dashboard functional | All features | Achieved | Internal | +| PostgreSQL stable | | Achieved | Internal | +| Webhook delivery rate | > 99% | Achieved | Internal | +| Test coverage | > 70% | ~5% (deferred) | Internal | +| GitHub stars | 100+ | Pending | External | ### Phase 3 Success Criteria | Metric | Target | Actual | Type | | ----------------------------- | ------- | ----------------- | -------- | -| Feature parity with WAHA Plus | 90%+ | โœ… Achieved | Internal | -| API response time (p95) | < 200ms | โœ… Achieved | Internal | -| Test coverage | > 80% | โš ๏ธ ~5% (deferred) | Internal | -| Documentation coverage | 100% | โœ… 95%+ | Internal | -| Production users | 50+ | ๐Ÿ“‹ Pending | External | -| GitHub stars | 500+ | ๐Ÿ“‹ Pending | External | -| Community contributors | 5+ | ๐Ÿ“‹ Pending | External | +| Feature parity with WAHA Plus | 90%+ | Achieved | Internal | +| API response time (p95) | < 200ms | Achieved | Internal | +| Test coverage | > 80% | ~5% (deferred) | Internal | +| Documentation coverage | 100% | 95%+ | Internal | +| Production users | 50+ | Pending | External | +| GitHub stars | 500+ | Pending | External | +| Community contributors | 5+ | Pending | External | --- diff --git a/docs/16-risk-management.md b/docs/16-risk-management.md index 0d02a52..e646052 100644 --- a/docs/16-risk-management.md +++ b/docs/16-risk-management.md @@ -122,14 +122,14 @@ const DEFAULT_SAFEGUARDS = { ```markdown ## Anti-Ban Best Practices -### DO โœ… +### DO - Warm up new numbers (normal usage for 1-2 weeks) - Use realistic delays between messages - Personalize messages (avoid identical content) - Respond to incoming messages - Use residential proxies if needed -### DON'T โŒ +### DON'T - Send bulk messages to unknown numbers - Use identical message templates - Send >100 messages/day on new numbers @@ -675,7 +675,7 @@ flowchart LR ## Weekly Risk Report - Week XX ### Summary -- Overall Risk Status: ๐ŸŸข Green / ๐ŸŸก Yellow / ๐Ÿ”ด Red +- Overall Risk Status: Green / Yellow / Red - New Risks Identified: X - Risks Mitigated: X - Active Incidents: X @@ -684,9 +684,9 @@ flowchart LR | KRI | Target | Actual | Status | |-----|--------|--------|--------| -| Error Rate | < 1% | X.XX% | ๐ŸŸข/๐ŸŸก/๐Ÿ”ด | -| Uptime | > 99.5% | XX.XX% | ๐ŸŸข/๐ŸŸก/๐Ÿ”ด | -| Response Time | < 500ms | XXXms | ๐ŸŸข/๐ŸŸก/๐Ÿ”ด | +| Error Rate | < 1% | X.XX% | // | +| Uptime | > 99.5% | XX.XX% | // | +| Response Time | < 500ms | XXXms | // | ### Top Risks This Week @@ -712,14 +712,14 @@ flowchart LR | ID | Risk | Probability | Impact | Level | Status | |----|------|-------------|--------|-------|--------| -| R001 | Protocol Changes | High | High | ๐Ÿ”ด Critical | Monitoring | -| R002 | Account Ban | Medium | Medium | ๐ŸŸก Medium | Mitigated | -| R003 | Security Breach | Low | Critical | ๐ŸŸก Medium | Mitigated | -| R004 | Maintainer Burnout | Medium | Medium | ๐ŸŸก Medium | Planning | -| R005 | Dependency Issues | High | Medium | ๐ŸŸก Medium | Automated | -| R006 | Legal Issues | Low | Critical | ๐ŸŸก Medium | Mitigated | -| R007 | Rate Limiting | High | Medium | ๐ŸŸก Medium | Mitigated | -| R008 | Data Loss | Low | High | ๐ŸŸก Medium | Mitigated | +| R001 | Protocol Changes | High | High | Critical | Monitoring | +| R002 | Account Ban | Medium | Medium | Medium | Mitigated | +| R003 | Security Breach | Low | Critical | Medium | Mitigated | +| R004 | Maintainer Burnout | Medium | Medium | Medium | Planning | +| R005 | Dependency Issues | High | Medium | Medium | Automated | +| R006 | Legal Issues | Low | Critical | Medium | Mitigated | +| R007 | Rate Limiting | High | Medium | Medium | Mitigated | +| R008 | Data Loss | Low | High | Medium | Mitigated | ### Risk Trend diff --git a/docs/17-dashboard-design.md b/docs/17-dashboard-design.md index 4a242e9..48a7957 100644 --- a/docs/17-dashboard-design.md +++ b/docs/17-dashboard-design.md @@ -100,11 +100,11 @@ flowchart TB ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ ๐Ÿ”ต OpenWA ๐Ÿ” Search ๐Ÿ‘ค Admin โ˜€๏ธ โ”‚ +โ”‚ OpenWA Search Admin โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ๐Ÿ“ฑ 5 โ”‚ โœ… 4 โ”‚ ๐Ÿ“จ 1,234 โ”‚ ๐Ÿ”— 3 โ”‚ โ”‚ +โ”‚ โ”‚ 5 โ”‚ 4 โ”‚ 1,234 โ”‚ 3 โ”‚ โ”‚ โ”‚ โ”‚ Sessions โ”‚ Connected โ”‚ Messages โ”‚ Webhooks โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ (Today) โ”‚ Active โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ @@ -112,7 +112,7 @@ flowchart TB โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ Sessions Overview โ”‚ โ”‚ Recent Activity โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ ๐ŸŸข โ”‚ ๐ŸŸข โ”‚ ๐ŸŸข โ”‚ ๐ŸŸก โ”‚ โ”‚ โ”‚ 10:30 Message sent โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ 10:30 Message sent โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ CS-1 โ”‚ CS-2 โ”‚ Salesโ”‚ Supp โ”‚ โ”‚ โ”‚ 10:28 Webhook called โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ 123 โ”‚ 456 โ”‚ 789 โ”‚ - โ”‚ โ”‚ โ”‚ 10:25 Session online โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ 10:20 Message recv โ”‚ โ”‚ @@ -142,34 +142,34 @@ flowchart TB โ”‚ โ† Sessions [+ New Session] โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ -โ”‚ ๐Ÿ” Search sessions... Filter: [All โ–พ] [Status โ–พ] โ”‚ +โ”‚ Search sessions... Filter: [All ] [Status ] โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ โ— Customer Support 1 ๐ŸŸข Connected โ”‚ โ”‚ -โ”‚ โ”‚ ๐Ÿ“ฑ +62 812-3456-789 โ”‚ โ”‚ -โ”‚ โ”‚ ๐Ÿ“จ 1,234 messages | Last active: 2 min ago โ”‚ โ”‚ +โ”‚ โ”‚ Customer Support 1 Connected โ”‚ โ”‚ +โ”‚ โ”‚ +62 812-3456-789 โ”‚ โ”‚ +โ”‚ โ”‚ 1,234 messages | Last active: 2 min ago โ”‚ โ”‚ โ”‚ โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ โ”‚ -โ”‚ โ”‚ [๐Ÿ“ท QR] [๐Ÿ’ฌ Test Chat] [โš™๏ธ Settings] [๐Ÿ—‘๏ธ Delete] โ”‚ โ”‚ +โ”‚ โ”‚ [ QR] [ Test Chat] [ Settings] [ Delete] โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ โ— Sales Bot ๐ŸŸข Connected โ”‚ โ”‚ -โ”‚ โ”‚ ๐Ÿ“ฑ +62 821-9876-543 โ”‚ โ”‚ -โ”‚ โ”‚ ๐Ÿ“จ 567 messages | Last active: 5 min ago โ”‚ โ”‚ +โ”‚ โ”‚ Sales Bot Connected โ”‚ โ”‚ +โ”‚ โ”‚ +62 821-9876-543 โ”‚ โ”‚ +โ”‚ โ”‚ 567 messages | Last active: 5 min ago โ”‚ โ”‚ โ”‚ โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ โ”‚ -โ”‚ โ”‚ [๐Ÿ“ท QR] [๐Ÿ’ฌ Test Chat] [โš™๏ธ Settings] [๐Ÿ—‘๏ธ Delete] โ”‚ โ”‚ +โ”‚ โ”‚ [ QR] [ Test Chat] [ Settings] [ Delete] โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ โ—‹ Support Backup ๐ŸŸก Disconnected โ”‚ โ”‚ -โ”‚ โ”‚ ๐Ÿ“ฑ Not connected โ”‚ โ”‚ -โ”‚ โ”‚ ๐Ÿ“จ 0 messages | Never active โ”‚ โ”‚ +โ”‚ โ”‚ Support Backup Disconnected โ”‚ โ”‚ +โ”‚ โ”‚ Not connected โ”‚ โ”‚ +โ”‚ โ”‚ 0 messages | Never active โ”‚ โ”‚ โ”‚ โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ โ”‚ -โ”‚ โ”‚ [๐Ÿ“ท Scan QR] [โš™๏ธ Settings] [๐Ÿ—‘๏ธ Delete] โ”‚ โ”‚ +โ”‚ โ”‚ [ Scan QR] [ Settings] [ Delete] โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ -โ”‚ Showing 3 of 3 sessions [โ—€] 1 [โ–ถ] โ”‚ +โ”‚ Showing 3 of 3 sessions [] 1 [] โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` @@ -178,25 +178,25 @@ flowchart TB ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ โ† Sessions / Customer Support 1 ๐ŸŸข Connected โ”‚ +โ”‚ โ† Sessions / Customer Support 1 Connected โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ ๐Ÿ‘ค โ”‚ Customer Support 1 โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ Customer Support 1 โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ Avatar โ”‚ +62 812-3456-789 โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ Status: ๐ŸŸข Connected โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ Status: Connected โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ Platform: Android โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ [Restart Session] [Logout] [Delete] โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ๐Ÿ“Š Statistics โ”‚ โš™๏ธ Configuration โ”‚ โ”‚ +โ”‚ โ”‚ Statistics โ”‚ Configuration โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ Messages Sent โ”‚ Auto Reconnect โ”‚ โ”‚ -โ”‚ โ”‚ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘ 1,234 โ”‚ [โœ“] Enabled โ”‚ โ”‚ +โ”‚ โ”‚ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘ 1,234 โ”‚ [] Enabled โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ Messages Received โ”‚ Webhook URL โ”‚ โ”‚ โ”‚ โ”‚ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ 2,567 โ”‚ https://... โ”‚ โ”‚ @@ -212,13 +212,13 @@ flowchart TB โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ Recent Messages [View All โ†’] โ”‚ โ”‚ โ”‚ โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ โ”‚ -โ”‚ โ”‚ โ†’ +62 821... | Hello, how can I help? | 10:30 โœ“โœ“ โ”‚ โ”‚ +โ”‚ โ”‚ โ†’ +62 821... | Hello, how can I help? | 10:30 โ”‚ โ”‚ โ”‚ โ”‚ โ† +62 821... | I need product info | 10:28 โ”‚ โ”‚ -โ”‚ โ”‚ โ†’ +62 821... | Sure! Here's our catalog | 10:25 โœ“โœ“ โ”‚ โ”‚ +โ”‚ โ”‚ โ†’ +62 821... | Sure! Here's our catalog | 10:25 โ”‚ โ”‚ โ”‚ โ”‚ โ† +62 813... | Thanks for your help! | 10:20 โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ -โ”‚ [๐Ÿ’ฌ Open Test Chat] โ”‚ +โ”‚ [ Open Test Chat] โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` @@ -249,7 +249,7 @@ flowchart TB โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ โ”‚ โ”‚ โ”‚ 1. Open WhatsApp on your phone โ”‚ -โ”‚ 2. Tap Menu โ‹ฎ or Settings โš™ โ”‚ +โ”‚ 2. Tap Menu โ‹ฎ or Settings โ”‚ โ”‚ 3. Tap Linked Devices โ”‚ โ”‚ 4. Tap Link a Device โ”‚ โ”‚ 5. Point your phone at this screen โ”‚ @@ -272,26 +272,26 @@ flowchart TB โ”‚ โ”‚ Contacts โ”‚ โ”‚ +62 821-9876-543 โ”‚ โ”‚ โ”‚ โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ โ”‚ John Doe โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ -โ”‚ โ”‚ ๐Ÿ” Search... โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ Search... โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ Hello! How can I help you? โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ ๐Ÿ‘ค John Doe โ”‚ โ”‚ โ”‚ โ”‚ 10:30 โœ“โœ“ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ John Doe โ”‚ โ”‚ โ”‚ โ”‚ 10:30 โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ Last: Hi! โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ I need help with my โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ ๐Ÿ‘ค Jane Smith โ”‚ โ”‚ โ”‚ โ”‚ order #12345 โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ Jane Smith โ”‚ โ”‚ โ”‚ โ”‚ order #12345 โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ Last: OK โ”‚ โ”‚ โ”‚ โ”‚ 10:31 โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ ๐Ÿ‘ค Bob Wilson โ”‚ โ”‚ โ”‚ โ”‚ Sure! Let me check that โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ Bob Wilson โ”‚ โ”‚ โ”‚ โ”‚ Sure! Let me check that โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ Last: Thx โ”‚ โ”‚ โ”‚ โ”‚ for you. One moment... โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ 10:32 โœ“โœ“ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ 10:32 โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ -โ”‚ โ”‚ [+ New Chat] โ”‚ โ”‚ ๐Ÿ“Ž [ ] ๐Ÿ“ค โ”‚ โ”‚ +โ”‚ โ”‚ [+ New Chat] โ”‚ โ”‚ [ ] โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ Type a message... โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ @@ -306,7 +306,7 @@ flowchart TB โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ๐Ÿ”— Main Webhook โœ… Active โ”‚ โ”‚ +โ”‚ โ”‚ Main Webhook Active โ”‚ โ”‚ โ”‚ โ”‚ https://api.example.com/webhook/openwa โ”‚ โ”‚ โ”‚ โ”‚ Events: message.received, message.ack, session.status โ”‚ โ”‚ โ”‚ โ”‚ Sessions: All โ”‚ โ”‚ @@ -316,7 +316,7 @@ flowchart TB โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ๐Ÿ”— Analytics Webhook โœ… Active โ”‚ โ”‚ +โ”‚ โ”‚ Analytics Webhook Active โ”‚ โ”‚ โ”‚ โ”‚ https://analytics.example.com/track โ”‚ โ”‚ โ”‚ โ”‚ Events: message.received โ”‚ โ”‚ โ”‚ โ”‚ Sessions: cs-1, sales โ”‚ โ”‚ @@ -326,7 +326,7 @@ flowchart TB โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ๐Ÿ”— Backup Webhook โธ๏ธ Disabled โ”‚ โ”‚ +โ”‚ โ”‚ Backup Webhook Disabled โ”‚ โ”‚ โ”‚ โ”‚ https://backup.example.com/wa โ”‚ โ”‚ โ”‚ โ”‚ Events: message.received, message.ack โ”‚ โ”‚ โ”‚ โ”‚ Sessions: All โ”‚ โ”‚ diff --git a/docs/19-plugin-architecture.md b/docs/19-plugin-architecture.md index c61e1a5..6d4e5f4 100644 --- a/docs/19-plugin-architecture.md +++ b/docs/19-plugin-architecture.md @@ -2,27 +2,27 @@ ## Implementation Status -> **Current Status: โš ๏ธ Partially Implemented** +> **Current Status: Partially Implemented** > > The core plugin infrastructure is functional. Advanced features are planned for future releases. | Component | Status | Location | |-----------|--------|----------| -| **HookManager** | โœ… Implemented | `src/core/hooks/hook-manager.service.ts` | -| **PluginLoaderService** | โœ… Implemented | `src/core/plugins/plugin-loader.service.ts` | -| **PluginStorageService** | โœ… Implemented | `src/core/plugins/plugin-storage.service.ts` | -| **Manifest loading** | โœ… Implemented | Loads from `plugins/` directory | -| **Plugin lifecycle** | โœ… Implemented | load, enable, disable, unload | -| **Dashboard UI** | โœ… Implemented | `dashboard/src/pages/Plugins.tsx` | -| **REST API** | โœ… Implemented | `src/modules/plugins/plugins.controller.ts` | +| **HookManager** | Implemented | `src/core/hooks/hook-manager.service.ts` | +| **PluginLoaderService** | Implemented | `src/core/plugins/plugin-loader.service.ts` | +| **PluginStorageService** | Implemented | `src/core/plugins/plugin-storage.service.ts` | +| **Manifest loading** | Implemented | Loads from `plugins/` directory | +| **Plugin lifecycle** | Implemented | load, enable, disable, unload | +| **Dashboard UI** | Implemented | `dashboard/src/pages/Plugins.tsx` | +| **REST API** | Implemented | `src/modules/plugins/plugins.controller.ts` | | Component | Status | Notes | |-----------|--------|-------| -| **@openwa/plugin-sdk** | ๐Ÿ”œ Planned | NPM package not yet published | -| **Sandboxed execution** | ๐Ÿ”œ Planned | vm2 isolation not implemented | -| **Permission enforcement** | โš ๏ธ Partial | Defined in manifest, not enforced | -| **Built-in plugins** | ๐Ÿ”œ Planned | Auto-reply, Translation examples | -| **Plugin marketplace** | ๐Ÿ”œ Planned | Install from npm/github | +| **@openwa/plugin-sdk** | Planned | NPM package not yet published | +| **Sandboxed execution** | Planned | vm2 isolation not implemented | +| **Permission enforcement** | Partial | Defined in manifest, not enforced | +| **Built-in plugins** | Planned | Auto-reply, Translation examples | +| **Plugin marketplace** | Planned | Install from npm/github | --- diff --git a/docs/22-n8n-integration.md b/docs/22-n8n-integration.md index e8a0068..a08a04f 100644 --- a/docs/22-n8n-integration.md +++ b/docs/22-n8n-integration.md @@ -11,13 +11,13 @@ OpenWA provides official n8n community nodes for integrating WhatsApp automation ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ n8n Workflow โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ OpenWA Node โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ OpenWA API โ”‚ +โ”‚ n8n Workflow โ”‚โ”€โ”€โ”€โ”€โ”‚ OpenWA Node โ”‚โ”€โ”€โ”€โ”€โ”‚ OpenWA API โ”‚ โ”‚ โ”‚ โ”‚ (credentials) โ”‚ โ”‚ (your server) โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ - โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ n8n Workflow โ”‚โ—€โ”€โ”€โ”€โ”€โ”‚ OpenWA Trigger โ”‚โ—€โ”€โ”€โ”€โ”€โ”‚ Webhook POST โ”‚ +โ”‚ n8n Workflow โ”‚โ”€โ”€โ”€โ”€โ”‚ OpenWA Trigger โ”‚โ”€โ”€โ”€โ”€โ”‚ Webhook POST โ”‚ โ”‚ (triggered) โ”‚ โ”‚ (listens) โ”‚ โ”‚ from OpenWA โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` @@ -147,7 +147,7 @@ Get notified on Slack when WhatsApp session disconnects. **Slack Message:** ``` -โš ๏ธ WhatsApp session "{{$json.sessionId}}" disconnected! + WhatsApp session "{{$json.sessionId}}" disconnected! Time: {{$json.timestamp}} Please check and reconnect. ``` diff --git a/scripts/openwa.sh b/scripts/openwa.sh index eea0acf..668e050 100755 --- a/scripts/openwa.sh +++ b/scripts/openwa.sh @@ -16,10 +16,10 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" # Log functions -log_info() { echo -e "${BLUE}โ„น${NC} $1"; } -log_success() { echo -e "${GREEN}โœ“${NC} $1"; } -log_warn() { echo -e "${YELLOW}โš ${NC} $1"; } -log_error() { echo -e "${RED}โœ—${NC} $1"; } +log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } +log_success() { echo -e "${GREEN}[OK]${NC} $1"; } +log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +log_error() { echo -e "${RED}[ERR]${NC} $1"; } # Load environment variables load_env() { diff --git a/sdk/javascript/src/index.ts b/sdk/javascript/src/index.ts index ae56714..540a537 100644 --- a/sdk/javascript/src/index.ts +++ b/sdk/javascript/src/index.ts @@ -22,7 +22,7 @@ * @packageDocumentation */ -// โ”€โ”€ Client Configuration โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +// -- Client Configuration ------------------------------------------ export interface OpenWAClientConfig { /** Base URL of the OpenWA API (e.g., 'http://localhost:2785') */ @@ -35,7 +35,7 @@ export interface OpenWAClientConfig { timeout?: number; } -// โ”€โ”€ Response Types โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +// -- Response Types ------------------------------------------------ export interface MessageResponse { messageId: string; @@ -50,7 +50,7 @@ export interface Session { pushName: string | null; } -// โ”€โ”€ Client Class โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +// -- Client Class -------------------------------------------------- export class OpenWAClient { private readonly config: Required; @@ -62,7 +62,7 @@ export class OpenWAClient { }; } - // Placeholder โ€” will be auto-generated from OpenAPI spec + // Placeholder - will be auto-generated from OpenAPI spec get sessions() { return { list: () => this.request('GET', '/api/sessions'), @@ -81,7 +81,7 @@ export class OpenWAClient { }; } - // โ”€โ”€ Internal HTTP client โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // -- Internal HTTP client ------------------------------------------ private async request(method: string, path: string, body?: unknown): Promise { const url = `${this.config.baseUrl}${path}`; diff --git a/sdk/python/openwa/__init__.py b/sdk/python/openwa/__init__.py index 2164a1f..06a1995 100644 --- a/sdk/python/openwa/__init__.py +++ b/sdk/python/openwa/__init__.py @@ -45,7 +45,7 @@ class MessageResponse: class OpenWAClient: """OpenWA API client. - This is a scaffold โ€” methods will be auto-generated from the OpenAPI spec. + This is a scaffold - methods will be auto-generated from the OpenAPI spec. """ def __init__(self, base_url: str, api_key: str, timeout: float = 30.0) -> None: @@ -55,7 +55,7 @@ def __init__(self, base_url: str, api_key: str, timeout: float = 30.0) -> None: timeout=timeout, ) - # Placeholder โ€” will be auto-generated from OpenAPI spec + # Placeholder - will be auto-generated from OpenAPI spec @property def sessions(self) -> "_SessionsResource": return _SessionsResource(self) diff --git a/src/app.module.ts b/src/app.module.ts index ca4a704..ddfe72d 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,4 +1,4 @@ -import { Module, DynamicModule, Type } from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ThrottlerModule } from '@nestjs/throttler'; @@ -26,16 +26,7 @@ import { CatalogModule } from './modules/catalog/catalog.module'; import { HooksModule } from './core/hooks'; import { PluginsModule } from './core/plugins'; import { PluginsApiModule } from './modules/plugins/plugins.module'; - -// Only import QueueModule if explicitly enabled to avoid Redis connection errors -const queueModules: Array = []; -if (process.env.QUEUE_ENABLED === 'true') { - // eslint-disable-next-line @typescript-eslint/no-require-imports - const queueModule = require('./modules/queue/queue.module') as { - QueueModule: Type; - }; - queueModules.push(queueModule.QueueModule); -} +import { QueueModule } from './modules/queue/queue.module'; @Module({ imports: [ @@ -142,7 +133,7 @@ if (process.env.QUEUE_ENABLED === 'true') { StorageModule, AuditModule, EventsModule, // WebSocket real-time events - ...queueModules, + ...(process.env.QUEUE_ENABLED === 'true' ? [QueueModule] : []), AuthModule, EngineModule, SessionModule, diff --git a/src/database/migrations/1779235200000-AddUuidDefaultsForPostgres.ts b/src/database/migrations/1779235200000-AddUuidDefaultsForPostgres.ts index 7e2db6e..32047cd 100644 --- a/src/database/migrations/1779235200000-AddUuidDefaultsForPostgres.ts +++ b/src/database/migrations/1779235200000-AddUuidDefaultsForPostgres.ts @@ -14,7 +14,7 @@ import { MigrationInterface, QueryRunner } from 'typeorm'; * This migration is a no-op on SQLite (TypeORM generates the UUID in the * driver layer there, so no DB default is needed). * - * `gen_random_uuid()` is built into PostgreSQL 13+ โ€” no extension required. + * `gen_random_uuid()` is built into PostgreSQL 13+, no extension required. */ export class AddUuidDefaultsForPostgres1779235200000 implements MigrationInterface { name = 'AddUuidDefaultsForPostgres1779235200000'; diff --git a/src/engine/adapters/whatsapp-web-js.adapter.ts b/src/engine/adapters/whatsapp-web-js.adapter.ts index 727a3db..d7c2a7d 100644 --- a/src/engine/adapters/whatsapp-web-js.adapter.ts +++ b/src/engine/adapters/whatsapp-web-js.adapter.ts @@ -27,6 +27,7 @@ import { ProductQueryOptions, PaginatedProducts, } from '../interfaces/whatsapp-engine.interface'; +import { BadRequestException } from '@nestjs/common'; import { createLogger } from '../../common/services/logger.service'; import { GroupChat, @@ -859,24 +860,22 @@ export class WhatsAppWebJsAdapter extends EventEmitter implements IWhatsAppEngin async postTextStatus(_text: string, _options?: TextStatusOptions): Promise { this.ensureReady(); - // whatsapp-web.js doesn't have native status posting - // This would require using the underlying WhatsApp Web API directly - throw new Error('postTextStatus not yet implemented in whatsapp-web.js adapter'); + throw new BadRequestException('postTextStatus is not supported by the current engine adapter'); } async postImageStatus(_media: MediaInput, _caption?: string): Promise { this.ensureReady(); - throw new Error('postImageStatus not yet implemented in whatsapp-web.js adapter'); + throw new BadRequestException('postImageStatus is not supported by the current engine adapter'); } async postVideoStatus(_media: MediaInput, _caption?: string): Promise { this.ensureReady(); - throw new Error('postVideoStatus not yet implemented in whatsapp-web.js adapter'); + throw new BadRequestException('postVideoStatus is not supported by the current engine adapter'); } async deleteStatus(_statusId: string): Promise { this.ensureReady(); - throw new Error('deleteStatus not yet implemented in whatsapp-web.js adapter'); + throw new BadRequestException('deleteStatus is not supported by the current engine adapter'); } // ========== Catalog (Phase 3) ========== @@ -905,12 +904,12 @@ export class WhatsAppWebJsAdapter extends EventEmitter implements IWhatsAppEngin async sendProduct(_chatId: string, _productId: string, _body?: string): Promise { this.ensureReady(); - throw new Error('sendProduct not yet implemented in whatsapp-web.js adapter'); + throw new BadRequestException('sendProduct is not supported by the current engine adapter'); } async sendCatalog(_chatId: string, _body?: string): Promise { this.ensureReady(); - throw new Error('sendCatalog not yet implemented in whatsapp-web.js adapter'); + throw new BadRequestException('sendCatalog is not supported by the current engine adapter'); } /* eslint-enable @typescript-eslint/require-await, @typescript-eslint/no-unused-vars */ diff --git a/src/main.ts b/src/main.ts index 61966fb..c89e73c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,7 +9,7 @@ import * as fs from 'fs'; import * as path from 'path'; // Configuration loading order (later sources do NOT override earlier ones): -// 1. Process env (Docker, shell, systemd) โ€” highest priority +// 1. Process env (Docker, shell, systemd) - highest priority // 2. .env (project-level overrides committed/managed by the user) // 3. data/.env.generated (Dashboard-managed config; created on first run) // @@ -162,8 +162,8 @@ async function bootstrap() { const port = process.env.PORT || 2785; await app.listen(port); - console.log(`๐Ÿš€ OpenWA is running on: http://localhost:${port}`); - console.log(`๐Ÿ“š Swagger docs: http://localhost:${port}/api/docs`); + console.log(`OpenWA is running on: http://localhost:${port}`); + console.log(`Swagger docs: http://localhost:${port}/api/docs`); } void bootstrap(); diff --git a/src/modules/auth/auth-validate.controller.ts b/src/modules/auth/auth-validate.controller.ts index bc27983..13609be 100644 --- a/src/modules/auth/auth-validate.controller.ts +++ b/src/modules/auth/auth-validate.controller.ts @@ -1,6 +1,7 @@ import { Controller, Post, Headers, HttpCode, HttpStatus } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse, ApiHeader } from '@nestjs/swagger'; import { AuthService } from './auth.service'; +import { Public } from './decorators/auth.decorators'; import { createLogger } from '../../common/services/logger.service'; @ApiTags('auth') @@ -10,6 +11,7 @@ export class AuthValidateController { constructor(private readonly authService: AuthService) {} + @Public() @Post('validate') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Validate an API key' }) diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 4b4d79f..ce3e662 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -2,7 +2,7 @@ import { Injectable, NotFoundException, UnauthorizedException, OnModuleInit } fr import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { createHash, randomBytes } from 'crypto'; -import { existsSync, writeFileSync, readFileSync } from 'fs'; +import { writeFileSync } from 'fs'; import { join } from 'path'; import { ApiKey, ApiKeyRole } from './entities/api-key.entity'; import { CreateApiKeyDto, UpdateApiKeyDto } from './dto'; @@ -20,57 +20,41 @@ export class AuthService implements OnModuleInit { ) {} async onModuleInit(): Promise { - // Seed a default API key if none exist + const apiBaseUrl = process.env.BASE_URL || `http://localhost:${process.env.PORT || 2785}`; + const dashboardUrl = process.env.DASHBOARD_URL || `http://localhost:${process.env.DASHBOARD_PORT || 2886}`; + + this.logger.log(''); + this.logger.log('โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”'); + this.logger.log(''); + this.logger.log(' Welcome to OpenWA - WhatsApp API Gateway'); + this.logger.log(''); + this.logger.log(` Dashboard: ${dashboardUrl}`); + this.logger.log(` API Docs: ${apiBaseUrl}/api/docs`); + this.logger.log(''); + const count = await this.apiKeyRepository.count(); - let displayKey: string; - let isNewKey = false; if (count === 0) { - // Use predictable key in development, random key in production - displayKey = + // WARNING: Setting NODE_ENV=development in production uses a hardcoded key. + // Always use NODE_ENV=production to generate a random 64-char hex key. + const rawKey = process.env.NODE_ENV === 'production' ? `owa_k1_${randomBytes(32).toString('hex')}` : 'dev-admin-key'; - await this.seedApiKey(displayKey, 'Default Admin Key', ApiKeyRole.ADMIN); - isNewKey = true; + await this.seedApiKey(rawKey, 'Default Admin Key', ApiKeyRole.ADMIN); + + this.logger.log(' API Key (displayed once on first boot):'); + this.logger.log(` ${rawKey}`); + this.logger.log(' Save this key. It will not be shown again.'); - // Save raw key to file for startup script to read try { - writeFileSync(API_KEY_FILE, displayKey, 'utf-8'); + writeFileSync(API_KEY_FILE, rawKey, 'utf-8'); } catch (err) { this.logger.warn('Could not save API key file', { error: String(err) }); } } else { - // Read saved API key from file if exists - if (existsSync(API_KEY_FILE)) { - try { - displayKey = readFileSync(API_KEY_FILE, 'utf-8').trim(); - } catch (error) { - this.logger.warn(`Failed to read API key file: ${API_KEY_FILE}`, { error: String(error) }); - displayKey = '(check dashboard for keys)'; - } - } else { - displayKey = '(check dashboard for keys)'; - } + this.logger.log(' API Key: (check dashboard or .api-key file)'); } - // Always show the welcome banner on startup - const apiBaseUrl = process.env.BASE_URL || `http://localhost:${process.env.PORT || 2785}`; - const dashboardUrl = process.env.DASHBOARD_URL || `http://localhost:${process.env.DASHBOARD_PORT || 2886}`; - - this.logger.log(''); - this.logger.log('โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”'); - this.logger.log(''); - this.logger.log(' ๐ŸŸข Welcome to OpenWA - WhatsApp API Gateway'); - this.logger.log(''); - this.logger.log(` ๐Ÿ“Š Dashboard: ${dashboardUrl}`); - this.logger.log(` ๐Ÿ“š API Docs: ${apiBaseUrl}/api/docs`); - this.logger.log(''); - if (isNewKey) { - this.logger.log(' ๐Ÿ”‘ API Key (newly created):'); - } else { - this.logger.log(' ๐Ÿ”‘ API Key:'); - } - this.logger.log(` ${displayKey}`); this.logger.log(''); this.logger.log('โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”'); this.logger.log(''); diff --git a/src/modules/auth/entities/api-key.entity.ts b/src/modules/auth/entities/api-key.entity.ts index 22c70d5..178163b 100644 --- a/src/modules/auth/entities/api-key.entity.ts +++ b/src/modules/auth/entities/api-key.entity.ts @@ -18,7 +18,7 @@ export class ApiKey { @Column({ type: 'varchar', length: 64 }) keyHash: string; - @Column({ type: 'varchar', length: 8 }) + @Column({ type: 'varchar', length: 12 }) keyPrefix: string; @Column({ diff --git a/src/modules/message/message.service.spec.ts b/src/modules/message/message.service.spec.ts index ad8d57a..042e31a 100644 --- a/src/modules/message/message.service.spec.ts +++ b/src/modules/message/message.service.spec.ts @@ -67,7 +67,7 @@ describe('MessageService', () => { service = module.get(MessageService); }); - // โ”€โ”€ sendText โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // -- sendText ------------------------------------------------------ describe('sendText', () => { it('should send text message and return messageId + timestamp', async () => { @@ -136,7 +136,7 @@ describe('MessageService', () => { }); }); - // โ”€โ”€ sendImage โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // -- sendImage ----------------------------------------------------- describe('sendImage', () => { it('should send image via URL', async () => { @@ -167,7 +167,7 @@ describe('MessageService', () => { }); }); - // โ”€โ”€ sendVideo / sendAudio / sendDocument / sendSticker โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // -- sendVideo / sendAudio / sendDocument / sendSticker ------------ describe('sendVideo', () => { it('should call engine.sendVideoMessage', async () => { @@ -213,7 +213,7 @@ describe('MessageService', () => { }); }); - // โ”€โ”€ sendLocation โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // -- sendLocation -------------------------------------------------- describe('sendLocation', () => { it('should send location with lat/lng', async () => { @@ -232,7 +232,7 @@ describe('MessageService', () => { }); }); - // โ”€โ”€ sendContact โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // -- sendContact --------------------------------------------------- describe('sendContact', () => { it('should send contact with name and number', async () => { @@ -250,7 +250,7 @@ describe('MessageService', () => { }); }); - // โ”€โ”€ reply / forward โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // -- reply / forward ----------------------------------------------- describe('reply', () => { it('should call engine.replyToMessage with quotedMessageId', async () => { @@ -292,7 +292,7 @@ describe('MessageService', () => { }); }); - // โ”€โ”€ saveIncomingMessage โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // -- saveIncomingMessage ------------------------------------------- describe('saveIncomingMessage', () => { it('should save with INCOMING direction', async () => { @@ -312,7 +312,7 @@ describe('MessageService', () => { }); }); - // โ”€โ”€ buildMediaInput (via sendImage) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // -- buildMediaInput (via sendImage) ------------------------------- describe('buildMediaInput validation', () => { it('should throw when neither url nor base64 is provided', async () => { @@ -331,7 +331,7 @@ describe('MessageService', () => { }); }); - // โ”€โ”€ reactToMessage / deleteMessage โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // -- reactToMessage / deleteMessage -------------------------------- describe('reactToMessage', () => { it('should call engine.reactToMessage', async () => { diff --git a/src/modules/message/message.service.ts b/src/modules/message/message.service.ts index cd245bd..27929eb 100644 --- a/src/modules/message/message.service.ts +++ b/src/modules/message/message.service.ts @@ -240,7 +240,7 @@ export class MessageService { // Save message as pending BEFORE sending const message = await this.saveOutgoingMessage(sessionId, { chatId: dto.chatId, - body: `๐Ÿ“ ${dto.description || 'Location'}`, + body: `${dto.description || 'Location'}`, type: 'location', }); @@ -278,7 +278,7 @@ export class MessageService { // Save message as pending BEFORE sending const message = await this.saveOutgoingMessage(sessionId, { chatId: dto.chatId, - body: `๐Ÿ“‡ ${dto.contactName}`, + body: `${dto.contactName}`, type: 'contact', }); diff --git a/src/modules/webhook/webhook.module.ts b/src/modules/webhook/webhook.module.ts index 34bfc85..33b6da3 100644 --- a/src/modules/webhook/webhook.module.ts +++ b/src/modules/webhook/webhook.module.ts @@ -1,22 +1,16 @@ -import { Module, DynamicModule, Type } from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Webhook } from './entities/webhook.entity'; import { WebhookService } from './webhook.service'; import { WebhookController } from './webhook.controller'; import { WebhooksListController } from './webhooks-list.controller'; - -// Only import QueueModule if explicitly enabled to avoid Redis connection errors -const queueModules: Array = []; -if (process.env.QUEUE_ENABLED === 'true') { - // eslint-disable-next-line @typescript-eslint/no-require-imports - const queueModule = require('../queue/queue.module') as { - QueueModule: Type; - }; - queueModules.push(queueModule.QueueModule); -} +import { QueueModule } from '../queue/queue.module'; @Module({ - imports: [TypeOrmModule.forFeature([Webhook], 'data'), ...queueModules], + imports: [ + TypeOrmModule.forFeature([Webhook], 'data'), + ...(process.env.QUEUE_ENABLED === 'true' ? [QueueModule] : []), + ], controllers: [WebhookController, WebhooksListController], providers: [WebhookService], exports: [WebhookService], diff --git a/src/modules/webhook/webhook.service.spec.ts b/src/modules/webhook/webhook.service.spec.ts index 1cf8292..55fc5b5 100644 --- a/src/modules/webhook/webhook.service.spec.ts +++ b/src/modules/webhook/webhook.service.spec.ts @@ -79,7 +79,7 @@ describe('WebhookService', () => { service = module.get(WebhookService); }); - // โ”€โ”€ create โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // -- create -------------------------------------------------------- describe('create', () => { it('should create a webhook with default events', async () => { @@ -123,7 +123,7 @@ describe('WebhookService', () => { }); }); - // โ”€โ”€ findBySession / findAll / findOne โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // -- findBySession / findAll / findOne ------------------------------ describe('findBySession', () => { it('should return webhooks for a session', async () => { @@ -163,7 +163,7 @@ describe('WebhookService', () => { }); }); - // โ”€โ”€ update โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // -- update -------------------------------------------------------- describe('update', () => { it('should update only provided fields', async () => { @@ -178,7 +178,7 @@ describe('WebhookService', () => { }); }); - // โ”€โ”€ delete โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // -- delete -------------------------------------------------------- describe('delete', () => { it('should remove the webhook', async () => { @@ -192,7 +192,7 @@ describe('WebhookService', () => { }); }); - // โ”€โ”€ dispatch (direct mode โ€” queue disabled) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // -- dispatch (direct mode - queue disabled) ---------------------------------- describe('dispatch (direct mode)', () => { const mockFetch = jest.fn(); @@ -285,7 +285,7 @@ describe('WebhookService', () => { }); }); - // โ”€โ”€ generateSignature (via dispatch) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // -- generateSignature (via dispatch) ------------------------------ describe('generateSignature', () => { it('should produce valid HMAC-SHA256 signature', async () => { @@ -341,7 +341,7 @@ describe('WebhookService', () => { }); }); - // โ”€โ”€ dispatch (queue mode) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // -- dispatch (queue mode) ----------------------------------------- describe('dispatch (queue mode)', () => { it('should add job to queue when queue is enabled', async () => { diff --git a/src/modules/webhook/webhook.service.ts b/src/modules/webhook/webhook.service.ts index d778744..3d52a03 100644 --- a/src/modules/webhook/webhook.service.ts +++ b/src/modules/webhook/webhook.service.ts @@ -1,4 +1,4 @@ -import { Injectable, NotFoundException, Optional } from '@nestjs/common'; +import { Injectable, NotFoundException, Optional, BadRequestException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { ConfigService } from '@nestjs/config'; @@ -302,54 +302,53 @@ export class WebhookService { webhook: Webhook, payload: WebhookPayload, headers: Record, - attempt = 1, ): Promise { - const body = JSON.stringify(payload); + for (let attempt = 1; attempt <= webhook.retryCount; attempt++) { + const body = JSON.stringify(payload); + headers['X-OpenWA-Retry-Count'] = String(attempt - 1); - // Update retry count header - headers['X-OpenWA-Retry-Count'] = String(attempt - 1); + if (webhook.secret && !headers['X-OpenWA-Signature']) { + headers['X-OpenWA-Signature'] = this.generateSignature(body, webhook.secret); + } - // Add signature if secret is configured and not already present - if (webhook.secret && !headers['X-OpenWA-Signature']) { - headers['X-OpenWA-Signature'] = this.generateSignature(body, webhook.secret); - } + try { + const response = await fetch(webhook.url, { + method: 'POST', + headers, + body, + signal: AbortSignal.timeout(this.configService.get('webhook.timeout', 10000)), + }); - try { - const response = await fetch(webhook.url, { - method: 'POST', - headers, - body, - signal: AbortSignal.timeout(this.configService.get('webhook.timeout', 10000)), - }); + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } + await this.webhookRepository.update(webhook.id, { + lastTriggeredAt: new Date(), + }); - // Update last triggered timestamp - await this.webhookRepository.update(webhook.id, { - lastTriggeredAt: new Date(), - }); + this.logger.debug(`Webhook delivered to ${webhook.id}`, { + webhookId: webhook.id, + deliveryId: payload.deliveryId, + action: 'webhook_delivered', + }); - this.logger.debug(`Webhook delivered to ${webhook.id}`, { - webhookId: webhook.id, - deliveryId: payload.deliveryId, - action: 'webhook_delivered', - }); - } catch (error) { - this.logger.error(`Webhook delivery failed for ${webhook.id}`, String(error), { - webhookId: webhook.id, - attempt, - deliveryId: payload.deliveryId, - action: 'webhook_delivery_failed', - }); + return; + } catch (error) { + this.logger.error(`Webhook delivery failed for ${webhook.id}`, String(error), { + webhookId: webhook.id, + attempt, + deliveryId: payload.deliveryId, + action: 'webhook_delivery_failed', + }); - if (attempt < webhook.retryCount) { - const delay = this.configService.get('webhook.retryDelay', 5000); - await this.delay(delay * attempt); - return this.deliverWebhook(webhook, payload, headers, attempt + 1); + if (attempt < webhook.retryCount) { + const delay = this.configService.get('webhook.retryDelay', 5000); + await this.delay(delay * attempt); + } else { + throw error; + } } - throw error; } } diff --git a/tsconfig.json b/tsconfig.json index 6e33920..0eb0b1d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,8 +13,8 @@ "target": "ES2023", "sourceMap": true, "outDir": "./dist", - "baseUrl": "./", "incremental": true, + "ignoreDeprecations": "6.0", "skipLibCheck": true, "strictNullChecks": true, "forceConsistentCasingInFileNames": true,