@nexustechpro/baileys 2.0.2 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +924 -1299
  3. package/WAProto/index.js +22 -18
  4. package/lib/Defaults/baileys-version.json +6 -2
  5. package/lib/Defaults/index.js +173 -172
  6. package/lib/Signal/libsignal.js +395 -292
  7. package/lib/Signal/lid-mapping.js +264 -171
  8. package/lib/Socket/Client/index.js +2 -2
  9. package/lib/Socket/Client/types.js +10 -10
  10. package/lib/Socket/Client/websocket.js +45 -310
  11. package/lib/Socket/business.js +375 -375
  12. package/lib/Socket/chats.js +916 -963
  13. package/lib/Socket/communities.js +430 -430
  14. package/lib/Socket/groups.js +342 -342
  15. package/lib/Socket/index.js +21 -22
  16. package/lib/Socket/messages-recv.js +963 -743
  17. package/lib/Socket/messages-send.js +273 -321
  18. package/lib/Socket/mex.js +50 -50
  19. package/lib/Socket/newsletter.js +148 -148
  20. package/lib/Socket/nexus-handler.js +296 -247
  21. package/lib/Socket/registration.js +50 -33
  22. package/lib/Socket/socket.js +872 -1201
  23. package/lib/Store/index.js +5 -5
  24. package/lib/Store/make-cache-manager-store.js +81 -81
  25. package/lib/Store/make-in-memory-store.js +416 -416
  26. package/lib/Store/make-ordered-dictionary.js +81 -81
  27. package/lib/Store/object-repository.js +30 -30
  28. package/lib/Types/Auth.js +1 -1
  29. package/lib/Types/Bussines.js +1 -1
  30. package/lib/Types/Call.js +1 -1
  31. package/lib/Types/Chat.js +7 -7
  32. package/lib/Types/Contact.js +1 -1
  33. package/lib/Types/Events.js +1 -1
  34. package/lib/Types/GroupMetadata.js +1 -1
  35. package/lib/Types/Label.js +24 -24
  36. package/lib/Types/LabelAssociation.js +6 -6
  37. package/lib/Types/Message.js +10 -10
  38. package/lib/Types/Newsletter.js +37 -29
  39. package/lib/Types/Product.js +1 -1
  40. package/lib/Types/Signal.js +1 -1
  41. package/lib/Types/Socket.js +2 -2
  42. package/lib/Types/State.js +55 -12
  43. package/lib/Types/USync.js +1 -1
  44. package/lib/Types/index.js +25 -25
  45. package/lib/Utils/auth-utils.js +264 -256
  46. package/lib/Utils/baileys-event-stream.js +55 -55
  47. package/lib/Utils/browser-utils.js +27 -27
  48. package/lib/Utils/business.js +228 -230
  49. package/lib/Utils/chat-utils.js +726 -764
  50. package/lib/Utils/companion-reg-client-utils.js +34 -0
  51. package/lib/Utils/crypto.js +109 -135
  52. package/lib/Utils/decode-wa-message.js +342 -314
  53. package/lib/Utils/event-buffer.js +547 -547
  54. package/lib/Utils/generics.js +295 -297
  55. package/lib/Utils/history.js +91 -83
  56. package/lib/Utils/index.js +25 -20
  57. package/lib/Utils/key-store.js +17 -0
  58. package/lib/Utils/link-preview.js +107 -98
  59. package/lib/Utils/logger.js +2 -2
  60. package/lib/Utils/lt-hash.js +47 -47
  61. package/lib/Utils/make-mutex.js +39 -39
  62. package/lib/Utils/message-retry-manager.js +148 -148
  63. package/lib/Utils/messages-media.js +579 -535
  64. package/lib/Utils/messages.js +821 -706
  65. package/lib/Utils/noise-handler.js +255 -255
  66. package/lib/Utils/pre-key-manager.js +105 -105
  67. package/lib/Utils/process-message.js +430 -412
  68. package/lib/Utils/reporting-utils.js +155 -0
  69. package/lib/Utils/signal.js +191 -159
  70. package/lib/Utils/sync-action-utils.js +33 -0
  71. package/lib/Utils/tc-token-utils.js +162 -0
  72. package/lib/Utils/use-multi-file-auth-state.js +120 -120
  73. package/lib/Utils/validate-connection.js +194 -194
  74. package/lib/WABinary/constants.js +1306 -1300
  75. package/lib/WABinary/decode.js +237 -237
  76. package/lib/WABinary/encode.js +232 -232
  77. package/lib/WABinary/generic-utils.js +252 -211
  78. package/lib/WABinary/index.js +6 -5
  79. package/lib/WABinary/jid-utils.js +279 -95
  80. package/lib/WABinary/types.js +1 -1
  81. package/lib/WAM/BinaryInfo.js +9 -9
  82. package/lib/WAM/constants.js +22852 -22852
  83. package/lib/WAM/encode.js +149 -149
  84. package/lib/WAM/index.js +3 -3
  85. package/lib/WAUSync/Protocols/USyncContactProtocol.js +28 -28
  86. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +53 -53
  87. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +26 -26
  88. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +37 -37
  89. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +50 -50
  90. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +28 -28
  91. package/lib/WAUSync/Protocols/index.js +4 -4
  92. package/lib/WAUSync/USyncQuery.js +93 -93
  93. package/lib/WAUSync/USyncUser.js +22 -22
  94. package/lib/WAUSync/index.js +3 -3
  95. package/lib/index.js +65 -66
  96. package/package.json +172 -143
  97. package/lib/Signal/Group/ciphertext-message.js +0 -12
  98. package/lib/Signal/Group/group-session-builder.js +0 -30
  99. package/lib/Signal/Group/group_cipher.js +0 -100
  100. package/lib/Signal/Group/index.js +0 -12
  101. package/lib/Signal/Group/keyhelper.js +0 -18
  102. package/lib/Signal/Group/sender-chain-key.js +0 -26
  103. package/lib/Signal/Group/sender-key-distribution-message.js +0 -63
  104. package/lib/Signal/Group/sender-key-message.js +0 -66
  105. package/lib/Signal/Group/sender-key-name.js +0 -48
  106. package/lib/Signal/Group/sender-key-record.js +0 -41
  107. package/lib/Signal/Group/sender-key-state.js +0 -84
  108. package/lib/Signal/Group/sender-message-key.js +0 -26
