@nexustechpro/baileys 1.0.1 → 1.0.3-rc.1

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.
@@ -1,4 +1,11 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.makeInMemoryStore = void 0;
7
+ const make_in_memory_store_1 = __importDefault(require("./make-in-memory-store"));
8
+ exports.makeInMemoryStore = make_in_memory_store_1.default;
1
9
  export * from './make-cache-manager-store.js';
2
- export * from './make-in-memory-store.js';
3
10
  export * from './make-ordered-dictionary.js';
4
11
  export * from './object-repository.js';
@@ -1,279 +1,298 @@
1
- import { Boom } from '@hapi/boom';
2
- import { proto } from '../../WAProto/index.js';
3
- import { areJidsSameUser, isHostedLidUser, isHostedPnUser, isJidBroadcast, isJidGroup, isJidMetaAI, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser
4
- // transferDevice
5
- } from '../WABinary/index.js';
6
- import { unpadRandomMax16 } from './generics.js';
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"
7
17
  export const getDecryptionJid = async (sender, repository) => {
8
- if (isLidUser(sender) || isHostedLidUser(sender)) {
9
- return sender;
10
- }
11
- const mapped = await repository.lidMapping.getLIDForPN(sender);
12
- return mapped || sender;
13
- };
18
+ if (isLidUser(sender) || isHostedLidUser(sender)) {
19
+ return sender
20
+ }
21
+ const mapped = await repository.lidMapping.getLIDForPN(sender)
22
+ return mapped || sender
23
+ }
14
24
  const storeMappingFromEnvelope = async (stanza, sender, repository, decryptionJid, logger) => {
15
- // TODO: Handle hosted IDs
16
- const { senderAlt } = extractAddressingContext(stanza);
17
- if (senderAlt && isLidUser(senderAlt) && isPnUser(sender) && decryptionJid === sender) {
18
- try {
19
- await repository.lidMapping.storeLIDPNMappings([{ lid: senderAlt, pn: sender }]);
20
- await repository.migrateSession(sender, senderAlt);
21
- logger.debug({ sender, senderAlt }, 'Stored LID mapping from envelope');
22
- }
23
- catch (error) {
24
- logger.warn({ sender, senderAlt, error }, 'Failed to store LID mapping');
25
- }
25
+ // TODO: Handle hosted IDs
26
+ const { senderAlt } = extractAddressingContext(stanza)
27
+ if (senderAlt && isLidUser(senderAlt) && isPnUser(sender) && decryptionJid === sender) {
28
+ try {
29
+ await repository.lidMapping.storeLIDPNMappings([{ lid: senderAlt, pn: sender }])
30
+ await repository.migrateSession(sender, senderAlt)
31
+ logger.debug({ sender, senderAlt }, "Stored LID mapping from envelope")
32
+ } catch (error) {
33
+ logger.warn({ sender, senderAlt, error }, "Failed to store LID mapping")
26
34
  }
27
- };
28
- export const NO_MESSAGE_FOUND_ERROR_TEXT = 'Message absent from node';
29
- export const MISSING_KEYS_ERROR_TEXT = 'Key used already or never filled';
35
+ }
36
+ }
37
+ export const NO_MESSAGE_FOUND_ERROR_TEXT = "Message absent from node"
38
+ export const MISSING_KEYS_ERROR_TEXT = "Key used already or never filled"
30
39
  // Retry configuration for failed decryption
