@sixcore/baileys 1.0.0 → 1.0.2

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 (228) hide show
  1. package/WAProto/index.js +14270 -302
  2. package/jessica.js +91 -0
  3. package/lib/Defaults/baileys-version.json +1 -1
  4. package/lib/Defaults/index.js +117 -79
  5. package/lib/Defaults/phonenumber-mcc.json +223 -0
  6. package/lib/Signal/Group/ciphertext-message.d.ts +9 -0
  7. package/lib/Signal/Group/ciphertext-message.js +15 -0
  8. package/lib/Signal/Group/group-session-builder.d.ts +14 -0
  9. package/lib/Signal/Group/group-session-builder.js +64 -0
  10. package/lib/Signal/Group/group_cipher.d.ts +17 -0
  11. package/lib/Signal/Group/group_cipher.js +96 -0
  12. package/lib/Signal/Group/index.d.ts +11 -0
  13. package/lib/Signal/Group/index.js +57 -0
  14. package/lib/Signal/Group/keyhelper.d.ts +10 -0
  15. package/lib/Signal/Group/keyhelper.js +55 -0
  16. package/lib/Signal/Group/queue-job.d.ts +1 -0
  17. package/lib/Signal/Group/queue-job.js +57 -0
  18. package/lib/Signal/Group/sender-chain-key.d.ts +13 -0
  19. package/lib/Signal/Group/sender-chain-key.js +34 -0
  20. package/lib/Signal/Group/sender-key-distribution-message.d.ts +16 -0
  21. package/lib/Signal/Group/sender-key-distribution-message.js +66 -0
  22. package/lib/Signal/Group/sender-key-message.d.ts +18 -0
  23. package/lib/Signal/Group/sender-key-message.js +69 -0
  24. package/lib/Signal/Group/sender-key-name.d.ts +17 -0
  25. package/lib/Signal/Group/sender-key-name.js +51 -0
  26. package/lib/Signal/Group/sender-key-record.d.ts +30 -0
  27. package/lib/Signal/Group/sender-key-record.js +53 -0
  28. package/lib/Signal/Group/sender-key-state.d.ts +38 -0
  29. package/lib/Signal/Group/sender-key-state.js +99 -0
  30. package/lib/Signal/Group/sender-message-key.d.ts +11 -0
  31. package/{WASignalGroup/sender_message_key.js → lib/Signal/Group/sender-message-key.js} +6 -16
  32. package/lib/Signal/libsignal.js +51 -29
  33. package/lib/Socket/business.d.ts +43 -42
  34. package/lib/Socket/chats.d.ts +222 -36
  35. package/lib/Socket/chats.js +173 -153
  36. package/lib/Socket/dugong.d.ts +254 -0
  37. package/lib/Socket/dugong.js +484 -0
  38. package/lib/Socket/groups.d.ts +7 -7
  39. package/lib/Socket/groups.js +37 -35
  40. package/lib/Socket/index.d.ts +52 -51
  41. package/lib/Socket/index.js +1 -0
  42. package/lib/Socket/messages-recv.d.ts +37 -34
  43. package/lib/Socket/messages-recv.js +175 -37
  44. package/lib/Socket/messages-send.d.ts +12 -18
  45. package/lib/Socket/messages-send.js +396 -574
  46. package/lib/Socket/newsletter.d.ts +28 -26
  47. package/lib/Socket/newsletter.js +132 -121
  48. package/lib/Socket/registration.d.ts +52 -49
  49. package/lib/Socket/registration.js +7 -7
  50. package/lib/Socket/socket.d.ts +0 -1
  51. package/lib/Socket/socket.js +49 -27
  52. package/lib/Socket/usync.d.ts +10 -11
  53. package/lib/Store/make-cache-manager-store.d.ts +1 -2
  54. package/lib/Store/make-in-memory-store.d.ts +2 -2
  55. package/lib/Store/make-in-memory-store.js +1 -5
  56. package/lib/Store/make-ordered-dictionary.js +2 -2
  57. package/lib/Types/Auth.d.ts +1 -0
  58. package/lib/Types/Call.d.ts +1 -1
  59. package/lib/Types/Chat.d.ts +7 -12
  60. package/lib/Types/Events.d.ts +2 -17
  61. package/lib/Types/GroupMetadata.d.ts +2 -3
  62. package/lib/Types/Label.d.ts +0 -11
  63. package/lib/Types/Label.js +1 -1
  64. package/lib/Types/LabelAssociation.js +1 -1
  65. package/lib/Types/Message.d.ts +10 -170
  66. package/lib/Types/Newsletter.d.ts +97 -86
  67. package/lib/Types/Newsletter.js +38 -32
  68. package/lib/Types/Socket.d.ts +2 -7
  69. package/lib/Types/index.d.ts +0 -9
  70. package/lib/Types/index.js +1 -1
  71. package/lib/Utils/auth-utils.js +14 -35
  72. package/lib/Utils/business.d.ts +1 -1
  73. package/lib/Utils/business.js +2 -2
  74. package/lib/Utils/chat-utils.d.ts +12 -11
  75. package/lib/Utils/chat-utils.js +36 -52
  76. package/lib/Utils/crypto.d.ts +16 -15
  77. package/lib/Utils/crypto.js +26 -74
  78. package/lib/Utils/decode-wa-message.d.ts +0 -17
  79. package/lib/Utils/decode-wa-message.js +17 -53
  80. package/lib/Utils/event-buffer.js +7 -10
  81. package/lib/Utils/generics.d.ts +17 -13
  82. package/lib/Utils/generics.js +79 -58
  83. package/lib/Utils/history.d.ts +2 -6
  84. package/lib/Utils/history.js +6 -4
  85. package/lib/Utils/logger.d.ts +3 -1
  86. package/lib/Utils/lt-hash.js +12 -12
  87. package/lib/Utils/make-mutex.d.ts +2 -2
  88. package/lib/Utils/messages-media.d.ts +28 -25
  89. package/lib/Utils/messages-media.js +733 -557
  90. package/lib/Utils/messages.js +68 -473
  91. package/lib/Utils/noise-handler.d.ts +5 -4
  92. package/lib/Utils/noise-handler.js +14 -19
  93. package/lib/Utils/process-message.d.ts +5 -5
  94. package/lib/Utils/process-message.js +23 -75
  95. package/lib/Utils/signal.d.ts +1 -2
  96. package/lib/Utils/signal.js +26 -32
  97. package/lib/Utils/use-multi-file-auth-state.d.ts +1 -0
  98. package/lib/Utils/use-multi-file-auth-state.js +66 -242
  99. package/lib/Utils/validate-connection.d.ts +1 -1
  100. package/lib/Utils/validate-connection.js +88 -64
  101. package/lib/WABinary/constants.d.ts +27 -24
  102. package/lib/WABinary/decode.d.ts +2 -1
  103. package/lib/WABinary/decode.js +11 -23
  104. package/lib/WABinary/encode.d.ts +2 -1
  105. package/lib/WABinary/encode.js +147 -134
  106. package/lib/WABinary/generic-utils.d.ts +5 -2
  107. package/lib/WABinary/generic-utils.js +125 -37
  108. package/lib/WABinary/jid-utils.d.ts +1 -1
  109. package/lib/WAM/BinaryInfo.d.ts +11 -2
  110. package/lib/WAM/encode.d.ts +2 -1
  111. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +3 -3
  112. package/lib/WAUSync/USyncUser.d.ts +2 -0
  113. package/lib/index.d.ts +12 -0
  114. package/lib/index.js +64 -1
  115. package/package.json +113 -51
  116. package/WAProto/GenerateStatics.sh +0 -4
  117. package/WAProto/WAProto.proto +0 -4357
  118. package/WAProto/index.d.ts +0 -50383
  119. package/WASignalGroup/GroupProtocol.js +0 -1697
  120. package/WASignalGroup/ciphertext_message.js +0 -16
  121. package/WASignalGroup/generate-proto.sh +0 -1
  122. package/WASignalGroup/group.proto +0 -42
  123. package/WASignalGroup/group_cipher.js +0 -120
  124. package/WASignalGroup/group_session_builder.js +0 -46
  125. package/WASignalGroup/index.js +0 -5
  126. package/WASignalGroup/keyhelper.js +0 -21
  127. package/WASignalGroup/protobufs.js +0 -3
  128. package/WASignalGroup/queue_job.js +0 -69
  129. package/WASignalGroup/sender_chain_key.js +0 -50
  130. package/WASignalGroup/sender_key_distribution_message.js +0 -78
  131. package/WASignalGroup/sender_key_message.js +0 -92
  132. package/WASignalGroup/sender_key_name.js +0 -70
  133. package/WASignalGroup/sender_key_record.js +0 -56
  134. package/WASignalGroup/sender_key_state.js +0 -129
  135. package/lib/Utils/use-single-file-auth-state.d.ts +0 -12
  136. package/lib/Utils/use-single-file-auth-state.js +0 -75
  137. package/src/Defaults/baileys-version.json +0 -3
  138. package/src/Defaults/index.ts +0 -133
  139. package/src/Signal/Group/ciphertext-message.ts +0 -9
  140. package/src/Signal/Group/group-session-builder.ts +0 -56
  141. package/src/Signal/Group/group_cipher.ts +0 -117
  142. package/src/Signal/Group/index.ts +0 -11
  143. package/src/Signal/Group/keyhelper.ts +0 -28
  144. package/src/Signal/Group/sender-chain-key.ts +0 -34
  145. package/src/Signal/Group/sender-key-distribution-message.ts +0 -95
  146. package/src/Signal/Group/sender-key-message.ts +0 -96
  147. package/src/Signal/Group/sender-key-name.ts +0 -66
  148. package/src/Signal/Group/sender-key-record.ts +0 -69
  149. package/src/Signal/Group/sender-key-state.ts +0 -134
  150. package/src/Signal/Group/sender-message-key.ts +0 -36
  151. package/src/Signal/libsignal.ts +0 -447
  152. package/src/Signal/lid-mapping.ts +0 -209
  153. package/src/Socket/Client/index.ts +0 -2
  154. package/src/Socket/Client/types.ts +0 -22
  155. package/src/Socket/Client/websocket.ts +0 -56
  156. package/src/Socket/business.ts +0 -421
  157. package/src/Socket/chats.ts +0 -1223
  158. package/src/Socket/communities.ts +0 -477
  159. package/src/Socket/groups.ts +0 -361
  160. package/src/Socket/index.ts +0 -22
  161. package/src/Socket/messages-recv.ts +0 -1563
  162. package/src/Socket/messages-send.ts +0 -1210
  163. package/src/Socket/mex.ts +0 -58
  164. package/src/Socket/newsletter.ts +0 -229
  165. package/src/Socket/socket.ts +0 -1072
  166. package/src/Types/Auth.ts +0 -115
  167. package/src/Types/Bussines.ts +0 -20
  168. package/src/Types/Call.ts +0 -14
  169. package/src/Types/Chat.ts +0 -138
  170. package/src/Types/Contact.ts +0 -24
  171. package/src/Types/Events.ts +0 -132
  172. package/src/Types/GroupMetadata.ts +0 -70
  173. package/src/Types/Label.ts +0 -48
  174. package/src/Types/LabelAssociation.ts +0 -35
  175. package/src/Types/Message.ts +0 -424
  176. package/src/Types/Newsletter.ts +0 -98
  177. package/src/Types/Product.ts +0 -85
  178. package/src/Types/Signal.ts +0 -76
  179. package/src/Types/Socket.ts +0 -150
  180. package/src/Types/State.ts +0 -43
  181. package/src/Types/USync.ts +0 -27
  182. package/src/Types/globals.d.ts +0 -8
  183. package/src/Types/index.ts +0 -67
  184. package/src/Utils/auth-utils.ts +0 -331
  185. package/src/Utils/browser-utils.ts +0 -31
  186. package/src/Utils/business.ts +0 -286
  187. package/src/Utils/chat-utils.ts +0 -933
  188. package/src/Utils/crypto.ts +0 -184
  189. package/src/Utils/decode-wa-message.ts +0 -355
  190. package/src/Utils/event-buffer.ts +0 -662
  191. package/src/Utils/generics.ts +0 -470
  192. package/src/Utils/history.ts +0 -114
  193. package/src/Utils/index.ts +0 -18
  194. package/src/Utils/link-preview.ts +0 -111
  195. package/src/Utils/logger.ts +0 -13
  196. package/src/Utils/lt-hash.ts +0 -65
  197. package/src/Utils/make-mutex.ts +0 -45
  198. package/src/Utils/message-retry-manager.ts +0 -229
  199. package/src/Utils/messages-media.ts +0 -820
  200. package/src/Utils/messages.ts +0 -1137
  201. package/src/Utils/noise-handler.ts +0 -192
  202. package/src/Utils/pre-key-manager.ts +0 -126
  203. package/src/Utils/process-message.ts +0 -622
  204. package/src/Utils/signal.ts +0 -214
  205. package/src/Utils/use-multi-file-auth-state.ts +0 -136
  206. package/src/Utils/validate-connection.ts +0 -253
  207. package/src/WABinary/constants.ts +0 -1305
  208. package/src/WABinary/decode.ts +0 -281
  209. package/src/WABinary/encode.ts +0 -253
  210. package/src/WABinary/generic-utils.ts +0 -127
  211. package/src/WABinary/index.ts +0 -5
  212. package/src/WABinary/jid-utils.ts +0 -128
  213. package/src/WABinary/types.ts +0 -17
  214. package/src/WAM/BinaryInfo.ts +0 -12
  215. package/src/WAM/constants.ts +0 -22889
  216. package/src/WAM/encode.ts +0 -169
  217. package/src/WAM/index.ts +0 -3
  218. package/src/WAUSync/Protocols/USyncContactProtocol.ts +0 -32
  219. package/src/WAUSync/Protocols/USyncDeviceProtocol.ts +0 -78
  220. package/src/WAUSync/Protocols/USyncDisappearingModeProtocol.ts +0 -35
  221. package/src/WAUSync/Protocols/USyncStatusProtocol.ts +0 -44
  222. package/src/WAUSync/Protocols/UsyncBotProfileProtocol.ts +0 -76
  223. package/src/WAUSync/Protocols/UsyncLIDProtocol.ts +0 -33
  224. package/src/WAUSync/Protocols/index.ts +0 -4
  225. package/src/WAUSync/USyncQuery.ts +0 -133
  226. package/src/WAUSync/USyncUser.ts +0 -32
  227. package/src/WAUSync/index.ts +0 -3
  228. package/src/index.ts +0 -13
