@fer2809fl/baileys 1.4.9 → 7.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.
Files changed (481) hide show
  1. package/LICENSE +21 -674
  2. package/README.md +556 -523
  3. package/WAProto/GenerateStatics.sh +3 -4
  4. package/WAProto/WAProto.proto +1215 -511
  5. package/WAProto/fix-imports.js +85 -0
  6. package/WAProto/index.d.ts +4347 -45387
  7. package/WAProto/index.js +65475 -137445
  8. package/engine-requirements.js +10 -0
  9. package/lib/Defaults/index.d.ts +34 -18
  10. package/lib/Defaults/index.d.ts.map +1 -0
  11. package/lib/Defaults/index.js +95 -67
  12. package/lib/Defaults/index.js.map +1 -0
  13. package/lib/Signal/Group/ciphertext-message.d.ts +1 -0
  14. package/lib/Signal/Group/ciphertext-message.d.ts.map +1 -0
  15. package/lib/Signal/Group/ciphertext-message.js +2 -5
  16. package/lib/Signal/Group/ciphertext-message.js.map +1 -0
  17. package/lib/Signal/Group/group-session-builder.d.ts +4 -3
  18. package/lib/Signal/Group/group-session-builder.d.ts.map +1 -0
  19. package/lib/Signal/Group/group-session-builder.js +7 -41
  20. package/lib/Signal/Group/group-session-builder.js.map +1 -0
  21. package/lib/Signal/Group/group_cipher.d.ts +4 -4
  22. package/lib/Signal/Group/group_cipher.d.ts.map +1 -0
  23. package/lib/Signal/Group/group_cipher.js +37 -60
  24. package/lib/Signal/Group/group_cipher.js.map +1 -0
  25. package/lib/Signal/Group/index.d.ts +12 -11
  26. package/lib/Signal/Group/index.d.ts.map +1 -0
  27. package/lib/Signal/Group/index.js +12 -57
  28. package/lib/Signal/Group/index.js.map +1 -0
  29. package/lib/Signal/Group/keyhelper.d.ts +2 -1
  30. package/lib/Signal/Group/keyhelper.d.ts.map +1 -0
  31. package/lib/Signal/Group/keyhelper.js +7 -44
  32. package/lib/Signal/Group/keyhelper.js.map +1 -0
  33. package/lib/Signal/Group/sender-chain-key.d.ts +3 -2
  34. package/lib/Signal/Group/sender-chain-key.d.ts.map +1 -0
  35. package/lib/Signal/Group/sender-chain-key.js +7 -15
  36. package/lib/Signal/Group/sender-chain-key.js.map +1 -0
  37. package/lib/Signal/Group/sender-key-distribution-message.d.ts +2 -1
  38. package/lib/Signal/Group/sender-key-distribution-message.d.ts.map +1 -0
  39. package/lib/Signal/Group/sender-key-distribution-message.js +8 -11
  40. package/lib/Signal/Group/sender-key-distribution-message.js.map +1 -0
  41. package/lib/Signal/Group/sender-key-message.d.ts +2 -1
  42. package/lib/Signal/Group/sender-key-message.d.ts.map +1 -0
  43. package/lib/Signal/Group/sender-key-message.js +9 -12
  44. package/lib/Signal/Group/sender-key-message.js.map +1 -0
  45. package/lib/Signal/Group/sender-key-name.d.ts +1 -0
  46. package/lib/Signal/Group/sender-key-name.d.ts.map +1 -0
  47. package/lib/Signal/Group/sender-key-name.js +2 -5
  48. package/lib/Signal/Group/sender-key-name.js.map +1 -0
  49. package/lib/Signal/Group/sender-key-record.d.ts +3 -2
  50. package/lib/Signal/Group/sender-key-record.d.ts.map +1 -0
  51. package/lib/Signal/Group/sender-key-record.js +12 -37
  52. package/lib/Signal/Group/sender-key-record.js.map +1 -0
  53. package/lib/Signal/Group/sender-key-state.d.ts +7 -6
  54. package/lib/Signal/Group/sender-key-state.d.ts.map +1 -0
  55. package/lib/Signal/Group/sender-key-state.js +27 -42
  56. package/lib/Signal/Group/sender-key-state.js.map +1 -0
  57. package/lib/Signal/Group/sender-message-key.d.ts +1 -0
  58. package/lib/Signal/Group/sender-message-key.d.ts.map +1 -0
  59. package/lib/Signal/Group/sender-message-key.js +4 -7
  60. package/lib/Signal/Group/sender-message-key.js.map +1 -0
  61. package/lib/Signal/libsignal.d.ts +5 -3
  62. package/lib/Signal/libsignal.d.ts.map +1 -0
  63. package/lib/Signal/libsignal.js +347 -90
  64. package/lib/Signal/libsignal.js.map +1 -0
  65. package/lib/Signal/lid-mapping.d.ts +23 -0
  66. package/lib/Signal/lid-mapping.d.ts.map +1 -0
  67. package/lib/Signal/lid-mapping.js +277 -0
  68. package/lib/Signal/lid-mapping.js.map +1 -0
  69. package/lib/Socket/Client/index.d.ts +3 -2
  70. package/lib/Socket/Client/index.d.ts.map +1 -0
  71. package/lib/Socket/Client/index.js +3 -18
  72. package/lib/Socket/Client/index.js.map +1 -0
  73. package/lib/Socket/Client/types.d.ts +4 -4
  74. package/lib/Socket/Client/types.d.ts.map +1 -0
  75. package/lib/Socket/Client/types.js +4 -6
  76. package/lib/Socket/Client/types.js.map +1 -0
  77. package/lib/Socket/Client/websocket.d.ts +3 -3
  78. package/lib/Socket/Client/websocket.d.ts.map +1 -0
  79. package/lib/Socket/Client/websocket.js +21 -78
  80. package/lib/Socket/Client/websocket.js.map +1 -0
  81. package/lib/Socket/business.d.ts +205 -93
  82. package/lib/Socket/business.d.ts.map +1 -0
  83. package/lib/Socket/business.js +162 -43
  84. package/lib/Socket/business.js.map +1 -0
  85. package/lib/Socket/chats.d.ts +77 -28
  86. package/lib/Socket/chats.d.ts.map +1 -0
  87. package/lib/Socket/chats.js +639 -248
  88. package/lib/Socket/chats.js.map +1 -0
  89. package/lib/Socket/communities.d.ts +340 -0
  90. package/lib/Socket/communities.d.ts.map +1 -0
  91. package/lib/Socket/communities.js +434 -0
  92. package/lib/Socket/communities.js.map +1 -0
  93. package/lib/Socket/groups.d.ts +93 -46
  94. package/lib/Socket/groups.d.ts.map +1 -0
  95. package/lib/Socket/groups.js +260 -102
  96. package/lib/Socket/groups.js.map +1 -0
  97. package/lib/Socket/index.d.ts +253 -98
  98. package/lib/Socket/index.d.ts.map +1 -0
  99. package/lib/Socket/index.js +11 -9
  100. package/lib/Socket/index.js.map +1 -0
  101. package/lib/Socket/messages-recv.d.ts +192 -83
  102. package/lib/Socket/messages-recv.d.ts.map +1 -0
  103. package/lib/Socket/messages-recv.js +1314 -559
  104. package/lib/Socket/messages-recv.js.map +1 -0
  105. package/lib/Socket/messages-send.d.ts +190 -75
  106. package/lib/Socket/messages-send.d.ts.map +1 -0
  107. package/lib/Socket/messages-send.js +937 -581
  108. package/lib/Socket/messages-send.js.map +1 -0
  109. package/lib/Socket/mex.d.ts +4 -0
  110. package/lib/Socket/mex.d.ts.map +1 -0
  111. package/lib/Socket/mex.js +62 -0
  112. package/lib/Socket/mex.js.map +1 -0
  113. package/lib/Socket/newsletter.d.ts +129 -73
  114. package/lib/Socket/newsletter.d.ts.map +1 -0
  115. package/lib/Socket/newsletter.js +373 -222
  116. package/lib/Socket/newsletter.js.map +1 -0
  117. package/lib/Socket/smgss.d.ts +119 -0
  118. package/lib/Socket/smgss.d.ts.map +1 -0
  119. package/lib/Socket/smgss.js +453 -0
  120. package/lib/Socket/smgss.js.map +1 -0
  121. package/lib/Socket/socket.d.ts +59 -270
  122. package/lib/Socket/socket.d.ts.map +1 -0
  123. package/lib/Socket/socket.js +738 -326
  124. package/lib/Socket/socket.js.map +1 -0
  125. package/lib/Store/index.d.ts +4 -2
  126. package/lib/Store/index.d.ts.map +1 -0
  127. package/lib/Store/index.js +4 -8
  128. package/lib/Store/index.js.map +1 -0
  129. package/lib/Store/make-in-memory-store.d.ts +74 -60
  130. package/lib/Store/make-in-memory-store.d.ts.map +1 -0
  131. package/lib/Store/make-in-memory-store.js +130 -119
  132. package/lib/Store/make-in-memory-store.js.map +1 -0
  133. package/lib/Store/make-ordered-dictionary.d.ts +4 -3
  134. package/lib/Store/make-ordered-dictionary.d.ts.map +1 -0
  135. package/lib/Store/make-ordered-dictionary.js +5 -6
  136. package/lib/Store/make-ordered-dictionary.js.map +1 -0
  137. package/lib/Store/object-repository.d.ts +6 -3
  138. package/lib/Store/object-repository.d.ts.map +1 -0
  139. package/lib/Store/object-repository.js +2 -5
  140. package/lib/Store/object-repository.js.map +1 -0
  141. package/lib/Types/Auth.d.ts +19 -5
  142. package/lib/Types/Auth.d.ts.map +1 -0
  143. package/lib/Types/Auth.js +2 -2
  144. package/lib/Types/Auth.js.map +1 -0
  145. package/lib/Types/Bussines.d.ts +25 -0
  146. package/lib/Types/Bussines.d.ts.map +1 -0
  147. package/lib/Types/Bussines.js +2 -0
  148. package/lib/Types/Bussines.js.map +1 -0
  149. package/lib/Types/Call.d.ts +3 -1
  150. package/lib/Types/Call.d.ts.map +1 -0
  151. package/lib/Types/Call.js +2 -2
  152. package/lib/Types/Call.js.map +1 -0
  153. package/lib/Types/Chat.d.ts +24 -9
  154. package/lib/Types/Chat.d.ts.map +1 -0
  155. package/lib/Types/Chat.js +8 -4
  156. package/lib/Types/Chat.js.map +1 -0
  157. package/lib/Types/Contact.d.ts +7 -4
  158. package/lib/Types/Contact.d.ts.map +1 -0
  159. package/lib/Types/Contact.js +2 -2
  160. package/lib/Types/Contact.js.map +1 -0
  161. package/lib/Types/Events.d.ts +90 -33
  162. package/lib/Types/Events.d.ts.map +1 -0
  163. package/lib/Types/Events.js +2 -2
  164. package/lib/Types/Events.js.map +1 -0
  165. package/lib/Types/GroupMetadata.d.ts +16 -9
  166. package/lib/Types/GroupMetadata.d.ts.map +1 -0
  167. package/lib/Types/GroupMetadata.js +2 -2
  168. package/lib/Types/GroupMetadata.js.map +1 -0
  169. package/lib/Types/Label.d.ts +12 -0
  170. package/lib/Types/Label.d.ts.map +1 -0
  171. package/lib/Types/Label.js +3 -5
  172. package/lib/Types/Label.js.map +1 -0
  173. package/lib/Types/LabelAssociation.d.ts +1 -0
  174. package/lib/Types/LabelAssociation.d.ts.map +1 -0
  175. package/lib/Types/LabelAssociation.js +3 -5
  176. package/lib/Types/LabelAssociation.js.map +1 -0
  177. package/lib/Types/Message.d.ts +176 -148
  178. package/lib/Types/Message.d.ts.map +1 -0
  179. package/lib/Types/Message.js +15 -7
  180. package/lib/Types/Message.js.map +1 -0
  181. package/lib/Types/Mex.d.ts +154 -0
  182. package/lib/Types/Mex.d.ts.map +1 -0
  183. package/lib/Types/Mex.js +39 -0
  184. package/lib/Types/Mex.js.map +1 -0
  185. package/lib/Types/Product.d.ts +2 -1
  186. package/lib/Types/Product.d.ts.map +1 -0
  187. package/lib/Types/Product.js +2 -2
  188. package/lib/Types/Product.js.map +1 -0
  189. package/lib/Types/Signal.d.ts +32 -2
  190. package/lib/Types/Signal.d.ts.map +1 -0
  191. package/lib/Types/Signal.js +2 -2
  192. package/lib/Types/Signal.js.map +1 -0
  193. package/lib/Types/Socket.d.ts +46 -20
  194. package/lib/Types/Socket.d.ts.map +1 -0
  195. package/lib/Types/Socket.js +3 -2
  196. package/lib/Types/Socket.js.map +1 -0
  197. package/lib/Types/State.d.ts +79 -2
  198. package/lib/Types/State.d.ts.map +1 -0
  199. package/lib/Types/State.js +56 -2
  200. package/lib/Types/State.js.map +1 -0
  201. package/lib/Types/USync.d.ts +3 -2
  202. package/lib/Types/USync.d.ts.map +1 -0
  203. package/lib/Types/USync.js +2 -2
  204. package/lib/Types/USync.js.map +1 -0
  205. package/lib/Types/index.d.ts +16 -14
  206. package/lib/Types/index.d.ts.map +1 -0
  207. package/lib/Types/index.js +15 -31
  208. package/lib/Types/index.js.map +1 -0
  209. package/lib/Utils/auth-utils.d.ts +10 -4
  210. package/lib/Utils/auth-utils.d.ts.map +1 -0
  211. package/lib/Utils/auth-utils.js +242 -151
  212. package/lib/Utils/auth-utils.js.map +1 -0
  213. package/lib/Utils/browser-utils.d.ts +4 -0
  214. package/lib/Utils/browser-utils.d.ts.map +1 -0
  215. package/lib/Utils/browser-utils.js +29 -0
  216. package/lib/Utils/browser-utils.js.map +1 -0
  217. package/lib/Utils/business.d.ts +3 -2
  218. package/lib/Utils/business.d.ts.map +1 -0
  219. package/lib/Utils/business.js +66 -69
  220. package/lib/Utils/business.js.map +1 -0
  221. package/lib/Utils/button-handler.d.ts +28 -0
  222. package/lib/Utils/button-handler.d.ts.map +1 -0
  223. package/lib/Utils/button-handler.js +79 -90
  224. package/lib/Utils/button-handler.js.map +1 -0
  225. package/lib/Utils/chat-utils.d.ts +42 -12
  226. package/lib/Utils/chat-utils.d.ts.map +1 -0
  227. package/lib/Utils/chat-utils.js +380 -238
  228. package/lib/Utils/chat-utils.js.map +1 -0
  229. package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
  230. package/lib/Utils/companion-reg-client-utils.d.ts.map +1 -0
  231. package/lib/Utils/companion-reg-client-utils.js +35 -0
  232. package/lib/Utils/companion-reg-client-utils.js.map +1 -0
  233. package/lib/Utils/crypto.d.ts +11 -14
  234. package/lib/Utils/crypto.d.ts.map +1 -0
  235. package/lib/Utils/crypto.js +41 -116
  236. package/lib/Utils/crypto.js.map +1 -0
  237. package/lib/Utils/decode-wa-message.d.ts +36 -5
  238. package/lib/Utils/decode-wa-message.d.ts.map +1 -0
  239. package/lib/Utils/decode-wa-message.js +173 -63
  240. package/lib/Utils/decode-wa-message.js.map +1 -0
  241. package/lib/Utils/event-buffer.d.ts +8 -7
  242. package/lib/Utils/event-buffer.d.ts.map +1 -0
  243. package/lib/Utils/event-buffer.js +174 -70
  244. package/lib/Utils/event-buffer.js.map +1 -0
  245. package/lib/Utils/generics.d.ts +20 -17
  246. package/lib/Utils/generics.d.ts.map +1 -0
  247. package/lib/Utils/generics.js +159 -207
  248. package/lib/Utils/generics.js.map +1 -0
  249. package/lib/Utils/history.d.ts +16 -11
  250. package/lib/Utils/history.d.ts.map +1 -0
  251. package/lib/Utils/history.js +93 -53
  252. package/lib/Utils/history.js.map +1 -0
  253. package/lib/Utils/identity-change-handler.d.ts +44 -0
  254. package/lib/Utils/identity-change-handler.d.ts.map +1 -0
  255. package/lib/Utils/identity-change-handler.js +50 -0
  256. package/lib/Utils/identity-change-handler.js.map +1 -0
  257. package/lib/Utils/index.d.ts +30 -18
  258. package/lib/Utils/index.d.ts.map +1 -0
  259. package/lib/Utils/index.js +30 -34
  260. package/lib/Utils/index.js.map +1 -0
  261. package/lib/Utils/interactive.d.ts +60 -0
  262. package/lib/Utils/interactive.d.ts.map +1 -0
  263. package/lib/Utils/interactive.js +87 -117
  264. package/lib/Utils/interactive.js.map +1 -0
  265. package/lib/Utils/link-preview.d.ts +4 -4
  266. package/lib/Utils/link-preview.d.ts.map +1 -0
  267. package/lib/Utils/link-preview.js +17 -58
  268. package/lib/Utils/link-preview.js.map +1 -0
  269. package/lib/Utils/logger.d.ts +8 -6
  270. package/lib/Utils/logger.d.ts.map +1 -0
  271. package/lib/Utils/logger.js +3 -10
  272. package/lib/Utils/logger.js.map +1 -0
  273. package/lib/Utils/lt-hash.d.ts +8 -12
  274. package/lib/Utils/lt-hash.d.ts.map +1 -0
  275. package/lib/Utils/lt-hash.js +3 -46
  276. package/lib/Utils/lt-hash.js.map +1 -0
  277. package/lib/Utils/make-mutex.d.ts +2 -0
  278. package/lib/Utils/make-mutex.d.ts.map +1 -0
  279. package/lib/Utils/make-mutex.js +24 -34
  280. package/lib/Utils/make-mutex.js.map +1 -0
  281. package/lib/Utils/message-composer.d.ts +93 -0
  282. package/lib/Utils/message-composer.d.ts.map +1 -0
  283. package/lib/Utils/message-composer.js +311 -0
  284. package/lib/Utils/message-composer.js.map +1 -0
  285. package/lib/Utils/message-retry-manager.d.ts +115 -0
  286. package/lib/Utils/message-retry-manager.d.ts.map +1 -0
  287. package/lib/Utils/message-retry-manager.js +265 -0
  288. package/lib/Utils/message-retry-manager.js.map +1 -0
  289. package/lib/Utils/messages-media.d.ts +82 -40
  290. package/lib/Utils/messages-media.d.ts.map +1 -0
  291. package/lib/Utils/messages-media.js +674 -677
  292. package/lib/Utils/messages-media.js.map +1 -0
  293. package/lib/Utils/messages-newsletter.d.ts +74 -0
  294. package/lib/Utils/messages-newsletter.d.ts.map +1 -0
  295. package/lib/Utils/messages-newsletter.js +128 -0
  296. package/lib/Utils/messages-newsletter.js.map +1 -0
  297. package/lib/Utils/messages.d.ts +38 -14
  298. package/lib/Utils/messages.d.ts.map +1 -0
  299. package/lib/Utils/messages.js +835 -602
  300. package/lib/Utils/messages.js.map +1 -0
  301. package/lib/Utils/noise-handler.d.ts +9 -8
  302. package/lib/Utils/noise-handler.d.ts.map +1 -0
  303. package/lib/Utils/noise-handler.js +149 -98
  304. package/lib/Utils/noise-handler.js.map +1 -0
  305. package/lib/Utils/offline-node-processor.d.ts +17 -0
  306. package/lib/Utils/offline-node-processor.d.ts.map +1 -0
  307. package/lib/Utils/offline-node-processor.js +40 -0
  308. package/lib/Utils/offline-node-processor.js.map +1 -0
  309. package/lib/Utils/pre-key-manager.d.ts +28 -0
  310. package/lib/Utils/pre-key-manager.d.ts.map +1 -0
  311. package/lib/Utils/pre-key-manager.js +106 -0
  312. package/lib/Utils/pre-key-manager.js.map +1 -0
  313. package/lib/Utils/process-message.d.ts +30 -12
  314. package/lib/Utils/process-message.d.ts.map +1 -0
  315. package/lib/Utils/process-message.js +438 -230
  316. package/lib/Utils/process-message.js.map +1 -0
  317. package/lib/Utils/reporting-utils.d.ts +11 -0
  318. package/lib/Utils/reporting-utils.d.ts.map +1 -0
  319. package/lib/Utils/reporting-utils.js +258 -0
  320. package/lib/Utils/reporting-utils.js.map +1 -0
  321. package/lib/Utils/shortcake-crypto.d.ts +36 -0
  322. package/lib/Utils/shortcake-crypto.d.ts.map +1 -0
  323. package/lib/Utils/shortcake-crypto.js +71 -0
  324. package/lib/Utils/shortcake-crypto.js.map +1 -0
  325. package/lib/Utils/shortcake.d.ts +36 -0
  326. package/lib/Utils/shortcake.d.ts.map +1 -0
  327. package/lib/Utils/shortcake.js +162 -0
  328. package/lib/Utils/shortcake.js.map +1 -0
  329. package/lib/Utils/signal.d.ts +20 -6
  330. package/lib/Utils/signal.d.ts.map +1 -0
  331. package/lib/Utils/signal.js +112 -64
  332. package/lib/Utils/signal.js.map +1 -0
  333. package/lib/Utils/stanza-ack.d.ts +11 -0
  334. package/lib/Utils/stanza-ack.d.ts.map +1 -0
  335. package/lib/Utils/stanza-ack.js +38 -0
  336. package/lib/Utils/stanza-ack.js.map +1 -0
  337. package/lib/Utils/sync-action-utils.d.ts +19 -0
  338. package/lib/Utils/sync-action-utils.d.ts.map +1 -0
  339. package/lib/Utils/sync-action-utils.js +49 -0
  340. package/lib/Utils/sync-action-utils.js.map +1 -0
  341. package/lib/Utils/tc-token-utils.d.ts +37 -0
  342. package/lib/Utils/tc-token-utils.d.ts.map +1 -0
  343. package/lib/Utils/tc-token-utils.js +164 -0
  344. package/lib/Utils/tc-token-utils.js.map +1 -0
  345. package/lib/Utils/use-multi-file-auth-state.d.ts +2 -1
  346. package/lib/Utils/use-multi-file-auth-state.d.ts.map +1 -0
  347. package/lib/Utils/use-multi-file-auth-state.js +40 -64
  348. package/lib/Utils/use-multi-file-auth-state.js.map +1 -0
  349. package/lib/Utils/use-single-file-auth-state.d.ts +6 -0
  350. package/lib/Utils/use-single-file-auth-state.d.ts.map +1 -0
  351. package/lib/Utils/use-single-file-auth-state.js +111 -0
  352. package/lib/Utils/use-single-file-auth-state.js.map +1 -0
  353. package/lib/Utils/use-sqlite-auth-state.d.ts +14 -0
  354. package/lib/Utils/use-sqlite-auth-state.d.ts.map +1 -0
  355. package/lib/Utils/use-sqlite-auth-state.js +107 -0
  356. package/lib/Utils/use-sqlite-auth-state.js.map +1 -0
  357. package/lib/Utils/validate-connection.d.ts +5 -3
  358. package/lib/Utils/validate-connection.d.ts.map +1 -0
  359. package/lib/Utils/validate-connection.js +82 -101
  360. package/lib/Utils/validate-connection.js.map +1 -0
  361. package/lib/WABinary/constants.d.ts +1 -0
  362. package/lib/WABinary/constants.d.ts.map +1 -0
  363. package/lib/WABinary/constants.js +10 -12
  364. package/lib/WABinary/constants.js.map +1 -0
  365. package/lib/WABinary/decode.d.ts +2 -1
  366. package/lib/WABinary/decode.d.ts.map +1 -0
  367. package/lib/WABinary/decode.js +49 -52
  368. package/lib/WABinary/decode.js.map +1 -0
  369. package/lib/WABinary/encode.d.ts +2 -1
  370. package/lib/WABinary/encode.d.ts.map +1 -0
  371. package/lib/WABinary/encode.js +19 -49
  372. package/lib/WABinary/encode.js.map +1 -0
  373. package/lib/WABinary/generic-utils.d.ts +5 -4
  374. package/lib/WABinary/generic-utils.d.ts.map +1 -0
  375. package/lib/WABinary/generic-utils.js +46 -43
  376. package/lib/WABinary/generic-utils.js.map +1 -0
  377. package/lib/WABinary/index.d.ts +6 -5
  378. package/lib/WABinary/index.d.ts.map +1 -0
  379. package/lib/WABinary/index.js +6 -21
  380. package/lib/WABinary/index.js.map +1 -0
  381. package/lib/WABinary/jid-utils.d.ts +32 -10
  382. package/lib/WABinary/jid-utils.d.ts.map +1 -0
  383. package/lib/WABinary/jid-utils.js +103 -59
  384. package/lib/WABinary/jid-utils.js.map +1 -0
  385. package/lib/WABinary/types.d.ts +2 -1
  386. package/lib/WABinary/types.d.ts.map +1 -0
  387. package/lib/WABinary/types.js +2 -2
  388. package/lib/WABinary/types.js.map +1 -0
  389. package/lib/WAM/BinaryInfo.d.ts +2 -1
  390. package/lib/WAM/BinaryInfo.d.ts.map +1 -0
  391. package/lib/WAM/BinaryInfo.js +2 -5
  392. package/lib/WAM/BinaryInfo.js.map +1 -0
  393. package/lib/WAM/constants.d.ts +5 -3
  394. package/lib/WAM/constants.d.ts.map +1 -0
  395. package/lib/WAM/constants.js +19071 -11568
  396. package/lib/WAM/constants.js.map +1 -0
  397. package/lib/WAM/encode.d.ts +2 -1
  398. package/lib/WAM/encode.d.ts.map +1 -0
  399. package/lib/WAM/encode.js +17 -22
  400. package/lib/WAM/encode.js.map +1 -0
  401. package/lib/WAM/index.d.ts +4 -3
  402. package/lib/WAM/index.d.ts.map +1 -0
  403. package/lib/WAM/index.js +4 -19
  404. package/lib/WAM/index.js.map +1 -0
  405. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +4 -3
  406. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts.map +1 -0
  407. package/lib/WAUSync/Protocols/USyncContactProtocol.js +33 -13
  408. package/lib/WAUSync/Protocols/USyncContactProtocol.js.map +1 -0
  409. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +3 -2
  410. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts.map +1 -0
  411. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +11 -14
  412. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js.map +1 -0
  413. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +3 -2
  414. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts.map +1 -0
  415. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +8 -11
  416. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js.map +1 -0
  417. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +3 -2
  418. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts.map +1 -0
  419. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +9 -13
  420. package/lib/WAUSync/Protocols/USyncStatusProtocol.js.map +1 -0
  421. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
  422. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts.map +1 -0
  423. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
  424. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js.map +1 -0
  425. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +4 -3
  426. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts.map +1 -0
  427. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +20 -22
  428. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js.map +1 -0
  429. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +5 -3
  430. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts.map +1 -0
  431. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +13 -8
  432. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js.map +1 -0
  433. package/lib/WAUSync/Protocols/index.d.ts +6 -4
  434. package/lib/WAUSync/Protocols/index.d.ts.map +1 -0
  435. package/lib/WAUSync/Protocols/index.js +6 -20
  436. package/lib/WAUSync/Protocols/index.js.map +1 -0
  437. package/lib/WAUSync/USyncQuery.d.ts +6 -4
  438. package/lib/WAUSync/USyncQuery.d.ts.map +1 -0
  439. package/lib/WAUSync/USyncQuery.js +44 -35
  440. package/lib/WAUSync/USyncQuery.js.map +1 -0
  441. package/lib/WAUSync/USyncUser.d.ts +10 -5
  442. package/lib/WAUSync/USyncUser.d.ts.map +1 -0
  443. package/lib/WAUSync/USyncUser.js +10 -5
  444. package/lib/WAUSync/USyncUser.js.map +1 -0
  445. package/lib/WAUSync/index.d.ts +4 -3
  446. package/lib/WAUSync/index.d.ts.map +1 -0
  447. package/lib/WAUSync/index.js +4 -19
  448. package/lib/WAUSync/index.js.map +1 -0
  449. package/lib/index.d.ts +12 -15
  450. package/lib/index.d.ts.map +1 -0
  451. package/lib/index.js +15 -48
  452. package/lib/index.js.map +1 -0
  453. package/package.json +44 -64
  454. package/WAProto/index.ts.ts +0 -53473
  455. package/WAProto/n +0 -1
  456. package/lib/Baileys.jpeg +0 -0
  457. package/lib/Defaults/baileys-version.json +0 -3
  458. package/lib/Defaults/n +0 -1
  459. package/lib/Signal/Group/queue-job.d.ts +0 -1
  460. package/lib/Signal/Group/queue-job.js +0 -57
  461. package/lib/Signal/n +0 -1
  462. package/lib/Socket/Client/n +0 -1
  463. package/lib/Socket/n +0 -1
  464. package/lib/Socket/setup.js +0 -480
  465. package/lib/Socket/setup.ts +0 -622
  466. package/lib/Socket/usync.d.ts +0 -36
  467. package/lib/Socket/usync.js +0 -70
  468. package/lib/Store/n +0 -1
  469. package/lib/Types/Newsletter.d.ts +0 -79
  470. package/lib/Types/Newsletter.js +0 -18
  471. package/lib/Types/n +0 -1
  472. package/lib/Utils/baileys-event-stream.d.ts +0 -16
  473. package/lib/Utils/baileys-event-stream.js +0 -63
  474. package/lib/Utils/error-handler.js +0 -37
  475. package/lib/Utils/n +0 -1
  476. package/lib/WABinary/jid-utils.js.bak +0 -83
  477. package/lib/WABinary/n +0 -1
  478. package/lib/WAM/n +0 -1
  479. package/lib/WAUSync/Protocols/n +0 -1
  480. package/lib/WAUSync/n +0 -1
  481. package/lib/n +0 -1