31
40
  export const DECRYPTION_RETRY_CONFIG = {
32
- maxRetries: 3,
33
- baseDelayMs: 100,
34
- sessionRecordErrors: ['No session record', 'SessionError: No session record']
35
- };
41
+ maxRetries: 3,
42
+ baseDelayMs: 100,
43
+ sessionRecordErrors: ["No session record", "SessionError: No session record"],
44
+ }
36
45
  export const NACK_REASONS = {
37
- ParsingError: 487,
38
- UnrecognizedStanza: 488,
39
- UnrecognizedStanzaClass: 489,
40
- UnrecognizedStanzaType: 490,
41
- InvalidProtobuf: 491,
42
- InvalidHostedCompanionStanza: 493,
43
- MissingMessageSecret: 495,
44
- SignalErrorOldCounter: 496,
45
- MessageDeletedOnPeer: 499,
46
- UnhandledError: 500,
47
- UnsupportedAdminRevoke: 550,
48
- UnsupportedLIDGroup: 551,
49
- DBOperationFailed: 552
50
- };
46
+ ParsingError: 487,
47
+ UnrecognizedStanza: 488,
48
+ UnrecognizedStanzaClass: 489,
49
+ UnrecognizedStanzaType: 490,
50
+ InvalidProtobuf: 491,
51
+ InvalidHostedCompanionStanza: 493,
52
+ MissingMessageSecret: 495,
53
+ SignalErrorOldCounter: 496,
54
+ MessageDeletedOnPeer: 499,
55
+ UnhandledError: 500,
56
+ UnsupportedAdminRevoke: 550,
57
+ UnsupportedLIDGroup: 551,
58
+ DBOperationFailed: 552,
59
+ }
51
60
  export const extractAddressingContext = (stanza) => {
52
- let senderAlt;
53
- let recipientAlt;
54
- const sender = stanza.attrs.participant || stanza.attrs.from;
55
- const addressingMode = stanza.attrs.addressing_mode || (sender?.endsWith('lid') ? 'lid' : 'pn');
56
- if (addressingMode === 'lid') {
57
- // Message is LID-addressed: sender is LID, extract corresponding PN
58
- // without device data
59
- senderAlt = stanza.attrs.participant_pn || stanza.attrs.sender_pn || stanza.attrs.peer_recipient_pn;
60
- recipientAlt = stanza.attrs.recipient_pn;
61
- // with device data
62
- //if (sender && senderAlt) senderAlt = transferDevice(sender, senderAlt)
63
- }
64
- else {
65
- // Message is PN-addressed: sender is PN, extract corresponding LID
66
- // without device data
67
- senderAlt = stanza.attrs.participant_lid || stanza.attrs.sender_lid || stanza.attrs.peer_recipient_lid;
68
- recipientAlt = stanza.attrs.recipient_lid;
69
- //with device data
70
- //if (sender && senderAlt) senderAlt = transferDevice(sender, senderAlt)
71
- }
72
- return {
73
- addressingMode,
74
- senderAlt,
75
- recipientAlt
76
- };
77
- };
61
+ let senderAlt
62
+ let recipientAlt
63
+ const sender = stanza.attrs.participant || stanza.attrs.from
64
+ const addressingMode = stanza.attrs.addressing_mode || (sender?.endsWith("lid") ? "lid" : "pn")
65
+ if (addressingMode === "lid") {
66
+ // Message is LID-addressed: sender is LID, extract corresponding PN
67
+ // without device data
68
+ senderAlt = stanza.attrs.participant_pn || stanza.attrs.sender_pn || stanza.attrs.peer_recipient_pn
69
+ recipientAlt = stanza.attrs.recipient_pn
70
+ // with device data
71
+ //if (sender && senderAlt) senderAlt = transferDevice(sender, senderAlt)
72
+ } else {
73
+ // Message is PN-addressed: sender is PN, extract corresponding LID
74
+ // without device data
75
+ senderAlt = stanza.attrs.participant_lid || stanza.attrs.sender_lid || stanza.attrs.peer_recipient_lid
76
+ recipientAlt = stanza.attrs.recipient_lid
77
+ //with device data
78
+ //if (sender && senderAlt) senderAlt = transferDevice(sender, senderAlt)
79
+ }
80
+ return {
81
+ addressingMode,
82
+ senderAlt,
83
+ recipientAlt,
84
+ }
85
+ }
78
86
  /**
79
87
  * Decode the received node as a message.
80
88
  * @note this will only parse the message, not decrypt it
81
89
  */