@@ -1,1563 +0,0 @@
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 } from '../Defaults'
7
- import type {
8
- GroupParticipant,
9
- MessageReceiptType,
10
- MessageRelayOptions,
11
- MessageUserReceipt,
12
- SocketConfig,
13
- WACallEvent,
14
- WAMessage,
15
- WAMessageKey,
16
- WAPatchName
17
- } from '../Types'
18
- import { WAMessageStatus, WAMessageStubType } from '../Types'
19
- import {
20
- aesDecryptCTR,
21
- aesEncryptGCM,
22
- cleanMessage,
23
- Curve,
24
- decodeMediaRetryNode,
25
- decodeMessageNode,
26
- decryptMessageNode,
27
- delay,
28
- derivePairingCodeKey,
29
- encodeBigEndian,
30
- encodeSignedDeviceIdentity,
31
- extractAddressingContext,
32
- getCallStatusFromNode,
33
- getHistoryMsg,
34
- getNextPreKeys,
35
- getStatusFromReceiptType,
36
- hkdf,
37
- MISSING_KEYS_ERROR_TEXT,
38
- NACK_REASONS,
39
- NO_MESSAGE_FOUND_ERROR_TEXT,
40
- unixTimestampSeconds,
41
- xmppPreKey,
42
- xmppSignedPreKey
43
- } from '../Utils'
44
- import { makeMutex } from '../Utils/make-mutex'
45
- import {
46
- areJidsSameUser,
47
- type BinaryNode,
48
- binaryNodeToString,
49
- getAllBinaryNodeChildren,
50
- getBinaryNodeChild,
51
- getBinaryNodeChildBuffer,
52
- getBinaryNodeChildren,
53
- getBinaryNodeChildString,
54
- isJidGroup,
55
- isJidNewsletter,
56
- isJidStatusBroadcast,
57
- isLidUser,
58
- isPnUser,
59
- jidDecode,
60
- jidNormalizedUser,
61
- S_WHATSAPP_NET
62
- } from '../WABinary'
63
- import { extractGroupMetadata } from './groups'
64
- import { makeMessagesSocket } from './messages-send'
65
-
66
- export const makeMessagesRecvSocket = (config: SocketConfig) => {
67
- const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid, enableAutoSessionRecreation } =
68
- config
69
- const sock = makeMessagesSocket(config)
70
- const {
71
- ev,
72
- authState,
73
- ws,
74
- processingMutex,
75
- signalRepository,
76
- query,
77
- upsertMessage,
78
- resyncAppState,
79
- onUnexpectedError,
80
- assertSessions,
81
- sendNode,
82
- relayMessage,
83
- sendReceipt,
84
- uploadPreKeys,
85
- sendPeerDataOperationMessage,
86
- messageRetryManager
87
- } = sock
88
-
89
- /** this mutex ensures that each retryRequest will wait for the previous one to finish */
90
- const retryMutex = makeMutex()
91
-
92
- const msgRetryCache =
93
- config.msgRetryCounterCache ||
94
- new NodeCache<number>({
95
- stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
96
- useClones: false
97
- })
98
- const callOfferCache =
99
- config.callOfferCache ||
100
- new NodeCache<WACallEvent>({
101
- stdTTL: DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins
102
- useClones: false
103
- })
104
-
105
- const placeholderResendCache =
106
- config.placeholderResendCache ||
107
- new NodeCache({
108
- stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
109
- useClones: false
110
- })
111
-
112
- // Debounce identity-change session refreshes per JID to avoid bursts
113
- const identityAssertDebounce = new NodeCache<boolean>({ stdTTL: 5, useClones: false })
114
-
115
- let sendActiveReceipts = false
116
-
117
- const fetchMessageHistory = async (
118
- count: number,
119
- oldestMsgKey: WAMessageKey,
120
- oldestMsgTimestamp: number | Long
121
- ): Promise<string> => {
122
- if (!authState.creds.me?.id) {
123
- throw new Boom('Not authenticated')
124
- }
125
-
126
- const pdoMessage: proto.Message.IPeerDataOperationRequestMessage = {
127
- historySyncOnDemandRequest: {
128
- chatJid: oldestMsgKey.remoteJid,
129
- oldestMsgFromMe: oldestMsgKey.fromMe,
130
- oldestMsgId: oldestMsgKey.id,
131
- oldestMsgTimestampMs: oldestMsgTimestamp,
132
- onDemandMsgCount: count
133
- },
134
- peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.HISTORY_SYNC_ON_DEMAND
135
- }
136
-
137
- return sendPeerDataOperationMessage(pdoMessage)
138
- }
139
-
140
- const requestPlaceholderResend = async (messageKey: WAMessageKey): Promise<string | undefined> => {
141
- if (!authState.creds.me?.id) {
142
- throw new Boom('Not authenticated')
143
- }
144
-
145
- if (placeholderResendCache.get(messageKey?.id!)) {
146
- logger.debug({ messageKey }, 'already requested resend')
147
- return
148
- } else {
149
- await placeholderResendCache.set(messageKey?.id!, true)
150
- }
151
-
152
- await delay(5000)
153
-
154
- if (!placeholderResendCache.get(messageKey?.id!)) {
155
- logger.debug({ messageKey }, 'message received while resend requested')
156
- return 'RESOLVED'
157
- }
158
-
159
- const pdoMessage = {
160
- placeholderMessageResendRequest: [
161
- {
162
- messageKey
163
- }
164
- ],
165
- peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
166
- }
167
-
168
- setTimeout(async () => {
169
- if (placeholderResendCache.get(messageKey?.id!)) {
170
- logger.debug({ messageKey }, 'PDO message without response after 15 seconds. Phone possibly offline')
171
- await placeholderResendCache.del(messageKey?.id!)
172
- }
173
- }, 15_000)
174
-
175
- return sendPeerDataOperationMessage(pdoMessage)
176
- }
177
-
178
- // Handles mex newsletter notifications
179
- const handleMexNewsletterNotification = async (node: BinaryNode) => {
180
- const mexNode = getBinaryNodeChild(node, 'mex')
181
- if (!mexNode?.content) {
182
- logger.warn({ node }, 'Invalid mex newsletter notification')
183
- return
184
- }
185
-
186
- let data: any
187
- try {
188
- data = JSON.parse(mexNode.content.toString())
189
- } catch (error) {
190
- logger.error({ err: error, node }, 'Failed to parse mex newsletter notification')
191
- return
192
- }
193
-
194
- const operation = data?.operation
195
- const updates = data?.updates
196
-
197
- if (!updates || !operation) {
198
- logger.warn({ data }, 'Invalid mex newsletter notification content')
199
- return
200
- }
201
-
202
- logger.info({ operation, updates }, 'got mex newsletter notification')
203
-
204
- switch (operation) {
205
- case 'NotificationNewsletterUpdate':
206
- for (const update of updates) {
207
- if (update.jid && update.settings && Object.keys(update.settings).length > 0) {
208
- ev.emit('newsletter-settings.update', {
209
- id: update.jid,
210
- update: update.settings
211
- })
212
- }
213
- }
214
-
215
- break
216
-
217
- case 'NotificationNewsletterAdminPromote':
218
- for (const update of updates) {
219
- if (update.jid && update.user) {
220
- ev.emit('newsletter-participants.update', {
221
- id: update.jid,
222
- author: node.attrs.from!,
223
- user: update.user,
224
- new_role: 'ADMIN',
225
- action: 'promote'
226
- })
227
- }
228
- }
229
-
230
- break
231
-
232
- default:
233
- logger.info({ operation, data }, 'Unhandled mex newsletter notification')
234
- break
235
- }
236
- }
237
-
238
- // Handles newsletter notifications
239
- const handleNewsletterNotification = async (node: BinaryNode) => {
240
- const from = node.attrs.from!
241
- const child = getAllBinaryNodeChildren(node)[0]!
242
- const author = node.attrs.participant!
243
-
244
- logger.info({ from, child }, 'got newsletter notification')
245
-
246
- switch (child.tag) {
247
- case 'reaction':
248
- const reactionUpdate = {
249
- id: from,
250
- server_id: child.attrs.message_id!,
251
- reaction: {
252
- code: getBinaryNodeChildString(child, 'reaction'),
253
- count: 1
254
- }
255
- }
256
- ev.emit('newsletter.reaction', reactionUpdate)
257
- break
258
-
259
- case 'view':
260
- const viewUpdate = {
261
- id: from,
262
- server_id: child.attrs.message_id!,
263
- count: parseInt(child.content?.toString() || '0', 10)
264
- }
265
- ev.emit('newsletter.view', viewUpdate)
266
- break
267
-
268
- case 'participant':
269
- const participantUpdate = {
270
- id: from,
271
- author,
272
- user: child.attrs.jid!,
273
- action: child.attrs.action!,
274
- new_role: child.attrs.role!
275
- }
276
- ev.emit('newsletter-participants.update', participantUpdate)
277
- break
278
-
279
- case 'update':
280
- const settingsNode = getBinaryNodeChild(child, 'settings')
281
- if (settingsNode) {
282
- const update: Record<string, any> = {}
283
- const nameNode = getBinaryNodeChild(settingsNode, 'name')
284
- if (nameNode?.content) update.name = nameNode.content.toString()
285
-
286
- const descriptionNode = getBinaryNodeChild(settingsNode, 'description')
287
- if (descriptionNode?.content) update.description = descriptionNode.content.toString()
288
-
289
- ev.emit('newsletter-settings.update', {
290
- id: from,
291
- update
292
- })
293
- }
294
-
295
- break
296
-
297
- case 'message':
298
- const plaintextNode = getBinaryNodeChild(child, 'plaintext')
299
- if (plaintextNode?.content) {
300
- try {
301
- const contentBuf =
302
- typeof plaintextNode.content === 'string'
303
- ? Buffer.from(plaintextNode.content, 'binary')
304
- : Buffer.from(plaintextNode.content as Uint8Array)
305
- const messageProto = proto.Message.decode(contentBuf).toJSON()
306
- const fullMessage = proto.WebMessageInfo.fromObject({
307
- key: {
308
- remoteJid: from,
309
- id: child.attrs.message_id || child.attrs.server_id,
310
- fromMe: false // TODO: is this really true though
311
- },
312
- message: messageProto,
313
- messageTimestamp: +child.attrs.t!
314
- }).toJSON() as WAMessage
315
- await upsertMessage(fullMessage, 'append')
316
- logger.info('Processed plaintext newsletter message')
317
- } catch (error) {
318
- logger.error({ error }, 'Failed to decode plaintext newsletter message')
319
- }
320
- }
321
-
322
- break
323
-
324
- default:
325
- logger.warn({ node }, 'Unknown newsletter notification')
326
- break
327
- }
328
- }
329
-
330
- const sendMessageAck = async ({ tag, attrs, content }: BinaryNode, errorCode?: number) => {
331
- const stanza: BinaryNode = {
332
- tag: 'ack',
333
- attrs: {
334
- id: attrs.id!,
335
- to: attrs.from!,
336
- class: tag
337
- }
338
- }
339
-
340
- if (!!errorCode) {
341
- stanza.attrs.error = errorCode.toString()
342
- }
343
-
344
- if (!!attrs.participant) {
345
- stanza.attrs.participant = attrs.participant
346
- }
347
-
348
- if (!!attrs.recipient) {
349
- stanza.attrs.recipient = attrs.recipient
350
- }
351
-
352
- if (
353
- !!attrs.type &&
354
- (tag !== 'message' || getBinaryNodeChild({ tag, attrs, content }, 'unavailable') || errorCode !== 0)
355
- ) {
356
- stanza.attrs.type = attrs.type
357
- }
358
-
359
- if (tag === 'message' && getBinaryNodeChild({ tag, attrs, content }, 'unavailable')) {
360
- stanza.attrs.from = authState.creds.me!.id
361
- }
362
-
363
- logger.debug({ recv: { tag, attrs }, sent: stanza.attrs }, 'sent ack')
364
- await sendNode(stanza)
365
- }
366
-
367
- const rejectCall = async (callId: string, callFrom: string) => {
368
- const stanza: BinaryNode = {
369
- tag: 'call',
370
- attrs: {
371
- from: authState.creds.me!.id,
372
- to: callFrom
373
- },
374
- content: [
375
- {
376
- tag: 'reject',
377
- attrs: {
378
- 'call-id': callId,
379
- 'call-creator': callFrom,
380
- count: '0'
381
- },
382
- content: undefined
383
- }
384
- ]
385
- }
386
- await query(stanza)
387
- }
388
-
389
- const sendRetryRequest = async (node: BinaryNode, forceIncludeKeys = false) => {
390
- const { fullMessage } = decodeMessageNode(node, authState.creds.me!.id, authState.creds.me!.lid || '')
391
- const { key: msgKey } = fullMessage
392
- const msgId = msgKey.id!
393
-
394
- if (messageRetryManager) {
395
- // Check if we've exceeded max retries using the new system
396
- if (messageRetryManager.hasExceededMaxRetries(msgId)) {
397
- logger.debug({ msgId }, 'reached retry limit with new retry manager, clearing')
398
- messageRetryManager.markRetryFailed(msgId)
399
- return
400
- }
401
-
402
- // Increment retry count using new system
403
- const retryCount = messageRetryManager.incrementRetryCount(msgId)
404
-
405
- // Use the new retry count for the rest of the logic
406
- const key = `${msgId}:${msgKey?.participant}`
407
- await msgRetryCache.set(key, retryCount)
408
- } else {
409
- // Fallback to old system
410
- const key = `${msgId}:${msgKey?.participant}`
411
- let retryCount = (await msgRetryCache.get<number>(key)) || 0
412
- if (retryCount >= maxMsgRetryCount) {
413
- logger.debug({ retryCount, msgId }, 'reached retry limit, clearing')
414
- await msgRetryCache.del(key)
415
- return
416
- }
417
-
418
- retryCount += 1
419
- await msgRetryCache.set(key, retryCount)
420
- }
421
-
422
- const key = `${msgId}:${msgKey?.participant}`
423
- const retryCount = (await msgRetryCache.get<number>(key)) || 1
424
-
425
- const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds
426
- const fromJid = node.attrs.from!
427
-
428
- // Check if we should recreate the session
429
- let shouldRecreateSession = false
430
- let recreateReason = ''
431
-
432
- if (enableAutoSessionRecreation && messageRetryManager) {
433
- try {
434
- // Check if we have a session with this JID
435
- const sessionId = signalRepository.jidToSignalProtocolAddress(fromJid)
436
- const hasSession = await signalRepository.validateSession(fromJid)
437
- const result = messageRetryManager.shouldRecreateSession(fromJid, retryCount, hasSession.exists)
438
- shouldRecreateSession = result.recreate
439
- recreateReason = result.reason
440
-
441
- if (shouldRecreateSession) {
442
- logger.debug({ fromJid, retryCount, reason: recreateReason }, 'recreating session for retry')
443
- // Delete existing session to force recreation
444
- await authState.keys.set({ session: { [sessionId]: null } })
445
- forceIncludeKeys = true
446
- }
447
- } catch (error) {
448
- logger.warn({ error, fromJid }, 'failed to check session recreation')
449
- }
450
- }
451
-
452
- if (retryCount <= 2) {
453
- // Use new retry manager for phone requests if available
454
- if (messageRetryManager) {
455
- // Schedule phone request with delay (like whatsmeow)
456
- messageRetryManager.schedulePhoneRequest(msgId, async () => {
457
- try {
458
- const requestId = await requestPlaceholderResend(msgKey)
459
- logger.debug(
460
- `sendRetryRequest: requested placeholder resend (${requestId}) for message ${msgId} (scheduled)`
461
- )
462
- } catch (error) {
463
- logger.warn({ error, msgId }, 'failed to send scheduled phone request')
464
- }
465
- })
466
- } else {
467
- // Fallback to immediate request
468
- const msgId = await requestPlaceholderResend(msgKey)
469
- logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`)
470
- }
471
- }
472
-
473
- const deviceIdentity = encodeSignedDeviceIdentity(account!, true)
474
- await authState.keys.transaction(async () => {
475
- const receipt: BinaryNode = {
476
- tag: 'receipt',
477
- attrs: {
478
- id: msgId,
479
- type: 'retry',
480
- to: node.attrs.from!
481
- },
482
- content: [
483
- {
484
- tag: 'retry',
485
- attrs: {
486
- count: retryCount.toString(),
487
- id: node.attrs.id!,
488
- t: node.attrs.t!,
489
- v: '1',
490
- // ADD ERROR FIELD
491
- error: '0'
492
- }
493
- },
494
- {
495
- tag: 'registration',
496
- attrs: {},
497
- content: encodeBigEndian(authState.creds.registrationId)
498
- }
499
- ]
500
- }
501
-
502
- if (node.attrs.recipient) {
503
- receipt.attrs.recipient = node.attrs.recipient
504
- }
505
-
506
- if (node.attrs.participant) {
507
- receipt.attrs.participant = node.attrs.participant
508
- }
509
-
510
- if (retryCount > 1 || forceIncludeKeys || shouldRecreateSession) {
511
- const { update, preKeys } = await getNextPreKeys(authState, 1)
512
-
513
- const [keyId] = Object.keys(preKeys)
514
- const key = preKeys[+keyId!]
515
-
516
- const content = receipt.content! as BinaryNode[]
517
- content.push({
518
- tag: 'keys',
519
- attrs: {},
520
- content: [
521
- { tag: 'type', attrs: {}, content: Buffer.from(KEY_BUNDLE_TYPE) },
522
- { tag: 'identity', attrs: {}, content: identityKey.public },
523
- xmppPreKey(key!, +keyId!),
524
- xmppSignedPreKey(signedPreKey),
525
- { tag: 'device-identity', attrs: {}, content: deviceIdentity }
526
- ]
527
- })
528
-
529
- ev.emit('creds.update', update)
530
- }
531
-
532
- await sendNode(receipt)
533
-
534
- logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt')
535
- }, authState?.creds?.me?.id || 'sendRetryRequest')
536
- }
537
-
538
- const handleEncryptNotification = async (node: BinaryNode) => {
539
- const from = node.attrs.from
540
- if (from === S_WHATSAPP_NET) {
541
- const countChild = getBinaryNodeChild(node, 'count')
542
- const count = +countChild!.attrs.value!
543
- const shouldUploadMorePreKeys = count < MIN_PREKEY_COUNT
544
-
545
- logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count')
546
- if (shouldUploadMorePreKeys) {
547
- await uploadPreKeys()
548
- }
549
- } else {
550
- const identityNode = getBinaryNodeChild(node, 'identity')
551
- if (identityNode) {
552
- logger.info({ jid: from }, 'identity changed')
553
- if (identityAssertDebounce.get(from!)) {
554
- logger.debug({ jid: from }, 'skipping identity assert (debounced)')
555
- return
556
- }
557
-
558
- identityAssertDebounce.set(from!, true)
559
- try {
560
- await assertSessions([from!], true)
561
- } catch (error) {
562
- logger.warn({ error, jid: from }, 'failed to assert sessions after identity change')
563
- }
564
- } else {
565
- logger.info({ node }, 'unknown encrypt notification')
566
- }
567
- }
568
- }
569
-
570
- const handleGroupNotification = (fullNode: BinaryNode, child: BinaryNode, msg: Partial<WAMessage>) => {
571
- // TODO: Support PN/LID (Here is only LID now)
572
-
573
- const actingParticipantLid = fullNode.attrs.participant
574
- const actingParticipantPn = fullNode.attrs.participant_pn
575
-
576
- const affectedParticipantLid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || actingParticipantLid!
577
- const affectedParticipantPn = getBinaryNodeChild(child, 'participant')?.attrs?.phone_number || actingParticipantPn!
578
-
579
- switch (child?.tag) {
580
- case 'create':
581
- const metadata = extractGroupMetadata(child)
582
-
583
- msg.messageStubType = WAMessageStubType.GROUP_CREATE
584
- msg.messageStubParameters = [metadata.subject]
585
- msg.key = { participant: metadata.owner, participantAlt: metadata.ownerPn }
586
-
587
- ev.emit('chats.upsert', [
588
- {
589
- id: metadata.id,
590
- name: metadata.subject,
591
- conversationTimestamp: metadata.creation
592
- }
593
- ])
594
- ev.emit('groups.upsert', [
595
- {
596
- ...metadata,
597
- author: actingParticipantLid,
598
- authorPn: actingParticipantPn
599
- }
600
- ])
601
- break
602
- case 'ephemeral':
603
- case 'not_ephemeral':
604
- msg.message = {
605
- protocolMessage: {
606
- type: proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
607
- ephemeralExpiration: +(child.attrs.expiration || 0)
608
- }
609
- }
610
- break
611
- case 'modify':
612
- const oldNumber = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid!)
613
- msg.messageStubParameters = oldNumber || []
614
- msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER
615
- break
616
- case 'promote':
617
- case 'demote':
618
- case 'remove':
619
- case 'add':
620
- case 'leave':
621
- const stubType = `GROUP_PARTICIPANT_${child.tag.toUpperCase()}`
622
- msg.messageStubType = WAMessageStubType[stubType as keyof typeof WAMessageStubType]
623
-
624
- const participants = getBinaryNodeChildren(child, 'participant').map(({ attrs }) => {
625
- // TODO: Store LID MAPPINGS
626
- return {
627
- id: attrs.jid!,
628
- phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined,
629
- lid: isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined,
630
- admin: (attrs.type || null) as GroupParticipant['admin']
631
- }
632
- })
633
-
634
- if (
635
- participants.length === 1 &&
636
- // if recv. "remove" message and sender removed themselves
637
- // mark as left
638
- (areJidsSameUser(participants[0]!.id, actingParticipantLid) ||
639
- areJidsSameUser(participants[0]!.id, actingParticipantPn)) &&
640
- child.tag === 'remove'
641
- ) {
642
- msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_LEAVE
643
- }
644
-
645
- msg.messageStubParameters = participants.map(a => JSON.stringify(a))
646
- break
647
- case 'subject':
648
- msg.messageStubType = WAMessageStubType.GROUP_CHANGE_SUBJECT
649
- msg.messageStubParameters = [child.attrs.subject!]
650
- break
651
- case 'description':
652
- const description = getBinaryNodeChild(child, 'body')?.content?.toString()
653
- msg.messageStubType = WAMessageStubType.GROUP_CHANGE_DESCRIPTION
654
- msg.messageStubParameters = description ? [description] : undefined
655
- break
656
- case 'announcement':
657
- case 'not_announcement':
658
- msg.messageStubType = WAMessageStubType.GROUP_CHANGE_ANNOUNCE
659
- msg.messageStubParameters = [child.tag === 'announcement' ? 'on' : 'off']
660
- break
661
- case 'locked':
662
- case 'unlocked':
663
- msg.messageStubType = WAMessageStubType.GROUP_CHANGE_RESTRICT
664
- msg.messageStubParameters = [child.tag === 'locked' ? 'on' : 'off']
665
- break
666
- case 'invite':
667
- msg.messageStubType = WAMessageStubType.GROUP_CHANGE_INVITE_LINK
668
- msg.messageStubParameters = [child.attrs.code!]
669
- break
670
- case 'member_add_mode':
671
- const addMode = child.content
672
- if (addMode) {
673
- msg.messageStubType = WAMessageStubType.GROUP_MEMBER_ADD_MODE
674
- msg.messageStubParameters = [addMode.toString()]
675
- }
676
-
677
- break
678
- case 'membership_approval_mode':
679
- const approvalMode = getBinaryNodeChild(child, 'group_join')
680
- if (approvalMode) {
681
- msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE
682
- msg.messageStubParameters = [approvalMode.attrs.state!]
683
- }
684
-
685
- break
686
- case 'created_membership_requests':
687
- msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD
688
- msg.messageStubParameters = [
689
- JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
690
- 'created',
691
- child.attrs.request_method!
692
- ]
693
- break
694
- case 'revoked_membership_requests':
695
- const isDenied = areJidsSameUser(affectedParticipantLid, actingParticipantLid)
696
- // TODO: LIDMAPPING SUPPORT
697
- msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD
698
- msg.messageStubParameters = [
699
- JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
700
- isDenied ? 'revoked' : 'rejected'
701
- ]
702
- break
703
- }
704
- }
705
-
706
- const processNotification = async (node: BinaryNode) => {
707
- const result: Partial<WAMessage> = {}
708
- const [child] = getAllBinaryNodeChildren(node)
709
- const nodeType = node.attrs.type
710
- const from = jidNormalizedUser(node.attrs.from)
711
-
712
- switch (nodeType) {
713
- case 'newsletter':
714
- await handleNewsletterNotification(node)
715
- break
716
- case 'mex':
717
- await handleMexNewsletterNotification(node)
718
- break
719
- case 'w:gp2':
720
- // TODO: HANDLE PARTICIPANT_PN
721
- handleGroupNotification(node, child!, result)
722
- break
723
- case 'mediaretry':
724
- const event = decodeMediaRetryNode(node)
725
- ev.emit('messages.media-update', [event])
726
- break
727
- case 'encrypt':
728
- await handleEncryptNotification(node)
729
- break
730
- case 'devices':
731
- const devices = getBinaryNodeChildren(child, 'device')
732
- if (
733
- areJidsSameUser(child!.attrs.jid, authState.creds.me!.id) ||
734
- areJidsSameUser(child!.attrs.lid, authState.creds.me!.lid)
735
- ) {
736
- const deviceData = devices.map(d => ({ id: d.attrs.jid, lid: d.attrs.lid }))
737
- logger.info({ deviceData }, 'my own devices changed')
738
- }
739
-
740
- //TODO: drop a new event, add hashes
741
-
742
- break
743
- case 'server_sync':
744
- const update = getBinaryNodeChild(node, 'collection')
745
- if (update) {
746
- const name = update.attrs.name as WAPatchName
747
- await resyncAppState([name], false)
748
- }
749
-
750
- break
751
- case 'picture':
752
- const setPicture = getBinaryNodeChild(node, 'set')
753
- const delPicture = getBinaryNodeChild(node, 'delete')
754
-
755
- ev.emit('contacts.update', [
756
- {
757
- id: jidNormalizedUser(node?.attrs?.from) || (setPicture || delPicture)?.attrs?.hash || '',
758
- imgUrl: setPicture ? 'changed' : 'removed'
759
- }
760
- ])
761
-
762
- if (isJidGroup(from)) {
763
- const node = setPicture || delPicture
764
- result.messageStubType = WAMessageStubType.GROUP_CHANGE_ICON
765
-
766
- if (setPicture) {
767
- result.messageStubParameters = [setPicture.attrs.id!]
768
- }
769
-
770
- result.participant = node?.attrs.author
771
- result.key = {
772
- ...(result.key || {}),
773
- participant: setPicture?.attrs.author
774
- }
775
- }
776
-
777
- break
778
- case 'account_sync':
779
- if (child!.tag === 'disappearing_mode') {
780
- const newDuration = +child!.attrs.duration!
781
- const timestamp = +child!.attrs.t!
782
-
783
- logger.info({ newDuration }, 'updated account disappearing mode')
784
-
785
- ev.emit('creds.update', {
786
- accountSettings: {
787
- ...authState.creds.accountSettings,
788
- defaultDisappearingMode: {
789
- ephemeralExpiration: newDuration,
790
- ephemeralSettingTimestamp: timestamp
791
- }
792
- }
793
- })
794
- } else if (child!.tag === 'blocklist') {
795
- const blocklists = getBinaryNodeChildren(child, 'item')
796
-
797
- for (const { attrs } of blocklists) {
798
- const blocklist = [attrs.jid!]
799
- const type = attrs.action === 'block' ? 'add' : 'remove'
800
- ev.emit('blocklist.update', { blocklist, type })
801
- }
802
- }
803
-
804
- break
805
- case 'link_code_companion_reg':
806
- const linkCodeCompanionReg = getBinaryNodeChild(node, 'link_code_companion_reg')
807
- const ref = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_ref'))
808
- const primaryIdentityPublicKey = toRequiredBuffer(
809
- getBinaryNodeChildBuffer(linkCodeCompanionReg, 'primary_identity_pub')
810
- )
811
- const primaryEphemeralPublicKeyWrapped = toRequiredBuffer(
812
- getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub')
813
- )
814
- const codePairingPublicKey = await decipherLinkPublicKey(primaryEphemeralPublicKeyWrapped)
815
- const companionSharedKey = Curve.sharedKey(
816
- authState.creds.pairingEphemeralKeyPair.private,
817
- codePairingPublicKey
818
- )
819
- const random = randomBytes(32)
820
- const linkCodeSalt = randomBytes(32)
821
- const linkCodePairingExpanded = await hkdf(companionSharedKey, 32, {
822
- salt: linkCodeSalt,
823
- info: 'link_code_pairing_key_bundle_encryption_key'
824
- })
825
- const encryptPayload = Buffer.concat([
826
- Buffer.from(authState.creds.signedIdentityKey.public),
827
- primaryIdentityPublicKey,
828
- random
829
- ])
830
- const encryptIv = randomBytes(12)
831
- const encrypted = aesEncryptGCM(encryptPayload, linkCodePairingExpanded, encryptIv, Buffer.alloc(0))
832
- const encryptedPayload = Buffer.concat([linkCodeSalt, encryptIv, encrypted])
833
- const identitySharedKey = Curve.sharedKey(authState.creds.signedIdentityKey.private, primaryIdentityPublicKey)
834
- const identityPayload = Buffer.concat([companionSharedKey, identitySharedKey, random])
835
- authState.creds.advSecretKey = (await hkdf(identityPayload, 32, { info: 'adv_secret' })).toString('base64')
836
- await query({
837
- tag: 'iq',
838
- attrs: {
839
- to: S_WHATSAPP_NET,
840
- type: 'set',
841
- id: sock.generateMessageTag(),
842
- xmlns: 'md'
843
- },
844
- content: [
845
- {
846
- tag: 'link_code_companion_reg',
847
- attrs: {
848
- jid: authState.creds.me!.id,
849
- stage: 'companion_finish'
850
- },
851
- content: [
852
- {
853
- tag: 'link_code_pairing_wrapped_key_bundle',
854
- attrs: {},
855
- content: encryptedPayload
856
- },
857
- {
858
- tag: 'companion_identity_public',
859
- attrs: {},
860
- content: authState.creds.signedIdentityKey.public
861
- },
862
- {
863
- tag: 'link_code_pairing_ref',
864
- attrs: {},
865
- content: ref
866
- }
867
- ]
868
- }
869
- ]
870
- })
871
- authState.creds.registered = true
872
- ev.emit('creds.update', authState.creds)
873
- break
874
- case 'privacy_token':
875
- await handlePrivacyTokenNotification(node)
876
- break
877
- }
878
-
879
- if (Object.keys(result).length) {
880
- return result
881
- }
882
- }
883
-
884
- const handlePrivacyTokenNotification = async (node: BinaryNode) => {
885
- const tokensNode = getBinaryNodeChild(node, 'tokens')
886
- const from = jidNormalizedUser(node.attrs.from)
887
-
888
- if (!tokensNode) return
889
-
890
- const tokenNodes = getBinaryNodeChildren(tokensNode, 'token')
891
-
892
- for (const tokenNode of tokenNodes) {
893
- const { attrs, content } = tokenNode
894
- const type = attrs.type
895
- const timestamp = attrs.t
896
-
897
- if (type === 'trusted_contact' && content instanceof Buffer) {
898
- logger.debug(
899
- {
900
- from,
901
- timestamp,
902
- tcToken: content
903
- },
904
- 'received trusted contact token'
905
- )
906
-
907
- await authState.keys.set({
908
- tctoken: { [from]: { token: content, timestamp } }
909
- })
910
- }
911
- }
912
- }
913
-
914
- async function decipherLinkPublicKey(data: Uint8Array | Buffer) {
915
- const buffer = toRequiredBuffer(data)
916
- const salt = buffer.slice(0, 32)
917
- const secretKey = await derivePairingCodeKey(authState.creds.pairingCode!, salt)
918
- const iv = buffer.slice(32, 48)
919
- const payload = buffer.slice(48, 80)
920
- return aesDecryptCTR(payload, secretKey, iv)
921
- }
922
-
923
- function toRequiredBuffer(data: Uint8Array | Buffer | undefined) {
924
- if (data === undefined) {
925
- throw new Boom('Invalid buffer', { statusCode: 400 })
926
- }
927
-
928
- return data instanceof Buffer ? data : Buffer.from(data)
929
- }
930
-
931
- const willSendMessageAgain = async (id: string, participant: string) => {
932
- const key = `${id}:${participant}`
933
- const retryCount = (await msgRetryCache.get<number>(key)) || 0
934
- return retryCount < maxMsgRetryCount
935
- }
936
-
937
- const updateSendMessageAgainCount = async (id: string, participant: string) => {
938
- const key = `${id}:${participant}`
939
- const newValue = ((await msgRetryCache.get<number>(key)) || 0) + 1
940
- await msgRetryCache.set(key, newValue)
941
- }
942
-
943
- const sendMessagesAgain = async (key: WAMessageKey, ids: string[], retryNode: BinaryNode) => {
944
- const remoteJid = key.remoteJid!
945
- const participant = key.participant || remoteJid
946
-
947
- const retryCount = +retryNode.attrs.count! || 1
948
-
949
- // Try to get messages from cache first, then fallback to getMessage
950
- const msgs: (proto.IMessage | undefined)[] = []
951
- for (const id of ids) {
952
- let msg: proto.IMessage | undefined
953
-
954
- // Try to get from retry cache first if enabled
955
- if (messageRetryManager) {
956
- const cachedMsg = messageRetryManager.getRecentMessage(remoteJid, id)
957
- if (cachedMsg) {
958
- msg = cachedMsg.message
959
- logger.debug({ jid: remoteJid, id }, 'found message in retry cache')
960
-
961
- // Mark retry as successful since we found the message
962
- messageRetryManager.markRetrySuccess(id)
963
- }
964
- }
965
-
966
- // Fallback to getMessage if not found in cache
967
- if (!msg) {
968
- msg = await getMessage({ ...key, id })
969
- if (msg) {
970
- logger.debug({ jid: remoteJid, id }, 'found message via getMessage')
971
- // Also mark as successful if found via getMessage
972
- if (messageRetryManager) {
973
- messageRetryManager.markRetrySuccess(id)
974
- }
975
- }
976
- }
977
-
978
- msgs.push(msg)
979
- }
980
-
981
- // if it's the primary jid sending the request
982
- // just re-send the message to everyone
983
- // prevents the first message decryption failure
984
- const sendToAll = !jidDecode(participant)?.device
985
-
986
- // Check if we should recreate session for this retry
987
- let shouldRecreateSession = false
988
- let recreateReason = ''
989
-
990
- if (enableAutoSessionRecreation && messageRetryManager) {
991
- try {
992
- const sessionId = signalRepository.jidToSignalProtocolAddress(participant)
993
-
994
- const hasSession = await signalRepository.validateSession(participant)
995
- const result = messageRetryManager.shouldRecreateSession(participant, retryCount, hasSession.exists)
996
- shouldRecreateSession = result.recreate
997
- recreateReason = result.reason
998
-
999
- if (shouldRecreateSession) {
1000
- logger.debug({ participant, retryCount, reason: recreateReason }, 'recreating session for outgoing retry')
1001
- await authState.keys.set({ session: { [sessionId]: null } })
1002
- }
1003
- } catch (error) {
1004
- logger.warn({ error, participant }, 'failed to check session recreation for outgoing retry')
1005
- }
1006
- }
1007
-
1008
- await assertSessions([participant], true)
1009
-
1010
- if (isJidGroup(remoteJid)) {
1011
- await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } })
1012
- }
1013
-
1014
- logger.debug({ participant, sendToAll, shouldRecreateSession, recreateReason }, 'forced new session for retry recp')
1015
-
1016
- for (const [i, msg] of msgs.entries()) {
1017
- if (!ids[i]) continue
1018
-
1019
- if (msg && (await willSendMessageAgain(ids[i], participant))) {
1020
- await updateSendMessageAgainCount(ids[i], participant)
1021
- const msgRelayOpts: MessageRelayOptions = { messageId: ids[i] }
1022
-
1023
- if (sendToAll) {
1024
- msgRelayOpts.useUserDevicesCache = false
1025
- } else {
1026
- msgRelayOpts.participant = {
1027
- jid: participant,
1028
- count: +retryNode.attrs.count!
1029
- }
1030
- }
1031
-
1032
- await relayMessage(key.remoteJid!, msg, msgRelayOpts)
1033
- } else {
1034
- logger.debug({ jid: key.remoteJid, id: ids[i] }, 'recv retry request, but message not available')
1035
- }
1036
- }
1037
- }
1038
-
1039
- const handleReceipt = async (node: BinaryNode) => {
1040
- const { attrs, content } = node
1041
- const isLid = attrs.from!.includes('lid')
1042
- const isNodeFromMe = areJidsSameUser(
1043
- attrs.participant || attrs.from,
1044
- isLid ? authState.creds.me?.lid : authState.creds.me?.id
1045
- )
1046
- const remoteJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient
1047
- const fromMe = !attrs.recipient || ((attrs.type === 'retry' || attrs.type === 'sender') && isNodeFromMe)
1048
-
1049
- const key: proto.IMessageKey = {
1050
- remoteJid,
1051
- id: '',
1052
- fromMe,
1053
- participant: attrs.participant
1054
- }
1055
-
1056
- if (shouldIgnoreJid(remoteJid!) && remoteJid !== S_WHATSAPP_NET) {
1057
- logger.debug({ remoteJid }, 'ignoring receipt from jid')
1058
- await sendMessageAck(node)
1059
- return
1060
- }
1061
-
1062
- const ids = [attrs.id!]
1063
- if (Array.isArray(content)) {
1064
- const items = getBinaryNodeChildren(content[0], 'item')
1065
- ids.push(...items.map(i => i.attrs.id!))
1066
- }
1067
-
1068
- try {
1069
- await Promise.all([
1070
- processingMutex.mutex(async () => {
1071
- const status = getStatusFromReceiptType(attrs.type)
1072
- if (
1073
- typeof status !== 'undefined' &&
1074
- // basically, we only want to know when a message from us has been delivered to/read by the other person
1075
- // or another device of ours has read some messages
1076
- (status >= proto.WebMessageInfo.Status.SERVER_ACK || !isNodeFromMe)
1077
- ) {
1078
- if (isJidGroup(remoteJid) || isJidStatusBroadcast(remoteJid!)) {
1079
- if (attrs.participant) {
1080
- const updateKey: keyof MessageUserReceipt =
1081
- status === proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp'
1082
- ev.emit(
1083
- 'message-receipt.update',
1084
- ids.map(id => ({
1085
- key: { ...key, id },
1086
- receipt: {
1087
- userJid: jidNormalizedUser(attrs.participant),
1088
- [updateKey]: +attrs.t!
1089
- }
1090
- }))
1091
- )
1092
- }
1093
- } else {
1094
- ev.emit(
1095
- 'messages.update',
1096
- ids.map(id => ({
1097
- key: { ...key, id },
1098
- update: { status }
1099
- }))
1100
- )
1101
- }
1102
- }
1103
-
1104
- if (attrs.type === 'retry') {
1105
- // correctly set who is asking for the retry
1106
- key.participant = key.participant || attrs.from
1107
- const retryNode = getBinaryNodeChild(node, 'retry')
1108
- if (ids[0] && key.participant && (await willSendMessageAgain(ids[0], key.participant))) {
1109
- if (key.fromMe) {
1110
- try {
1111
- await updateSendMessageAgainCount(ids[0], key.participant)
1112
- logger.debug({ attrs, key }, 'recv retry request')
1113
- await sendMessagesAgain(key, ids, retryNode!)
1114
- } catch (error: unknown) {
1115
- logger.error(
1116
- { key, ids, trace: error instanceof Error ? error.stack : 'Unknown error' },
1117
- 'error in sending message again'
1118
- )
1119
- }
1120
- } else {
1121
- logger.info({ attrs, key }, 'recv retry for not fromMe message')
1122
- }
1123
- } else {
1124
- logger.info({ attrs, key }, 'will not send message again, as sent too many times')
1125
- }
1126
- }
1127
- })
1128
- ])
1129
- } finally {
1130
- await sendMessageAck(node)
1131
- }
1132
- }
1133
-
1134
- const handleNotification = async (node: BinaryNode) => {
1135
- const remoteJid = node.attrs.from
1136
- if (shouldIgnoreJid(remoteJid!) && remoteJid !== S_WHATSAPP_NET) {
1137
- logger.debug({ remoteJid, id: node.attrs.id }, 'ignored notification')
1138
- await sendMessageAck(node)
1139
- return
1140
- }
1141
-
1142
- try {
1143
- await Promise.all([
1144
- processingMutex.mutex(async () => {
1145
- const msg = await processNotification(node)
1146
- if (msg) {
1147
- const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me!.id)
1148
- const { senderAlt: participantAlt, addressingMode } = extractAddressingContext(node)
1149
- msg.key = {
1150
- remoteJid,
1151
- fromMe,
1152
- participant: node.attrs.participant,
1153
- participantAlt,
1154
- addressingMode,
1155
- id: node.attrs.id,
1156
- ...(msg.key || {})
1157
- }
1158
- msg.participant ??= node.attrs.participant
1159
- msg.messageTimestamp = +node.attrs.t!
1160
-
1161
- const fullMsg = proto.WebMessageInfo.fromObject(msg) as WAMessage
1162
- await upsertMessage(fullMsg, 'append')
1163
- }
1164
- })
1165
- ])
1166
- } finally {
1167
- await sendMessageAck(node)
1168
- }
1169
- }
1170
-
1171
- const handleMessage = async (node: BinaryNode) => {
1172
- if (shouldIgnoreJid(node.attrs.from!) && node.attrs.from !== S_WHATSAPP_NET) {
1173
- logger.debug({ key: node.attrs.key }, 'ignored message')
1174
- await sendMessageAck(node, NACK_REASONS.UnhandledError)
1175
- return
1176
- }
1177
-
1178
- const encNode = getBinaryNodeChild(node, 'enc')
1179
- // TODO: temporary fix for crashes and issues resulting of failed msmsg decryption
1180
- if (encNode && encNode.attrs.type === 'msmsg') {
1181
- logger.debug({ key: node.attrs.key }, 'ignored msmsg')
1182
- await sendMessageAck(node, NACK_REASONS.MissingMessageSecret)
1183
- return
1184
- }
1185
-
1186
- const {
1187
- fullMessage: msg,
1188
- category,
1189
- author,
1190
- decrypt
1191
- } = decryptMessageNode(node, authState.creds.me!.id, authState.creds.me!.lid || '', signalRepository, logger)
1192
-
1193
- const alt = msg.key.participantAlt || msg.key.remoteJidAlt
1194
- // store new mappings we didn't have before
1195
- if (!!alt) {
1196
- const altServer = jidDecode(alt)?.server
1197
- const primaryJid = msg.key.participant || msg.key.remoteJid!
1198
- if (altServer === 'lid') {
1199
- if (!(await signalRepository.lidMapping.getPNForLID(alt))) {
1200
- await signalRepository.lidMapping.storeLIDPNMappings([{ lid: alt, pn: primaryJid }])
1201
- await signalRepository.migrateSession(primaryJid, alt)
1202
- }
1203
- } else {
1204
- await signalRepository.lidMapping.storeLIDPNMappings([{ lid: primaryJid, pn: alt }])
1205
- await signalRepository.migrateSession(alt, primaryJid)
1206
- }
1207
- }
1208
-
1209
- if (msg.key?.remoteJid && msg.key?.id && messageRetryManager) {
1210
- messageRetryManager.addRecentMessage(msg.key.remoteJid, msg.key.id, msg.message!)
1211
- logger.debug(
1212
- {
1213
- jid: msg.key.remoteJid,
1214
- id: msg.key.id
1215
- },
1216
- 'Added message to recent cache for retry receipts'
1217
- )
1218
- }
1219
-
1220
- try {
1221
- await processingMutex.mutex(async () => {
1222
- await decrypt()
1223
- // message failed to decrypt
1224
- if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT && msg.category !== 'peer') {
1225
- if (
1226
- msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT ||
1227
- msg.messageStubParameters?.[0] === NO_MESSAGE_FOUND_ERROR_TEXT
1228
- ) {
1229
- return sendMessageAck(node)
1230
- }
1231
-
1232
- const errorMessage = msg?.messageStubParameters?.[0] || ''
1233
- const isPreKeyError = errorMessage.includes('PreKey')
1234
-
1235
- logger.debug(`[handleMessage] Attempting retry request for failed decryption`)
1236
-
1237
- // Handle both pre-key and normal retries in single mutex
1238
- await retryMutex.mutex(async () => {
1239
- try {
1240
- if (!ws.isOpen) {
1241
- logger.debug({ node }, 'Connection closed, skipping retry')
1242
- return
1243
- }
1244
-
1245
- // Handle pre-key errors with upload and delay
1246
- if (isPreKeyError) {
1247
- logger.info({ error: errorMessage }, 'PreKey error detected, uploading and retrying')
1248
-
1249
- try {
1250
- logger.debug('Uploading pre-keys for error recovery')
1251
- await uploadPreKeys(5)
1252
- logger.debug('Waiting for server to process new pre-keys')
1253
- await delay(1000)
1254
- } catch (uploadErr) {
1255
- logger.error({ uploadErr }, 'Pre-key upload failed, proceeding with retry anyway')
1256
- }
1257
- }
1258
-
1259
- const encNode = getBinaryNodeChild(node, 'enc')
1260
- await sendRetryRequest(node, !encNode)
1261
- if (retryRequestDelayMs) {
1262
- await delay(retryRequestDelayMs)
1263
- }
1264
- } catch (err) {
1265
- logger.error({ err, isPreKeyError }, 'Failed to handle retry, attempting basic retry')
1266
- // Still attempt retry even if pre-key upload failed
1267
- try {
1268
- const encNode = getBinaryNodeChild(node, 'enc')
1269
- await sendRetryRequest(node, !encNode)
1270
- } catch (retryErr) {
1271
- logger.error({ retryErr }, 'Failed to send retry after error handling')
1272
- }
1273
- }
1274
-
1275
- await sendMessageAck(node, NACK_REASONS.UnhandledError)
1276
- })
1277
- } else {
1278
- const isNewsletter = isJidNewsletter(msg.key.remoteJid!)
1279
- if (!isNewsletter) {
1280
- // no type in the receipt => message delivered
1281
- let type: MessageReceiptType = undefined
1282
- let participant = msg.key.participant
1283
- if (category === 'peer') {
1284
- // special peer message
1285
- type = 'peer_msg'
1286
- } else if (msg.key.fromMe) {
1287
- // message was sent by us from a different device
1288
- type = 'sender'
1289
- // need to specially handle this case
1290
- if (isLidUser(msg.key.remoteJid!) || isLidUser(msg.key.remoteJidAlt)) {
1291
- participant = author // TODO: investigate sending receipts to LIDs and not PNs
1292
- }
1293
- } else if (!sendActiveReceipts) {
1294
- type = 'inactive'
1295
- }
1296
-
1297
- await sendReceipt(msg.key.remoteJid!, participant!, [msg.key.id!], type)
1298
-
1299
- // send ack for history message
1300
- const isAnyHistoryMsg = getHistoryMsg(msg.message!)
1301
- if (isAnyHistoryMsg) {
1302
- const jid = jidNormalizedUser(msg.key.remoteJid!)
1303
- await sendReceipt(jid, undefined, [msg.key.id!], 'hist_sync') // TODO: investigate
1304
- }
1305
- } else {
1306
- await sendMessageAck(node)
1307
- logger.debug({ key: msg.key }, 'processed newsletter message without receipts')
1308
- }
1309
- }
1310
-
1311
- cleanMessage(msg, authState.creds.me!.id, authState.creds.me!.lid!)
1312
-
1313
- await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify')
1314
- })
1315
- } catch (error) {
1316
- logger.error({ error, node: binaryNodeToString(node) }, 'error in handling message')
1317
- }
1318
- }
1319
-
1320
- const handleCall = async (node: BinaryNode) => {
1321
- const { attrs } = node
1322
- const [infoChild] = getAllBinaryNodeChildren(node)
1323
- const status = getCallStatusFromNode(infoChild!)
1324
-
1325
- if (!infoChild) {
1326
- throw new Boom('Missing call info in call node')
1327
- }
1328
-
1329
- const callId = infoChild.attrs['call-id']!
1330
- const from = infoChild.attrs.from! || infoChild.attrs['call-creator']!
1331
-
1332
- const call: WACallEvent = {
1333
- chatId: attrs.from!,
1334
- from,
1335
- id: callId,
1336
- date: new Date(+attrs.t! * 1000),
1337
- offline: !!attrs.offline,
1338
- status
1339
- }
1340
-
1341
- if (status === 'offer') {
1342
- call.isVideo = !!getBinaryNodeChild(infoChild, 'video')
1343
- call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid']
1344
- call.groupJid = infoChild.attrs['group-jid']
1345
- await callOfferCache.set(call.id, call)
1346
- }
1347
-
1348
- const existingCall = await callOfferCache.get<WACallEvent>(call.id)
1349
-
1350
- // use existing call info to populate this event
1351
- if (existingCall) {
1352
- call.isVideo = existingCall.isVideo
1353
- call.isGroup = existingCall.isGroup
1354
- }
1355
-
1356
- // delete data once call has ended
1357
- if (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') {
1358
- await callOfferCache.del(call.id)
1359
- }
1360
-
1361
- ev.emit('call', [call])
1362
-
1363
- await sendMessageAck(node)
1364
- }
1365
-
1366
- const handleBadAck = async ({ attrs }: BinaryNode) => {
1367
- const key: WAMessageKey = { remoteJid: attrs.from, fromMe: true, id: attrs.id }
1368
-
1369
- // WARNING: REFRAIN FROM ENABLING THIS FOR NOW. IT WILL CAUSE A LOOP
1370
- // // current hypothesis is that if pash is sent in the ack
1371
- // // it means -- the message hasn't reached all devices yet
1372
- // // we'll retry sending the message here
1373
- // if(attrs.phash) {
1374
- // logger.info({ attrs }, 'received phash in ack, resending message...')
1375
- // const msg = await getMessage(key)
1376
- // if(msg) {
1377
- // await relayMessage(key.remoteJid!, msg, { messageId: key.id!, useUserDevicesCache: false })
1378
- // } else {
1379
- // logger.warn({ attrs }, 'could not send message again, as it was not found')
1380
- // }
1381
- // }
1382
-
1383
- // error in acknowledgement,
1384
- // device could not display the message
1385
- if (attrs.error) {
1386
- logger.warn({ attrs }, 'received error in ack')
1387
- ev.emit('messages.update', [
1388
- {
1389
- key,
1390
- update: {
1391
- status: WAMessageStatus.ERROR,
1392
- messageStubParameters: [attrs.error]
1393
- }
1394
- }
1395
- ])
1396
-
1397
- // resend the message with device_fanout=false, use at your own risk
1398
- // if (attrs.error === '475') {
1399
- // const msg = await getMessage(key)
1400
- // if (msg) {
1401
- // await relayMessage(key.remoteJid!, msg, {
1402
- // messageId: key.id!,
1403
- // useUserDevicesCache: false,
1404
- // additionalAttributes: {
1405
- // device_fanout: 'false'
1406
- // }
1407
- // })
1408
- // }
1409
- // }
1410
- }
1411
- }
1412
-
1413
- /// processes a node with the given function
1414
- /// and adds the task to the existing buffer if we're buffering events
1415
- const processNodeWithBuffer = async <T>(
1416
- node: BinaryNode,
1417
- identifier: string,
1418
- exec: (node: BinaryNode, offline: boolean) => Promise<T>
1419
- ) => {
1420
- ev.buffer()
1421
- await execTask()
1422
- ev.flush()
1423
-
1424
- function execTask() {
1425
- return exec(node, false).catch(err => onUnexpectedError(err, identifier))
1426
- }
1427
- }
1428
-
1429
- type MessageType = 'message' | 'call' | 'receipt' | 'notification'
1430
-
1431
- type OfflineNode = {
1432
- type: MessageType
1433
- node: BinaryNode
1434
- }
1435
-
1436
- const makeOfflineNodeProcessor = () => {
1437
- const nodeProcessorMap: Map<MessageType, (node: BinaryNode) => Promise<void>> = new Map([
1438
- ['message', handleMessage],
1439
- ['call', handleCall],
1440
- ['receipt', handleReceipt],
1441
- ['notification', handleNotification]
1442
- ])
1443
- const nodes: OfflineNode[] = []
1444
- let isProcessing = false
1445
-
1446
- const enqueue = (type: MessageType, node: BinaryNode) => {
1447
- nodes.push({ type, node })
1448
-
1449
- if (isProcessing) {
1450
- return
1451
- }
1452
-
1453
- isProcessing = true
1454
-
1455
- const promise = async () => {
1456
- while (nodes.length && ws.isOpen) {
1457
- const { type, node } = nodes.shift()!
1458
-
1459
- const nodeProcessor = nodeProcessorMap.get(type)
1460
-
1461
- if (!nodeProcessor) {
1462
- onUnexpectedError(new Error(`unknown offline node type: ${type}`), 'processing offline node')
1463
- continue
1464
- }
1465
-
1466
- await nodeProcessor(node)
1467
- }
1468
-
1469
- isProcessing = false
1470
- }
1471
-
1472
- promise().catch(error => onUnexpectedError(error, 'processing offline nodes'))
1473
- }
1474
-
1475
- return { enqueue }
1476
- }
1477
-
1478
- const offlineNodeProcessor = makeOfflineNodeProcessor()
1479
-
1480
- const processNode = async (
1481
- type: MessageType,
1482
- node: BinaryNode,
1483
- identifier: string,
1484
- exec: (node: BinaryNode) => Promise<void>
1485
- ) => {
1486
- const isOffline = !!node.attrs.offline
1487
-
1488
- if (isOffline) {
1489
- offlineNodeProcessor.enqueue(type, node)
1490
- } else {
1491
- await processNodeWithBuffer(node, identifier, exec)
1492
- }
1493
- }
1494
-
1495
- // recv a message
1496
- ws.on('CB:message', async (node: BinaryNode) => {
1497
- await processNode('message', node, 'processing message', handleMessage)
1498
- })
1499
-
1500
- ws.on('CB:call', async (node: BinaryNode) => {
1501
- await processNode('call', node, 'handling call', handleCall)
1502
- })
1503
-
1504
- ws.on('CB:receipt', async node => {
1505
- await processNode('receipt', node, 'handling receipt', handleReceipt)
1506
- })
1507
-
1508
- ws.on('CB:notification', async (node: BinaryNode) => {
1509
- await processNode('notification', node, 'handling notification', handleNotification)
1510
- })
1511
- ws.on('CB:ack,class:message', (node: BinaryNode) => {
1512
- handleBadAck(node).catch(error => onUnexpectedError(error, 'handling bad ack'))
1513
- })
1514
-
1515
- ev.on('call', async ([call]) => {
1516
- if (!call) {
1517
- return
1518
- }
1519
-
1520
- // missed call + group call notification message generation
1521
- if (call.status === 'timeout' || (call.status === 'offer' && call.isGroup)) {
1522
- const msg: WAMessage = {
1523
- key: {
1524
- remoteJid: call.chatId,
1525
- id: call.id,
1526
- fromMe: false
1527
- },
1528
- messageTimestamp: unixTimestampSeconds(call.date)
1529
- }
1530
- if (call.status === 'timeout') {
1531
- if (call.isGroup) {
1532
- msg.messageStubType = call.isVideo
1533
- ? WAMessageStubType.CALL_MISSED_GROUP_VIDEO
1534
- : WAMessageStubType.CALL_MISSED_GROUP_VOICE
1535
- } else {
1536
- msg.messageStubType = call.isVideo ? WAMessageStubType.CALL_MISSED_VIDEO : WAMessageStubType.CALL_MISSED_VOICE
1537
- }
1538
- } else {
1539
- msg.message = { call: { callKey: Buffer.from(call.id) } }
1540
- }
1541
-
1542
- const protoMsg = proto.WebMessageInfo.fromObject(msg) as WAMessage
1543
- await upsertMessage(protoMsg, call.offline ? 'append' : 'notify')
1544
- }
1545
- })
1546
-
1547
- ev.on('connection.update', ({ isOnline }) => {
1548
- if (typeof isOnline !== 'undefined') {
1549
- sendActiveReceipts = isOnline
1550
- logger.trace(`sendActiveReceipts set to "${sendActiveReceipts}"`)
1551
- }
1552
- })
1553
-
1554
- return {
1555
- ...sock,
1556
- sendMessageAck,
1557
- sendRetryRequest,
1558
- rejectCall,
1559
- fetchMessageHistory,
1560
- requestPlaceholderResend,
1561
- messageRetryManager
1562
- }
1563
- }