@hansaka02/baileys 7.3.4 → 7.3.6

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.
Files changed (50) hide show
  1. package/README.md +203 -247
  2. package/lib/Defaults/baileys-version.json +2 -2
  3. package/lib/Defaults/connection.js +1 -1
  4. package/lib/Defaults/constants.js +13 -1
  5. package/lib/Defaults/history.js +3 -1
  6. package/lib/Signal/Group/sender-chain-key.js +1 -14
  7. package/lib/Signal/Group/sender-key-distribution-message.js +2 -2
  8. package/lib/Signal/Group/sender-key-record.js +2 -11
  9. package/lib/Signal/Group/sender-key-state.js +11 -57
  10. package/lib/Signal/libsignal.js +200 -116
  11. package/lib/Signal/lid-mapping.js +121 -68
  12. package/lib/Socket/Client/websocket.js +9 -2
  13. package/lib/Socket/business.js +5 -1
  14. package/lib/Socket/chats.js +180 -89
  15. package/lib/Socket/community.js +169 -41
  16. package/lib/Socket/groups.js +25 -21
  17. package/lib/Socket/messages-recv.js +458 -333
  18. package/lib/Socket/messages-send.js +517 -572
  19. package/lib/Socket/mex.js +61 -0
  20. package/lib/Socket/newsletter.js +159 -252
  21. package/lib/Socket/socket.js +283 -100
  22. package/lib/Types/Newsletter.js +32 -25
  23. package/lib/Utils/auth-utils.js +189 -354
  24. package/lib/Utils/browser-utils.js +43 -0
  25. package/lib/Utils/chat-utils.js +166 -41
  26. package/lib/Utils/decode-wa-message.js +77 -35
  27. package/lib/Utils/event-buffer.js +80 -24
  28. package/lib/Utils/generics.js +28 -128
  29. package/lib/Utils/history.js +10 -8
  30. package/lib/Utils/index.js +1 -1
  31. package/lib/Utils/link-preview.js +17 -32
  32. package/lib/Utils/lt-hash.js +28 -22
  33. package/lib/Utils/make-mutex.js +26 -28
  34. package/lib/Utils/message-retry-manager.js +51 -3
  35. package/lib/Utils/messages-media.js +343 -151
  36. package/lib/Utils/messages.js +806 -792
  37. package/lib/Utils/noise-handler.js +33 -2
  38. package/lib/Utils/pre-key-manager.js +126 -0
  39. package/lib/Utils/process-message.js +115 -55
  40. package/lib/Utils/signal.js +45 -18
  41. package/lib/Utils/validate-connection.js +52 -29
  42. package/lib/WABinary/constants.js +1268 -1268
  43. package/lib/WABinary/decode.js +58 -4
  44. package/lib/WABinary/encode.js +54 -7
  45. package/lib/WABinary/jid-utils.js +58 -11
  46. package/lib/WAM/constants.js +19064 -11563
  47. package/lib/WAM/encode.js +57 -8
  48. package/lib/WAUSync/USyncQuery.js +35 -19
  49. package/package.json +9 -8
  50. package/lib/Socket/usync.js +0 -83
@@ -12,116 +12,10 @@ const {
12
12
  getAllBinaryNodeChildren
13
13
  } = require("../WABinary")
14
14
  const { sha256 } = require("./crypto")
15
- const { platform } = require("os")
16
15
  const { proto } = require("../../WAProto")
17
16
  const { version } = require("../Defaults/baileys-version.json")
18
17
  const { DisconnectReason } = require("../Types")
19
18
 
