@ryuu-reinzz/baileys 3.5.0 → 5.0.0
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/README.md +30 -25
- package/WAProto/fix-imports.js +22 -18
- package/WAProto/index.js +22 -18
- package/lib/Defaults/index.js +10 -9
- package/lib/Signal/libsignal.js +46 -19
- package/lib/Signal/lid-mapping.js +6 -0
- package/lib/Socket/chats.js +241 -39
- package/lib/Socket/groups.js +20 -0
- package/lib/Socket/messages-recv.js +736 -314
- package/lib/Socket/messages-send.js +279 -129
- package/lib/Socket/newsletter.js +2 -2
- package/lib/Socket/socket.js +56 -25
- package/lib/Types/{Newsletter.js → Mex.js} +9 -3
- package/lib/Types/State.js +43 -0
- package/lib/Types/index.js +1 -1
- package/lib/Utils/auth-utils.js +12 -0
- package/lib/Utils/chat-utils.js +80 -20
- package/lib/Utils/companion-reg-client-utils.js +35 -0
- package/lib/Utils/decode-wa-message.js +34 -0
- package/lib/Utils/event-buffer.js +49 -1
- package/lib/Utils/generics.js +12 -3
- package/lib/Utils/history.js +12 -9
- package/lib/Utils/identity-change-handler.js +1 -0
- package/lib/Utils/index.js +3 -1
- package/lib/Utils/link-preview.js +2 -2
- package/lib/Utils/message-retry-manager.js +40 -0
- package/lib/Utils/messages-media.js +21 -7
- package/lib/Utils/messages.js +28 -5
- package/lib/Utils/offline-node-processor.js +40 -0
- package/lib/Utils/process-message.js +103 -1
- package/lib/Utils/signal.js +42 -0
- package/lib/Utils/stanza-ack.js +38 -0
- package/lib/Utils/sync-action-utils.js +1 -0
- package/lib/Utils/tc-token-utils.js +149 -4
- package/lib/Utils/validate-connection.js +3 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +26 -3
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
- package/lib/WAUSync/Protocols/index.js +1 -0
- package/lib/WAUSync/USyncQuery.js +6 -2
- package/lib/WAUSync/USyncUser.js +8 -0
- package/package.json +39 -12
package/lib/Socket/socket.js
CHANGED
|
@@ -3,14 +3,15 @@ import { randomBytes } from 'crypto';
|
|
|
3
3
|
import { URL } from 'url';
|
|
4
4
|
import { promisify } from 'util';
|
|
5
5
|
import { proto } from '../../WAProto/index.js';
|
|
6
|
-
import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT,
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
6
|
+
import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT, NOISE_WA_HEADER, PROCESSABLE_HISTORY_TYPES, TimeMs, UPLOAD_TIMEOUT } from '../Defaults/index.js';
|
|
7
|
+
import { QueryIds, ReachoutTimelockEnforcementType } from '../Types/index.js';
|
|
8
|
+
import { DisconnectReason, XWAPaths } from '../Types/index.js';
|
|
9
|
+
import { addTransactionCapability, aesEncryptCTR, bindWaitForConnectionUpdate, buildPairingQRData, bytesToCrockford, configureSuccessfulPairing, Curve, derivePairingCodeKey, generateLoginNode, generateMdTagPrefix, generateRegistrationNode, getCodeFromWSError, getCompanionPlatformId, getErrorCodeFromStreamError, getNextPreKeysNode, makeEventBuffer, makeNoiseHandler, promiseTimeout, signedKeyPair, xmppSignedPreKey } from '../Utils/index.js';
|
|
10
10
|
import { assertNodeErrorFree, binaryNodeToString, encodeBinaryNode, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildren, isLidUser, jidDecode, jidEncode, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
11
11
|
import { BinaryInfo } from '../WAM/BinaryInfo.js';
|
|
12
12
|
import { USyncQuery, USyncUser } from '../WAUSync/index.js';
|
|
13
13
|
import { WebSocketClient } from './Client/index.js';
|
|
14
|
+
import { executeWMexQuery } from './mex.js';
|
|
14
15
|
/**
|
|
15
16
|
* Connects to WA servers and performs:
|
|
16
17
|
* - simple queries (no retry mechanism, wait for connection establishment)
|
|
@@ -270,6 +271,7 @@ export const makeSocket = (config) => {
|
|
|
270
271
|
let keepAliveReq;
|
|
271
272
|
let qrTimer;
|
|
272
273
|
let closed = false;
|
|
274
|
+
const socketEndHandlers = [];
|
|
273
275
|
/** log & process any unexpected errors */
|
|
274
276
|
const onUnexpectedError = (err, msg) => {
|
|
275
277
|
logger.error({ err }, `unexpected error in '${msg}'`);
|
|
@@ -344,25 +346,16 @@ export const makeSocket = (config) => {
|
|
|
344
346
|
const countChild = getBinaryNodeChild(result, 'count');
|
|
345
347
|
return +countChild.attrs.value;
|
|
346
348
|
};
|
|
347
|
-
//
|
|
349
|
+
// WAWeb has no time throttle here; the server drives uploads via PreKeyLow notifications.
|
|
348
350
|
let uploadPreKeysPromise = null;
|
|
349
|
-
let lastUploadTime = 0;
|
|
350
351
|
/** generates and uploads a set of pre-keys to the server */
|
|
351
|
-
const uploadPreKeys = async (count = MIN_PREKEY_COUNT
|
|
352
|
-
// Check minimum interval (except for retries)
|
|
353
|
-
if (retryCount === 0) {
|
|
354
|
-
const timeSinceLastUpload = Date.now() - lastUploadTime;
|
|
355
|
-
if (timeSinceLastUpload < MIN_UPLOAD_INTERVAL) {
|
|
356
|
-
logger.debug(`Skipping upload, only ${timeSinceLastUpload}ms since last upload`);
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
// Prevent multiple concurrent uploads
|
|
352
|
+
const uploadPreKeys = async (count = MIN_PREKEY_COUNT) => {
|
|
361
353
|
if (uploadPreKeysPromise) {
|
|
362
354
|
logger.debug('Pre-key upload already in progress, waiting for completion');
|
|
363
355
|
await uploadPreKeysPromise;
|
|
356
|
+
return;
|
|
364
357
|
}
|
|
365
|
-
const uploadLogic = async () => {
|
|
358
|
+
const uploadLogic = async (retryCount) => {
|
|
366
359
|
logger.info({ count, retryCount }, 'uploading pre-keys');
|
|
367
360
|
// Generate and save pre-keys atomically (prevents ID collisions on retry)
|
|
368
361
|
const node = await keys.transaction(async () => {
|
|
@@ -370,29 +363,28 @@ export const makeSocket = (config) => {
|
|
|
370
363
|
const { update, node } = await getNextPreKeysNode({ creds, keys }, count);
|
|
371
364
|
// Update credentials immediately to prevent duplicate IDs on retry
|
|
372
365
|
ev.emit('creds.update', update);
|
|
373
|
-
return node;
|
|
366
|
+
return node;
|
|
374
367
|
}, creds?.me?.id || 'upload-pre-keys');
|
|
375
368
|
// Upload to server (outside transaction, can fail without affecting local keys)
|
|
376
369
|
try {
|
|
377
370
|
await query(node);
|
|
378
371
|
logger.info({ count }, 'uploaded pre-keys successfully');
|
|
379
|
-
lastUploadTime = Date.now();
|
|
380
372
|
}
|
|
381
373
|
catch (uploadError) {
|
|
382
374
|
logger.error({ uploadError: uploadError.toString(), count }, 'Failed to upload pre-keys to server');
|
|
383
|
-
//
|
|
375
|
+
// Recurse into uploadLogic; calling uploadPreKeys would await its own in-flight promise.
|
|
384
376
|
if (retryCount < 3) {
|
|
385
377
|
const backoffDelay = Math.min(1000 * Math.pow(2, retryCount), 10000);
|
|
386
378
|
logger.info(`Retrying pre-key upload in ${backoffDelay}ms`);
|
|
387
379
|
await new Promise(resolve => setTimeout(resolve, backoffDelay));
|
|
388
|
-
return
|
|
380
|
+
return uploadLogic(retryCount + 1);
|
|
389
381
|
}
|
|
390
382
|
throw uploadError;
|
|
391
383
|
}
|
|
392
384
|
};
|
|
393
385
|
// Add timeout protection
|
|
394
386
|
uploadPreKeysPromise = Promise.race([
|
|
395
|
-
uploadLogic(),
|
|
387
|
+
uploadLogic(0),
|
|
396
388
|
new Promise((_, reject) => setTimeout(() => reject(new Boom('Pre-key upload timeout', { statusCode: 408 })), UPLOAD_TIMEOUT))
|
|
397
389
|
]);
|
|
398
390
|
try {
|
|
@@ -486,12 +478,21 @@ export const makeSocket = (config) => {
|
|
|
486
478
|
ws.removeAllListeners('close');
|
|
487
479
|
ws.removeAllListeners('open');
|
|
488
480
|
ws.removeAllListeners('message');
|
|
481
|
+
signalRepository.close?.();
|
|
489
482
|
if (!ws.isClosed && !ws.isClosing) {
|
|
490
483
|
try {
|
|
491
484
|
await ws.close();
|
|
492
485
|
}
|
|
493
486
|
catch { }
|
|
494
487
|
}
|
|
488
|
+
for (const handler of socketEndHandlers) {
|
|
489
|
+
try {
|
|
490
|
+
await handler(error);
|
|
491
|
+
}
|
|
492
|
+
catch (err) {
|
|
493
|
+
logger.error({ err }, 'error in socket end handler');
|
|
494
|
+
}
|
|
495
|
+
}
|
|
495
496
|
ev.emit('connection.update', {
|
|
496
497
|
connection: 'close',
|
|
497
498
|
lastDisconnect: {
|
|
@@ -500,6 +501,7 @@ export const makeSocket = (config) => {
|
|
|
500
501
|
}
|
|
501
502
|
});
|
|
502
503
|
ev.removeAllListeners('connection.update');
|
|
504
|
+
ev.destroy();
|
|
503
505
|
};
|
|
504
506
|
const waitForSocketOpen = async () => {
|
|
505
507
|
if (ws.isOpen) {
|
|
@@ -629,7 +631,7 @@ export const makeSocket = (config) => {
|
|
|
629
631
|
{
|
|
630
632
|
tag: 'companion_platform_id',
|
|
631
633
|
attrs: {},
|
|
632
|
-
content:
|
|
634
|
+
content: getCompanionPlatformId(browser)
|
|
633
635
|
},
|
|
634
636
|
{
|
|
635
637
|
tag: 'companion_platform_display',
|
|
@@ -712,7 +714,7 @@ export const makeSocket = (config) => {
|
|
|
712
714
|
return;
|
|
713
715
|
}
|
|
714
716
|
const ref = refNode.content.toString('utf-8');
|
|
715
|
-
const qr =
|
|
717
|
+
const qr = buildPairingQRData(ref, noiseKeyB64, identityKeyB64, advB64, browser);
|
|
716
718
|
ev.emit('connection.update', { qr });
|
|
717
719
|
qrTimer = setTimeout(genPairQR, qrMs);
|
|
718
720
|
qrMs = qrTimeout || 20000; // shorter subsequent qrs
|
|
@@ -891,6 +893,32 @@ export const makeSocket = (config) => {
|
|
|
891
893
|
logger.debug({ error }, 'failed to send unified_session telemetry');
|
|
892
894
|
}
|
|
893
895
|
};
|
|
896
|
+
const registerSocketEndHandler = (handler) => {
|
|
897
|
+
socketEndHandlers.push(handler);
|
|
898
|
+
};
|
|
899
|
+
/**
|
|
900
|
+
* Fetches your account's standing when it comes to restrictions.
|
|
901
|
+
* @returns Returns the state of the restrictions.
|
|
902
|
+
*/
|
|
903
|
+
const fetchAccountReachoutTimelock = async () => {
|
|
904
|
+
const queryResult = await executeWMexQuery({}, QueryIds.REACHOUT_TIMELOCK, XWAPaths.xwa2_fetch_account_reachout_timelock, query, generateMessageTag);
|
|
905
|
+
const result = {
|
|
906
|
+
isActive: !!queryResult?.is_active,
|
|
907
|
+
timeEnforcementEnds: queryResult?.time_enforcement_ends && queryResult?.time_enforcement_ends !== '0'
|
|
908
|
+
? new Date(parseInt(queryResult.time_enforcement_ends, 10) * 1000)
|
|
909
|
+
: undefined,
|
|
910
|
+
enforcementType: queryResult?.enforcement_type ?? ReachoutTimelockEnforcementType.DEFAULT
|
|
911
|
+
};
|
|
912
|
+
ev.emit('connection.update', { reachoutTimeLock: result });
|
|
913
|
+
return result;
|
|
914
|
+
};
|
|
915
|
+
/**
|
|
916
|
+
* Fetches your account's new chat limits.
|
|
917
|
+
* @returns Returns the quota and the usage.
|
|
918
|
+
*/
|
|
919
|
+
const fetchNewChatMessageCap = async () => {
|
|
920
|
+
return executeWMexQuery({ input: { type: 'INDIVIDUAL_NEW_CHAT_MSG' } }, QueryIds.MESSAGE_CAPPING_INFO, XWAPaths.xwa2_message_capping_info, query, generateMessageTag);
|
|
921
|
+
};
|
|
894
922
|
return {
|
|
895
923
|
type: 'md',
|
|
896
924
|
ws,
|
|
@@ -908,6 +936,7 @@ export const makeSocket = (config) => {
|
|
|
908
936
|
sendNode,
|
|
909
937
|
logout,
|
|
910
938
|
end,
|
|
939
|
+
registerSocketEndHandler,
|
|
911
940
|
onUnexpectedError,
|
|
912
941
|
uploadPreKeys,
|
|
913
942
|
uploadPreKeysToServerIfRequired,
|
|
@@ -921,7 +950,9 @@ export const makeSocket = (config) => {
|
|
|
921
950
|
waitForConnectionUpdate: bindWaitForConnectionUpdate(ev),
|
|
922
951
|
sendWAMBuffer,
|
|
923
952
|
executeUSyncQuery,
|
|
924
|
-
onWhatsApp
|
|
953
|
+
onWhatsApp,
|
|
954
|
+
fetchAccountReachoutTimelock,
|
|
955
|
+
fetchNewChatMessageCap
|
|
925
956
|
};
|
|
926
957
|
};
|
|
927
958
|
/**
|
|
@@ -9,9 +9,13 @@ export var XWAPaths;
|
|
|
9
9
|
XWAPaths["xwa2_newsletter_unmute_v2"] = "xwa2_newsletter_unmute_v2";
|
|
10
10
|
XWAPaths["xwa2_newsletter_follow"] = "xwa2_newsletter_follow";
|
|
11
11
|
XWAPaths["xwa2_newsletter_unfollow"] = "xwa2_newsletter_unfollow";
|
|
12
|
+
XWAPaths["xwa2_newsletter_join_v2"] = "xwa2_newsletter_join_v2";
|
|
13
|
+
XWAPaths["xwa2_newsletter_leave_v2"] = "xwa2_newsletter_leave_v2";
|
|
12
14
|
XWAPaths["xwa2_newsletter_change_owner"] = "xwa2_newsletter_change_owner";
|
|
13
15
|
XWAPaths["xwa2_newsletter_demote"] = "xwa2_newsletter_demote";
|
|
14
16
|
XWAPaths["xwa2_newsletter_delete_v2"] = "xwa2_newsletter_delete_v2";
|
|
17
|
+
XWAPaths["xwa2_fetch_account_reachout_timelock"] = "xwa2_fetch_account_reachout_timelock";
|
|
18
|
+
XWAPaths["xwa2_message_capping_info"] = "xwa2_message_capping_info";
|
|
15
19
|
})(XWAPaths || (XWAPaths = {}));
|
|
16
20
|
export var QueryIds;
|
|
17
21
|
(function (QueryIds) {
|
|
@@ -19,13 +23,15 @@ export var QueryIds;
|
|
|
19
23
|
QueryIds["UPDATE_METADATA"] = "24250201037901610";
|
|
20
24
|
QueryIds["METADATA"] = "6563316087068696";
|
|
21
25
|
QueryIds["SUBSCRIBERS"] = "9783111038412085";
|
|
22
|
-
QueryIds["FOLLOW"] = "
|
|
23
|
-
QueryIds["UNFOLLOW"] = "
|
|
26
|
+
QueryIds["FOLLOW"] = "24404358912487870";
|
|
27
|
+
QueryIds["UNFOLLOW"] = "9767147403369991";
|
|
24
28
|
QueryIds["MUTE"] = "29766401636284406";
|
|
25
29
|
QueryIds["UNMUTE"] = "9864994326891137";
|
|
26
30
|
QueryIds["ADMIN_COUNT"] = "7130823597031706";
|
|
27
31
|
QueryIds["CHANGE_OWNER"] = "7341777602580933";
|
|
28
32
|
QueryIds["DEMOTE"] = "6551828931592903";
|
|
29
33
|
QueryIds["DELETE"] = "30062808666639665";
|
|
34
|
+
QueryIds["REACHOUT_TIMELOCK"] = "23983697327930364";
|
|
35
|
+
QueryIds["MESSAGE_CAPPING_INFO"] = "24503548349331633";
|
|
30
36
|
})(QueryIds || (QueryIds = {}));
|
|
31
|
-
//# sourceMappingURL=
|
|
37
|
+
//# sourceMappingURL=Mex.js.map
|
package/lib/Types/State.js
CHANGED
|
@@ -10,4 +10,47 @@ export var SyncState;
|
|
|
10
10
|
/** Initial sync is complete, or was skipped. The socket is fully operational and events are processed in real-time. */
|
|
11
11
|
SyncState[SyncState["Online"] = 3] = "Online";
|
|
12
12
|
})(SyncState || (SyncState = {}));
|
|
13
|
+
export var ReachoutTimelockEnforcementType;
|
|
14
|
+
(function (ReachoutTimelockEnforcementType) {
|
|
15
|
+
ReachoutTimelockEnforcementType["BIZ_COMMERCE_VIOLATION_ALCOHOL"] = "BIZ_COMMERCE_VIOLATION_ALCOHOL";
|
|
16
|
+
ReachoutTimelockEnforcementType["BIZ_COMMERCE_VIOLATION_ADULT"] = "BIZ_COMMERCE_VIOLATION_ADULT";
|
|
17
|
+
ReachoutTimelockEnforcementType["BIZ_COMMERCE_VIOLATION_ANIMALS"] = "BIZ_COMMERCE_VIOLATION_ANIMALS";
|
|
18
|
+
ReachoutTimelockEnforcementType["BIZ_COMMERCE_VIOLATION_BODY_PARTS_FLUIDS"] = "BIZ_COMMERCE_VIOLATION_BODY_PARTS_FLUIDS";
|
|
19
|
+
ReachoutTimelockEnforcementType["BIZ_COMMERCE_VIOLATION_DATING"] = "BIZ_COMMERCE_VIOLATION_DATING";
|
|
20
|
+
ReachoutTimelockEnforcementType["BIZ_COMMERCE_VIOLATION_DIGITAL_SERVICES_PRODUCTS"] = "BIZ_COMMERCE_VIOLATION_DIGITAL_SERVICES_PRODUCTS";
|
|
21
|
+
ReachoutTimelockEnforcementType["BIZ_COMMERCE_VIOLATION_DRUGS"] = "BIZ_COMMERCE_VIOLATION_DRUGS";
|
|
22
|
+
ReachoutTimelockEnforcementType["BIZ_COMMERCE_VIOLATION_DRUGS_ONLY_OTC"] = "BIZ_COMMERCE_VIOLATION_DRUGS_ONLY_OTC";
|
|
23
|
+
ReachoutTimelockEnforcementType["BIZ_COMMERCE_VIOLATION_GAMBLING"] = "BIZ_COMMERCE_VIOLATION_GAMBLING";
|
|
24
|
+
ReachoutTimelockEnforcementType["BIZ_COMMERCE_VIOLATION_HEALTHCARE"] = "BIZ_COMMERCE_VIOLATION_HEALTHCARE";
|
|
25
|
+
ReachoutTimelockEnforcementType["BIZ_COMMERCE_VIOLATION_REAL_FAKE_CURRENCY"] = "BIZ_COMMERCE_VIOLATION_REAL_FAKE_CURRENCY";
|
|
26
|
+
ReachoutTimelockEnforcementType["BIZ_COMMERCE_VIOLATION_SUPPLEMENTS"] = "BIZ_COMMERCE_VIOLATION_SUPPLEMENTS";
|
|
27
|
+
ReachoutTimelockEnforcementType["BIZ_COMMERCE_VIOLATION_TOBACCO"] = "BIZ_COMMERCE_VIOLATION_TOBACCO";
|
|
28
|
+
ReachoutTimelockEnforcementType["BIZ_COMMERCE_VIOLATION_VIOLENT_CONTENT"] = "BIZ_COMMERCE_VIOLATION_VIOLENT_CONTENT";
|
|
29
|
+
ReachoutTimelockEnforcementType["BIZ_COMMERCE_VIOLATION_WEAPONS"] = "BIZ_COMMERCE_VIOLATION_WEAPONS";
|
|
30
|
+
ReachoutTimelockEnforcementType["BIZ_QUALITY"] = "BIZ_QUALITY";
|
|
31
|
+
/** This means there is no restriction */
|
|
32
|
+
ReachoutTimelockEnforcementType["DEFAULT"] = "DEFAULT";
|
|
33
|
+
ReachoutTimelockEnforcementType["WEB_COMPANION_ONLY"] = "WEB_COMPANION_ONLY";
|
|
34
|
+
})(ReachoutTimelockEnforcementType || (ReachoutTimelockEnforcementType = {}));
|
|
35
|
+
export var NewChatMessageCappingStatusType;
|
|
36
|
+
(function (NewChatMessageCappingStatusType) {
|
|
37
|
+
NewChatMessageCappingStatusType["NONE"] = "NONE";
|
|
38
|
+
NewChatMessageCappingStatusType["FIRST_WARNING"] = "FIRST_WARNING";
|
|
39
|
+
NewChatMessageCappingStatusType["SECOND_WARNING"] = "SECOND_WARNING";
|
|
40
|
+
NewChatMessageCappingStatusType["CAPPED"] = "CAPPED";
|
|
41
|
+
})(NewChatMessageCappingStatusType || (NewChatMessageCappingStatusType = {}));
|
|
42
|
+
export var NewChatMessageCappingMVStatusType;
|
|
43
|
+
(function (NewChatMessageCappingMVStatusType) {
|
|
44
|
+
NewChatMessageCappingMVStatusType["NOT_ELIGIBLE"] = "NOT_ELIGIBLE";
|
|
45
|
+
NewChatMessageCappingMVStatusType["NOT_ACTIVE"] = "NOT_ACTIVE";
|
|
46
|
+
NewChatMessageCappingMVStatusType["ACTIVE"] = "ACTIVE";
|
|
47
|
+
NewChatMessageCappingMVStatusType["ACTIVE_UPGRADE_AVAILABLE"] = "ACTIVE_UPGRADE_AVAILABLE";
|
|
48
|
+
})(NewChatMessageCappingMVStatusType || (NewChatMessageCappingMVStatusType = {}));
|
|
49
|
+
export var NewChatMessageCappingOTEStatusType;
|
|
50
|
+
(function (NewChatMessageCappingOTEStatusType) {
|
|
51
|
+
NewChatMessageCappingOTEStatusType["NOT_ELIGIBLE"] = "NOT_ELIGIBLE";
|
|
52
|
+
NewChatMessageCappingOTEStatusType["ELIGIBLE"] = "ELIGIBLE";
|
|
53
|
+
NewChatMessageCappingOTEStatusType["ACTIVE_IN_CURRENT_CYCLE"] = "ACTIVE_IN_CURRENT_CYCLE";
|
|
54
|
+
NewChatMessageCappingOTEStatusType["EXHAUSTED"] = "EXHAUSTED";
|
|
55
|
+
})(NewChatMessageCappingOTEStatusType || (NewChatMessageCappingOTEStatusType = {}));
|
|
13
56
|
//# sourceMappingURL=State.js.map
|
package/lib/Types/index.js
CHANGED
|
@@ -9,7 +9,7 @@ export * from './Events.js';
|
|
|
9
9
|
export * from './Product.js';
|
|
10
10
|
export * from './Call.js';
|
|
11
11
|
export * from './Signal.js';
|
|
12
|
-
export * from './
|
|
12
|
+
export * from './Mex.js';
|
|
13
13
|
export var DisconnectReason;
|
|
14
14
|
(function (DisconnectReason) {
|
|
15
15
|
DisconnectReason[DisconnectReason["connectionClosed"] = 428] = "connectionClosed";
|
package/lib/Utils/auth-utils.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import NodeCache from '@cacheable/node-cache';
|
|
2
|
+
import { Boom } from '@hapi/boom';
|
|
2
3
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
3
4
|
import { Mutex } from 'async-mutex';
|
|
4
5
|
import { randomBytes } from 'crypto';
|
|
@@ -264,6 +265,17 @@ export const addTransactionCapability = (state, logger, { maxCommitRetries, dela
|
|
|
264
265
|
}
|
|
265
266
|
};
|
|
266
267
|
};
|
|
268
|
+
/**
|
|
269
|
+
* Returns the authenticated user's JID, or throws a Boom-401 if creds are not yet authenticated.
|
|
270
|
+
* Use this anywhere we'd otherwise reach for `creds.me!.id` to fail fast with a descriptive error.
|
|
271
|
+
*/
|
|
272
|
+
export const assertMeId = (creds) => {
|
|
273
|
+
const id = creds.me?.id;
|
|
274
|
+
if (!id) {
|
|
275
|
+
throw new Boom('Cannot proceed: socket is not authenticated yet (creds.me.id is missing)', { statusCode: 401 });
|
|
276
|
+
}
|
|
277
|
+
return id;
|
|
278
|
+
};
|
|
267
279
|
export const initAuthCreds = () => {
|
|
268
280
|
const identityKey = Curve.generateKeyPair();
|
|
269
281
|
return {
|
package/lib/Utils/chat-utils.js
CHANGED
|
@@ -38,7 +38,7 @@ const to64BitNetworkOrder = (e) => {
|
|
|
38
38
|
buff.writeUint32BE(e, 4);
|
|
39
39
|
return buff;
|
|
40
40
|
};
|
|
41
|
-
const makeLtHashGenerator = ({ indexValueMap, hash }) => {
|
|
41
|
+
export const makeLtHashGenerator = ({ indexValueMap, hash }) => {
|
|
42
42
|
indexValueMap = { ...indexValueMap };
|
|
43
43
|
const addBuffs = [];
|
|
44
44
|
const subBuffs = [];
|
|
@@ -48,7 +48,10 @@ const makeLtHashGenerator = ({ indexValueMap, hash }) => {
|
|
|
48
48
|
const prevOp = indexValueMap[indexMacBase64];
|
|
49
49
|
if (operation === proto.SyncdMutation.SyncdOperation.REMOVE) {
|
|
50
50
|
if (!prevOp) {
|
|
51
|
-
|
|
51
|
+
// WA Web does not throw here — it logs a warning and skips the subtract.
|
|
52
|
+
// The missing REMOVE will cause an LTHash mismatch, which is handled
|
|
53
|
+
// by the MAC validation layer (snapshot recovery or retry).
|
|
54
|
+
return;
|
|
52
55
|
}
|
|
53
56
|
// remove from index value mac, since this mutation is erased
|
|
54
57
|
delete indexValueMap[indexMacBase64];
|
|
@@ -80,10 +83,33 @@ const generatePatchMac = (snapshotMac, valueMacs, version, type, key) => {
|
|
|
80
83
|
return hmacSign(total, key);
|
|
81
84
|
};
|
|
82
85
|
export const newLTHashState = () => ({ version: 0, hash: Buffer.alloc(128), indexValueMap: {} });
|
|
86
|
+
export const ensureLTHashStateVersion = (state) => {
|
|
87
|
+
if (typeof state.version !== 'number' || isNaN(state.version)) {
|
|
88
|
+
state.version = 0;
|
|
89
|
+
}
|
|
90
|
+
return state;
|
|
91
|
+
};
|
|
92
|
+
export const MAX_SYNC_ATTEMPTS = 2;
|
|
93
|
+
/**
|
|
94
|
+
* Check if an error is a missing app state sync key.
|
|
95
|
+
* WA Web treats these as "Blocked" (waits for key arrival), not fatal.
|
|
96
|
+
* In Baileys we retry with a snapshot which may use a different key.
|
|
97
|
+
*/
|
|
98
|
+
export const isMissingKeyError = (error) => {
|
|
99
|
+
return error?.data?.isMissingKey === true;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Determines if an app state sync error is unrecoverable.
|
|
103
|
+
* TypeError indicates a WASM crash; otherwise we give up after MAX_SYNC_ATTEMPTS.
|
|
104
|
+
* Missing keys are NOT checked here — they are handled separately as "Blocked".
|
|
105
|
+
*/
|
|
106
|
+
export const isAppStateSyncIrrecoverable = (error, attempts) => {
|
|
107
|
+
return attempts >= MAX_SYNC_ATTEMPTS || error?.name === 'TypeError';
|
|
108
|
+
};
|
|
83
109
|
export const encodeSyncdPatch = async ({ type, index, syncAction, apiVersion, operation }, myAppStateKeyId, state, getAppStateSyncKey) => {
|
|
84
110
|
const key = !!myAppStateKeyId ? await getAppStateSyncKey(myAppStateKeyId) : undefined;
|
|
85
111
|
if (!key) {
|
|
86
|
-
throw new Boom(`myAppStateKey ("${myAppStateKeyId}") not present`, {
|
|
112
|
+
throw new Boom(`myAppStateKey ("${myAppStateKeyId}") not present`, { data: { isMissingKey: true } });
|
|
87
113
|
}
|
|
88
114
|
const encKeyId = Buffer.from(myAppStateKeyId, 'base64');
|
|
89
115
|
state = { ...state, indexValueMap: { ...state.indexValueMap } };
|
|
@@ -139,17 +165,36 @@ export const decodeSyncdMutations = async (msgMutations, initialState, getAppSta
|
|
|
139
165
|
// otherwise, if it's only a record -- it'll be a SET mutation
|
|
140
166
|
const operation = 'operation' in msgMutation ? msgMutation.operation : proto.SyncdMutation.SyncdOperation.SET;
|
|
141
167
|
const record = 'record' in msgMutation && !!msgMutation.record ? msgMutation.record : msgMutation;
|
|
142
|
-
|
|
168
|
+
let key;
|
|
169
|
+
try {
|
|
170
|
+
key = await getKey(record.keyId.id);
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
// Missing-key errors must propagate so the orchestrator can park the
|
|
174
|
+
// collection (Blocked) and retry when APP_STATE_SYNC_KEY_SHARE arrives.
|
|
175
|
+
// Other errors → individual record corruption, skip and keep going.
|
|
176
|
+
if (isMissingKeyError(err))
|
|
177
|
+
throw err;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
143
180
|
const content = record.value.blob;
|
|
144
181
|
const encContent = content.subarray(0, -32);
|
|
145
182
|
const ogValueMac = content.subarray(-32);
|
|
146
183
|
if (validateMacs) {
|
|
147
184
|
const contentHmac = generateMac(operation, encContent, record.keyId.id, key.valueMacKey);
|
|
148
185
|
if (Buffer.compare(contentHmac, ogValueMac) !== 0) {
|
|
149
|
-
|
|
186
|
+
// HMAC verification failed — skip this record
|
|
187
|
+
continue;
|
|
150
188
|
}
|
|
151
189
|
}
|
|
152
|
-
|
|
190
|
+
let result;
|
|
191
|
+
try {
|
|
192
|
+
result = aesDecrypt(encContent, key.valueEncryptionKey);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
// decrypt failed — skip this record instead of aborting
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
153
198
|
const syncAction = proto.SyncActionData.decode(result);
|
|
154
199
|
if (validateMacs) {
|
|
155
200
|
const hmac = hmacSign(syncAction.index, key.indexKey);
|
|
@@ -175,8 +220,7 @@ export const decodeSyncdMutations = async (msgMutations, initialState, getAppSta
|
|
|
175
220
|
const keyEnc = await getAppStateSyncKey(base64Key);
|
|
176
221
|
if (!keyEnc) {
|
|
177
222
|
throw new Boom(`failed to find key "${base64Key}" to decode mutation`, {
|
|
178
|
-
|
|
179
|
-
data: { msgMutations }
|
|
223
|
+
data: { isMissingKey: true, msgMutations }
|
|
180
224
|
});
|
|
181
225
|
}
|
|
182
226
|
const keys = mutationKeys(keyEnc.keyData);
|
|
@@ -189,7 +233,7 @@ export const decodeSyncdPatch = async (msg, name, initialState, getAppStateSyncK
|
|
|
189
233
|
const base64Key = Buffer.from(msg.keyId.id).toString('base64');
|
|
190
234
|
const mainKeyObj = await getAppStateSyncKey(base64Key);
|
|
191
235
|
if (!mainKeyObj) {
|
|
192
|
-
throw new Boom(`failed to find key "${base64Key}" to decode patch`, {
|
|
236
|
+
throw new Boom(`failed to find key "${base64Key}" to decode patch`, { data: { isMissingKey: true, msg } });
|
|
193
237
|
}
|
|
194
238
|
const mainKey = mutationKeys(mainKeyObj.keyData);
|
|
195
239
|
const mutationmacs = msg.mutations.map(mutation => mutation.record.value.blob.slice(-32));
|
|
@@ -250,7 +294,7 @@ export const downloadExternalPatch = async (blob, options) => {
|
|
|
250
294
|
const syncData = proto.SyncdMutations.decode(buffer);
|
|
251
295
|
return syncData;
|
|
252
296
|
};
|
|
253
|
-
export const decodeSyncdSnapshot = async (name, snapshot, getAppStateSyncKey, minimumVersionNumber, validateMacs = true) => {
|
|
297
|
+
export const decodeSyncdSnapshot = async (name, snapshot, getAppStateSyncKey, minimumVersionNumber, validateMacs = true, logger) => {
|
|
254
298
|
const newState = newLTHashState();
|
|
255
299
|
newState.version = toNumber(snapshot.version.version);
|
|
256
300
|
const mutationMap = {};
|
|
@@ -267,12 +311,17 @@ export const decodeSyncdSnapshot = async (name, snapshot, getAppStateSyncKey, mi
|
|
|
267
311
|
const base64Key = Buffer.from(snapshot.keyId.id).toString('base64');
|
|
268
312
|
const keyEnc = await getAppStateSyncKey(base64Key);
|
|
269
313
|
if (!keyEnc) {
|
|
270
|
-
throw new Boom(`failed to find key "${base64Key}" to decode mutation
|
|
314
|
+
throw new Boom(`failed to find key "${base64Key}" to decode mutation`, { data: { isMissingKey: true } });
|
|
271
315
|
}
|
|
272
316
|
const result = mutationKeys(keyEnc.keyData);
|
|
273
317
|
const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey);
|
|
274
318
|
if (Buffer.compare(snapshot.mac, computedSnapshotMac) !== 0) {
|
|
275
|
-
|
|
319
|
+
// LTHash verification may fail when decodeSyncdMutations skipped undecryptable
|
|
320
|
+
// records (poisoned server-side snapshot); the aggregate client hash diverges
|
|
321
|
+
// from the server-computed mac. Fall through with a warning so the session stays
|
|
322
|
+
// alive with partial state, symmetric to how decodePatches handles its own
|
|
323
|
+
// LTHash mismatch a few lines below.
|
|
324
|
+
logger?.warn({ name, version: newState.version }, 'LTHash verification failed on snapshot, continuing with partial state');
|
|
276
325
|
}
|
|
277
326
|
}
|
|
278
327
|
return {
|
|
@@ -297,24 +346,34 @@ export const decodePatches = async (name, syncds, initial, getAppStateSyncKey, o
|
|
|
297
346
|
const patchVersion = toNumber(version.version);
|
|
298
347
|
newState.version = patchVersion;
|
|
299
348
|
const shouldMutate = typeof minimumVersionNumber === 'undefined' || patchVersion > minimumVersionNumber;
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
349
|
+
let decodeResult;
|
|
350
|
+
try {
|
|
351
|
+
decodeResult = await decodeSyncdPatch(syncd, name, newState, getAppStateSyncKey, shouldMutate
|
|
352
|
+
? mutation => {
|
|
353
|
+
const index = mutation.syncAction.index?.toString();
|
|
354
|
+
mutationMap[index] = mutation;
|
|
355
|
+
}
|
|
356
|
+
: () => { }, validateMacs);
|
|
357
|
+
}
|
|
358
|
+
catch (err) {
|
|
359
|
+
if (isMissingKeyError(err))
|
|
360
|
+
throw err;
|
|
361
|
+
logger?.warn({ name, version: patchVersion, error: err.message }, 'failed to decode patch, skipping');
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
306
364
|
newState.hash = decodeResult.hash;
|
|
307
365
|
newState.indexValueMap = decodeResult.indexValueMap;
|
|
308
366
|
if (validateMacs) {
|
|
309
367
|
const base64Key = Buffer.from(keyId.id).toString('base64');
|
|
310
368
|
const keyEnc = await getAppStateSyncKey(base64Key);
|
|
311
369
|
if (!keyEnc) {
|
|
312
|
-
throw new Boom(`failed to find key "${base64Key}" to decode mutation
|
|
370
|
+
throw new Boom(`failed to find key "${base64Key}" to decode mutation`, { data: { isMissingKey: true } });
|
|
313
371
|
}
|
|
314
372
|
const result = mutationKeys(keyEnc.keyData);
|
|
315
373
|
const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey);
|
|
316
374
|
if (Buffer.compare(snapshotMac, computedSnapshotMac) !== 0) {
|
|
317
|
-
|
|
375
|
+
logger?.warn({ name, version: newState.version }, 'LTHash verification failed, skipping remaining patches');
|
|
376
|
+
break;
|
|
318
377
|
}
|
|
319
378
|
}
|
|
320
379
|
// clear memory used up by the mutations
|
|
@@ -779,6 +838,7 @@ export const processSyncAction = (syncAction, ev, me, initialSyncOpts, logger) =
|
|
|
779
838
|
action.lidContactAction.firstName ||
|
|
780
839
|
action.lidContactAction.username ||
|
|
781
840
|
undefined,
|
|
841
|
+
username: action.lidContactAction.username || undefined,
|
|
782
842
|
lid: id,
|
|
783
843
|
phoneNumber: undefined
|
|
784
844
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export var CompanionWebClientType;
|
|
2
|
+
(function (CompanionWebClientType) {
|
|
3
|
+
CompanionWebClientType[CompanionWebClientType["UNKNOWN"] = 0] = "UNKNOWN";
|
|
4
|
+
CompanionWebClientType[CompanionWebClientType["CHROME"] = 1] = "CHROME";
|
|
5
|
+
CompanionWebClientType[CompanionWebClientType["EDGE"] = 2] = "EDGE";
|
|
6
|
+
CompanionWebClientType[CompanionWebClientType["FIREFOX"] = 3] = "FIREFOX";
|
|
7
|
+
CompanionWebClientType[CompanionWebClientType["IE"] = 4] = "IE";
|
|
8
|
+
CompanionWebClientType[CompanionWebClientType["OPERA"] = 5] = "OPERA";
|
|
9
|
+
CompanionWebClientType[CompanionWebClientType["SAFARI"] = 6] = "SAFARI";
|
|
10
|
+
CompanionWebClientType[CompanionWebClientType["ELECTRON"] = 7] = "ELECTRON";
|
|
11
|
+
CompanionWebClientType[CompanionWebClientType["UWP"] = 8] = "UWP";
|
|
12
|
+
CompanionWebClientType[CompanionWebClientType["OTHER_WEB_CLIENT"] = 9] = "OTHER_WEB_CLIENT";
|
|
13
|
+
})(CompanionWebClientType || (CompanionWebClientType = {}));
|
|
14
|
+
const BROWSER_TO_COMPANION_WEB_CLIENT = {
|
|
15
|
+
Chrome: CompanionWebClientType.CHROME,
|
|
16
|
+
Edge: CompanionWebClientType.EDGE,
|
|
17
|
+
Firefox: CompanionWebClientType.FIREFOX,
|
|
18
|
+
IE: CompanionWebClientType.IE,
|
|
19
|
+
Opera: CompanionWebClientType.OPERA,
|
|
20
|
+
Safari: CompanionWebClientType.SAFARI
|
|
21
|
+
};
|
|
22
|
+
export const getCompanionWebClientType = ([os, browserName]) => {
|
|
23
|
+
if (browserName === 'Desktop') {
|
|
24
|
+
return os === 'Windows' ? CompanionWebClientType.UWP : CompanionWebClientType.ELECTRON;
|
|
25
|
+
}
|
|
26
|
+
return BROWSER_TO_COMPANION_WEB_CLIENT[browserName] || CompanionWebClientType.OTHER_WEB_CLIENT;
|
|
27
|
+
};
|
|
28
|
+
export const getCompanionPlatformId = (browser) => {
|
|
29
|
+
return getCompanionWebClientType(browser).toString();
|
|
30
|
+
};
|
|
31
|
+
export const buildPairingQRData = (ref, noiseKeyB64, identityKeyB64, advB64, browser) => {
|
|
32
|
+
return ('https://wa.me/settings/linked_devices#' +
|
|
33
|
+
[ref, noiseKeyB64, identityKeyB64, advB64, getCompanionPlatformId(browser)].join(','));
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=companion-reg-client-utils.js.map
|
|
@@ -27,13 +27,16 @@ const storeMappingFromEnvelope = async (stanza, sender, repository, decryptionJi
|
|
|
27
27
|
};
|
|
28
28
|
export const NO_MESSAGE_FOUND_ERROR_TEXT = 'Message absent from node';
|
|
29
29
|
export const MISSING_KEYS_ERROR_TEXT = 'Key used already or never filled';
|
|
30
|
+
export const ACCOUNT_RESTRICTED_TEXT = 'Your account has been restricted';
|
|
30
31
|
// Retry configuration for failed decryption
|
|
31
32
|
export const DECRYPTION_RETRY_CONFIG = {
|
|
32
33
|
maxRetries: 3,
|
|
33
34
|
baseDelayMs: 100,
|
|
34
35
|
sessionRecordErrors: ['No session record', 'SessionError: No session record']
|
|
35
36
|
};
|
|
37
|
+
/** NACK reason codes we send to the server (client → server) */
|
|
36
38
|
export const NACK_REASONS = {
|
|
39
|
+
SenderReachoutTimelocked: 463,
|
|
37
40
|
ParsingError: 487,
|
|
38
41
|
UnrecognizedStanza: 488,
|
|
39
42
|
UnrecognizedStanzaClass: 489,
|
|
@@ -48,6 +51,21 @@ export const NACK_REASONS = {
|
|
|
48
51
|
UnsupportedLIDGroup: 551,
|
|
49
52
|
DBOperationFailed: 552
|
|
50
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* Server-side error codes returned in ack stanzas (server → client) that we
|
|
56
|
+
* currently have dedicated handlers for. Extend as more handlers are added.
|
|
57
|
+
* Distinct from the client-side NackReason enum (WAWebCreateNackFromStanza).
|
|
58
|
+
*/
|
|
59
|
+
export const SERVER_ERROR_CODES = {
|
|
60
|
+
/**
|
|
61
|
+
* 1:1 message missing privacy token (tctoken). Usually means the account is
|
|
62
|
+
* restricted: WhatsApp blocks starting new chats but preserves existing ones,
|
|
63
|
+
* since established chats already carry a tctoken.
|
|
64
|
+
*/
|
|
65
|
+
MessageAccountRestriction: '463',
|
|
66
|
+
/** Stanza validation failure (SMAX_INVALID) — likely stale device session */
|
|
67
|
+
SmaxInvalid: '479'
|
|
68
|
+
};
|
|
51
69
|
export const extractAddressingContext = (stanza) => {
|
|
52
70
|
let senderAlt;
|
|
53
71
|
let recipientAlt;
|
|
@@ -88,6 +106,12 @@ export function decodeMessageNode(stanza, meId, meLid) {
|
|
|
88
106
|
const from = stanza.attrs.from;
|
|
89
107
|
const participant = stanza.attrs.participant;
|
|
90
108
|
const recipient = stanza.attrs.recipient;
|
|
109
|
+
if (!msgId) {
|
|
110
|
+
throw new Boom('Invalid message stanza: missing id attribute', { data: stanza });
|
|
111
|
+
}
|
|
112
|
+
if (!from) {
|
|
113
|
+
throw new Boom('Invalid message stanza: missing from attribute', { data: stanza });
|
|
114
|
+
}
|
|
91
115
|
const addressingContext = extractAddressingContext(stanza);
|
|
92
116
|
const isMe = (jid) => areJidsSameUser(jid, meId);
|
|
93
117
|
const isMeLid = (jid) => areJidsSameUser(jid, meLid);
|
|
@@ -102,6 +126,12 @@ export function decodeMessageNode(stanza, meId, meLid) {
|
|
|
102
126
|
chatId = recipient;
|
|
103
127
|
}
|
|
104
128
|
else {
|
|
129
|
+
// Peer-routed self stanzas (history sync, app-state sync, etc.) arrive
|
|
130
|
+
// with `from` set to our own device but no `recipient` attribute —
|
|
131
|
+
// still mark as fromMe so self-only protocolMessage handlers run.
|
|
132
|
+
if (isMe(from) || isMeLid(from)) {
|
|
133
|
+
fromMe = true;
|
|
134
|
+
}
|
|
105
135
|
chatId = from;
|
|
106
136
|
}
|
|
107
137
|
msgType = 'chat';
|
|
@@ -148,10 +178,14 @@ export function decodeMessageNode(stanza, meId, meLid) {
|
|
|
148
178
|
const key = {
|
|
149
179
|
remoteJid: chatId,
|
|
150
180
|
remoteJidAlt: !isJidGroup(chatId) ? addressingContext.senderAlt : undefined,
|
|
181
|
+
remoteJidUsername: !isJidGroup(chatId)
|
|
182
|
+
? stanza.attrs.peer_recipient_username || stanza.attrs.recipient_username
|
|
183
|
+
: undefined,
|
|
151
184
|
fromMe,
|
|
152
185
|
id: msgId,
|
|
153
186
|
participant,
|
|
154
187
|
participantAlt: isJidGroup(chatId) ? addressingContext.senderAlt : undefined,
|
|
188
|
+
participantUsername: stanza.attrs.participant ? stanza.attrs.participant_username : undefined,
|
|
155
189
|
addressingMode: addressingContext.addressingMode,
|
|
156
190
|
...(msgType === 'newsletter' && stanza.attrs.server_id ? { server_id: stanza.attrs.server_id } : {})
|
|
157
191
|
};
|