@nexustechpro/baileys 1.0.8 → 1.1.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/README.md CHANGED
@@ -733,42 +733,6 @@ await sock.sendMessage(jid, {
733
733
  })
734
734
  ```
735
735
 
736
- #### Pin Message
737
- ```javascript
738
- await sock.sendMessage(jid, {
739
- pin: {
740
- type: 1, // 0 to remove
741
- time: 86400, // 24 hours in seconds
742
- key: message.key
743
- }
744
- })
745
- ```
746
-
747
- **Pin Time Options:**
748
-
749
- | Time | Seconds |
750
- |------|-----------|
751
- | 24h | 86,400 |
752
- | 7d | 604,800 |
753
- | 30d | 2,592,000 |
754
-
755
- #### Keep Message
756
- ```javascript
757
- await sock.sendMessage(jid, {
758
- keep: message.key,
759
- type: 1, // 2 to unpin
760
- time: 86400
761
- })
762
- ```
763
-
764
- **Keep Time Options:**
765
-
766
- | Time | Seconds |
767
- |------|-----------|
768
- | 24h | 86,400 |
769
- | 7d | 604,800 |
770
- | 30d | 2,592,000 |
771
-
772
736
  #### Poll Message
773
737
  ```javascript
774
738
  await sock.sendMessage(jid, {
@@ -1373,6 +1337,51 @@ await sock.chatModify({ pin: true }, jid)
1373
1337
  await sock.chatModify({ pin: false }, jid)
1374
1338
  ```
1375
1339
 
1340
+
1341
+ #### Pin Message
1342
+ ```javascript
1343
+ // Method 1: Using boolean (requires quoted message)
1344
+ await sock.sendMessage(jid, { pin: true }, { quoted: message })
1345
+
1346
+ // Method 2: Using pin object with message key
1347
+ await sock.sendMessage(jid, {
1348
+ pin: {
1349
+ key: message.key // or stanzaId, id, etc.
1350
+ }
1351
+ })
1352
+
1353
+ // Unpin message
1354
+ await sock.sendMessage(jid, {
1355
+ pin: {
1356
+ key: message.key,
1357
+ unpin: true
1358
+ }
1359
+ })
1360
+
1361
+ // Alternative unpin syntax
1362
+ await sock.sendMessage(jid, { pin: false }, { quoted: message })
1363
+ ```
1364
+
1365
+ #### Keep Message
1366
+ ```javascript
1367
+ await sock.sendMessage(jid, {
1368
+ keep: message.key,
1369
+ type: 1 // 1 to keep, 2 to unkeep
1370
+ })
1371
+ ```
1372
+
1373
+ **Pin/Keep Details:**
1374
+
1375
+ | Operation | Method | Syntax |
1376
+ |-----------|--------|--------|
1377
+ | Pin (quoted) | Boolean | `{ pin: true }` with `{ quoted: message }` |
1378
+ | Pin (object) | Object | `{ pin: { key: message.key } }` |
1379
+ | Unpin (quoted) | Boolean | `{ pin: false }` with `{ quoted: message }` |
1380
+ | Unpin (object) | Object | `{ pin: { key: message.key, unpin: true } }` |
1381
+ | Keep | Key | `{ keep: message.key, type: 1 }` |
1382
+ | Unkeep | Key | `{ keep: message.key, type: 2 }` |
1383
+
1384
+
1376
1385
  ### Delete Chat
1377
1386
  ```javascript
1378
1387
  const lastMsg = await getLastMessageInChat(jid)
@@ -107,7 +107,7 @@ export const MEDIA_HKDF_KEY_MAPPING = {
107
107
  };
108
108
  export const MEDIA_KEYS = Object.keys(MEDIA_PATH_MAP);
109
109
  export const MIN_PREKEY_COUNT = 5;
110
- export const INITIAL_PREKEY_COUNT = 812;
110
+ export const INITIAL_PREKEY_COUNT = 95; // Changed from 812 to 95
111
111
  export const UPLOAD_TIMEOUT = 30000; // 30 seconds
112
112
  export const MIN_UPLOAD_INTERVAL = 5000; // 5 seconds minimum between uploads
113
113
  export const DEFAULT_CACHE_TTLS = {
@@ -104,14 +104,30 @@ export function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
104
104
  }
105
105
  },
106
106
 
107
- deleteSession: jids => jids.length && txn(() => parsedKeys.set({ session: Object.fromEntries(jids.map(j => [jidToAddr(j).toString(), null])) }), `del-${jids.length}`),
107
+ deleteSession: jids => jids.length && txn(async () => {
108
+ const sessionAddrs = jids.map(j => jidToAddr(j).toString())
109
+ // Load batched sessions
110
+ const batchData = await parsedKeys.get("session", ["_index"])
111
+ const sessionBatch = batchData?.['_index'] || {}
112
+
113
+ // Remove the specified sessions
114
+ sessionAddrs.forEach(addr => {
115
+ delete sessionBatch[addr]
116
+ })
117
+
118
+ // Store updated batch
119
+ await parsedKeys.set({ session: { "_index": sessionBatch } })
120
+ }, `del-${jids.length}`),
108
121
 
109
122
  migrateSession: async (fromJid, toJid) => {
110
123
  if (!fromJid || (!isLidUser(toJid) && !isHostedLidUser(toJid))) return { migrated: 0, skipped: 0, total: 0 }
111
124
  if (!isPnUser(fromJid) && !isHostedPnUser(fromJid)) return { migrated: 0, skipped: 0, total: 1 }
112
125
 
113
126
  const { user } = jidDecode(fromJid)
114
- const { [user]: userDevices } = await parsedKeys.get("device-list", [user])
127
+ // Load device-list from batched storage
128
+ const batchData = await parsedKeys.get("device-list", ["_index"])
129
+ const deviceListBatch = batchData?.['_index'] || {}
130
+ const userDevices = deviceListBatch[user]
115
131
  if (!userDevices?.length) return { migrated: 0, skipped: 0, total: 0 }
116
132
 
117
133
  const { device: fromDevice } = jidDecode(fromJid)
@@ -119,41 +135,43 @@ export function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
119
135
  if (!userDevices.includes(fromDeviceStr)) userDevices.push(fromDeviceStr)
120
136
 
121
137
  const uncachedDevices = userDevices.filter(d => !migratedCache.has(`${user}.${d}`))
122
- const deviceSessionKeys = uncachedDevices.map(d => `${user}.${d}`)
123
- const existingSessions = await parsedKeys.get("session", deviceSessionKeys)
124
-
125
- const deviceJids = Object.entries(existingSessions)
126
- .filter(([_, s]) => s)
127
- .map(([k]) => {
128
- const [_, device] = k.split(".")
129
- const num = Number.parseInt(device)
130
- return num === 0 ? `${user}@s.whatsapp.net` : num === 99 ? `${user}:99@hosted` : `${user}:${num}@s.whatsapp.net`
138
+
139
+ // Load batched sessions
140
+ const sessionBatchData = await parsedKeys.get("session", ["_index"])
141
+ const sessionBatch = sessionBatchData?.['_index'] || {}
142
+
143
+ const deviceJids = uncachedDevices
144
+ .map(d => {
145
+ const num = Number.parseInt(d)
146
+ const addrStr = num === 0 ? `${user}.0` : `${user}.${d}`
147
+ return { addr: addrStr, jid: num === 0 ? `${user}@s.whatsapp.net` : num === 99 ? `${user}:99@hosted` : `${user}:${num}@s.whatsapp.net` }
131
148
  })
149
+ .filter(({ addr }) => sessionBatch[addr])
132
150
 
133
151
  return txn(async () => {
134
- const pnAddrStrs = Array.from(new Set(deviceJids.map(jid => jidToAddr(jid).toString())))
135
- const pnSessions = await parsedKeys.get("session", pnAddrStrs)
136
-
137
- const updates = {}
152
+ const pnAddrStrs = Array.from(new Set(deviceJids.map(d => jidToAddr(d.jid).toString())))
153
+ const updatedBatch = { ...sessionBatch }
138
154
  let migrated = 0
139
155
 
140
- for (const jid of deviceJids) {
156
+ for (const { jid } of deviceJids) {
141
157
  const pnAddr = jidToAddr(jid).toString()
142
158
  const lidAddr = jidToAddr(transferDevice(jid, toJid)).toString()
143
- const pnSession = pnSessions[pnAddr]
159
+ const pnSession = updatedBatch[pnAddr]
144
160
 
145
161
  if (pnSession) {
146
162
  const sess = libsignal.SessionRecord.deserialize(pnSession)
147
163
  if (sess.haveOpenSession()) {
148
- updates[lidAddr] = sess.serialize()
149
- updates[pnAddr] = null
164
+ updatedBatch[lidAddr] = sess.serialize()
165
+ delete updatedBatch[pnAddr]
150
166
  migrated++
151
167
  migratedCache.set(`${user}.${jidDecode(jid).device || 0}`, true)
152
168
  }
153
169
  }
154
170
  }
155
171
 
156
- if (Object.keys(updates).length > 0) await parsedKeys.set({ session: updates })
172
+ if (migrated > 0) {
173
+ await parsedKeys.set({ session: { "_index": updatedBatch } })
174
+ }
157
175
  return { migrated, skipped: deviceJids.length - migrated, total: deviceJids.length }
158
176
  }, `migrate-${deviceJids.length}`)
159
177
  },
@@ -176,7 +194,10 @@ function signalStorage({ creds, keys }, lidMapping, logger) {
176
194
  loadSession: async id => {
177
195
  try {
178
196
  const addr = await resolveLID(id)
179
- const { [addr]: sess } = await keys.get("session", [addr])
197
+ // Load from batched session storage
198
+ const batchData = await keys.get("session", ["_index"])
199
+ const sessionBatch = batchData?.['_index'] || {}
200
+ const sess = sessionBatch[addr]
180
201
  return sess ? libsignal.SessionRecord.deserialize(sess) : null
181
202
  } catch (e) {
182
203
  logger?.error?.(`[Signal] Load session: ${e.message}`)
@@ -186,7 +207,22 @@ function signalStorage({ creds, keys }, lidMapping, logger) {
186
207
 
187
208
  storeSession: async (id, session) => {
188
209
  const addr = await resolveLID(id)
189
- await keys.set({ session: { [addr]: session.serialize() } })
210
+ // Store in batched session storage
211
+ const existingData = await keys.get("session", ["_index"])
212
+ const sessionBatch = existingData?.['_index'] || {}
213
+
214
+ // Add/update the session
215
+ sessionBatch[addr] = session.serialize()
216
+
217
+ // Keep only the most recent 1000 sessions to prevent unlimited growth
218
+ const sessionKeys = Object.keys(sessionBatch).sort()
219
+ const recentSessions = sessionKeys.slice(-1000)
220
+ const trimmedBatch = {}
221
+ recentSessions.forEach(key => {
222
+ trimmedBatch[key] = sessionBatch[key]
223
+ })
224
+
225
+ await keys.set({ session: { "_index": trimmedBatch } })
190
226
  },
191
227
 
192
228
  isTrustedIdentity: () => true,
@@ -282,6 +282,9 @@ export const makeGroupsSocket = (config) => {
282
282
  };
283
283
  export const extractGroupMetadata = (result) => {
284
284
  const group = getBinaryNodeChild(result, 'group');
285
+ if (!group) {
286
+ throw new Error('Group node not found in result');
287
+ }
285
288
  const descChild = getBinaryNodeChild(group, 'description');
286
289
  let desc;
287
290
  let descId;
@@ -68,6 +68,7 @@ export const makeMessagesRecvSocket = (config) => {
68
68
  uploadPreKeys,
69
69
  sendPeerDataOperationMessage,
70
70
  messageRetryManager,
71
+ triggerPreKeyCheck,
71
72
  } = sock
72
73
  /** this mutex ensures that each retryRequest will wait for the previous one to finish */
73
74
  const retryMutex = makeMutex()
@@ -141,7 +142,7 @@ export const makeMessagesRecvSocket = (config) => {
141
142
  const handleMexNewsletterNotification = async (node) => {
142
143
  const mexNode = getBinaryNodeChild(node, "mex")
143
144
  if (!mexNode?.content) {
144
- logger.warn({ node }, "Invalid mex newsletter notification")
145
+ //logger.warn({ node }, "Invalid mex newsletter notification")
145
146
  return
146
147
  }
147
148
  let data
@@ -334,157 +335,174 @@ export const makeMessagesRecvSocket = (config) => {
334
335
  await query(stanza)
335
336
  }
336
337
  const sendRetryRequest = async (node, forceIncludeKeys = false) => {
337
- const { fullMessage } = decodeMessageNode(node, authState.creds.me.id, authState.creds.me.lid || "")
338
- const { key: msgKey } = fullMessage
339
- const msgId = msgKey.id
340
- if (messageRetryManager) {
341
- // Check if we've exceeded max retries using the new system
342
- if (messageRetryManager.hasExceededMaxRetries(msgId)) {
343
- logger.debug({ msgId }, "reached retry limit with new retry manager, clearing")
344
- messageRetryManager.markRetryFailed(msgId)
345
- return
346
- }
347
- // Increment retry count using new system
348
- const retryCount = messageRetryManager.incrementRetryCount(msgId)
349
- // Use the new retry count for the rest of the logic
350
- const key = `${msgId}:${msgKey?.participant}`
351
- msgRetryCache.set(key, retryCount)
352
- } else {
353
- // Fallback to old system
354
- const key = `${msgId}:${msgKey?.participant}`
355
- let retryCount = (await msgRetryCache.get(key)) || 0
356
- if (retryCount >= maxMsgRetryCount) {
357
- logger.debug({ retryCount, msgId }, "reached retry limit, clearing")
358
- msgRetryCache.del(key)
359
- return
360
- }
361
- retryCount += 1
362
- await msgRetryCache.set(key, retryCount)
338
+ const { fullMessage } = decodeMessageNode(node, authState.creds.me.id, authState.creds.me.lid || "")
339
+ const { key: msgKey } = fullMessage
340
+ const msgId = msgKey.id
341
+
342
+ if (messageRetryManager) {
343
+ if (messageRetryManager.hasExceededMaxRetries(msgId)) {
344
+ logger.debug({ msgId }, "reached retry limit with new retry manager, clearing")
345
+ messageRetryManager.markRetryFailed(msgId)
346
+ return
363
347
  }
348
+ const retryCount = messageRetryManager.incrementRetryCount(msgId)
364
349
  const key = `${msgId}:${msgKey?.participant}`
365
- const retryCount = (await msgRetryCache.get(key)) || 1
366
- const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds
367
- const fromJid = node.attrs.from
368
- // Check if we should recreate the session
369
- let shouldRecreateSession = false
370
- let recreateReason = ""
371
- if (enableAutoSessionRecreation && messageRetryManager) {
372
- try {
373
- // Check if we have a session with this JID
374
- const sessionId = signalRepository.jidToSignalProtocolAddress(fromJid)
375
- const hasSession = await signalRepository.validateSession(fromJid)
376
- const result = messageRetryManager.shouldRecreateSession(fromJid, retryCount, hasSession.exists)
377
- shouldRecreateSession = result.recreate
378
- recreateReason = result.reason
379
- if (shouldRecreateSession) {
380
- logger.debug({ fromJid, retryCount, reason: recreateReason }, "recreating session for retry")
381
- // Delete existing session to force recreation
382
- await authState.keys.set({ session: { [sessionId]: null } })
383
- forceIncludeKeys = true
350
+ msgRetryCache.set(key, retryCount)
351
+ } else {
352
+ const key = `${msgId}:${msgKey?.participant}`
353
+ let retryCount = (await msgRetryCache.get(key)) || 0
354
+ if (retryCount >= maxMsgRetryCount) {
355
+ logger.debug({ retryCount, msgId }, "reached retry limit, clearing")
356
+ msgRetryCache.del(key)
357
+ return
358
+ }
359
+ retryCount += 1
360
+ await msgRetryCache.set(key, retryCount)
361
+ }
362
+
363
+ const key = `${msgId}:${msgKey?.participant}`
364
+ const retryCount = (await msgRetryCache.get(key)) || 1
365
+ const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds
366
+ const fromJid = node.attrs.from
367
+
368
+ // ENHANCED: Check both PN and potential LID for session
369
+ let shouldRecreateSession = false
370
+ let recreateReason = ""
371
+
372
+ if (enableAutoSessionRecreation && messageRetryManager) {
373
+ try {
374
+ const sessionId = signalRepository.jidToSignalProtocolAddress(fromJid)
375
+ const hasSession = await signalRepository.validateSession(fromJid)
376
+
377
+ // Also check if LID mapping exists and session should be under LID
378
+ let lidJid = null
379
+ if (isPnUser(fromJid)) {
380
+ lidJid = await signalRepository.lidMapping.getLIDForPN(fromJid)
381
+ }
382
+
383
+ const result = messageRetryManager.shouldRecreateSession(fromJid, retryCount, hasSession.exists)
384
+ shouldRecreateSession = result.recreate
385
+ recreateReason = result.reason
386
+
387
+ if (shouldRecreateSession) {
388
+ logger.debug({ fromJid, lidJid, retryCount, reason: recreateReason }, "recreating session for retry")
389
+
390
+ // Delete both PN and LID sessions to force clean rebuild
391
+ await authState.keys.set({ session: { [sessionId]: null } })
392
+ if (lidJid) {
393
+ const lidSessionId = signalRepository.jidToSignalProtocolAddress(lidJid)
394
+ await authState.keys.set({ session: { [lidSessionId]: null } })
384
395
  }
385
- } catch (error) {
386
- logger.warn({ error, fromJid }, "failed to check session recreation")
396
+
397
+ forceIncludeKeys = true
387
398
  }
399
+ } catch (error) {
400
+ logger.warn({ error, fromJid }, "failed to check session recreation")
388
401
  }
389
- if (retryCount <= 2) {
390
- // Use new retry manager for phone requests if available
391
- if (messageRetryManager) {
392
- // Schedule phone request with delay (like whatsmeow)
393
- messageRetryManager.schedulePhoneRequest(msgId, async () => {
394
- try {
395
- const requestId = await requestPlaceholderResend(msgKey)
396
- logger.debug(
397
- `sendRetryRequest: requested placeholder resend (${requestId}) for message ${msgId} (scheduled)`,
398
- )
399
- } catch (error) {
400
- logger.warn({ error, msgId }, "failed to send scheduled phone request")
401
- }
402
- })
403
- } else {
404
- // Fallback to immediate request
405
- const msgId = await requestPlaceholderResend(msgKey)
406
- logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`)
407
- }
402
+ }
403
+
404
+ // Rest of the function remains the same...
405
+ if (retryCount <= 2) {
406
+ if (messageRetryManager) {
407
+ messageRetryManager.schedulePhoneRequest(msgId, async () => {
408
+ try {
409
+ const requestId = await requestPlaceholderResend(msgKey)
410
+ logger.debug(
411
+ `sendRetryRequest: requested placeholder resend (${requestId}) for message ${msgId} (scheduled)`,
412
+ )
413
+ } catch (error) {
414
+ logger.warn({ error, msgId }, "failed to send scheduled phone request")
415
+ }
416
+ })
417
+ } else {
418
+ const msgId = await requestPlaceholderResend(msgKey)
419
+ logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`)
408
420
  }
409
- const deviceIdentity = encodeSignedDeviceIdentity(account, true)
410
- await authState.keys.transaction(async () => {
411
- const receipt = {
412
- tag: "receipt",
413
- attrs: {
414
- id: msgId,
415
- type: "retry",
416
- to: node.attrs.from,
421
+ }
422
+
423
+ const deviceIdentity = encodeSignedDeviceIdentity(account, true)
424
+ await authState.keys.transaction(async () => {
425
+ const receipt = {
426
+ tag: "receipt",
427
+ attrs: {
428
+ id: msgId,
429
+ type: "retry",
430
+ to: node.attrs.from,
431
+ },
432
+ content: [
433
+ {
434
+ tag: "retry",
435
+ attrs: {
436
+ count: retryCount.toString(),
437
+ id: node.attrs.id,
438
+ t: node.attrs.t,
439
+ v: "1",
440
+ error: "0",
441
+ },
442
+ },
443
+ {
444
+ tag: "registration",
445
+ attrs: {},
446
+ content: encodeBigEndian(authState.creds.registrationId),
417
447
  },
448
+ ],
449
+ }
450
+
451
+ if (node.attrs.recipient) {
452
+ receipt.attrs.recipient = node.attrs.recipient
453
+ }
454
+ if (node.attrs.participant) {
455
+ receipt.attrs.participant = node.attrs.participant
456
+ }
457
+
458
+ if (retryCount > 1 || forceIncludeKeys || shouldRecreateSession) {
459
+ const { update, preKeys } = await getNextPreKeys(authState, 1)
460
+ const [keyId] = Object.keys(preKeys)
461
+ const key = preKeys[+keyId]
462
+ const content = receipt.content
463
+ content.push({
464
+ tag: "keys",
465
+ attrs: {},
418
466
  content: [
419
- {
420
- tag: "retry",
421
- attrs: {
422
- count: retryCount.toString(),
423
- id: node.attrs.id,
424
- t: node.attrs.t,
425
- v: "1",
426
- // ADD ERROR FIELD
427
- error: "0",
428
- },
429
- },
430
- {
431
- tag: "registration",
432
- attrs: {},
433
- content: encodeBigEndian(authState.creds.registrationId),
434
- },
467
+ { tag: "type", attrs: {}, content: Buffer.from(KEY_BUNDLE_TYPE) },
468
+ { tag: "identity", attrs: {}, content: identityKey.public },
469
+ xmppPreKey(key, +keyId),
470
+ xmppSignedPreKey(signedPreKey),
471
+ { tag: "device-identity", attrs: {}, content: deviceIdentity },
435
472
  ],
436
- }
437
- if (node.attrs.recipient) {
438
- receipt.attrs.recipient = node.attrs.recipient
439
- }
440
- if (node.attrs.participant) {
441
- receipt.attrs.participant = node.attrs.participant
442
- }
443
- if (retryCount > 1 || forceIncludeKeys || shouldRecreateSession) {
444
- const { update, preKeys } = await getNextPreKeys(authState, 1)
445
- const [keyId] = Object.keys(preKeys)
446
- const key = preKeys[+keyId]
447
- const content = receipt.content
448
- content.push({
449
- tag: "keys",
450
- attrs: {},
451
- content: [
452
- { tag: "type", attrs: {}, content: Buffer.from(KEY_BUNDLE_TYPE) },
453
- { tag: "identity", attrs: {}, content: identityKey.public },
454
- xmppPreKey(key, +keyId),
455
- xmppSignedPreKey(signedPreKey),
456
- { tag: "device-identity", attrs: {}, content: deviceIdentity },
457
- ],
458
- })
459
- ev.emit("creds.update", update)
460
- }
461
- await sendNode(receipt)
462
- logger.info({ msgAttrs: node.attrs, retryCount }, "sent retry receipt")
463
- }, authState?.creds?.me?.id || "sendRetryRequest")
464
- }
465
- const handleEncryptNotification = async (node) => {
466
- const from = node.attrs.from
467
- if (from === S_WHATSAPP_NET) {
468
- const countChild = getBinaryNodeChild(node, "count")
469
- const count = +countChild.attrs.value
470
- const shouldUploadMorePreKeys = count < MIN_PREKEY_COUNT
471
- logger.debug({ count, shouldUploadMorePreKeys }, "recv pre-key count")
472
- if (shouldUploadMorePreKeys) {
473
- smartPreKeyMonitor("server-notification").catch(err => {
474
- logger.error({ err }, "Failed to upload pre-keys after server notification")
475
473
  })
476
- }
477
- } else {
478
- const identityNode = getBinaryNodeChild(node, "identity")
479
- if (identityNode) {
480
- logger.info({ jid: from }, "identity changed")
481
- // not handling right now
482
- // signal will override new identity anyway
474
+ ev.emit("creds.update", update)
475
+ }
476
+
477
+ await sendNode(receipt)
478
+ logger.info({ msgAttrs: node.attrs, retryCount }, "sent retry receipt")
479
+ }, authState?.creds?.me?.id || "sendRetryRequest")
480
+ }
481
+
482
+ const handleEncryptNotification = async (node) => {
483
+ const from = node.attrs.from
484
+ if (from === S_WHATSAPP_NET) {
485
+ const countChild = getBinaryNodeChild(node, "count")
486
+ const count = +countChild.attrs.value
487
+ const shouldUploadMorePreKeys = count < MIN_PREKEY_COUNT
488
+ logger.debug({ count, shouldUploadMorePreKeys }, "recv pre-key count")
489
+ if (shouldUploadMorePreKeys) {
490
+ // Use debounced trigger if available, otherwise fallback to direct upload
491
+ if (typeof triggerPreKeyCheck === 'function') {
492
+ triggerPreKeyCheck("server-notification", "normal")
483
493
  } else {
484
- logger.info({ node }, "unknown encrypt notification")
494
+ await uploadPreKeys(MIN_PREKEY_COUNT)
485
495
  }
486
496
  }
497
+ } else {
498
+ const identityNode = getBinaryNodeChild(node, "identity")
499
+ if (identityNode) {
500
+ logger.info({ jid: from }, "identity changed")
501
+ } else {
502
+ logger.info({ node }, "unknown encrypt notification")
503
+ }
487
504
  }
505
+ }
488
506
  const handleGroupNotification = (fullNode, child, msg) => {
489
507
  // TODO: Support PN/LID (Here is only LID now)
490
508
  const actingParticipantLid = fullNode.attrs.participant