@@ -1,314 +1,342 @@
1
- import { Boom } from "@hapi/boom"
2
- import { proto } from "../../WAProto/index.js"
3
- import {
4
- areJidsSameUser,
5
- isHostedLidUser,
6
- isHostedPnUser,
7
- isJidBroadcast,
8
- isJidGroup,
9
- isJidMetaAI,
10
- isJidNewsletter,
11
- isJidStatusBroadcast,
12
- isLidUser,
13
- isPnUser,
14
- // transferDevice
15
- } from "../WABinary/index.js"
16
- import { unpadRandomMax16 } from "./generics.js"
17
- export const getDecryptionJid = async (sender, repository, logger) => {
18
- // Skip LID mapping for newsletters, status broadcasts, and meta AI - they don't use it
19
- if (isJidNewsletter(sender) || isJidStatusBroadcast(sender) || isJidMetaAI(sender)) {
20
- return sender
21
- }
22
-
23
- if (isLidUser(sender) || isHostedLidUser(sender)) {
24
- return sender
25
- }
26
- const mapped = await repository.lidMapping.getLIDForPN(sender)
27
- //AUTO-RECOVERY: If mapping is missing, try to fetch it
28
- if (!mapped) {
29
- logger.warn({ sender }, "Lid mapping missing, attempting recovery")
30
- mapped = await repository.lidMapping.recoverMissingMapping(sender, logger)
31
- }
32
- return mapped || sender
33
- }
34
- const storeMappingFromEnvelope = async (stanza, sender, repository, decryptionJid, logger) => {
35
- // TODO: Handle hosted IDs
36
- const { senderAlt } = extractAddressingContext(stanza)
37
- if (senderAlt && isLidUser(senderAlt) && isPnUser(sender) && decryptionJid === sender) {
38
- try {
39
- await repository.lidMapping.storeLIDPNMappings([{ lid: senderAlt, pn: sender }])
40
- await repository.migrateSession(sender, senderAlt)
41
- logger.debug({ sender, senderAlt }, "Stored LID mapping from envelope")
42
- } catch (error) {
43
- logger.warn({ sender, senderAlt, error }, "Failed to store LID mapping")
44
- }
45
- }
46
- }
47
- export const NO_MESSAGE_FOUND_ERROR_TEXT = "Message absent from node"
48
- export const MISSING_KEYS_ERROR_TEXT = "Key used already or never filled"
49
- // Retry configuration for failed decryption
50
- export const DECRYPTION_RETRY_CONFIG = {
51
- maxRetries: 3,
52
- baseDelayMs: 100,
53
- sessionRecordErrors: ["No session record", "SessionError: No session record"],
54
- }
55
- export const NACK_REASONS = {
56
- ParsingError: 487,
57
- UnrecognizedStanza: 488,
58
- UnrecognizedStanzaClass: 489,
59
- UnrecognizedStanzaType: 490,
60
- InvalidProtobuf: 491,
61
- InvalidHostedCompanionStanza: 493,
62
- MissingMessageSecret: 495,
63
- SignalErrorOldCounter: 496,
64
- MessageDeletedOnPeer: 499,
65
- UnhandledError: 500,
66
- UnsupportedAdminRevoke: 550,
67
- UnsupportedLIDGroup: 551,
68
- DBOperationFailed: 552,
69
- }
70
- export const extractAddressingContext = (stanza) => {
71
- let senderAlt
72
- let recipientAlt
73
- const sender = stanza.attrs.participant || stanza.attrs.from
74
- const addressingMode = stanza.attrs.addressing_mode || (sender?.endsWith("lid") ? "lid" : "pn")
75
- if (addressingMode === "lid") {
76
- // Message is LID-addressed: sender is LID, extract corresponding PN
77
- // without device data
78
- senderAlt = stanza.attrs.participant_pn || stanza.attrs.sender_pn || stanza.attrs.peer_recipient_pn
79
- recipientAlt = stanza.attrs.recipient_pn
80
- // with device data
81
- //if (sender && senderAlt) senderAlt = transferDevice(sender, senderAlt)
82
- } else {
83
- // Message is PN-addressed: sender is PN, extract corresponding LID
84
- // without device data
85
- senderAlt = stanza.attrs.participant_lid || stanza.attrs.sender_lid || stanza.attrs.peer_recipient_lid
86
- recipientAlt = stanza.attrs.recipient_lid
87
- //with device data
88
- //if (sender && senderAlt) senderAlt = transferDevice(sender, senderAlt)
89
- }
90
- return {
91
- addressingMode,
92
- senderAlt,
93
- recipientAlt,
94
- }
95
- }
96
- /**
97
- * Decode the received node as a message.
98
- * @note this will only parse the message, not decrypt it
99
- */
100
- export function decodeMessageNode(stanza, meId, meLid) {
101
- let msgType
102
- let chatId
103
- let author
104
- let fromMe = false
105
- const msgId = stanza.attrs.id
106
- const from = stanza.attrs.from
107
- const participant = stanza.attrs.participant
108
- const recipient = stanza.attrs.recipient
109
- const addressingContext = extractAddressingContext(stanza)
110
- const isMe = (jid) => areJidsSameUser(jid, meId)
111
- const isMeLid = (jid) => areJidsSameUser(jid, meLid)
112
- if (isPnUser(from) || isLidUser(from) || isHostedLidUser(from) || isHostedPnUser(from)) {
113
- if (recipient && !isJidMetaAI(recipient)) {
114
- if (!isMe(from) && !isMeLid(from)) {
115
- throw new Boom("receipient present, but msg not from me", { data: stanza })
116
- }
117
- if (isMe(from) || isMeLid(from)) {
118
- fromMe = true
119
- }
120
- chatId = recipient
121
- } else {
122
- chatId = from
123
- }
124
- msgType = "chat"
125
- author = from
126
- } else if (isJidGroup(from)) {
127
- if (!participant) {
128
- throw new Boom("No participant in group message")
129
- }
130
- if (isMe(participant) || isMeLid(participant)) {
131
- fromMe = true
132
- }
133
- msgType = "group"
134
- author = participant
135
- chatId = from
136
- } else if (isJidBroadcast(from)) {
137
- if (!participant) {
138
- throw new Boom("No participant in group message")
139
- }
140
- const isParticipantMe = isMe(participant)
141
- if (isJidStatusBroadcast(from)) {
142
- msgType = isParticipantMe ? "direct_peer_status" : "other_status"
143
- } else {
144
- msgType = isParticipantMe ? "peer_broadcast" : "other_broadcast"
145
- }
146
- fromMe = isParticipantMe
147
- chatId = from
148
- author = participant
149
- } else if (isJidNewsletter(from)) {
150
- msgType = "newsletter"
151
- chatId = from
152
- author = from
153
- if (isMe(from) || isMeLid(from)) {
154
- fromMe = true
155
- }
156
- } else {
157
- throw new Boom("Unknown message type", { data: stanza })
158
- }
159
- const pushname = stanza?.attrs?.notify
160
- const key = {
161
- remoteJid: chatId,
162
- remoteJidAlt: !isJidGroup(chatId) ? addressingContext.senderAlt : undefined,
163
- fromMe,
164
- id: msgId,
165
- participant,
166
- participantAlt: isJidGroup(chatId) ? addressingContext.senderAlt : undefined,
167
- addressingMode: addressingContext.addressingMode,
168
- ...(msgType === "newsletter" && stanza.attrs.server_id ? { server_id: stanza.attrs.server_id } : {}),
169
- }
170
- const fullMessage = {
171
- key,
172
- messageTimestamp: +stanza.attrs.t,
173
- pushName: pushname,
174
- broadcast: isJidBroadcast(from),
175
- }
176
- if (key.fromMe) {
177
- fullMessage.status = proto.WebMessageInfo.Status.SERVER_ACK
178
- }
179
- return {
180
- fullMessage,
181
- author,
182
- sender: msgType === "chat" ? author : chatId,
183
- }
184
- }
185
- export const decryptMessageNode = (stanza, meId, meLid, repository, logger) => {
186
- const { fullMessage, author, sender } = decodeMessageNode(stanza, meId, meLid)
187
- return {
188
- fullMessage,
189
- category: stanza.attrs.category,
190
- author,
191
- async decrypt() {
192
- let decryptables = 0
193
- if (Array.isArray(stanza.content)) {
194
- for (const { tag, attrs, content } of stanza.content) {
195
- if (tag === "verified_name" && content instanceof Uint8Array) {
196
- const cert = proto.VerifiedNameCertificate.decode(content)
197
- const details = proto.VerifiedNameCertificate.Details.decode(cert.details)
198
- fullMessage.verifiedBizName = details.verifiedName
199
- }
200
- if (tag === "unavailable" && attrs.type === "view_once") {
201
- fullMessage.key.isViewOnce = true // TODO: remove from here and add a STUB TYPE
202
- }
203
- if (tag !== "enc" && tag !== "plaintext") {
204
- continue
205
- }
206
- if (!(content instanceof Uint8Array)) {
207
- continue
208
- }
209
- decryptables += 1
210
- let msgBuffer
211
- const decryptionJid = await getDecryptionJid(author, repository, logger)
212
- let decryptionAltJid = null
213
- const { senderAlt } = extractAddressingContext(stanza)
214
- if (senderAlt) {
215
- decryptionAltJid = await getDecryptionJid(senderAlt, repository, logger)
216
- }
217
- if (tag !== "plaintext") {
218
- // TODO: Handle hosted devices
219
- await storeMappingFromEnvelope(stanza, author, repository, decryptionJid, logger)
220
- }
221
- try {
222
- const e2eType = tag === "plaintext" ? "plaintext" : attrs.type
223
- switch (e2eType) {
224
- case "skmsg":
225
- msgBuffer = await repository.decryptGroupMessage({
226
- group: sender,
227
- authorJid: author,
228
- msg: content,
229
- })
230
- break
231
- case "pkmsg":
232
- case "msg":
233
- try {
234
- msgBuffer = await repository.decryptMessage({
235
- jid: decryptionJid,
236
- alternateJid: decryptionAltJid,
237
- type: e2eType,
238
- ciphertext: content,
239
- })
240
- } catch (decryptErr) {
241
- const cleanError = new Error(decryptErr?.message || decryptErr?.toString() || "Decryption failed")
242
- cleanError.stack = decryptErr?.stack
243
- throw cleanError
244
- }
245
- break
246
- case "plaintext":
247
- msgBuffer = content
248
- break
249
- default:
250
- throw new Error(`Unknown e2e type: ${e2eType}`)
251
- }
252
- let msg = proto.Message.decode(e2eType !== "plaintext" ? unpadRandomMax16(msgBuffer) : msgBuffer)
253
- msg = msg.deviceSentMessage?.message || msg
254
- if (msg.senderKeyDistributionMessage) {
255
- //eslint-disable-next-line max-depth
256
- try {
257
- await repository.processSenderKeyDistributionMessage({
258
- authorJid: author,
259
- item: msg.senderKeyDistributionMessage,
260
- })
261
- } catch (err) {
262
- logger.error({ key: fullMessage.key, err }, "failed to process sender key distribution message")
263
- }
264
- }
265
- if (fullMessage.message) {
266
- Object.assign(fullMessage.message, msg)
267
- } else {
268
- fullMessage.message = msg
269
- }
270
- } catch (err) {
271
- const errorMessage = err?.message || err?.toString() || ""
272
- const isBadMac = errorMessage.includes("Bad MAC")
273
- const isMessageCounter = errorMessage.includes("Key used already or never filled")
274
-
275
- const errorContext = {
276
- key: fullMessage.key,
277
- err,
278
- errorMessage,
279
- messageType: tag === "plaintext" ? "plaintext" : attrs.type,
280
- sender,
281
- author,
282
- isSessionRecordError: isSessionRecordError(err),
283
- isBadMac,
284
- isMessageCounter,
285
- }
286
-
287
- if (isBadMac || isMessageCounter) {
288
- errorContext.isSignalSessionCorrupted = true
289
- errorContext.requiresSessionReset = true
290
- logger.error(
291
- { ...errorContext, jid: decryptionJid },
292
- `Signal session corrupted - ${isBadMac ? 'BAD_MAC' : 'MESSAGE_COUNTER'} error, session reset required`,
293
- )
294
- }
295
-
296
- logger.error(errorContext, "failed to decrypt message")
297
- fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT
298
- fullMessage.messageStubParameters = [errorMessage]
299
- }
300
- }
301
- }
302
- // if nothing was found to decrypt
303
- if (!decryptables) {
304
- fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT
305
- fullMessage.messageStubParameters = [NO_MESSAGE_FOUND_ERROR_TEXT]
306
- }
307
- },
308
- }
309
- }
310
- function isSessionRecordError(error) {
311
- const errorMessage = error?.message || error?.toString() || ""
312
- return DECRYPTION_RETRY_CONFIG.sessionRecordErrors.some((errorPattern) => errorMessage.includes(errorPattern))
313
- }
314
- //# sourceMappingURL=decode-wa-message.js.map
1
+ import { Boom } from "@hapi/boom"
2
+ import { proto } from "../../WAProto/index.js"
3
+ import {
4
+ areJidsSameUser,
5
+ isHostedLidUser,
6
+ isHostedPnUser,
7
+ isJidBroadcast,
8
+ isJidGroup,
9
+ isJidMetaAI,
10
+ isJidNewsletter,
11
+ isJidStatusBroadcast,
12
+ isLidUser,
13
+ isPnUser,
14
+ // transferDevice
15
+ } from "../WABinary/index.js"
16
+ import { unpadRandomMax16 } from "./generics.js"
17
+ export const getDecryptionJid = async (sender, repository, logger) => {
18
+ // Skip LID mapping for newsletters, status broadcasts, and meta AI - they don't use it
19
+ if (isJidNewsletter(sender) || isJidStatusBroadcast(sender) || isJidMetaAI(sender)) {
20
+ return sender
21
+ }
22
+
23
+ if (isLidUser(sender) || isHostedLidUser(sender)) {
24
+ return sender
25
+ }
26
+ const mapped = await repository.lidMapping.getLIDForPN(sender)
27
+ return mapped || sender
28
+ }
29
+ const storeMappingFromEnvelope = async (stanza, sender, repository, decryptionJid, logger, meId, meLid) => {
30
+ const { senderAlt } = extractAddressingContext(stanza)
31
+ if (!senderAlt) return
32
+
33
+ // Case 1: PN-addressed message — sender is PN, senderAlt is LID
34
+ if (isLidUser(senderAlt) && isPnUser(sender) && decryptionJid === sender) {
35
+ if (areJidsSameUser(sender, meId) || areJidsSameUser(senderAlt, meLid)) return // never remap own identity
36
+ try {
37
+ await repository.lidMapping.storeLIDPNMappings([{ lid: senderAlt, pn: sender }])
38
+ repository.migrateSession(sender, senderAlt).catch(err => logger?.warn?.({ sender, senderAlt, err }, 'Failed to migrate session (PN→LID)'))
39
+ logger.debug({ sender, senderAlt }, 'Stored LID mapping from envelope (PN→LID)')
40
+ } catch (error) {
41
+ logger.warn({ sender, senderAlt, error }, 'Failed to store LID mapping (PN→LID)')
42
+ }
43
+ }
44
+ // Case 2: LID-addressed message — sender is LID, senderAlt is PN
45
+ else if (isPnUser(senderAlt) && isLidUser(sender)) {
46
+ if (areJidsSameUser(sender, meLid) || areJidsSameUser(senderAlt, meId)) return // never remap own identity
47
+ try {
48
+ await repository.lidMapping.storeLIDPNMappings([{ lid: sender, pn: senderAlt }])
49
+ repository.migrateSession(senderAlt, sender).catch(err => logger?.warn?.({ sender, senderAlt, err }, 'Failed to migrate session (LID→PN)'))
50
+ logger.debug({ sender, senderAlt }, 'Stored LID mapping from envelope (LID→PN)')
51
+ } catch (error) {
52
+ logger.warn({ sender, senderAlt, error }, 'Failed to store LID mapping (LID→PN)')
53
+ }
54
+ }
55
+ }
56
+ export const NO_MESSAGE_FOUND_ERROR_TEXT = "Message absent from node"
57
+ export const MISSING_KEYS_ERROR_TEXT = "Key used already or never filled"
58
+ export const ACCOUNT_RESTRICTED_TEXT = 'Your account has been restricted';
59
+ // Retry configuration for failed decryption
60
+ export const DECRYPTION_RETRY_CONFIG = {
61
+ maxRetries: 3,
62
+ baseDelayMs: 100,
63
+ sessionRecordErrors: ["No session record", "SessionError: No session record"],
64
+ }
65
+ export const NACK_REASONS = {
66
+ ParsingError: 487,
67
+ UnrecognizedStanza: 488,
68
+ UnrecognizedStanzaClass: 489,
69
+ UnrecognizedStanzaType: 490,
70
+ InvalidProtobuf: 491,
71
+ InvalidHostedCompanionStanza: 493,
72
+ MissingMessageSecret: 495,
73
+ SignalErrorOldCounter: 496,
74
+ MessageDeletedOnPeer: 499,
75
+ UnhandledError: 500,
76
+ UnsupportedAdminRevoke: 550,
77
+ UnsupportedLIDGroup: 551,
78
+ DBOperationFailed: 552,
79
+ }
80
+ export const extractAddressingContext = (stanza) => {
81
+ let senderAlt
82
+ let recipientAlt
83
+ const sender = stanza.attrs.participant || stanza.attrs.from
84
+ const addressingMode = stanza.attrs.addressing_mode || (sender?.endsWith("lid") ? "lid" : "pn")
85
+ if (addressingMode === "lid") {
86
+ // Message is LID-addressed: sender is LID, extract corresponding PN
87
+ // without device data
88
+ senderAlt = stanza.attrs.participant_pn || stanza.attrs.sender_pn || stanza.attrs.peer_recipient_pn
89
+ recipientAlt = stanza.attrs.recipient_pn
90
+ // with device data
91
+ //if (sender && senderAlt) senderAlt = transferDevice(sender, senderAlt)
92
+ } else {
93
+ // Message is PN-addressed: sender is PN, extract corresponding LID
94
+ // without device data
95
+ senderAlt = stanza.attrs.participant_lid || stanza.attrs.sender_lid || stanza.attrs.peer_recipient_lid
96
+ recipientAlt = stanza.attrs.recipient_lid
97
+ //with device data
98
+ //if (sender && senderAlt) senderAlt = transferDevice(sender, senderAlt)
99
+ }
100
+ return {
101
+ addressingMode,
102
+ senderAlt,
103
+ recipientAlt,
104
+ }
105
+ }
106
+ /**
107
+ * Server-side error codes returned in ack stanzas (server → client) that we
108
+ * currently have dedicated handlers for. Extend as more handlers are added.
109
+ * Distinct from the client-side NackReason enum (WAWebCreateNackFromStanza).
110
+ */
111
+ export const SERVER_ERROR_CODES = {
112
+ /**
113
+ * 1:1 message missing privacy token (tctoken). Usually means the account is
114
+ * restricted: WhatsApp blocks starting new chats but preserves existing ones,
115
+ * since established chats already carry a tctoken.
116
+ */
117
+ MessageAccountRestriction: '463',
118
+ /** Stanza validation failure (SMAX_INVALID) — likely stale device session */
119
+ SmaxInvalid: '479'
120
+ };
121
+ /**
122
+ * Decode the received node as a message.
123
+ * @note this will only parse the message, not decrypt it
124
+ */
125
+ export function decodeMessageNode(stanza, meId, meLid) {
126
+ let msgType
127
+ let chatId
128
+ let author
129
+ let fromMe = false
130
+ const msgId = stanza.attrs.id
131
+ const from = stanza.attrs.from
132
+ const participant = stanza.attrs.participant
133
+ const recipient = stanza.attrs.recipient
134
+ const addressingContext = extractAddressingContext(stanza)
135
+ const isMe = (jid) => areJidsSameUser(jid, meId)
136
+ const isMeLid = (jid) => areJidsSameUser(jid, meLid)
137
+ if (isPnUser(from) || isLidUser(from) || isHostedLidUser(from) || isHostedPnUser(from)) {
138
+ if (recipient && !isJidMetaAI(recipient)) {
139
+ if (!isMe(from) && !isMeLid(from)) {
140
+ throw new Boom("receipient present, but msg not from me", { data: stanza })
141
+ }
142
+ if (isMe(from) || isMeLid(from)) {
143
+ fromMe = true
144
+ }
145
+ chatId = recipient
146
+ } else {
147
+ chatId = from
148
+ }
149
+ msgType = "chat"
150
+ author = from
151
+ } else if (isJidGroup(from)) {
152
+ if (!participant) {
153
+ throw new Boom("No participant in group message")
154
+ }
155
+ if (isMe(participant) || isMeLid(participant)) {
156
+ fromMe = true
157
+ }
158
+ msgType = "group"
159
+ author = participant
160
+ chatId = from
161
+ } else if (isJidBroadcast(from)) {
162
+ if (!participant) {
163
+ throw new Boom("No participant in group message")
164
+ }
165
+ const isParticipantMe = isMe(participant)
166
+ if (isJidStatusBroadcast(from)) {
167
+ msgType = isParticipantMe ? "direct_peer_status" : "other_status"
168
+ } else {
169
+ msgType = isParticipantMe ? "peer_broadcast" : "other_broadcast"
170
+ }
171
+ fromMe = isParticipantMe
172
+ chatId = from
173
+ author = participant
174
+ } else if (isJidNewsletter(from)) {
175
+ msgType = "newsletter"
176
+ chatId = from
177
+ author = from
178
+ if (isMe(from) || isMeLid(from)) {
179
+ fromMe = true
180
+ }
181
+ } else {
182
+ throw new Boom("Unknown message type", { data: stanza })
183
+ }
184
+ const pushname = stanza?.attrs?.notify
185
+ const key = {
186
+ remoteJid: chatId,
187
+ remoteJidAlt: !isJidGroup(chatId) ? addressingContext.senderAlt : undefined,
188
+ fromMe,
189
+ id: msgId,
190
+ participant,
191
+ participantAlt: isJidGroup(chatId) ? addressingContext.senderAlt : undefined,
192
+ addressingMode: addressingContext.addressingMode,
193
+ ...(msgType === "newsletter" && stanza.attrs.server_id ? { server_id: stanza.attrs.server_id } : {}),
194
+ }
195
+ const fullMessage = {
196
+ key,
197
+ messageTimestamp: +stanza.attrs.t,
198
+ pushName: pushname,
199
+ broadcast: isJidBroadcast(from),
200
+ }
201
+ if (key.fromMe) {
202
+ fullMessage.status = proto.WebMessageInfo.Status.SERVER_ACK
203
+ }
204
+ return {
205
+ fullMessage,
206
+ author,
207
+ sender: msgType === "chat" ? author : chatId,
208
+ }
209
+ }
210
+ export const decryptMessageNode = (stanza, meId, meLid, repository, logger) => {
211
+ const { fullMessage, author, sender } = decodeMessageNode(stanza, meId, meLid)
212
+ return {
213
+ fullMessage,
214
+ category: stanza.attrs.category,
215
+ author,
216
+ async decrypt() {
217
+ let decryptables = 0
218
+ if (Array.isArray(stanza.content)) {
219
+ for (const { tag, attrs, content } of stanza.content) {
220
+ if (tag === "verified_name" && content instanceof Uint8Array) {
221
+ const cert = proto.VerifiedNameCertificate.decode(content)
222
+ const details = proto.VerifiedNameCertificate.Details.decode(cert.details)
223
+ fullMessage.verifiedBizName = details.verifiedName
224
+ }
225
+ if (tag === "unavailable" && attrs.type === "view_once") {
226
+ fullMessage.key.isViewOnce = true // TODO: remove from here and add a STUB TYPE
227
+ }
228
+ if (tag !== "enc" && tag !== "plaintext") {
229
+ continue
230
+ }
231
+ if (!(content instanceof Uint8Array)) {
232
+ continue
233
+ }
234
+ decryptables += 1
235
+ let msgBuffer
236
+ const decryptionJid = await getDecryptionJid(author, repository, logger)
237
+ let decryptionAltJid = null
238
+ const { senderAlt } = extractAddressingContext(stanza)
239
+ if (senderAlt) {
240
+ decryptionAltJid = await getDecryptionJid(senderAlt, repository, logger)
241
+ }
242
+ if (tag !== 'plaintext') {
243
+ storeMappingFromEnvelope(stanza, author, repository, decryptionJid, logger, meId, meLid).catch(err => logger?.warn?.({ err }, 'storeMappingFromEnvelope failed'))
244
+ }
245
+ try {
246
+ const e2eType = tag === "plaintext" ? "plaintext" : attrs.type
247
+ switch (e2eType) {
248
+ case "skmsg":
249
+ try {
250
+ msgBuffer = await repository.decryptGroupMessage({
251
+ group: sender,
252
+ authorJid: author,
253
+ msg: content,
254
+ })
255
+ } catch (decryptErr) {
256
+ const errMsg = decryptErr?.message || decryptErr?.toString() || ''
257
+ if (errMsg.includes('invalid sender key session') || errMsg.includes('memory access out of bounds')) {
258
+ logger?.debug?.({ group: sender, author: author, errMsg }, '[Signal] Stale/corrupt sender key session, deleting')
259
+ try {
260
+ await repository.deleteSenderKey(sender, author)
261
+ } catch (e) {
262
+ logger?.warn?.({ err: e }, 'Failed to delete sender key')
263
+ }
264
+ }
265
+ throw decryptErr
266
+ }
267
+ break
268
+ case "pkmsg":
269
+ case "msg":
270
+ try {
271
+ msgBuffer = await repository.decryptMessage({ jid: decryptionJid, type: e2eType, ciphertext: content })
272
+ if (msgBuffer === null) {
273
+ throw new Error("DuplicatedMessage")
274
+ }
275
+ } catch (decryptErr) {
276
+ const errMsg = decryptErr?.message || decryptErr?.toString() || ''
277
+ if ((errMsg.includes('InvalidMessage') || errMsg.includes('BadMac')) && e2eType === 'msg') {
278
+ logger?.debug?.({ jid: decryptionJid, errMsg }, '[Signal] Stale session (InvalidMessage/BadMac), deleting')
279
+ try {
280
+ const toDelete = [decryptionJid]
281
+ await repository.deleteSession(toDelete)
282
+ } catch { }
283
+ }
284
+ const cleanError = new Error(errMsg || 'Decryption failed')
285
+ cleanError.stack = decryptErr?.stack
286
+ throw cleanError
287
+ }
288
+ break
289
+ case "plaintext":
290
+ msgBuffer = content
291
+ break
292
+ default:
293
+ throw new Error(`Unknown e2e type: ${e2eType}`)
294
+ }
295
+ let msg = proto.Message.decode(e2eType !== "plaintext" ? unpadRandomMax16(msgBuffer) : msgBuffer)
296
+ msg = msg.deviceSentMessage?.message || msg
297
+ if (msg.senderKeyDistributionMessage) {
298
+ //eslint-disable-next-line max-depth
299
+ try {
300
+ await repository.processSenderKeyDistributionMessage({
301
+ authorJid: author,
302
+ item: msg.senderKeyDistributionMessage,
303
+ })
304
+ } catch (err) {
305
+ logger.error({ key: fullMessage.key, err }, "failed to process sender key distribution message")
306
+ }
307
+ }
308
+ if (fullMessage.message) {
309
+ Object.assign(fullMessage.message, msg)
310
+ } else {
311
+ fullMessage.message = msg
312
+ }
313
+ const viewOnceInner =
314
+ msg?.viewOnceMessage?.message ||
315
+ msg?.viewOnceMessageV2?.message ||
316
+ msg?.viewOnceMessageV2Extension?.message
317
+ if (
318
+ viewOnceInner?.imageMessage?.viewOnce ||
319
+ viewOnceInner?.videoMessage?.viewOnce ||
320
+ viewOnceInner?.audioMessage?.viewOnce
321
+ ) {
322
+ fullMessage.key.isViewOnce = true
323
+ }
324
+ } catch (err) {
325
+ const errorMessage = err?.message || err?.toString() || ""
326
+ const errStr = err?.message || (typeof err === "string" ? err : "") || ""
327
+ const isExpectedDecryptErr = errStr.includes("InvalidPreKeyId") || errStr.includes("SessionNotFound") || errStr.includes("InvalidMessage") || errStr.includes("no sender key state") || errStr.includes("invalid sender key session") || errStr.includes("memory access out of bounds") || errStr.includes("old counter") || errStr.includes("DuplicatedMessage") || errStr.includes("BadMac")
328
+ ; (isExpectedDecryptErr ? logger?.debug?.bind(logger) : logger?.error?.bind(logger))?.({ key: fullMessage.key, err, errorMessage, messageType: tag === "plaintext" ? "plaintext" : attrs.type, sender, author }, "failed to decrypt message")
329
+ fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT
330
+ fullMessage.messageStubParameters = [errorMessage]
331
+ }
332
+ }
333
+ }
334
+ // if nothing was found to decrypt
335
+ if (!decryptables) {
336
+ fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT
337
+ fullMessage.messageStubParameters = [NO_MESSAGE_FOUND_ERROR_TEXT]
338
+ }
339
+ },
340
+ }
341
+ }
342
+ //# sourceMappingURL=decode-wa-message.js.map