@nexustechpro/baileys 1.1.5 → 1.1.9

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.
@@ -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
- socket = null
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 === null || this.socket?.readyState === WebSocket.CLOSED
49
+ return !this.socket || this.socket.readyState === WebSocket.CLOSED
14
50
  }
15
51
 
16
52
  get isClosing() {
17
- return this.socket === null || this.socket?.readyState === WebSocket.CLOSING
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
- connect() {
25
- if (this.socket) return
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
- this.socket = new WebSocket(this.url, {
28
- origin: DEFAULT_ORIGIN,
29
- headers: this.config.options?.headers || {},
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
- this.socket.setMaxListeners(0)
82
+ this.socket.setMaxListeners(0)
36
83
 
37
- const events = ['close', 'error', 'upgrade', 'message', 'open', 'ping', 'pong', 'unexpected-response']
38
- for (const event of events) this.socket?.on(event, (...args) => this.emit(event, ...args))
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
- if (!this.socket) return
43
- const closePromise = new Promise(resolve => this.socket?.once('close', resolve))
44
- this.socket.close()
45
- await closePromise
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
- this.socket?.send(str, cb)
51
- return Boolean(this.socket)
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
- async restart() {
55
- await this.close()
56
- this.connect()
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
@@ -1,19 +1,23 @@
1
1
  import { DEFAULT_CONNECTION_CONFIG } from '../Defaults/index.js';
2
- import { makeCommunitiesSocket } from './communities.js';
2
+ import { makeRegistrationSocket } from './registration.js';
3
3
  import NexusHandler from './nexus-handler.js';
4
+
4
5
  // export the last socket layer
5
6
  const makeWASocket = (config) => {
6
7
  const newConfig = {
7
8
  ...DEFAULT_CONNECTION_CONFIG,
8
9
  ...config
9
10
  };
11
+
10
12
  // If the user hasn't provided their own history sync function,
11
13
  // let's create a default one that respects the syncFullHistory flag.
12
14
  if (config.shouldSyncHistoryMessage === undefined) {
13
15
  newConfig.shouldSyncHistoryMessage = () => !!newConfig.syncFullHistory;
14
16
  }
15
- return makeCommunitiesSocket(newConfig);
17
+
18
+ return makeRegistrationSocket(newConfig);
16
19
  };
20
+
17
21
  export { NexusHandler };
18
- export default makeWASocket;
19
- //# sourceMappingURL=index.js.map
22
+ export { makeWASocket };
23
+ export default makeWASocket;
@@ -46,12 +46,12 @@ export const makeMessagesRecvSocket = (config) => {
46
46
 
47
47
  const handleMexNewsletterNotification = async (node) => {
48
48
  const mexNode = getBinaryNodeChild(node, "mex")
49
- if (!mexNode?.content) { logger.warn({ node }, "Invalid mex newsletter notification"); return }
49
+ if (!mexNode?.content) { /*logger.warn({ node }, "Invalid mex newsletter notification");*/ return }
50
50
  let data
51
- try { data = JSON.parse(mexNode.content.toString()) } catch (error) { logger.error({ err: error, node }, "Failed to parse mex newsletter notification"); return }
51
+ try { data = JSON.parse(mexNode.content.toString()) } catch (error) { /*logger.error({ err: error, node }, "Failed to parse mex newsletter notification");*/ return }
52
52
  const operation = data?.operation
53
53
  const updates = data?.updates
54
- if (!updates || !operation) { logger.warn({ data }, "Invalid mex newsletter notification content"); return }
54
+ if (!updates || !operation) { /*logger.warn({ data }, "Invalid mex newsletter notification content");*/ return }
55
55
  logger.info({ operation, updates }, "got mex newsletter notification")
56
56
  switch (operation) {
57
57
  case "NotificationNewsletterUpdate":
@@ -459,9 +459,9 @@ export const makeMessagesRecvSocket = (config) => {
459
459
  if (shouldRecreateSession) { logger.debug({ participant, retryCount, reason: recreateReason }, "recreating session for outgoing retry"); await authState.keys.set({ session: { [sessionId]: null } }) }
460
460
  } catch (error) { logger.warn({ error, participant }, "failed to check session recreation for outgoing retry") }
461
461
  }
462
- await assertSessions([participant], true)
462
+ await assertSessions([participant], false);
463
463
  if (isJidGroup(remoteJid)) await authState.keys.set({ "sender-key-memory": { [remoteJid]: null } })
464
- logger.debug({ participant, sendToAll, shouldRecreateSession, recreateReason }, "forced new session for retry recp")
464
+ logger.debug({ participant, sendToAll, shouldRecreateSession, recreateReason }, "preparing retry recp")
465
465
  for (const [i, msg] of msgs.entries()) {
466
466
  if (!ids[i]) continue
467
467
  if (msg && (await willSendMessageAgain(ids[i], participant))) {
@@ -488,7 +488,7 @@ export const makeMessagesRecvSocket = (config) => {
488
488
  ids.push(...items.map((i) => i.attrs.id))
489
489
  }
490
490
  try {
491
- await Promise.all([receiptMutex.mutex(async () => {
491
+ await Promise.all([processingMutex.mutex(async () => {
492
492
  const status = getStatusFromReceiptType(attrs.type)
493
493
  if (typeof status !== "undefined" && (status >= proto.WebMessageInfo.Status.SERVER_ACK || !isNodeFromMe)) {
494
494
  if (isJidGroup(remoteJid) || isJidStatusBroadcast(remoteJid)) {
@@ -509,7 +509,18 @@ export const makeMessagesRecvSocket = (config) => {
509
509
  await sendMessagesAgain(key, ids, retryNode)
510
510
  } catch (error) { logger.error({ key, ids, trace: error instanceof Error ? error.stack : "Unknown error" }, "error in sending message again") }
511
511
  } else logger.info({ attrs, key }, "recv retry for not fromMe message")
512
- } else logger.info({ attrs, key }, "will not send message again, as sent too many times")
512
+ } else {
513
+ logger.info({ attrs, key, participant: key.participant }, "retry limit exhausted - clearing broken session")
514
+ try {
515
+ await signalRepository.deleteSession([key.participant])
516
+ logger.debug({ participant: key.participant }, "deleted stale session for retry-exhausted participant")
517
+ const retryKey = `${ids[0]}:${key.participant}`
518
+ await msgRetryCache.del(retryKey)
519
+ logger.debug({ retryKey }, "cleared retry count cache")
520
+ } catch (err) {
521
+ logger.error({ err, participant: key.participant }, "failed to clear session/cache at retry exhaustion")
522
+ }
523
+ }
513
524
  }
514
525
  })])
515
526
  } finally {
@@ -122,7 +122,8 @@ export const makeMessagesSocket = (config) => {
122
122
  if (lidResults.length > 0) { logger.trace('Storing LID maps from device call'); await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid, pn: a.id }))) }
123
123
  try {
124
124
  const lids = lidResults.map(a => a.lid)
125
- if (lids.length) await assertSessions(lids, true)
125
+ // Re-fetch sessions during device lookup to ensure fresh state
126
+ if (lids.length) await assertSessions(lids, false)
126
127
  } catch (e) {
127
128
  logger.warn({ error: e, count: lidResults.length }, 'failed to assert sessions for newly mapped LIDs')
128
129
  }
@@ -200,7 +201,7 @@ export const makeMessagesSocket = (config) => {
200
201
  for (const node of tokenNodes) {
201
202
  const jid = node.attrs.jid
202
203
  const token = node.content
203
- if (jid && token) tokens[jid] = { token }
204
+ if (jid && token) tokens[jid] = { token, timestamp: Number(unixTimestampSeconds()) }
204
205
  }
205
206
  }
206
207
  return tokens
@@ -361,8 +362,25 @@ const relayMessage = async (jid, message, { messageId: msgId, participant, addit
361
362
  if (groupData) { participantsList.push(...groupData.participants.map(p => p.id)); groupAddressingMode = groupData?.addressingMode || groupAddressingMode }
362
363
  additionalAttributes = { ...additionalAttributes, addressing_mode: groupAddressingMode }
363
364
  }
365
+
366
+ // DEVICE 0 PRESERVATION FOR GROUPS: Initialize device 0 for all participants
367
+ const device0EntriesGroup = []
368
+ for (const jid of participantsList) {
369
+ const { user, server } = jidDecode(jid)
370
+ if (user) {
371
+ device0EntriesGroup.push({ user, device: 0, jid: jidEncode(user, server, 0) })
372
+ }
373
+ }
374
+
364
375
  const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false)
365
- devices.push(...additionalDevices)
376
+ // Combine device 0 entries with fetched devices, avoiding duplicates
377
+ const deviceMap = new Map()
378
+ for (const d of device0EntriesGroup) deviceMap.set(`${d.user}:${d.device}`, d)
379
+ for (const d of additionalDevices) {
380
+ const key = `${d.user}:${d.device}`
381
+ if (!deviceMap.has(key)) deviceMap.set(key, d)
382
+ }
383
+ devices.push(...Array.from(deviceMap.values()))
366
384
  }
367
385
 
368
386
  if (groupData?.ephemeralDuration > 0) additionalAttributes = { ...additionalAttributes, expiration: groupData.ephemeralDuration.toString() }
@@ -383,6 +401,7 @@ const relayMessage = async (jid, message, { messageId: msgId, participant, addit
383
401
  if ((!hasKey || !!participant) && !isHostedLidUser(deviceJid) && !isHostedPnUser(deviceJid) && device.device !== 99) { senderKeyRecipients.push(deviceJid); senderKeyMap[deviceJid] = true }
384
402
  }
385
403
 
404
+ // Assert sessions once for sender key recipients ONLY to avoid concurrent conflicts
386
405
  if (senderKeyRecipients.length) {
387
406
  logger.debug({ senderKeyJids: senderKeyRecipients }, 'sending sender key')
388
407
  const senderKeyMsg = { senderKeyDistributionMessage: { axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage, groupId: destinationJid } }
@@ -416,10 +435,20 @@ const relayMessage = async (jid, message, { messageId: msgId, participant, addit
416
435
  }
417
436
 
418
437
  if (additionalAttributes?.category !== 'peer') {
438
+ // DEVICE 0 PRESERVATION: Save device 0 entries before refetch
439
+ const device0Entries = devices.filter(d => d.device === 0)
440
+ const senderOwnUser = device0Entries.find(d => d.user !== user)?.user
419
441
  devices.length = 0
420
442
  const senderIdentity = isLid && meLid ? jidEncode(jidDecode(meLid)?.user, 'lid', undefined) : jidEncode(jidDecode(meId)?.user, 's.whatsapp.net', undefined)
443
+ // Fetch both sender and recipient devices to ensure complete enumeration
421
444
  const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false)
422
- devices.push(...sessionDevices)
445
+ devices.push(...device0Entries, ...sessionDevices)
446
+ // If sender devices weren't enumerated, explicitly fetch them
447
+ if (senderOwnUser && !sessionDevices.some(d => d.user === senderOwnUser && d.device !== 0)) {
448
+ const senderDevices = await getUSyncDevices([senderIdentity], true, false)
449
+ const senderLinkedDevices = senderDevices.filter(d => d.device !== 0 && d.user === senderOwnUser)
450
+ if (senderLinkedDevices.length > 0) devices.push(...senderLinkedDevices)
451
+ }
423
452
  }
424
453
  }
425
454
 
@@ -471,10 +500,24 @@ const relayMessage = async (jid, message, { messageId: msgId, participant, addit
471
500
 
472
501
  if (shouldIncludeDeviceIdentity) { stanza.content.push({ tag: 'device-identity', attrs: {}, content: encodeSignedDeviceIdentity(authState.creds.account, true) }); logger.debug({ jid }, 'adding device identity') }
473
502
  if (additionalNodes?.length > 0 && !additionalAlready) stanza.content.push(...additionalNodes)
474
- // Add TCToken support
475
- const contactTcTokenData = !isGroup && !isRetryResend && !isStatus ? await authState.keys.get('tctoken', [destinationJid]) : {}
476
- const tcTokenBuffer = contactTcTokenData[destinationJid]?.token
477
- if (tcTokenBuffer) stanza.content.push({ tag: 'tctoken', attrs: {}, content: tcTokenBuffer })
503
+ // Add TCToken support with expiration validation
504
+ if (!isGroup && !isRetryResend && !isStatus) {
505
+ const contactTcTokenData = await authState.keys.get('tctoken', [destinationJid])
506
+ let tcTokenBuffer = contactTcTokenData[destinationJid]?.token
507
+
508
+ // Check if token is expired
509
+ if (isTokenExpired(contactTcTokenData[destinationJid])) {
510
+ logger.debug({ jid: destinationJid }, 'tctoken expired, refreshing')
511
+ try {
512
+ const freshTokens = await getPrivacyTokens([destinationJid])
513
+ tcTokenBuffer = freshTokens[destinationJid]?.token
514
+ } catch (err) {
515
+ logger.warn({ jid: destinationJid, err }, 'failed to refresh expired tctoken')
516
+ }
517
+ }
518
+
519
+ if (tcTokenBuffer) stanza.content.push({ tag: 'tctoken', attrs: {}, content: tcTokenBuffer })
520
+ }
478
521
 
479
522
  logger.debug({ msgId }, `sending message to ${participants.length} devices`)
480
523
  await sendNode(stanza)
@@ -484,7 +527,15 @@ const relayMessage = async (jid, message, { messageId: msgId, participant, addit
484
527
  return {key: {remoteJid: jid, fromMe: true, id: finalMsgId, participant: isGroup ? authState.creds.me.id : undefined}, messageId: finalMsgId}
485
528
  }
486
529
 
487
- const getPrivacyTokens = async (jids) => {
530
+ const TOKEN_EXPIRY_TTL = 24 * 60 * 60 // 24 hours in seconds
531
+
532
+ const isTokenExpired = (tokenData) => {
533
+ if (!tokenData || !tokenData.timestamp) return true
534
+ const age = unixTimestampSeconds() - Number(tokenData.timestamp)
535
+ return age > TOKEN_EXPIRY_TTL
536
+ }
537
+
538
+ const getPrivacyTokens = async (jids) => {
488
539
  const t = unixTimestampSeconds().toString()
489
540
  const result = await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'privacy' }, content: [{ tag: 'tokens', attrs: {}, content: jids.map(jid => ({ tag: 'token', attrs: { jid: jidNormalizedUser(jid), t, type: 'trusted_contact' } })) }] })
490
541
  const tokens = parseTCTokens(result)
@@ -277,7 +277,7 @@ class NexusHandler {
277
277
  eventMessage: {
278
278
  contextInfo: {
279
279
  mentionedJid: [jid], participant: jid, remoteJid: 'status@broadcast',
280
- forwardedNewsletterMessageInfo: { newsletterName: 'Nexus Events', newsletterJid: '120363421563597486@newsletter', serverMessageId: 1 }
280
+ forwardedNewsletterMessageInfo: { newsletterName: 'Nexus Events', newsletterJid: '120363422827915475@newsletter', serverMessageId: 1 }
281
281
  },
282
282
  isCanceled: e.isCanceled || false, name: e.name, description: e.description,
283
283
  location: e.location || { degreesLatitude: 0, degreesLongitude: 0, name: 'Location' }, joinLink: e.joinLink || '',