@periskope/baileys 7.0.0-beta-2 → 7.0.0-beta-3
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.
- package/WAProto/GenerateStatics.sh +2 -2
- package/WAProto/index.d.ts +45605 -2820
- package/WAProto/index.js +152067 -24346
- package/lib/Defaults/index.d.ts +5 -0
- package/lib/Defaults/index.d.ts.map +1 -1
- package/lib/Defaults/index.js +8 -2
- package/lib/Defaults/index.js.map +1 -1
- package/lib/Signal/Group/queue-job.d.ts +1 -0
- package/lib/Signal/Group/queue-job.d.ts.map +1 -1
- package/lib/Signal/Group/queue-job.js +3 -0
- package/lib/Signal/Group/queue-job.js.map +1 -1
- package/lib/Signal/Group/sender-key-distribution-message.d.ts.map +1 -1
- package/lib/Signal/Group/sender-key-distribution-message.js +1 -1
- package/lib/Signal/Group/sender-key-distribution-message.js.map +1 -1
- package/lib/Signal/Group/sender-key-message.d.ts.map +1 -1
- package/lib/Signal/Group/sender-key-message.js +1 -1
- package/lib/Signal/Group/sender-key-message.js.map +1 -1
- package/lib/Signal/Group/sender-key-state.d.ts.map +1 -1
- package/lib/Signal/Group/sender-key-state.js +6 -1
- package/lib/Signal/Group/sender-key-state.js.map +1 -1
- package/lib/Signal/libsignal.d.ts.map +1 -1
- package/lib/Signal/libsignal.js +147 -9
- package/lib/Signal/libsignal.js.map +1 -1
- package/lib/Socket/business.d.ts +18 -7
- package/lib/Socket/business.d.ts.map +1 -1
- package/lib/Socket/business.js +122 -1
- package/lib/Socket/business.js.map +1 -1
- package/lib/Socket/chats.d.ts +8 -2
- package/lib/Socket/chats.d.ts.map +1 -1
- package/lib/Socket/chats.js +38 -1
- package/lib/Socket/chats.js.map +1 -1
- package/lib/Socket/communities.d.ts +17 -4
- package/lib/Socket/communities.d.ts.map +1 -1
- package/lib/Socket/communities.js +45 -1
- package/lib/Socket/communities.js.map +1 -1
- package/lib/Socket/groups.d.ts +7 -2
- package/lib/Socket/groups.d.ts.map +1 -1
- package/lib/Socket/groups.js +1 -1
- package/lib/Socket/groups.js.map +1 -1
- package/lib/Socket/index.d.ts +17 -4
- package/lib/Socket/index.d.ts.map +1 -1
- package/lib/Socket/messages-recv.d.ts +11 -4
- package/lib/Socket/messages-recv.d.ts.map +1 -1
- package/lib/Socket/messages-recv.js +73 -10
- package/lib/Socket/messages-recv.js.map +1 -1
- package/lib/Socket/messages-send.d.ts +11 -4
- package/lib/Socket/messages-send.d.ts.map +1 -1
- package/lib/Socket/messages-send.js +443 -55
- package/lib/Socket/messages-send.js.map +1 -1
- package/lib/Socket/newsletter.d.ts +7 -2
- package/lib/Socket/newsletter.d.ts.map +1 -1
- package/lib/Socket/socket.d.ts +2 -2
- package/lib/Socket/socket.d.ts.map +1 -1
- package/lib/Socket/socket.js +147 -25
- package/lib/Socket/socket.js.map +1 -1
- package/lib/Socket/usync.d.ts +2 -2
- package/lib/Types/Auth.d.ts +1 -0
- package/lib/Types/Auth.d.ts.map +1 -1
- package/lib/Types/Chat.d.ts +3 -0
- package/lib/Types/Chat.d.ts.map +1 -1
- package/lib/Types/Chat.js.map +1 -1
- package/lib/Types/Message.d.ts +21 -1
- package/lib/Types/Message.d.ts.map +1 -1
- package/lib/Types/Signal.d.ts +20 -0
- package/lib/Types/Signal.d.ts.map +1 -1
- package/lib/Utils/chat-utils.d.ts.map +1 -1
- package/lib/Utils/chat-utils.js +18 -1
- package/lib/Utils/chat-utils.js.map +1 -1
- package/lib/Utils/decode-wa-message.d.ts +5 -0
- package/lib/Utils/decode-wa-message.d.ts.map +1 -1
- package/lib/Utils/decode-wa-message.js +51 -3
- package/lib/Utils/decode-wa-message.js.map +1 -1
- package/lib/Utils/messages-media.d.ts.map +1 -1
- package/lib/Utils/messages-media.js +4 -1
- package/lib/Utils/messages-media.js.map +1 -1
- package/lib/Utils/messages.d.ts.map +1 -1
- package/lib/Utils/messages.js +35 -15
- package/lib/Utils/messages.js.map +1 -1
- package/lib/Utils/use-multi-file-auth-state.js +1 -1
- package/lib/Utils/use-multi-file-auth-state.js.map +1 -1
- package/lib/Utils/validate-connection.js +2 -2
- package/lib/Utils/validate-connection.js.map +1 -1
- package/package.json +4 -2
|
@@ -4,6 +4,7 @@ import { proto } from '../../WAProto/index.js';
|
|
|
4
4
|
import { DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
|
|
5
5
|
import { aggregateMessageKeysNotFromMe, assertMediaContent, bindWaitForEvent, decryptMediaRetryData, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds } from '../Utils/index.js';
|
|
6
6
|
import { getUrlInfo } from '../Utils/link-preview.js';
|
|
7
|
+
import { makeKeyedMutex } from '../Utils/make-mutex.js';
|
|
7
8
|
import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, isJidUser, jidDecode, jidEncode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
8
9
|
import { USyncQuery, USyncUser } from '../WAUSync/index.js';
|
|
9
10
|
import { makeGroupsSocket } from './groups.js';
|
|
@@ -17,6 +18,8 @@ export const makeMessagesSocket = (config) => {
|
|
|
17
18
|
stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
|
|
18
19
|
useClones: false
|
|
19
20
|
});
|
|
21
|
+
// Prevent race conditions in Signal session encryption by user
|
|
22
|
+
const encryptionMutex = makeKeyedMutex();
|
|
20
23
|
let mediaConn;
|
|
21
24
|
const refreshMediaConn = async (forceGet = false) => {
|
|
22
25
|
const media = await mediaConn;
|
|
@@ -108,6 +111,34 @@ export const makeMessagesSocket = (config) => {
|
|
|
108
111
|
const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self';
|
|
109
112
|
await sendReceipts(keys, readType);
|
|
110
113
|
};
|
|
114
|
+
/**
|
|
115
|
+
* Deduplicate JIDs when both LID and PN versions exist for same user
|
|
116
|
+
* Prefers LID over PN to maintain single encryption layer
|
|
117
|
+
*/
|
|
118
|
+
const deduplicateLidPnJids = (jids) => {
|
|
119
|
+
const lidUsers = new Set();
|
|
120
|
+
const filteredJids = [];
|
|
121
|
+
// Collect all LID users
|
|
122
|
+
for (const jid of jids) {
|
|
123
|
+
if (jid.includes('@lid')) {
|
|
124
|
+
const user = jidDecode(jid)?.user;
|
|
125
|
+
if (user)
|
|
126
|
+
lidUsers.add(user);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Filter out PN versions when LID exists
|
|
130
|
+
for (const jid of jids) {
|
|
131
|
+
if (jid.includes('@s.whatsapp.net')) {
|
|
132
|
+
const user = jidDecode(jid)?.user;
|
|
133
|
+
if (user && lidUsers.has(user)) {
|
|
134
|
+
logger.debug({ jid }, 'Skipping PN - LID version exists');
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
filteredJids.push(jid);
|
|
139
|
+
}
|
|
140
|
+
return filteredJids;
|
|
141
|
+
};
|
|
111
142
|
/** Fetch all the devices we've to send a message to */
|
|
112
143
|
const getUSyncDevices = async (jids, useCache, ignoreZeroDevices) => {
|
|
113
144
|
const deviceResults = [];
|
|
@@ -115,14 +146,33 @@ export const makeMessagesSocket = (config) => {
|
|
|
115
146
|
logger.debug('not using cache for devices');
|
|
116
147
|
}
|
|
117
148
|
const toFetch = [];
|
|
118
|
-
|
|
149
|
+
// Deduplicate and normalize JIDs
|
|
150
|
+
jids = deduplicateLidPnJids(Array.from(new Set(jids)));
|
|
119
151
|
for (let jid of jids) {
|
|
120
|
-
const
|
|
152
|
+
const decoded = jidDecode(jid);
|
|
153
|
+
const user = decoded?.user;
|
|
154
|
+
const device = decoded?.device;
|
|
155
|
+
const isExplicitDevice = typeof device === 'number' && device >= 0;
|
|
156
|
+
// Handle explicit device JIDs directly
|
|
157
|
+
if (isExplicitDevice && user) {
|
|
158
|
+
deviceResults.push({
|
|
159
|
+
user,
|
|
160
|
+
device,
|
|
161
|
+
wireJid: jid // Preserve exact JID format for wire protocol
|
|
162
|
+
});
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
// For user JIDs, normalize and prepare for device enumeration
|
|
121
166
|
jid = jidNormalizedUser(jid);
|
|
122
167
|
if (useCache) {
|
|
123
168
|
const devices = userDevicesCache.get(user);
|
|
124
169
|
if (devices) {
|
|
125
|
-
|
|
170
|
+
const isLidJid = jid.includes('@lid');
|
|
171
|
+
const devicesWithWire = devices.map(d => ({
|
|
172
|
+
...d,
|
|
173
|
+
wireJid: isLidJid ? jidEncode(d.user, 'lid', d.device) : jidEncode(d.user, 's.whatsapp.net', d.device)
|
|
174
|
+
}));
|
|
175
|
+
deviceResults.push(...devicesWithWire);
|
|
126
176
|
logger.trace({ user }, 'using cache for devices');
|
|
127
177
|
}
|
|
128
178
|
else {
|
|
@@ -136,6 +186,14 @@ export const makeMessagesSocket = (config) => {
|
|
|
136
186
|
if (!toFetch.length) {
|
|
137
187
|
return deviceResults;
|
|
138
188
|
}
|
|
189
|
+
const requestedLidUsers = new Set();
|
|
190
|
+
for (const jid of toFetch) {
|
|
191
|
+
if (jid.includes('@lid')) {
|
|
192
|
+
const user = jidDecode(jid)?.user;
|
|
193
|
+
if (user)
|
|
194
|
+
requestedLidUsers.add(user);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
139
197
|
const query = new USyncQuery().withContext('message').withDeviceProtocol();
|
|
140
198
|
for (const jid of toFetch) {
|
|
141
199
|
query.withUser(new USyncUser().withId(jid));
|
|
@@ -147,7 +205,26 @@ export const makeMessagesSocket = (config) => {
|
|
|
147
205
|
for (const item of extracted) {
|
|
148
206
|
deviceMap[item.user] = deviceMap[item.user] || [];
|
|
149
207
|
deviceMap[item.user]?.push(item);
|
|
150
|
-
|
|
208
|
+
}
|
|
209
|
+
// Process each user's devices as a group for bulk LID migration
|
|
210
|
+
for (const [user, userDevices] of Object.entries(deviceMap)) {
|
|
211
|
+
const isLidUser = requestedLidUsers.has(user);
|
|
212
|
+
// Process all devices for this user
|
|
213
|
+
for (const item of userDevices) {
|
|
214
|
+
const finalWireJid = isLidUser
|
|
215
|
+
? jidEncode(user, 'lid', item.device)
|
|
216
|
+
: jidEncode(item.user, 's.whatsapp.net', item.device);
|
|
217
|
+
deviceResults.push({
|
|
218
|
+
...item,
|
|
219
|
+
wireJid: finalWireJid
|
|
220
|
+
});
|
|
221
|
+
logger.debug({
|
|
222
|
+
user: item.user,
|
|
223
|
+
device: item.device,
|
|
224
|
+
finalWireJid,
|
|
225
|
+
usedLid: isLidUser
|
|
226
|
+
}, 'Processed device with LID priority');
|
|
227
|
+
}
|
|
151
228
|
}
|
|
152
229
|
for (const key in deviceMap) {
|
|
153
230
|
userDevicesCache.set(key, deviceMap[key]);
|
|
@@ -155,24 +232,179 @@ export const makeMessagesSocket = (config) => {
|
|
|
155
232
|
}
|
|
156
233
|
return deviceResults;
|
|
157
234
|
};
|
|
235
|
+
// Helper to check if JID has migrated LID session
|
|
236
|
+
const checkForMigratedLidSession = async (jid) => {
|
|
237
|
+
if (!jid.includes('@s.whatsapp.net'))
|
|
238
|
+
return false;
|
|
239
|
+
const lidMapping = signalRepository.getLIDMappingStore();
|
|
240
|
+
const lidForPN = await lidMapping.getLIDForPN(jid);
|
|
241
|
+
if (!lidForPN?.includes('@lid'))
|
|
242
|
+
return false;
|
|
243
|
+
const lidSignalId = signalRepository.jidToSignalProtocolAddress(lidForPN);
|
|
244
|
+
const lidSessions = await authState.keys.get('session', [lidSignalId]);
|
|
245
|
+
return !!lidSessions[lidSignalId];
|
|
246
|
+
};
|
|
158
247
|
const assertSessions = async (jids, force) => {
|
|
159
248
|
let didFetchNewSession = false;
|
|
160
|
-
|
|
249
|
+
const jidsRequiringFetch = [];
|
|
250
|
+
// Apply same deduplication as in getUSyncDevices
|
|
251
|
+
jids = deduplicateLidPnJids(jids);
|
|
161
252
|
if (force) {
|
|
162
|
-
|
|
253
|
+
// Check which sessions are missing (with LID migration check)
|
|
254
|
+
const addrs = jids.map(jid => signalRepository.jidToSignalProtocolAddress(jid));
|
|
255
|
+
const sessions = await authState.keys.get('session', addrs);
|
|
256
|
+
// Helper to check session for a JID
|
|
257
|
+
const checkJidSession = async (jid) => {
|
|
258
|
+
const signalId = signalRepository.jidToSignalProtocolAddress(jid);
|
|
259
|
+
let hasSession = !!sessions[signalId];
|
|
260
|
+
// Check for migrated LID session if PN session missing
|
|
261
|
+
if (!hasSession) {
|
|
262
|
+
hasSession = await checkForMigratedLidSession(jid);
|
|
263
|
+
if (hasSession) {
|
|
264
|
+
logger.debug({ jid }, 'Found migrated LID session during force assert, skipping PN fetch');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Add to fetch list if no session exists
|
|
268
|
+
if (!hasSession) {
|
|
269
|
+
if (jid.includes('@lid')) {
|
|
270
|
+
logger.debug({ jid }, 'No LID session found, will create new LID session');
|
|
271
|
+
}
|
|
272
|
+
jidsRequiringFetch.push(jid);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
// Process all JIDs
|
|
276
|
+
for (const jid of jids) {
|
|
277
|
+
await checkJidSession(jid);
|
|
278
|
+
}
|
|
163
279
|
}
|
|
164
280
|
else {
|
|
281
|
+
const lidMapping = signalRepository.getLIDMappingStore();
|
|
165
282
|
const addrs = jids.map(jid => signalRepository.jidToSignalProtocolAddress(jid));
|
|
166
283
|
const sessions = await authState.keys.get('session', addrs);
|
|
284
|
+
// Group JIDs by user for bulk migration
|
|
285
|
+
const userGroups = new Map();
|
|
167
286
|
for (const jid of jids) {
|
|
168
|
-
const
|
|
169
|
-
if (!
|
|
170
|
-
|
|
287
|
+
const user = jidNormalizedUser(jid);
|
|
288
|
+
if (!userGroups.has(user)) {
|
|
289
|
+
userGroups.set(user, []);
|
|
290
|
+
}
|
|
291
|
+
userGroups.get(user).push(jid);
|
|
292
|
+
}
|
|
293
|
+
// Helper to check LID mapping for a user
|
|
294
|
+
const checkUserLidMapping = async (user, userJids) => {
|
|
295
|
+
if (!userJids.some(jid => jid.includes('@s.whatsapp.net'))) {
|
|
296
|
+
return { shouldMigrate: false, lidForPN: undefined };
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
const mapping = await lidMapping.getLIDForPN(user);
|
|
300
|
+
if (mapping?.includes('@lid')) {
|
|
301
|
+
logger.debug({ user, lidForPN: mapping, deviceCount: userJids.length }, 'User has LID mapping - preparing bulk migration');
|
|
302
|
+
return { shouldMigrate: true, lidForPN: mapping };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
logger.debug({ user, error }, 'Failed to check LID mapping for user');
|
|
307
|
+
}
|
|
308
|
+
return { shouldMigrate: false, lidForPN: undefined };
|
|
309
|
+
};
|
|
310
|
+
// Helper to migrate a single device
|
|
311
|
+
const migrateDeviceToLid = async (jid, lidForPN) => {
|
|
312
|
+
if (!jid.includes('@s.whatsapp.net'))
|
|
313
|
+
return;
|
|
314
|
+
try {
|
|
315
|
+
const jidDecoded = jidDecode(jid);
|
|
316
|
+
const deviceId = jidDecoded?.device || 0;
|
|
317
|
+
const lidDecoded = jidDecode(lidForPN);
|
|
318
|
+
const lidWithDevice = jidEncode(lidDecoded?.user, 'lid', deviceId);
|
|
319
|
+
await signalRepository.migrateSession(jid, lidWithDevice);
|
|
320
|
+
logger.debug({ fromJid: jid, toJid: lidWithDevice }, 'Migrated device session to LID');
|
|
321
|
+
// Delete PN session after successful migration
|
|
322
|
+
try {
|
|
323
|
+
await signalRepository.deleteSession(jid);
|
|
324
|
+
logger.debug({ deletedPNSession: jid }, 'Deleted PN session after migration');
|
|
325
|
+
}
|
|
326
|
+
catch (deleteError) {
|
|
327
|
+
logger.warn({ jid, error: deleteError }, 'Failed to delete PN session');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
catch (migrationError) {
|
|
331
|
+
logger.warn({ jid, error: migrationError }, 'Failed to migrate device session');
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
// Process each user group for potential bulk LID migration
|
|
335
|
+
for (const [user, userJids] of userGroups) {
|
|
336
|
+
const mappingResult = await checkUserLidMapping(user, userJids);
|
|
337
|
+
const shouldMigrateUser = mappingResult.shouldMigrate;
|
|
338
|
+
const lidForPN = mappingResult.lidForPN;
|
|
339
|
+
// Migrate all devices for this user if LID mapping exists
|
|
340
|
+
if (shouldMigrateUser && lidForPN) {
|
|
341
|
+
// Migrate each device individually
|
|
342
|
+
for (const jid of userJids) {
|
|
343
|
+
await migrateDeviceToLid(jid, lidForPN);
|
|
344
|
+
}
|
|
345
|
+
logger.info({
|
|
346
|
+
user,
|
|
347
|
+
lidMapping: lidForPN,
|
|
348
|
+
deviceCount: userJids.length
|
|
349
|
+
}, 'Completed migration attempt for user devices');
|
|
350
|
+
}
|
|
351
|
+
// Helper to check session for migrated user
|
|
352
|
+
const checkMigratedSession = async (jid) => {
|
|
353
|
+
const signalId = signalRepository.jidToSignalProtocolAddress(jid);
|
|
354
|
+
let hasSession = !!sessions[signalId];
|
|
355
|
+
let jidToFetch = jid;
|
|
356
|
+
// Check if we should use migrated LID session instead
|
|
357
|
+
if (shouldMigrateUser && lidForPN && jid.includes('@s.whatsapp.net')) {
|
|
358
|
+
const originalDecoded = jidDecode(jid);
|
|
359
|
+
const deviceId = originalDecoded?.device || 0;
|
|
360
|
+
const lidDecoded = jidDecode(lidForPN);
|
|
361
|
+
const lidWithDevice = jidEncode(lidDecoded?.user, 'lid', deviceId);
|
|
362
|
+
// Check if LID session exists
|
|
363
|
+
const lidSignalId = signalRepository.jidToSignalProtocolAddress(lidWithDevice);
|
|
364
|
+
const lidSessions = await authState.keys.get('session', [lidSignalId]);
|
|
365
|
+
hasSession = !!lidSessions[lidSignalId];
|
|
366
|
+
jidToFetch = lidWithDevice;
|
|
367
|
+
if (hasSession) {
|
|
368
|
+
logger.debug({ originalJid: jid, lidJid: lidWithDevice }, '✅ Found bulk-migrated LID session');
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// Add to fetch list if no session exists
|
|
372
|
+
if (!hasSession) {
|
|
373
|
+
jidsRequiringFetch.push(jidToFetch);
|
|
374
|
+
logger.debug({ jid: jidToFetch, originalJid: jid !== jidToFetch ? jid : undefined }, 'Adding to session fetch list');
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
// Now check which sessions need to be fetched for this user
|
|
378
|
+
for (const jid of userJids) {
|
|
379
|
+
await checkMigratedSession(jid);
|
|
171
380
|
}
|
|
172
381
|
}
|
|
173
382
|
}
|
|
174
383
|
if (jidsRequiringFetch.length) {
|
|
175
384
|
logger.debug({ jidsRequiringFetch }, 'fetching sessions');
|
|
385
|
+
// DEBUG: Check if there are PN versions of LID users being fetched
|
|
386
|
+
const lidUsersBeingFetched = new Set();
|
|
387
|
+
const pnUsersBeingFetched = new Set();
|
|
388
|
+
for (const jid of jidsRequiringFetch) {
|
|
389
|
+
const user = jidDecode(jid)?.user;
|
|
390
|
+
if (user) {
|
|
391
|
+
if (jid.includes('@lid')) {
|
|
392
|
+
lidUsersBeingFetched.add(user);
|
|
393
|
+
}
|
|
394
|
+
else if (jid.includes('@s.whatsapp.net')) {
|
|
395
|
+
pnUsersBeingFetched.add(user);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// Find overlaps
|
|
400
|
+
const overlapping = Array.from(pnUsersBeingFetched).filter(user => lidUsersBeingFetched.has(user));
|
|
401
|
+
if (overlapping.length > 0) {
|
|
402
|
+
logger.warn({
|
|
403
|
+
overlapping,
|
|
404
|
+
lidUsersBeingFetched: Array.from(lidUsersBeingFetched),
|
|
405
|
+
pnUsersBeingFetched: Array.from(pnUsersBeingFetched)
|
|
406
|
+
}, 'Fetching both LID and PN sessions for same users');
|
|
407
|
+
}
|
|
176
408
|
const result = await query({
|
|
177
409
|
tag: 'iq',
|
|
178
410
|
attrs: {
|
|
@@ -216,43 +448,132 @@ export const makeMessagesSocket = (config) => {
|
|
|
216
448
|
});
|
|
217
449
|
return msgId;
|
|
218
450
|
};
|
|
219
|
-
const createParticipantNodes = async (jids, message, extraAttrs) => {
|
|
451
|
+
const createParticipantNodes = async (jids, message, extraAttrs, dsmMessage) => {
|
|
220
452
|
let patched = await patchMessageBeforeSending(message, jids);
|
|
221
453
|
if (!Array.isArray(patched)) {
|
|
222
454
|
patched = jids ? jids.map(jid => ({ recipientJid: jid, ...patched })) : [patched];
|
|
223
455
|
}
|
|
224
456
|
let shouldIncludeDeviceIdentity = false;
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
457
|
+
const meId = authState.creds.me.id;
|
|
458
|
+
const meLid = authState.creds.me?.lid;
|
|
459
|
+
const meLidUser = meLid ? jidDecode(meLid)?.user : null;
|
|
460
|
+
const devicesByUser = new Map();
|
|
461
|
+
for (const patchedMessageWithJid of patched) {
|
|
462
|
+
const { recipientJid: wireJid, ...patchedMessage } = patchedMessageWithJid;
|
|
463
|
+
if (!wireJid)
|
|
464
|
+
continue;
|
|
465
|
+
// Extract user from JID for grouping
|
|
466
|
+
const decoded = jidDecode(wireJid);
|
|
467
|
+
const user = decoded?.user;
|
|
468
|
+
if (!user)
|
|
469
|
+
continue;
|
|
470
|
+
if (!devicesByUser.has(user)) {
|
|
471
|
+
devicesByUser.set(user, []);
|
|
472
|
+
}
|
|
473
|
+
devicesByUser.get(user).push({ recipientJid: wireJid, patchedMessage });
|
|
474
|
+
}
|
|
475
|
+
// Process each user's devices sequentially, but different users in parallel
|
|
476
|
+
const userEncryptionPromises = Array.from(devicesByUser.entries()).map(([user, userDevices]) => encryptionMutex.mutex(user, async () => {
|
|
477
|
+
logger.debug({ user, deviceCount: userDevices.length }, 'Acquiring encryption lock for user devices');
|
|
478
|
+
const userNodes = [];
|
|
479
|
+
// Helper to get encryption JID with LID migration
|
|
480
|
+
const getEncryptionJid = async (wireJid) => {
|
|
481
|
+
if (!wireJid.includes('@s.whatsapp.net'))
|
|
482
|
+
return wireJid;
|
|
483
|
+
try {
|
|
484
|
+
const lidMapping = signalRepository.getLIDMappingStore();
|
|
485
|
+
const lidForPN = await lidMapping.getLIDForPN(wireJid);
|
|
486
|
+
if (!lidForPN?.includes('@lid'))
|
|
487
|
+
return wireJid;
|
|
488
|
+
// Preserve device ID from original wire JID
|
|
489
|
+
const wireDecoded = jidDecode(wireJid);
|
|
490
|
+
const deviceId = wireDecoded?.device || 0;
|
|
491
|
+
const lidDecoded = jidDecode(lidForPN);
|
|
492
|
+
const lidWithDevice = jidEncode(lidDecoded?.user, 'lid', deviceId);
|
|
493
|
+
// Migrate session to LID for unified encryption layer
|
|
494
|
+
try {
|
|
495
|
+
await signalRepository.migrateSession(wireJid, lidWithDevice);
|
|
496
|
+
const recipientUser = jidNormalizedUser(wireJid);
|
|
497
|
+
const ownPnUser = jidNormalizedUser(meId);
|
|
498
|
+
const isOwnDevice = recipientUser === ownPnUser;
|
|
499
|
+
logger.info({ wireJid, lidWithDevice, isOwnDevice }, 'Migrated to LID encryption');
|
|
500
|
+
// Delete PN session after successful migration
|
|
501
|
+
try {
|
|
502
|
+
await signalRepository.deleteSession(wireJid);
|
|
503
|
+
logger.debug({ deletedPNSession: wireJid }, 'Deleted PN session');
|
|
504
|
+
}
|
|
505
|
+
catch (deleteError) {
|
|
506
|
+
logger.warn({ wireJid, error: deleteError }, 'Failed to delete PN session');
|
|
507
|
+
}
|
|
508
|
+
return lidWithDevice;
|
|
247
509
|
}
|
|
248
|
-
|
|
510
|
+
catch (migrationError) {
|
|
511
|
+
logger.warn({ wireJid, error: migrationError }, 'Failed to migrate session');
|
|
512
|
+
return wireJid;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
catch (error) {
|
|
516
|
+
logger.debug({ wireJid, error }, 'Failed to check LID mapping');
|
|
517
|
+
return wireJid;
|
|
518
|
+
}
|
|
249
519
|
};
|
|
250
|
-
|
|
520
|
+
// Encrypt to this user's devices sequentially to prevent session corruption
|
|
521
|
+
for (const { recipientJid: wireJid, patchedMessage } of userDevices) {
|
|
522
|
+
// DSM logic: Use DSM for own other devices (following whatsmeow implementation)
|
|
523
|
+
let messageToEncrypt = patchedMessage;
|
|
524
|
+
if (dsmMessage) {
|
|
525
|
+
const { user: targetUser } = jidDecode(wireJid);
|
|
526
|
+
const { user: ownPnUser } = jidDecode(meId);
|
|
527
|
+
const ownLidUser = meLidUser;
|
|
528
|
+
// Check if this is our device (same user, different device)
|
|
529
|
+
const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser);
|
|
530
|
+
// Exclude exact sender device (whatsmeow: if jid == ownJID || jid == ownLID { continue })
|
|
531
|
+
const isExactSenderDevice = wireJid === meId || (authState.creds.me?.lid && wireJid === authState.creds.me.lid);
|
|
532
|
+
if (isOwnUser && !isExactSenderDevice) {
|
|
533
|
+
messageToEncrypt = dsmMessage;
|
|
534
|
+
logger.debug({ wireJid, targetUser }, 'Using DSM for own device');
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
const bytes = encodeWAMessage(messageToEncrypt);
|
|
538
|
+
// Get encryption JID with LID migration
|
|
539
|
+
const encryptionJid = await getEncryptionJid(wireJid);
|
|
540
|
+
// ENCRYPT: Use the determined encryption identity (prefers migrated LID)
|
|
541
|
+
const { type, ciphertext } = await signalRepository.encryptMessage({
|
|
542
|
+
jid: encryptionJid, // Unified encryption layer (LID when available)
|
|
543
|
+
data: bytes
|
|
544
|
+
});
|
|
545
|
+
if (type === 'pkmsg') {
|
|
546
|
+
shouldIncludeDeviceIdentity = true;
|
|
547
|
+
}
|
|
548
|
+
const node = {
|
|
549
|
+
tag: 'to',
|
|
550
|
+
attrs: { jid: wireJid }, // Always use original wire identity in envelope
|
|
551
|
+
content: [
|
|
552
|
+
{
|
|
553
|
+
tag: 'enc',
|
|
554
|
+
attrs: {
|
|
555
|
+
v: '2',
|
|
556
|
+
type,
|
|
557
|
+
...(extraAttrs || {})
|
|
558
|
+
},
|
|
559
|
+
content: ciphertext
|
|
560
|
+
}
|
|
561
|
+
]
|
|
562
|
+
};
|
|
563
|
+
userNodes.push(node);
|
|
564
|
+
}
|
|
565
|
+
logger.debug({ user, nodesCreated: userNodes.length }, 'Releasing encryption lock for user devices');
|
|
566
|
+
return userNodes;
|
|
251
567
|
}));
|
|
568
|
+
// Wait for all users to complete (users are processed in parallel)
|
|
569
|
+
const userNodesArrays = await Promise.all(userEncryptionPromises);
|
|
570
|
+
const nodes = userNodesArrays.flat();
|
|
252
571
|
return { nodes, shouldIncludeDeviceIdentity };
|
|
253
572
|
};
|
|
254
573
|
const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, statusJidList }) => {
|
|
255
574
|
const meId = authState.creds.me.id;
|
|
575
|
+
const meLid = authState.creds.me?.lid;
|
|
576
|
+
// ADDRESSING CONSISTENCY: Keep envelope addressing as user provided, handle LID migration in encryption
|
|
256
577
|
let shouldIncludeDeviceIdentity = false;
|
|
257
578
|
const { user, server } = jidDecode(jid);
|
|
258
579
|
const statusJid = 'status@broadcast';
|
|
@@ -260,11 +581,22 @@ export const makeMessagesSocket = (config) => {
|
|
|
260
581
|
const isStatus = jid === statusJid;
|
|
261
582
|
const isLid = server === 'lid';
|
|
262
583
|
const isNewsletter = server === 'newsletter';
|
|
584
|
+
// Keep user's original JID choice for envelope addressing
|
|
585
|
+
const finalJid = jid;
|
|
586
|
+
// ADDRESSING CONSISTENCY: Match own identity to conversation context
|
|
587
|
+
let ownId = meId;
|
|
588
|
+
if (isLid && meLid) {
|
|
589
|
+
ownId = meLid;
|
|
590
|
+
logger.debug({ to: jid, ownId }, 'Using LID identity for @lid conversation');
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
logger.debug({ to: jid, ownId }, 'Using PN identity for @s.whatsapp.net conversation');
|
|
594
|
+
}
|
|
263
595
|
msgId = msgId || generateMessageIDV2(sock.user?.id);
|
|
264
596
|
useUserDevicesCache = useUserDevicesCache !== false;
|
|
265
597
|
useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus;
|
|
266
598
|
const participants = [];
|
|
267
|
-
const destinationJid = !isStatus ?
|
|
599
|
+
const destinationJid = !isStatus ? finalJid : statusJid;
|
|
268
600
|
const binaryNodeContent = [];
|
|
269
601
|
const devices = [];
|
|
270
602
|
const meMsg = {
|
|
@@ -283,7 +615,11 @@ export const makeMessagesSocket = (config) => {
|
|
|
283
615
|
additionalAttributes = { ...additionalAttributes, device_fanout: 'false' };
|
|
284
616
|
}
|
|
285
617
|
const { user, device } = jidDecode(participant.jid);
|
|
286
|
-
devices.push({
|
|
618
|
+
devices.push({
|
|
619
|
+
user,
|
|
620
|
+
device,
|
|
621
|
+
wireJid: participant.jid // Use the participant JID as wire JID
|
|
622
|
+
});
|
|
287
623
|
}
|
|
288
624
|
await authState.keys.transaction(async () => {
|
|
289
625
|
const mediaType = getMediaType(message);
|
|
@@ -342,9 +678,10 @@ export const makeMessagesSocket = (config) => {
|
|
|
342
678
|
participantsList.push(...statusJidList);
|
|
343
679
|
}
|
|
344
680
|
if (!isStatus) {
|
|
681
|
+
const groupAddressingMode = groupData?.addressingMode || (isLid ? 'lid' : 'pn');
|
|
345
682
|
additionalAttributes = {
|
|
346
683
|
...additionalAttributes,
|
|
347
|
-
addressing_mode:
|
|
684
|
+
addressing_mode: groupAddressingMode
|
|
348
685
|
};
|
|
349
686
|
}
|
|
350
687
|
const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false);
|
|
@@ -355,19 +692,24 @@ export const makeMessagesSocket = (config) => {
|
|
|
355
692
|
throw new Boom('Per-jid patching is not supported in groups');
|
|
356
693
|
}
|
|
357
694
|
const bytes = encodeWAMessage(patched);
|
|
695
|
+
// This should match the group's addressing mode and conversation context
|
|
696
|
+
const groupAddressingMode = groupData?.addressingMode || (isLid ? 'lid' : 'pn');
|
|
697
|
+
const groupSenderIdentity = groupAddressingMode === 'lid' && meLid ? meLid : meId;
|
|
358
698
|
const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
|
|
359
699
|
group: destinationJid,
|
|
360
700
|
data: bytes,
|
|
361
|
-
meId
|
|
701
|
+
meId: groupSenderIdentity
|
|
362
702
|
});
|
|
363
703
|
const senderKeyJids = [];
|
|
364
704
|
// ensure a connection is established with every device
|
|
365
|
-
for (const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
705
|
+
for (const device of devices) {
|
|
706
|
+
// This preserves the LID migration results from getUSyncDevices
|
|
707
|
+
const deviceJid = device.wireJid;
|
|
708
|
+
const hasKey = !!senderKeyMap[deviceJid];
|
|
709
|
+
if (!hasKey || !!participant) {
|
|
710
|
+
senderKeyJids.push(deviceJid);
|
|
369
711
|
// store that this person has had the sender keys sent to them
|
|
370
|
-
senderKeyMap[
|
|
712
|
+
senderKeyMap[deviceJid] = true;
|
|
371
713
|
}
|
|
372
714
|
}
|
|
373
715
|
// if there are some participants with whom the session has not been established
|
|
@@ -393,23 +735,54 @@ export const makeMessagesSocket = (config) => {
|
|
|
393
735
|
await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } });
|
|
394
736
|
}
|
|
395
737
|
else {
|
|
396
|
-
const { user:
|
|
738
|
+
const { user: ownUser } = jidDecode(ownId);
|
|
397
739
|
if (!participant) {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
740
|
+
const targetUserServer = isLid ? 'lid' : 's.whatsapp.net';
|
|
741
|
+
devices.push({
|
|
742
|
+
user,
|
|
743
|
+
device: 0,
|
|
744
|
+
wireJid: jidEncode(user, targetUserServer, 0)
|
|
745
|
+
});
|
|
746
|
+
// Own user matches conversation addressing mode
|
|
747
|
+
if (user !== ownUser) {
|
|
748
|
+
const ownUserServer = isLid ? 'lid' : 's.whatsapp.net';
|
|
749
|
+
const ownUserForAddressing = isLid && meLid ? jidDecode(meLid).user : jidDecode(meId).user;
|
|
750
|
+
devices.push({
|
|
751
|
+
user: ownUserForAddressing,
|
|
752
|
+
device: 0,
|
|
753
|
+
wireJid: jidEncode(ownUserForAddressing, ownUserServer, 0)
|
|
754
|
+
});
|
|
401
755
|
}
|
|
402
756
|
if (additionalAttributes?.['category'] !== 'peer') {
|
|
403
|
-
|
|
404
|
-
devices.
|
|
757
|
+
// Clear placeholders and enumerate actual devices
|
|
758
|
+
devices.length = 0;
|
|
759
|
+
// Use conversation-appropriate sender identity
|
|
760
|
+
const senderIdentity = isLid && meLid
|
|
761
|
+
? jidEncode(jidDecode(meLid)?.user, 'lid', undefined)
|
|
762
|
+
: jidEncode(jidDecode(meId)?.user, 's.whatsapp.net', undefined);
|
|
763
|
+
// Enumerate devices for sender and target with consistent addressing
|
|
764
|
+
const sessionDevices = await getUSyncDevices([senderIdentity, jid], false, false);
|
|
765
|
+
devices.push(...sessionDevices);
|
|
766
|
+
logger.debug({
|
|
767
|
+
deviceCount: devices.length,
|
|
768
|
+
devices: devices.map(d => `${d.user}:${d.device}@${jidDecode(d.wireJid)?.server}`)
|
|
769
|
+
}, 'Device enumeration complete with unified addressing');
|
|
405
770
|
}
|
|
406
771
|
}
|
|
407
772
|
const allJids = [];
|
|
408
773
|
const meJids = [];
|
|
409
774
|
const otherJids = [];
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
775
|
+
const { user: mePnUser } = jidDecode(meId);
|
|
776
|
+
const { user: meLidUser } = meLid ? jidDecode(meLid) : { user: null };
|
|
777
|
+
for (const { user, wireJid } of devices) {
|
|
778
|
+
const isExactSenderDevice = wireJid === meId || (meLid && wireJid === meLid);
|
|
779
|
+
if (isExactSenderDevice) {
|
|
780
|
+
logger.debug({ wireJid, meId, meLid }, 'Skipping exact sender device (whatsmeow pattern)');
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
// Check if this is our device (could match either PN or LID user)
|
|
784
|
+
const isMe = user === mePnUser || (meLidUser && user === meLidUser);
|
|
785
|
+
const jid = wireJid;
|
|
413
786
|
if (isMe) {
|
|
414
787
|
meJids.push(jid);
|
|
415
788
|
}
|
|
@@ -418,10 +791,11 @@ export const makeMessagesSocket = (config) => {
|
|
|
418
791
|
}
|
|
419
792
|
allJids.push(jid);
|
|
420
793
|
}
|
|
421
|
-
await assertSessions(
|
|
794
|
+
await assertSessions([...otherJids, ...meJids], false);
|
|
422
795
|
const [{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }] = await Promise.all([
|
|
423
|
-
|
|
424
|
-
createParticipantNodes(
|
|
796
|
+
// For own devices: use DSM if available (1:1 chats only)
|
|
797
|
+
createParticipantNodes(meJids, meMsg || message, extraAttrs),
|
|
798
|
+
createParticipantNodes(otherJids, message, extraAttrs, meMsg)
|
|
425
799
|
]);
|
|
426
800
|
participants.push(...meNodes);
|
|
427
801
|
participants.push(...otherNodes);
|
|
@@ -446,6 +820,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
446
820
|
tag: 'message',
|
|
447
821
|
attrs: {
|
|
448
822
|
id: msgId,
|
|
823
|
+
to: destinationJid,
|
|
449
824
|
type: getMessageType(message),
|
|
450
825
|
...(additionalAttributes || {})
|
|
451
826
|
},
|
|
@@ -492,6 +867,9 @@ export const makeMessagesSocket = (config) => {
|
|
|
492
867
|
if (message.pollCreationMessage || message.pollCreationMessageV2 || message.pollCreationMessageV3) {
|
|
493
868
|
return 'poll';
|
|
494
869
|
}
|
|
870
|
+
if (message.eventMessage) {
|
|
871
|
+
return 'event';
|
|
872
|
+
}
|
|
495
873
|
return 'text';
|
|
496
874
|
};
|
|
497
875
|
const getMediaType = (message) => {
|
|
@@ -654,12 +1032,14 @@ export const makeMessagesSocket = (config) => {
|
|
|
654
1032
|
}),
|
|
655
1033
|
//TODO: CACHE
|
|
656
1034
|
getProfilePicUrl: sock.profilePictureUrl,
|
|
1035
|
+
getCallLink: sock.createCallLink,
|
|
657
1036
|
upload: waUploadToServer,
|
|
658
1037
|
mediaCache: config.mediaCache,
|
|
659
1038
|
options: config.options,
|
|
660
1039
|
messageId: generateMessageIDV2(sock.user?.id),
|
|
661
1040
|
...options
|
|
662
1041
|
});
|
|
1042
|
+
const isEventMsg = 'event' in content && !!content.event;
|
|
663
1043
|
const isDeleteMsg = 'delete' in content && !!content.delete;
|
|
664
1044
|
const isEditMsg = 'edit' in content && !!content.edit;
|
|
665
1045
|
const isPinMsg = 'pin' in content && !!content.pin;
|
|
@@ -690,6 +1070,14 @@ export const makeMessagesSocket = (config) => {
|
|
|
690
1070
|
}
|
|
691
1071
|
});
|
|
692
1072
|
}
|
|
1073
|
+
else if (isEventMsg) {
|
|
1074
|
+
additionalNodes.push({
|
|
1075
|
+
tag: 'meta',
|
|
1076
|
+
attrs: {
|
|
1077
|
+
event_type: 'creation'
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
693
1081
|
if ('cachedGroupMetadata' in options) {
|
|
694
1082
|
console.warn('cachedGroupMetadata in sendMessage are deprecated, now cachedGroupMetadata is part of the socket config.');
|
|
695
1083
|
}
|