@periskope/baileys 6.7.18-17-3 → 6.7.18-17-6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/Signal/Group/group_cipher.d.ts +1 -0
- package/lib/Signal/Group/group_cipher.js +39 -29
- package/lib/Signal/Group/queue-job.d.ts +0 -1
- package/lib/Signal/Group/queue-job.js +5 -2
- package/lib/Signal/libsignal.js +176 -59
- package/lib/Signal/lid-mapping.d.ts +19 -0
- package/lib/Signal/lid-mapping.js +115 -0
- package/lib/Socket/business.d.ts +4 -2
- package/lib/Socket/index.d.ts +4 -2
- package/lib/Socket/messages-recv.d.ts +4 -2
- package/lib/Socket/messages-recv.js +31 -1
- package/lib/Socket/messages-send.d.ts +4 -2
- package/lib/Socket/messages-send.js +438 -59
- package/lib/Socket/socket.js +17 -0
- package/lib/Types/Auth.d.ts +3 -0
- package/lib/Types/Signal.d.ts +20 -0
- package/lib/Utils/auth-utils.d.ts +1 -1
- package/lib/Utils/auth-utils.js +41 -348
- package/lib/Utils/decode-wa-message.d.ts +5 -0
- package/lib/Utils/decode-wa-message.js +51 -2
- package/package.json +2 -1
|
@@ -8,6 +8,7 @@ export declare class GroupCipher {
|
|
|
8
8
|
private readonly senderKeyStore;
|
|
9
9
|
private readonly senderKeyName;
|
|
10
10
|
constructor(senderKeyStore: SenderKeyStore, senderKeyName: SenderKeyName);
|
|
11
|
+
private queueJob;
|
|
11
12
|
encrypt(paddedPlaintext: Uint8Array | string): Promise<Uint8Array>;
|
|
12
13
|
decrypt(senderKeyMessageBytes: Uint8Array): Promise<Uint8Array>;
|
|
13
14
|
private getSenderKey;
|
|
@@ -1,45 +1,55 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.GroupCipher = void 0;
|
|
4
|
-
/* @ts-ignore */
|
|
5
7
|
const crypto_1 = require("libsignal/src/crypto");
|
|
8
|
+
const queue_job_1 = __importDefault(require("./queue-job"));
|
|
6
9
|
const sender_key_message_1 = require("./sender-key-message");
|
|
7
10
|
class GroupCipher {
|
|
8
11
|
constructor(senderKeyStore, senderKeyName) {
|
|
9
12
|
this.senderKeyStore = senderKeyStore;
|
|
10
13
|
this.senderKeyName = senderKeyName;
|
|
11
14
|
}
|
|
15
|
+
queueJob(awaitable) {
|
|
16
|
+
return (0, queue_job_1.default)(this.senderKeyName.toString(), awaitable);
|
|
17
|
+
}
|
|
12
18
|
async encrypt(paddedPlaintext) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
return await this.queueJob(async () => {
|
|
20
|
+
const record = await this.senderKeyStore.loadSenderKey(this.senderKeyName);
|
|
21
|
+
if (!record) {
|
|
22
|
+
throw new Error('No SenderKeyRecord found for encryption');
|
|
23
|
+
}
|
|
24
|
+
const senderKeyState = record.getSenderKeyState();
|
|
25
|
+
if (!senderKeyState) {
|
|
26
|
+
throw new Error('No session to encrypt message');
|
|
27
|
+
}
|
|
28
|
+
const iteration = senderKeyState.getSenderChainKey().getIteration();
|
|
29
|
+
const senderKey = this.getSenderKey(senderKeyState, iteration === 0 ? 0 : iteration + 1);
|
|
30
|
+
const ciphertext = await this.getCipherText(senderKey.getIv(), senderKey.getCipherKey(), paddedPlaintext);
|
|
31
|
+
const senderKeyMessage = new sender_key_message_1.SenderKeyMessage(senderKeyState.getKeyId(), senderKey.getIteration(), ciphertext, senderKeyState.getSigningKeyPrivate());
|
|
32
|
+
await this.senderKeyStore.storeSenderKey(this.senderKeyName, record);
|
|
33
|
+
return senderKeyMessage.serialize();
|
|
34
|
+
});
|
|
27
35
|
}
|
|
28
36
|
async decrypt(senderKeyMessageBytes) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
return await this.queueJob(async () => {
|
|
38
|
+
const record = await this.senderKeyStore.loadSenderKey(this.senderKeyName);
|
|
39
|
+
if (!record) {
|
|
40
|
+
throw new Error('No SenderKeyRecord found for decryption');
|
|
41
|
+
}
|
|
42
|
+
const senderKeyMessage = new sender_key_message_1.SenderKeyMessage(null, null, null, null, senderKeyMessageBytes);
|
|
43
|
+
const senderKeyState = record.getSenderKeyState(senderKeyMessage.getKeyId());
|
|
44
|
+
if (!senderKeyState) {
|
|
45
|
+
throw new Error('No session found to decrypt message');
|
|
46
|
+
}
|
|
47
|
+
senderKeyMessage.verifySignature(senderKeyState.getSigningKeyPublic());
|
|
48
|
+
const senderKey = this.getSenderKey(senderKeyState, senderKeyMessage.getIteration());
|
|
49
|
+
const plaintext = await this.getPlainText(senderKey.getIv(), senderKey.getCipherKey(), senderKeyMessage.getCipherText());
|
|
50
|
+
await this.senderKeyStore.storeSenderKey(this.senderKeyName, record);
|
|
51
|
+
return plaintext;
|
|
52
|
+
});
|
|
43
53
|
}
|
|
44
54
|
getSenderKey(senderKeyState, iteration) {
|
|
45
55
|
let senderChainKey = senderKeyState.getSenderChainKey();
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = queueJob;
|
|
1
4
|
const _queueAsyncBuckets = new Map();
|
|
2
5
|
const _gcLimit = 10000;
|
|
3
6
|
async function _asyncQueueExecutor(queue, cleanup) {
|
|
4
7
|
let offt = 0;
|
|
8
|
+
// eslint-disable-next-line no-constant-condition
|
|
5
9
|
while (true) {
|
|
6
10
|
const limit = Math.min(queue.length, _gcLimit);
|
|
7
11
|
for (let i = offt; i < limit; i++) {
|
|
@@ -28,7 +32,7 @@ async function _asyncQueueExecutor(queue, cleanup) {
|
|
|
28
32
|
}
|
|
29
33
|
cleanup();
|
|
30
34
|
}
|
|
31
|
-
|
|
35
|
+
function queueJob(bucket, awaitable) {
|
|
32
36
|
// Skip name assignment since it's readonly in strict mode
|
|
33
37
|
if (typeof bucket !== 'string') {
|
|
34
38
|
console.warn('Unhandled bucket type (for naming):', typeof bucket, bucket);
|
|
@@ -51,4 +55,3 @@ export default function queueJob(bucket, awaitable) {
|
|
|
51
55
|
}
|
|
52
56
|
return job;
|
|
53
57
|
}
|
|
54
|
-
//# sourceMappingURL=queue-job.js.map
|
package/lib/Signal/libsignal.js
CHANGED
|
@@ -35,22 +35,27 @@ 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
|
|
45
|
-
const
|
|
46
|
-
|
|
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
|
+
});
|
|
54
|
+
const repository = {
|
|
47
55
|
decryptGroupMessage({ group, authorJid, msg }) {
|
|
48
56
|
const senderName = jidToSignalSenderKeyName(group, authorJid);
|
|
49
57
|
const cipher = new Group_1.GroupCipher(storage, senderName);
|
|
50
|
-
|
|
51
|
-
return parsedKeys.transaction(async () => {
|
|
52
|
-
return cipher.decrypt(msg);
|
|
53
|
-
});
|
|
58
|
+
return cipher.decrypt(msg);
|
|
54
59
|
},
|
|
55
60
|
async processSenderKeyDistributionMessage({ item, authorJid }) {
|
|
56
61
|
const builder = new Group_1.GroupSessionBuilder(storage);
|
|
@@ -64,88 +69,200 @@ function makeLibSignalRepository(auth) {
|
|
|
64
69
|
if (!senderKey) {
|
|
65
70
|
await storage.storeSenderKey(senderName, new sender_key_record_1.SenderKeyRecord());
|
|
66
71
|
}
|
|
67
|
-
|
|
68
|
-
const { [senderNameStr]: senderKey } = await auth.keys.get('sender-key', [senderNameStr]);
|
|
69
|
-
if (!senderKey) {
|
|
70
|
-
await storage.storeSenderKey(senderName, new sender_key_record_1.SenderKeyRecord());
|
|
71
|
-
}
|
|
72
|
-
await builder.process(senderName, senderMsg);
|
|
73
|
-
});
|
|
72
|
+
await builder.process(senderName, senderMsg);
|
|
74
73
|
},
|
|
75
74
|
async decryptMessage({ jid, type, ciphertext }) {
|
|
76
75
|
const addr = jidToSignalProtocolAddress(jid);
|
|
77
76
|
const session = new libsignal.SessionCipher(storage, addr);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
return result;
|
|
92
|
-
});
|
|
77
|
+
let result;
|
|
78
|
+
switch (type) {
|
|
79
|
+
case 'pkmsg':
|
|
80
|
+
result = await session.decryptPreKeyWhisperMessage(ciphertext);
|
|
81
|
+
break;
|
|
82
|
+
case 'msg':
|
|
83
|
+
result = await session.decryptWhisperMessage(ciphertext);
|
|
84
|
+
break;
|
|
85
|
+
default:
|
|
86
|
+
throw new Error(`Unknown message type: ${type}`);
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
93
89
|
},
|
|
94
90
|
async encryptMessage({ jid, data }) {
|
|
95
|
-
|
|
91
|
+
// LID SINGLE SOURCE OF TRUTH: Always prefer LID when available
|
|
92
|
+
let encryptionJid = jid;
|
|
93
|
+
// Check for LID mapping and use it if session exists
|
|
94
|
+
if (jid.includes('@s.whatsapp.net')) {
|
|
95
|
+
const lidForPN = await lidMapping.getLIDForPN(jid);
|
|
96
|
+
if (lidForPN === null || lidForPN === void 0 ? void 0 : lidForPN.includes('@lid')) {
|
|
97
|
+
const lidAddr = jidToSignalProtocolAddress(lidForPN);
|
|
98
|
+
const { [lidAddr.toString()]: lidSession } = await auth.keys.get('session', [lidAddr.toString()]);
|
|
99
|
+
if (lidSession) {
|
|
100
|
+
// LID session exists, use it
|
|
101
|
+
encryptionJid = lidForPN;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// Try to migrate if PN session exists
|
|
105
|
+
const pnAddr = jidToSignalProtocolAddress(jid);
|
|
106
|
+
const { [pnAddr.toString()]: pnSession } = await auth.keys.get('session', [pnAddr.toString()]);
|
|
107
|
+
if (pnSession) {
|
|
108
|
+
// Migrate PN to LID
|
|
109
|
+
await repository.migrateSession(jid, lidForPN);
|
|
110
|
+
encryptionJid = lidForPN;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const addr = jidToSignalProtocolAddress(encryptionJid);
|
|
96
116
|
const cipher = new libsignal.SessionCipher(storage, addr);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const type = sigType === 3 ? 'pkmsg' : 'msg';
|
|
101
|
-
return { type, ciphertext: Buffer.from(body, 'binary') };
|
|
102
|
-
});
|
|
117
|
+
const { type: sigType, body } = await cipher.encrypt(data);
|
|
118
|
+
const type = sigType === 3 ? 'pkmsg' : 'msg';
|
|
119
|
+
return { type, ciphertext: Buffer.from(body, 'binary') };
|
|
103
120
|
},
|
|
104
121
|
async encryptGroupMessage({ group, meId, data }) {
|
|
105
122
|
const senderName = jidToSignalSenderKeyName(group, meId);
|
|
106
123
|
const builder = new Group_1.GroupSessionBuilder(storage);
|
|
107
124
|
const senderNameStr = senderName.toString();
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
};
|
|
120
|
-
});
|
|
125
|
+
const { [senderNameStr]: senderKey } = await auth.keys.get('sender-key', [senderNameStr]);
|
|
126
|
+
if (!senderKey) {
|
|
127
|
+
await storage.storeSenderKey(senderName, new sender_key_record_1.SenderKeyRecord());
|
|
128
|
+
}
|
|
129
|
+
const senderKeyDistributionMessage = await builder.create(senderName);
|
|
130
|
+
const session = new Group_1.GroupCipher(storage, senderName);
|
|
131
|
+
const ciphertext = await session.encrypt(data);
|
|
132
|
+
return {
|
|
133
|
+
ciphertext,
|
|
134
|
+
senderKeyDistributionMessage: senderKeyDistributionMessage.serialize()
|
|
135
|
+
};
|
|
121
136
|
},
|
|
122
137
|
async injectE2ESession({ jid, session }) {
|
|
123
138
|
const cipher = new libsignal.SessionBuilder(storage, jidToSignalProtocolAddress(jid));
|
|
124
|
-
|
|
125
|
-
await cipher.initOutgoing(session);
|
|
126
|
-
});
|
|
139
|
+
await cipher.initOutgoing(session);
|
|
127
140
|
},
|
|
128
141
|
jidToSignalProtocolAddress(jid) {
|
|
129
142
|
return jidToSignalProtocolAddress(jid).toString();
|
|
143
|
+
},
|
|
144
|
+
async storeLIDPNMapping(lid, pn) {
|
|
145
|
+
await lidMapping.storeLIDPNMapping(lid, pn);
|
|
146
|
+
},
|
|
147
|
+
getLIDMappingStore() {
|
|
148
|
+
return lidMapping;
|
|
149
|
+
},
|
|
150
|
+
async validateSession(jid) {
|
|
151
|
+
try {
|
|
152
|
+
const addr = jidToSignalProtocolAddress(jid);
|
|
153
|
+
const session = await storage.loadSession(addr.toString());
|
|
154
|
+
if (!session) {
|
|
155
|
+
return { exists: false, reason: 'no session' };
|
|
156
|
+
}
|
|
157
|
+
if (!session.haveOpenSession()) {
|
|
158
|
+
return { exists: false, reason: 'no open session' };
|
|
159
|
+
}
|
|
160
|
+
return { exists: true };
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
return { exists: false, reason: 'validation error' };
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
async deleteSession(jid) {
|
|
167
|
+
const addr = jidToSignalProtocolAddress(jid);
|
|
168
|
+
return auth.keys.transaction(async () => {
|
|
169
|
+
await auth.keys.set({ session: { [addr.toString()]: null } });
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
async migrateSession(fromJid, toJid) {
|
|
173
|
+
// Only migrate PN → LID
|
|
174
|
+
if (!fromJid.includes('@s.whatsapp.net') || !toJid.includes('@lid')) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const fromDecoded = (0, WABinary_1.jidDecode)(fromJid);
|
|
178
|
+
const toDecoded = (0, WABinary_1.jidDecode)(toJid);
|
|
179
|
+
if (!fromDecoded || !toDecoded)
|
|
180
|
+
return;
|
|
181
|
+
const deviceId = fromDecoded.device || 0;
|
|
182
|
+
const migrationKey = `${fromDecoded.user}.${deviceId}→${toDecoded.user}.${deviceId}`;
|
|
183
|
+
// Check if recently migrated (5 min window)
|
|
184
|
+
if (recentMigrations.has(migrationKey)) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
// Check if LID session already exists
|
|
188
|
+
const lidAddr = jidToSignalProtocolAddress(toJid);
|
|
189
|
+
const { [lidAddr.toString()]: lidExists } = await auth.keys.get('session', [lidAddr.toString()]);
|
|
190
|
+
if (lidExists) {
|
|
191
|
+
recentMigrations.set(migrationKey, true);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
return auth.keys.transaction(async () => {
|
|
195
|
+
// Store mapping
|
|
196
|
+
await lidMapping.storeLIDPNMapping(toJid, fromJid);
|
|
197
|
+
// Load and copy session
|
|
198
|
+
const fromAddr = jidToSignalProtocolAddress(fromJid);
|
|
199
|
+
const fromSession = await storage.loadSession(fromAddr.toString());
|
|
200
|
+
if (fromSession === null || fromSession === void 0 ? void 0 : fromSession.haveOpenSession()) {
|
|
201
|
+
// Deep copy session to prevent reference issues
|
|
202
|
+
const sessionBytes = fromSession.serialize();
|
|
203
|
+
const copiedSession = libsignal.SessionRecord.deserialize(sessionBytes);
|
|
204
|
+
// Store at LID address
|
|
205
|
+
await storage.storeSession(lidAddr.toString(), copiedSession);
|
|
206
|
+
// Delete PN session - maintain single encryption layer
|
|
207
|
+
await auth.keys.set({ session: { [fromAddr.toString()]: null } });
|
|
208
|
+
}
|
|
209
|
+
recentMigrations.set(migrationKey, true);
|
|
210
|
+
});
|
|
211
|
+
},
|
|
212
|
+
async encryptMessageWithWire({ encryptionJid, wireJid, data }) {
|
|
213
|
+
const result = await repository.encryptMessage({ jid: encryptionJid, data });
|
|
214
|
+
return { ...result, wireJid };
|
|
215
|
+
},
|
|
216
|
+
destroy() {
|
|
217
|
+
recentMigrations.clear();
|
|
130
218
|
}
|
|
131
219
|
};
|
|
220
|
+
return repository;
|
|
132
221
|
}
|
|
133
222
|
const jidToSignalProtocolAddress = (jid) => {
|
|
134
|
-
const
|
|
135
|
-
|
|
223
|
+
const decoded = (0, WABinary_1.jidDecode)(jid);
|
|
224
|
+
const { user, device, server } = decoded;
|
|
225
|
+
// LID addresses get _1 suffix for Signal protocol
|
|
226
|
+
const signalUser = server === 'lid' ? `${user}_1` : user;
|
|
227
|
+
const finalDevice = device || 0;
|
|
228
|
+
return new libsignal.ProtocolAddress(signalUser, finalDevice);
|
|
136
229
|
};
|
|
137
230
|
const jidToSignalSenderKeyName = (group, user) => {
|
|
138
231
|
return new sender_key_name_1.SenderKeyName(group, jidToSignalProtocolAddress(user));
|
|
139
232
|
};
|
|
140
|
-
function signalStorage({ creds, keys }) {
|
|
233
|
+
function signalStorage({ creds, keys }, lidMapping) {
|
|
141
234
|
return {
|
|
142
235
|
loadSession: async (id) => {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
236
|
+
try {
|
|
237
|
+
// LID SINGLE SOURCE OF TRUTH: Auto-redirect PN to LID if mapping exists
|
|
238
|
+
let actualId = id;
|
|
239
|
+
if (id.includes('.') && !id.includes('_1')) {
|
|
240
|
+
// This is a PN signal address format (e.g., "1234567890.0")
|
|
241
|
+
// Convert back to JID to check for LID mapping
|
|
242
|
+
const parts = id.split('.');
|
|
243
|
+
const device = parts[1] || '0';
|
|
244
|
+
const pnJid = device === '0' ? `${parts[0]}@s.whatsapp.net` : `${parts[0]}:${device}@s.whatsapp.net`;
|
|
245
|
+
const lidForPN = await lidMapping.getLIDForPN(pnJid);
|
|
246
|
+
if (lidForPN === null || lidForPN === void 0 ? void 0 : lidForPN.includes('@lid')) {
|
|
247
|
+
const lidAddr = jidToSignalProtocolAddress(lidForPN);
|
|
248
|
+
const lidId = lidAddr.toString();
|
|
249
|
+
// Check if LID session exists
|
|
250
|
+
const { [lidId]: lidSession } = await keys.get('session', [lidId]);
|
|
251
|
+
if (lidSession) {
|
|
252
|
+
actualId = lidId;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const { [actualId]: sess } = await keys.get('session', [actualId]);
|
|
257
|
+
if (sess) {
|
|
258
|
+
return libsignal.SessionRecord.deserialize(sess);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch (e) {
|
|
262
|
+
return null;
|
|
146
263
|
}
|
|
264
|
+
return null;
|
|
147
265
|
},
|
|
148
|
-
// TODO: Replace with libsignal.SessionRecord when type exports are added to libsignal
|
|
149
266
|
storeSession: async (id, session) => {
|
|
150
267
|
await keys.set({ session: { [id]: session.serialize() } });
|
|
151
268
|
},
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SignalKeyStoreWithTransaction } from '../Types';
|
|
2
|
+
export declare class LIDMappingStore {
|
|
3
|
+
private readonly keys;
|
|
4
|
+
private getLIDFromPN?;
|
|
5
|
+
private getPNFromLID?;
|
|
6
|
+
constructor(keys: SignalKeyStoreWithTransaction, getLIDFromPN?: (pn: string) => Promise<string | null>, getPNFromLID?: (lid: string) => Promise<string | null>);
|
|
7
|
+
/**
|
|
8
|
+
* Store LID-PN mapping - USER LEVEL
|
|
9
|
+
*/
|
|
10
|
+
storeLIDPNMapping(lid: string, pn: string): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Get LID for PN - Returns device-specific LID based on user mapping
|
|
13
|
+
*/
|
|
14
|
+
getLIDForPN(pn: string): Promise<string | null>;
|
|
15
|
+
/**
|
|
16
|
+
* Get PN for LID - USER LEVEL with device construction
|
|
17
|
+
*/
|
|
18
|
+
getPNForLID(lid: string): Promise<string | null>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
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, getLIDFromPN, getPNFromLID) {
|
|
11
|
+
this.keys = keys;
|
|
12
|
+
this.getLIDFromPN = getLIDFromPN;
|
|
13
|
+
this.getPNFromLID = getPNFromLID;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Store LID-PN mapping - USER LEVEL
|
|
17
|
+
*/
|
|
18
|
+
async storeLIDPNMapping(lid, pn) {
|
|
19
|
+
// Validate inputs
|
|
20
|
+
if (!(((0, WABinary_1.isLidUser)(lid) && (0, WABinary_1.isJidUser)(pn)) || ((0, WABinary_1.isJidUser)(lid) && (0, WABinary_1.isLidUser)(pn)))) {
|
|
21
|
+
logger_1.default.warn(`Invalid LID-PN mapping: ${lid}, ${pn}`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const [lidJid, pnJid] = (0, WABinary_1.isLidUser)(lid) ? [lid, pn] : [pn, lid];
|
|
25
|
+
const lidDecoded = (0, WABinary_1.jidDecode)(lidJid);
|
|
26
|
+
const pnDecoded = (0, WABinary_1.jidDecode)(pnJid);
|
|
27
|
+
if (!lidDecoded || !pnDecoded)
|
|
28
|
+
return;
|
|
29
|
+
const pnUser = pnDecoded.user;
|
|
30
|
+
const lidUser = lidDecoded.user;
|
|
31
|
+
logger_1.default.trace(`Storing USER LID mapping: PN ${pnUser} → LID ${lidUser}`);
|
|
32
|
+
await this.keys.transaction(async () => {
|
|
33
|
+
await this.keys.set({
|
|
34
|
+
'lid-mapping': {
|
|
35
|
+
[pnUser]: lidUser, // "554396160286" -> "102765716062358"
|
|
36
|
+
[`${lidUser}_reverse`]: pnUser // "102765716062358_reverse" -> "554396160286"
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
logger_1.default.trace(`USER LID mapping stored: PN ${pnUser} → LID ${lidUser}`);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get LID for PN - Returns device-specific LID based on user mapping
|
|
44
|
+
*/
|
|
45
|
+
async getLIDForPN(pn) {
|
|
46
|
+
if (!(0, WABinary_1.isJidUser)(pn))
|
|
47
|
+
return null;
|
|
48
|
+
const decoded = (0, WABinary_1.jidDecode)(pn);
|
|
49
|
+
if (!decoded)
|
|
50
|
+
return null;
|
|
51
|
+
// Look up user-level mapping (whatsmeow approach)
|
|
52
|
+
const pnUser = decoded.user;
|
|
53
|
+
const stored = await this.keys.get('lid-mapping', [pnUser]);
|
|
54
|
+
let lidUser = stored[pnUser];
|
|
55
|
+
if (!lidUser) {
|
|
56
|
+
if (this.getLIDFromPN) {
|
|
57
|
+
const lid = await this.getLIDFromPN(`${pnUser}@s.whatsapp.net`);
|
|
58
|
+
if (lid) {
|
|
59
|
+
const decodedLid = (0, WABinary_1.jidDecode)(lid);
|
|
60
|
+
if (decodedLid) {
|
|
61
|
+
lidUser = decodedLid.user;
|
|
62
|
+
await this.storeLIDPNMapping(`${lidUser}@lid`, `${pnUser}@s.whatsapp.net`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (!lidUser) {
|
|
68
|
+
logger_1.default.trace(`No LID mapping found for PN user ${pnUser}`);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (typeof lidUser !== 'string')
|
|
72
|
+
return null;
|
|
73
|
+
// Push the PN device ID to the LID to maintain device separation
|
|
74
|
+
const pnDevice = decoded.device !== undefined ? decoded.device : 0;
|
|
75
|
+
const deviceSpecificLid = `${lidUser}:${pnDevice}@lid`;
|
|
76
|
+
logger_1.default.trace(`getLIDForPN: ${pn} → ${deviceSpecificLid} (user mapping with device ${pnDevice})`);
|
|
77
|
+
return deviceSpecificLid;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get PN for LID - USER LEVEL with device construction
|
|
81
|
+
*/
|
|
82
|
+
async getPNForLID(lid) {
|
|
83
|
+
if (!(0, WABinary_1.isLidUser)(lid))
|
|
84
|
+
return null;
|
|
85
|
+
const decoded = (0, WABinary_1.jidDecode)(lid);
|
|
86
|
+
if (!decoded)
|
|
87
|
+
return null;
|
|
88
|
+
// Look up reverse user mapping
|
|
89
|
+
const lidUser = decoded.user;
|
|
90
|
+
const stored = await this.keys.get('lid-mapping', [`${lidUser}_reverse`]);
|
|
91
|
+
let pnUser = stored[`${lidUser}_reverse`];
|
|
92
|
+
if (!pnUser) {
|
|
93
|
+
if (this.getPNFromLID) {
|
|
94
|
+
const pn = await this.getPNFromLID(`${lidUser}@lid`);
|
|
95
|
+
if (pn) {
|
|
96
|
+
const decodedJid = (0, WABinary_1.jidDecode)(pn);
|
|
97
|
+
if (decodedJid) {
|
|
98
|
+
pnUser = decodedJid.user;
|
|
99
|
+
await this.storeLIDPNMapping(`${lidUser}@lid`, `${pnUser}@s.whatsapp.net`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (!pnUser || typeof pnUser !== 'string') {
|
|
105
|
+
logger_1.default.trace(`No reverse mapping found for LID user: ${lidUser}`);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
// Construct device-specific PN JID
|
|
109
|
+
const lidDevice = decoded.device !== undefined ? decoded.device : 0;
|
|
110
|
+
const pnJid = `${pnUser}:${lidDevice}@s.whatsapp.net`;
|
|
111
|
+
logger_1.default.trace(`Found reverse mapping: ${lid} → ${pnJid}`);
|
|
112
|
+
return pnJid;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
exports.LIDMappingStore = LIDMappingStore;
|
package/lib/Socket/business.d.ts
CHANGED
|
@@ -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>;
|
package/lib/Socket/index.d.ts
CHANGED
|
@@ -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 () => {
|