@@ -1,163 +1,497 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.makeMessagesRecvSocket = void 0;
7
- const boom_1 = require("@hapi/boom");
8
- const crypto_1 = require("crypto");
9
- const node_cache_1 = __importDefault(require("@cacheable/node-cache"));
10
- const WAProto_1 = require("../../WAProto");
11
- const Defaults_1 = require("../Defaults");
12
- const Types_1 = require("../Types");
13
- const Utils_1 = require("../Utils");
14
- const make_mutex_1 = require("../Utils/make-mutex");
15
- const WABinary_1 = require("../WABinary");
16
- const groups_1 = require("./groups");
17
- const messages_send_1 = require("./messages-send");
18
- const makeMessagesRecvSocket = (config) => {
19
- const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid } = config;
20
- const sock = (0, messages_send_1.makeMessagesSocket)(config);
21
- const { ev, authState, ws, processingMutex, signalRepository, query, upsertMessage, resyncAppState, groupMetadata, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, createParticipantNodes, getUSyncDevices, sendPeerDataOperationMessage, } = sock;
1
+ import NodeCache from '@cacheable/node-cache';
2
+ import { Boom } from '@hapi/boom';
3
+ import { randomBytes } from 'crypto';
4
+ import Long from 'long';
5
+ import { proto } from '../../WAProto/index.js';
6
+ import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT, PLACEHOLDER_MAX_AGE_SECONDS, STATUS_EXPIRY_SECONDS } from '../Defaults/index.js';
7
+ import { ReachoutTimelockEnforcementType, WAMessageStatus, WAMessageStubType } from '../Types/index.js';
8
+ import { ACCOUNT_RESTRICTED_TEXT, aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, extractE2ESessionFromRetryReceipt, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getPlatformType, getStatusFromReceiptType, handleIdentityChange, hkdf, makeShortcakeFlow, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, SERVER_ERROR_CODES, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
9
+ import { makeMutex } from '../Utils/make-mutex.js';
10
+ import { makeOfflineNodeProcessor } from '../Utils/offline-node-processor.js';
11
+ import { buildAckStanza } from '../Utils/stanza-ack.js';
12
+ import { buildMergedTcTokenIndexWrite, isTcTokenExpired, readTcTokenIndex, resolveIssuanceJid, resolveTcTokenJid, storeTcTokensFromIqResult, TC_TOKEN_INDEX_KEY } from '../Utils/tc-token-utils.js';
13
+ import { areJidsSameUser, binaryNodeToString, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, getBinaryNodeChildUInt, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
14
+ import { extractGroupMetadata } from './groups.js';
15
+ import { makeMessagesSocket } from './messages-send.js';
16
+ const ENFORCEMENT_TYPE_VALUES = new Set(Object.values(ReachoutTimelockEnforcementType));
17
+ function isValidEnforcementType(value) {
18
+ return typeof value === 'string' && ENFORCEMENT_TYPE_VALUES.has(value);
19
+ }
20
+ export const makeMessagesRecvSocket = (config) => {
21
+ const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid, enableAutoSessionRecreation } = config;
22
+ const sock = makeMessagesSocket(config);
23
+ const { userDevicesCache, devicesMutex, ev, authState, ws, messageMutex, notificationMutex, receiptMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, messageRetryManager, registerSocketEndHandler, issuePrivacyTokens, fetchAccountReachoutTimelock, placeholderResendCache } = sock;
24
+ const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
25
+ /** Shortcake passkey-linking flow, only when a `signPasskeyAssertion` is configured. */
26
+ const shortcakeFlow = config.signPasskeyAssertion
27
+ ? makeShortcakeFlow({
28
+ logger,
29
+ query,
30
+ signAssertion: config.signPasskeyAssertion,
31
+ getCreds: () => authState.creds,
32
+ updateCreds: patch => ev.emit('creds.update', patch),
33
+ deviceType: getPlatformType(config.browser[1]),
34
+ emitVerificationCode: code => logger.debug({ code }, 'shortcake verification code')
35
+ })
36
+ : null;
37
+ registerSocketEndHandler(() => shortcakeFlow?.clearSession());
38
+ /** Serializes prologue -> continuation off the notification mutex, so the ack isn't blocked by the handshake. */
39
+ const shortcakeMutex = makeMutex();
40
+ /** Runs the forced-passkey handshake (detached); with no signer, surfaces `passkeyRequired` and warns. */
41
+ const handleShortcakeNotification = (node) => {
42
+ if (node.attrs.type === 'passkey_prologue_request') {
43
+ ev.emit('connection.update', { passkeyRequired: { hasSigner: !!shortcakeFlow } });
44
+ }
45
+ if (!shortcakeFlow) {
46
+ if (node.attrs.type === 'passkey_prologue_request') {
47
+ logger.warn({ id: node.attrs.id }, 'server requested passkey prologue but no signPasskeyAssertion configured');
48
+ }
49
+ return;
50
+ }
51
+ // run detached + serialized: the notification is acked immediately, and the
52
+ // handshake (external signer + follow-up IQs) never blocks other notifications
53
+ shortcakeMutex
54
+ .mutex(() => shortcakeFlow.handleIncomingNotification(node))
55
+ .catch(err => logger.error({ err, id: node.attrs.id }, 'shortcake handshake failed'));
56
+ };
22
57
  /** this mutex ensures that each retryRequest will wait for the previous one to finish */
23
- const retryMutex = (0, make_mutex_1.makeMutex)();
24
- const msgRetryCache = config.msgRetryCounterCache || new node_cache_1.default({
25
- stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
26
- useClones: false
27
- });
28
- const callOfferCache = config.callOfferCache || new node_cache_1.default({
29
- stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins
30
- useClones: false
31
- });
32
- const placeholderResendCache = config.placeholderResendCache || new node_cache_1.default({
33
- stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
34
- useClones: false
35
- });
58
+ const retryMutex = makeMutex();
59
+ const msgRetryCache = config.msgRetryCounterCache ||
60
+ new NodeCache({
61
+ stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
62
+ useClones: false
63
+ });
64
+ const callOfferCache = config.callOfferCache ||
65
+ new NodeCache({
66
+ stdTTL: DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins
67
+ useClones: false
68
+ });
69
+ // Debounce identity-change session refreshes per JID to avoid bursts
70
+ const identityAssertDebounce = new NodeCache({ stdTTL: 5, useClones: false });
36
71
  let sendActiveReceipts = false;
37
- const sendMessageAck = async ({ tag, attrs, content }, errorCode) => {
38
- const stanza = {
39
- tag: 'ack',
40
- attrs: {
41
- id: attrs.id,
42
- to: attrs.from,
43
- class: tag
44
- }
72
+ const fetchMessageHistory = async (count, oldestMsgKey, oldestMsgTimestamp) => {
73
+ if (!authState.creds.me?.id) {
74
+ throw new Boom('Not authenticated');
75
+ }
76
+ const pdoMessage = {
77
+ historySyncOnDemandRequest: {
78
+ chatJid: oldestMsgKey.remoteJid,
79
+ oldestMsgFromMe: oldestMsgKey.fromMe,
80
+ oldestMsgId: oldestMsgKey.id,
81
+ oldestMsgTimestampMs: oldestMsgTimestamp,
82
+ onDemandMsgCount: count
83
+ },
84
+ peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.HISTORY_SYNC_ON_DEMAND
45
85
  };
46
- if (!!errorCode) {
47
- stanza.attrs.error = errorCode.toString();
86
+ return sendPeerDataOperationMessage(pdoMessage);
87
+ };
88
+ const requestPlaceholderResend = async (messageKey, msgData) => {
89
+ if (!authState.creds.me?.id) {
90
+ throw new Boom('Not authenticated');
48
91
  }
49
- if (!!attrs.participant) {
50
- stanza.attrs.participant = attrs.participant;
92
+ if (await placeholderResendCache.get(messageKey?.id)) {
93
+ logger.debug({ messageKey }, 'already requested resend');
94
+ return;
51
95
  }
52
- if (!!attrs.recipient) {
53
- stanza.attrs.recipient = attrs.recipient;
96
+ else {
97
+ // Store original message data so PDO response handler can preserve
98
+ // metadata (LID details, timestamps, etc.) that the phone may omit
99
+ await placeholderResendCache.set(messageKey?.id, msgData || true);
54
100
  }
55
- if (!!attrs.type && (tag !== 'message' || (0, WABinary_1.getBinaryNodeChild)({ tag, attrs, content }, 'unavailable') || errorCode !== 0)) {
56
- stanza.attrs.type = attrs.type;
101
+ await delay(2000);
102
+ if (!(await placeholderResendCache.get(messageKey?.id))) {
103
+ logger.debug({ messageKey }, 'message received while resend requested');
104
+ return 'RESOLVED';
57
105
  }
58
- if (tag === 'message' && (0, WABinary_1.getBinaryNodeChild)({ tag, attrs, content }, 'unavailable')) {
59
- stanza.attrs.from = authState.creds.me.id;
106
+ const pdoMessage = {
107
+ placeholderMessageResendRequest: [
108
+ {
109
+ messageKey
110
+ }
111
+ ],
112
+ peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
113
+ };
114
+ setTimeout(async () => {
115
+ if (await placeholderResendCache.get(messageKey?.id)) {
116
+ logger.debug({ messageKey }, 'PDO message without response after 8 seconds. Phone possibly offline');
117
+ await placeholderResendCache.del(messageKey?.id);
118
+ }
119
+ }, 8000);
120
+ return sendPeerDataOperationMessage(pdoMessage);
121
+ };
122
+ const handleMexNotification = async (node) => {
123
+ const updateNode = getBinaryNodeChild(node, 'update');
124
+ if (updateNode) {
125
+ const opName = updateNode.attrs?.op_name;
126
+ if (!opName) {
127
+ logger.warn({ node: binaryNodeToString(node) }, 'mex notification missing op_name, fallback to legacy');
128
+ await handleLegacyMexNewsletterNotification(node);
129
+ return;
130
+ }
131
+ let mexResponse;
132
+ try {
133
+ mexResponse = JSON.parse(updateNode.content.toString());
134
+ }
135
+ catch (error) {
136
+ logger.error({ err: error, opName }, 'failed to parse mex notification JSON');
137
+ return;
138
+ }
139
+ if (mexResponse.errors?.length) {
140
+ logger.warn({ errors: mexResponse.errors, opName }, 'mex notification has GQL errors');
141
+ return;
142
+ }
143
+ const data = mexResponse.data;
144
+ if (!data) {
145
+ logger.warn({ opName }, 'mex notification has null data');
146
+ return;
147
+ }
148
+ logger.debug({ opName }, 'processing mex notification');
149
+ switch (opName) {
150
+ case 'NotificationUserReachoutTimelockUpdate':
151
+ handleReachoutTimelockNotification(data);
152
+ break;
153
+ case 'MessageCappingInfoNotification':
154
+ handleMessageCappingNotification(data);
155
+ break;
156
+ // newsletter ops still use the legacy <mex> child structure
157
+ case 'NotificationNewsletterUpdate':
158
+ case 'NotificationLinkedProfilesUpdates':
159
+ case 'NotificationNewsletterAdminPromote':
160
+ case 'NotificationNewsletterAdminDemote':
161
+ case 'NotificationNewsletterUserSettingChange':
162
+ case 'NotificationNewsletterJoin':
163
+ case 'NotificationNewsletterLeave':
164
+ case 'NotificationNewsletterStateChange':
165
+ case 'NotificationNewsletterAdminMetadataUpdate':
166
+ case 'NotificationNewsletterOwnerUpdate':
167
+ case 'NotificationNewsletterAdminInviteRevoke':
168
+ case 'NotificationNewsletterWamoSubStatusChange':
169
+ case 'NotificationNewsletterBlockUser':
170
+ case 'NotificationNewsletterPaidPartnership':
171
+ case 'NotificationNewsletterMilestone':
172
+ case 'NewsletterResponseStateUpdate':
173
+ await handleLegacyMexNewsletterNotification(node);
174
+ break;
175
+ default:
176
+ logger.debug({ opName }, 'unhandled mex notification');
177
+ break;
178
+ }
179
+ return;
60
180
  }
61
- logger.debug({ recv: { tag, attrs }, sent: stanza.attrs }, 'sent ack');
62
- await sendNode(stanza);
181
+ await handleLegacyMexNewsletterNotification(node);
63
182
  };
64
- const offerCall = async (toJid, isVideo = false) => {
65
- const callId = (0, crypto_1.randomBytes)(16).toString('hex').toUpperCase().substring(0, 64);
66
- const offerContent = [];
67
- offerContent.push({ tag: 'audio', attrs: { enc: 'opus', rate: '16000' }, content: undefined });
68
- offerContent.push({ tag: 'audio', attrs: { enc: 'opus', rate: '8000' }, content: undefined });
69
- if (isVideo) {
70
- offerContent.push({
71
- tag: 'video',
72
- attrs: {
73
- orientation: '0',
74
- 'screen_width': '1920',
75
- 'screen_height': '1080',
76
- 'device_orientation': '0',
77
- enc: 'vp8',
78
- dec: 'vp8',
183
+ const handleReachoutTimelockNotification = (data) => {
184
+ const payload = data.xwa2_notify_account_reachout_timelock;
185
+ if (!payload) {
186
+ logger.warn('reachout timelock notification missing payload');
187
+ return;
188
+ }
189
+ if (!payload.is_active) {
190
+ logger.info('reachout timelock restriction lifted');
191
+ ev.emit('connection.update', {
192
+ reachoutTimeLock: {
193
+ isActive: false,
194
+ enforcementType: ReachoutTimelockEnforcementType.DEFAULT
79
195
  }
80
196
  });
197
+ return;
81
198
  }
82
- offerContent.push({ tag: 'net', attrs: { medium: '3' }, content: undefined });
83
- offerContent.push({ tag: 'capability', attrs: { ver: '1' }, content: new Uint8Array([1, 4, 255, 131, 207, 4]) });
84
- offerContent.push({ tag: 'encopt', attrs: { keygen: '2' }, content: undefined });
85
- const encKey = (0, crypto_1.randomBytes)(32);
86
- const devices = (await getUSyncDevices([toJid], true, false)).map(({ user, device }) => (0, WABinary_1.jidEncode)(user, 's.whatsapp.net', device));
87
- await assertSessions(devices, true);
88
- const { nodes: destinations, shouldIncludeDeviceIdentity } = await createParticipantNodes(devices, {
89
- call: {
90
- callKey: encKey
199
+ // WA Web defaults to now+60s when the server omits the expiry
200
+ const timeEnforcementEnds = payload.time_enforcement_ends
201
+ ? new Date(parseInt(payload.time_enforcement_ends, 10) * 1000)
202
+ : new Date(Date.now() + 60000);
203
+ const enforcementType = isValidEnforcementType(payload.enforcement_type)
204
+ ? payload.enforcement_type
205
+ : ReachoutTimelockEnforcementType.DEFAULT;
206
+ logger.info({ enforcementType, timeEnforcementEnds }, 'reachout timelock restriction set');
207
+ ev.emit('connection.update', {
208
+ reachoutTimeLock: {
209
+ isActive: true,
210
+ timeEnforcementEnds,
211
+ enforcementType
91
212
  }
92
213
  });
93
- offerContent.push({ tag: 'destination', attrs: {}, content: destinations });
94
- if (shouldIncludeDeviceIdentity) {
95
- offerContent.push({
96
- tag: 'device-identity',
97
- attrs: {},
98
- content: (0, Utils_1.encodeSignedDeviceIdentity)(authState.creds.account, true)
99
- });
214
+ };
215
+ const handleMessageCappingNotification = (data) => {
216
+ const payload = data.xwa2_notify_new_chat_messages_capping_info_update;
217
+ if (!payload) {
218
+ logger.warn('message capping notification missing payload');
219
+ return;
100
220
  }
101
- const stanza = ({
102
- tag: 'call',
103
- attrs: {
104
- to: toJid,
105
- },
106
- content: [{
107
- tag: 'offer',
108
- attrs: {
109
- 'call-id': callId,
110
- 'call-creator': authState.creds.me.id,
111
- },
112
- content: offerContent,
113
- }],
114
- });
115
- await query(stanza);
116
- return {
117
- callId,
118
- toJid,
119
- isVideo,
120
- };
221
+ logger.info({ payload }, 'received message capping update');
222
+ ev.emit('message-capping.update', payload);
223
+ };
224
+ const handleLegacyMexNewsletterNotification = async (node) => {
225
+ const mexNode = getBinaryNodeChild(node, 'mex');
226
+ const updateNode = mexNode?.content ? null : getBinaryNodeChild(node, 'update') || getAllBinaryNodeChildren(node)[0];
227
+ const payloadNode = mexNode?.content ? mexNode : updateNode;
228
+ if (!payloadNode?.content) {
229
+ logger.warn({ node: binaryNodeToString(node) }, 'invalid mex newsletter notification');
230
+ return;
231
+ }
232
+ let data;
233
+ try {
234
+ const payloadContent = payloadNode.content;
235
+ if (Array.isArray(payloadContent)) {
236
+ logger.warn({ payloadNode }, 'invalid mex newsletter notification payload format');
237
+ return;
238
+ }
239
+ const contentBuf = typeof payloadContent === 'string' ? Buffer.from(payloadContent, 'binary') : Buffer.from(payloadContent);
240
+ data = JSON.parse(contentBuf.toString());
241
+ }
242
+ catch (error) {
243
+ logger.error({ err: error, node: binaryNodeToString(node) }, 'failed to parse mex newsletter notification');
244
+ return;
245
+ }
246
+ const operation = data?.operation ?? payloadNode?.attrs?.op_name;
247
+ let updates = data?.updates;
248
+ if (!updates) {
249
+ const linkedProfiles = data?.data?.xwa2_notify_linked_profiles;
250
+ if (linkedProfiles) {
251
+ updates = [linkedProfiles];
252
+ }
253
+ }
254
+ if (!updates || !operation) {
255
+ logger.warn({ data }, 'invalid mex newsletter notification content');
256
+ return;
257
+ }
258
+ logger.info({ operation, updates }, 'got mex newsletter notification');
259
+ switch (operation) {
260
+ case 'NotificationNewsletterUpdate':
261
+ for (const update of updates) {
262
+ if (update.jid && update.settings && Object.keys(update.settings).length > 0) {
263
+ ev.emit('newsletter-settings.update', {
264
+ id: update.jid,
265
+ update: update.settings
266
+ });
267
+ }
268
+ }
269
+ break;
270
+ case 'NotificationNewsletterAdminPromote':
271
+ for (const update of updates) {
272
+ if (update.jid && update.user) {
273
+ ev.emit('newsletter-participants.update', {
274
+ id: update.jid,
275
+ author: node.attrs.from,
276
+ user: update.user,
277
+ new_role: 'ADMIN',
278
+ action: 'promote'
279
+ });
280
+ }
281
+ }
282
+ break;
283
+ case 'NotificationLinkedProfilesUpdates':
284
+ for (const update of updates) {
285
+ const lid = update?.jid;
286
+ const addedProfiles = Array.isArray(update?.added_profiles) ? update.added_profiles : [];
287
+ const mappings = [];
288
+ for (const profile of addedProfiles) {
289
+ const pn = typeof profile === 'string' ? profile : (profile?.pn ?? profile?.jid ?? null);
290
+ if (lid && pn) {
291
+ const mapping = { lid, pn };
292
+ ev.emit('lid-mapping.update', mapping);
293
+ mappings.push(mapping);
294
+ }
295
+ }
296
+ await signalRepository.lidMapping.storeLIDPNMappings(mappings);
297
+ }
298
+ break;
299
+ default:
300
+ logger.info({ operation, data }, 'unhandled mex newsletter notification');
301
+ break;
302
+ }
303
+ };
304
+ // Handles newsletter notifications
305
+ const handleNewsletterNotification = async (node) => {
306
+ const from = node.attrs.from;
307
+ const children = getAllBinaryNodeChildren(node);
308
+ const author = node.attrs.participant;
309
+ for (const child of children) {
310
+ logger.debug({ from, child }, 'got newsletter notification');
311
+ switch (child.tag) {
312
+ case 'reaction': {
313
+ const reactionUpdate = {
314
+ id: from,
315
+ server_id: child.attrs.message_id,
316
+ reaction: {
317
+ code: getBinaryNodeChildString(child, 'reaction'),
318
+ count: 1
319
+ }
320
+ };
321
+ ev.emit('newsletter.reaction', reactionUpdate);
322
+ break;
323
+ }
324
+ case 'view': {
325
+ const viewUpdate = {
326
+ id: from,
327
+ server_id: child.attrs.message_id,
328
+ count: parseInt(child.content?.toString() || '0', 10)
329
+ };
330
+ ev.emit('newsletter.view', viewUpdate);
331
+ break;
332
+ }
333
+ case 'participant': {
334
+ const participantUpdate = {
335
+ id: from,
336
+ author,
337
+ user: child.attrs.jid,
338
+ action: child.attrs.action,
339
+ new_role: child.attrs.role
340
+ };
341
+ ev.emit('newsletter-participants.update', participantUpdate);
342
+ break;
343
+ }
344
+ case 'update': {
345
+ const settingsNode = getBinaryNodeChild(child, 'settings');
346
+ if (settingsNode) {
347
+ const update = {};
348
+ const nameNode = getBinaryNodeChild(settingsNode, 'name');
349
+ if (nameNode?.content)
350
+ update.name = nameNode.content.toString();
351
+ const descriptionNode = getBinaryNodeChild(settingsNode, 'description');
352
+ if (descriptionNode?.content)
353
+ update.description = descriptionNode.content.toString();
354
+ ev.emit('newsletter-settings.update', {
355
+ id: from,
356
+ update
357
+ });
358
+ }
359
+ break;
360
+ }
361
+ case 'message': {
362
+ const plaintextNode = getBinaryNodeChild(child, 'plaintext');
363
+ if (plaintextNode?.content) {
364
+ try {
365
+ const contentBuf = typeof plaintextNode.content === 'string'
366
+ ? Buffer.from(plaintextNode.content, 'binary')
367
+ : Buffer.from(plaintextNode.content);
368
+ const messageProto = proto.Message.decode(contentBuf).toJSON();
369
+ const fullMessage = proto.WebMessageInfo.fromObject({
370
+ key: {
371
+ remoteJid: from,
372
+ id: child.attrs.message_id || child.attrs.server_id,
373
+ fromMe: false // TODO: is this really true though
374
+ },
375
+ message: messageProto,
376
+ messageTimestamp: +child.attrs.t
377
+ }).toJSON();
378
+ await upsertMessage(fullMessage, 'append');
379
+ logger.debug('Processed plaintext newsletter message');
380
+ }
381
+ catch (error) {
382
+ logger.error({ error }, 'Failed to decode plaintext newsletter message');
383
+ }
384
+ }
385
+ break;
386
+ }
387
+ default:
388
+ logger.warn({ node, child }, 'Unknown newsletter notification child');
389
+ break;
390
+ }
391
+ }
392
+ };
393
+ const sendMessageAck = async (node, errorCode) => {
394
+ const stanza = buildAckStanza(node, errorCode, authState.creds.me.id);
395
+ logger.debug({ recv: { tag: node.tag, attrs: node.attrs }, sent: stanza.attrs }, 'sent ack');
396
+ await sendNode(stanza);
121
397
  };
122
398
  const rejectCall = async (callId, callFrom) => {
123
- const stanza = ({
399
+ const stanza = {
124
400
  tag: 'call',
125
401
  attrs: {
126
402
  from: authState.creds.me.id,
127
- to: callFrom,
403
+ to: callFrom
128
404
  },
129
- content: [{
405
+ content: [
406
+ {
130
407
  tag: 'reject',
131
408
  attrs: {
132
409
  'call-id': callId,
133
410
  'call-creator': callFrom,
134
- count: '0',
411
+ count: '0'
135
412
  },
136
- content: undefined,
137
- }],
138
- });
413
+ content: undefined
414
+ }
415
+ ]
416
+ };
139
417
  await query(stanza);
140
418
  };
141
419
  const sendRetryRequest = async (node, forceIncludeKeys = false) => {
142
- const { fullMessage } = (0, Utils_1.decodeMessageNode)(node, authState.creds.me.id, authState.creds.me.lid || '');
420
+ const { fullMessage } = decodeMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '');
143
421
  const { key: msgKey } = fullMessage;
144
422
  const msgId = msgKey.id;
145
- const key = `${msgId}:${msgKey === null || msgKey === void 0 ? void 0 : msgKey.participant}`;
146
- let retryCount = msgRetryCache.get(key) || 0;
147
- if (retryCount >= maxMsgRetryCount) {
148
- logger.debug({ retryCount, msgId }, 'reached retry limit, clearing');
149
- msgRetryCache.del(key);
150
- return;
423
+ if (messageRetryManager) {
424
+ // Check if we've exceeded max retries using the new system
425
+ if (messageRetryManager.hasExceededMaxRetries(msgId)) {
426
+ logger.debug({ msgId }, 'reached retry limit with new retry manager, clearing');
427
+ messageRetryManager.markRetryFailed(msgId);
428
+ return;
429
+ }
430
+ // Increment retry count using new system
431
+ const retryCount = messageRetryManager.incrementRetryCount(msgId);
432
+ // Use the new retry count for the rest of the logic
433
+ const key = `${msgId}:${msgKey?.participant}`;
434
+ await msgRetryCache.set(key, retryCount);
151
435
  }
152
- retryCount += 1;
153
- msgRetryCache.set(key, retryCount);
436
+ else {
437
+ // Fallback to old system
438
+ const key = `${msgId}:${msgKey?.participant}`;
439
+ let retryCount = (await msgRetryCache.get(key)) || 0;
440
+ if (retryCount >= maxMsgRetryCount) {
441
+ logger.debug({ retryCount, msgId }, 'reached retry limit, clearing');
442
+ await msgRetryCache.del(key);
443
+ return;
444
+ }
445
+ retryCount += 1;
446
+ await msgRetryCache.set(key, retryCount);
447
+ }
448
+ const key = `${msgId}:${msgKey?.participant}`;
449
+ const retryCount = (await msgRetryCache.get(key)) || 1;
154
450
  const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds;
155
- if (retryCount === 1) {
156
- //request a resend via phone
157
- const msgId = await requestPlaceholderResend(msgKey);
158
- logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`);
451
+ const fromJid = node.attrs.from;
452
+ // Check if we should recreate the session
453
+ let shouldRecreateSession = false;
454
+ let recreateReason = '';
455
+ if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1) {
456
+ try {
457
+ // Check if we have a session with this JID
458
+ const sessionId = signalRepository.jidToSignalProtocolAddress(fromJid);
459
+ const hasSession = await signalRepository.validateSession(fromJid);
460
+ const result = messageRetryManager.shouldRecreateSession(fromJid, hasSession.exists);
461
+ shouldRecreateSession = result.recreate;
462
+ recreateReason = result.reason;
463
+ if (shouldRecreateSession) {
464
+ logger.debug({ fromJid, retryCount, reason: recreateReason }, 'recreating session for retry');
465
+ // Delete existing session to force recreation
466
+ await authState.keys.set({ session: { [sessionId]: null } });
467
+ forceIncludeKeys = true;
468
+ }
469
+ }
470
+ catch (error) {
471
+ logger.warn({ error, fromJid }, 'failed to check session recreation');
472
+ }
159
473
  }
160
- const deviceIdentity = (0, Utils_1.encodeSignedDeviceIdentity)(account, true);
474
+ if (retryCount <= 2) {
475
+ // Use new retry manager for phone requests if available
476
+ if (messageRetryManager) {
477
+ // Schedule phone request with delay (like whatsmeow)
478
+ messageRetryManager.schedulePhoneRequest(msgId, async () => {
479
+ try {
480
+ const requestId = await requestPlaceholderResend(msgKey);
481
+ logger.debug(`sendRetryRequest: requested placeholder resend (${requestId}) for message ${msgId} (scheduled)`);
482
+ }
483
+ catch (error) {
484
+ logger.warn({ error, msgId }, 'failed to send scheduled phone request');
485
+ }
486
+ });
487
+ }
488
+ else {
489
+ // Fallback to immediate request
490
+ const msgId = await requestPlaceholderResend(msgKey);
491
+ logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`);
492
+ }
493
+ }
494
+ const deviceIdentity = encodeSignedDeviceIdentity(account, true);
161
495
  await authState.keys.transaction(async () => {
162
496
  const receipt = {
163
497
  tag: 'receipt',
@@ -173,13 +507,15 @@ const makeMessagesRecvSocket = (config) => {
173
507
  count: retryCount.toString(),
174
508
  id: node.attrs.id,
175
509
  t: node.attrs.t,
176
- v: '1'
510
+ v: '1',
511
+ // ADD ERROR FIELD
512
+ error: '0'
177
513
  }
178
514
  },
179
515
  {
180
516
  tag: 'registration',
181
517
  attrs: {},
182
- content: (0, Utils_1.encodeBigEndian)(authState.creds.registrationId)
518
+ content: encodeBigEndian(authState.creds.registrationId)
183
519
  }
184
520
  ]
185
521
  };
@@ -189,8 +525,8 @@ const makeMessagesRecvSocket = (config) => {
189
525
  if (node.attrs.participant) {
190
526
  receipt.attrs.participant = node.attrs.participant;
191
527
  }
192
- if (retryCount > 1 || forceIncludeKeys) {
193
- const { update, preKeys } = await (0, Utils_1.getNextPreKeys)(authState, 1);
528
+ if (retryCount > 1 || forceIncludeKeys || shouldRecreateSession) {
529
+ const { update, preKeys } = await getNextPreKeys(authState, 1);
194
530
  const [keyId] = Object.keys(preKeys);
195
531
  const key = preKeys[+keyId];
196
532
  const content = receipt.content;
@@ -198,10 +534,10 @@ const makeMessagesRecvSocket = (config) => {
198
534
  tag: 'keys',
199
535
  attrs: {},
200
536
  content: [
201
- { tag: 'type', attrs: {}, content: Buffer.from(Defaults_1.KEY_BUNDLE_TYPE) },
537
+ { tag: 'type', attrs: {}, content: Buffer.from(KEY_BUNDLE_TYPE) },
202
538
  { tag: 'identity', attrs: {}, content: identityKey.public },
203
- (0, Utils_1.xmppPreKey)(key, +keyId),
204
- (0, Utils_1.xmppSignedPreKey)(signedPreKey),
539
+ xmppPreKey(key, +keyId),
540
+ xmppSignedPreKey(signedPreKey),
205
541
  { tag: 'device-identity', attrs: {}, content: deviceIdentity }
206
542
  ]
207
543
  });
@@ -209,63 +545,119 @@ const makeMessagesRecvSocket = (config) => {
209
545
  }
210
546
  await sendNode(receipt);
211
547
  logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt');
548
+ }, authState?.creds?.me?.id || 'sendRetryRequest');
549
+ };
550
+ // Mirrors WAWeb/Handle/PreKeyLow.js: skip a re-issued notification with the same stanza id.
551
+ const inFlightPreKeyLow = new Set();
552
+ /**
553
+ * Fire-and-forget tctoken re-issuance after a peer's device identity changed.
554
+ * Mirrors WAWebSendTcTokenWhenDeviceIdentityChange — runs in parallel with
555
+ * the session refresh (not after it).
556
+ */
557
+ const reissueTcTokenAfterIdentityChange = (from) => {
558
+ void (async () => {
559
+ const normalizedJid = jidNormalizedUser(from);
560
+ const tcJid = await resolveTcTokenJid(normalizedJid, getLIDForPN);
561
+ const tcTokenData = await authState.keys.get('tctoken', [tcJid]);
562
+ const senderTs = tcTokenData?.[tcJid]?.senderTimestamp;
563
+ if (senderTs === null || senderTs === undefined || isTcTokenExpired(senderTs)) {
564
+ return;
565
+ }
566
+ logger.debug({ jid: normalizedJid, senderTimestamp: senderTs }, 'identity changed, re-issuing tctoken');
567
+ const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
568
+ const issueJid = await resolveIssuanceJid(normalizedJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID);
569
+ const result = await issuePrivacyTokens([issueJid], senderTs);
570
+ await storeTcTokensFromIqResult({
571
+ result,
572
+ fallbackJid: tcJid,
573
+ keys: authState.keys,
574
+ getLIDForPN,
575
+ onNewJidStored: trackTcTokenJid
576
+ });
577
+ })().catch(err => {
578
+ logger.debug({ jid: from, err: err?.message }, 'failed to re-issue tctoken after identity change');
212
579
  });
213
580
  };
214
581
  const handleEncryptNotification = async (node) => {
215
582
  const from = node.attrs.from;
216
- if (from === WABinary_1.S_WHATSAPP_NET) {
217
- const countChild = (0, WABinary_1.getBinaryNodeChild)(node, 'count');
583
+ if (from === S_WHATSAPP_NET) {
584
+ const stanzaId = node.attrs.id;
585
+ if (stanzaId && inFlightPreKeyLow.has(stanzaId)) {
586
+ return;
587
+ }
588
+ const countChild = getBinaryNodeChild(node, 'count');
218
589
  const count = +countChild.attrs.value;
219
- const shouldUploadMorePreKeys = count < Defaults_1.MIN_PREKEY_COUNT;
590
+ const shouldUploadMorePreKeys = count < MIN_PREKEY_COUNT;
220
591
  logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count');
221
592
  if (shouldUploadMorePreKeys) {
222
- await uploadPreKeys();
593
+ if (stanzaId)
594
+ inFlightPreKeyLow.add(stanzaId);
595
+ try {
596
+ await uploadPreKeys();
597
+ }
598
+ finally {
599
+ if (stanzaId)
600
+ inFlightPreKeyLow.delete(stanzaId);
601
+ }
223
602
  }
224
603
  }
225
604
  else {
226
- const identityNode = (0, WABinary_1.getBinaryNodeChild)(node, 'identity');
227
- if (identityNode) {
228
- logger.info({ jid: from }, 'identity changed');
229
- // not handling right now
230
- // signal will override new identity anyway
231
- }
232
- else {
605
+ const result = await handleIdentityChange(node, {
606
+ meId: authState.creds.me?.id,
607
+ meLid: authState.creds.me?.lid,
608
+ validateSession: signalRepository.validateSession,
609
+ assertSessions,
610
+ debounceCache: identityAssertDebounce,
611
+ logger,
612
+ onBeforeSessionRefresh: reissueTcTokenAfterIdentityChange
613
+ });
614
+ if (result.action === 'no_identity_node') {
233
615
  logger.info({ node }, 'unknown encrypt notification');
234
616
  }
235
617
  }
236
618
  };
237
- const handleGroupNotification = (participant, child, msg) => {
238
- var _a, _b, _c, _d;
239
- const participantJid = ((_b = (_a = (0, WABinary_1.getBinaryNodeChild)(child, 'participant')) === null || _a === void 0 ? void 0 : _a.attrs) === null || _b === void 0 ? void 0 : _b.jid) || participant;
240
- switch (child === null || child === void 0 ? void 0 : child.tag) {
619
+ const handleGroupNotification = (fullNode, child, msg) => {
620
+ // TODO: Support PN/LID (Here is only LID now)
621
+ const actingParticipantLid = fullNode.attrs.participant;
622
+ const actingParticipantPn = fullNode.attrs.participant_pn;
623
+ const actingParticipantUsername = fullNode.attrs.participant_username;
624
+ const affectedParticipantLid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || actingParticipantLid;
625
+ const affectedParticipantPn = getBinaryNodeChild(child, 'participant')?.attrs?.phone_number || actingParticipantPn;
626
+ switch (child?.tag) {
241
627
  case 'create':
242
- const metadata = (0, groups_1.extractGroupMetadata)(child);
243
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_CREATE;
628
+ const metadata = extractGroupMetadata(child);
629
+ msg.messageStubType = WAMessageStubType.GROUP_CREATE;
244
630
  msg.messageStubParameters = [metadata.subject];
245
- msg.key = { participant: metadata.owner };
246
- ev.emit('chats.upsert', [{
631
+ msg.key = { participant: metadata.owner, participantAlt: metadata.ownerPn };
632
+ ev.emit('chats.upsert', [
633
+ {
247
634
  id: metadata.id,
248
635
  name: metadata.subject,
249
- conversationTimestamp: metadata.creation,
250
- }]);
251
- ev.emit('groups.upsert', [{
636
+ conversationTimestamp: metadata.creation
637
+ }
638
+ ]);
639
+ ev.emit('groups.upsert', [
640
+ {
252
641
  ...metadata,
253
- author: participant
254
- }]);
642
+ author: actingParticipantLid,
643
+ authorPn: actingParticipantPn,
644
+ authorUsername: actingParticipantUsername
645
+ }
646
+ ]);
255
647
  break;
256
648
  case 'ephemeral':
257
649
  case 'not_ephemeral':
258
650
  msg.message = {
259
651
  protocolMessage: {
260
- type: WAProto_1.proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
652
+ type: proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
261
653
  ephemeralExpiration: +(child.attrs.expiration || 0)
262
654
  }
263
655
  };
264
656
  break;
265
657
  case 'modify':
266
- const oldNumber = (0, WABinary_1.getBinaryNodeChildren)(child, 'participant').map(p => p.attrs.jid);
658
+ const oldNumber = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid);
267
659
  msg.messageStubParameters = oldNumber || [];
268
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER;
660
+ msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER;
269
661
  break;
270
662
  case 'promote':
271
663
  case 'demote':
@@ -273,179 +665,225 @@ const makeMessagesRecvSocket = (config) => {
273
665
  case 'add':
274
666
  case 'leave':
275
667
  const stubType = `GROUP_PARTICIPANT_${child.tag.toUpperCase()}`;
276
- msg.messageStubType = Types_1.WAMessageStubType[stubType];
277
- const participants = (0, WABinary_1.getBinaryNodeChildren)(child, 'participant').map(p => p.attrs.jid);
668
+ msg.messageStubType = WAMessageStubType[stubType];
669
+ const participants = getBinaryNodeChildren(child, 'participant').map(({ attrs }) => {
670
+ const isLid = isLidUser(attrs.jid);
671
+ const hasPn = isPnUser(attrs.phone_number);
672
+ return {
673
+ id: isLid && hasPn ? attrs.phone_number : attrs.jid,
674
+ phoneNumber: isLid && hasPn ? attrs.phone_number : undefined,
675
+ lid: isLid ? attrs.jid : isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined,
676
+ username: attrs.participant_username || attrs.username || undefined,
677
+ admin: (attrs.type || null)
678
+ };
679
+ });
278
680
  if (participants.length === 1 &&
279
681
  // if recv. "remove" message and sender removed themselves
280
682
  // mark as left
281
- (0, WABinary_1.areJidsSameUser)(participants[0], participant) &&
683
+ (areJidsSameUser(participants[0].id, actingParticipantLid) ||
684
+ areJidsSameUser(participants[0].id, actingParticipantPn)) &&
282
685
  child.tag === 'remove') {
283
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_PARTICIPANT_LEAVE;
686
+ msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_LEAVE;
284
687
  }
285
- msg.messageStubParameters = participants;
688
+ msg.messageStubParameters = participants.map(a => JSON.stringify(a));
286
689
  break;
287
690
  case 'subject':
288
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_SUBJECT;
691
+ msg.messageStubType = WAMessageStubType.GROUP_CHANGE_SUBJECT;
289
692
  msg.messageStubParameters = [child.attrs.subject];
290
693
  break;
291
694
  case 'description':
292
- const description = (_d = (_c = (0, WABinary_1.getBinaryNodeChild)(child, 'body')) === null || _c === void 0 ? void 0 : _c.content) === null || _d === void 0 ? void 0 : _d.toString();
293
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_DESCRIPTION;
695
+ const description = getBinaryNodeChild(child, 'body')?.content?.toString();
696
+ msg.messageStubType = WAMessageStubType.GROUP_CHANGE_DESCRIPTION;
294
697
  msg.messageStubParameters = description ? [description] : undefined;
295
698
  break;
296
699
  case 'announcement':
297
700
  case 'not_announcement':
298
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_ANNOUNCE;
299
- msg.messageStubParameters = [(child.tag === 'announcement') ? 'on' : 'off'];
701
+ msg.messageStubType = WAMessageStubType.GROUP_CHANGE_ANNOUNCE;
702
+ msg.messageStubParameters = [child.tag === 'announcement' ? 'on' : 'off'];
300
703
  break;
301
704
  case 'locked':
302
705
  case 'unlocked':
303
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_RESTRICT;
304
- msg.messageStubParameters = [(child.tag === 'locked') ? 'on' : 'off'];
706
+ msg.messageStubType = WAMessageStubType.GROUP_CHANGE_RESTRICT;
707
+ msg.messageStubParameters = [child.tag === 'locked' ? 'on' : 'off'];
305
708
  break;
306
709
  case 'invite':
307
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_INVITE_LINK;
710
+ msg.messageStubType = WAMessageStubType.GROUP_CHANGE_INVITE_LINK;
308
711
  msg.messageStubParameters = [child.attrs.code];
309
712
  break;
310
713
  case 'member_add_mode':
311
714
  const addMode = child.content;
312
715
  if (addMode) {
313
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBER_ADD_MODE;
716
+ msg.messageStubType = WAMessageStubType.GROUP_MEMBER_ADD_MODE;
314
717
  msg.messageStubParameters = [addMode.toString()];
315
718
  }
316
719
  break;
317
720
  case 'membership_approval_mode':
318
- const approvalMode = (0, WABinary_1.getBinaryNodeChild)(child, 'group_join');
721
+ const approvalMode = getBinaryNodeChild(child, 'group_join');
319
722
  if (approvalMode) {
320
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE;
723
+ msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE;
321
724
  msg.messageStubParameters = [approvalMode.attrs.state];
322
725
  }
323
726
  break;
324
727
  case 'created_membership_requests':
325
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
326
- msg.messageStubParameters = [participantJid, 'created', child.attrs.request_method];
728
+ msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
729
+ msg.messageStubParameters = [
730
+ JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
731
+ 'created',
732
+ child.attrs.request_method
733
+ ];
327
734
  break;
328
735
  case 'revoked_membership_requests':
329
- const isDenied = (0, WABinary_1.areJidsSameUser)(participantJid, participant);
330
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
331
- msg.messageStubParameters = [participantJid, isDenied ? 'revoked' : 'rejected'];
736
+ const isDenied = areJidsSameUser(affectedParticipantLid, actingParticipantLid);
737
+ // TODO: LIDMAPPING SUPPORT
738
+ msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
739
+ msg.messageStubParameters = [
740
+ JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
741
+ isDenied ? 'revoked' : 'rejected'
742
+ ];
332
743
  break;
333
- break;
334
- default:
335
- // console.log("BAILEYS-DEBUG:", JSON.stringify({ ...child, content: Buffer.isBuffer(child.content) ? child.content.toString() : child.content, participant }, null, 2))
336
744
  }
337
745
  };
338
- const handleNewsletterNotification = (id, node) => {
339
- const messages = (0, WABinary_1.getBinaryNodeChild)(node, 'messages');
340
- const message = (0, WABinary_1.getBinaryNodeChild)(messages, 'message');
341
- const serverId = message.attrs.server_id;
342
- const reactionsList = (0, WABinary_1.getBinaryNodeChild)(message, 'reactions');
343
- const viewsList = (0, WABinary_1.getBinaryNodeChildren)(message, 'views_count');
344
- if (reactionsList) {
345
- const reactions = (0, WABinary_1.getBinaryNodeChildren)(reactionsList, 'reaction');
346
- if (reactions.length === 0) {
347
- ev.emit('newsletter.reaction', { id, 'server_id': serverId, reaction: { removed: true } });
348
- }
349
- reactions.forEach(item => {
350
- var _a, _b;
351
- ev.emit('newsletter.reaction', { id, 'server_id': serverId, reaction: { code: (_a = item.attrs) === null || _a === void 0 ? void 0 : _a.code, count: +((_b = item.attrs) === null || _b === void 0 ? void 0 : _b.count) } });
352
- });
746
+ const handleDevicesNotification = async (node) => {
747
+ const [child] = getAllBinaryNodeChildren(node);
748
+ const from = jidNormalizedUser(node.attrs.from);
749
+ if (!child) {
750
+ logger.debug({ from }, 'devices notification missing child, skipping');
751
+ return;
353
752
  }
354
- if (viewsList.length) {
355
- viewsList.forEach(item => {
356
- ev.emit('newsletter.view', { id, 'server_id': serverId, count: +item.attrs.count });
357
- });
753
+ const tag = child.tag;
754
+ const deviceHash = child.attrs.device_hash;
755
+ const devices = getBinaryNodeChildren(child, 'device');
756
+ if (areJidsSameUser(from, authState.creds.me.id) || areJidsSameUser(from, authState.creds.me.lid)) {
757
+ const deviceJids = devices.map(d => d.attrs.jid);
758
+ logger.info({ deviceJids }, 'got my own devices');
358
759
  }
359
- };
360
- const handleMexNewsletterNotification = (id, node) => {
361
- var _a;
362
- const operation = node === null || node === void 0 ? void 0 : node.attrs.op_name;
363
- const content = JSON.parse((_a = node === null || node === void 0 ? void 0 : node.content) === null || _a === void 0 ? void 0 : _a.toString());
364
- let contentPath;
365
- if (operation === Types_1.MexOperations.PROMOTE || operation === Types_1.MexOperations.DEMOTE) {
366
- let action;
367
- if (operation === Types_1.MexOperations.PROMOTE) {
368
- action = 'promote';
369
- contentPath = content.data[Types_1.XWAPaths.PROMOTE];
370
- }
371
- if (operation === Types_1.MexOperations.DEMOTE) {
372
- action = 'demote';
373
- contentPath = content.data[Types_1.XWAPaths.DEMOTE];
374
- }
375
- ev.emit('newsletter-participants.update', { id, author: contentPath.actor.pn, user: contentPath.user.pn, new_role: contentPath.user_new_role, action });
760
+ if (!devices.length) {
761
+ logger.debug({ from, tag }, 'no devices in notification, skipping');
762
+ return;
376
763
  }
377
- if (operation === Types_1.MexOperations.UPDATE) {
378
- contentPath = content.data[Types_1.XWAPaths.METADATA_UPDATE];
379
- ev.emit('newsletter-settings.update', { id, update: contentPath.thread_metadata.settings });
764
+ const decoded = [];
765
+ for (const d of devices) {
766
+ const jid = d.attrs.jid;
767
+ if (!jid)
768
+ continue;
769
+ const parts = jidDecode(jid);
770
+ if (!parts) {
771
+ logger.debug({ jid }, 'failed to decode device jid, skipping');
772
+ continue;
773
+ }
774
+ decoded.push({ jid, user: parts.user, server: parts.server, device: parts.device });
380
775
  }
776
+ if (!decoded.length)
777
+ return;
778
+ await devicesMutex.mutex(async () => {
779
+ const byUser = new Map();
780
+ for (const d of decoded) {
781
+ const list = byUser.get(d.user) || [];
782
+ list.push(d);
783
+ byUser.set(d.user, list);
784
+ }
785
+ for (const [user, entries] of byUser) {
786
+ if (tag === 'update') {
787
+ logger.debug({ user }, `${user}'s device list updated, dropping cached devices`);
788
+ await userDevicesCache?.del(user);
789
+ continue;
790
+ }
791
+ if (tag === 'remove') {
792
+ await signalRepository.deleteSession(entries.map(e => e.jid));
793
+ }
794
+ const existingCache = (await userDevicesCache?.get(user)) || [];
795
+ if (!existingCache.length) {
796
+ // No baseline yet; skip applying the delta so getUSyncDevices can
797
+ // later fetch the full device list. Caching just the notification
798
+ // entries would make a partial list look authoritative.
799
+ logger.debug({ user, tag }, 'device list not cached, deferring to USync refresh');
800
+ continue;
801
+ }
802
+ const affected = new Set(entries.map(e => e.device));
803
+ let updatedDevices;
804
+ switch (tag) {
805
+ case 'add':
806
+ logger.info({ deviceHash, count: entries.length }, 'devices added');
807
+ updatedDevices = [
808
+ ...existingCache.filter(d => !affected.has(d.device)),
809
+ ...entries.map(e => ({ user: e.user, server: e.server, device: e.device }))
810
+ ];
811
+ break;
812
+ case 'remove':
813
+ logger.info({ deviceHash, count: entries.length }, 'devices removed');
814
+ updatedDevices = existingCache.filter(d => !affected.has(d.device));
815
+ break;
816
+ default:
817
+ logger.debug({ tag }, 'Unknown device list change tag');
818
+ continue;
819
+ }
820
+ if (updatedDevices.length === 0) {
821
+ await userDevicesCache?.del(user);
822
+ }
823
+ else {
824
+ await userDevicesCache?.set(user, updatedDevices);
825
+ }
826
+ }
827
+ });
381
828
  };
382
829
  const processNotification = async (node) => {
383
- var _a, _b;
384
830
  const result = {};
385
- const [child] = (0, WABinary_1.getAllBinaryNodeChildren)(node);
831
+ const [child] = getAllBinaryNodeChildren(node);
386
832
  const nodeType = node.attrs.type;
387
- const from = (0, WABinary_1.jidNormalizedUser)(node.attrs.from);
833
+ const from = jidNormalizedUser(node.attrs.from);
388
834
  switch (nodeType) {
389
- case 'privacy_token':
390
- const tokenList = (0, WABinary_1.getBinaryNodeChildren)(child, 'token');
391
- for (const { attrs, content } of tokenList) {
392
- const jid = attrs.jid;
393
- ev.emit('chats.update', [
394
- {
395
- id: jid,
396
- tcToken: content
397
- }
398
- ]);
399
- logger.debug({ jid }, 'got privacy token update');
400
- }
401
- break;
402
835
  case 'newsletter':
403
- handleNewsletterNotification(node.attrs.from, child);
836
+ await handleNewsletterNotification(node);
404
837
  break;
405
838
  case 'mex':
406
- handleMexNewsletterNotification(node.attrs.from, child);
839
+ await handleMexNotification(node);
407
840
  break;
408
841
  case 'w:gp2':
409
- handleGroupNotification(node.attrs.participant, child, result);
842
+ // TODO: HANDLE PARTICIPANT_PN
843
+ handleGroupNotification(node, child, result);
410
844
  break;
411
845
  case 'mediaretry':
412
- const event = (0, Utils_1.decodeMediaRetryNode)(node);
846
+ const event = decodeMediaRetryNode(node);
413
847
  ev.emit('messages.media-update', [event]);
414
848
  break;
415
849
  case 'encrypt':
416
850
  await handleEncryptNotification(node);
417
851
  break;
418
852
  case 'devices':
419
- const devices = (0, WABinary_1.getBinaryNodeChildren)(child, 'device');
420
- if ((0, WABinary_1.areJidsSameUser)(child.attrs.jid, authState.creds.me.id)) {
421
- const deviceJids = devices.map(d => d.attrs.jid);
422
- logger.info({ deviceJids }, 'got my own devices');
853
+ try {
854
+ await handleDevicesNotification(node);
855
+ }
856
+ catch (error) {
857
+ logger.error({ error, node }, 'failed to handle devices notification');
423
858
  }
424
859
  break;
425
860
  case 'server_sync':
426
- const update = (0, WABinary_1.getBinaryNodeChild)(node, 'collection');
861
+ const update = getBinaryNodeChild(node, 'collection');
427
862
  if (update) {
428
863
  const name = update.attrs.name;
429
864
  await resyncAppState([name], false);
430
865
  }
431
866
  break;
432
867
  case 'picture':
433
- const setPicture = (0, WABinary_1.getBinaryNodeChild)(node, 'set');
434
- const delPicture = (0, WABinary_1.getBinaryNodeChild)(node, 'delete');
435
- ev.emit('contacts.update', [{
436
- id: from || ((_b = (_a = (setPicture || delPicture)) === null || _a === void 0 ? void 0 : _a.attrs) === null || _b === void 0 ? void 0 : _b.hash) || '',
868
+ const setPicture = getBinaryNodeChild(node, 'set');
869
+ const delPicture = getBinaryNodeChild(node, 'delete');
870
+ // TODO: WAJIDHASH stuff proper support inhouse
871
+ ev.emit('contacts.update', [
872
+ {
873
+ id: jidNormalizedUser(node?.attrs?.from) || (setPicture || delPicture)?.attrs?.hash || '',
437
874
  imgUrl: setPicture ? 'changed' : 'removed'
438
- }]);
439
- if ((0, WABinary_1.isJidGroup)(from)) {
875
+ }
876
+ ]);
877
+ if (isJidGroup(from)) {
440
878
  const node = setPicture || delPicture;
441
- result.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_ICON;
879
+ result.messageStubType = WAMessageStubType.GROUP_CHANGE_ICON;
442
880
  if (setPicture) {
443
881
  result.messageStubParameters = [setPicture.attrs.id];
444
882
  }
445
- result.participant = node === null || node === void 0 ? void 0 : node.attrs.author;
883
+ result.participant = node?.attrs.author;
446
884
  result.key = {
447
- ...result.key || {},
448
- participant: setPicture === null || setPicture === void 0 ? void 0 : setPicture.attrs.author
885
+ ...(result.key || {}),
886
+ participant: setPicture?.attrs.author
449
887
  };
450
888
  }
451
889
  break;
@@ -459,44 +897,48 @@ const makeMessagesRecvSocket = (config) => {
459
897
  ...authState.creds.accountSettings,
460
898
  defaultDisappearingMode: {
461
899
  ephemeralExpiration: newDuration,
462
- ephemeralSettingTimestamp: timestamp,
463
- },
900
+ ephemeralSettingTimestamp: timestamp
901
+ }
464
902
  }
465
903
  });
466
904
  }
467
905
  else if (child.tag === 'blocklist') {
468
- const blocklists = (0, WABinary_1.getBinaryNodeChildren)(child, 'item');
906
+ const blocklists = getBinaryNodeChildren(child, 'item');
469
907
  for (const { attrs } of blocklists) {
470
908
  const blocklist = [attrs.jid];
471
- const type = (attrs.action === 'block') ? 'add' : 'remove';
909
+ const type = attrs.action === 'block' ? 'add' : 'remove';
472
910
  ev.emit('blocklist.update', { blocklist, type });
473
911
  }
474
912
  }
475
913
  break;
476
914
  case 'link_code_companion_reg':
477
- const linkCodeCompanionReg = (0, WABinary_1.getBinaryNodeChild)(node, 'link_code_companion_reg');
478
- const ref = toRequiredBuffer((0, WABinary_1.getBinaryNodeChildBuffer)(linkCodeCompanionReg, 'link_code_pairing_ref'));
479
- const primaryIdentityPublicKey = toRequiredBuffer((0, WABinary_1.getBinaryNodeChildBuffer)(linkCodeCompanionReg, 'primary_identity_pub'));
480
- const primaryEphemeralPublicKeyWrapped = toRequiredBuffer((0, WABinary_1.getBinaryNodeChildBuffer)(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub'));
915
+ const linkCodeCompanionReg = getBinaryNodeChild(node, 'link_code_companion_reg');
916
+ const ref = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_ref'));
917
+ const primaryIdentityPublicKey = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'primary_identity_pub'));
918
+ const primaryEphemeralPublicKeyWrapped = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub'));
481
919
  const codePairingPublicKey = await decipherLinkPublicKey(primaryEphemeralPublicKeyWrapped);
482
- const companionSharedKey = Utils_1.Curve.sharedKey(authState.creds.pairingEphemeralKeyPair.private, codePairingPublicKey);
483
- const random = (0, crypto_1.randomBytes)(32);
484
- const linkCodeSalt = (0, crypto_1.randomBytes)(32);
485
- const linkCodePairingExpanded = await (0, Utils_1.hkdf)(companionSharedKey, 32, {
920
+ const companionSharedKey = Curve.sharedKey(authState.creds.pairingEphemeralKeyPair.private, codePairingPublicKey);
921
+ const random = randomBytes(32);
922
+ const linkCodeSalt = randomBytes(32);
923
+ const linkCodePairingExpanded = hkdf(companionSharedKey, 32, {
486
924
  salt: linkCodeSalt,
487
925
  info: 'link_code_pairing_key_bundle_encryption_key'
488
926
  });
489
- const encryptPayload = Buffer.concat([Buffer.from(authState.creds.signedIdentityKey.public), primaryIdentityPublicKey, random]);
490
- const encryptIv = (0, crypto_1.randomBytes)(12);
491
- const encrypted = (0, Utils_1.aesEncryptGCM)(encryptPayload, linkCodePairingExpanded, encryptIv, Buffer.alloc(0));
927
+ const encryptPayload = Buffer.concat([
928
+ Buffer.from(authState.creds.signedIdentityKey.public),
929
+ primaryIdentityPublicKey,
930
+ random
931
+ ]);
932
+ const encryptIv = randomBytes(12);
933
+ const encrypted = aesEncryptGCM(encryptPayload, linkCodePairingExpanded, encryptIv, Buffer.alloc(0));
492
934
  const encryptedPayload = Buffer.concat([linkCodeSalt, encryptIv, encrypted]);
493
- const identitySharedKey = Utils_1.Curve.sharedKey(authState.creds.signedIdentityKey.private, primaryIdentityPublicKey);
935
+ const identitySharedKey = Curve.sharedKey(authState.creds.signedIdentityKey.private, primaryIdentityPublicKey);
494
936
  const identityPayload = Buffer.concat([companionSharedKey, identitySharedKey, random]);
495
- authState.creds.advSecretKey = (await (0, Utils_1.hkdf)(identityPayload, 32, { info: 'adv_secret' })).toString('base64');
937
+ authState.creds.advSecretKey = Buffer.from(hkdf(identityPayload, 32, { info: 'adv_secret' })).toString('base64');
496
938
  await query({
497
939
  tag: 'iq',
498
940
  attrs: {
499
- to: WABinary_1.S_WHATSAPP_NET,
941
+ to: S_WHATSAPP_NET,
500
942
  type: 'set',
501
943
  id: sock.generateMessageTag(),
502
944
  xmlns: 'md'
@@ -506,7 +948,7 @@ const makeMessagesRecvSocket = (config) => {
506
948
  tag: 'link_code_companion_reg',
507
949
  attrs: {
508
950
  jid: authState.creds.me.id,
509
- stage: 'companion_finish',
951
+ stage: 'companion_finish'
510
952
  },
511
953
  content: [
512
954
  {
@@ -530,53 +972,212 @@ const makeMessagesRecvSocket = (config) => {
530
972
  });
531
973
  authState.creds.registered = true;
532
974
  ev.emit('creds.update', authState.creds);
975
+ break;
976
+ case 'passkey_prologue_request':
977
+ case 'crsc_continuation':
978
+ handleShortcakeNotification(node);
979
+ break;
980
+ case 'privacy_token':
981
+ await handlePrivacyTokenNotification(node);
982
+ break;
533
983
  }
534
984
  if (Object.keys(result).length) {
535
985
  return result;
536
986
  }
537
987
  };
988
+ /**
989
+ * In-memory cache of storage JIDs with stored tctokens, seeded from the persisted index.
990
+ * Used to coalesce writes during a session; pruning always re-reads the persisted index
991
+ * to cover writes made by other layers (e.g. history sync).
992
+ */
993
+ const tcTokenKnownJids = new Set();
994
+ const tcTokenIndexLoaded = (async () => {
995
+ try {
996
+ const jids = await readTcTokenIndex(authState.keys);
997
+ for (const jid of jids)
998
+ tcTokenKnownJids.add(jid);
999
+ logger.debug({ count: tcTokenKnownJids.size }, 'loaded tctoken index');
1000
+ }
1001
+ catch (err) {
1002
+ logger.warn({ err: err?.message }, 'failed to load tctoken index');
1003
+ }
1004
+ })();
1005
+ let tcTokenIndexTimer;
1006
+ async function flushTcTokenIndex() {
1007
+ if (tcTokenIndexTimer) {
1008
+ clearTimeout(tcTokenIndexTimer);
1009
+ tcTokenIndexTimer = undefined;
1010
+ }
1011
+ // Merge with whatever is already persisted so we don't clobber writes from other
1012
+ // paths (history sync, concurrent sessions on the same store).
1013
+ const write = await buildMergedTcTokenIndexWrite(authState.keys, tcTokenKnownJids);
1014
+ return authState.keys.set({ tctoken: write });
1015
+ }
1016
+ function scheduleTcTokenIndexSave() {
1017
+ if (tcTokenIndexTimer) {
1018
+ clearTimeout(tcTokenIndexTimer);
1019
+ }
1020
+ tcTokenIndexTimer = setTimeout(() => {
1021
+ tcTokenIndexTimer = undefined;
1022
+ flushTcTokenIndex().catch(err => {
1023
+ logger.warn({ err: err?.message }, 'failed to save tctoken index');
1024
+ });
1025
+ }, 5000);
1026
+ }
1027
+ function trackTcTokenJid(jid) {
1028
+ if (jid && jid !== TC_TOKEN_INDEX_KEY && !tcTokenKnownJids.has(jid)) {
1029
+ tcTokenKnownJids.add(jid);
1030
+ scheduleTcTokenIndexSave();
1031
+ }
1032
+ }
1033
+ const handlePrivacyTokenNotification = async (node) => {
1034
+ const tokensNode = getBinaryNodeChild(node, 'tokens');
1035
+ if (!tokensNode)
1036
+ return;
1037
+ const from = jidNormalizedUser(node.attrs.from);
1038
+ // WA Web uses: senderLid ?? toLid(from) for the storage key
1039
+ // The sender_lid attribute provides the LID directly when available
1040
+ const senderLid = node.attrs.sender_lid && isLidUser(jidNormalizedUser(node.attrs.sender_lid))
1041
+ ? jidNormalizedUser(node.attrs.sender_lid)
1042
+ : undefined;
1043
+ const fallbackJid = senderLid ?? (await resolveTcTokenJid(from, getLIDForPN));
1044
+ logger.debug({ from, storageJid: fallbackJid }, 'processing privacy token notification');
1045
+ await storeTcTokensFromIqResult({
1046
+ result: node,
1047
+ fallbackJid,
1048
+ keys: authState.keys,
1049
+ getLIDForPN,
1050
+ onNewJidStored: trackTcTokenJid
1051
+ });
1052
+ };
538
1053
  async function decipherLinkPublicKey(data) {
539
1054
  const buffer = toRequiredBuffer(data);
540
1055
  const salt = buffer.slice(0, 32);
541
- const secretKey = await (0, Utils_1.derivePairingCodeKey)(authState.creds.pairingCode, salt);
1056
+ const secretKey = await derivePairingCodeKey(authState.creds.pairingCode, salt);
542
1057
  const iv = buffer.slice(32, 48);
543
1058
  const payload = buffer.slice(48, 80);
544
- return (0, Utils_1.aesDecryptCTR)(payload, secretKey, iv);
1059
+ return aesDecryptCTR(payload, secretKey, iv);
545
1060
  }
546
1061
  function toRequiredBuffer(data) {
547
1062
  if (data === undefined) {
548
- throw new boom_1.Boom('Invalid buffer', { statusCode: 400 });
1063
+ throw new Boom('Invalid buffer', { statusCode: 400 });
549
1064
  }
550
1065
  return data instanceof Buffer ? data : Buffer.from(data);
551
1066
  }
552
- const willSendMessageAgain = (id, participant) => {
1067
+ const willSendMessageAgain = async (id, participant) => {
553
1068
  const key = `${id}:${participant}`;
554
- const retryCount = msgRetryCache.get(key) || 0;
1069
+ const retryCount = (await msgRetryCache.get(key)) || 0;
555
1070
  return retryCount < maxMsgRetryCount;
556
1071
  };
557
- const updateSendMessageAgainCount = (id, participant) => {
1072
+ const updateSendMessageAgainCount = async (id, participant) => {
558
1073
  const key = `${id}:${participant}`;
559
- const newValue = (msgRetryCache.get(key) || 0) + 1;
560
- msgRetryCache.set(key, newValue);
1074
+ const newValue = ((await msgRetryCache.get(key)) || 0) + 1;
1075
+ await msgRetryCache.set(key, newValue);
561
1076
  };
562
- const sendMessagesAgain = async (key, ids, retryNode) => {
563
- var _a;
564
- // todo: implement a cache to store the last 256 sent messages (copy whatsmeow)
565
- const msgs = await Promise.all(ids.map(id => getMessage({ ...key, id })));
1077
+ const sendMessagesAgain = async (key, ids, retryNode, receiptNode) => {
566
1078
  const remoteJid = key.remoteJid;
567
1079
  const participant = key.participant || remoteJid;
1080
+ const retryCount = +retryNode.attrs.count || 1;
1081
+ const msgId = ids[0];
1082
+ // Try to get messages from cache first, then fallback to getMessage
1083
+ const msgs = [];
1084
+ for (const id of ids) {
1085
+ let msg;
1086
+ // Try to get from retry cache first if enabled
1087
+ if (messageRetryManager) {
1088
+ const cachedMsg = messageRetryManager.getRecentMessage(remoteJid, id);
1089
+ if (cachedMsg) {
1090
+ msg = cachedMsg.message;
1091
+ logger.debug({ jid: remoteJid, id }, 'found message in retry cache');
1092
+ // Mark retry as successful since we found the message
1093
+ messageRetryManager.markRetrySuccess(id);
1094
+ }
1095
+ }
1096
+ // Fallback to getMessage if not found in cache
1097
+ if (!msg) {
1098
+ msg = await getMessage({ ...key, id });
1099
+ if (msg) {
1100
+ logger.debug({ jid: remoteJid, id }, 'found message via getMessage');
1101
+ // Also mark as successful if found via getMessage
1102
+ if (messageRetryManager) {
1103
+ messageRetryManager.markRetrySuccess(id);
1104
+ }
1105
+ }
1106
+ }
1107
+ msgs.push(msg);
1108
+ }
568
1109
  // if it's the primary jid sending the request
569
1110
  // just re-send the message to everyone
570
1111
  // prevents the first message decryption failure
571
- const sendToAll = !((_a = (0, WABinary_1.jidDecode)(participant)) === null || _a === void 0 ? void 0 : _a.device);
572
- await assertSessions([participant], true);
573
- if ((0, WABinary_1.isJidGroup)(remoteJid)) {
1112
+ const sendToAll = !jidDecode(participant)?.device;
1113
+ const sessionId = signalRepository.jidToSignalProtocolAddress(participant);
1114
+ let injectedFromBundle = false;
1115
+ const bundle = extractE2ESessionFromRetryReceipt(receiptNode);
1116
+ if (bundle) {
1117
+ try {
1118
+ await signalRepository.injectE2ESession({ jid: participant, session: bundle });
1119
+ injectedFromBundle = true;
1120
+ logger.debug({ participant, retryCount }, 'injected session from retry receipt key bundle');
1121
+ }
1122
+ catch (error) {
1123
+ logger.warn({ error, participant }, 'failed to inject session from retry receipt');
1124
+ }
1125
+ }
1126
+ if (!injectedFromBundle) {
1127
+ const receivedRegId = getBinaryNodeChildUInt(receiptNode, 'registration', 4);
1128
+ if (typeof receivedRegId === 'number' && Number.isInteger(receivedRegId)) {
1129
+ const info = await signalRepository.getSessionInfo(participant);
1130
+ if (info && info.registrationId !== 0 && info.registrationId !== receivedRegId) {
1131
+ logger.info({ participant, stored: info.registrationId, received: receivedRegId }, 'reg id mismatch on retry without bundle, deleting session');
1132
+ await authState.keys.set({ session: { [sessionId]: null } });
1133
+ }
1134
+ }
1135
+ }
1136
+ const BASE_KEY_CHECK_RETRY = 2;
1137
+ if (msgId && messageRetryManager) {
1138
+ const info = await signalRepository.getSessionInfo(participant);
1139
+ if (info) {
1140
+ if (retryCount === BASE_KEY_CHECK_RETRY) {
1141
+ messageRetryManager.saveBaseKey(sessionId, msgId, info.baseKey);
1142
+ }
1143
+ else if (retryCount > BASE_KEY_CHECK_RETRY) {
1144
+ if (messageRetryManager.hasSameBaseKey(sessionId, msgId, info.baseKey)) {
1145
+ logger.warn({ participant, retryCount }, 'base key collision on retry, forcing fresh session');
1146
+ await authState.keys.set({ session: { [sessionId]: null } });
1147
+ }
1148
+ messageRetryManager.deleteBaseKey(sessionId, msgId);
1149
+ }
1150
+ }
1151
+ }
1152
+ let shouldRecreateSession = false;
1153
+ let recreateReason = '';
1154
+ if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1 && !injectedFromBundle) {
1155
+ try {
1156
+ const hasSession = await signalRepository.validateSession(participant);
1157
+ const result = messageRetryManager.shouldRecreateSession(participant, hasSession.exists);
1158
+ shouldRecreateSession = result.recreate;
1159
+ recreateReason = result.reason;
1160
+ if (shouldRecreateSession) {
1161
+ logger.debug({ participant, retryCount, reason: recreateReason }, 'recreating session for outgoing retry');
1162
+ await authState.keys.set({ session: { [sessionId]: null } });
1163
+ }
1164
+ }
1165
+ catch (error) {
1166
+ logger.warn({ error, participant }, 'failed to check session recreation for outgoing retry');
1167
+ }
1168
+ }
1169
+ if (!injectedFromBundle) {
1170
+ await assertSessions([participant], true);
1171
+ }
1172
+ if (isJidGroup(remoteJid)) {
574
1173
  await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } });
575
1174
  }
576
- logger.debug({ participant, sendToAll }, 'forced new session for retry recp');
1175
+ logger.debug({ participant, sendToAll, shouldRecreateSession, recreateReason, injectedFromBundle }, 'prepared session for retry resend');
577
1176
  for (const [i, msg] of msgs.entries()) {
578
- if (msg) {
579
- updateSendMessageAgainCount(ids[i], participant);
1177
+ if (!ids[i])
1178
+ continue;
1179
+ if (msg && (await willSendMessageAgain(ids[i], participant))) {
1180
+ await updateSendMessageAgainCount(ids[i], participant);
580
1181
  const msgRelayOpts = { messageId: ids[i] };
581
1182
  if (sendToAll) {
582
1183
  msgRelayOpts.useUserDevicesCache = false;
@@ -595,11 +1196,10 @@ const makeMessagesRecvSocket = (config) => {
595
1196
  }
596
1197
  };
597
1198
  const handleReceipt = async (node) => {
598
- var _a, _b;
599
1199
  const { attrs, content } = node;
600
1200
  const isLid = attrs.from.includes('lid');
601
- const isNodeFromMe = (0, WABinary_1.areJidsSameUser)(attrs.participant || attrs.from, isLid ? (_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.lid : (_b = authState.creds.me) === null || _b === void 0 ? void 0 : _b.id);
602
- const remoteJid = !isNodeFromMe || (0, WABinary_1.isJidGroup)(attrs.from) ? attrs.from : attrs.recipient;
1201
+ const isNodeFromMe = areJidsSameUser(attrs.participant || attrs.from, isLid ? authState.creds.me?.lid : authState.creds.me?.id);
1202
+ const remoteJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient;
603
1203
  const fromMe = !attrs.recipient || ((attrs.type === 'retry' || attrs.type === 'sender') && isNodeFromMe);
604
1204
  const key = {
605
1205
  remoteJid,
@@ -607,33 +1207,26 @@ const makeMessagesRecvSocket = (config) => {
607
1207
  fromMe,
608
1208
  participant: attrs.participant
609
1209
  };
610
- if (shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') {
611
- logger.debug({ remoteJid }, 'ignoring receipt from jid');
612
- await sendMessageAck(node);
613
- return;
614
- }
615
1210
  const ids = [attrs.id];
616
1211
  if (Array.isArray(content)) {
617
- const items = (0, WABinary_1.getBinaryNodeChildren)(content[0], 'item');
1212
+ const items = getBinaryNodeChildren(content[0], 'item');
618
1213
  ids.push(...items.map(i => i.attrs.id));
619
1214
  }
620
1215
  try {
621
1216
  await Promise.all([
622
- processingMutex.mutex(async () => {
623
- const status = (0, Utils_1.getStatusFromReceiptType)(attrs.type);
1217
+ receiptMutex.mutex(async () => {
1218
+ const status = getStatusFromReceiptType(attrs.type);
624
1219
  if (typeof status !== 'undefined' &&
625
- (
626
1220
  // basically, we only want to know when a message from us has been delivered to/read by the other person
627
1221
  // or another device of ours has read some messages
628
- status >= WAProto_1.proto.WebMessageInfo.Status.SERVER_ACK ||
629
- !isNodeFromMe)) {
630
- if ((0, WABinary_1.isJidGroup)(remoteJid) || (0, WABinary_1.isJidStatusBroadcast)(remoteJid)) {
1222
+ (status >= proto.WebMessageInfo.Status.SERVER_ACK || !isNodeFromMe)) {
1223
+ if (isJidGroup(remoteJid) || isJidStatusBroadcast(remoteJid)) {
631
1224
  if (attrs.participant) {
632
- const updateKey = status === WAProto_1.proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp';
1225
+ const updateKey = status === proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp';
633
1226
  ev.emit('message-receipt.update', ids.map(id => ({
634
1227
  key: { ...key, id },
635
1228
  receipt: {
636
- userJid: (0, WABinary_1.jidNormalizedUser)(attrs.participant),
1229
+ userJid: jidNormalizedUser(attrs.participant),
637
1230
  [updateKey]: +attrs.t
638
1231
  }
639
1232
  })));
@@ -642,22 +1235,23 @@ const makeMessagesRecvSocket = (config) => {
642
1235
  else {
643
1236
  ev.emit('messages.update', ids.map(id => ({
644
1237
  key: { ...key, id },
645
- update: { status }
1238
+ update: { status, messageTimestamp: toNumber(+(attrs.t ?? 0)) }
646
1239
  })));
647
1240
  }
648
1241
  }
649
1242
  if (attrs.type === 'retry') {
650
1243
  // correctly set who is asking for the retry
651
1244
  key.participant = key.participant || attrs.from;
652
- const retryNode = (0, WABinary_1.getBinaryNodeChild)(node, 'retry');
653
- if (willSendMessageAgain(ids[0], key.participant)) {
1245
+ const retryNode = getBinaryNodeChild(node, 'retry');
1246
+ if (ids[0] && key.participant && (await willSendMessageAgain(ids[0], key.participant))) {
654
1247
  if (key.fromMe) {
655
1248
  try {
1249
+ await updateSendMessageAgainCount(ids[0], key.participant);
656
1250
  logger.debug({ attrs, key }, 'recv retry request');
657
- await sendMessagesAgain(key, ids, retryNode);
1251
+ await sendMessagesAgain(key, ids, retryNode, node);
658
1252
  }
659
1253
  catch (error) {
660
- logger.error({ key, ids, trace: error.stack }, 'error in sending message again');
1254
+ logger.error({ key, ids, trace: error instanceof Error ? error.stack : 'Unknown error' }, 'error in sending message again');
661
1255
  }
662
1256
  }
663
1257
  else {
@@ -672,269 +1266,338 @@ const makeMessagesRecvSocket = (config) => {
672
1266
  ]);
673
1267
  }
674
1268
  finally {
675
- await sendMessageAck(node);
1269
+ await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack receipt'));
676
1270
  }
677
1271
  };
678
1272
  const handleNotification = async (node) => {
679
1273
  const remoteJid = node.attrs.from;
680
- if (shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') {
681
- logger.debug({ remoteJid, id: node.attrs.id }, 'ignored notification');
682
- await sendMessageAck(node);
683
- return;
684
- }
685
1274
  try {
686
1275
  await Promise.all([
687
- processingMutex.mutex(async () => {
688
- var _a;
1276
+ notificationMutex.mutex(async () => {
689
1277
  const msg = await processNotification(node);
690
1278
  if (msg) {
691
- const fromMe = (0, WABinary_1.areJidsSameUser)(node.attrs.participant || remoteJid, authState.creds.me.id);
1279
+ const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me.id);
1280
+ const { senderAlt: participantAlt, addressingMode } = extractAddressingContext(node);
692
1281
  msg.key = {
693
1282
  remoteJid,
694
1283
  fromMe,
695
1284
  participant: node.attrs.participant,
1285
+ participantAlt,
1286
+ participantUsername: node.attrs.participant_username,
1287
+ addressingMode,
696
1288
  id: node.attrs.id,
697
1289
  ...(msg.key || {})
698
1290
  };
699
- (_a = msg.participant) !== null && _a !== void 0 ? _a : (msg.participant = node.attrs.participant);
1291
+ msg.participant ?? (msg.participant = node.attrs.participant);
700
1292
  msg.messageTimestamp = +node.attrs.t;
701
- const fullMsg = WAProto_1.proto.WebMessageInfo.fromObject(msg);
1293
+ const fullMsg = proto.WebMessageInfo.fromObject(msg);
702
1294
  await upsertMessage(fullMsg, 'append');
703
1295
  }
704
1296
  })
705
1297
  ]);
706
1298
  }
707
1299
  finally {
708
- await sendMessageAck(node);
1300
+ await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack notification'));
709
1301
  }
710
1302
  };
711
1303
  const handleMessage = async (node) => {
712
- var _a, _b, _c;
713
- if (shouldIgnoreJid(node.attrs.from) && node.attrs.from !== '@s.whatsapp.net') {
714
- logger.debug({ key: node.attrs.key }, 'ignored message');
715
- await sendMessageAck(node);
716
- return;
717
- }
718
- const encNode = (0, WABinary_1.getBinaryNodeChild)(node, 'enc');
1304
+ const encNode = getBinaryNodeChild(node, 'enc');
719
1305
  // TODO: temporary fix for crashes and issues resulting of failed msmsg decryption
720
- if (encNode && encNode.attrs.type === 'msmsg') {
1306
+ if (encNode?.attrs.type === 'msmsg') {
721
1307
  logger.debug({ key: node.attrs.key }, 'ignored msmsg');
722
- await sendMessageAck(node);
1308
+ await sendMessageAck(node, NACK_REASONS.MissingMessageSecret);
723
1309
  return;
724
1310
  }
725
- let response;
726
- if ((0, WABinary_1.getBinaryNodeChild)(node, 'unavailable') && !encNode) {
727
- await sendMessageAck(node);
728
- const { key } = (0, Utils_1.decodeMessageNode)(node, authState.creds.me.id, authState.creds.me.lid || '').fullMessage;
729
- response = await requestPlaceholderResend(key);
730
- if (response === 'RESOLVED') {
731
- return;
732
- }
733
- logger.debug('received unavailable message, acked and requested resend from phone');
734
- }
735
- else {
736
- if (placeholderResendCache.get(node.attrs.id)) {
737
- placeholderResendCache.del(node.attrs.id);
738
- }
739
- }
740
- const { fullMessage: msg, category, author, decrypt } = (0, Utils_1.decryptMessageNode)(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger);
741
- if (response && ((_a = msg === null || msg === void 0 ? void 0 : msg.messageStubParameters) === null || _a === void 0 ? void 0 : _a[0]) === Utils_1.NO_MESSAGE_FOUND_ERROR_TEXT) {
742
- msg.messageStubParameters = [Utils_1.NO_MESSAGE_FOUND_ERROR_TEXT, response];
743
- }
744
- if (((_c = (_b = msg.message) === null || _b === void 0 ? void 0 : _b.protocolMessage) === null || _c === void 0 ? void 0 : _c.type) === WAProto_1.proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER && node.attrs.sender_pn) {
745
- ev.emit('chats.phoneNumberShare', { lid: node.attrs.from, jid: node.attrs.sender_pn });
746
- }
1311
+ let acked = false;
747
1312
  try {
748
- await Promise.all([
749
- processingMutex.mutex(async () => {
750
- var _a, _b, _c, _d, _e, _f;
751
- await decrypt();
752
- // message failed to decrypt
753
- if (msg.messageStubType === WAProto_1.proto.WebMessageInfo.StubType.CIPHERTEXT) {
754
- if (((_a = msg === null || msg === void 0 ? void 0 : msg.messageStubParameters) === null || _a === void 0 ? void 0 : _a[0]) === Utils_1.MISSING_KEYS_ERROR_TEXT) {
755
- return sendMessageAck(node, Utils_1.NACK_REASONS.ParsingError);
1313
+ const { fullMessage: msg, category, author, decrypt } = decryptMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger);
1314
+ const alt = msg.key.participantAlt || msg.key.remoteJidAlt;
1315
+ // store new mappings we didn't have before
1316
+ if (!!alt) {
1317
+ const altServer = jidDecode(alt)?.server;
1318
+ const primaryJid = msg.key.participant || msg.key.remoteJid;
1319
+ if (altServer === 'lid') {
1320
+ if (!(await signalRepository.lidMapping.getPNForLID(alt))) {
1321
+ await signalRepository.lidMapping.storeLIDPNMappings([{ lid: alt, pn: primaryJid }]);
1322
+ await signalRepository.migrateSession(primaryJid, alt);
1323
+ }
1324
+ }
1325
+ else {
1326
+ await signalRepository.lidMapping.storeLIDPNMappings([{ lid: primaryJid, pn: alt }]);
1327
+ await signalRepository.migrateSession(alt, primaryJid);
1328
+ }
1329
+ }
1330
+ await messageMutex.mutex(async () => {
1331
+ await decrypt();
1332
+ if (msg.key?.remoteJid && msg.key?.id && msg.message && messageRetryManager) {
1333
+ messageRetryManager.addRecentMessage(msg.key.remoteJid, msg.key.id, msg.message);
1334
+ }
1335
+ // message failed to decrypt
1336
+ if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT && msg.category !== 'peer') {
1337
+ if (msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT) {
1338
+ acked = true;
1339
+ return sendMessageAck(node, NACK_REASONS.ParsingError);
1340
+ }
1341
+ if (msg.messageStubParameters?.[0] === NO_MESSAGE_FOUND_ERROR_TEXT) {
1342
+ // Message arrived without encryption (e.g. CTWA ads messages).
1343
+ // Check if this is eligible for placeholder resend (matching WA Web filters).
1344
+ const unavailableNode = getBinaryNodeChild(node, 'unavailable');
1345
+ const unavailableType = unavailableNode?.attrs?.type;
1346
+ if (unavailableType === 'bot_unavailable_fanout' ||
1347
+ unavailableType === 'hosted_unavailable_fanout' ||
1348
+ unavailableType === 'view_once_unavailable_fanout') {
1349
+ logger.debug({ msgId: msg.key.id, unavailableType }, 'skipping placeholder resend for excluded unavailable type');
1350
+ acked = true;
1351
+ return sendMessageAck(node);
756
1352
  }
757
- retryMutex.mutex(async () => {
758
- if (ws.isOpen) {
759
- if ((0, WABinary_1.getBinaryNodeChild)(node, 'unavailable')) {
1353
+ const messageAge = unixTimestampSeconds() - toNumber(msg.messageTimestamp);
1354
+ if (messageAge > PLACEHOLDER_MAX_AGE_SECONDS) {
1355
+ logger.debug({ msgId: msg.key.id, messageAge }, 'skipping placeholder resend for old message');
1356
+ acked = true;
1357
+ return sendMessageAck(node);
1358
+ }
1359
+ // Request the real content from the phone via placeholder resend PDO.
1360
+ // Upsert the CIPHERTEXT stub as a placeholder (like WA Web's processPlaceholderMsg),
1361
+ // and store the requestId in stubParameters[1] so users can correlate
1362
+ // with the incoming PDO response event.
1363
+ const cleanKey = {
1364
+ remoteJid: msg.key.remoteJid,
1365
+ fromMe: msg.key.fromMe,
1366
+ id: msg.key.id,
1367
+ participant: msg.key.participant
1368
+ };
1369
+ // Cache the original message metadata so the PDO response handler
1370
+ // can preserve key fields (LID details etc.) that the phone may omit
1371
+ const msgData = {
1372
+ key: msg.key,
1373
+ messageTimestamp: msg.messageTimestamp,
1374
+ pushName: msg.pushName,
1375
+ participant: msg.participant,
1376
+ verifiedBizName: msg.verifiedBizName
1377
+ };
1378
+ requestPlaceholderResend(cleanKey, msgData)
1379
+ .then(requestId => {
1380
+ if (requestId && requestId !== 'RESOLVED') {
1381
+ logger.debug({ msgId: msg.key.id, requestId }, 'requested placeholder resend for unavailable message');
1382
+ ev.emit('messages.update', [
1383
+ {
1384
+ key: msg.key,
1385
+ update: { messageStubParameters: [NO_MESSAGE_FOUND_ERROR_TEXT, requestId] }
1386
+ }
1387
+ ]);
1388
+ }
1389
+ })
1390
+ .catch(err => {
1391
+ logger.warn({ err, msgId: msg.key.id }, 'failed to request placeholder resend for unavailable message');
1392
+ });
1393
+ acked = true;
1394
+ await sendMessageAck(node);
1395
+ // Don't return — fall through to upsertMessage so the stub is emitted
1396
+ }
1397
+ else {
1398
+ // Skip retry for expired status messages (>24h old)
1399
+ if (isJidStatusBroadcast(msg.key.remoteJid)) {
1400
+ const messageAge = unixTimestampSeconds() - toNumber(msg.messageTimestamp);
1401
+ if (messageAge > STATUS_EXPIRY_SECONDS) {
1402
+ logger.debug({ msgId: msg.key.id, messageAge, remoteJid: msg.key.remoteJid }, 'skipping retry for expired status message');
1403
+ acked = true;
1404
+ return sendMessageAck(node);
1405
+ }
1406
+ }
1407
+ logger.debug('[handleMessage] Attempting retry request for failed decryption');
1408
+ // WAWeb only retry-receipts here; server emits PreKeyLow if prekeys run low.
1409
+ await retryMutex.mutex(async () => {
1410
+ try {
1411
+ if (!ws.isOpen) {
1412
+ logger.debug({ node }, 'Connection closed, skipping retry');
760
1413
  return;
761
1414
  }
762
- const encNode = (0, WABinary_1.getBinaryNodeChild)(node, 'enc');
1415
+ const encNode = getBinaryNodeChild(node, 'enc');
763
1416
  await sendRetryRequest(node, !encNode);
764
1417
  if (retryRequestDelayMs) {
765
- await (0, Utils_1.delay)(retryRequestDelayMs);
1418
+ await delay(retryRequestDelayMs);
766
1419
  }
767
1420
  }
768
- else {
769
- logger.debug({ node }, 'connection closed, ignoring retry req');
1421
+ catch (err) {
1422
+ logger.error({ err }, 'Failed to send retry');
770
1423
  }
1424
+ acked = true;
1425
+ await sendMessageAck(node, NACK_REASONS.UnhandledError);
771
1426
  });
772
1427
  }
773
- else {
1428
+ }
1429
+ else {
1430
+ if (messageRetryManager && msg.key.id) {
1431
+ messageRetryManager.cancelPendingPhoneRequest(msg.key.id);
1432
+ }
1433
+ const isNewsletter = isJidNewsletter(msg.key.remoteJid);
1434
+ if (!isNewsletter) {
774
1435
  // no type in the receipt => message delivered
775
1436
  let type = undefined;
776
- if ((_b = msg.key.participant) === null || _b === void 0 ? void 0 : _b.endsWith('@lid')) {
777
- msg.key.participant = node.attrs.participant_pn || authState.creds.me.id;
778
- }
779
- if ((0, WABinary_1.isJidGroup)(msg.key.remoteJid) && ((_f = (_e = (_d = (_c = msg.message) === null || _c === void 0 ? void 0 : _c.extendedTextMessage) === null || _d === void 0 ? void 0 : _d.contextInfo) === null || _e === void 0 ? void 0 : _e.participant) === null || _f === void 0 ? void 0 : _f.endsWith('@lid'))) {
780
- if (msg.message.extendedTextMessage.contextInfo) {
781
- const metadata = await groupMetadata(msg.key.remoteJid);
782
- const sender = msg.message.extendedTextMessage.contextInfo.participant;
783
- const found = metadata.participants.find(p => p.id === sender);
784
- msg.message.extendedTextMessage.contextInfo.participant = (found === null || found === void 0 ? void 0 : found.jid) || sender;
785
- }
786
- }
787
- if (!(0, WABinary_1.isJidGroup)(msg.key.remoteJid) && (0, WABinary_1.isLidUser)(msg.key.remoteJid)) {
788
- msg.key.remoteJid = node.attrs.sender_pn || node.attrs.peer_recipient_pn;
789
- }
790
1437
  let participant = msg.key.participant;
791
- if (category === 'peer') { // special peer message
1438
+ if (category === 'peer') {
1439
+ // special peer message
792
1440
  type = 'peer_msg';
793
1441
  }
794
- else if (msg.key.fromMe) { // message was sent by us from a different device
1442
+ else if (msg.key.fromMe) {
1443
+ // message was sent by us from a different device
795
1444
  type = 'sender';
796
1445
  // need to specially handle this case
797
- if ((0, WABinary_1.isJidUser)(msg.key.remoteJid)) {
798
- participant = author;
1446
+ if (isLidUser(msg.key.remoteJid) || isLidUser(msg.key.remoteJidAlt)) {
1447
+ participant = author; // TODO: investigate sending receipts to LIDs and not PNs
799
1448
  }
800
1449
  }
801
1450
  else if (!sendActiveReceipts) {
802
1451
  type = 'inactive';
803
1452
  }
1453
+ acked = true;
804
1454
  await sendReceipt(msg.key.remoteJid, participant, [msg.key.id], type);
805
1455
  // send ack for history message
806
- const isAnyHistoryMsg = (0, Utils_1.getHistoryMsg)(msg.message);
1456
+ const isAnyHistoryMsg = getHistoryMsg(msg.message);
807
1457
  if (isAnyHistoryMsg) {
808
- const jid = (0, WABinary_1.jidNormalizedUser)(msg.key.remoteJid);
809
- await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync');
1458
+ const jid = jidNormalizedUser(msg.key.remoteJid);
1459
+ await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync'); // TODO: investigate
810
1460
  }
811
1461
  }
812
- (0, Utils_1.cleanMessage)(msg, authState.creds.me.id);
813
- await sendMessageAck(node);
814
- await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
815
- })
816
- ]);
1462
+ else {
1463
+ acked = true;
1464
+ await sendMessageAck(node);
1465
+ logger.debug({ key: msg.key }, 'processed newsletter message without receipts');
1466
+ }
1467
+ }
1468
+ cleanMessage(msg, authState.creds.me.id, authState.creds.me.lid);
1469
+ await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
1470
+ });
817
1471
  }
818
1472
  catch (error) {
819
- logger.error({ error, node }, 'error in handling message');
1473
+ logger.error({ error, node: binaryNodeToString(node) }, 'error in handling message');
1474
+ if (!acked) {
1475
+ await sendMessageAck(node, NACK_REASONS.UnhandledError).catch(ackErr => logger.error({ ackErr }, 'failed to ack message after error'));
1476
+ }
820
1477
  }
821
1478
  };
822
- const fetchMessageHistory = async (count, oldestMsgKey, oldestMsgTimestamp) => {
823
- var _a;
824
- if (!((_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.id)) {
825
- throw new boom_1.Boom('Not authenticated');
826
- }
827
- const pdoMessage = {
828
- historySyncOnDemandRequest: {
829
- chatJid: oldestMsgKey.remoteJid,
830
- oldestMsgFromMe: oldestMsgKey.fromMe,
831
- oldestMsgId: oldestMsgKey.id,
832
- oldestMsgTimestampMs: oldestMsgTimestamp,
833
- onDemandMsgCount: count
834
- },
835
- peerDataOperationRequestType: WAProto_1.proto.Message.PeerDataOperationRequestType.HISTORY_SYNC_ON_DEMAND
836
- };
837
- return sendPeerDataOperationMessage(pdoMessage);
838
- };
839
- const requestPlaceholderResend = async (messageKey) => {
840
- var _a;
841
- if (!((_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.id)) {
842
- throw new boom_1.Boom('Not authenticated');
843
- }
844
- if (placeholderResendCache.get(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id)) {
845
- logger.debug({ messageKey }, 'already requested resend');
846
- return;
1479
+ const handleCall = async (node) => {
1480
+ try {
1481
+ const { attrs } = node;
1482
+ const [infoChild] = getAllBinaryNodeChildren(node);
1483
+ if (!infoChild) {
1484
+ throw new Boom('Missing call info in call node');
1485
+ }
1486
+ const status = getCallStatusFromNode(infoChild);
1487
+ const callId = infoChild.attrs['call-id'];
1488
+ const from = infoChild.attrs.from || infoChild.attrs['call-creator'];
1489
+ const call = {
1490
+ chatId: attrs.from,
1491
+ from,
1492
+ callerPn: infoChild.attrs['caller_pn'],
1493
+ id: callId,
1494
+ date: new Date(+attrs.t * 1000),
1495
+ offline: !!attrs.offline,
1496
+ status
1497
+ };
1498
+ if (status === 'relaylatency') {
1499
+ const latencyValue = infoChild.attrs.latency || infoChild.attrs['latency_ms'] || infoChild.attrs['latency-ms'];
1500
+ const latencyMs = latencyValue ? Number(latencyValue) : undefined;
1501
+ if (Number.isFinite(latencyMs)) {
1502
+ call.latencyMs = latencyMs;
1503
+ }
1504
+ }
1505
+ if (status === 'offer') {
1506
+ call.isVideo = !!getBinaryNodeChild(infoChild, 'video');
1507
+ call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid'];
1508
+ call.groupJid = infoChild.attrs['group-jid'];
1509
+ await callOfferCache.set(call.id, call);
1510
+ }
1511
+ const existingCall = await callOfferCache.get(call.id);
1512
+ // use existing call info to populate this event
1513
+ if (existingCall) {
1514
+ call.isVideo = existingCall.isVideo;
1515
+ call.isGroup = existingCall.isGroup;
1516
+ call.callerPn = call.callerPn || existingCall.callerPn;
1517
+ }
1518
+ // delete data once call has ended
1519
+ if (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') {
1520
+ await callOfferCache.del(call.id);
1521
+ }
1522
+ ev.emit('call', [call]);
847
1523
  }
848
- else {
849
- placeholderResendCache.set(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id, true);
1524
+ catch (error) {
1525
+ logger.error({ error, node: binaryNodeToString(node) }, 'error in handling call');
850
1526
  }
851
- await (0, Utils_1.delay)(5000);
852
- if (!placeholderResendCache.get(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id)) {
853
- logger.debug({ messageKey }, 'message received while resend requested');
854
- return 'RESOLVED';
1527
+ finally {
1528
+ await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack call'));
855
1529
  }
856
- const pdoMessage = {
857
- placeholderMessageResendRequest: [{
858
- messageKey
859
- }],
860
- peerDataOperationRequestType: WAProto_1.proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
861
- };
862
- setTimeout(() => {
863
- if (placeholderResendCache.get(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id)) {
864
- logger.debug({ messageKey }, 'PDO message without response after 15 seconds. Phone possibly offline');
865
- placeholderResendCache.del(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id);
866
- }
867
- }, 15000);
868
- return sendPeerDataOperationMessage(pdoMessage);
869
- };
870
- const handleCall = async (node) => {
871
- const { attrs } = node;
872
- const [infoChild] = (0, WABinary_1.getAllBinaryNodeChildren)(node);
873
- const callId = infoChild.attrs['call-id'];
874
- const from = infoChild.attrs.from || infoChild.attrs['call-creator'];
875
- const status = (0, Utils_1.getCallStatusFromNode)(infoChild);
876
- const call = {
877
- chatId: attrs.from,
878
- from,
879
- id: callId,
880
- date: new Date(+attrs.t * 1000),
881
- offline: !!attrs.offline,
882
- status,
883
- };
884
- if (status === 'offer') {
885
- call.isVideo = !!(0, WABinary_1.getBinaryNodeChild)(infoChild, 'video');
886
- call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid'];
887
- call.groupJid = infoChild.attrs['group-jid'];
888
- callOfferCache.set(call.id, call);
889
- }
890
- const existingCall = callOfferCache.get(call.id);
891
- // use existing call info to populate this event
892
- if (existingCall) {
893
- call.isVideo = existingCall.isVideo;
894
- call.isGroup = existingCall.isGroup;
895
- }
896
- // delete data once call has ended
897
- if (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') {
898
- callOfferCache.del(call.id);
899
- }
900
- ev.emit('call', [call]);
901
- await sendMessageAck(node);
902
1530
  };
903
1531
  const handleBadAck = async ({ attrs }) => {
904
- const key = { remoteJid: attrs.from, fromMe: true, id: attrs.id, 'server_id': attrs === null || attrs === void 0 ? void 0 : attrs.server_id };
905
- // current hypothesis is that if pash is sent in the ack
906
- // it means -- the message hasn't reached all devices yet
907
- // we'll retry sending the message here
908
- if (attrs.phash) {
909
- logger.info({ attrs }, 'received phash in ack, resending message...');
910
- const cacheKey = `${key.remoteJid}:${key.id}`;
911
- if ((msgRetryCache.get(cacheKey) || 0) >= maxMsgRetryCount) {
912
- logger.warn({ attrs }, 'reached max retry count, not sending message again');
913
- msgRetryCache.del(cacheKey);
914
- return;
1532
+ const key = { remoteJid: attrs.from, fromMe: true, id: attrs.id };
1533
+ // WARNING: REFRAIN FROM ENABLING THIS FOR NOW. IT WILL CAUSE A LOOP
1534
+ // // current hypothesis is that if pash is sent in the ack
1535
+ // // it means -- the message hasn't reached all devices yet
1536
+ // // we'll retry sending the message here
1537
+ // if(attrs.phash) {
1538
+ // logger.info({ attrs }, 'received phash in ack, resending message...')
1539
+ // const msg = await getMessage(key)
1540
+ // if(msg) {
1541
+ // await relayMessage(key.remoteJid!, msg, { messageId: key.id!, useUserDevicesCache: false })
1542
+ // } else {
1543
+ // logger.warn({ attrs }, 'could not send message again, as it was not found')
1544
+ // }
1545
+ // }
1546
+ // error in acknowledgement,
1547
+ // device could not display the message
1548
+ if (attrs.error) {
1549
+ const isReachoutTimelocked = attrs.error === String(NACK_REASONS.SenderReachoutTimelocked);
1550
+ if (attrs.error === SERVER_ERROR_CODES.MessageAccountRestriction) {
1551
+ // 463 = 1:1 message missing privacy token (tctoken). Usually means the
1552
+ // account is restricted: WhatsApp blocks starting new chats but preserves
1553
+ // existing ones, since established chats already carry a tctoken.
1554
+ // WA Web prevents this client-side (disables the compose bar).
1555
+ // No retry — retrying counts as another "reach out" and worsens the restriction.
1556
+ logger.warn({ msgId: attrs.id, from: attrs.from }, 'error 463: account restricted or missing tctoken for contact');
1557
+ const ackFrom = attrs.from;
1558
+ if (ackFrom && !inFlight463Recoveries.has(ackFrom)) {
1559
+ inFlight463Recoveries.add(ackFrom);
1560
+ void (async () => {
1561
+ try {
1562
+ const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
1563
+ const tcStorageJid = await resolveTcTokenJid(ackFrom, getLIDForPN);
1564
+ const issueJid = await resolveIssuanceJid(ackFrom, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID);
1565
+ const result = await issuePrivacyTokens([issueJid], unixTimestampSeconds());
1566
+ await storeTcTokensFromIqResult({
1567
+ result,
1568
+ fallbackJid: tcStorageJid,
1569
+ keys: authState.keys,
1570
+ getLIDForPN,
1571
+ onNewJidStored: trackTcTokenJid
1572
+ });
1573
+ logger.debug({ from: ackFrom }, 'completed 463 token recovery issuance');
1574
+ }
1575
+ catch (err) {
1576
+ logger.debug({ from: ackFrom, err: err?.message }, 'failed 463 token recovery issuance');
1577
+ }
1578
+ finally {
1579
+ inFlight463Recoveries.delete(ackFrom);
1580
+ }
1581
+ })();
1582
+ }
1583
+ }
1584
+ else if (attrs.error === SERVER_ERROR_CODES.SmaxInvalid) {
1585
+ logger.warn({ msgId: attrs.id, from: attrs.from }, 'smax-invalid (479): stanza rejected by server — likely stale device session or malformed addressing');
915
1586
  }
916
- const retryCount = msgRetryCache.get(cacheKey) || 0;
917
- const msg = await getMessage(key);
918
- if (msg) {
919
- await relayMessage(key.remoteJid, msg, { messageId: key.id, useUserDevicesCache: false });
920
- msgRetryCache.set(cacheKey, retryCount + 1);
1587
+ else if (isReachoutTimelocked) {
1588
+ // user is temporarily restricted, fetch current restriction details
1589
+ await fetchAccountReachoutTimelock().catch(err => logger.warn({ err }, 'failed to fetch reachout timelock'));
1590
+ logger.warn({ attrs }, 'received error in ack');
921
1591
  }
922
1592
  else {
923
- logger.warn({ attrs }, 'could not send message again, as it was not found');
1593
+ logger.warn({ attrs }, 'received error in ack');
924
1594
  }
925
- }
926
- // error in acknowledgement,
927
- // device could not display the message
928
- if (attrs.error) {
929
- logger.warn({ attrs }, 'received error in ack');
930
1595
  ev.emit('messages.update', [
931
1596
  {
932
1597
  key,
933
1598
  update: {
934
- status: Types_1.WAMessageStatus.ERROR,
935
- messageStubParameters: [
936
- attrs.error
937
- ]
1599
+ status: WAMessageStatus.ERROR,
1600
+ messageStubParameters: isReachoutTimelocked ? [attrs.error, ACCOUNT_RESTRICTED_TEXT] : [attrs.error]
938
1601
  }
939
1602
  }
940
1603
  ]);
@@ -947,69 +1610,61 @@ const makeMessagesRecvSocket = (config) => {
947
1610
  await execTask();
948
1611
  ev.flush();
949
1612
  function execTask() {
950
- return exec(node, false)
951
- .catch(err => onUnexpectedError(err, identifier));
1613
+ return exec(node, false).catch(err => onUnexpectedError(err, identifier));
952
1614
  }
953
1615
  };
954
- const makeOfflineNodeProcessor = () => {
955
- const nodeProcessorMap = new Map([
956
- ['message', handleMessage],
957
- ['call', handleCall],
958
- ['receipt', handleReceipt],
959
- ['notification', handleNotification]
960
- ]);
961
- const nodes = [];
962
- let isProcessing = false;
963
- const enqueue = (type, node) => {
964
- nodes.push({ type, node });
965
- if (isProcessing) {
966
- return;
967
- }
968
- isProcessing = true;
969
- const promise = async () => {
970
- while (nodes.length && ws.isOpen) {
971
- const { type, node } = nodes.shift();
972
- const nodeProcessor = nodeProcessorMap.get(type);
973
- if (!nodeProcessor) {
974
- onUnexpectedError(new Error(`unknown offline node type: ${type}`), 'processing offline node');
975
- continue;
976
- }
977
- await nodeProcessor(node);
978
- }
979
- isProcessing = false;
980
- };
981
- promise().catch(error => onUnexpectedError(error, 'processing offline nodes'));
982
- };
983
- return { enqueue };
984
- };
985
- const offlineNodeProcessor = makeOfflineNodeProcessor();
986
- const processNode = (type, node, identifier, exec) => {
1616
+ const offlineNodeProcessor = makeOfflineNodeProcessor(new Map([
1617
+ ['message', handleMessage],
1618
+ ['call', handleCall],
1619
+ ['receipt', handleReceipt],
1620
+ ['notification', handleNotification]
1621
+ ]), {
1622
+ isWsOpen: () => ws.isOpen,
1623
+ onUnexpectedError,
1624
+ yieldToEventLoop: () => new Promise(resolve => setImmediate(resolve))
1625
+ });
1626
+ const processNode = async (type, node, identifier, exec) => {
1627
+ // Fast path: ack and drop ignored JIDs before entering the buffer/queue
1628
+ const from = node.attrs.from;
1629
+ let ignoreJid = from;
1630
+ if (type === 'receipt' && from) {
1631
+ const attrs = node.attrs;
1632
+ const isLid = attrs.from.includes('lid');
1633
+ const isNodeFromMe = areJidsSameUser(attrs.participant || attrs.from, isLid ? authState.creds.me?.lid : authState.creds.me?.id);
1634
+ ignoreJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient;
1635
+ }
1636
+ if (ignoreJid && ignoreJid !== S_WHATSAPP_NET && shouldIgnoreJid(ignoreJid)) {
1637
+ await sendMessageAck(node, type === 'message' ? NACK_REASONS.UnhandledError : undefined);
1638
+ return;
1639
+ }
987
1640
  const isOffline = !!node.attrs.offline;
988
1641
  if (isOffline) {
989
1642
  offlineNodeProcessor.enqueue(type, node);
990
1643
  }
991
1644
  else {
992
- processNodeWithBuffer(node, identifier, exec);
1645
+ await processNodeWithBuffer(node, identifier, exec);
993
1646
  }
994
1647
  };
995
1648
  // recv a message
996
- ws.on('CB:message', (node) => {
997
- processNode('message', node, 'processing message', handleMessage);
1649
+ ws.on('CB:message', async (node) => {
1650
+ await processNode('message', node, 'processing message', handleMessage);
998
1651
  });
999
1652
  ws.on('CB:call', async (node) => {
1000
- processNode('call', node, 'handling call', handleCall);
1653
+ await processNode('call', node, 'handling call', handleCall);
1001
1654
  });
1002
- ws.on('CB:receipt', node => {
1003
- processNode('receipt', node, 'handling receipt', handleReceipt);
1655
+ ws.on('CB:receipt', async (node) => {
1656
+ await processNode('receipt', node, 'handling receipt', handleReceipt);
1004
1657
  });
1005
1658
  ws.on('CB:notification', async (node) => {
1006
- processNode('notification', node, 'handling notification', handleNotification);
1659
+ await processNode('notification', node, 'handling notification', handleNotification);
1007
1660
  });
1008
1661
  ws.on('CB:ack,class:message', (node) => {
1009
- handleBadAck(node)
1010
- .catch(error => onUnexpectedError(error, 'handling bad ack'));
1662
+ handleBadAck(node).catch(error => onUnexpectedError(error, 'handling bad ack'));
1011
1663
  });
1012
- ev.on('call', ([call]) => {
1664
+ ev.on('call', async ([call]) => {
1665
+ if (!call) {
1666
+ return;
1667
+ }
1013
1668
  // missed call + group call notification message generation
1014
1669
  if (call.status === 'timeout' || (call.status === 'offer' && call.isGroup)) {
1015
1670
  const msg = {
@@ -1018,37 +1673,137 @@ const makeMessagesRecvSocket = (config) => {
1018
1673
  id: call.id,
1019
1674
  fromMe: false
1020
1675
  },
1021
- messageTimestamp: (0, Utils_1.unixTimestampSeconds)(call.date),
1676
+ messageTimestamp: unixTimestampSeconds(call.date)
1022
1677
  };
1023
1678
  if (call.status === 'timeout') {
1024
1679
  if (call.isGroup) {
1025
- msg.messageStubType = call.isVideo ? Types_1.WAMessageStubType.CALL_MISSED_GROUP_VIDEO : Types_1.WAMessageStubType.CALL_MISSED_GROUP_VOICE;
1680
+ msg.messageStubType = call.isVideo
1681
+ ? WAMessageStubType.CALL_MISSED_GROUP_VIDEO
1682
+ : WAMessageStubType.CALL_MISSED_GROUP_VOICE;
1026
1683
  }
1027
1684
  else {
1028
- msg.messageStubType = call.isVideo ? Types_1.WAMessageStubType.CALL_MISSED_VIDEO : Types_1.WAMessageStubType.CALL_MISSED_VOICE;
1685
+ msg.messageStubType = call.isVideo ? WAMessageStubType.CALL_MISSED_VIDEO : WAMessageStubType.CALL_MISSED_VOICE;
1029
1686
  }
1030
1687
  }
1031
1688
  else {
1032
1689
  msg.message = { call: { callKey: Buffer.from(call.id) } };
1033
1690
  }
1034
- const protoMsg = WAProto_1.proto.WebMessageInfo.fromObject(msg);
1035
- upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
1691
+ const protoMsg = proto.WebMessageInfo.fromObject(msg);
1692
+ await upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
1036
1693
  }
1037
1694
  });
1038
- ev.on('connection.update', ({ isOnline }) => {
1695
+ /** timestamp of last tctoken prune run — throttles to once per 24h */
1696
+ let lastTcTokenPruneTs = 0;
1697
+ /** dedupe in-flight 463 recovery token issuance by target JID */
1698
+ const inFlight463Recoveries = new Set();
1699
+ ev.on('connection.update', ({ isOnline, connection }) => {
1039
1700
  if (typeof isOnline !== 'undefined') {
1040
1701
  sendActiveReceipts = isOnline;
1041
1702
  logger.trace(`sendActiveReceipts set to "${sendActiveReceipts}"`);
1042
1703
  }
1704
+ // Flush pending tctoken index save on disconnect to avoid writing after close
1705
+ if (connection === 'close' && tcTokenIndexTimer) {
1706
+ clearTimeout(tcTokenIndexTimer);
1707
+ tcTokenIndexTimer = undefined;
1708
+ // Best-effort flush — may fail if store is already closed
1709
+ try {
1710
+ void Promise.resolve(flushTcTokenIndex()).catch(() => { });
1711
+ }
1712
+ catch {
1713
+ /* ignore sync errors */
1714
+ }
1715
+ }
1716
+ // Prune expired tctokens when coming online, at most once per 24 hours
1717
+ // Matches WA Web's CLEAN_TC_TOKENS task
1718
+ // Note: don't gate on tcTokenKnownJids.size — the index may still be loading
1719
+ if (isOnline) {
1720
+ const now = Date.now();
1721
+ const DAY_MS = 24 * 60 * 60 * 1000;
1722
+ if (now - lastTcTokenPruneTs >= DAY_MS) {
1723
+ lastTcTokenPruneTs = now;
1724
+ void pruneExpiredTcTokens();
1725
+ }
1726
+ }
1043
1727
  });
1728
+ registerSocketEndHandler(() => {
1729
+ if (!config.msgRetryCounterCache && msgRetryCache.close) {
1730
+ msgRetryCache.close();
1731
+ }
1732
+ if (!config.callOfferCache && callOfferCache.close) {
1733
+ callOfferCache.close();
1734
+ }
1735
+ identityAssertDebounce.close();
1736
+ sendActiveReceipts = false;
1737
+ });
1738
+ async function pruneExpiredTcTokens() {
1739
+ try {
1740
+ await tcTokenIndexLoaded;
1741
+ // Union with the persisted index picks up JIDs added by other layers
1742
+ // (history sync) without needing inter-module wiring.
1743
+ const persisted = await readTcTokenIndex(authState.keys);
1744
+ const allJids = new Set(tcTokenKnownJids);
1745
+ for (const jid of persisted)
1746
+ allJids.add(jid);
1747
+ if (!allJids.size)
1748
+ return;
1749
+ const jids = [...allJids];
1750
+ const allTokens = await authState.keys.get('tctoken', jids);
1751
+ const writes = {};
1752
+ const survivors = new Set();
1753
+ let mutated = 0;
1754
+ for (const jid of jids) {
1755
+ const entry = allTokens[jid];
1756
+ if (!entry) {
1757
+ // Tracked but nothing in store — drop from index.
1758
+ mutated++;
1759
+ continue;
1760
+ }
1761
+ const hasPeerToken = !!entry.token?.length;
1762
+ const peerTokenExpired = hasPeerToken && isTcTokenExpired(entry.timestamp);
1763
+ const hasSenderTs = entry.senderTimestamp !== undefined;
1764
+ const senderTsExpired = hasSenderTs && isTcTokenExpired(entry.senderTimestamp);
1765
+ const keepPeerToken = hasPeerToken && !peerTokenExpired;
1766
+ const keepSenderTs = hasSenderTs && !senderTsExpired;
1767
+ if (!keepPeerToken && !keepSenderTs) {
1768
+ writes[jid] = null;
1769
+ mutated++;
1770
+ }
1771
+ else if (peerTokenExpired && keepSenderTs) {
1772
+ writes[jid] = { token: Buffer.alloc(0), senderTimestamp: entry.senderTimestamp };
1773
+ survivors.add(jid);
1774
+ mutated++;
1775
+ }
1776
+ else {
1777
+ survivors.add(jid);
1778
+ }
1779
+ }
1780
+ if (mutated === 0)
1781
+ return;
1782
+ await authState.keys.set({
1783
+ tctoken: {
1784
+ ...writes,
1785
+ [TC_TOKEN_INDEX_KEY]: {
1786
+ token: Buffer.from(JSON.stringify([...survivors]))
1787
+ }
1788
+ }
1789
+ });
1790
+ tcTokenKnownJids.clear();
1791
+ for (const jid of survivors)
1792
+ tcTokenKnownJids.add(jid);
1793
+ logger.debug({ mutated, remaining: survivors.size }, 'pruned expired tctokens');
1794
+ }
1795
+ catch (err) {
1796
+ logger.warn({ err: err?.message }, 'failed to prune expired tctokens');
1797
+ }
1798
+ }
1044
1799
  return {
1045
1800
  ...sock,
1046
1801
  sendMessageAck,
1047
1802
  sendRetryRequest,
1048
1803
  rejectCall,
1049
- offerCall,
1050
1804
  fetchMessageHistory,
1051
1805
  requestPlaceholderResend,
1806
+ messageRetryManager
1052
1807
  };
1053
1808
  };
1054
- exports.makeMessagesRecvSocket = makeMessagesRecvSocket;
1809
+ //# sourceMappingURL=messages-recv.js.map