@periskope/baileys 6.7.18-17-3 → 6.7.18-17-4

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.
@@ -35,15 +35,24 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.makeLibSignalRepository = makeLibSignalRepository;
37
37
  const libsignal = __importStar(require("libsignal"));
38
+ /* @ts-ignore */
39
+ const lru_cache_1 = require("lru-cache");
38
40
  const Utils_1 = require("../Utils");
39
41
  const WABinary_1 = require("../WABinary");
40
42
  const sender_key_name_1 = require("./Group/sender-key-name");
41
43
  const sender_key_record_1 = require("./Group/sender-key-record");
42
44
  const Group_1 = require("./Group");
45
+ const lid_mapping_1 = require("./lid-mapping");
43
46
  function makeLibSignalRepository(auth) {
44
- const storage = signalStorage(auth);
47
+ const lidMapping = new lid_mapping_1.LIDMappingStore(auth.keys);
48
+ const storage = signalStorage(auth, lidMapping);
49
+ // Simple operation-level deduplication (5 minutes)
50
+ const recentMigrations = new lru_cache_1.LRUCache({
51
+ max: 500,
52
+ ttl: 5 * 60 * 1000
53
+ });
45
54
  const parsedKeys = auth.keys;
46
- return {
55
+ const repository = {
47
56
  decryptGroupMessage({ group, authorJid, msg }) {
48
57
  const senderName = jidToSignalSenderKeyName(group, authorJid);
49
58
  const cipher = new Group_1.GroupCipher(storage, senderName);
@@ -92,7 +101,31 @@ function makeLibSignalRepository(auth) {
92
101
  });
93
102
  },
94
103
  async encryptMessage({ jid, data }) {
95
- const addr = jidToSignalProtocolAddress(jid);
104
+ // LID SINGLE SOURCE OF TRUTH: Always prefer LID when available
105
+ let encryptionJid = jid;
106
+ // Check for LID mapping and use it if session exists
107
+ if (jid.includes('@s.whatsapp.net')) {
108
+ const lidForPN = await lidMapping.getLIDForPN(jid);
109
+ if (lidForPN === null || lidForPN === void 0 ? void 0 : lidForPN.includes('@lid')) {
110
+ const lidAddr = jidToSignalProtocolAddress(lidForPN);
111
+ const { [lidAddr.toString()]: lidSession } = await auth.keys.get('session', [lidAddr.toString()]);
112
+ if (lidSession) {
113
+ // LID session exists, use it
114
+ encryptionJid = lidForPN;
115
+ }
116
+ else {
117
+ // Try to migrate if PN session exists
118
+ const pnAddr = jidToSignalProtocolAddress(jid);
119
+ const { [pnAddr.toString()]: pnSession } = await auth.keys.get('session', [pnAddr.toString()]);
120
+ if (pnSession) {
121
+ // Migrate PN to LID
122
+ await repository.migrateSession(jid, lidForPN);
123
+ encryptionJid = lidForPN;
124
+ }
125
+ }
126
+ }
127
+ }
128
+ const addr = jidToSignalProtocolAddress(encryptionJid);
96
129
  const cipher = new libsignal.SessionCipher(storage, addr);
97
130
  // Use transaction to ensure atomicity
98
131
  return parsedKeys.transaction(async () => {
@@ -127,23 +160,128 @@ function makeLibSignalRepository(auth) {
127
160
  },
128
161
  jidToSignalProtocolAddress(jid) {
129
162
  return jidToSignalProtocolAddress(jid).toString();
163
+ },
164
+ async storeLIDPNMapping(lid, pn) {
165
+ await lidMapping.storeLIDPNMapping(lid, pn);
166
+ },
167
+ getLIDMappingStore() {
168
+ return lidMapping;
169
+ },
170
+ async validateSession(jid) {
171
+ try {
172
+ const addr = jidToSignalProtocolAddress(jid);
173
+ const session = await storage.loadSession(addr.toString());
174
+ if (!session) {
175
+ return { exists: false, reason: 'no session' };
176
+ }
177
+ if (!session.haveOpenSession()) {
178
+ return { exists: false, reason: 'no open session' };
179
+ }
180
+ return { exists: true };
181
+ }
182
+ catch (error) {
183
+ return { exists: false, reason: 'validation error' };
184
+ }
185
+ },
186
+ async deleteSession(jid) {
187
+ const addr = jidToSignalProtocolAddress(jid);
188
+ return auth.keys.transaction(async () => {
189
+ await auth.keys.set({ session: { [addr.toString()]: null } });
190
+ });
191
+ },
192
+ async migrateSession(fromJid, toJid) {
193
+ // Only migrate PN → LID
194
+ if (!fromJid.includes('@s.whatsapp.net') || !toJid.includes('@lid')) {
195
+ return;
196
+ }
197
+ const fromDecoded = (0, WABinary_1.jidDecode)(fromJid);
198
+ const toDecoded = (0, WABinary_1.jidDecode)(toJid);
199
+ if (!fromDecoded || !toDecoded)
200
+ return;
201
+ const deviceId = fromDecoded.device || 0;
202
+ const migrationKey = `${fromDecoded.user}.${deviceId}→${toDecoded.user}.${deviceId}`;
203
+ // Check if recently migrated (5 min window)
204
+ if (recentMigrations.has(migrationKey)) {
205
+ return;
206
+ }
207
+ // Check if LID session already exists
208
+ const lidAddr = jidToSignalProtocolAddress(toJid);
209
+ const { [lidAddr.toString()]: lidExists } = await auth.keys.get('session', [lidAddr.toString()]);
210
+ if (lidExists) {
211
+ recentMigrations.set(migrationKey, true);
212
+ return;
213
+ }
214
+ return auth.keys.transaction(async () => {
215
+ // Store mapping
216
+ await lidMapping.storeLIDPNMapping(toJid, fromJid);
217
+ // Load and copy session
218
+ const fromAddr = jidToSignalProtocolAddress(fromJid);
219
+ const fromSession = await storage.loadSession(fromAddr.toString());
220
+ if (fromSession === null || fromSession === void 0 ? void 0 : fromSession.haveOpenSession()) {
221
+ // Deep copy session to prevent reference issues
222
+ const sessionBytes = fromSession.serialize();
223
+ const copiedSession = libsignal.SessionRecord.deserialize(sessionBytes);
224
+ // Store at LID address
225
+ await storage.storeSession(lidAddr.toString(), copiedSession);
226
+ // Delete PN session - maintain single encryption layer
227
+ await auth.keys.set({ session: { [fromAddr.toString()]: null } });
228
+ }
229
+ recentMigrations.set(migrationKey, true);
230
+ });
231
+ },
232
+ async encryptMessageWithWire({ encryptionJid, wireJid, data }) {
233
+ const result = await repository.encryptMessage({ jid: encryptionJid, data });
234
+ return { ...result, wireJid };
235
+ },
236
+ destroy() {
237
+ recentMigrations.clear();
130
238
  }
131
239
  };
240
+ return repository;
132
241
  }
133
242
  const jidToSignalProtocolAddress = (jid) => {
134
- const { user, device } = (0, WABinary_1.jidDecode)(jid);
135
- return new libsignal.ProtocolAddress(user, device || 0);
243
+ const decoded = (0, WABinary_1.jidDecode)(jid);
244
+ const { user, device, server } = decoded;
245
+ // LID addresses get _1 suffix for Signal protocol
246
+ const signalUser = server === 'lid' ? `${user}_1` : user;
247
+ const finalDevice = device || 0;
248
+ return new libsignal.ProtocolAddress(signalUser, finalDevice);
136
249
  };
137
250
  const jidToSignalSenderKeyName = (group, user) => {
138
251
  return new sender_key_name_1.SenderKeyName(group, jidToSignalProtocolAddress(user));
139
252
  };
140
- function signalStorage({ creds, keys }) {
253
+ function signalStorage({ creds, keys }, lidMapping) {
141
254
  return {
142
255
  loadSession: async (id) => {
143
- const { [id]: sess } = await keys.get('session', [id]);
144
- if (sess) {
145
- return libsignal.SessionRecord.deserialize(sess);
256
+ try {
257
+ // LID SINGLE SOURCE OF TRUTH: Auto-redirect PN to LID if mapping exists
258
+ let actualId = id;
259
+ if (id.includes('.') && !id.includes('_1')) {
260
+ // This is a PN signal address format (e.g., "1234567890.0")
261
+ // Convert back to JID to check for LID mapping
262
+ const parts = id.split('.');
263
+ const device = parts[1] || '0';
264
+ const pnJid = device === '0' ? `${parts[0]}@s.whatsapp.net` : `${parts[0]}:${device}@s.whatsapp.net`;
265
+ const lidForPN = await lidMapping.getLIDForPN(pnJid);
266
+ if (lidForPN === null || lidForPN === void 0 ? void 0 : lidForPN.includes('@lid')) {
267
+ const lidAddr = jidToSignalProtocolAddress(lidForPN);
268
+ const lidId = lidAddr.toString();
269
+ // Check if LID session exists
270
+ const { [lidId]: lidSession } = await keys.get('session', [lidId]);
271
+ if (lidSession) {
272
+ actualId = lidId;
273
+ }
274
+ }
275
+ }
276
+ const { [actualId]: sess } = await keys.get('session', [actualId]);
277
+ if (sess) {
278
+ return libsignal.SessionRecord.deserialize(sess);
279
+ }
280
+ }
281
+ catch (e) {
282
+ return null;
146
283
  }
284
+ return null;
147
285
  },
148
286
  // TODO: Replace with libsignal.SessionRecord when type exports are added to libsignal
149
287
  storeSession: async (id, session) => {
@@ -0,0 +1,17 @@
1
+ import type { SignalKeyStoreWithTransaction } from '../Types';
2
+ export declare class LIDMappingStore {
3
+ private readonly keys;
4
+ constructor(keys: SignalKeyStoreWithTransaction);
5
+ /**
6
+ * Store LID-PN mapping - USER LEVEL
7
+ */
8
+ storeLIDPNMapping(lid: string, pn: string): Promise<void>;
9
+ /**
10
+ * Get LID for PN - Returns device-specific LID based on user mapping
11
+ */
12
+ getLIDForPN(pn: string): Promise<string | null>;
13
+ /**
14
+ * Get PN for LID - USER LEVEL with device construction
15
+ */
16
+ getPNForLID(lid: string): Promise<string | null>;
17
+ }
@@ -0,0 +1,89 @@
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.LIDMappingStore = void 0;
7
+ const logger_1 = __importDefault(require("../Utils/logger"));
8
+ const WABinary_1 = require("../WABinary");
9
+ class LIDMappingStore {
10
+ constructor(keys) {
11
+ this.keys = keys;
12
+ }
13
+ /**
14
+ * Store LID-PN mapping - USER LEVEL
15
+ */
16
+ async storeLIDPNMapping(lid, pn) {
17
+ // Validate inputs
18
+ if (!(((0, WABinary_1.isLidUser)(lid) && (0, WABinary_1.isJidUser)(pn)) || ((0, WABinary_1.isJidUser)(lid) && (0, WABinary_1.isLidUser)(pn)))) {
19
+ logger_1.default.warn(`Invalid LID-PN mapping: ${lid}, ${pn}`);
20
+ return;
21
+ }
22
+ const [lidJid, pnJid] = (0, WABinary_1.isLidUser)(lid) ? [lid, pn] : [pn, lid];
23
+ const lidDecoded = (0, WABinary_1.jidDecode)(lidJid);
24
+ const pnDecoded = (0, WABinary_1.jidDecode)(pnJid);
25
+ if (!lidDecoded || !pnDecoded)
26
+ return;
27
+ const pnUser = pnDecoded.user;
28
+ const lidUser = lidDecoded.user;
29
+ logger_1.default.trace(`Storing USER LID mapping: PN ${pnUser} → LID ${lidUser}`);
30
+ await this.keys.transaction(async () => {
31
+ await this.keys.set({
32
+ 'lid-mapping': {
33
+ [pnUser]: lidUser, // "554396160286" -> "102765716062358"
34
+ [`${lidUser}_reverse`]: pnUser // "102765716062358_reverse" -> "554396160286"
35
+ }
36
+ });
37
+ });
38
+ logger_1.default.trace(`USER LID mapping stored: PN ${pnUser} → LID ${lidUser}`);
39
+ }
40
+ /**
41
+ * Get LID for PN - Returns device-specific LID based on user mapping
42
+ */
43
+ async getLIDForPN(pn) {
44
+ if (!(0, WABinary_1.isJidUser)(pn))
45
+ return null;
46
+ const decoded = (0, WABinary_1.jidDecode)(pn);
47
+ if (!decoded)
48
+ return null;
49
+ // Look up user-level mapping (whatsmeow approach)
50
+ const pnUser = decoded.user;
51
+ const stored = await this.keys.get('lid-mapping', [pnUser]);
52
+ const lidUser = stored[pnUser];
53
+ if (!lidUser) {
54
+ logger_1.default.trace(`No LID mapping found for PN user ${pnUser}`);
55
+ return null;
56
+ }
57
+ if (typeof lidUser !== 'string')
58
+ return null;
59
+ // Push the PN device ID to the LID to maintain device separation
60
+ const pnDevice = decoded.device !== undefined ? decoded.device : 0;
61
+ const deviceSpecificLid = `${lidUser}:${pnDevice}@lid`;
62
+ logger_1.default.trace(`getLIDForPN: ${pn} → ${deviceSpecificLid} (user mapping with device ${pnDevice})`);
63
+ return deviceSpecificLid;
64
+ }
65
+ /**
66
+ * Get PN for LID - USER LEVEL with device construction
67
+ */
68
+ async getPNForLID(lid) {
69
+ if (!(0, WABinary_1.isLidUser)(lid))
70
+ return null;
71
+ const decoded = (0, WABinary_1.jidDecode)(lid);
72
+ if (!decoded)
73
+ return null;
74
+ // Look up reverse user mapping
75
+ const lidUser = decoded.user;
76
+ const stored = await this.keys.get('lid-mapping', [`${lidUser}_reverse`]);
77
+ const pnUser = stored[`${lidUser}_reverse`];
78
+ if (!pnUser || typeof pnUser !== 'string') {
79
+ logger_1.default.trace(`No reverse mapping found for LID user: ${lidUser}`);
80
+ return null;
81
+ }
82
+ // Construct device-specific PN JID
83
+ const lidDevice = decoded.device !== undefined ? decoded.device : 0;
84
+ const pnJid = `${pnUser}:${lidDevice}@s.whatsapp.net`;
85
+ logger_1.default.trace(`Found reverse mapping: ${lid} → ${pnJid}`);
86
+ return pnJid;
87
+ }
88
+ }
89
+ exports.LIDMappingStore = LIDMappingStore;
@@ -32,11 +32,13 @@ export declare const makeBusinessSocket: (config: SocketConfig) => {
32
32
  [_: string]: string;
33
33
  }>;
34
34
  sendPeerDataOperationMessage: (pdoMessage: import("../Types").WAProto.Message.IPeerDataOperationRequestMessage) => Promise<string>;
35
- createParticipantNodes: (jids: string[], message: import("../Types").WAProto.IMessage, extraAttrs?: BinaryNode["attrs"]) => Promise<{
35
+ createParticipantNodes: (jids: string[], message: import("../Types").WAProto.IMessage, extraAttrs?: BinaryNode["attrs"], dsmMessage?: import("../Types").WAProto.IMessage) => Promise<{
36
36
  nodes: BinaryNode[];
37
37
  shouldIncludeDeviceIdentity: boolean;
38
38
  }>;
39
- getUSyncDevices: (jids: string[], useCache: boolean, ignoreZeroDevices: boolean) => Promise<import("../WABinary").JidWithDevice[]>;
39
+ getUSyncDevices: (jids: string[], useCache: boolean, ignoreZeroDevices: boolean) => Promise<(import("../WABinary").JidWithDevice & {
40
+ wireJid: string;
41
+ })[]>;
40
42
  updateMediaMessage: (message: import("../Types").WAProto.IWebMessageInfo) => Promise<import("../Types").WAProto.IWebMessageInfo>;
41
43
  sendMessage: (jid: string, content: import("../Types").AnyMessageContent, options?: import("../Types").MiscMessageGenerationOptions) => Promise<import("../Types").WAProto.WebMessageInfo | undefined>;
42
44
  newsletterCreate: (name: string, description?: string) => Promise<import("../Types").NewsletterMetadata>;
@@ -31,11 +31,13 @@ declare const makeWASocket: (config: UserFacingSocketConfig) => {
31
31
  [_: string]: string;
32
32
  }>;
33
33
  sendPeerDataOperationMessage: (pdoMessage: import("../Types").WAProto.Message.IPeerDataOperationRequestMessage) => Promise<string>;
34
- createParticipantNodes: (jids: string[], message: import("../Types").WAProto.IMessage, extraAttrs?: import("..").BinaryNode["attrs"]) => Promise<{
34
+ createParticipantNodes: (jids: string[], message: import("../Types").WAProto.IMessage, extraAttrs?: import("..").BinaryNode["attrs"], dsmMessage?: import("../Types").WAProto.IMessage) => Promise<{
35
35
  nodes: import("..").BinaryNode[];
36
36
  shouldIncludeDeviceIdentity: boolean;
37
37
  }>;
38
- getUSyncDevices: (jids: string[], useCache: boolean, ignoreZeroDevices: boolean) => Promise<import("..").JidWithDevice[]>;
38
+ getUSyncDevices: (jids: string[], useCache: boolean, ignoreZeroDevices: boolean) => Promise<(import("..").JidWithDevice & {
39
+ wireJid: string;
40
+ })[]>;
39
41
  updateMediaMessage: (message: import("../Types").WAProto.IWebMessageInfo) => Promise<import("../Types").WAProto.IWebMessageInfo>;
40
42
  sendMessage: (jid: string, content: import("../Types").AnyMessageContent, options?: import("../Types").MiscMessageGenerationOptions) => Promise<import("../Types").WAProto.WebMessageInfo | undefined>;
41
43
  newsletterCreate: (name: string, description?: string) => Promise<import("../Types").NewsletterMetadata>;
@@ -21,11 +21,13 @@ export declare const makeMessagesRecvSocket: (config: SocketConfig) => {
21
21
  [_: string]: string;
22
22
  }>;
23
23
  sendPeerDataOperationMessage: (pdoMessage: proto.Message.IPeerDataOperationRequestMessage) => Promise<string>;
24
- createParticipantNodes: (jids: string[], message: proto.IMessage, extraAttrs?: BinaryNode["attrs"]) => Promise<{
24
+ createParticipantNodes: (jids: string[], message: proto.IMessage, extraAttrs?: BinaryNode["attrs"], dsmMessage?: proto.IMessage) => Promise<{
25
25
  nodes: BinaryNode[];
26
26
  shouldIncludeDeviceIdentity: boolean;
27
27
  }>;
28
- getUSyncDevices: (jids: string[], useCache: boolean, ignoreZeroDevices: boolean) => Promise<import("../WABinary").JidWithDevice[]>;
28
+ getUSyncDevices: (jids: string[], useCache: boolean, ignoreZeroDevices: boolean) => Promise<(import("../WABinary").JidWithDevice & {
29
+ wireJid: string;
30
+ })[]>;
29
31
  updateMediaMessage: (message: proto.IWebMessageInfo) => Promise<proto.IWebMessageInfo>;
30
32
  sendMessage: (jid: string, content: import("../Types").AnyMessageContent, options?: import("../Types").MiscMessageGenerationOptions) => Promise<proto.WebMessageInfo | undefined>;
31
33
  newsletterCreate: (name: string, description?: string) => Promise<import("../Types").NewsletterMetadata>;
@@ -661,7 +661,7 @@ const makeMessagesRecvSocket = (config) => {
661
661
  }
662
662
  };
663
663
  const handleMessage = async (node) => {
664
- var _a, _b, _c;
664
+ var _a, _b, _c, _d, _e, _f, _g;
665
665
  if (shouldIgnoreJid(node.attrs.from) && node.attrs.from !== '@s.whatsapp.net') {
666
666
  logger.debug({ key: node.attrs.key }, 'ignored message');
667
667
  await sendMessageAck(node);
@@ -697,6 +697,36 @@ const makeMessagesRecvSocket = (config) => {
697
697
  node.attrs.sender_pn) {
698
698
  ev.emit('chats.phoneNumberShare', { lid: node.attrs.from, jid: node.attrs.sender_pn });
699
699
  }
700
+ if ((_f = (_e = (_d = msg.message) === null || _d === void 0 ? void 0 : _d.protocolMessage) === null || _e === void 0 ? void 0 : _e.lidMigrationMappingSyncMessage) === null || _f === void 0 ? void 0 : _f.encodedMappingPayload) {
701
+ try {
702
+ const payload = msg.message.protocolMessage.lidMigrationMappingSyncMessage.encodedMappingPayload;
703
+ const decoded = WAProto_1.proto.LIDMigrationMappingSyncPayload.decode(payload);
704
+ logger.debug({
705
+ mappingCount: ((_g = decoded.pnToLidMappings) === null || _g === void 0 ? void 0 : _g.length) || 0,
706
+ timestamp: decoded.chatDbMigrationTimestamp
707
+ }, 'Received LID migration sync message from server');
708
+ const lidMapping = signalRepository.getLIDMappingStore();
709
+ if (decoded.pnToLidMappings && decoded.pnToLidMappings.length > 0) {
710
+ for (const mapping of decoded.pnToLidMappings) {
711
+ const pn = `${mapping.pn}@s.whatsapp.net`;
712
+ // Use latestLid if available, otherwise assignedLid (proper LID refresh)
713
+ const lidValue = mapping.latestLid || mapping.assignedLid;
714
+ const lid = `${lidValue}@lid`;
715
+ await lidMapping.storeLIDPNMapping(lid, pn);
716
+ logger.debug({
717
+ pn,
718
+ lid,
719
+ assignedLid: mapping.assignedLid,
720
+ latestLid: mapping.latestLid,
721
+ usedLatest: !!mapping.latestLid
722
+ }, 'Stored server-provided PN-LID mapping');
723
+ }
724
+ }
725
+ }
726
+ catch (error) {
727
+ logger.error({ error }, 'Failed to process LID migration sync message');
728
+ }
729
+ }
700
730
  try {
701
731
  await Promise.all([
702
732
  processingMutex.mutex(async () => {
@@ -16,11 +16,13 @@ export declare const makeMessagesSocket: (config: SocketConfig) => {
16
16
  [_: string]: string;
17
17
  }>;
18
18
  sendPeerDataOperationMessage: (pdoMessage: proto.Message.IPeerDataOperationRequestMessage) => Promise<string>;
19
- createParticipantNodes: (jids: string[], message: proto.IMessage, extraAttrs?: BinaryNode["attrs"]) => Promise<{
19
+ createParticipantNodes: (jids: string[], message: proto.IMessage, extraAttrs?: BinaryNode["attrs"], dsmMessage?: proto.IMessage) => Promise<{
20
20
  nodes: BinaryNode[];
21
21
  shouldIncludeDeviceIdentity: boolean;
22
22
  }>;
23
- getUSyncDevices: (jids: string[], useCache: boolean, ignoreZeroDevices: boolean) => Promise<JidWithDevice[]>;
23
+ getUSyncDevices: (jids: string[], useCache: boolean, ignoreZeroDevices: boolean) => Promise<(JidWithDevice & {
24
+ wireJid: string;
25
+ })[]>;
24
26
  updateMediaMessage: (message: proto.IWebMessageInfo) => Promise<proto.IWebMessageInfo>;
25
27
  sendMessage: (jid: string, content: AnyMessageContent, options?: MiscMessageGenerationOptions) => Promise<proto.WebMessageInfo | undefined>;
26
28
  newsletterCreate: (name: string, description?: string) => Promise<import("../Types").NewsletterMetadata>;
@@ -10,6 +10,7 @@ const WAProto_1 = require("../../WAProto");
10
10
  const Defaults_1 = require("../Defaults");
11
11
  const Utils_1 = require("../Utils");
12
12
  const link_preview_1 = require("../Utils/link-preview");
13
+ const make_mutex_1 = require("../Utils/make-mutex");
13
14
  const WABinary_1 = require("../WABinary");
14
15
  const WAUSync_1 = require("../WAUSync");
15
16
  const groups_1 = require("./groups");
@@ -23,6 +24,8 @@ const makeMessagesSocket = (config) => {
23
24
  stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
24
25
  useClones: false
25
26
  });
27
+ // Prevent race conditions in Signal session encryption by user
28
+ const encryptionMutex = (0, make_mutex_1.makeKeyedMutex)();
26
29
  let mediaConn;
27
30
  const refreshMediaConn = async (forceGet = false) => {
28
31
  const media = await mediaConn;
@@ -111,22 +114,70 @@ const makeMessagesSocket = (config) => {
111
114
  const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self';
112
115
  await sendReceipts(keys, readType);
113
116
  };
117
+ /**
118
+ * Deduplicate JIDs when both LID and PN versions exist for same user
119
+ * Prefers LID over PN to maintain single encryption layer
120
+ */
121
+ const deduplicateLidPnJids = (jids) => {
122
+ var _a, _b;
123
+ const lidUsers = new Set();
124
+ const filteredJids = [];
125
+ // Collect all LID users
126
+ for (const jid of jids) {
127
+ if (jid.includes('@lid')) {
128
+ const user = (_a = (0, WABinary_1.jidDecode)(jid)) === null || _a === void 0 ? void 0 : _a.user;
129
+ if (user)
130
+ lidUsers.add(user);
131
+ }
132
+ }
133
+ // Filter out PN versions when LID exists
134
+ for (const jid of jids) {
135
+ if (jid.includes('@s.whatsapp.net')) {
136
+ const user = (_b = (0, WABinary_1.jidDecode)(jid)) === null || _b === void 0 ? void 0 : _b.user;
137
+ if (user && lidUsers.has(user)) {
138
+ logger.debug({ jid }, 'Skipping PN - LID version exists');
139
+ continue;
140
+ }
141
+ }
142
+ filteredJids.push(jid);
143
+ }
144
+ return filteredJids;
145
+ };
114
146
  /** Fetch all the devices we've to send a message to */
115
147
  const getUSyncDevices = async (jids, useCache, ignoreZeroDevices) => {
116
- var _a;
148
+ var _a, _b;
117
149
  const deviceResults = [];
118
150
  if (!useCache) {
119
151
  logger.debug('not using cache for devices');
120
152
  }
121
153
  const toFetch = [];
122
- jids = Array.from(new Set(jids));
154
+ // Deduplicate and normalize JIDs
155
+ jids = deduplicateLidPnJids(Array.from(new Set(jids)));
123
156
  for (let jid of jids) {
124
- const user = (_a = (0, WABinary_1.jidDecode)(jid)) === null || _a === void 0 ? void 0 : _a.user;
157
+ const decoded = (0, WABinary_1.jidDecode)(jid);
158
+ const user = decoded === null || decoded === void 0 ? void 0 : decoded.user;
159
+ const device = decoded === null || decoded === void 0 ? void 0 : decoded.device;
160
+ const isExplicitDevice = typeof device === 'number' && device >= 0;
161
+ // Handle explicit device JIDs directly
162
+ if (isExplicitDevice && user) {
163
+ deviceResults.push({
164
+ user,
165
+ device,
166
+ wireJid: jid // Preserve exact JID format for wire protocol
167
+ });
168
+ continue;
169
+ }
170
+ // For user JIDs, normalize and prepare for device enumeration
125
171
  jid = (0, WABinary_1.jidNormalizedUser)(jid);
126
172
  if (useCache) {
127
173
  const devices = userDevicesCache.get(user);
128
174
  if (devices) {
129
- deviceResults.push(...devices);
175
+ const isLidJid = jid.includes('@lid');
176
+ const devicesWithWire = devices.map(d => ({
177
+ ...d,
178
+ wireJid: isLidJid ? (0, WABinary_1.jidEncode)(d.user, 'lid', d.device) : (0, WABinary_1.jidEncode)(d.user, 's.whatsapp.net', d.device)
179
+ }));
180
+ deviceResults.push(...devicesWithWire);
130
181
  logger.trace({ user }, 'using cache for devices');
131
182
  }
132
183
  else {
@@ -140,6 +191,14 @@ const makeMessagesSocket = (config) => {
140
191
  if (!toFetch.length) {
141
192
  return deviceResults;
142
193
  }
194
+ const requestedLidUsers = new Set();
195
+ for (const jid of toFetch) {
196
+ if (jid.includes('@lid')) {
197
+ const user = (_a = (0, WABinary_1.jidDecode)(jid)) === null || _a === void 0 ? void 0 : _a.user;
198
+ if (user)
199
+ requestedLidUsers.add(user);
200
+ }
201
+ }
143
202
  const query = new WAUSync_1.USyncQuery().withContext('message').withDeviceProtocol();
144
203
  for (const jid of toFetch) {
145
204
  query.withUser(new WAUSync_1.USyncUser().withId(jid));
@@ -150,8 +209,27 @@ const makeMessagesSocket = (config) => {
150
209
  const deviceMap = {};
151
210
  for (const item of extracted) {
152
211
  deviceMap[item.user] = deviceMap[item.user] || [];
153
- deviceMap[item.user].push(item);
154
- deviceResults.push(item);
212
+ (_b = deviceMap[item.user]) === null || _b === void 0 ? void 0 : _b.push(item);
213
+ }
214
+ // Process each user's devices as a group for bulk LID migration
215
+ for (const [user, userDevices] of Object.entries(deviceMap)) {
216
+ const isLidUser = requestedLidUsers.has(user);
217
+ // Process all devices for this user
218
+ for (const item of userDevices) {
219
+ const finalWireJid = isLidUser
220
+ ? (0, WABinary_1.jidEncode)(user, 'lid', item.device)
221
+ : (0, WABinary_1.jidEncode)(item.user, 's.whatsapp.net', item.device);
222
+ deviceResults.push({
223
+ ...item,
224
+ wireJid: finalWireJid
225
+ });
226
+ logger.debug({
227
+ user: item.user,
228
+ device: item.device,
229
+ finalWireJid,
230
+ usedLid: isLidUser
231
+ }, 'Processed device with LID priority');
232
+ }
155
233
  }
156
234
  for (const key in deviceMap) {
157
235
  userDevicesCache.set(key, deviceMap[key]);
@@ -159,24 +237,180 @@ const makeMessagesSocket = (config) => {
159
237
  }
160
238
  return deviceResults;
161
239
  };
240
+ // Helper to check if JID has migrated LID session
241
+ const checkForMigratedLidSession = async (jid) => {
242
+ if (!jid.includes('@s.whatsapp.net'))
243
+ return false;
244
+ const lidMapping = signalRepository.getLIDMappingStore();
245
+ const lidForPN = await lidMapping.getLIDForPN(jid);
246
+ if (!(lidForPN === null || lidForPN === void 0 ? void 0 : lidForPN.includes('@lid')))
247
+ return false;
248
+ const lidSignalId = signalRepository.jidToSignalProtocolAddress(lidForPN);
249
+ const lidSessions = await authState.keys.get('session', [lidSignalId]);
250
+ return !!lidSessions[lidSignalId];
251
+ };
162
252
  const assertSessions = async (jids, force) => {
253
+ var _a;
163
254
  let didFetchNewSession = false;
164
- let jidsRequiringFetch = [];
255
+ const jidsRequiringFetch = [];
256
+ // Apply same deduplication as in getUSyncDevices
257
+ jids = deduplicateLidPnJids(jids);
165
258
  if (force) {
166
- jidsRequiringFetch = jids;
259
+ // Check which sessions are missing (with LID migration check)
260
+ const addrs = jids.map(jid => signalRepository.jidToSignalProtocolAddress(jid));
261
+ const sessions = await authState.keys.get('session', addrs);
262
+ // Helper to check session for a JID
263
+ const checkJidSession = async (jid) => {
264
+ const signalId = signalRepository.jidToSignalProtocolAddress(jid);
265
+ let hasSession = !!sessions[signalId];
266
+ // Check for migrated LID session if PN session missing
267
+ if (!hasSession) {
268
+ hasSession = await checkForMigratedLidSession(jid);
269
+ if (hasSession) {
270
+ logger.debug({ jid }, 'Found migrated LID session during force assert, skipping PN fetch');
271
+ }
272
+ }
273
+ // Add to fetch list if no session exists
274
+ if (!hasSession) {
275
+ if (jid.includes('@lid')) {
276
+ logger.debug({ jid }, 'No LID session found, will create new LID session');
277
+ }
278
+ jidsRequiringFetch.push(jid);
279
+ }
280
+ };
281
+ // Process all JIDs
282
+ for (const jid of jids) {
283
+ await checkJidSession(jid);
284
+ }
167
285
  }
168
286
  else {
287
+ const lidMapping = signalRepository.getLIDMappingStore();
169
288
  const addrs = jids.map(jid => signalRepository.jidToSignalProtocolAddress(jid));
170
289
  const sessions = await authState.keys.get('session', addrs);
290
+ // Group JIDs by user for bulk migration
291
+ const userGroups = new Map();
171
292
  for (const jid of jids) {
172
- const signalId = signalRepository.jidToSignalProtocolAddress(jid);
173
- if (!sessions[signalId]) {
174
- jidsRequiringFetch.push(jid);
293
+ const user = (0, WABinary_1.jidNormalizedUser)(jid);
294
+ if (!userGroups.has(user)) {
295
+ userGroups.set(user, []);
296
+ }
297
+ userGroups.get(user).push(jid);
298
+ }
299
+ // Helper to check LID mapping for a user
300
+ const checkUserLidMapping = async (user, userJids) => {
301
+ if (!userJids.some(jid => jid.includes('@s.whatsapp.net'))) {
302
+ return { shouldMigrate: false, lidForPN: undefined };
303
+ }
304
+ try {
305
+ const mapping = await lidMapping.getLIDForPN(user);
306
+ if (mapping === null || mapping === void 0 ? void 0 : mapping.includes('@lid')) {
307
+ logger.debug({ user, lidForPN: mapping, deviceCount: userJids.length }, 'User has LID mapping - preparing bulk migration');
308
+ return { shouldMigrate: true, lidForPN: mapping };
309
+ }
310
+ }
311
+ catch (error) {
312
+ logger.debug({ user, error }, 'Failed to check LID mapping for user');
313
+ }
314
+ return { shouldMigrate: false, lidForPN: undefined };
315
+ };
316
+ // Helper to migrate a single device
317
+ const migrateDeviceToLid = async (jid, lidForPN) => {
318
+ if (!jid.includes('@s.whatsapp.net'))
319
+ return;
320
+ try {
321
+ const jidDecoded = (0, WABinary_1.jidDecode)(jid);
322
+ const deviceId = (jidDecoded === null || jidDecoded === void 0 ? void 0 : jidDecoded.device) || 0;
323
+ const lidDecoded = (0, WABinary_1.jidDecode)(lidForPN);
324
+ const lidWithDevice = (0, WABinary_1.jidEncode)(lidDecoded === null || lidDecoded === void 0 ? void 0 : lidDecoded.user, 'lid', deviceId);
325
+ await signalRepository.migrateSession(jid, lidWithDevice);
326
+ logger.debug({ fromJid: jid, toJid: lidWithDevice }, 'Migrated device session to LID');
327
+ // Delete PN session after successful migration
328
+ try {
329
+ await signalRepository.deleteSession(jid);
330
+ logger.debug({ deletedPNSession: jid }, 'Deleted PN session after migration');
331
+ }
332
+ catch (deleteError) {
333
+ logger.warn({ jid, error: deleteError }, 'Failed to delete PN session');
334
+ }
335
+ }
336
+ catch (migrationError) {
337
+ logger.warn({ jid, error: migrationError }, 'Failed to migrate device session');
338
+ }
339
+ };
340
+ // Process each user group for potential bulk LID migration
341
+ for (const [user, userJids] of userGroups) {
342
+ const mappingResult = await checkUserLidMapping(user, userJids);
343
+ const shouldMigrateUser = mappingResult.shouldMigrate;
344
+ const lidForPN = mappingResult.lidForPN;
345
+ // Migrate all devices for this user if LID mapping exists
346
+ if (shouldMigrateUser && lidForPN) {
347
+ // Migrate each device individually
348
+ for (const jid of userJids) {
349
+ await migrateDeviceToLid(jid, lidForPN);
350
+ }
351
+ logger.info({
352
+ user,
353
+ lidMapping: lidForPN,
354
+ deviceCount: userJids.length
355
+ }, 'Completed migration attempt for user devices');
356
+ }
357
+ // Helper to check session for migrated user
358
+ const checkMigratedSession = async (jid) => {
359
+ const signalId = signalRepository.jidToSignalProtocolAddress(jid);
360
+ let hasSession = !!sessions[signalId];
361
+ let jidToFetch = jid;
362
+ // Check if we should use migrated LID session instead
363
+ if (shouldMigrateUser && lidForPN && jid.includes('@s.whatsapp.net')) {
364
+ const originalDecoded = (0, WABinary_1.jidDecode)(jid);
365
+ const deviceId = (originalDecoded === null || originalDecoded === void 0 ? void 0 : originalDecoded.device) || 0;
366
+ const lidDecoded = (0, WABinary_1.jidDecode)(lidForPN);
367
+ const lidWithDevice = (0, WABinary_1.jidEncode)(lidDecoded === null || lidDecoded === void 0 ? void 0 : lidDecoded.user, 'lid', deviceId);
368
+ // Check if LID session exists
369
+ const lidSignalId = signalRepository.jidToSignalProtocolAddress(lidWithDevice);
370
+ const lidSessions = await authState.keys.get('session', [lidSignalId]);
371
+ hasSession = !!lidSessions[lidSignalId];
372
+ jidToFetch = lidWithDevice;
373
+ if (hasSession) {
374
+ logger.debug({ originalJid: jid, lidJid: lidWithDevice }, '✅ Found bulk-migrated LID session');
375
+ }
376
+ }
377
+ // Add to fetch list if no session exists
378
+ if (!hasSession) {
379
+ jidsRequiringFetch.push(jidToFetch);
380
+ logger.debug({ jid: jidToFetch, originalJid: jid !== jidToFetch ? jid : undefined }, 'Adding to session fetch list');
381
+ }
382
+ };
383
+ // Now check which sessions need to be fetched for this user
384
+ for (const jid of userJids) {
385
+ await checkMigratedSession(jid);
175
386
  }
176
387
  }
177
388
  }
178
389
  if (jidsRequiringFetch.length) {
179
390
  logger.debug({ jidsRequiringFetch }, 'fetching sessions');
391
+ // DEBUG: Check if there are PN versions of LID users being fetched
392
+ const lidUsersBeingFetched = new Set();
393
+ const pnUsersBeingFetched = new Set();
394
+ for (const jid of jidsRequiringFetch) {
395
+ const user = (_a = (0, WABinary_1.jidDecode)(jid)) === null || _a === void 0 ? void 0 : _a.user;
396
+ if (user) {
397
+ if (jid.includes('@lid')) {
398
+ lidUsersBeingFetched.add(user);
399
+ }
400
+ else if (jid.includes('@s.whatsapp.net')) {
401
+ pnUsersBeingFetched.add(user);
402
+ }
403
+ }
404
+ }
405
+ // Find overlaps
406
+ const overlapping = Array.from(pnUsersBeingFetched).filter(user => lidUsersBeingFetched.has(user));
407
+ if (overlapping.length > 0) {
408
+ logger.warn({
409
+ overlapping,
410
+ lidUsersBeingFetched: Array.from(lidUsersBeingFetched),
411
+ pnUsersBeingFetched: Array.from(pnUsersBeingFetched)
412
+ }, 'Fetching both LID and PN sessions for same users');
413
+ }
180
414
  const result = await query({
181
415
  tag: 'iq',
182
416
  attrs: {
@@ -222,44 +456,135 @@ const makeMessagesSocket = (config) => {
222
456
  });
223
457
  return msgId;
224
458
  };
225
- const createParticipantNodes = async (jids, message, extraAttrs) => {
459
+ const createParticipantNodes = async (jids, message, extraAttrs, dsmMessage) => {
460
+ var _a, _b;
226
461
  let patched = await patchMessageBeforeSending(message, jids);
227
462
  if (!Array.isArray(patched)) {
228
463
  patched = jids ? jids.map(jid => ({ recipientJid: jid, ...patched })) : [patched];
229
464
  }
230
465
  let shouldIncludeDeviceIdentity = false;
231
- const nodes = await Promise.all(patched.map(async (patchedMessageWithJid) => {
232
- const { recipientJid: jid, ...patchedMessage } = patchedMessageWithJid;
233
- if (!jid) {
234
- return {};
235
- }
236
- const bytes = (0, Utils_1.encodeWAMessage)(patchedMessage);
237
- const { type, ciphertext } = await signalRepository.encryptMessage({ jid, data: bytes });
238
- if (type === 'pkmsg') {
239
- shouldIncludeDeviceIdentity = true;
240
- }
241
- const node = {
242
- tag: 'to',
243
- attrs: { jid },
244
- content: [
245
- {
246
- tag: 'enc',
247
- attrs: {
248
- v: '2',
249
- type,
250
- ...(extraAttrs || {})
251
- },
252
- content: ciphertext
466
+ const meId = authState.creds.me.id;
467
+ const meLid = (_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.lid;
468
+ const meLidUser = meLid ? (_b = (0, WABinary_1.jidDecode)(meLid)) === null || _b === void 0 ? void 0 : _b.user : null;
469
+ const devicesByUser = new Map();
470
+ for (const patchedMessageWithJid of patched) {
471
+ const { recipientJid: wireJid, ...patchedMessage } = patchedMessageWithJid;
472
+ if (!wireJid)
473
+ continue;
474
+ // Extract user from JID for grouping
475
+ const decoded = (0, WABinary_1.jidDecode)(wireJid);
476
+ const user = decoded === null || decoded === void 0 ? void 0 : decoded.user;
477
+ if (!user)
478
+ continue;
479
+ if (!devicesByUser.has(user)) {
480
+ devicesByUser.set(user, []);
481
+ }
482
+ devicesByUser.get(user).push({ recipientJid: wireJid, patchedMessage });
483
+ }
484
+ // Process each user's devices sequentially, but different users in parallel
485
+ const userEncryptionPromises = Array.from(devicesByUser.entries()).map(([user, userDevices]) => encryptionMutex.mutex(user, async () => {
486
+ var _a;
487
+ logger.debug({ user, deviceCount: userDevices.length }, 'Acquiring encryption lock for user devices');
488
+ const userNodes = [];
489
+ // Helper to get encryption JID with LID migration
490
+ const getEncryptionJid = async (wireJid) => {
491
+ if (!wireJid.includes('@s.whatsapp.net'))
492
+ return wireJid;
493
+ try {
494
+ const lidMapping = signalRepository.getLIDMappingStore();
495
+ const lidForPN = await lidMapping.getLIDForPN(wireJid);
496
+ if (!(lidForPN === null || lidForPN === void 0 ? void 0 : lidForPN.includes('@lid')))
497
+ return wireJid;
498
+ // Preserve device ID from original wire JID
499
+ const wireDecoded = (0, WABinary_1.jidDecode)(wireJid);
500
+ const deviceId = (wireDecoded === null || wireDecoded === void 0 ? void 0 : wireDecoded.device) || 0;
501
+ const lidDecoded = (0, WABinary_1.jidDecode)(lidForPN);
502
+ const lidWithDevice = (0, WABinary_1.jidEncode)(lidDecoded === null || lidDecoded === void 0 ? void 0 : lidDecoded.user, 'lid', deviceId);
503
+ // Migrate session to LID for unified encryption layer
504
+ try {
505
+ await signalRepository.migrateSession(wireJid, lidWithDevice);
506
+ const recipientUser = (0, WABinary_1.jidNormalizedUser)(wireJid);
507
+ const ownPnUser = (0, WABinary_1.jidNormalizedUser)(meId);
508
+ const isOwnDevice = recipientUser === ownPnUser;
509
+ logger.info({ wireJid, lidWithDevice, isOwnDevice }, 'Migrated to LID encryption');
510
+ // Delete PN session after successful migration
511
+ try {
512
+ await signalRepository.deleteSession(wireJid);
513
+ logger.debug({ deletedPNSession: wireJid }, 'Deleted PN session');
514
+ }
515
+ catch (deleteError) {
516
+ logger.warn({ wireJid, error: deleteError }, 'Failed to delete PN session');
517
+ }
518
+ return lidWithDevice;
253
519
  }
254
- ]
520
+ catch (migrationError) {
521
+ logger.warn({ wireJid, error: migrationError }, 'Failed to migrate session');
522
+ return wireJid;
523
+ }
524
+ }
525
+ catch (error) {
526
+ logger.debug({ wireJid, error }, 'Failed to check LID mapping');
527
+ return wireJid;
528
+ }
255
529
  };
256
- return node;
530
+ // Encrypt to this user's devices sequentially to prevent session corruption
531
+ for (const { recipientJid: wireJid, patchedMessage } of userDevices) {
532
+ // DSM logic: Use DSM for own other devices (following whatsmeow implementation)
533
+ let messageToEncrypt = patchedMessage;
534
+ if (dsmMessage) {
535
+ const { user: targetUser } = (0, WABinary_1.jidDecode)(wireJid);
536
+ const { user: ownPnUser } = (0, WABinary_1.jidDecode)(meId);
537
+ const ownLidUser = meLidUser;
538
+ // Check if this is our device (same user, different device)
539
+ const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser);
540
+ // Exclude exact sender device (whatsmeow: if jid == ownJID || jid == ownLID { continue })
541
+ const isExactSenderDevice = wireJid === meId || (((_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.lid) && wireJid === authState.creds.me.lid);
542
+ if (isOwnUser && !isExactSenderDevice) {
543
+ messageToEncrypt = dsmMessage;
544
+ logger.debug({ wireJid, targetUser }, 'Using DSM for own device');
545
+ }
546
+ }
547
+ const bytes = (0, Utils_1.encodeWAMessage)(messageToEncrypt);
548
+ // Get encryption JID with LID migration
549
+ const encryptionJid = await getEncryptionJid(wireJid);
550
+ // ENCRYPT: Use the determined encryption identity (prefers migrated LID)
551
+ const { type, ciphertext } = await signalRepository.encryptMessage({
552
+ jid: encryptionJid, // Unified encryption layer (LID when available)
553
+ data: bytes
554
+ });
555
+ if (type === 'pkmsg') {
556
+ shouldIncludeDeviceIdentity = true;
557
+ }
558
+ const node = {
559
+ tag: 'to',
560
+ attrs: { jid: wireJid }, // Always use original wire identity in envelope
561
+ content: [
562
+ {
563
+ tag: 'enc',
564
+ attrs: {
565
+ v: '2',
566
+ type,
567
+ ...(extraAttrs || {})
568
+ },
569
+ content: ciphertext
570
+ }
571
+ ]
572
+ };
573
+ userNodes.push(node);
574
+ }
575
+ logger.debug({ user, nodesCreated: userNodes.length }, 'Releasing encryption lock for user devices');
576
+ return userNodes;
257
577
  }));
578
+ // Wait for all users to complete (users are processed in parallel)
579
+ const userNodesArrays = await Promise.all(userEncryptionPromises);
580
+ const nodes = userNodesArrays.flat();
258
581
  return { nodes, shouldIncludeDeviceIdentity };
259
582
  };
260
583
  const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, statusJidList }) => {
261
- var _a;
584
+ var _a, _b;
262
585
  const meId = authState.creds.me.id;
586
+ const meLid = (_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.lid;
587
+ // ADDRESSING CONSISTENCY: Keep envelope addressing as user provided, handle LID migration in encryption
263
588
  let shouldIncludeDeviceIdentity = false;
264
589
  const { user, server } = (0, WABinary_1.jidDecode)(jid);
265
590
  const statusJid = 'status@broadcast';
@@ -267,11 +592,22 @@ const makeMessagesSocket = (config) => {
267
592
  const isStatus = jid === statusJid;
268
593
  const isLid = server === 'lid';
269
594
  const isNewsletter = server === 'newsletter';
270
- msgId = msgId || (0, Utils_1.generateMessageIDV2)((_a = sock.user) === null || _a === void 0 ? void 0 : _a.id);
595
+ // Keep user's original JID choice for envelope addressing
596
+ const finalJid = jid;
597
+ // ADDRESSING CONSISTENCY: Match own identity to conversation context
598
+ let ownId = meId;
599
+ if (isLid && meLid) {
600
+ ownId = meLid;
601
+ logger.debug({ to: jid, ownId }, 'Using LID identity for @lid conversation');
602
+ }
603
+ else {
604
+ logger.debug({ to: jid, ownId }, 'Using PN identity for @s.whatsapp.net conversation');
605
+ }
606
+ msgId = msgId || (0, Utils_1.generateMessageIDV2)((_b = sock.user) === null || _b === void 0 ? void 0 : _b.id);
271
607
  useUserDevicesCache = useUserDevicesCache !== false;
272
608
  useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus;
273
609
  const participants = [];
274
- const destinationJid = !isStatus ? (0, WABinary_1.jidEncode)(user, isLid ? 'lid' : isGroup ? 'g.us' : 's.whatsapp.net') : statusJid;
610
+ const destinationJid = !isStatus ? finalJid : statusJid;
275
611
  const binaryNodeContent = [];
276
612
  const devices = [];
277
613
  const meMsg = {
@@ -290,7 +626,11 @@ const makeMessagesSocket = (config) => {
290
626
  additionalAttributes = { ...additionalAttributes, device_fanout: 'false' };
291
627
  }
292
628
  const { user, device } = (0, WABinary_1.jidDecode)(participant.jid);
293
- devices.push({ user, device });
629
+ devices.push({
630
+ user,
631
+ device,
632
+ wireJid: participant.jid // Use the participant JID as wire JID
633
+ });
294
634
  }
295
635
  await authState.keys.transaction(async () => {
296
636
  var _a, _b, _c, _d, _e;
@@ -350,9 +690,10 @@ const makeMessagesSocket = (config) => {
350
690
  participantsList.push(...statusJidList);
351
691
  }
352
692
  if (!isStatus) {
693
+ const groupAddressingMode = (groupData === null || groupData === void 0 ? void 0 : groupData.addressingMode) || (isLid ? 'lid' : 'pn');
353
694
  additionalAttributes = {
354
695
  ...additionalAttributes,
355
- addressing_mode: (groupData === null || groupData === void 0 ? void 0 : groupData.addressingMode) || 'pn'
696
+ addressing_mode: groupAddressingMode
356
697
  };
357
698
  }
358
699
  const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false);
@@ -363,19 +704,24 @@ const makeMessagesSocket = (config) => {
363
704
  throw new boom_1.Boom('Per-jid patching is not supported in groups');
364
705
  }
365
706
  const bytes = (0, Utils_1.encodeWAMessage)(patched);
707
+ // This should match the group's addressing mode and conversation context
708
+ const groupAddressingMode = (groupData === null || groupData === void 0 ? void 0 : groupData.addressingMode) || (isLid ? 'lid' : 'pn');
709
+ const groupSenderIdentity = groupAddressingMode === 'lid' && meLid ? meLid : meId;
366
710
  const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
367
711
  group: destinationJid,
368
712
  data: bytes,
369
- meId
713
+ meId: groupSenderIdentity
370
714
  });
371
715
  const senderKeyJids = [];
372
716
  // ensure a connection is established with every device
373
- for (const { user, device } of devices) {
374
- const jid = (0, WABinary_1.jidEncode)(user, (groupData === null || groupData === void 0 ? void 0 : groupData.addressingMode) === 'lid' ? 'lid' : 's.whatsapp.net', device);
375
- if (!senderKeyMap[jid] || !!participant) {
376
- senderKeyJids.push(jid);
717
+ for (const device of devices) {
718
+ // This preserves the LID migration results from getUSyncDevices
719
+ const deviceJid = device.wireJid;
720
+ const hasKey = !!senderKeyMap[deviceJid];
721
+ if (!hasKey || !!participant) {
722
+ senderKeyJids.push(deviceJid);
377
723
  // store that this person has had the sender keys sent to them
378
- senderKeyMap[jid] = true;
724
+ senderKeyMap[deviceJid] = true;
379
725
  }
380
726
  }
381
727
  // if there are some participants with whom the session has not been established
@@ -401,23 +747,54 @@ const makeMessagesSocket = (config) => {
401
747
  await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } });
402
748
  }
403
749
  else {
404
- const { user: meUser } = (0, WABinary_1.jidDecode)(meId);
750
+ const { user: ownUser } = (0, WABinary_1.jidDecode)(ownId);
405
751
  if (!participant) {
406
- devices.push({ user });
407
- if (user !== meUser) {
408
- devices.push({ user: meUser });
752
+ const targetUserServer = isLid ? 'lid' : 's.whatsapp.net';
753
+ devices.push({
754
+ user,
755
+ device: 0,
756
+ wireJid: (0, WABinary_1.jidEncode)(user, targetUserServer, 0)
757
+ });
758
+ // Own user matches conversation addressing mode
759
+ if (user !== ownUser) {
760
+ const ownUserServer = isLid ? 'lid' : 's.whatsapp.net';
761
+ const ownUserForAddressing = isLid && meLid ? (0, WABinary_1.jidDecode)(meLid).user : (0, WABinary_1.jidDecode)(meId).user;
762
+ devices.push({
763
+ user: ownUserForAddressing,
764
+ device: 0,
765
+ wireJid: (0, WABinary_1.jidEncode)(ownUserForAddressing, ownUserServer, 0)
766
+ });
409
767
  }
410
768
  if ((additionalAttributes === null || additionalAttributes === void 0 ? void 0 : additionalAttributes['category']) !== 'peer') {
411
- const additionalDevices = await getUSyncDevices([meId, jid], !!useUserDevicesCache, true);
412
- devices.push(...additionalDevices);
769
+ // Clear placeholders and enumerate actual devices
770
+ devices.length = 0;
771
+ // Use conversation-appropriate sender identity
772
+ const senderIdentity = isLid && meLid
773
+ ? (0, WABinary_1.jidEncode)((_b = (0, WABinary_1.jidDecode)(meLid)) === null || _b === void 0 ? void 0 : _b.user, 'lid', undefined)
774
+ : (0, WABinary_1.jidEncode)((_c = (0, WABinary_1.jidDecode)(meId)) === null || _c === void 0 ? void 0 : _c.user, 's.whatsapp.net', undefined);
775
+ // Enumerate devices for sender and target with consistent addressing
776
+ const sessionDevices = await getUSyncDevices([senderIdentity, jid], false, false);
777
+ devices.push(...sessionDevices);
778
+ logger.debug({
779
+ deviceCount: devices.length,
780
+ devices: devices.map(d => { var _a; return `${d.user}:${d.device}@${(_a = (0, WABinary_1.jidDecode)(d.wireJid)) === null || _a === void 0 ? void 0 : _a.server}`; })
781
+ }, 'Device enumeration complete with unified addressing');
413
782
  }
414
783
  }
415
784
  const allJids = [];
416
785
  const meJids = [];
417
786
  const otherJids = [];
418
- for (const { user, device } of devices) {
419
- const isMe = user === meUser;
420
- const jid = (0, WABinary_1.jidEncode)(isMe && isLid ? ((_c = (_b = authState.creds) === null || _b === void 0 ? void 0 : _b.me) === null || _c === void 0 ? void 0 : _c.lid.split(':')[0]) || user : user, isLid ? 'lid' : 's.whatsapp.net', device);
787
+ const { user: mePnUser } = (0, WABinary_1.jidDecode)(meId);
788
+ const { user: meLidUser } = meLid ? (0, WABinary_1.jidDecode)(meLid) : { user: null };
789
+ for (const { user, wireJid } of devices) {
790
+ const isExactSenderDevice = wireJid === meId || (meLid && wireJid === meLid);
791
+ if (isExactSenderDevice) {
792
+ logger.debug({ wireJid, meId, meLid }, 'Skipping exact sender device (whatsmeow pattern)');
793
+ continue;
794
+ }
795
+ // Check if this is our device (could match either PN or LID user)
796
+ const isMe = user === mePnUser || (meLidUser && user === meLidUser);
797
+ const jid = wireJid;
421
798
  if (isMe) {
422
799
  meJids.push(jid);
423
800
  }
@@ -426,10 +803,11 @@ const makeMessagesSocket = (config) => {
426
803
  }
427
804
  allJids.push(jid);
428
805
  }
429
- await assertSessions(allJids, false);
806
+ await assertSessions([...otherJids, ...meJids], false);
430
807
  const [{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }] = await Promise.all([
431
- createParticipantNodes(meJids, meMsg, extraAttrs),
432
- createParticipantNodes(otherJids, message, extraAttrs)
808
+ // For own devices: use DSM if available (1:1 chats only)
809
+ createParticipantNodes(meJids, meMsg || message, extraAttrs),
810
+ createParticipantNodes(otherJids, message, extraAttrs, meMsg)
433
811
  ]);
434
812
  participants.push(...meNodes);
435
813
  participants.push(...otherNodes);
@@ -454,6 +832,7 @@ const makeMessagesSocket = (config) => {
454
832
  tag: 'message',
455
833
  attrs: {
456
834
  id: msgId,
835
+ to: destinationJid,
457
836
  type: getMessageType(message),
458
837
  ...(additionalAttributes || {})
459
838
  },
@@ -504,12 +504,29 @@ const makeSocket = (config) => {
504
504
  });
505
505
  // login complete
506
506
  ws.on('CB:success', async (node) => {
507
+ var _a;
507
508
  await uploadPreKeysToServerIfRequired();
508
509
  await sendPassiveIq('active');
509
510
  logger.info('opened connection to WA');
510
511
  clearTimeout(qrTimer); // will never happen in all likelyhood -- but just in case WA sends success on first try
511
512
  ev.emit('creds.update', { me: { ...authState.creds.me, lid: node.attrs.lid } });
512
513
  ev.emit('connection.update', { connection: 'open' });
514
+ if (node.attrs.lid && ((_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.id)) {
515
+ const myLID = node.attrs.lid;
516
+ process.nextTick(async () => {
517
+ try {
518
+ const myPN = authState.creds.me.id;
519
+ // Store our own LID-PN mapping
520
+ await signalRepository.storeLIDPNMapping(myLID, myPN);
521
+ // Create LID session for ourselves (whatsmeow pattern)
522
+ await signalRepository.migrateSession(myPN, myLID);
523
+ logger.info({ myPN, myLID }, 'Own LID session created successfully');
524
+ }
525
+ catch (error) {
526
+ logger.error({ error, lid: myLID }, 'Failed to create own LID session');
527
+ }
528
+ });
529
+ }
513
530
  });
514
531
  ws.on('CB:stream:error', (node) => {
515
532
  logger.error({ node }, 'stream errored out');
@@ -69,6 +69,7 @@ export type SignalDataTypeMap = {
69
69
  };
70
70
  'app-state-sync-key': proto.Message.IAppStateSyncKeyData;
71
71
  'app-state-sync-version': LTHashState;
72
+ 'lid-mapping': string;
72
73
  };
73
74
  export type SignalDataSet = {
74
75
  [T in keyof SignalDataTypeMap]?: {
@@ -1,4 +1,5 @@
1
1
  import { proto } from '../../WAProto';
2
+ import type { LIDMappingStore } from '../Signal/lid-mapping';
2
3
  type DecryptGroupSignalOpts = {
3
4
  group: string;
4
5
  authorJid: string;
@@ -17,6 +18,11 @@ type EncryptMessageOpts = {
17
18
  jid: string;
18
19
  data: Uint8Array;
19
20
  };
21
+ type EncryptMessageWithWireOpts = {
22
+ encryptionJid: string;
23
+ wireJid: string;
24
+ data: Uint8Array;
25
+ };
20
26
  type EncryptGroupMessageOpts = {
21
27
  group: string;
22
28
  data: Uint8Array;
@@ -47,11 +53,25 @@ export type SignalRepository = {
47
53
  type: 'pkmsg' | 'msg';
48
54
  ciphertext: Uint8Array;
49
55
  }>;
56
+ encryptMessageWithWire(opts: EncryptMessageWithWireOpts): Promise<{
57
+ type: 'pkmsg' | 'msg';
58
+ ciphertext: Uint8Array;
59
+ wireJid: string;
60
+ }>;
50
61
  encryptGroupMessage(opts: EncryptGroupMessageOpts): Promise<{
51
62
  senderKeyDistributionMessage: Uint8Array;
52
63
  ciphertext: Uint8Array;
53
64
  }>;
54
65
  injectE2ESession(opts: E2ESessionOpts): Promise<void>;
55
66
  jidToSignalProtocolAddress(jid: string): string;
67
+ storeLIDPNMapping(lid: string, pn: string): Promise<void>;
68
+ getLIDMappingStore(): LIDMappingStore;
69
+ migrateSession(fromJid: string, toJid: string): Promise<void>;
70
+ validateSession(jid: string): Promise<{
71
+ exists: boolean;
72
+ reason?: string;
73
+ }>;
74
+ deleteSession(jid: string): Promise<void>;
75
+ destroy(): void;
56
76
  };
57
77
  export {};
@@ -18,6 +18,11 @@ export declare const NACK_REASONS: {
18
18
  UnsupportedLIDGroup: number;
19
19
  DBOperationFailed: number;
20
20
  };
21
+ export declare const extractAddressingContext: (stanza: BinaryNode) => {
22
+ addressingMode: string;
23
+ senderAlt: string;
24
+ recipientAlt: string;
25
+ };
21
26
  /**
22
27
  * Decode the received node as a message.
23
28
  * @note this will only parse the message, not decrypt it
@@ -1,11 +1,37 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.decryptMessageNode = exports.NACK_REASONS = exports.MISSING_KEYS_ERROR_TEXT = exports.NO_MESSAGE_FOUND_ERROR_TEXT = void 0;
3
+ exports.decryptMessageNode = exports.extractAddressingContext = exports.NACK_REASONS = exports.MISSING_KEYS_ERROR_TEXT = exports.NO_MESSAGE_FOUND_ERROR_TEXT = void 0;
4
4
  exports.decodeMessageNode = decodeMessageNode;
5
5
  const boom_1 = require("@hapi/boom");
6
6
  const WAProto_1 = require("../../WAProto");
7
7
  const WABinary_1 = require("../WABinary");
8
8
  const generics_1 = require("./generics");
9
+ const getDecryptionJid = async (sender, repository) => {
10
+ if (!sender.includes('@s.whatsapp.net')) {
11
+ return sender;
12
+ }
13
+ const lidMapping = repository.getLIDMappingStore();
14
+ const normalizedSender = (0, WABinary_1.jidNormalizedUser)(sender);
15
+ const lidForPN = await lidMapping.getLIDForPN(normalizedSender);
16
+ if (lidForPN === null || lidForPN === void 0 ? void 0 : lidForPN.includes('@lid')) {
17
+ const senderDecoded = (0, WABinary_1.jidDecode)(sender);
18
+ const deviceId = (senderDecoded === null || senderDecoded === void 0 ? void 0 : senderDecoded.device) || 0;
19
+ return (0, WABinary_1.jidEncode)((0, WABinary_1.jidDecode)(lidForPN).user, 'lid', deviceId);
20
+ }
21
+ return sender;
22
+ };
23
+ const storeMappingFromEnvelope = async (stanza, sender, decryptionJid, repository, logger) => {
24
+ const { senderAlt } = (0, exports.extractAddressingContext)(stanza);
25
+ if (senderAlt && (0, WABinary_1.isLidUser)(senderAlt) && (0, WABinary_1.isJidUser)(sender) && decryptionJid === sender) {
26
+ try {
27
+ await repository.storeLIDPNMapping(senderAlt, sender);
28
+ logger.debug({ sender, senderAlt }, 'Stored LID mapping from envelope');
29
+ }
30
+ catch (error) {
31
+ logger.warn({ sender, senderAlt, error }, 'Failed to store LID mapping');
32
+ }
33
+ }
34
+ };
9
35
  exports.NO_MESSAGE_FOUND_ERROR_TEXT = 'Message absent from node';
10
36
  exports.MISSING_KEYS_ERROR_TEXT = 'Key used already or never filled';
11
37
  exports.NACK_REASONS = {
@@ -23,6 +49,27 @@ exports.NACK_REASONS = {
23
49
  UnsupportedLIDGroup: 551,
24
50
  DBOperationFailed: 552
25
51
  };
52
+ const extractAddressingContext = (stanza) => {
53
+ const addressingMode = stanza.attrs.addressing_mode || 'pn';
54
+ let senderAlt;
55
+ let recipientAlt;
56
+ if (addressingMode === 'lid') {
57
+ // Message is LID-addressed: sender is LID, extract corresponding PN
58
+ senderAlt = stanza.attrs.participant_pn || stanza.attrs.sender_pn;
59
+ recipientAlt = stanza.attrs.recipient_pn;
60
+ }
61
+ else {
62
+ // Message is PN-addressed: sender is PN, extract corresponding LID
63
+ senderAlt = stanza.attrs.participant_lid || stanza.attrs.sender_lid;
64
+ recipientAlt = stanza.attrs.recipient_lid;
65
+ }
66
+ return {
67
+ addressingMode,
68
+ senderAlt,
69
+ recipientAlt
70
+ };
71
+ };
72
+ exports.extractAddressingContext = extractAddressingContext;
26
73
  /**
27
74
  * Decode the received node as a message.
28
75
  * @note this will only parse the message, not decrypt it
@@ -150,11 +197,13 @@ const decryptMessageNode = (stanza, meId, meLid, repository, logger) => {
150
197
  case 'pkmsg':
151
198
  case 'msg':
152
199
  const user = (0, WABinary_1.isJidUser)(sender) ? sender : author;
200
+ const decryptionJid = await getDecryptionJid(user, repository);
153
201
  msgBuffer = await repository.decryptMessage({
154
- jid: user,
202
+ jid: decryptionJid,
155
203
  type: e2eType,
156
204
  ciphertext: content
157
205
  });
206
+ await storeMappingFromEnvelope(stanza, user, decryptionJid, repository, logger);
158
207
  break;
159
208
  case 'plaintext':
160
209
  msgBuffer = content;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@periskope/baileys",
3
- "version": "6.7.18-17-3",
3
+ "version": "6.7.18-17-4",
4
4
  "description": "WhatsApp API",
5
5
  "keywords": [
6
6
  "periskope",
@@ -66,6 +66,7 @@
66
66
  "jimp": "^1.6.0",
67
67
  "json": "^11.0.0",
68
68
  "link-preview-js": "^3.0.0",
69
+ "lru-cache": "^10.4.3",
69
70
  "open": "^8.4.2",
70
71
  "prettier": "^3.5.3",
71
72
  "protobufjs-cli": "^1.1.3",