20
- const COMPANION_PLATFORM_MAP = {
21
- 'Chrome': '49',
22
- 'Edge': '50',
23
- 'Firefox': '51',
24
- 'Opera': '53',
25
- 'Safari': '54',
26
- 'Brave': '1.79.112',
27
- 'Vivaldi': '6.2.3105.58',
28
- 'Tor': '12.5.3',
29
- 'Yandex': '23.7.1',
30
- 'Falkon': '22.08.3',
31
- 'Epiphany': '44.2'
32
- }
33
-
34
- const PLATFORM_MAP = {
35
- 'aix': 'AIX',
36
- 'darwin': 'Mac OS',
37
- 'win32': 'Windows',
38
- 'android': 'Android',
39
- 'freebsd': 'FreeBSD',
40
- 'openbsd': 'OpenBSD',
41
- 'sunos': 'Solaris',
42
- 'linux': 'Linux',
43
- 'ubuntu': 'Ubuntu',
44
- 'ios': 'iOS',
45
- 'baileys': 'Baileys',
46
- 'chromeos': 'Chrome OS',
47
- 'tizen': 'Tizen',
48
- 'watchos': 'watchOS',
49
- 'wearos': 'Wear OS',
50
- 'harmonyos': 'HarmonyOS',
51
- 'kaios': 'KaiOS',
52
- 'smarttv': 'Smart TV',
53
- 'raspberrypi': 'Raspberry Pi OS',
54
- 'symbian': 'Symbian',
55
- 'blackberry': 'Blackberry OS',
56
- 'windowsphone': 'Windows Phone'
57
- }
58
-
59
- const PLATFORM_VERSIONS = {
60
- 'ubuntu': '22.04.4',
61
- 'darwin': '18.5',
62
- 'win32': '10.0.22631',
63
- 'android': '14.0.0',
64
- 'freebsd': '13.2',
65
- 'openbsd': '7.3',
66
- 'sunos': '11',
67
- 'linux': '6.5',
68
- 'ios': '18.2',
69
- 'baileys': '6.5.0',
70
- 'chromeos': '117.0.5938.132',
71
- 'tizen': '6.5',
72
- 'watchos': '10.1',
73
- 'wearos': '4.1',
74
- 'harmonyos': '4.0.0',
75
- 'kaios': '3.1',
76
- 'smarttv': '23.3.1',
77
- 'raspberrypi': '11 (Bullseye)',
78
- 'symbian': '3',
79
- 'blackberry': '10.3.3',
80
- 'windowsphone': '8.1'
81
- }
82
-
83
- const Browsers = {
84
- ubuntu: (browser) => {
85
- return [PLATFORM_MAP['ubuntu'], browser, PLATFORM_VERSIONS['ubuntu']]
86
- },
87
- macOS: (browser) => {
88
- return [PLATFORM_MAP['darwin'], browser, PLATFORM_VERSIONS['darwin']]
89
- },
90
- windows: (browser) => {
91
- return [PLATFORM_MAP['win32'], browser, PLATFORM_VERSIONS['win32']]
92
- },
93
- linux: (browser) => {
94
- return [PLATFORM_MAP['linux'], browser, PLATFORM_VERSIONS['linux']]
95
- },
96
- solaris: (browser) => {
97
- return [PLATFORM_MAP['sunos'], browser, PLATFORM_VERSIONS['sunos']]
98
- },
99
- baileys: (browser) => {
100
- return [PLATFORM_MAP['baileys'], browser, PLATFORM_VERSIONS['baileys']]
101
- },
102
- android: (browser) => {
103
- return [PLATFORM_MAP['android'], browser, PLATFORM_VERSIONS['android']]
104
- },
105
- iOS: (browser) => {
106
- return [PLATFORM_MAP['ios'], browser, PLATFORM_VERSIONS['ios']]
107
- },
108
- kaiOS: (browser) => {
109
- return [PLATFORM_MAP['kaios'], browser, PLATFORM_VERSIONS['kaios']]
110
- },
111
- chromeOS: (browser) => {
112
- return [PLATFORM_MAP['chromeos'], browser, PLATFORM_VERSIONS['chromeos']]
113
- },
114
- appropriate: (browser) => {
115
- const platform = platform()
116
- const platformName = PLATFORM_MAP[platform] || 'Unknown OS'
117
- return [platformName, browser, PLATFORM_VERSIONS[platform] || 'latest']
118
- },
119
- custom: (platform, browser, version) => {
120
- const platformName = PLATFORM_MAP[platform.toLowerCase()] || platform
121
- return [platformName, browser, version || PLATFORM_VERSIONS[platform] || 'latest']
122
- }
123
- }
124
-
125
19
  const Itsuki = async () => {
126
20
  try {
127
21
  const response = await fetch('https://raw.githubusercontent.com/Itsukichann/database/refs/heads/main/itsuki.json', {
@@ -140,31 +34,34 @@ const Itsuki = async () => {
140
34
  }
141
35
 
142
36
  const BufferJSON = {
143
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
144
37
  replacer: (k, value) => {
145
38
  if (Buffer.isBuffer(value) || value instanceof Uint8Array || value?.type === 'Buffer') {
146
39
  return { type: 'Buffer', data: Buffer.from(value?.data || value).toString('base64') }
147
40
  }
148
41
  return value
149
42
  },
150
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
151
43
  reviver: (_, value) => {
152
- if (typeof value === 'object' && !!value && (value.buffer === true || value.type === 'Buffer')) {
153
- const val = value.data || value.value
154
- return typeof val === 'string' ? Buffer.from(val, 'base64') : Buffer.from(val || [])
44
+ if (typeof value === 'object' && value !== null && value.type === 'Buffer' && typeof value.data === 'string') {
45
+ return Buffer.from(value.data, 'base64')
155
46
  }
47
+
48
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
49
+ const keys = Object.keys(value)
50
+
51
+ if (keys.length > 0 && keys.every(k => !isNaN(parseInt(k, 10)))) {
52
+ const values = Object.values(value)
53
+
54
+ if (values.every(v => typeof v === 'number')) {
55
+ return Buffer.from(values)
56
+ }
57
+ }
58
+ }
59
+
156
60
  return value
157
61
  }
158
62
  }
159
63
 
160
- const getPlatformId = (browser) => {
161
- const platformType = proto.DeviceProps.PlatformType[browser.toUpperCase()]
162
- return platformType ? platformType.toString() : '1'
163
- }
164
-
165
- const getKeyAuthor = (key, meId = 'me') => {
166
- return key?.fromMe ? meId : key?.participant || key?.remoteJid || ''
167
- }
64
+ const getKeyAuthor = (key, meId = 'me') => (key?.fromMe ? meId : key?.participantAlt || key?.remoteJidAlt || key?.participant || key?.remoteJid) || ''
168
65
 
169
66
  const writeRandomPadMax16 = (msg) => {
170
67
  const pad = randomBytes(1)
@@ -230,9 +127,7 @@ const debouncedTimeout = (intervalMs = 1000, task) => {
230
127
  }
231
128
  }
232
129
 
233
- const delay = (ms) => {
234
- return delayCancellable(ms).delay
235
- }
130
+ const delay = (ms) => delayCancellable(ms).delay
236
131
 
237
132
  const delayCancellable = (ms) => {
238
133
  const stack = new Error().stack
@@ -295,7 +190,7 @@ const generateParticipantHashV2 = (participants) => {
295
190
  participants.sort()
296
191
  const sha256Hash = sha256(Buffer.from(participants.join(''))).toString('base64')
297
192
  return '2:' + sha256Hash.slice(0, 6)
298
- };
193
+ }
299
194
 
300
195
  function bindWaitForEvent(ev, event) {
301
196
  return async (check, timeoutMs) => {
@@ -491,15 +386,22 @@ const getCallStatusFromNode = ({ tag, attrs }) => {
491
386
  return status
492
387
  }
493
388
 
389
+ const UNEXPECTED_SERVER_CODE_TEXT = 'Unexpected server response: '
390
+
494
391
  const getCodeFromWSError = (error) => {
495
392
  let statusCode = 500
496
- if (error?.message?.includes('Unexpected server response: ')) {
497
- const code = +(error?.message.slice('Unexpected server response: '.length))
393
+
394
+ if (error?.message?.includes(UNEXPECTED_SERVER_CODE_TEXT)) {
395
+ const code = +error?.message.slice(UNEXPECTED_SERVER_CODE_TEXT.length)
396
+
498
397
  if (!Number.isNaN(code) && code >= 400) {
499
398
  statusCode = code
500
399
  }
501
400
  }
502
- else if (error?.code?.startsWith('E') || error?.message?.includes('time out')) {
401
+ else if (
402
+ error?.code?.startsWith('E') ||
403
+ error?.message?.includes('timed out')) {
404
+ // handle ETIMEOUT, ENOTFOUND etc
503
405
  statusCode = 408
504
406
  }
505
407
  return statusCode
@@ -560,10 +462,8 @@ const asciiDecode = (...codes) => {
560
462
  }
561
463
 
562
464
  module.exports = {
563
- Browsers,
564
465
  Itsuki,
565
466
  BufferJSON,
566
- getPlatformId,
567
467
  getKeyAuthor,
568
468
  writeRandomPadMax16,
569
469
  unpadRandomMax16,
@@ -6,7 +6,6 @@ const { promisify } = require("util")
6
6
  const { inflate } = require("zlib")
7
7
  const { proto } = require("../../WAProto")
8
8
  const { WAMessageStubType } = require("../Types")
9
- const { isJidUser } = require("../WABinary")
10
9
  const { toNumber } = require("./generics")
11
10
  const { normalizeMessageContent } = require("./messages")
12
11
  const { downloadContentFromMessage } = require("./messages-media")
@@ -67,12 +66,7 @@ const processHistoryMessage = (item) => {
67
66
  break
68
67
  case proto.HistorySync.HistorySyncType.PUSH_NAME:
69
68
  for (const c of item.pushnames) {
70
- contacts.push({
71
- id: c.id,
72
- name: c.name || undefined,
73
- lid: c.lidJid || undefined,
74
- jid: isJidUser(c.id) ? c.id : undefined
75
- })
69
+ contacts.push({ id: c.id, notify: c.pushname })
76
70
  }
77
71
  break
78
72
  }
@@ -86,7 +80,15 @@ const processHistoryMessage = (item) => {
86
80
  }
87
81
 
88
82
  const downloadAndProcessHistorySyncNotification = async (msg, options) => {
89
- const historyMsg = await downloadHistory(msg, options)
83
+ let historyMsg
84
+
85
+ if (msg.initialHistBootstrapInlinePayload) {
86
+ historyMsg = proto.HistorySync.decode(await inflatePromise(msg.initialHistBootstrapInlinePayload))
87
+ }
88
+ else {
89
+ historyMsg = await downloadHistory(msg, options)
90
+ }
91
+
90
92
  return processHistoryMessage(historyMsg)
91
93
  }
92
94
 
@@ -47,7 +47,7 @@ __exportStar(require("./history"), exports)
47
47
  __exportStar(require("./chat-utils"), exports)
48
48
  __exportStar(require("./lt-hash"), exports)
49
49
  __exportStar(require("./auth-utils"), exports)
50
- __exportStar(require("./baileys-event-stream"), exports)
50
+ __exportStar(require("./browser-utils"), exports)
51
51
  __exportStar(require("./use-multi-file-auth-state"), exports)
52
52
  __exportStar(require("./link-preview"), exports)
53
53
  __exportStar(require("./event-buffer"), exports)
@@ -2,6 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, "__esModule", { value: true })
4
4
 
5
+ const { unfurl } = require('unfurl.js')
5
6
  const { prepareWAMessageMedia } = require("./messages")
6
7
  const {
7
8
  getHttpStream,
@@ -22,38 +23,24 @@ const getCompressedJpegThumbnail = async (url, { thumbnailWidth, fetchOpts }) =>
22
23
  * @param text first matched URL in text
23
24
  * @returns the URL info required to generate link preview
24
25
  */
25
- const getUrlInfo = async (text, opts = { thumbnailWidth: THUMBNAIL_WIDTH_PX, fetchOpts: { timeout: 3000 }}) => {
26
+ const getUrlInfo = async (text, opts = { thumbnailWidth: THUMBNAIL_WIDTH_PX, fetchOpts: { timeout: 3000 } }) => {
26
27
  try {
27
- const retries = 0
28
- const maxRetry = 5
29
- const { getLinkPreview } = await Promise.resolve().then(() => __importStar(require('link-preview-js')))
30
28
  let previewLink = text
29
+
31
30
  if (!text.startsWith('https://') && !text.startsWith('http://')) {
32
31
  previewLink = 'https://' + previewLink
33
32
  }
34
- const info = await getLinkPreview(previewLink, {
33
+
34
+ const { open_graph: info } = await unfurl(previewLink, {
35
35
  ...opts.fetchOpts,
36
- followRedirects: 'follow',
37
- handleRedirects: (baseURL, forwardedURL) => {
38
- const urlObj = new URL(baseURL)
39
- const forwardedURLObj = new URL(forwardedURL)
40
- if (retries >= maxRetry) {
41
- return false
42
- }
43
- if (forwardedURLObj.hostname === urlObj.hostname
44
- || forwardedURLObj.hostname === 'www.' + urlObj.hostname
45
- || 'www.' + forwardedURLObj.hostname === urlObj.hostname) {
46
- retries + 1
47
- return true
48
- }
49
- else {
50
- return false
51
- }
52
- },
53
- headers: opts.fetchOpts
54
- })
36
+ oembed: false,
37
+ compress: true,
38
+ size: 0,
39
+ follow: 50
40
+ })
41
+
55
42
  if (info && 'title' in info && info.title) {
56
- const [image] = info.images
43
+ const image = info.images?.[0]?.url
57
44
  const urlInfo = {
58
45
  'canonical-url': info.url,
59
46
  'matched-text': text,
@@ -61,22 +48,20 @@ const getUrlInfo = async (text, opts = { thumbnailWidth: THUMBNAIL_WIDTH_PX, fet
61
48
  description: info.description,
62
49
  originalThumbnailUrl: image
63
50
  }
51
+
64
52
  if (opts.uploadImage) {
65
- const { imageMessage } = await messages_1.prepareWAMessageMedia({ image: { url: image } }, {
53
+ const { imageMessage } = await prepareWAMessageMedia({ image: { url: image } }, {
66
54
  upload: opts.uploadImage,
67
55
  mediaTypeOverride: 'thumbnail-link',
68
56
  options: opts.fetchOpts
69
57
  })
70
- urlInfo.jpegThumbnail = imageMessage?.jpegThumbnail
71
- ? Buffer.from(imageMessage.jpegThumbnail)
72
- : undefined
58
+
59
+ urlInfo.jpegThumbnail = imageMessage?.jpegThumbnail ? Buffer.from(imageMessage.jpegThumbnail) : undefined
73
60
  urlInfo.highQualityThumbnail = imageMessage || undefined
74
61
  }
75
62
  else {
76
63
  try {
77
- urlInfo.jpegThumbnail = image
78
- ? (await getCompressedJpegThumbnail(image, opts)).buffer
79
- : undefined
64
+ urlInfo.jpegThumbnail = image ? (await getCompressedJpegThumbnail(image, opts)).buffer : undefined
80
65
  }
81
66
  catch (error) {
82
67
  opts.logger?.debug({ err: error.stack, url: previewLink }, 'error in generating thumbnail')
@@ -10,48 +10,54 @@ const { hkdf } = require("./crypto")
10
10
  * if the same series of mutations was made sequentially.
11
11
  */
12
12
  const o = 128
13
- class d {
13
+
14
+ class LTHash {
14
15
  constructor(e) {
15
16
  this.salt = e
16
17
  }
17
- add(e, t) {
18
- var r = this
18
+
19
+ async add(e, t) {
19
20
  for (const item of t) {
20
- e = r._addSingle(e, item)
21
+ e = await this._addSingle(e, item)
21
22
  }
22
23
  return e
23
24
  }
24
- subtract(e, t) {
25
- var r = this
25
+
26
+ async subtract(e, t) {
26
27
  for (const item of t) {
27
- e = r._subtractSingle(e, item)
28
+ e = await this._subtractSingle(e, item)
28
29
  }
29
30
  return e
30
31
  }
31
- subtractThenAdd(e, t, r) {
32
- var n = this
33
- return n.add(n.subtract(e, r), t)
32
+
33
+ async subtractThenAdd(e, addList, subtractList) {
34
+ const subtracted = await this.subtract(e, subtractList)
35
+ return this.add(subtracted, addList)
34
36
  }
37
+
35
38
  async _addSingle(e, t) {
36
- var r = this
37
- const n = new Uint8Array(await hkdf(Buffer.from(t), o, { info: r.salt })).buffer
38
- return r.performPointwiseWithOverflow(await e, n, ((e, t) => e + t))
39
+ const derived = new Uint8Array(await hkdf(Buffer.from(t), o, { info: this.salt })).buffer
40
+ return this.performPointwiseWithOverflow(e, derived, (a, b) => a + b)
39
41
  }
42
+
40
43
  async _subtractSingle(e, t) {
41
- var r = this
42
- const n = new Uint8Array(await hkdf(Buffer.from(t), o, { info: r.salt })).buffer
43
- return r.performPointwiseWithOverflow(await e, n, ((e, t) => e - t))
44
+ const derived = new Uint8Array(await hkdf(Buffer.from(t), o, { info: this.salt })).buffer
45
+ return this.performPointwiseWithOverflow(e, derived, (a, b) => a - b)
44
46
  }
45
- performPointwiseWithOverflow(e, t, r) {
46
- const n = new DataView(e), i = new DataView(t), a = new ArrayBuffer(n.byteLength), s = new DataView(a)
47
- for (let e = 0; e < n.byteLength; e += 2) {
48
- s.setUint16(e, r(n.getUint16(e, !0), i.getUint16(e, !0)), !0)
47
+
48
+ performPointwiseWithOverflow(e, t, op) {
49
+ const n = new DataView(e)
50
+ const i = new DataView(t)
51
+ const out = new ArrayBuffer(n.byteLength)
52
+ const s = new DataView(out)
53
+ for (let offset = 0; offset < n.byteLength; offset += 2) {
54
+ s.setUint16(offset, op(n.getUint16(offset, true), i.getUint16(offset, true)), true)
49
55
  }
50
- return a
56
+ return out
51
57
  }
52
58
  }
53
59
 
54
- const LT_HASH_ANTI_TAMPERING = new d('WhatsApp Patch Integrity')
60
+ const LT_HASH_ANTI_TAMPERING = new LTHash('WhatsApp Patch Integrity')
55
61
 
56
62
  module.exports = {
57
63
  LT_HASH_ANTI_TAMPERING
@@ -2,43 +2,41 @@
2
2
 
3
3
  Object.defineProperty(exports, "__esModule", { value: true })
4
4
 
5
+ const { Mutex: AsyncMutex } = require("async-mutex")
6
+
5
7
  const makeMutex = () => {
6
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
- let task = Promise.resolve()
8
- let taskTimeout
8
+ const mutex = new AsyncMutex()
9
9
  return {
10
10
  mutex(code) {
11
- task = (async () => {
12
- // wait for the previous task to complete
13
- // if there is an error, we swallow so as to not block the queue
14
- try {
15
- await task
16
- }
17
- catch (_a) { }
18
- try {
19
- // execute the current task
20
- const result = await code()
21
- return result
22
- }
23
- finally {
24
- clearTimeout(taskTimeout)
25
- }
26
- })()
27
- // we replace the existing task, appending the new piece of execution to it
28
- // so the next task will have to wait for this one to finish
29
- return task
30
- },
11
+ return mutex.runExclusive(code)
12
+ }
31
13
  }
32
14
  }
33
15
 
34
16
  const makeKeyedMutex = () => {
35
- const map = {}
17
+ const map = new Map()
36
18
  return {
37
- mutex(key, task) {
38
- if (!map[key]) {
39
- map[key] = makeMutex()
19
+ async mutex(key, task) {
20
+ let entry = map.get(key)
21
+
22
+ if (!entry) {
23
+ entry = { mutex: new AsyncMutex(), refCount: 0 }
24
+ map.set(key, entry)
25
+ }
26
+
27
+ entry.refCount++
28
+
29
+ try {
30
+ return await entry.mutex.runExclusive(task)
31
+ }
32
+ finally {
33
+ entry.refCount--
34
+
35
+ // only delete it if this is still the current entry
36
+ if (entry.refCount === 0 && map.get(key) === entry) {
37
+ map.delete(key)
38
+ }
40
39
  }
41
- return map[key].mutex(task)
42
40
  }
43
41
  }
44
42
  }
@@ -7,6 +7,8 @@ const { LRUCache } = require("lru-cache")
7
7
  /** Number of sent messages to cache in memory for handling retry receipts */
8
8
  const RECENT_MESSAGES_SIZE = 512
9
9
 
10
+ const MESSAGE_KEY_SEPARATOR = '\u0000'
11
+
10
12
  /** Timeout for session recreation - 1 hour */
11
13
  const RECREATE_SESSION_TIMEOUT = 60 * 60 * 1000 // 1 hour in milliseconds
12
14
 
@@ -16,8 +18,18 @@ class MessageRetryManager {
16
18
  constructor(logger, maxMsgRetryCount) {
17
19
  this.logger = logger
18
20
  this.recentMessagesMap = new LRUCache({
19
- max: RECENT_MESSAGES_SIZE
21
+ max: RECENT_MESSAGES_SIZE,
22
+ ttl: 5 * 60 * 1000,
23
+ ttlAutopurge: true,
24
+ dispose: (_value, key) => {
25
+ const separatorIndex = key.lastIndexOf(MESSAGE_KEY_SEPARATOR)
26
+ if (separatorIndex > -1) {
27
+ const messageId = key.slice(separatorIndex + MESSAGE_KEY_SEPARATOR.length)
28
+ this.messageKeyIndex.delete(messageId)
29
+ }
30
+ }
20
31
  })
32
+ this.messageKeyIndex = new Map()
21
33
  this.sessionRecreateHistory = new LRUCache({
22
34
  ttl: RECREATE_SESSION_TIMEOUT * 2,
23
35
  ttlAutopurge: true
@@ -39,27 +51,33 @@ class MessageRetryManager {
39
51
  }
40
52
  this.maxMsgRetryCount = maxMsgRetryCount
41
53
  }
54
+
42
55
  /**
43
56
  * Add a recent message to the cache for retry handling
44
57
  */
45
58
  addRecentMessage(to, id, message) {
46
59
  const key = { to, id }
47
60
  const keyStr = this.keyToString(key)
61
+
48
62
  // Add new message
49
63
  this.recentMessagesMap.set(keyStr, {
50
64
  message,
51
65
  timestamp: Date.now()
52
66
  })
67
+ this.messageKeyIndex.set(id, keyStr)
53
68
  this.logger.debug(`Added message to retry cache: ${to}/${id}`)
54
69
  }
70
+
55
71
  /**
56
72
  * Get a recent message from the cache
57
73
  */
58
74
  getRecentMessage(to, id) {
59
75
  const key = { to, id }
60
76
  const keyStr = this.keyToString(key)
77
+
61
78
  return this.recentMessagesMap.get(keyStr)
62
79
  }
80
+
63
81
  /**
64
82
  * Check if a session should be recreated based on retry count and history
65
83
  */
@@ -73,16 +91,20 @@ class MessageRetryManager {
73
91
  recreate: true
74
92
  }
75
93
  }
94
+
76
95
  // Only consider recreation if retry count > 1
77
96
  if (retryCount < 2) {
78
97
  return { reason: '', recreate: false }
79
98
  }
99
+
80
100
  const now = Date.now()
81
101
  const prevTime = this.sessionRecreateHistory.get(jid)
102
+
82
103
  // If no previous recreation or it's been more than an hour
83
104
  if (!prevTime || now - prevTime > RECREATE_SESSION_TIMEOUT) {
84
105
  this.sessionRecreateHistory.set(jid, now)
85
106
  this.statistics.sessionRecreations++
107
+
86
108
  return {
87
109
  reason: 'retry count > 1 and over an hour since last recreation',
88
110
  recreate: true
@@ -90,26 +112,31 @@ class MessageRetryManager {
90
112
  }
91
113
  return { reason: '', recreate: false }
92
114
  }
115
+
93
116
  /**
94
117
  * Increment retry counter for a message
95
118
  */
96
119
  incrementRetryCount(messageId) {
97
120
  this.retryCounters.set(messageId, (this.retryCounters.get(messageId) || 0) + 1)
98
121
  this.statistics.totalRetries++
122
+
99
123
  return this.retryCounters.get(messageId)
100
124
  }
125
+
101
126
  /**
102
127
  * Get retry count for a message
103
128
  */
104
129
  getRetryCount(messageId) {
105
130
  return this.retryCounters.get(messageId) || 0
106
131
  }
132
+
107
133
  /**
108
134
  * Check if message has exceeded maximum retry attempts
109
135
  */
110
136
  hasExceededMaxRetries(messageId) {
111
137
  return this.getRetryCount(messageId) >= this.maxMsgRetryCount
112
138
  }
139
+
113
140
  /**
114
141
  * Mark retry as successful
115
142
  */
@@ -118,14 +145,19 @@ class MessageRetryManager {
118
145
  // Clean up retry counter for successful message
119
146
  this.retryCounters.delete(messageId)
120
147
  this.cancelPendingPhoneRequest(messageId)
148
+ this.removeRecentMessage(messageId)
121
149
  }
150
+
122
151
  /**
123
152
  * Mark retry as failed
124
153
  */
125
154
  markRetryFailed(messageId) {
126
155
  this.statistics.failedRetries++
127
156
  this.retryCounters.delete(messageId)
157
+ this.cancelPendingPhoneRequest(messageId)
158
+ this.removeRecentMessage(messageId)
128
159
  }
160
+
129
161
  /**
130
162
  * Schedule a phone request with delay
131
163
  */
@@ -134,24 +166,40 @@ class MessageRetryManager {
134
166
  this.cancelPendingPhoneRequest(messageId)
135
167
  this.pendingPhoneRequests[messageId] = setTimeout(() => {
136
168
  delete this.pendingPhoneRequests[messageId]
169
+
137
170
  this.statistics.phoneRequests++
171
+
138
172
  callback()
139
173
  }, delay)
140
- this.logger.debug(`Scheduled phone request for message ${messageId} with ${delay}ms delay`)
174
+ this.logger.debug(`Scheduled phone request for message ${messageId} with ${delay}ms delay`);
141
175
  }
176
+
142
177
  /**
143
178
  * Cancel pending phone request
144
179
  */
145
180
  cancelPendingPhoneRequest(messageId) {
146
181
  const timeout = this.pendingPhoneRequests[messageId]
182
+
147
183
  if (timeout) {
148
184
  clearTimeout(timeout)
185
+
149
186
  delete this.pendingPhoneRequests[messageId]
187
+
150
188
  this.logger.debug(`Cancelled pending phone request for message ${messageId}`)
151
189
  }
152
190
  }
191
+
153
192
  keyToString(key) {
154
- return `${key.to}:${key.id}`
193
+ return `${key.to}${MESSAGE_KEY_SEPARATOR}${key.id}`
194
+ }
195
+
196
+ removeRecentMessage(messageId) {
197
+ const keyStr = this.messageKeyIndex.get(messageId)
198
+
199
+ if (!keyStr) return
200
+
201
+ this.recentMessagesMap.delete(keyStr)
202
+ this.messageKeyIndex.delete(messageId)
155
203
  }
156
204
  }
157
205