@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
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
This document describes the internal architecture of Waliwa library.
|
|
4
|
+
|
|
5
|
+
## Design Principles
|
|
6
|
+
|
|
7
|
+
### 1. Hybrid Protocol
|
|
8
|
+
Waliwa implements **two distinct WhatsApp protocols** with seamless switching:
|
|
9
|
+
|
|
10
|
+
- **WhatsApp Web Multi-Device (MD)** - The primary protocol used by WhatsApp Web. Uses WebSocket + Noise Protocol Framework (XX pattern) + custom binary format.
|
|
11
|
+
- **Mobile API Pairing** - Companion device registration via phone number + pairing code. Used by official WhatsApp Business API clients.
|
|
12
|
+
|
|
13
|
+
The `HybridSocketManager` automatically falls back between these protocols when one fails.
|
|
14
|
+
|
|
15
|
+
### 2. RAM-Efficient Design
|
|
16
|
+
Three key strategies to minimize memory usage:
|
|
17
|
+
|
|
18
|
+
- **Buffer Pooling** - Reuse pre-allocated Uint8Array buffers for WebSocket frames instead of allocating new ones per message
|
|
19
|
+
- **Lazy Loading** - Signal keys/sessions loaded on-demand, not all at startup
|
|
20
|
+
- **Debounced Disk IO** - Batch credential file writes every 50-100ms instead of per-update
|
|
21
|
+
|
|
22
|
+
### 3. Clean-Room Implementation
|
|
23
|
+
All protocol-specific code is written from scratch based on publicly documented specs:
|
|
24
|
+
- Noise Protocol Framework (noiseprotocol.org)
|
|
25
|
+
- WhatsApp binary format (publicly documented through reverse engineering)
|
|
26
|
+
- Signal protocol (open source spec)
|
|
27
|
+
|
|
28
|
+
No code is copied from Baileys, ZapoJS, or any other WhatsApp library.
|
|
29
|
+
|
|
30
|
+
## Layer Architecture
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
34
|
+
│ Application Layer │
|
|
35
|
+
│ (User code using WASocket) │
|
|
36
|
+
└─────────────────────────────────────────────────────────────┘
|
|
37
|
+
│
|
|
38
|
+
▼
|
|
39
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
40
|
+
│ Public API Layer │
|
|
41
|
+
│ makeWASocket(), sendMessage(), groupCreate(), ev.on() │
|
|
42
|
+
└─────────────────────────────────────────────────────────────┘
|
|
43
|
+
│
|
|
44
|
+
┌─────────────────────┼─────────────────────┐
|
|
45
|
+
▼ ▼ ▼
|
|
46
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
47
|
+
│ Messages │ │ Groups │ │ Calls │
|
|
48
|
+
│ Sender │ │ Manager │ │ Handler │
|
|
49
|
+
│ Receiver │ │ │ │ │
|
|
50
|
+
│ Media │ │ │ │ │
|
|
51
|
+
└──────────────┘ └──────────────┘ └──────────────┘
|
|
52
|
+
│
|
|
53
|
+
▼
|
|
54
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
55
|
+
│ Socket Layer │
|
|
56
|
+
│ WAWebSocket (Noise-encrypted WebSocket frames) │
|
|
57
|
+
│ MobileAPISocket (Pairing code flow) │
|
|
58
|
+
│ HybridSocketManager (Auto-fallback) │
|
|
59
|
+
└─────────────────────────────────────────────────────────────┘
|
|
60
|
+
│
|
|
61
|
+
▼
|
|
62
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
63
|
+
│ Core Layer │
|
|
64
|
+
│ NoiseHandshake (XX pattern implementation) │
|
|
65
|
+
│ TrafficCipher (AES-256-GCM post-handshake) │
|
|
66
|
+
│ BinaryEncoder/Decoder (WA binary format) │
|
|
67
|
+
│ Crypto utilities (Curve25519, HKDF, AES, HMAC) │
|
|
68
|
+
└─────────────────────────────────────────────────────────────┘
|
|
69
|
+
│
|
|
70
|
+
▼
|
|
71
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
72
|
+
│ Auth Layer │
|
|
73
|
+
│ FileAuthState (JSON file storage) │
|
|
74
|
+
│ QR Code generator (WA Web pairing) │
|
|
75
|
+
│ Pairing Code generator (Mobile API) │
|
|
76
|
+
│ Signal Key Store (pre-keys, sessions, identity) │
|
|
77
|
+
└─────────────────────────────────────────────────────────────┘
|
|
78
|
+
│
|
|
79
|
+
▼
|
|
80
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
81
|
+
│ Utilities Layer │
|
|
82
|
+
│ Logger (lightweight alternative to pino) │
|
|
83
|
+
│ JID utilities (parse, normalize, validate) │
|
|
84
|
+
│ Retry/Throttle (exponential backoff, rate limiter) │
|
|
85
|
+
│ Event Emitter (typed, Baileys-compatible) │
|
|
86
|
+
└─────────────────────────────────────────────────────────────┘
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Module Dependencies
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
src/index.ts
|
|
93
|
+
├── socket/wa-socket.ts (main entry point)
|
|
94
|
+
│ ├── socket/ws-socket.ts (WebSocket layer)
|
|
95
|
+
│ │ ├── core/noise.ts (Noise protocol)
|
|
96
|
+
│ │ │ └── core/crypto.ts (crypto primitives)
|
|
97
|
+
│ │ └── core/binary.ts (binary encoding)
|
|
98
|
+
│ ├── socket/hybrid.ts (Mobile API + fallback)
|
|
99
|
+
│ ├── auth/auth-state.ts (file storage)
|
|
100
|
+
│ ├── auth/qr-code.ts (QR pairing)
|
|
101
|
+
│ ├── auth/pairing-code.ts (Mobile pairing)
|
|
102
|
+
│ ├── messages/send.ts (message operations)
|
|
103
|
+
│ │ └── messages/message-encoder.ts (encoding)
|
|
104
|
+
│ ├── messages/media.ts (upload/download)
|
|
105
|
+
│ ├── groups/management.ts (group operations)
|
|
106
|
+
│ ├── calls/handler.ts (call handling)
|
|
107
|
+
│ ├── advanced/features.ts (polls, newsletter, etc)
|
|
108
|
+
│ ├── events/emitter.ts (typed events)
|
|
109
|
+
│ └── utils/{jid,logger,retry}.ts (utilities)
|
|
110
|
+
└── types/index.ts (all type definitions)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Connection Lifecycle
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
1. User calls makeWASocket(config)
|
|
117
|
+
↓
|
|
118
|
+
2. WASocketImpl created with config
|
|
119
|
+
↓
|
|
120
|
+
3. connect() called automatically
|
|
121
|
+
↓
|
|
122
|
+
4. WAWebSocket established to wss://web.whatsapp.com/ws/chat
|
|
123
|
+
↓
|
|
124
|
+
5. Noise Protocol XX handshake performed
|
|
125
|
+
- Client sends: ephemeral + encrypted static + payload
|
|
126
|
+
- Server responds: ephemeral + encrypted static + ES + payload
|
|
127
|
+
- Both derive traffic keys via HKDF
|
|
128
|
+
↓
|
|
129
|
+
6. Traffic cipher initialized (AES-256-GCM)
|
|
130
|
+
↓
|
|
131
|
+
7. Auth flow starts:
|
|
132
|
+
- If creds.registered = true: send login IQ
|
|
133
|
+
- If mobile mode: request pairing code
|
|
134
|
+
- Else: generate QR code, start refresh cycle
|
|
135
|
+
↓
|
|
136
|
+
8. Server sends success/failure node
|
|
137
|
+
- On success: emit 'connection.update' with connection='open'
|
|
138
|
+
- On failure: emit error, retry with exponential backoff
|
|
139
|
+
↓
|
|
140
|
+
9. Keep-alive ping every 20s
|
|
141
|
+
↓
|
|
142
|
+
10. Incoming messages processed by MessageReceiver
|
|
143
|
+
- Decode binary node
|
|
144
|
+
- Parse message type (text, image, video, etc)
|
|
145
|
+
- Emit 'message.upsert' event
|
|
146
|
+
↓
|
|
147
|
+
11. On disconnect:
|
|
148
|
+
- Reject all pending messages
|
|
149
|
+
- Auto-reconnect with exponential backoff
|
|
150
|
+
- Switch protocol mode if max retries reached
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Message Encoding Flow
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
User: sock.sendMessage(jid, { text: 'Hello' })
|
|
157
|
+
↓
|
|
158
|
+
1. MessageSender.sendMessage() called
|
|
159
|
+
↓
|
|
160
|
+
2. generateMessageId() returns "3EB0..."
|
|
161
|
+
↓
|
|
162
|
+
3. encodeMessageNode() builds BinaryNode:
|
|
163
|
+
{
|
|
164
|
+
tag: 'message',
|
|
165
|
+
attrs: { to: jid, type: 'text', id: msgId },
|
|
166
|
+
content: [{ tag: 'conversation', attrs: {}, content: 'Hello' }]
|
|
167
|
+
}
|
|
168
|
+
↓
|
|
169
|
+
4. encodeBinaryNode() serializes to Uint8Array:
|
|
170
|
+
- Tag header byte
|
|
171
|
+
- Tag string (looked up in dictionary, packed nibble, or raw bytes)
|
|
172
|
+
- Attributes (key-value pairs)
|
|
173
|
+
- Content (recursive children)
|
|
174
|
+
↓
|
|
175
|
+
5. WAWebSocket.send():
|
|
176
|
+
- TrafficCipher.encrypt() with AES-256-GCM (counter nonce)
|
|
177
|
+
- Prepend WA frame header: [0x57 0x41] [version] [3-byte length]
|
|
178
|
+
- ws.send(Buffer.from(frame))
|
|
179
|
+
↓
|
|
180
|
+
6. Server ACKs message with 'ack' node
|
|
181
|
+
- MessageTracker resolves pending Promise
|
|
182
|
+
- Returns SendMessageResult to user
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## File Layout
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
waliwa/
|
|
189
|
+
├── src/
|
|
190
|
+
│ ├── index.ts # Public exports
|
|
191
|
+
│ ├── types/index.ts # All TypeScript types
|
|
192
|
+
│ ├── core/
|
|
193
|
+
│ │ ├── crypto.ts # Curve25519, AES, HKDF, HMAC
|
|
194
|
+
│ │ ├── binary.ts # WA binary encoder/decoder
|
|
195
|
+
│ │ └── noise.ts # Noise Protocol XX handshake
|
|
196
|
+
│ ├── socket/
|
|
197
|
+
│ │ ├── ws-socket.ts # WA WebSocket layer
|
|
198
|
+
│ │ ├── wa-socket.ts # Main WASocket interface
|
|
199
|
+
│ │ └── hybrid.ts # Mobile API + Hybrid manager
|
|
200
|
+
│ ├── auth/
|
|
201
|
+
│ │ ├── auth-state.ts # File-based auth state
|
|
202
|
+
│ │ ├── qr-code.ts # QR code generation
|
|
203
|
+
│ │ └── pairing-code.ts # Mobile pairing code
|
|
204
|
+
│ ├── messages/
|
|
205
|
+
│ │ ├── message-encoder.ts # Message encoding/decoding
|
|
206
|
+
│ │ ├── send.ts # Send/receive operations
|
|
207
|
+
│ │ └── media.ts # Media upload/download
|
|
208
|
+
│ ├── groups/management.ts # Group operations
|
|
209
|
+
│ ├── calls/handler.ts # Call handling
|
|
210
|
+
│ ├── advanced/features.ts # Polls, newsletter, etc
|
|
211
|
+
│ ├── events/emitter.ts # Typed event emitter
|
|
212
|
+
│ └── utils/
|
|
213
|
+
│ ├── jid.ts # JID utilities
|
|
214
|
+
│ ├── logger.ts # Logger
|
|
215
|
+
│ └── retry.ts # Retry/throttle
|
|
216
|
+
├── examples/
|
|
217
|
+
│ ├── echo-bot/ # Basic echo bot
|
|
218
|
+
│ ├── group-bot/ # Group management bot
|
|
219
|
+
│ ├── ai-bot/ # AI chatbot with GPT
|
|
220
|
+
│ └── rest-gateway/ # REST API gateway
|
|
221
|
+
├── docs/ # Documentation
|
|
222
|
+
├── package.json
|
|
223
|
+
├── tsconfig.json
|
|
224
|
+
├── LICENSE
|
|
225
|
+
└── README.md
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Performance Considerations
|
|
229
|
+
|
|
230
|
+
### Memory Usage
|
|
231
|
+
- BufferPool: Pre-allocate 16 buffers of 8KB each (configurable)
|
|
232
|
+
- Lazy session loading: Only load Signal sessions when first message exchanged
|
|
233
|
+
- Conversation history: Optional, max 10 messages per user (configurable)
|
|
234
|
+
- Debounced writes: Coalesce 100ms of state changes into single disk write
|
|
235
|
+
|
|
236
|
+
### Network Usage
|
|
237
|
+
- WebSocket compression disabled (perMessageDeflate: false) - avoids CPU overhead
|
|
238
|
+
- Keep-alive ping every 20s (configurable)
|
|
239
|
+
- Max payload: 16MB (matches WhatsApp server limit)
|
|
240
|
+
- HTTP/2 used for media upload/download (via axios)
|
|
241
|
+
|
|
242
|
+
### CPU Usage
|
|
243
|
+
- AES-GCM via @noble/ciphers (no native bindings, pure JS)
|
|
244
|
+
- Curve25519 via @noble/curves (constant-time, audited)
|
|
245
|
+
- Binary encoding with dictionary lookup (O(1) for common tags)
|
|
246
|
+
- No protobuf runtime reflection - all encoding done manually
|
|
247
|
+
|
|
248
|
+
## Error Handling
|
|
249
|
+
|
|
250
|
+
### Reconnection Strategy
|
|
251
|
+
```
|
|
252
|
+
Disconnect detected
|
|
253
|
+
↓
|
|
254
|
+
Increment retry counter
|
|
255
|
+
↓
|
|
256
|
+
Delay = min(baseInterval * 2^retries, 60000)
|
|
257
|
+
↓
|
|
258
|
+
Reconnect attempt
|
|
259
|
+
↓
|
|
260
|
+
┌──→ Success → Reset counter
|
|
261
|
+
│
|
|
262
|
+
└──→ Failure → Loop back if counter < maxRetries
|
|
263
|
+
↓
|
|
264
|
+
If counter >= maxRetries:
|
|
265
|
+
- Switch protocol mode (Web ↔ Mobile)
|
|
266
|
+
- Reset counter
|
|
267
|
+
- Try again
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Message Retry Strategy
|
|
271
|
+
- Max 5 retries per message (configurable)
|
|
272
|
+
- Exponential backoff: 500ms → 1s → 2s → 4s → 8s
|
|
273
|
+
- On final failure: emit 'message.update' with status='error'
|
|
274
|
+
- Pending messages cleared on disconnect (rejected with 'Connection closed' error)
|
|
275
|
+
|
|
276
|
+
## Extension Points
|
|
277
|
+
|
|
278
|
+
### Custom Auth State
|
|
279
|
+
```typescript
|
|
280
|
+
import { AuthState, AuthenticationCreds, SignalKeyStore } from 'waliwa';
|
|
281
|
+
|
|
282
|
+
class RedisAuthState implements AuthState {
|
|
283
|
+
creds: AuthenticationCreds;
|
|
284
|
+
keys: SignalKeyStore;
|
|
285
|
+
// Implement with Redis backend
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const sock = makeWASocket({
|
|
289
|
+
authState: new RedisAuthState(redisClient)
|
|
290
|
+
});
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Custom Logger
|
|
294
|
+
```typescript
|
|
295
|
+
import { WALogger } from 'waliwa';
|
|
296
|
+
|
|
297
|
+
class MyLogger implements WALogger {
|
|
298
|
+
// Implement with your logging system (Winston, Bunyan, etc.)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const sock = makeWASocket({
|
|
302
|
+
logger: new MyLogger()
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Custom Media Storage
|
|
307
|
+
The MediaUploader/MediaDownloader classes can be extended to use S3, Google Cloud Storage, or other backends instead of WhatsApp CDN.
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
# Baileys Issues Fixes
|
|
2
|
+
|
|
3
|
+
Waliwa mengatasi 9+ issues yang sering dikeluhkan di Baileys. Setiap fix dijelaskan dengan masalah, solusi, dan cara pakai.
|
|
4
|
+
|
|
5
|
+
## Daftar Fix
|
|
6
|
+
|
|
7
|
+
### 1. Pre-key Exhaustion (Baileys Issue)
|
|
8
|
+
|
|
9
|
+
**Masalah:** Pre-keys habis tanpa warning, bot tiba-tiba tidak bisa menerima pesan baru.
|
|
10
|
+
|
|
11
|
+
**Solusi:** `PreKeyManager` di SKDM dengan auto-regeneration.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// Otomatis di SKDM - pre-keys di-regenerate saat < minPreKeys (default: 5)
|
|
15
|
+
const skdm = sock.getSKDM();
|
|
16
|
+
console.log(skdm.preKeyManager.getStats());
|
|
17
|
+
// { total: 95, needsRegen: false, ... }
|
|
18
|
+
|
|
19
|
+
// Manual check
|
|
20
|
+
if (skdm.preKeyManager.needsRegeneration()) {
|
|
21
|
+
await skdm.preKeyManager.regeneratePreKeys();
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Lihat:** [SKDM documentation](./SKDM.md) untuk detail lengkap.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
### 2. Race Condition di Message Sending
|
|
30
|
+
|
|
31
|
+
**Masalah:** Concurrent sends ke same chat cause duplicate messages, wrong order, atau message ID collision.
|
|
32
|
+
|
|
33
|
+
**Solusi:** `MessageQueue` dengan per-recipient serialization.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// Otomatis di WASocket.sendMessage() - tiap recipient punya queue sendiri
|
|
37
|
+
// Concurrent across different recipients (parallelism)
|
|
38
|
+
// Sequential per recipient (order guarantee)
|
|
39
|
+
|
|
40
|
+
// Manual usage untuk custom operations:
|
|
41
|
+
import { MessageQueue } from 'waliwa';
|
|
42
|
+
|
|
43
|
+
const queue = new MessageQueue(10); // Max 10 concurrent recipients
|
|
44
|
+
await queue.enqueue('user1@s.whatsapp.net', async () => {
|
|
45
|
+
return await sock.sendMessage('user1@s.whatsapp.net', { text: 'Hi' });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Stats
|
|
49
|
+
console.log(queue.getStats());
|
|
50
|
+
// { totalQueues: 1, totalPending: 0, activeCount: 0, maxConcurrency: 10 }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
### 3. Memory Leak dari Event Listeners
|
|
56
|
+
|
|
57
|
+
**Masalah:** Listeners tidak di-cleanup properly saat objects di-destroy, menyebabkan memory leak overtime.
|
|
58
|
+
|
|
59
|
+
**Solusi:** `WeakEventEmitter` dengan FinalizationRegistry untuk auto-cleanup.
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { WeakEventEmitter } from 'waliwa';
|
|
63
|
+
|
|
64
|
+
const emitter = new WeakEventEmitter();
|
|
65
|
+
|
|
66
|
+
class MyComponent {
|
|
67
|
+
private data: any[] = [];
|
|
68
|
+
|
|
69
|
+
constructor() {
|
|
70
|
+
// Auto-cleanup saat MyComponent di-GC
|
|
71
|
+
emitter.subscribeWeak(this, 'message', (msg) => {
|
|
72
|
+
this.data.push(msg);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Atau manual cleanup
|
|
78
|
+
emitter.cleanupTarget(myComponentInstance);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
### 4. Connection Hang
|
|
84
|
+
|
|
85
|
+
**Masalah:** WebSocket connection hang, tidak detect disconnect, bot "appear online" tapi tidak receive/send messages.
|
|
86
|
+
|
|
87
|
+
**Solusi:** `ConnectionHeartbeat` dengan proper dead detection.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { ConnectionHeartbeat } from 'waliwa';
|
|
91
|
+
|
|
92
|
+
const heartbeat = new ConnectionHeartbeat({
|
|
93
|
+
sendPing: async () => {
|
|
94
|
+
// Send WA ping
|
|
95
|
+
await sock.send(encodeBinaryNode({
|
|
96
|
+
tag: 'iq',
|
|
97
|
+
attrs: { type: 'get', id: `ping-${Date.now()}` },
|
|
98
|
+
content: [{ tag: 'ping', attrs: {} }]
|
|
99
|
+
}));
|
|
100
|
+
},
|
|
101
|
+
onDead: () => {
|
|
102
|
+
console.error('Connection dead, triggering reconnect');
|
|
103
|
+
// Custom recovery logic
|
|
104
|
+
},
|
|
105
|
+
pingIntervalMs: 20000, // Ping every 20s
|
|
106
|
+
pongTimeoutMs: 10000, // Wait 10s for pong
|
|
107
|
+
maxMissedPongs: 3 // Mark dead after 3 misses
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
heartbeat.start();
|
|
111
|
+
|
|
112
|
+
// When pong received (di WA message handler):
|
|
113
|
+
heartbeat.receivedPong();
|
|
114
|
+
|
|
115
|
+
// Check health
|
|
116
|
+
console.log(heartbeat.isHealthy()); // true/false
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Otomatis di WASocket:** Heartbeat aktif secara default, mengirim ping setiap 20 detik.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### 5. Decryption Failure untuk Old Messages
|
|
124
|
+
|
|
125
|
+
**Masalah:** Messages dari long-time-no-chat contacts fail to decrypt karena session keys expired/missing.
|
|
126
|
+
|
|
127
|
+
**Solusi:** `SessionManager.prefetchSessions()` + `DecryptionRetryQueue`.
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const skdm = sock.getSKDM();
|
|
131
|
+
|
|
132
|
+
// Prefetch sessions sebelum bulk decrypt group messages
|
|
133
|
+
skdm.sessionManager.prefetchSessions([
|
|
134
|
+
'user1@s.whatsapp.net',
|
|
135
|
+
'user2@s.whatsapp.net'
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
// Decryption retry queue - retry failed decryptions after key sync
|
|
139
|
+
import { DecryptionRetryQueue } from 'waliwa';
|
|
140
|
+
|
|
141
|
+
const retryQueue = new DecryptionRetryQueue({
|
|
142
|
+
maxAttempts: 3,
|
|
143
|
+
maxQueueSize: 1000,
|
|
144
|
+
retryHandler: async (item) => {
|
|
145
|
+
// Custom retry logic - request key sync, then retry
|
|
146
|
+
await requestKeySync(item.senderJid);
|
|
147
|
+
return await retryDecryption(item.encryptedData);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Enqueue failed decryption
|
|
152
|
+
retryQueue.enqueue('msg-123', '6281@s.whatsapp.net', encryptedData);
|
|
153
|
+
|
|
154
|
+
// Trigger immediate retry (e.g., after key sync complete)
|
|
155
|
+
await retryQueue.retryNow();
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
### 6. Duplicate Messages
|
|
161
|
+
|
|
162
|
+
**Masalah:** Messages diterima multiple times, bot reply 2x, confusing user.
|
|
163
|
+
|
|
164
|
+
**Solusi:** `IdempotencyCache` dengan TTL.
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// Otomatis di WASocket - incoming message IDs di-check
|
|
168
|
+
// Jika ID sudah pernah dilihat dalam 24 jam, skip
|
|
169
|
+
|
|
170
|
+
import { IdempotencyCache } from 'waliwa';
|
|
171
|
+
|
|
172
|
+
const cache = new IdempotencyCache(10000, 24 * 60 * 60 * 1000);
|
|
173
|
+
|
|
174
|
+
// Check duplicate
|
|
175
|
+
if (cache.isDuplicate(messageId)) {
|
|
176
|
+
console.log('Skipping duplicate message');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Filter batch
|
|
181
|
+
const uniqueMessages = cache.filterDuplicates(messages);
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
### 7. Group Events Missing
|
|
187
|
+
|
|
188
|
+
**Masalah:** Group participant events (join/leave) kadang tidak fired, atau fired multiple times rapidly.
|
|
189
|
+
|
|
190
|
+
**Solusi:** `GroupEventCoalescer` - merge rapid events dalam window 100ms.
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { GroupEventCoalescer } from 'waliwa';
|
|
194
|
+
|
|
195
|
+
const coalescer = new GroupEventCoalescer(
|
|
196
|
+
(events) => {
|
|
197
|
+
// Receive coalesced events
|
|
198
|
+
for (const event of events) {
|
|
199
|
+
console.log(`Group ${event.id}: ${event.action} ${event.participants.length} users`);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
100 // Window 100ms
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Add events
|
|
206
|
+
coalescer.addEvent({
|
|
207
|
+
id: 'group@g.us',
|
|
208
|
+
participants: ['user1@s.whatsapp.net'],
|
|
209
|
+
action: 'add',
|
|
210
|
+
timestamp: Date.now()
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Multiple rapid events akan di-merge
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
### 8. Media Upload Timeout
|
|
219
|
+
|
|
220
|
+
**Masalah:** Large files (>5MB) sering timeout saat upload, no resume support.
|
|
221
|
+
|
|
222
|
+
**Solusi:** `ChunkedMediaUploader` dengan chunking + resume.
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import { ChunkedMediaUploader } from 'waliwa';
|
|
226
|
+
|
|
227
|
+
const uploader = new ChunkedMediaUploader({
|
|
228
|
+
chunkSize: 1024 * 1024, // 1MB chunks
|
|
229
|
+
maxRetries: 3
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Upload dengan resume support
|
|
233
|
+
const uploadId = 'upload-123';
|
|
234
|
+
const fileBuffer = await readFile('large-video.mp4'); // 50MB
|
|
235
|
+
|
|
236
|
+
await uploader.uploadChunked(fileBuffer, uploadId, async (chunk, chunkIndex, totalChunks) => {
|
|
237
|
+
console.log(`Uploading chunk ${chunkIndex + 1}/${totalChunks}`);
|
|
238
|
+
// Upload chunk ke WhatsApp CDN
|
|
239
|
+
await uploadChunkToCDN(chunk, chunkIndex);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Jika upload interrupted, resume dari last successful chunk
|
|
243
|
+
const resumePoint = uploader.getResumePoint(uploadId);
|
|
244
|
+
console.log(`Resume from chunk ${resumePoint}`);
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
### 9. History Sync Incomplete
|
|
250
|
+
|
|
251
|
+
**Masalah:** Initial history sync tidak complete, messages lost saat bot start.
|
|
252
|
+
|
|
253
|
+
**Solusi:** `HistorySyncManager` dengan resume support.
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import { HistorySyncManager } from 'waliwa';
|
|
257
|
+
|
|
258
|
+
const syncManager = new HistorySyncManager();
|
|
259
|
+
|
|
260
|
+
// Start sync
|
|
261
|
+
syncManager.startSync();
|
|
262
|
+
|
|
263
|
+
// Track progress per chat
|
|
264
|
+
syncManager.trackProgress('user1@s.whatsapp.net', 'msg-123', Date.now());
|
|
265
|
+
|
|
266
|
+
// Get resume state untuk specific chat
|
|
267
|
+
const resume = syncManager.getResumeState('user1@s.whatsapp.net');
|
|
268
|
+
if (resume) {
|
|
269
|
+
// Request sync from lastMessageId onwards
|
|
270
|
+
await requestHistorySync('user1@s.whatsapp.net', resume.lastMessageId);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Complete sync
|
|
274
|
+
syncManager.completeSync();
|
|
275
|
+
|
|
276
|
+
// Persist untuk survive restart
|
|
277
|
+
const state = syncManager.serialize();
|
|
278
|
+
await fs.writeFile('./auth/sync-state.json', state);
|
|
279
|
+
|
|
280
|
+
// Restore on next start
|
|
281
|
+
const saved = await fs.readFile('./auth/sync-state.json', 'utf-8');
|
|
282
|
+
syncManager.deserialize(saved);
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
### 10. Smarter Reconnection Strategy
|
|
288
|
+
|
|
289
|
+
**Masalah:** Baileys pakai exponential backoff yang sama untuk semua scenarios. Flood server, dapat banned.
|
|
290
|
+
|
|
291
|
+
**Solusi:** `SmarterReconnector` dengan phased backoff.
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
import { SmarterReconnector } from 'waliwa';
|
|
295
|
+
|
|
296
|
+
const reconnector = new SmarterReconnector(
|
|
297
|
+
async () => {
|
|
298
|
+
// Reconnect logic
|
|
299
|
+
await sock.connect();
|
|
300
|
+
},
|
|
301
|
+
{ maxAttempts: 20 }
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
// Schedule next reconnect
|
|
305
|
+
reconnector.scheduleNext();
|
|
306
|
+
|
|
307
|
+
// Phases:
|
|
308
|
+
// Phase 1 (attempts 1-3): Fast retry - 1s, 2s, 4s (network blip)
|
|
309
|
+
// Phase 2 (attempts 4-6): Medium - 10s, 20s, 30s (server issues)
|
|
310
|
+
// Phase 3 (attempts 7+): Slow - 60s+ (long-term issues, with jitter)
|
|
311
|
+
|
|
312
|
+
console.log(reconnector.getCurrentPhase()); // 1, 2, or 3
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## All-in-One Usage
|
|
318
|
+
|
|
319
|
+
Semua fix aktif secara default di WASocket:
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
const sock = makeWASocket({
|
|
323
|
+
authState: state,
|
|
324
|
+
printQRInTerminal: true,
|
|
325
|
+
// Semua fix aktif otomatis:
|
|
326
|
+
// - Pre-key auto-regen
|
|
327
|
+
// - Message queue (race condition fix)
|
|
328
|
+
// - Idempotency cache (duplicate fix)
|
|
329
|
+
// - Heartbeat (connection hang fix)
|
|
330
|
+
// - Health monitor
|
|
331
|
+
// - Circuit breaker
|
|
332
|
+
// - Rate limit tracker
|
|
333
|
+
// - Message persistence
|
|
334
|
+
// - Webhook system
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Check system stats
|
|
338
|
+
console.log(sock.getSystemStats());
|
|
339
|
+
|
|
340
|
+
// Check health
|
|
341
|
+
console.log(sock.getHealth());
|
|
342
|
+
|
|
343
|
+
// Get SKDM stats
|
|
344
|
+
console.log(sock.getSKDMStats());
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Comparison: Baileys vs Waliwa
|
|
348
|
+
|
|
349
|
+
| Issue | Baileys | Waliwa |
|
|
350
|
+
|-------|---------|--------|
|
|
351
|
+
| Pre-key exhaustion | Manual regen required | ✅ Auto-regen |
|
|
352
|
+
| Race condition | No built-in queue | ✅ Per-recipient queue |
|
|
353
|
+
| Memory leak | Listeners accumulate | ✅ WeakEventEmitter |
|
|
354
|
+
| Connection hang | No detection | ✅ Heartbeat + dead detection |
|
|
355
|
+
| Decryption failure | Lost forever | ✅ Retry queue + prefetch |
|
|
356
|
+
| Duplicate messages | Process 2x | ✅ Idempotency cache |
|
|
357
|
+
| Group events | Missing/duplicate | ✅ Event coalescing |
|
|
358
|
+
| Media upload timeout | No resume | ✅ Chunked upload |
|
|
359
|
+
| History sync | Incomplete | ✅ Resume support |
|
|
360
|
+
| Reconnect strategy | Same backoff | ✅ Phased backoff |
|