@kangwifi-pro/waliwa 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +600 -0
- package/dist/advanced/features.d.ts +211 -0
- package/dist/advanced/features.d.ts.map +1 -0
- package/dist/advanced/features.js +671 -0
- package/dist/advanced/features.js.map +1 -0
- package/dist/auth/auth-state.d.ts +89 -0
- package/dist/auth/auth-state.d.ts.map +1 -0
- package/dist/auth/auth-state.js +330 -0
- package/dist/auth/auth-state.js.map +1 -0
- package/dist/auth/pairing-code.d.ts +78 -0
- package/dist/auth/pairing-code.d.ts.map +1 -0
- package/dist/auth/pairing-code.js +191 -0
- package/dist/auth/pairing-code.js.map +1 -0
- package/dist/auth/qr-code.d.ts +60 -0
- package/dist/auth/qr-code.d.ts.map +1 -0
- package/dist/auth/qr-code.js +151 -0
- package/dist/auth/qr-code.js.map +1 -0
- package/dist/calls/handler.d.ts +61 -0
- package/dist/calls/handler.d.ts.map +1 -0
- package/dist/calls/handler.js +117 -0
- package/dist/calls/handler.js.map +1 -0
- package/dist/core/binary.d.ts +74 -0
- package/dist/core/binary.d.ts.map +1 -0
- package/dist/core/binary.js +448 -0
- package/dist/core/binary.js.map +1 -0
- package/dist/core/crypto.d.ts +60 -0
- package/dist/core/crypto.d.ts.map +1 -0
- package/dist/core/crypto.js +170 -0
- package/dist/core/crypto.js.map +1 -0
- package/dist/core/noise.d.ts +106 -0
- package/dist/core/noise.d.ts.map +1 -0
- package/dist/core/noise.js +307 -0
- package/dist/core/noise.js.map +1 -0
- package/dist/events/emitter.d.ts +44 -0
- package/dist/events/emitter.d.ts.map +1 -0
- package/dist/events/emitter.js +79 -0
- package/dist/events/emitter.js.map +1 -0
- package/dist/features/index.d.ts +342 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +755 -0
- package/dist/features/index.js.map +1 -0
- package/dist/fixes/index.d.ts +333 -0
- package/dist/fixes/index.d.ts.map +1 -0
- package/dist/fixes/index.js +762 -0
- package/dist/fixes/index.js.map +1 -0
- package/dist/groups/management.d.ts +86 -0
- package/dist/groups/management.d.ts.map +1 -0
- package/dist/groups/management.js +443 -0
- package/dist/groups/management.js.map +1 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +236 -0
- package/dist/index.js.map +1 -0
- package/dist/messages/media.d.ts +93 -0
- package/dist/messages/media.d.ts.map +1 -0
- package/dist/messages/media.js +252 -0
- package/dist/messages/media.js.map +1 -0
- package/dist/messages/message-encoder.d.ts +70 -0
- package/dist/messages/message-encoder.d.ts.map +1 -0
- package/dist/messages/message-encoder.js +453 -0
- package/dist/messages/message-encoder.js.map +1 -0
- package/dist/messages/send.d.ts +101 -0
- package/dist/messages/send.d.ts.map +1 -0
- package/dist/messages/send.js +409 -0
- package/dist/messages/send.js.map +1 -0
- package/dist/recovery/index.d.ts +125 -0
- package/dist/recovery/index.d.ts.map +1 -0
- package/dist/recovery/index.js +584 -0
- package/dist/recovery/index.js.map +1 -0
- package/dist/skdm/index.d.ts +220 -0
- package/dist/skdm/index.d.ts.map +1 -0
- package/dist/skdm/index.js +600 -0
- package/dist/skdm/index.js.map +1 -0
- package/dist/socket/hybrid.d.ts +118 -0
- package/dist/socket/hybrid.d.ts.map +1 -0
- package/dist/socket/hybrid.js +352 -0
- package/dist/socket/hybrid.js.map +1 -0
- package/dist/socket/wa-socket.d.ts +300 -0
- package/dist/socket/wa-socket.d.ts.map +1 -0
- package/dist/socket/wa-socket.js +1094 -0
- package/dist/socket/wa-socket.js.map +1 -0
- package/dist/socket/ws-socket.d.ts +96 -0
- package/dist/socket/ws-socket.d.ts.map +1 -0
- package/dist/socket/ws-socket.js +302 -0
- package/dist/socket/ws-socket.js.map +1 -0
- package/dist/types/index.d.ts +444 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/jid.d.ts +46 -0
- package/dist/utils/jid.d.ts.map +1 -0
- package/dist/utils/jid.js +152 -0
- package/dist/utils/jid.js.map +1 -0
- package/dist/utils/logger.d.ts +45 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +79 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/retry.d.ts +43 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +175 -0
- package/dist/utils/retry.js.map +1 -0
- package/docs/API.md +745 -0
- package/docs/ARCHITECTURE.md +307 -0
- package/docs/BAILEYS_FIXES.md +360 -0
- package/docs/FEATURES.md +532 -0
- package/docs/PROTOCOL.md +489 -0
- package/docs/RECOVERY.md +409 -0
- package/docs/SKDM.md +233 -0
- package/examples/ai-bot/index.ts +479 -0
- package/examples/ai-bot/package.json +17 -0
- package/examples/echo-bot/index.ts +171 -0
- package/examples/echo-bot/package.json +18 -0
- package/examples/group-bot/index.ts +557 -0
- package/examples/group-bot/package.json +17 -0
- package/examples/rest-gateway/index.ts +499 -0
- package/examples/rest-gateway/package.json +19 -0
- package/package.json +75 -0
- package/src/advanced/features.ts +817 -0
- package/src/auth/auth-state.ts +342 -0
- package/src/auth/pairing-code.ts +246 -0
- package/src/auth/qr-code.ts +191 -0
- package/src/calls/handler.ts +153 -0
- package/src/core/binary.ts +464 -0
- package/src/core/crypto.ts +189 -0
- package/src/core/noise.ts +406 -0
- package/src/events/emitter.ts +88 -0
- package/src/features/index.ts +921 -0
- package/src/fixes/index.ts +882 -0
- package/src/groups/management.ts +497 -0
- package/src/index.ts +274 -0
- package/src/messages/media.ts +372 -0
- package/src/messages/message-encoder.ts +520 -0
- package/src/messages/send.ts +521 -0
- package/src/recovery/index.ts +704 -0
- package/src/skdm/index.ts +693 -0
- package/src/socket/hybrid.ts +414 -0
- package/src/socket/wa-socket.ts +1347 -0
- package/src/socket/ws-socket.ts +355 -0
- package/src/types/index.ts +457 -0
- package/src/utils/jid.ts +156 -0
- package/src/utils/logger.ts +84 -0
- package/src/utils/retry.ts +205 -0
- package/tsconfig.json +30 -0
package/docs/FEATURES.md
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
# New Features
|
|
2
|
+
|
|
3
|
+
Waliwa menambahkan fitur-fitur baru yang tidak ada di Baileys atau ZapoJS. Semua fitur terintegrasi dengan WASocket dan dapat diakses dengan mudah.
|
|
4
|
+
|
|
5
|
+
## Daftar Fitur
|
|
6
|
+
|
|
7
|
+
1. [Health Monitor](#1-health-monitor)
|
|
8
|
+
2. [Multi-account Manager](#2-multi-account-manager)
|
|
9
|
+
3. [Webhook System](#3-webhook-system)
|
|
10
|
+
4. [Circuit Breaker](#4-circuit-breaker)
|
|
11
|
+
5. [Rate Limit Tracker](#5-rate-limit-tracker)
|
|
12
|
+
6. [Message Persistence](#6-message-persistence)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. Health Monitor
|
|
17
|
+
|
|
18
|
+
Real-time health tracking dengan auto-recovery. Memberikan score 0-100 berdasarkan multiple metrics.
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
const sock = makeWASocket({ authState: state });
|
|
22
|
+
|
|
23
|
+
// Get current health
|
|
24
|
+
const health = sock.getHealth();
|
|
25
|
+
console.log(health);
|
|
26
|
+
// {
|
|
27
|
+
// status: 'healthy', // 'healthy' | 'degraded' | 'unhealthy'
|
|
28
|
+
// score: 95, // 0-100
|
|
29
|
+
// connection: {
|
|
30
|
+
// connected: true,
|
|
31
|
+
// uptime: 3600000, // ms
|
|
32
|
+
// lastPingTime: 1234567890,
|
|
33
|
+
// reconnectCount: 2
|
|
34
|
+
// },
|
|
35
|
+
// metrics: {
|
|
36
|
+
// messagesSent: 145,
|
|
37
|
+
// messagesReceived: 89,
|
|
38
|
+
// sendFailures: 3,
|
|
39
|
+
// avgLatencyMs: 245
|
|
40
|
+
// },
|
|
41
|
+
// resources: {
|
|
42
|
+
// memoryUsageMb: 75.5,
|
|
43
|
+
// pendingSends: 0,
|
|
44
|
+
// queueSize: 0
|
|
45
|
+
// },
|
|
46
|
+
// issues: [], // List of issues jika ada
|
|
47
|
+
// timestamp: 1234567890
|
|
48
|
+
// }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Health Events
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { HealthMonitor } from 'waliwa';
|
|
55
|
+
|
|
56
|
+
sock.healthMonitor.on('health:check', (status) => {
|
|
57
|
+
console.log(`Health: ${status.status} (${status.score}/100)`);
|
|
58
|
+
if (status.issues.length > 0) {
|
|
59
|
+
console.warn('Issues:', status.issues);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
sock.healthMonitor.on('health:recovery', (status) => {
|
|
64
|
+
console.warn('Health degraded, triggering recovery');
|
|
65
|
+
// Custom recovery: reconnect, restart services, dll
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Custom Recovery Actions
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
sock.healthMonitor.on('health:recovery', async (status) => {
|
|
73
|
+
console.warn(`Health score: ${status.score}, issues: ${status.issues}`);
|
|
74
|
+
|
|
75
|
+
// Different recovery strategies based on issues
|
|
76
|
+
if (status.issues.includes('Connection appears down')) {
|
|
77
|
+
await sock.end();
|
|
78
|
+
await sock.connect();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (status.issues.includes('High memory usage')) {
|
|
82
|
+
// Force garbage collection
|
|
83
|
+
if (global.gc) global.gc();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (status.issues.includes('High send failure rate')) {
|
|
87
|
+
// Investigate failed messages
|
|
88
|
+
const failed = sock.messagePersistence.getFailed();
|
|
89
|
+
console.log(`${failed.length} failed messages`);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 2. Multi-account Manager
|
|
97
|
+
|
|
98
|
+
Manage multiple WhatsApp accounts dari satu process. Berguna untuk customer service bots, multiple business accounts, dll.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { MultiAccountManager } from 'waliwa';
|
|
102
|
+
|
|
103
|
+
const manager = new MultiAccountManager({
|
|
104
|
+
maxAccounts: 10,
|
|
105
|
+
logger: defaultLogger
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Register accounts
|
|
109
|
+
manager.registerAccount({
|
|
110
|
+
id: 'sales',
|
|
111
|
+
name: 'Sales Bot',
|
|
112
|
+
authFolder: './auth-sales',
|
|
113
|
+
enabled: true,
|
|
114
|
+
config: {
|
|
115
|
+
printQRInTerminal: false,
|
|
116
|
+
ramOptimizationLevel: 3 // Ultra untuk multi-account
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
manager.registerAccount({
|
|
121
|
+
id: 'support',
|
|
122
|
+
name: 'Support Bot',
|
|
123
|
+
authFolder: './auth-support',
|
|
124
|
+
enabled: true,
|
|
125
|
+
config: {
|
|
126
|
+
printQRInTerminal: false
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Connect accounts
|
|
131
|
+
await manager.connectAccount('sales');
|
|
132
|
+
await manager.connectAccount('support');
|
|
133
|
+
|
|
134
|
+
// Send message from specific account
|
|
135
|
+
await manager.sendMessageFrom('sales', '6281@s.whatsapp.net', {
|
|
136
|
+
text: 'Hello from Sales!'
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await manager.sendMessageFrom('support', '6282@s.whatsapp.net', {
|
|
140
|
+
text: 'Hello from Support!'
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Broadcast to all accounts
|
|
144
|
+
await manager.broadcast('6281@s.whatsapp.net', {
|
|
145
|
+
text: 'Broadcast from all accounts'
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// List all accounts status
|
|
149
|
+
console.log(manager.listAccounts());
|
|
150
|
+
// [
|
|
151
|
+
// { id: 'sales', name: 'Sales Bot', connected: true, messagesSent: 5, ... },
|
|
152
|
+
// { id: 'support', name: 'Support Bot', connected: true, messagesSent: 3, ... }
|
|
153
|
+
// ]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Per-account Events
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
manager.on('account:connected', (accountId) => {
|
|
160
|
+
console.log(`Account ${accountId} connected`);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
manager.on('account:disconnected', (accountId) => {
|
|
164
|
+
console.log(`Account ${accountId} disconnected`);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
manager.on('account:message', (accountId, messages) => {
|
|
168
|
+
console.log(`Account ${accountId} received ${messages.length} messages`);
|
|
169
|
+
// Route to different handlers based on accountId
|
|
170
|
+
if (accountId === 'sales') {
|
|
171
|
+
handleSalesMessage(messages);
|
|
172
|
+
} else if (accountId === 'support') {
|
|
173
|
+
handleSupportMessage(messages);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 3. Webhook System
|
|
181
|
+
|
|
182
|
+
Reliable webhook delivery dengan HMAC-SHA256 signature + retry + dead letter queue.
|
|
183
|
+
|
|
184
|
+
### Register Webhook
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
sock.registerWebhook({
|
|
188
|
+
url: 'https://yourapp.com/webhook',
|
|
189
|
+
events: ['message.sent', 'message.failed', 'auth.success'],
|
|
190
|
+
secret: 'your-webhook-secret', // HMAC signing
|
|
191
|
+
enabled: true,
|
|
192
|
+
retryCount: 5,
|
|
193
|
+
retryDelayMs: 1000,
|
|
194
|
+
timeoutMs: 10000
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Register untuk all events
|
|
198
|
+
sock.registerWebhook({
|
|
199
|
+
url: 'https://yourapp.com/all-events',
|
|
200
|
+
events: ['*'],
|
|
201
|
+
enabled: true
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Verify Webhook Signature (di receiver)
|
|
206
|
+
|
|
207
|
+
```javascript
|
|
208
|
+
// Express middleware untuk verify HMAC signature
|
|
209
|
+
const crypto = require('crypto');
|
|
210
|
+
|
|
211
|
+
app.post('/webhook', (req, res) => {
|
|
212
|
+
const signature = req.headers['x-waliwa-signature'];
|
|
213
|
+
const event = req.headers['x-waliwa-event'];
|
|
214
|
+
|
|
215
|
+
// Verify signature
|
|
216
|
+
const expected = 'sha256=' + crypto
|
|
217
|
+
.createHmac('sha256', 'your-webhook-secret')
|
|
218
|
+
.update(JSON.stringify(req.body))
|
|
219
|
+
.digest('hex');
|
|
220
|
+
|
|
221
|
+
if (signature !== expected) {
|
|
222
|
+
return res.status(401).send('Invalid signature');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Process event
|
|
226
|
+
console.log(`Received event: ${event}`);
|
|
227
|
+
console.log('Payload:', req.body);
|
|
228
|
+
|
|
229
|
+
res.json({ received: true });
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Webhook Stats & Dead Letter Queue
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
const stats = sock.webhookManager.getStats();
|
|
237
|
+
console.log(stats);
|
|
238
|
+
// {
|
|
239
|
+
// sent: 145,
|
|
240
|
+
// failed: 3,
|
|
241
|
+
// retried: 8,
|
|
242
|
+
// pending: 0,
|
|
243
|
+
// deadLettered: 2
|
|
244
|
+
// }
|
|
245
|
+
|
|
246
|
+
// Get dead letter queue
|
|
247
|
+
const dlq = sock.webhookManager.getDeadLetterQueue();
|
|
248
|
+
console.log(`${dlq.length} permanently failed webhooks`);
|
|
249
|
+
|
|
250
|
+
// Retry dari DLQ
|
|
251
|
+
sock.webhookManager.retryDeadLetter(); // Retry all
|
|
252
|
+
// atau specific:
|
|
253
|
+
sock.webhookManager.retryDeadLetter('wh-1234567890-abc');
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## 4. Circuit Breaker
|
|
259
|
+
|
|
260
|
+
Prevent retry storms untuk failed recipients. Saat recipient terus gagal (e.g., user block bot, invalid number), circuit breaker akan "open" dan stop sending sementara.
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
// Otomatis aktif di WASocket.sendMessage()
|
|
264
|
+
// Check sebelum send:
|
|
265
|
+
if (!sock.circuitBreaker.canSend(jid)) {
|
|
266
|
+
console.log(`Circuit open for ${jid}, skipping send`);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Stats
|
|
271
|
+
console.log(sock.circuitBreaker.getOpenCircuits());
|
|
272
|
+
// [
|
|
273
|
+
// { jid: '6281@s.whatsapp.net', failureCount: 5, lastFailureTime: 1234567890 },
|
|
274
|
+
// { jid: '6282@s.whatsapp.net', failureCount: 3, lastFailureTime: 1234567891 }
|
|
275
|
+
// ]
|
|
276
|
+
|
|
277
|
+
// Manually reset circuit
|
|
278
|
+
sock.resetCircuitBreaker('6281@s.whatsapp.net');
|
|
279
|
+
|
|
280
|
+
// Get state untuk specific recipient
|
|
281
|
+
const state = sock.circuitBreaker.getState('6281@s.whatsapp.net');
|
|
282
|
+
// 'closed' (normal) | 'open' (blocked) | 'half-open' (testing)
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### States
|
|
286
|
+
|
|
287
|
+
| State | Behavior |
|
|
288
|
+
|-------|----------|
|
|
289
|
+
| `closed` | Normal operation, sends allowed |
|
|
290
|
+
| `open` | Blocked, no sends (after 5 failures) |
|
|
291
|
+
| `half-open` | Allow 1 test send (after cooldown 60s) |
|
|
292
|
+
|
|
293
|
+
### Configuration
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
import { CircuitBreaker } from 'waliwa';
|
|
297
|
+
|
|
298
|
+
// Custom instance
|
|
299
|
+
const breaker = new CircuitBreaker({
|
|
300
|
+
failureThreshold: 5, // Open after 5 failures
|
|
301
|
+
cooldownMs: 60000 // 1 minute cooldown
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## 5. Rate Limit Tracker
|
|
308
|
+
|
|
309
|
+
Per-recipient rate limiting untuk comply dengan WhatsApp server limits.
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
// Default limits (WhatsApp server):
|
|
313
|
+
// - 5 messages/sec per recipient
|
|
314
|
+
// - 60 messages/min per recipient
|
|
315
|
+
// - 1000 messages/hour per recipient
|
|
316
|
+
|
|
317
|
+
// Otomatis di WASocket.sendMessage()
|
|
318
|
+
// Throw error jika exceeded:
|
|
319
|
+
try {
|
|
320
|
+
await sock.sendMessage(jid, { text: 'Hi' });
|
|
321
|
+
} catch (err) {
|
|
322
|
+
if (err.message.includes('Rate limit exceeded')) {
|
|
323
|
+
console.log('Rate limited, waiting...');
|
|
324
|
+
await sleep(60000); // Wait 1 min
|
|
325
|
+
await sock.sendMessage(jid, { text: 'Hi' });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Check remaining quota
|
|
330
|
+
const quota = sock.rateLimitTracker.getRemainingQuota('6281@s.whatsapp.net');
|
|
331
|
+
console.log(quota);
|
|
332
|
+
// { second: 4, minute: 45, hour: 950 }
|
|
333
|
+
|
|
334
|
+
// Manual reset (e.g., after user interaction)
|
|
335
|
+
sock.rateLimitTracker.reset('6281@s.whatsapp.net');
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Custom Limits
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import { RateLimitTracker } from 'waliwa';
|
|
342
|
+
|
|
343
|
+
const tracker = new RateLimitTracker({
|
|
344
|
+
perSecond: 3, // Custom: 3/sec
|
|
345
|
+
perMinute: 30, // Custom: 30/min
|
|
346
|
+
perHour: 500 // Custom: 500/hour
|
|
347
|
+
});
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## 6. Message Persistence
|
|
353
|
+
|
|
354
|
+
Persist outgoing messages untuk survive restarts. Pending messages akan di-retry setelah bot restart.
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
// Otomatis aktif di WASocket.sendMessage()
|
|
358
|
+
// Setiap message disimpan dengan status 'pending'
|
|
359
|
+
// Setelah send berhasil: status='sent'
|
|
360
|
+
// Setelah send gagal: status='failed'
|
|
361
|
+
|
|
362
|
+
// Get pending messages (untuk manual retry)
|
|
363
|
+
const pending = sock.messagePersistence.getPending();
|
|
364
|
+
console.log(`${pending.length} pending messages`);
|
|
365
|
+
|
|
366
|
+
// Get failed messages
|
|
367
|
+
const failed = sock.messagePersistence.getFailed();
|
|
368
|
+
console.log(`${failed.length} failed messages`);
|
|
369
|
+
|
|
370
|
+
// Retry all failed messages
|
|
371
|
+
await sock.retryFailedMessages();
|
|
372
|
+
|
|
373
|
+
// Manual update
|
|
374
|
+
sock.messagePersistence.update('msg-123', {
|
|
375
|
+
status: 'sent',
|
|
376
|
+
attempts: 2
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// Delete old message
|
|
380
|
+
sock.messagePersistence.delete('msg-123');
|
|
381
|
+
|
|
382
|
+
// Cleanup old messages (older than 24 hours)
|
|
383
|
+
const cleaned = sock.messagePersistence.cleanup(24 * 60 * 60 * 1000);
|
|
384
|
+
console.log(`Cleaned ${cleaned} old messages`);
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Auto-recovery on Restart
|
|
388
|
+
|
|
389
|
+
Saat bot restart, pending messages otomatis di-retry:
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
const sock = makeWASocket({ authState: state });
|
|
393
|
+
|
|
394
|
+
sock.ev.on('connection.update', async ({ connection }) => {
|
|
395
|
+
if (connection === 'open') {
|
|
396
|
+
// Otomatis: pending messages dari session sebelumnya di-retry
|
|
397
|
+
console.log('Bot reconnected, retrying pending messages...');
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## Integration Examples
|
|
405
|
+
|
|
406
|
+
### Complete Production Setup
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
import { makeWASocket, useFileAuthState } from 'waliwa';
|
|
410
|
+
|
|
411
|
+
async function main() {
|
|
412
|
+
const state = await useFileAuthState('./auth');
|
|
413
|
+
|
|
414
|
+
const sock = makeWASocket({
|
|
415
|
+
authState: state,
|
|
416
|
+
printQRInTerminal: true,
|
|
417
|
+
ramOptimizationLevel: 2,
|
|
418
|
+
|
|
419
|
+
// SKDM config
|
|
420
|
+
minPreKeys: 5,
|
|
421
|
+
targetPreKeyCount: 100,
|
|
422
|
+
maxSessionCacheSize: 500
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// Register webhook untuk external system
|
|
426
|
+
sock.registerWebhook({
|
|
427
|
+
url: 'https://api.yourapp.com/whatsapp/events',
|
|
428
|
+
events: ['message.sent', 'message.failed', 'auth.success', 'auth.failure'],
|
|
429
|
+
secret: process.env.WEBHOOK_SECRET,
|
|
430
|
+
enabled: true,
|
|
431
|
+
retryCount: 5,
|
|
432
|
+
retryDelayMs: 1000,
|
|
433
|
+
timeoutMs: 10000
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// Health monitoring
|
|
437
|
+
sock.healthMonitor.on('health:check', (status) => {
|
|
438
|
+
if (status.status === 'degraded' || status.status === 'unhealthy') {
|
|
439
|
+
console.warn(`Health degraded: ${status.score}/100`, status.issues);
|
|
440
|
+
// Send alert to admin
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
sock.healthMonitor.on('health:recovery', async (status) => {
|
|
445
|
+
console.warn('Triggering recovery actions');
|
|
446
|
+
// Custom recovery: restart, notify admin, dll
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// Message handling
|
|
450
|
+
sock.ev.on('message.upsert', async ({ messages, type }) => {
|
|
451
|
+
if (type !== 'notify') return;
|
|
452
|
+
|
|
453
|
+
for (const msg of messages) {
|
|
454
|
+
if (msg.key.fromMe) continue;
|
|
455
|
+
|
|
456
|
+
try {
|
|
457
|
+
// sendMessage sudah otomatis:
|
|
458
|
+
// - Check circuit breaker
|
|
459
|
+
// - Check rate limit
|
|
460
|
+
// - Persist message
|
|
461
|
+
// - Queue per recipient
|
|
462
|
+
// - Trigger webhook
|
|
463
|
+
await sock.sendMessage(msg.key.remoteJid, {
|
|
464
|
+
text: 'Hello!',
|
|
465
|
+
quoted: msg
|
|
466
|
+
});
|
|
467
|
+
} catch (err) {
|
|
468
|
+
if (err.message.includes('Circuit breaker')) {
|
|
469
|
+
console.log(`Skipping send to ${msg.key.remoteJid} (circuit open)`);
|
|
470
|
+
} else if (err.message.includes('Rate limit')) {
|
|
471
|
+
console.log(`Rate limited for ${msg.key.remoteJid}`);
|
|
472
|
+
} else {
|
|
473
|
+
console.error('Send failed:', err.message);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
// Periodic stats logging
|
|
480
|
+
setInterval(() => {
|
|
481
|
+
const stats = sock.getSystemStats();
|
|
482
|
+
console.log('System stats:', JSON.stringify(stats, null, 2));
|
|
483
|
+
}, 60000);
|
|
484
|
+
|
|
485
|
+
// Graceful shutdown
|
|
486
|
+
process.on('SIGINT', async () => {
|
|
487
|
+
console.log('Shutting down...');
|
|
488
|
+
await sock.end();
|
|
489
|
+
process.exit(0);
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
main();
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Monitoring Dashboard
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
// Endpoint untuk monitoring (e.g., di REST gateway)
|
|
500
|
+
app.get('/health', (req, res) => {
|
|
501
|
+
res.json(sock.getHealth());
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
app.get('/stats', (req, res) => {
|
|
505
|
+
res.json({
|
|
506
|
+
system: sock.getSystemStats(),
|
|
507
|
+
skdm: sock.getSKDMStats(),
|
|
508
|
+
health: sock.getHealth()
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
app.get('/stats/circuits', (req, res) => {
|
|
513
|
+
res.json({
|
|
514
|
+
open: sock.circuitBreaker.getOpenCircuits(),
|
|
515
|
+
webhooks: sock.webhookManager.getStats(),
|
|
516
|
+
messages: {
|
|
517
|
+
pending: sock.messagePersistence.getPending().length,
|
|
518
|
+
failed: sock.messagePersistence.getFailed().length
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
app.post('/circuits/:jid/reset', (req, res) => {
|
|
524
|
+
sock.resetCircuitBreaker(req.params.jid);
|
|
525
|
+
res.json({ success: true });
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
app.post('/messages/retry-failed', async (req, res) => {
|
|
529
|
+
await sock.retryFailedMessages();
|
|
530
|
+
res.json({ success: true });
|
|
531
|
+
});
|
|
532
|
+
```
|