@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/RECOVERY.md
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
# Multi-Branch SKDM Recovery System
|
|
2
|
+
|
|
3
|
+
Unlike basic reconnect yang hanya mencoba satu jalur, Waliwa mengimplementasikan sistem pemulihan Multi-Branch Session Key & Device Management (SKDM) yang lengkap. Saat pemutusan koneksi (disconnect) terjadi, sistem akan mencoba berbagai strategi pemulihan secara berurutan.
|
|
4
|
+
|
|
5
|
+
## Tabel Cabang Pemulihan
|
|
6
|
+
|
|
7
|
+
| Disconnect Reason | Recovery Branches (in order) | Fatal? |
|
|
8
|
+
|-------------------|------------------------------|--------|
|
|
9
|
+
| **Connection lost** (1006/1001) | Immediate retry → Stream reconnect → Backoff → Key refresh → Pre-key fetch → Full re-auth | ❌ |
|
|
10
|
+
| **Rate limited** (429) | Wait cooldown (5min) → Backoff retry | ❌ |
|
|
11
|
+
| **Restart required** (428) | Update app version → Key refresh → Full re-auth QR | ❌ |
|
|
12
|
+
| **Logged out** (401/403) | Key refresh → No recovery | ✅ Fatal |
|
|
13
|
+
| **Forbidden** (511) | Immediate retry → Key refresh → No recovery | ❌ |
|
|
14
|
+
| **Multi-device mismatch** (409) | Key refresh → Full re-auth QR → No recovery | ❌ |
|
|
15
|
+
| **Bad session** | Key refresh → Pre-key fetch → Full re-auth QR | ❌ |
|
|
16
|
+
| **Connection replaced** (440) | Key refresh → Full re-auth QR | ❌ |
|
|
17
|
+
| **Unknown** | Backoff retry → Key refresh → Full re-auth QR | ❌ |
|
|
18
|
+
|
|
19
|
+
Setiap cabang memiliki batas percobaan (retry limit) tersendiri (default 3). Total percobaan di semua cabang dibatasi (default 20).
|
|
20
|
+
|
|
21
|
+
## Konfigurasi
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { makeWASocket, useFileAuthState } from 'waliwa';
|
|
25
|
+
|
|
26
|
+
const { state } = await useFileAuthState('./auth');
|
|
27
|
+
|
|
28
|
+
const sock = makeWASocket({
|
|
29
|
+
authState: state,
|
|
30
|
+
printQRInTerminal: true,
|
|
31
|
+
|
|
32
|
+
// Multi-Branch SKDM Recovery configuration
|
|
33
|
+
recovery: {
|
|
34
|
+
enabled: true, // Enable recovery system (default: true)
|
|
35
|
+
maxAttemptsPerBranch: 3, // Max attempts per branch (default: 3)
|
|
36
|
+
maxTotalAttempts: 20, // Total max attempts across all branches (default: 20)
|
|
37
|
+
initialDelayMs: 1000, // Initial delay for backoff (default: 1000ms)
|
|
38
|
+
maxDelayMs: 60000, // Max delay for backoff (default: 60000ms)
|
|
39
|
+
rateLimitedDelayMs: 300000 // Cooldown for rate limited (default: 5min)
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Pemulihan Per Disconnect Reason
|
|
45
|
+
|
|
46
|
+
### 1. Connection Lost (codes 1006, 1001, 0, undefined)
|
|
47
|
+
|
|
48
|
+
Branch paling lengkap - 6 strategi pemulihan berurutan.
|
|
49
|
+
|
|
50
|
+
**Flow:**
|
|
51
|
+
```
|
|
52
|
+
Connection lost detected
|
|
53
|
+
↓
|
|
54
|
+
1. Immediate retry (0ms delay)
|
|
55
|
+
↓ fail
|
|
56
|
+
2. Stream reconnect (500ms delay) - resume stream state
|
|
57
|
+
↓ fail
|
|
58
|
+
3. Backoff retry (1s, 2s, 4s exponential)
|
|
59
|
+
↓ fail x 3
|
|
60
|
+
4. Key refresh (SKDM regenerate pre-keys)
|
|
61
|
+
↓ fail
|
|
62
|
+
5. Pre-key fetch (force new pre-keys from server)
|
|
63
|
+
↓ fail
|
|
64
|
+
6. Full re-auth QR (clear creds, new QR scan)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 2. Rate Limited (code 429)
|
|
68
|
+
|
|
69
|
+
Wait untuk cooldown lalu coba lagi.
|
|
70
|
+
|
|
71
|
+
**Flow:**
|
|
72
|
+
```
|
|
73
|
+
Rate limited detected (429)
|
|
74
|
+
↓
|
|
75
|
+
1. Wait cooldown 5 minutes
|
|
76
|
+
↓
|
|
77
|
+
2. Backoff retry
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 3. Restart Required (code 428)
|
|
81
|
+
|
|
82
|
+
Server butuh update version. Update browser identification.
|
|
83
|
+
|
|
84
|
+
**Flow:**
|
|
85
|
+
```
|
|
86
|
+
Restart required (428)
|
|
87
|
+
↓
|
|
88
|
+
1. Update app version (bump browser version)
|
|
89
|
+
↓
|
|
90
|
+
2. Key refresh
|
|
91
|
+
↓
|
|
92
|
+
3. Full re-auth QR
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 4. Logged Out (codes 401, 403) - FATAL
|
|
96
|
+
|
|
97
|
+
Tidak ada pemulihan yang possible. Clear creds dan emit fatal event.
|
|
98
|
+
|
|
99
|
+
**Flow:**
|
|
100
|
+
```
|
|
101
|
+
Logged out (401/403)
|
|
102
|
+
↓
|
|
103
|
+
1. Key refresh (last attempt)
|
|
104
|
+
↓
|
|
105
|
+
❌ No recovery - emit 'recovery:failed' dengan fatal=true
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 5. Forbidden (code 511)
|
|
109
|
+
|
|
110
|
+
Coba sekali lagi, lalu refresh keys.
|
|
111
|
+
|
|
112
|
+
**Flow:**
|
|
113
|
+
```
|
|
114
|
+
Forbidden (511)
|
|
115
|
+
↓
|
|
116
|
+
1. Immediate retry
|
|
117
|
+
↓ fail
|
|
118
|
+
2. Key refresh
|
|
119
|
+
↓ fail
|
|
120
|
+
❌ No recovery
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 6. Multi-device Mismatch (code 409)
|
|
124
|
+
|
|
125
|
+
Multi-device state tidak sinkron dengan server.
|
|
126
|
+
|
|
127
|
+
**Flow:**
|
|
128
|
+
```
|
|
129
|
+
Multi-device mismatch (409)
|
|
130
|
+
↓
|
|
131
|
+
1. Key refresh
|
|
132
|
+
↓ fail
|
|
133
|
+
2. Full re-auth QR
|
|
134
|
+
↓ fail
|
|
135
|
+
❌ No recovery
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 7. Bad Session (generic)
|
|
139
|
+
|
|
140
|
+
Session tidak valid, butuh refresh dan re-fetch pre-keys.
|
|
141
|
+
|
|
142
|
+
**Flow:**
|
|
143
|
+
```
|
|
144
|
+
Bad session detected
|
|
145
|
+
↓
|
|
146
|
+
1. Key refresh
|
|
147
|
+
↓ fail
|
|
148
|
+
2. Pre-key fetch
|
|
149
|
+
↓ fail
|
|
150
|
+
3. Full re-auth QR
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### 8. Connection Replaced (code 440)
|
|
154
|
+
|
|
155
|
+
Koneksi diganti dengan device lain. Coba re-auth.
|
|
156
|
+
|
|
157
|
+
**Flow:**
|
|
158
|
+
```
|
|
159
|
+
Connection replaced (440)
|
|
160
|
+
↓
|
|
161
|
+
1. Key refresh
|
|
162
|
+
↓ fail
|
|
163
|
+
2. Full re-auth QR
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Aksi Pemulihan (Recovery Actions)
|
|
167
|
+
|
|
168
|
+
| Action | Deskripsi | Default Delay |
|
|
169
|
+
|--------|-----------|---------------|
|
|
170
|
+
| `immediate_retry` | Coba reconnect langsung tanpa delay | 0ms |
|
|
171
|
+
| `stream_reconnect` | Resume koneksi dari stream state yang tersimpan | 500ms |
|
|
172
|
+
| `backoff_retry` | Reconnect dengan exponential backoff | 1s, 2s, 4s... |
|
|
173
|
+
| `wait_cooldown` | Tunggu cooldown 5 menit (untuk rate limit) | 300000ms |
|
|
174
|
+
| `update_app_version` | Update identifikasi browser/app version | 1s |
|
|
175
|
+
| `key_refresh` | Refresh Signal keys via SKDM | 1s |
|
|
176
|
+
| `prekey_fetch` | Fetch pre-keys baru dari server | 1s |
|
|
177
|
+
| `full_reauth_qr` | Full re-authentication dengan QR code baru | 1s |
|
|
178
|
+
| `no_recovery` | Tidak ada pemulihan yang possible (fatal) | - |
|
|
179
|
+
|
|
180
|
+
## Event
|
|
181
|
+
|
|
182
|
+
RecoveryManager mengeluarkan beberapa event untuk monitoring:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
sock.recoveryManager.on('recovery:start', (info) => {
|
|
186
|
+
console.log(`Recovery started: ${info.reason} (code: ${info.code})`);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
sock.recoveryManager.on('recovery:branch:start', (info) => {
|
|
190
|
+
console.log(`Branch started: ${info.reason}`);
|
|
191
|
+
console.log(`Actions: ${info.actions.join(' → ')}`);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
sock.recoveryManager.on('recovery:action:start', (info) => {
|
|
195
|
+
console.log(`Action: ${info.action} (attempt ${info.attempt})`);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
sock.recoveryManager.on('recovery:action:success', (info) => {
|
|
199
|
+
console.log(`✓ ${info.action} succeeded in ${info.duration}ms`);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
sock.recoveryManager.on('recovery:action:failed', (info) => {
|
|
203
|
+
console.log(`✗ ${info.action} failed: ${info.error}`);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
sock.recoveryManager.on('recovery:success', (info) => {
|
|
207
|
+
console.log(`✓ Recovery succeeded after ${info.totalAttempts} attempts in ${info.duration}ms`);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
sock.recoveryManager.on('recovery:failed', (info) => {
|
|
211
|
+
console.log(`✗ Recovery failed (fatal: ${info.fatal})`);
|
|
212
|
+
if (info.fatal) {
|
|
213
|
+
console.log('Manual intervention required (re-scan QR)');
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
sock.recoveryManager.on('recovery:progress', (info) => {
|
|
218
|
+
console.log(`Progress: ${info.progress.toFixed(1)}% (${info.totalAttempts}/${info.maxTotalAttempts})`);
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## API
|
|
223
|
+
|
|
224
|
+
### Get Recovery Stats
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
const stats = sock.getRecoveryStats();
|
|
228
|
+
console.log(stats);
|
|
229
|
+
// {
|
|
230
|
+
// totalRecoveries: 5,
|
|
231
|
+
// successfulRecoveries: 4,
|
|
232
|
+
// failedRecoveries: 1,
|
|
233
|
+
// successRate: 0.8,
|
|
234
|
+
// byReason: {
|
|
235
|
+
// connection_lost: { success: 3, failure: 0 },
|
|
236
|
+
// rate_limited: { success: 1, failure: 0 },
|
|
237
|
+
// logged_out: { success: 0, failure: 1 }
|
|
238
|
+
// }
|
|
239
|
+
// }
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Check Recovery Status
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
if (sock.isRecovering()) {
|
|
246
|
+
const ctx = sock.getCurrentRecovery();
|
|
247
|
+
console.log(`Recovering from: ${ctx.reason}`);
|
|
248
|
+
console.log(`Current action: ${ctx.history[ctx.history.length - 1]?.action}`);
|
|
249
|
+
console.log(`Progress: ${ctx.totalAttempts}/${20} total attempts`);
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Manual Trigger (untuk testing)
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import { mapCodeToReason } from 'waliwa';
|
|
257
|
+
|
|
258
|
+
// Simulate disconnect
|
|
259
|
+
const reason = mapCodeToReason(429);
|
|
260
|
+
const success = await sock.triggerRecovery(reason, 429, 'rate limit exceeded');
|
|
261
|
+
console.log(`Recovery ${success ? 'succeeded' : 'failed'}`);
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Cancel Ongoing Recovery
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
// Cancel recovery manually
|
|
268
|
+
sock.recoveryManager.cancel();
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Custom Branches
|
|
272
|
+
|
|
273
|
+
Anda dapat mengganti (override) cabang default dengan yang custom:
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import { makeWASocket, RecoveryManager, type RecoveryBranch } from 'waliwa';
|
|
277
|
+
|
|
278
|
+
const customBranches: RecoveryBranch[] = [
|
|
279
|
+
{
|
|
280
|
+
reason: 'connection_lost',
|
|
281
|
+
actions: ['immediate_retry', 'backoff_retry'], // Simplified
|
|
282
|
+
maxAttempts: 5
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
reason: 'rate_limited',
|
|
286
|
+
actions: ['wait_cooldown', 'immediate_retry'],
|
|
287
|
+
maxAttempts: 2
|
|
288
|
+
}
|
|
289
|
+
// ... (other branches pakai default)
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
// Pass via config (atau instantiate manual)
|
|
293
|
+
const recoveryManager = new RecoveryManager({
|
|
294
|
+
branches: customBranches,
|
|
295
|
+
maxAttemptsPerBranch: 5,
|
|
296
|
+
maxTotalAttempts: 30
|
|
297
|
+
});
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Custom Action Handlers
|
|
301
|
+
|
|
302
|
+
Override handler default untuk action tertentu:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
import { makeWASocket, type RecoveryAction } from 'waliwa';
|
|
306
|
+
|
|
307
|
+
const sock = makeWASocket({
|
|
308
|
+
authState: state,
|
|
309
|
+
recovery: {
|
|
310
|
+
enabled: true,
|
|
311
|
+
actionHandlers: {
|
|
312
|
+
// Custom handler untuk key_refresh
|
|
313
|
+
key_refresh: async (ctx) => {
|
|
314
|
+
console.log(`Custom key refresh (attempt ${ctx.attempt})`);
|
|
315
|
+
// Custom logic: backup old keys sebelum refresh
|
|
316
|
+
await backupKeys();
|
|
317
|
+
// Lalu refresh
|
|
318
|
+
await sock.getSKDM()?.preKeyManager.regeneratePreKeys();
|
|
319
|
+
// Try reconnect
|
|
320
|
+
return await sock.recoveryManager.connect();
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
// Custom handler untuk full_reauth_qr
|
|
324
|
+
full_reauth_qr: async (ctx) => {
|
|
325
|
+
console.log('Custom full re-auth');
|
|
326
|
+
// Send notification ke admin
|
|
327
|
+
await notifyAdmin('Bot needs re-authentication!');
|
|
328
|
+
// Lalu start re-auth
|
|
329
|
+
await sock.recoveryManager.startFullReauth();
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Production Setup Example
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
import { makeWASocket, useFileAuthState } from 'waliwa';
|
|
341
|
+
|
|
342
|
+
async function main() {
|
|
343
|
+
const { state } = await useFileAuthState('./auth');
|
|
344
|
+
|
|
345
|
+
const sock = makeWASocket({
|
|
346
|
+
authState: state,
|
|
347
|
+
printQRInTerminal: true,
|
|
348
|
+
ramOptimizationLevel: 2,
|
|
349
|
+
|
|
350
|
+
// Multi-branch recovery
|
|
351
|
+
recovery: {
|
|
352
|
+
enabled: true,
|
|
353
|
+
maxAttemptsPerBranch: 3,
|
|
354
|
+
maxTotalAttempts: 20,
|
|
355
|
+
initialDelayMs: 1000,
|
|
356
|
+
maxDelayMs: 60000,
|
|
357
|
+
rateLimitedDelayMs: 300000 // 5 minutes
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Monitor recovery events
|
|
362
|
+
sock.recoveryManager.on('recovery:start', ({ reason, code }) => {
|
|
363
|
+
console.warn(`⚠️ Recovery started: ${reason} (code ${code})`);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
sock.recoveryManager.on('recovery:success', ({ reason, totalAttempts, duration }) => {
|
|
367
|
+
console.log(`✅ Recovery succeeded: ${reason} in ${duration}ms (${totalAttempts} attempts)`);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
sock.recoveryManager.on('recovery:failed', ({ reason, fatal }) => {
|
|
371
|
+
console.error(`❌ Recovery failed: ${reason} (fatal: ${fatal})`);
|
|
372
|
+
if (fatal) {
|
|
373
|
+
// Send alert ke admin - manual QR scan required
|
|
374
|
+
notifyAdmin(`Bot logged out! Reason: ${reason}. Manual re-auth required.`);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// Periodic stats logging
|
|
379
|
+
setInterval(() => {
|
|
380
|
+
const stats = sock.getRecoveryStats();
|
|
381
|
+
console.log('Recovery stats:', stats);
|
|
382
|
+
}, 60000);
|
|
383
|
+
|
|
384
|
+
// Graceful shutdown
|
|
385
|
+
process.on('SIGINT', async () => {
|
|
386
|
+
console.log('Shutting down...');
|
|
387
|
+
// Cancel ongoing recovery
|
|
388
|
+
sock.recoveryManager.cancel();
|
|
389
|
+
await sock.end();
|
|
390
|
+
process.exit(0);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
main();
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## Perbandingan dengan Baileys
|
|
398
|
+
|
|
399
|
+
| Aspek | Baileys | Waliwa |
|
|
400
|
+
|-------|---------|--------|
|
|
401
|
+
| Reconnect strategy | Single path (exponential backoff) | Multi-branch berdasarkan disconnect reason |
|
|
402
|
+
| Rate limit handling | Sama dengan disconnect biasa | Cooldown khusus 5 menit |
|
|
403
|
+
| Logged out | Coba reconnect sama | Fatal - clear creds, minta QR ulang |
|
|
404
|
+
| Connection replaced | Tetap coba reconnect | Key refresh + re-auth QR |
|
|
405
|
+
| Multi-device mismatch | Tidak ada handling khusus | Key refresh + re-auth QR |
|
|
406
|
+
| Max retry limit | Single global limit | Per-branch + total cap |
|
|
407
|
+
| Custom handlers | Tidak ada | Setiap action bisa di-override |
|
|
408
|
+
| Recovery events | Tidak ada | 7 event types untuk monitoring |
|
|
409
|
+
| Recovery stats | Tidak ada | Per-reason success/failure tracking |
|
package/docs/SKDM.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# SKDM (Store Key Data Manager)
|
|
2
|
+
|
|
3
|
+
SKDM adalah subsystem cerdas untuk manage Signal protocol keys. Dibuat untuk mengatasi issue-issue Baileys yang sering dikeluhkan terkait key management.
|
|
4
|
+
|
|
5
|
+
## Masalah Baileys yang Diatasi SKDM
|
|
6
|
+
|
|
7
|
+
| Baileys Issue | SKDM Solution |
|
|
8
|
+
|---------------|---------------|
|
|
9
|
+
| Pre-key exhaustion (pre-keys habis tanpa warning) | PreKeyManager dengan auto-regeneration |
|
|
10
|
+
| Session corruption on disconnect | AtomicFileWriter (write ke .tmp lalu rename) |
|
|
11
|
+
| Decryption failure untuk old messages | SessionManager.prefetchSessions() |
|
|
12
|
+
| Memory leak dari sessions lama | LRUCache dengan TTL eviction |
|
|
13
|
+
| App state keys bertumpuk tanpa cleanup | AppStateKeyManager.cleanup() |
|
|
14
|
+
| Identity changes gak tracked | IdentityKeyManager dengan change detection |
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { makeWASocket, useFileAuthState, SKDM } from 'waliwa';
|
|
20
|
+
|
|
21
|
+
const state = await useFileAuthState('./auth');
|
|
22
|
+
|
|
23
|
+
// SKDM otomatis initialized di dalam WASocket
|
|
24
|
+
const sock = makeWASocket({
|
|
25
|
+
authState: state,
|
|
26
|
+
printQRInTerminal: true
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Akses SKDM instance
|
|
30
|
+
const skdm = sock.getSKDM();
|
|
31
|
+
|
|
32
|
+
// Get stats
|
|
33
|
+
console.log(skdm?.getStats());
|
|
34
|
+
// Output:
|
|
35
|
+
// {
|
|
36
|
+
// preKeys: { total: 95, nextId: 105, lastRegenTime: 1234567890, needsRegen: false },
|
|
37
|
+
// sessions: { size: 12, hits: 45, misses: 3, hitRate: 0.937, totalSessions: 50, expiredSessions: 0 },
|
|
38
|
+
// identities: { total: 50, changed: 0 },
|
|
39
|
+
// appStateKeys: 15,
|
|
40
|
+
// pendingSaves: 0
|
|
41
|
+
// }
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Komponen SKDM
|
|
45
|
+
|
|
46
|
+
### 1. PreKeyManager
|
|
47
|
+
|
|
48
|
+
Auto-regenerate pre-keys saat hampir habis. Mencegah Baileys issue di mana bot tiba-tiba tidak bisa menerima pesan baru karena pre-keys habis.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
const skdm = sock.getSKDM();
|
|
52
|
+
|
|
53
|
+
// Manual trigger
|
|
54
|
+
await skdm.preKeyManager.regeneratePreKeys();
|
|
55
|
+
|
|
56
|
+
// Check stats
|
|
57
|
+
const stats = skdm.preKeyManager.getStats();
|
|
58
|
+
console.log(`Pre-keys: ${stats.total}/${stats.target}`);
|
|
59
|
+
console.log(`Needs regeneration: ${stats.needsRegen}`);
|
|
60
|
+
|
|
61
|
+
// Consume pre-key (otomatis saat receive message)
|
|
62
|
+
skdm.preKeyManager.consumePreKey(42);
|
|
63
|
+
// Auto-triggers regeneration if low
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Konfigurasi:**
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
makeWASocket({
|
|
70
|
+
authState: state,
|
|
71
|
+
// SKDM config
|
|
72
|
+
minPreKeys: 5, // Regenerate jika < 5 pre-keys
|
|
73
|
+
maxPreKeysBatch: 50, // Max 50 pre-keys per regeneration
|
|
74
|
+
targetPreKeyCount: 100, // Maintain 100 pre-keys total
|
|
75
|
+
enableKeyRotation: true // Background rotation tiap 1 jam
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 2. SessionManager
|
|
80
|
+
|
|
81
|
+
LRU cache untuk sessions - mencegah memory leak.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
const skdm = sock.getSKDM();
|
|
85
|
+
|
|
86
|
+
// Get session (auto-cache)
|
|
87
|
+
const session = skdm.sessionManager.getSession('6281@s.whatsapp.net');
|
|
88
|
+
|
|
89
|
+
// Prefetch sessions untuk multiple users
|
|
90
|
+
// Berguna sebelum bulk decrypt group messages
|
|
91
|
+
skdm.sessionManager.prefetchSessions([
|
|
92
|
+
'user1@s.whatsapp.net',
|
|
93
|
+
'user2@s.whatsapp.net',
|
|
94
|
+
'user3@s.whatsapp.net'
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
// Cleanup expired sessions (otomatis tiap 6 jam)
|
|
98
|
+
const cleaned = skdm.sessionManager.cleanupExpiredSessions();
|
|
99
|
+
console.log(`Cleaned ${cleaned} sessions`);
|
|
100
|
+
|
|
101
|
+
// Get cache stats
|
|
102
|
+
const stats = skdm.sessionManager.getStats();
|
|
103
|
+
console.log(`Cache hit rate: ${(stats.hitRate * 100).toFixed(1)}%`);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 3. IdentityKeyManager
|
|
107
|
+
|
|
108
|
+
Track identity changes untuk security.
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
const skdm = sock.getSKDM();
|
|
112
|
+
|
|
113
|
+
// Check identity changes (security alerts)
|
|
114
|
+
const changed = skdm.identityManager.getChangedIdentities();
|
|
115
|
+
if (changed.length > 0) {
|
|
116
|
+
console.warn('Identity changed untuk users:', changed);
|
|
117
|
+
// Verify dengan user, lalu acknowledge
|
|
118
|
+
for (const jid of changed) {
|
|
119
|
+
// After manual verification:
|
|
120
|
+
skdm.identityManager.acknowledge(jid);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Trust identity (override safety)
|
|
125
|
+
skdm.identityManager.trust('6281@s.whatsapp.net');
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 4. AppStateKeyManager
|
|
129
|
+
|
|
130
|
+
Manage app state sync keys dengan cleanup otomatis.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
const skdm = sock.getSKDM();
|
|
134
|
+
|
|
135
|
+
// Cleanup old app state keys (keep last 50)
|
|
136
|
+
const cleaned = skdm.appStateManager.cleanup(50);
|
|
137
|
+
console.log(`Cleaned ${cleaned} old app state keys`);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 5. AtomicFileWriter
|
|
141
|
+
|
|
142
|
+
Atomic writes untuk prevent corruption saat disconnect/power failure.
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { AtomicFileWriter } from 'waliwa';
|
|
146
|
+
|
|
147
|
+
// Safe write - akan write ke .tmp lalu rename (atomic di POSIX)
|
|
148
|
+
await AtomicFileWriter.writeFile(
|
|
149
|
+
'./auth/session.json',
|
|
150
|
+
JSON.stringify(sessionData),
|
|
151
|
+
'utf-8'
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// Even jika process crash saat write:
|
|
155
|
+
// - .tmp file mungkin tertinggal (safe to delete)
|
|
156
|
+
// - Original file tidak corrupt
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## LRU Cache
|
|
160
|
+
|
|
161
|
+
SKDM menggunakan LRU cache custom untuk RAM efficiency.
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { LRUCache } from 'waliwa';
|
|
165
|
+
|
|
166
|
+
const cache = new LRUCache<string, any>(500); // Max 500 entries
|
|
167
|
+
|
|
168
|
+
cache.set('key1', { data: 'value' });
|
|
169
|
+
const value = cache.get('key1');
|
|
170
|
+
|
|
171
|
+
const stats = cache.getStats();
|
|
172
|
+
console.log(stats);
|
|
173
|
+
// { size: 1, hits: 1, misses: 0, hitRate: 1 }
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Monitoring
|
|
177
|
+
|
|
178
|
+
SKDM exposes stats untuk monitoring:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// Comprehensive stats
|
|
182
|
+
const stats = sock.getSKDMStats();
|
|
183
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
184
|
+
|
|
185
|
+
// System stats (includes SKDM + other components)
|
|
186
|
+
const systemStats = sock.getSystemStats();
|
|
187
|
+
console.log(JSON.stringify(systemStats, null, 2));
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Background Jobs
|
|
191
|
+
|
|
192
|
+
SKDM menjalankan beberapa background jobs otomatis:
|
|
193
|
+
|
|
194
|
+
| Job | Interval | Purpose |
|
|
195
|
+
|-----|----------|---------|
|
|
196
|
+
| Pre-key rotation check | 1 hour | Regenerate pre-keys jika low |
|
|
197
|
+
| Session cleanup | 6 hours | Hapus expired sessions (>30 days) |
|
|
198
|
+
| AppState key cleanup | 24 hours | Hapus unused app state keys |
|
|
199
|
+
|
|
200
|
+
Jobs otomatis di-stop saat `sock.end()` dipanggil.
|
|
201
|
+
|
|
202
|
+
## Configuration
|
|
203
|
+
|
|
204
|
+
Semua konfigurasi SKDM via WaliwaConfig:
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
makeWASocket({
|
|
208
|
+
authState: state,
|
|
209
|
+
|
|
210
|
+
// PreKey config
|
|
211
|
+
minPreKeys: 5,
|
|
212
|
+
maxPreKeysBatch: 50,
|
|
213
|
+
targetPreKeyCount: 100,
|
|
214
|
+
enableKeyRotation: true,
|
|
215
|
+
|
|
216
|
+
// Session cache config
|
|
217
|
+
maxSessionCacheSize: 500,
|
|
218
|
+
sessionExpiryMs: 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
219
|
+
enableSessionPrefetch: true,
|
|
220
|
+
|
|
221
|
+
// Storage
|
|
222
|
+
storageFolder: './auth',
|
|
223
|
+
prefix: 'waliwa-session'
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Best Practices
|
|
228
|
+
|
|
229
|
+
1. **Always call `sock.end()`** saat shutdown untuk flush pending saves
|
|
230
|
+
2. **Monitor `getSKDMStats()`** secara periodik untuk detect issues
|
|
231
|
+
3. **Handle identity changes** sebagai security alert
|
|
232
|
+
4. **Don't manually edit** session files - use SKDM API
|
|
233
|
+
5. **Backup auth folder** sebelum updates besar
|