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,