@ryuu-reinzz/baileys 3.0.0-beta.2 → 3.0.0-beta.21
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/LICENSE +1 -1
- package/README.md +6 -0
- package/WAProto/fix-imports.js +70 -18
- package/WAProto/index.js +197 -160
- package/lib/Defaults/index.js +17 -4
- package/lib/Signal/libsignal.js +63 -2
- package/lib/Signal/lid-mapping.js +170 -70
- package/lib/Socket/Client/websocket.js +5 -1
- package/lib/Socket/business.js +11 -8
- package/lib/Socket/chats.js +55 -28
- package/lib/Socket/index.js +0 -6
- package/lib/Socket/messages-recv.js +152 -75
- package/lib/Socket/messages-send.js +230 -148
- package/lib/Socket/socket.js +69 -15
- package/lib/Utils/auth-utils.js +53 -20
- package/lib/Utils/chat-utils.js +100 -51
- package/lib/Utils/crypto.js +2 -26
- package/lib/Utils/event-buffer.js +33 -7
- package/lib/Utils/generics.js +4 -1
- package/lib/Utils/history.js +46 -5
- package/lib/Utils/identity-change-handler.js +49 -0
- package/lib/Utils/index.js +2 -0
- package/lib/Utils/lt-hash.js +2 -42
- package/lib/Utils/make-mutex.js +20 -27
- package/lib/Utils/message-retry-manager.js +58 -5
- package/lib/Utils/messages-media.js +151 -40
- package/lib/Utils/messages.js +43 -23
- package/lib/Utils/noise-handler.js +139 -85
- package/lib/Utils/process-message.js +57 -14
- package/lib/Utils/reporting-utils.js +258 -0
- package/lib/Utils/sync-action-utils.js +48 -0
- package/lib/Utils/tc-token-utils.js +18 -0
- package/lib/Utils/use-sqlite-auth-state.js +122 -0
- package/lib/WABinary/decode.js +24 -0
- package/lib/WABinary/encode.js +5 -1
- package/lib/WABinary/generic-utils.js +19 -8
- package/package.json +7 -2
package/lib/Defaults/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { proto } from '../../WAProto/index.js';
|
|
|
2
2
|
import { makeLibSignalRepository } from '../Signal/libsignal.js';
|
|
3
3
|
import { Browsers } from '../Utils/browser-utils.js';
|
|
4
4
|
import logger from '../Utils/logger.js';
|
|
5
|
-
const version = [2, 3000,
|
|
5
|
+
const version = [2, 3000, 1033105955];
|
|
6
6
|
export const UNAUTHORIZED_CODES = [401, 403, 419];
|
|
7
7
|
export const DEFAULT_ORIGIN = 'https://web.whatsapp.com';
|
|
8
8
|
export const CALL_VIDEO_PREFIX = 'https://call.whatsapp.com/video/';
|
|
@@ -15,15 +15,20 @@ export const WA_ADV_DEVICE_SIG_PREFIX = Buffer.from([6, 1]);
|
|
|
15
15
|
export const WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX = Buffer.from([6, 5]);
|
|
16
16
|
export const WA_ADV_HOSTED_DEVICE_SIG_PREFIX = Buffer.from([6, 6]);
|
|
17
17
|
export const WA_DEFAULT_EPHEMERAL = 7 * 24 * 60 * 60;
|
|
18
|
+
/** Status messages older than 24 hours are considered expired */
|
|
19
|
+
export const STATUS_EXPIRY_SECONDS = 24 * 60 * 60;
|
|
20
|
+
/** WA Web enforces a 14-day maximum age for placeholder resend requests */
|
|
21
|
+
export const PLACEHOLDER_MAX_AGE_SECONDS = 14 * 24 * 60 * 60;
|
|
18
22
|
export const NOISE_MODE = 'Noise_XX_25519_AESGCM_SHA256\0\0\0\0';
|
|
19
23
|
export const DICT_VERSION = 3;
|
|
20
24
|
export const KEY_BUNDLE_TYPE = Buffer.from([5]);
|
|
21
25
|
export const NOISE_WA_HEADER = Buffer.from([87, 65, 6, DICT_VERSION]); // last is "DICT_VERSION"
|
|
22
26
|
/** from: https://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url */
|
|
23
27
|
export const URL_REGEX = /https:\/\/(?![^:@\/\s]+:[^:@\/\s]+@)[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(:\d+)?(\/[^\s]*)?/g;
|
|
24
|
-
// TODO: Add WA root CA
|
|
25
28
|
export const WA_CERT_DETAILS = {
|
|
26
|
-
SERIAL: 0
|
|
29
|
+
SERIAL: 0,
|
|
30
|
+
ISSUER: 'WhatsAppLongTerm1',
|
|
31
|
+
PUBLIC_KEY: Buffer.from('142375574d0a587166aae71ebe516437c4a28b73e3695c6ce1f7f9545da8ee6b', 'hex')
|
|
27
32
|
};
|
|
28
33
|
export const PROCESSABLE_HISTORY_TYPES = [
|
|
29
34
|
proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP,
|
|
@@ -51,7 +56,9 @@ export const DEFAULT_CONNECTION_CONFIG = {
|
|
|
51
56
|
markOnlineOnConnect: true,
|
|
52
57
|
syncFullHistory: true,
|
|
53
58
|
patchMessageBeforeSending: msg => msg,
|
|
54
|
-
shouldSyncHistoryMessage: () =>
|
|
59
|
+
shouldSyncHistoryMessage: ({ syncType }) => {
|
|
60
|
+
return syncType !== proto.HistorySync.HistorySyncType.FULL;
|
|
61
|
+
},
|
|
55
62
|
shouldIgnoreJid: () => false,
|
|
56
63
|
linkPreviewImageThumbnailWidth: 192,
|
|
57
64
|
transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 3000 },
|
|
@@ -112,4 +119,10 @@ export const DEFAULT_CACHE_TTLS = {
|
|
|
112
119
|
CALL_OFFER: 5 * 60, // 5 minutes
|
|
113
120
|
USER_DEVICES: 5 * 60 // 5 minutes
|
|
114
121
|
};
|
|
122
|
+
export const TimeMs = {
|
|
123
|
+
Minute: 60 * 1000,
|
|
124
|
+
Hour: 60 * 60 * 1000,
|
|
125
|
+
Day: 24 * 60 * 60 * 1000,
|
|
126
|
+
Week: 7 * 24 * 60 * 60 * 1000
|
|
127
|
+
};
|
|
115
128
|
//# sourceMappingURL=index.js.map
|
package/lib/Signal/libsignal.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
// @ts-ignore
|
|
2
2
|
import * as libsignal from 'libsignal';
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import { PreKeyWhisperMessage } from 'libsignal/src/protobufs.js';
|
|
3
5
|
import { LRUCache } from 'lru-cache';
|
|
4
6
|
import { generateSignalPubKey } from '../Utils/index.js';
|
|
5
7
|
import { isHostedLidUser, isHostedPnUser, isLidUser, isPnUser, jidDecode, transferDevice, WAJIDDomains } from '../WABinary/index.js';
|
|
@@ -7,6 +9,28 @@ import { SenderKeyName } from './Group/sender-key-name.js';
|
|
|
7
9
|
import { SenderKeyRecord } from './Group/sender-key-record.js';
|
|
8
10
|
import { GroupCipher, GroupSessionBuilder, SenderKeyDistributionMessage } from './Group/index.js';
|
|
9
11
|
import { LIDMappingStore } from './lid-mapping.js';
|
|
12
|
+
/** Extract identity key from PreKeyWhisperMessage for identity change detection */
|
|
13
|
+
function extractIdentityFromPkmsg(ciphertext) {
|
|
14
|
+
try {
|
|
15
|
+
if (!ciphertext || ciphertext.length < 2) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
// Version byte check (version 3)
|
|
19
|
+
const version = ciphertext[0];
|
|
20
|
+
if ((version & 0xf) !== 3) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
// Parse protobuf (skip version byte)
|
|
24
|
+
const preKeyProto = PreKeyWhisperMessage.decode(ciphertext.slice(1));
|
|
25
|
+
if (preKeyProto.identityKey?.length === 33) {
|
|
26
|
+
return new Uint8Array(preKeyProto.identityKey);
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
10
34
|
export function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
|
|
11
35
|
const lidMapping = new LIDMappingStore(auth.keys, logger, pnToLIDFunc);
|
|
12
36
|
const storage = signalStorage(auth, lidMapping);
|
|
@@ -48,6 +72,17 @@ export function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
|
|
|
48
72
|
async decryptMessage({ jid, type, ciphertext }) {
|
|
49
73
|
const addr = jidToSignalProtocolAddress(jid);
|
|
50
74
|
const session = new libsignal.SessionCipher(storage, addr);
|
|
75
|
+
// Extract and save sender's identity key before decryption for identity change detection
|
|
76
|
+
if (type === 'pkmsg') {
|
|
77
|
+
const identityKey = extractIdentityFromPkmsg(ciphertext);
|
|
78
|
+
if (identityKey) {
|
|
79
|
+
const addrStr = addr.toString();
|
|
80
|
+
const identityChanged = await storage.saveIdentity(addrStr, identityKey);
|
|
81
|
+
if (identityChanged) {
|
|
82
|
+
logger.info({ jid, addr: addrStr }, 'identity key changed or new contact, session will be re-established');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
51
86
|
async function doDecrypt() {
|
|
52
87
|
let result;
|
|
53
88
|
switch (type) {
|
|
@@ -296,7 +331,33 @@ function signalStorage({ creds, keys }, lidMapping) {
|
|
|
296
331
|
await keys.set({ session: { [wireJid]: session.serialize() } });
|
|
297
332
|
},
|
|
298
333
|
isTrustedIdentity: () => {
|
|
299
|
-
return true; //
|
|
334
|
+
return true; // TOFU - Trust on First Use (same as WhatsApp Web)
|
|
335
|
+
},
|
|
336
|
+
loadIdentityKey: async (id) => {
|
|
337
|
+
const wireJid = await resolveLIDSignalAddress(id);
|
|
338
|
+
const { [wireJid]: key } = await keys.get('identity-key', [wireJid]);
|
|
339
|
+
return key || undefined;
|
|
340
|
+
},
|
|
341
|
+
saveIdentity: async (id, identityKey) => {
|
|
342
|
+
const wireJid = await resolveLIDSignalAddress(id);
|
|
343
|
+
const { [wireJid]: existingKey } = await keys.get('identity-key', [wireJid]);
|
|
344
|
+
const keysMatch = existingKey &&
|
|
345
|
+
existingKey.length === identityKey.length &&
|
|
346
|
+
existingKey.every((byte, i) => byte === identityKey[i]);
|
|
347
|
+
if (existingKey && !keysMatch) {
|
|
348
|
+
// Identity changed - clear session and update key
|
|
349
|
+
await keys.set({
|
|
350
|
+
session: { [wireJid]: null },
|
|
351
|
+
'identity-key': { [wireJid]: identityKey }
|
|
352
|
+
});
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
if (!existingKey) {
|
|
356
|
+
// New contact - Trust on First Use (TOFU)
|
|
357
|
+
await keys.set({ 'identity-key': { [wireJid]: identityKey } });
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
return false;
|
|
300
361
|
},
|
|
301
362
|
loadPreKey: async (id) => {
|
|
302
363
|
const keyId = id.toString();
|
|
@@ -7,16 +7,16 @@ export class LIDMappingStore {
|
|
|
7
7
|
ttlAutopurge: true,
|
|
8
8
|
updateAgeOnGet: true
|
|
9
9
|
});
|
|
10
|
+
this.inflightLIDLookups = new Map();
|
|
11
|
+
this.inflightPNLookups = new Map();
|
|
10
12
|
this.keys = keys;
|
|
11
13
|
this.pnToLIDFunc = pnToLIDFunc;
|
|
12
14
|
this.logger = logger;
|
|
13
15
|
}
|
|
14
|
-
/**
|
|
15
|
-
* Store LID-PN mapping - USER LEVEL
|
|
16
|
-
*/
|
|
17
16
|
async storeLIDPNMappings(pairs) {
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
if (pairs.length === 0)
|
|
18
|
+
return;
|
|
19
|
+
const validatedPairs = [];
|
|
20
20
|
for (const { lid, pn } of pairs) {
|
|
21
21
|
if (!((isLidUser(lid) && isPnUser(pn)) || (isPnUser(lid) && isLidUser(pn)))) {
|
|
22
22
|
this.logger.warn(`Invalid LID-PN mapping: ${lid}, ${pn}`);
|
|
@@ -25,67 +25,135 @@ export class LIDMappingStore {
|
|
|
25
25
|
const lidDecoded = jidDecode(lid);
|
|
26
26
|
const pnDecoded = jidDecode(pn);
|
|
27
27
|
if (!lidDecoded || !pnDecoded)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
continue;
|
|
29
|
+
validatedPairs.push({ pnUser: pnDecoded.user, lidUser: lidDecoded.user });
|
|
30
|
+
}
|
|
31
|
+
if (validatedPairs.length === 0)
|
|
32
|
+
return;
|
|
33
|
+
const cacheMissSet = new Set();
|
|
34
|
+
const existingMappings = new Map();
|
|
35
|
+
for (const { pnUser } of validatedPairs) {
|
|
36
|
+
const cached = this.mappingCache.get(`pn:${pnUser}`);
|
|
37
|
+
if (cached) {
|
|
38
|
+
existingMappings.set(pnUser, cached);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
cacheMissSet.add(pnUser);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (cacheMissSet.size > 0) {
|
|
45
|
+
const cacheMisses = [...cacheMissSet];
|
|
46
|
+
this.logger.trace(`Batch fetching ${cacheMisses.length} LID mappings from database`);
|
|
47
|
+
const stored = await this.keys.get('lid-mapping', cacheMisses);
|
|
48
|
+
for (const pnUser of cacheMisses) {
|
|
49
|
+
const existingLidUser = stored[pnUser];
|
|
36
50
|
if (existingLidUser) {
|
|
37
|
-
|
|
51
|
+
existingMappings.set(pnUser, existingLidUser);
|
|
38
52
|
this.mappingCache.set(`pn:${pnUser}`, existingLidUser);
|
|
39
53
|
this.mappingCache.set(`lid:${existingLidUser}`, pnUser);
|
|
40
54
|
}
|
|
41
55
|
}
|
|
56
|
+
}
|
|
57
|
+
const pairMap = {};
|
|
58
|
+
for (const { pnUser, lidUser } of validatedPairs) {
|
|
59
|
+
const existingLidUser = existingMappings.get(pnUser);
|
|
42
60
|
if (existingLidUser === lidUser) {
|
|
43
61
|
this.logger.debug({ pnUser, lidUser }, 'LID mapping already exists, skipping');
|
|
44
62
|
continue;
|
|
45
63
|
}
|
|
46
64
|
pairMap[pnUser] = lidUser;
|
|
47
65
|
}
|
|
66
|
+
if (Object.keys(pairMap).length === 0)
|
|
67
|
+
return;
|
|
48
68
|
this.logger.trace({ pairMap }, `Storing ${Object.keys(pairMap).length} pn mappings`);
|
|
69
|
+
const batchData = {};
|
|
70
|
+
for (const [pnUser, lidUser] of Object.entries(pairMap)) {
|
|
71
|
+
batchData[pnUser] = lidUser;
|
|
72
|
+
batchData[`${lidUser}_reverse`] = pnUser;
|
|
73
|
+
}
|
|
49
74
|
await this.keys.transaction(async () => {
|
|
50
|
-
|
|
51
|
-
await this.keys.set({
|
|
52
|
-
'lid-mapping': {
|
|
53
|
-
[pnUser]: lidUser,
|
|
54
|
-
[`${lidUser}_reverse`]: pnUser
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
this.mappingCache.set(`pn:${pnUser}`, lidUser);
|
|
58
|
-
this.mappingCache.set(`lid:${lidUser}`, pnUser);
|
|
59
|
-
}
|
|
75
|
+
await this.keys.set({ 'lid-mapping': batchData });
|
|
60
76
|
}, 'lid-mapping');
|
|
77
|
+
// Update cache after successful DB write
|
|
78
|
+
for (const [pnUser, lidUser] of Object.entries(pairMap)) {
|
|
79
|
+
this.mappingCache.set(`pn:${pnUser}`, lidUser);
|
|
80
|
+
this.mappingCache.set(`lid:${lidUser}`, pnUser);
|
|
81
|
+
}
|
|
61
82
|
}
|
|
62
|
-
/**
|
|
63
|
-
* Get LID for PN - Returns device-specific LID based on user mapping
|
|
64
|
-
*/
|
|
65
83
|
async getLIDForPN(pn) {
|
|
66
84
|
return (await this.getLIDsForPNs([pn]))?.[0]?.lid || null;
|
|
67
85
|
}
|
|
68
86
|
async getLIDsForPNs(pns) {
|
|
87
|
+
if (pns.length === 0)
|
|
88
|
+
return null;
|
|
89
|
+
const sortedPns = [...new Set(pns)].sort();
|
|
90
|
+
const cacheKey = sortedPns.join(',');
|
|
91
|
+
const inflight = this.inflightLIDLookups.get(cacheKey);
|
|
92
|
+
if (inflight) {
|
|
93
|
+
this.logger.trace(`Coalescing getLIDsForPNs request for ${sortedPns.length} PNs`);
|
|
94
|
+
return inflight;
|
|
95
|
+
}
|
|
96
|
+
const promise = this._getLIDsForPNsImpl(pns);
|
|
97
|
+
this.inflightLIDLookups.set(cacheKey, promise);
|
|
98
|
+
try {
|
|
99
|
+
return await promise;
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
this.inflightLIDLookups.delete(cacheKey);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async _getLIDsForPNsImpl(pns) {
|
|
69
106
|
const usyncFetch = {};
|
|
70
|
-
// mapped from pn to lid mapping to prevent duplication in results later
|
|
71
107
|
const successfulPairs = {};
|
|
108
|
+
const pending = [];
|
|
109
|
+
const addResolvedPair = (pn, decoded, lidUser) => {
|
|
110
|
+
const normalizedLidUser = lidUser.toString();
|
|
111
|
+
if (!normalizedLidUser) {
|
|
112
|
+
this.logger.warn(`Invalid or empty LID user for PN ${pn}: lidUser = "${lidUser}"`);
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
// Push the PN device ID to the LID to maintain device separation
|
|
116
|
+
const pnDevice = decoded.device !== undefined ? decoded.device : 0;
|
|
117
|
+
const deviceSpecificLid = `${normalizedLidUser}${!!pnDevice ? `:${pnDevice}` : ``}@${decoded.server === 'hosted' ? 'hosted.lid' : 'lid'}`;
|
|
118
|
+
this.logger.trace(`getLIDForPN: ${pn} → ${deviceSpecificLid} (user mapping with device ${pnDevice})`);
|
|
119
|
+
successfulPairs[pn] = { lid: deviceSpecificLid, pn };
|
|
120
|
+
return true;
|
|
121
|
+
};
|
|
72
122
|
for (const pn of pns) {
|
|
73
123
|
if (!isPnUser(pn) && !isHostedPnUser(pn))
|
|
74
124
|
continue;
|
|
75
125
|
const decoded = jidDecode(pn);
|
|
76
126
|
if (!decoded)
|
|
77
127
|
continue;
|
|
78
|
-
// Check cache first for PN → LID mapping
|
|
79
128
|
const pnUser = decoded.user;
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
129
|
+
const cached = this.mappingCache.get(`pn:${pnUser}`);
|
|
130
|
+
if (cached && typeof cached === 'string') {
|
|
131
|
+
if (!addResolvedPair(pn, decoded, cached)) {
|
|
132
|
+
this.logger.warn(`Invalid entry for ${pn} (pair not resolved)`);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
pending.push({ pn, pnUser, decoded });
|
|
138
|
+
}
|
|
139
|
+
if (pending.length) {
|
|
140
|
+
const pnUsers = [...new Set(pending.map(item => item.pnUser))];
|
|
141
|
+
const stored = await this.keys.get('lid-mapping', pnUsers);
|
|
142
|
+
for (const pnUser of pnUsers) {
|
|
143
|
+
const lidUser = stored[pnUser];
|
|
144
|
+
if (lidUser && typeof lidUser === 'string') {
|
|
86
145
|
this.mappingCache.set(`pn:${pnUser}`, lidUser);
|
|
87
146
|
this.mappingCache.set(`lid:${lidUser}`, pnUser);
|
|
88
147
|
}
|
|
148
|
+
}
|
|
149
|
+
for (const { pn, pnUser, decoded } of pending) {
|
|
150
|
+
const cached = this.mappingCache.get(`pn:${pnUser}`);
|
|
151
|
+
if (cached && typeof cached === 'string') {
|
|
152
|
+
if (!addResolvedPair(pn, decoded, cached)) {
|
|
153
|
+
this.logger.warn(`Invalid entry for ${pn} (pair not resolved)`);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
89
157
|
else {
|
|
90
158
|
this.logger.trace(`No LID mapping found for PN user ${pnUser}; batch getting from USync`);
|
|
91
159
|
const device = decoded.device || 0;
|
|
@@ -99,19 +167,8 @@ export class LIDMappingStore {
|
|
|
99
167
|
else {
|
|
100
168
|
usyncFetch[normalizedPn]?.push(device);
|
|
101
169
|
}
|
|
102
|
-
continue;
|
|
103
170
|
}
|
|
104
171
|
}
|
|
105
|
-
lidUser = lidUser.toString();
|
|
106
|
-
if (!lidUser) {
|
|
107
|
-
this.logger.warn(`Invalid or empty LID user for PN ${pn}: lidUser = "${lidUser}"`);
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
// Push the PN device ID to the LID to maintain device separation
|
|
111
|
-
const pnDevice = decoded.device !== undefined ? decoded.device : 0;
|
|
112
|
-
const deviceSpecificLid = `${lidUser}${!!pnDevice ? `:${pnDevice}` : ``}@${decoded.server === 'hosted' ? 'hosted.lid' : 'lid'}`;
|
|
113
|
-
this.logger.trace(`getLIDForPN: ${pn} → ${deviceSpecificLid} (user mapping with device ${pnDevice})`);
|
|
114
|
-
successfulPairs[pn] = { lid: deviceSpecificLid, pn };
|
|
115
172
|
}
|
|
116
173
|
if (Object.keys(usyncFetch).length > 0) {
|
|
117
174
|
const result = await this.pnToLIDFunc?.(Object.keys(usyncFetch)); // this function already adds LIDs to mapping
|
|
@@ -134,38 +191,81 @@ export class LIDMappingStore {
|
|
|
134
191
|
}
|
|
135
192
|
}
|
|
136
193
|
else {
|
|
137
|
-
|
|
194
|
+
this.logger.warn('USync fetch yielded no results for pending PNs');
|
|
138
195
|
}
|
|
139
196
|
}
|
|
140
|
-
return Object.values(successfulPairs);
|
|
197
|
+
return Object.values(successfulPairs).length > 0 ? Object.values(successfulPairs) : null;
|
|
141
198
|
}
|
|
142
|
-
/**
|
|
143
|
-
* Get PN for LID - USER LEVEL with device construction
|
|
144
|
-
*/
|
|
145
199
|
async getPNForLID(lid) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (
|
|
200
|
+
return (await this.getPNsForLIDs([lid]))?.[0]?.pn || null;
|
|
201
|
+
}
|
|
202
|
+
async getPNsForLIDs(lids) {
|
|
203
|
+
if (lids.length === 0)
|
|
150
204
|
return null;
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
205
|
+
const sortedLids = [...new Set(lids)].sort();
|
|
206
|
+
const cacheKey = sortedLids.join(',');
|
|
207
|
+
const inflight = this.inflightPNLookups.get(cacheKey);
|
|
208
|
+
if (inflight) {
|
|
209
|
+
this.logger.trace(`Coalescing getPNsForLIDs request for ${sortedLids.length} LIDs`);
|
|
210
|
+
return inflight;
|
|
211
|
+
}
|
|
212
|
+
const promise = this._getPNsForLIDsImpl(lids);
|
|
213
|
+
this.inflightPNLookups.set(cacheKey, promise);
|
|
214
|
+
try {
|
|
215
|
+
return await promise;
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
this.inflightPNLookups.delete(cacheKey);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async _getPNsForLIDsImpl(lids) {
|
|
222
|
+
const successfulPairs = {};
|
|
223
|
+
const pending = [];
|
|
224
|
+
const addResolvedPair = (lid, decoded, pnUser) => {
|
|
158
225
|
if (!pnUser || typeof pnUser !== 'string') {
|
|
159
|
-
|
|
160
|
-
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
const lidDevice = decoded.device !== undefined ? decoded.device : 0;
|
|
229
|
+
const pnJid = `${pnUser}:${lidDevice}@${decoded.domainType === WAJIDDomains.HOSTED_LID ? 'hosted' : 's.whatsapp.net'}`;
|
|
230
|
+
this.logger.trace(`Found reverse mapping: ${lid} → ${pnJid}`);
|
|
231
|
+
successfulPairs[lid] = { lid, pn: pnJid };
|
|
232
|
+
return true;
|
|
233
|
+
};
|
|
234
|
+
for (const lid of lids) {
|
|
235
|
+
if (!isLidUser(lid))
|
|
236
|
+
continue;
|
|
237
|
+
const decoded = jidDecode(lid);
|
|
238
|
+
if (!decoded)
|
|
239
|
+
continue;
|
|
240
|
+
const lidUser = decoded.user;
|
|
241
|
+
const cached = this.mappingCache.get(`lid:${lidUser}`);
|
|
242
|
+
if (cached && typeof cached === 'string') {
|
|
243
|
+
addResolvedPair(lid, decoded, cached);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
pending.push({ lid, lidUser, decoded });
|
|
247
|
+
}
|
|
248
|
+
if (pending.length) {
|
|
249
|
+
const reverseKeys = [...new Set(pending.map(item => `${item.lidUser}_reverse`))];
|
|
250
|
+
const stored = await this.keys.get('lid-mapping', reverseKeys);
|
|
251
|
+
for (const { lid, lidUser, decoded } of pending) {
|
|
252
|
+
let pnUser = this.mappingCache.get(`lid:${lidUser}`);
|
|
253
|
+
if (!pnUser || typeof pnUser !== 'string') {
|
|
254
|
+
pnUser = stored[`${lidUser}_reverse`];
|
|
255
|
+
if (pnUser && typeof pnUser === 'string') {
|
|
256
|
+
this.mappingCache.set(`lid:${lidUser}`, pnUser);
|
|
257
|
+
this.mappingCache.set(`pn:${pnUser}`, lidUser);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (pnUser && typeof pnUser === 'string') {
|
|
261
|
+
addResolvedPair(lid, decoded, pnUser);
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
this.logger.trace(`No reverse mapping found for LID user: ${lidUser}`);
|
|
265
|
+
}
|
|
161
266
|
}
|
|
162
|
-
this.mappingCache.set(`lid:${lidUser}`, pnUser);
|
|
163
267
|
}
|
|
164
|
-
|
|
165
|
-
const lidDevice = decoded.device !== undefined ? decoded.device : 0;
|
|
166
|
-
const pnJid = `${pnUser}:${lidDevice}@${decoded.domainType === WAJIDDomains.HOSTED_LID ? 'hosted' : 's.whatsapp.net'}`;
|
|
167
|
-
this.logger.trace(`Found reverse mapping: ${lid} → ${pnJid}`);
|
|
168
|
-
return pnJid;
|
|
268
|
+
return Object.values(successfulPairs).length ? Object.values(successfulPairs) : null;
|
|
169
269
|
}
|
|
170
270
|
}
|
|
171
271
|
//# sourceMappingURL=lid-mapping.js.map
|
|
@@ -35,11 +35,15 @@ export class WebSocketClient extends AbstractSocketClient {
|
|
|
35
35
|
this.socket?.on(event, (...args) => this.emit(event, ...args));
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
close() {
|
|
38
|
+
async close() {
|
|
39
39
|
if (!this.socket) {
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
|
+
const closePromise = new Promise(resolve => {
|
|
43
|
+
this.socket?.once('close', resolve);
|
|
44
|
+
});
|
|
42
45
|
this.socket.close();
|
|
46
|
+
await closePromise;
|
|
43
47
|
this.socket = null;
|
|
44
48
|
}
|
|
45
49
|
send(str, cb) {
|
package/lib/Socket/business.js
CHANGED
|
@@ -10,35 +10,38 @@ export const makeBusinessSocket = (config) => {
|
|
|
10
10
|
const node = [];
|
|
11
11
|
const simpleFields = ['address', 'email', 'description'];
|
|
12
12
|
node.push(...simpleFields
|
|
13
|
-
.filter(key => args[key])
|
|
13
|
+
.filter(key => args[key] !== undefined && args[key] !== null)
|
|
14
14
|
.map(key => ({
|
|
15
15
|
tag: key,
|
|
16
16
|
attrs: {},
|
|
17
17
|
content: args[key]
|
|
18
18
|
})));
|
|
19
|
-
if (args.websites) {
|
|
19
|
+
if (args.websites !== undefined) {
|
|
20
20
|
node.push(...args.websites.map(website => ({
|
|
21
21
|
tag: 'website',
|
|
22
22
|
attrs: {},
|
|
23
23
|
content: website
|
|
24
24
|
})));
|
|
25
25
|
}
|
|
26
|
-
if (args.hours) {
|
|
26
|
+
if (args.hours !== undefined) {
|
|
27
27
|
node.push({
|
|
28
28
|
tag: 'business_hours',
|
|
29
29
|
attrs: { timezone: args.hours.timezone },
|
|
30
|
-
content: args.hours.days.map(
|
|
30
|
+
content: args.hours.days.map(dayConfig => {
|
|
31
31
|
const base = {
|
|
32
32
|
tag: 'business_hours_config',
|
|
33
|
-
attrs: {
|
|
33
|
+
attrs: {
|
|
34
|
+
day_of_week: dayConfig.day,
|
|
35
|
+
mode: dayConfig.mode
|
|
36
|
+
}
|
|
34
37
|
};
|
|
35
|
-
if (
|
|
38
|
+
if (dayConfig.mode === 'specific_hours') {
|
|
36
39
|
return {
|
|
37
40
|
...base,
|
|
38
41
|
attrs: {
|
|
39
42
|
...base.attrs,
|
|
40
|
-
open_time:
|
|
41
|
-
close_time:
|
|
43
|
+
open_time: dayConfig.openTimeInMinutes,
|
|
44
|
+
close_time: dayConfig.closeTimeInMinutes
|
|
42
45
|
}
|
|
43
46
|
};
|
|
44
47
|
}
|