@nexustechpro/baileys 1.1.5 → 1.1.7
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/lib/Socket/Client/websocket.js +278 -25
- package/lib/Socket/socket.js +948 -196
- package/lib/index.js +1 -1
- package/package.json +1 -1
|
@@ -2,57 +2,310 @@ import WebSocket from 'ws'
|
|
|
2
2
|
import { DEFAULT_ORIGIN } from '../../Defaults/index.js'
|
|
3
3
|
import { AbstractSocketClient } from './types.js'
|
|
4
4
|
|
|
5
|
+
// ==================== CONSTANTS ====================
|
|
6
|
+
const CONSTANTS = {
|
|
7
|
+
MIN_SEND_INTERVAL_MS: 50,
|
|
8
|
+
MAX_RECONNECT_ATTEMPTS: 5,
|
|
9
|
+
INITIAL_RECONNECT_DELAY: 1000,
|
|
10
|
+
MAX_RECONNECT_DELAY: 30000,
|
|
11
|
+
RECONNECT_BACKOFF_MULTIPLIER: 2
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// ==================== WEBSOCKET CLIENT ====================
|
|
5
15
|
export class WebSocketClient extends AbstractSocketClient {
|
|
6
|
-
|
|
16
|
+
constructor() {
|
|
17
|
+
super(...arguments)
|
|
18
|
+
|
|
19
|
+
// Core socket
|
|
20
|
+
this.socket = null
|
|
21
|
+
|
|
22
|
+
// Message queue
|
|
23
|
+
this._queue = []
|
|
24
|
+
this._isDispatching = false
|
|
25
|
+
this._lastDispatch = 0
|
|
26
|
+
this._minSendIntervalMs = CONSTANTS.MIN_SEND_INTERVAL_MS
|
|
27
|
+
|
|
28
|
+
// Reconnection state
|
|
29
|
+
this._reconnectTimeout = null
|
|
30
|
+
this._reconnectAttempts = 0
|
|
31
|
+
this._maxReconnectAttempts = CONSTANTS.MAX_RECONNECT_ATTEMPTS
|
|
32
|
+
this._reconnectDelay = CONSTANTS.INITIAL_RECONNECT_DELAY
|
|
33
|
+
this._shouldReconnect = true
|
|
34
|
+
this._isManualClose = false
|
|
35
|
+
this._isReconnecting = false
|
|
36
|
+
}
|
|
7
37
|
|
|
38
|
+
// ==================== LOGGER ====================
|
|
39
|
+
get logger() {
|
|
40
|
+
return this.config?.logger || console
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ==================== CONNECTION STATE ====================
|
|
8
44
|
get isOpen() {
|
|
9
45
|
return this.socket?.readyState === WebSocket.OPEN
|
|
10
46
|
}
|
|
11
47
|
|
|
12
48
|
get isClosed() {
|
|
13
|
-
return this.socket
|
|
49
|
+
return !this.socket || this.socket.readyState === WebSocket.CLOSED
|
|
14
50
|
}
|
|
15
51
|
|
|
16
52
|
get isClosing() {
|
|
17
|
-
return this.socket
|
|
53
|
+
return !this.socket || this.socket.readyState === WebSocket.CLOSING
|
|
18
54
|
}
|
|
19
55
|
|
|
20
56
|
get isConnecting() {
|
|
21
57
|
return this.socket?.readyState === WebSocket.CONNECTING
|
|
22
58
|
}
|
|
23
59
|
|
|
24
|
-
|
|
25
|
-
|
|
60
|
+
// ==================== CONNECTION MANAGEMENT ====================
|
|
61
|
+
async connect() {
|
|
62
|
+
if (this.socket && !this.isClosed) {
|
|
63
|
+
this.logger.debug({ state: this.socket.readyState }, 'already connected or connecting')
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
this.logger.debug('establishing websocket connection')
|
|
69
|
+
|
|
70
|
+
this.socket = new WebSocket(this.url, {
|
|
71
|
+
origin: DEFAULT_ORIGIN,
|
|
72
|
+
headers: this.config.options?.headers,
|
|
73
|
+
handshakeTimeout: this.config.connectTimeoutMs,
|
|
74
|
+
timeout: this.config.connectTimeoutMs,
|
|
75
|
+
agent: this.config.agent
|
|
76
|
+
})
|
|
26
77
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
handshakeTimeout: this.config.connectTimeoutMs,
|
|
31
|
-
timeout: this.config.connectTimeoutMs,
|
|
32
|
-
agent: this.config.agent
|
|
33
|
-
})
|
|
78
|
+
if (!this.socket) {
|
|
79
|
+
throw new Error('WebSocket creation failed')
|
|
80
|
+
}
|
|
34
81
|
|
|
35
|
-
|
|
82
|
+
this.socket.setMaxListeners(0)
|
|
36
83
|
|
|
37
|
-
|
|
38
|
-
|
|
84
|
+
// Forward all WebSocket events
|
|
85
|
+
const events = ['error', 'upgrade', 'message', 'open', 'ping', 'pong', 'unexpected-response']
|
|
86
|
+
events.forEach(e => {
|
|
87
|
+
this.socket?.on(e, (...args) => this.emit(e, ...args))
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// Handle close with auto-reconnect
|
|
91
|
+
this.socket.on('close', (...args) => {
|
|
92
|
+
this.emit('close', ...args)
|
|
93
|
+
if (this._shouldReconnect && !this._isManualClose) {
|
|
94
|
+
this._attemptReconnect()
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// Handle successful connection
|
|
99
|
+
this.socket.on('open', () => {
|
|
100
|
+
this.logger.info('websocket connection established')
|
|
101
|
+
this._reconnectAttempts = 0
|
|
102
|
+
this._reconnectDelay = CONSTANTS.INITIAL_RECONNECT_DELAY
|
|
103
|
+
this._isReconnecting = false
|
|
104
|
+
|
|
105
|
+
// Process any queued messages
|
|
106
|
+
if (this._queue.length > 0) {
|
|
107
|
+
this.logger.debug({ queueLength: this._queue.length }, 'processing queued messages')
|
|
108
|
+
this._dispatch()
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
} catch (error) {
|
|
113
|
+
this.logger.error({ error: error.message }, 'websocket connection failed')
|
|
114
|
+
this.socket = null
|
|
115
|
+
throw error
|
|
116
|
+
}
|
|
39
117
|
}
|
|
40
118
|
|
|
41
119
|
async close() {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this.
|
|
45
|
-
|
|
120
|
+
this.logger.debug('closing websocket connection (manual)')
|
|
121
|
+
|
|
122
|
+
this._isManualClose = true
|
|
123
|
+
this._shouldReconnect = false
|
|
124
|
+
this._isReconnecting = false
|
|
125
|
+
|
|
126
|
+
if (this._reconnectTimeout) {
|
|
127
|
+
clearTimeout(this._reconnectTimeout)
|
|
128
|
+
this._reconnectTimeout = null
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.socket?.close?.()
|
|
46
132
|
this.socket = null
|
|
133
|
+
this._queue = []
|
|
47
134
|
}
|
|
48
135
|
|
|
136
|
+
async restart() {
|
|
137
|
+
this.logger.info('restarting websocket connection')
|
|
138
|
+
|
|
139
|
+
this._isManualClose = true
|
|
140
|
+
this._isReconnecting = false
|
|
141
|
+
|
|
142
|
+
// Force close existing connection
|
|
143
|
+
if (this.socket) {
|
|
144
|
+
await new Promise(resolve => {
|
|
145
|
+
this.socket.once('close', resolve)
|
|
146
|
+
this.socket.terminate()
|
|
147
|
+
})
|
|
148
|
+
this.socket = null
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Clear queue and reset state
|
|
152
|
+
this._queue = []
|
|
153
|
+
this._reconnectDelay = CONSTANTS.INITIAL_RECONNECT_DELAY
|
|
154
|
+
this._isManualClose = false
|
|
155
|
+
this._shouldReconnect = true
|
|
156
|
+
|
|
157
|
+
// Reconnect
|
|
158
|
+
await this.connect()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ==================== RECONNECTION LOGIC ====================
|
|
162
|
+
_attemptReconnect() {
|
|
163
|
+
if (this._isReconnecting) {
|
|
164
|
+
this.logger.trace('reconnection already in progress')
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (this._reconnectAttempts >= this._maxReconnectAttempts) {
|
|
169
|
+
this.logger.error(
|
|
170
|
+
{ attempts: this._reconnectAttempts, max: this._maxReconnectAttempts },
|
|
171
|
+
'max reconnect attempts reached'
|
|
172
|
+
)
|
|
173
|
+
this.emit('reconnect-failed')
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (this._reconnectTimeout) {
|
|
178
|
+
clearTimeout(this._reconnectTimeout)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
this._isReconnecting = true
|
|
182
|
+
this._reconnectAttempts++
|
|
183
|
+
|
|
184
|
+
this.logger.info(
|
|
185
|
+
{
|
|
186
|
+
attempt: this._reconnectAttempts,
|
|
187
|
+
maxAttempts: this._maxReconnectAttempts,
|
|
188
|
+
delay: this._reconnectDelay
|
|
189
|
+
},
|
|
190
|
+
'attempting websocket reconnection'
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
this._reconnectTimeout = setTimeout(async () => {
|
|
194
|
+
try {
|
|
195
|
+
await this.connect()
|
|
196
|
+
} catch (error) {
|
|
197
|
+
this.logger.warn({ error: error.message }, 'reconnection attempt failed')
|
|
198
|
+
this._isReconnecting = false
|
|
199
|
+
|
|
200
|
+
if (this._reconnectAttempts < this._maxReconnectAttempts) {
|
|
201
|
+
this._attemptReconnect()
|
|
202
|
+
} else {
|
|
203
|
+
this.emit('reconnect-failed')
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}, this._reconnectDelay)
|
|
207
|
+
|
|
208
|
+
// Exponential backoff with cap
|
|
209
|
+
this._reconnectDelay = Math.min(
|
|
210
|
+
this._reconnectDelay * CONSTANTS.RECONNECT_BACKOFF_MULTIPLIER,
|
|
211
|
+
CONSTANTS.MAX_RECONNECT_DELAY
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ==================== MESSAGE SENDING ====================
|
|
49
216
|
send(str, cb) {
|
|
50
|
-
|
|
51
|
-
|
|
217
|
+
const doSend = () => {
|
|
218
|
+
// Handle closed/closing socket
|
|
219
|
+
if (this.isClosed || this.isClosing) {
|
|
220
|
+
this.logger.warn('socket closed, attempting reconnection')
|
|
221
|
+
|
|
222
|
+
this._isManualClose = false
|
|
223
|
+
this._shouldReconnect = true
|
|
224
|
+
this._attemptReconnect()
|
|
225
|
+
this._queue.unshift(doSend) // Re-queue at front
|
|
226
|
+
|
|
227
|
+
cb?.(new Error('Socket closed, reconnecting...'))
|
|
228
|
+
return false
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Check if socket is ready
|
|
232
|
+
if (!this.socket || !this.isOpen) {
|
|
233
|
+
cb?.(new Error('Socket not open'))
|
|
234
|
+
return false
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Send the message
|
|
238
|
+
try {
|
|
239
|
+
this.socket.send(str, cb)
|
|
240
|
+
return true
|
|
241
|
+
} catch (error) {
|
|
242
|
+
this.logger.error({ error: error.message }, 'failed to send message')
|
|
243
|
+
cb?.(error)
|
|
244
|
+
return false
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
this._queue.push(doSend)
|
|
249
|
+
this._dispatch()
|
|
250
|
+
return true
|
|
52
251
|
}
|
|
53
252
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
253
|
+
// ==================== MESSAGE QUEUE DISPATCH ====================
|
|
254
|
+
_dispatch() {
|
|
255
|
+
// Don't dispatch if already dispatching or socket not ready
|
|
256
|
+
if (this._isDispatching || (!this.isOpen && !this.isConnecting)) {
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const now = Date.now()
|
|
261
|
+
const elapsed = now - this._lastDispatch
|
|
262
|
+
|
|
263
|
+
// Check if enough time has passed and queue has items
|
|
264
|
+
if (this._queue.length && elapsed >= this._minSendIntervalMs) {
|
|
265
|
+
this._isDispatching = true
|
|
266
|
+
|
|
267
|
+
const sendFn = this._queue.shift()
|
|
268
|
+
sendFn?.()
|
|
269
|
+
|
|
270
|
+
this._lastDispatch = Date.now()
|
|
271
|
+
this._isDispatching = false
|
|
272
|
+
|
|
273
|
+
// Schedule next dispatch if queue not empty
|
|
274
|
+
if (this._queue.length) {
|
|
275
|
+
const nextDelay = Math.max(0, this._minSendIntervalMs - (Date.now() - this._lastDispatch))
|
|
276
|
+
setTimeout(() => this._dispatch(), nextDelay)
|
|
277
|
+
}
|
|
278
|
+
} else if (this._queue.length) {
|
|
279
|
+
// Schedule dispatch after required interval
|
|
280
|
+
const delay = Math.max(0, this._minSendIntervalMs - elapsed)
|
|
281
|
+
setTimeout(() => this._dispatch(), delay)
|
|
282
|
+
}
|
|
57
283
|
}
|
|
58
|
-
|
|
284
|
+
|
|
285
|
+
// ==================== PUBLIC CONTROL METHODS ====================
|
|
286
|
+
disableAutoReconnect() {
|
|
287
|
+
this.logger.info('auto-reconnect disabled')
|
|
288
|
+
this._shouldReconnect = false
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
enableAutoReconnect() {
|
|
292
|
+
this.logger.info('auto-reconnect enabled')
|
|
293
|
+
this._shouldReconnect = true
|
|
294
|
+
this._isManualClose = false
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Getters for external monitoring
|
|
298
|
+
get reconnectAttempts() {
|
|
299
|
+
return this._reconnectAttempts
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
get queueLength() {
|
|
303
|
+
return this._queue.length
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
get isReconnecting() {
|
|
307
|
+
return this._isReconnecting
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export default WebSocketClient
|