82
90
  export function decodeMessageNode(stanza, meId, meLid) {
83
- let msgType;
84
- let chatId;
85
- let author;
86
- let fromMe = false;
87
- const msgId = stanza.attrs.id;
88
- const from = stanza.attrs.from;
89
- const participant = stanza.attrs.participant;
90
- const recipient = stanza.attrs.recipient;
91
- const addressingContext = extractAddressingContext(stanza);
92
- const isMe = (jid) => areJidsSameUser(jid, meId);
93
- const isMeLid = (jid) => areJidsSameUser(jid, meLid);
94
- if (isPnUser(from) || isLidUser(from) || isHostedLidUser(from) || isHostedPnUser(from)) {
95
- if (recipient && !isJidMetaAI(recipient)) {
96
- if (!isMe(from) && !isMeLid(from)) {
97
- throw new Boom('receipient present, but msg not from me', { data: stanza });
98
- }
99
- if (isMe(from) || isMeLid(from)) {
100
- fromMe = true;
101
- }
102
- chatId = recipient;
103
- }
104
- else {
105
- chatId = from;
106
- }
107
- msgType = 'chat';
108
- author = from;
91
+ let msgType
92
+ let chatId
93
+ let author
94
+ let fromMe = false
95
+ const msgId = stanza.attrs.id
96
+ const from = stanza.attrs.from
97
+ const participant = stanza.attrs.participant
98
+ const recipient = stanza.attrs.recipient
99
+ const addressingContext = extractAddressingContext(stanza)
100
+ const isMe = (jid) => areJidsSameUser(jid, meId)
101
+ const isMeLid = (jid) => areJidsSameUser(jid, meLid)
102
+ if (isPnUser(from) || isLidUser(from) || isHostedLidUser(from) || isHostedPnUser(from)) {
103
+ if (recipient && !isJidMetaAI(recipient)) {
104
+ if (!isMe(from) && !isMeLid(from)) {
105
+ throw new Boom("receipient present, but msg not from me", { data: stanza })
106
+ }
107
+ if (isMe(from) || isMeLid(from)) {
108
+ fromMe = true
109
+ }
110
+ chatId = recipient
111
+ } else {
112
+ chatId = from
109
113
  }
110
- else if (isJidGroup(from)) {
111
- if (!participant) {
112
- throw new Boom('No participant in group message');
113
- }
114
- if (isMe(participant) || isMeLid(participant)) {
115
- fromMe = true;
116
- }
117
- msgType = 'group';
118
- author = participant;
119
- chatId = from;
114
+ msgType = "chat"
115
+ author = from
116
+ } else if (isJidGroup(from)) {
117
+ if (!participant) {
118
+ throw new Boom("No participant in group message")
120
119
  }
121
- else if (isJidBroadcast(from)) {
122
- if (!participant) {
123
- throw new Boom('No participant in group message');
124
- }
125
- const isParticipantMe = isMe(participant);
126
- if (isJidStatusBroadcast(from)) {
127
- msgType = isParticipantMe ? 'direct_peer_status' : 'other_status';
128
- }
129
- else {
130
- msgType = isParticipantMe ? 'peer_broadcast' : 'other_broadcast';
131
- }
132
- fromMe = isParticipantMe;
133
- chatId = from;
134
- author = participant;
120
+ if (isMe(participant) || isMeLid(participant)) {
121
+ fromMe = true
135
122
  }
136
- else if (isJidNewsletter(from)) {
137
- msgType = 'newsletter';
138
- chatId = from;
139
- author = from;
140
- if (isMe(from) || isMeLid(from)) {
141
- fromMe = true;
142
- }
123
+ msgType = "group"
124
+ author = participant
125
+ chatId = from
126
+ } else if (isJidBroadcast(from)) {
127
+ if (!participant) {
128
+ throw new Boom("No participant in group message")
143
129
  }
144
- else {
145
- throw new Boom('Unknown message type', { data: stanza });
130
+ const isParticipantMe = isMe(participant)
131
+ if (isJidStatusBroadcast(from)) {
132
+ msgType = isParticipantMe ? "direct_peer_status" : "other_status"
133
+ } else {
134
+ msgType = isParticipantMe ? "peer_broadcast" : "other_broadcast"
146
135
  }
147
- const pushname = stanza?.attrs?.notify;
148
- const key = {
149
- remoteJid: chatId,
150
- remoteJidAlt: !isJidGroup(chatId) ? addressingContext.senderAlt : undefined,
151
- fromMe,
152
- id: msgId,
153
- participant,
154
- participantAlt: isJidGroup(chatId) ? addressingContext.senderAlt : undefined,
155
- addressingMode: addressingContext.addressingMode,
156
- ...(msgType === 'newsletter' && stanza.attrs.server_id ? { server_id: stanza.attrs.server_id } : {})
157
- };
158
- const fullMessage = {
159
- key,
160
- messageTimestamp: +stanza.attrs.t,
161
- pushName: pushname,
162
- broadcast: isJidBroadcast(from)
163
- };
164
- if (key.fromMe) {
165
- fullMessage.status = proto.WebMessageInfo.Status.SERVER_ACK;
136
+ fromMe = isParticipantMe
137
+ chatId = from
138
+ author = participant
139
+ } else if (isJidNewsletter(from)) {
140
+ msgType = "newsletter"
141
+ chatId = from
142
+ author = from
143
+ if (isMe(from) || isMeLid(from)) {
144
+ fromMe = true
166
145
  }
167
- return {
168
- fullMessage,
169
- author,
170
- sender: msgType === 'chat' ? author : chatId
171
- };
146
+ } else {
147
+ throw new Boom("Unknown message type", { data: stanza })
148
+ }
149
+ const pushname = stanza?.attrs?.notify
150
+ const key = {
151
+ remoteJid: chatId,
152
+ remoteJidAlt: !isJidGroup(chatId) ? addressingContext.senderAlt : undefined,
153
+ fromMe,
154
+ id: msgId,
155
+ participant,
156
+ participantAlt: isJidGroup(chatId) ? addressingContext.senderAlt : undefined,
157
+ addressingMode: addressingContext.addressingMode,
158
+ ...(msgType === "newsletter" && stanza.attrs.server_id ? { server_id: stanza.attrs.server_id } : {}),
159
+ }
160
+ const fullMessage = {
161
+ key,
162
+ messageTimestamp: +stanza.attrs.t,
163
+ pushName: pushname,
164
+ broadcast: isJidBroadcast(from),
165
+ }
166
+ if (key.fromMe) {
167
+ fullMessage.status = proto.WebMessageInfo.Status.SERVER_ACK
168
+ }
169
+ return {
170
+ fullMessage,
171
+ author,
172
+ sender: msgType === "chat" ? author : chatId,
173
+ }
172
174
  }
173
175
  export const decryptMessageNode = (stanza, meId, meLid, repository, logger) => {
174
- const { fullMessage, author, sender } = decodeMessageNode(stanza, meId, meLid);
175
- return {
176
- fullMessage,
177
- category: stanza.attrs.category,
178
- author,
179
- async decrypt() {
180
- let decryptables = 0;
181
- if (Array.isArray(stanza.content)) {
182
- for (const { tag, attrs, content } of stanza.content) {
183
- if (tag === 'verified_name' && content instanceof Uint8Array) {
184
- const cert = proto.VerifiedNameCertificate.decode(content);
185
- const details = proto.VerifiedNameCertificate.Details.decode(cert.details);
186
- fullMessage.verifiedBizName = details.verifiedName;
187
- }
188
- if (tag === 'unavailable' && attrs.type === 'view_once') {
189
- fullMessage.key.isViewOnce = true; // TODO: remove from here and add a STUB TYPE
190
- }
191
- if (tag !== 'enc' && tag !== 'plaintext') {
192
- continue;
193
- }
194
- if (!(content instanceof Uint8Array)) {
195
- continue;
196
- }
197
- decryptables += 1;
198
- let msgBuffer;
199
- const decryptionJid = await getDecryptionJid(author, repository);
200
- if (tag !== 'plaintext') {
201
- // TODO: Handle hosted devices
202
- await storeMappingFromEnvelope(stanza, author, repository, decryptionJid, logger);
203
- }
204
- try {
205
- const e2eType = tag === 'plaintext' ? 'plaintext' : attrs.type;
206
- switch (e2eType) {
207
- case 'skmsg':
208
- msgBuffer = await repository.decryptGroupMessage({
209
- group: sender,
210
- authorJid: author,
211
- msg: content
212
- });
213
- break;
214
- case 'pkmsg':
215
- case 'msg':
216
- msgBuffer = await repository.decryptMessage({
217
- jid: decryptionJid,
218
- type: e2eType,
219
- ciphertext: content
220
- });
221
- break;
222
- case 'plaintext':
223
- msgBuffer = content;
224
- break;
225
- default:
226
- throw new Error(`Unknown e2e type: ${e2eType}`);
227
- }
228
- let msg = proto.Message.decode(e2eType !== 'plaintext' ? unpadRandomMax16(msgBuffer) : msgBuffer);
229
- msg = msg.deviceSentMessage?.message || msg;
230
- if (msg.senderKeyDistributionMessage) {
231
- //eslint-disable-next-line max-depth
232
- try {
233
- await repository.processSenderKeyDistributionMessage({
234
- authorJid: author,
235
- item: msg.senderKeyDistributionMessage
236
- });
237
- }
238
- catch (err) {
239
- logger.error({ key: fullMessage.key, err }, 'failed to process sender key distribution message');
240
- }
241
- }
242
- if (fullMessage.message) {
243
- Object.assign(fullMessage.message, msg);
244
- }
245
- else {
246
- fullMessage.message = msg;
247
- }
248
- }
249
- catch (err) {
250
- const errorContext = {
251
- key: fullMessage.key,
252
- err,
253
- messageType: tag === 'plaintext' ? 'plaintext' : attrs.type,
254
- sender,
255
- author,
256
- isSessionRecordError: isSessionRecordError(err)
257
- };
258
- logger.error(errorContext, 'failed to decrypt message');
259
- fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT;
260
- fullMessage.messageStubParameters = [err.message.toString()];
261
- }
262
- }
176
+ const { fullMessage, author, sender } = decodeMessageNode(stanza, meId, meLid)
177
+ return {
178
+ fullMessage,
179
+ category: stanza.attrs.category,
180
+ author,
181
+ async decrypt() {
182
+ let decryptables = 0
183
+ if (Array.isArray(stanza.content)) {
184
+ for (const { tag, attrs, content } of stanza.content) {
185
+ if (tag === "verified_name" && content instanceof Uint8Array) {
186
+ const cert = proto.VerifiedNameCertificate.decode(content)
187
+ const details = proto.VerifiedNameCertificate.Details.decode(cert.details)
188
+ fullMessage.verifiedBizName = details.verifiedName
189
+ }
190
+ if (tag === "unavailable" && attrs.type === "view_once") {
191
+ fullMessage.key.isViewOnce = true // TODO: remove from here and add a STUB TYPE
192
+ }
193
+ if (tag !== "enc" && tag !== "plaintext") {
194
+ continue
195
+ }
196
+ if (!(content instanceof Uint8Array)) {
197
+ continue
198
+ }
199
+ decryptables += 1
200
+ let msgBuffer
201
+ const decryptionJid = await getDecryptionJid(author, repository)
202
+ if (tag !== "plaintext") {
203
+ // TODO: Handle hosted devices
204
+ await storeMappingFromEnvelope(stanza, author, repository, decryptionJid, logger)
205
+ }
206
+ try {
207
+ const e2eType = tag === "plaintext" ? "plaintext" : attrs.type
208
+ switch (e2eType) {
209
+ case "skmsg":
210
+ msgBuffer = await repository.decryptGroupMessage({
211
+ group: sender,
212
+ authorJid: author,
213
+ msg: content,
214
+ })
215
+ break
216
+ case "pkmsg":
217
+ case "msg":
218
+ try {
219
+ msgBuffer = await repository.decryptMessage({
220
+ jid: decryptionJid,
221
+ type: e2eType,
222
+ ciphertext: content,
223
+ })
224
+ } catch (decryptErr) {
225
+ const cleanError = new Error(decryptErr?.message || decryptErr?.toString() || "Decryption failed")
226
+ cleanError.stack = decryptErr?.stack
227
+ throw cleanError
228
+ }
229
+ break
230
+ case "plaintext":
231
+ msgBuffer = content
232
+ break
233
+ default:
234
+ throw new Error(`Unknown e2e type: ${e2eType}`)
263
235
  }
264
- // if nothing was found to decrypt
265
- if (!decryptables) {
266
- fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT;
267
- fullMessage.messageStubParameters = [NO_MESSAGE_FOUND_ERROR_TEXT];
236
+ let msg = proto.Message.decode(e2eType !== "plaintext" ? unpadRandomMax16(msgBuffer) : msgBuffer)
237
+ msg = msg.deviceSentMessage?.message || msg
238
+ if (msg.senderKeyDistributionMessage) {
239
+ //eslint-disable-next-line max-depth
240
+ try {
241
+ await repository.processSenderKeyDistributionMessage({
242
+ authorJid: author,
243
+ item: msg.senderKeyDistributionMessage,
244
+ })
245
+ } catch (err) {
246
+ logger.error({ key: fullMessage.key, err }, "failed to process sender key distribution message")
247
+ }
268
248
  }
249
+ if (fullMessage.message) {
250
+ Object.assign(fullMessage.message, msg)
251
+ } else {
252
+ fullMessage.message = msg
253
+ }
254
+ } catch (err) {
255
+ const errorMessage = err?.message || err?.toString() || ""
256
+ const isBadMac = errorMessage.includes("Bad MAC")
257
+ const isMessageCounter = errorMessage.includes("Key used already or never filled")
258
+
259
+ const errorContext = {
260
+ key: fullMessage.key,
261
+ err,
262
+ errorMessage,
263
+ messageType: tag === "plaintext" ? "plaintext" : attrs.type,
264
+ sender,
265
+ author,
266
+ isSessionRecordError: isSessionRecordError(err),
267
+ isBadMac,
268
+ isMessageCounter,
269
+ }
270
+
271
+ if (isBadMac || isMessageCounter) {
272
+ errorContext.isSignalSessionCorrupted = true
273
+ errorContext.requiresSessionReset = true
274
+ logger.error(
275
+ { ...errorContext, jid: decryptionJid },
276
+ `Signal session corrupted - ${isBadMac ? 'BAD_MAC' : 'MESSAGE_COUNTER'} error, session reset required`,
277
+ )
278
+ }
279
+
280
+ logger.error(errorContext, "failed to decrypt message")
281
+ fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT
282
+ fullMessage.messageStubParameters = [errorMessage]
283
+ }
269
284
  }
270
- };
271
- };
272
- /**
273
- * Utility function to check if an error is related to missing session record
274
- */
285
+ }
286
+ // if nothing was found to decrypt
287
+ if (!decryptables) {
288
+ fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT
289
+ fullMessage.messageStubParameters = [NO_MESSAGE_FOUND_ERROR_TEXT]
290
+ }
291
+ },
292
+ }
293
+ }
275
294
  function isSessionRecordError(error) {
276
- const errorMessage = error?.message || error?.toString() || '';
277
- return DECRYPTION_RETRY_CONFIG.sessionRecordErrors.some(errorPattern => errorMessage.includes(errorPattern));
295
+ const errorMessage = error?.message || error?.toString() || ""
296
+ return DECRYPTION_RETRY_CONFIG.sessionRecordErrors.some((errorPattern) => errorMessage.includes(errorPattern))
278
297
  }
279
- //# sourceMappingURL=decode-wa-message.js.map
298
+ //# sourceMappingURL=decode-wa-message.js.map