davexbaileys 2.5.21 → 2.5.23
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 +15 -179
- package/WAProto/GenerateStatics.sh +2 -0
- package/lib/Defaults/baileys-version.json +1 -1
- package/lib/Defaults/index.js +14 -3
- package/lib/Socket/chats.js +69 -14
- package/lib/Socket/communities.js +430 -0
- package/lib/Socket/groups.js +7 -1
- package/lib/Socket/index.js +11 -6
- package/lib/Socket/messages-recv.js +63 -30
- package/lib/Socket/messages-send.js +114 -3
- package/lib/Utils/browser-utils.js +33 -0
- package/lib/Utils/chat-utils.js +22 -12
- package/lib/Utils/decode-wa-message.js +7 -0
- package/lib/Utils/event-buffer.js +3 -1
- package/lib/Utils/generics.js +9 -0
- package/lib/Utils/history.js +11 -4
- package/lib/Utils/index.js +7 -0
- package/lib/Utils/message-retry-manager.js +151 -0
- package/lib/Utils/messages.js +31 -3
- package/lib/Utils/offline-node-processor.js +34 -0
- package/lib/Utils/pre-key-manager.js +95 -0
- package/lib/Utils/process-message.js +6 -4
- package/lib/Utils/stanza-ack.js +45 -0
- package/lib/Utils/sync-action-utils.js +23 -0
- package/lib/Utils/tc-token-utils.js +209 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +26 -3
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +38 -0
- package/lib/WAUSync/Protocols/index.js +1 -0
- package/lib/WAUSync/USyncQuery.js +5 -0
- package/lib/WAUSync/USyncUser.js +8 -0
- package/package.json +15 -4
|
@@ -17,11 +17,17 @@ const WAUSync_1 = require("../WAUSync");
|
|
|
17
17
|
const groups_1 = require("./groups");
|
|
18
18
|
const newsletter_1 = require("./newsletter");
|
|
19
19
|
const GiftedStatus = require("./gcstatus");
|
|
20
|
+
const tc_token_utils_1 = require("../Utils/tc-token-utils");
|
|
21
|
+
const lid_mapping_1 = require("../Utils/lid-mapping");
|
|
20
22
|
// Permanently blacklist device JIDs that WhatsApp rejects with not-acceptable.
|
|
21
23
|
// These are typically stale companion devices. Avoids repeated failed IQ queries.
|
|
22
24
|
const _deadDeviceJids = new Set();
|
|
23
25
|
|
|
24
26
|
const makeMessagesSocket = (config) => {
|
|
27
|
+
// Per-socket set of tctoken storage JIDs with a fire-and-forget `issuePrivacyTokens` IQ in flight.
|
|
28
|
+
// Prevents duplicate IQs from rapid back-to-back sends before `senderTimestamp` persists.
|
|
29
|
+
// Scoped per-socket so multi-account processes don't have one socket suppress another's issuance.
|
|
30
|
+
const _inFlightTcTokenIssuance = new Set();
|
|
25
31
|
const {
|
|
26
32
|
logger,
|
|
27
33
|
linkPreviewImageThumbnailWidth,
|
|
@@ -45,6 +51,23 @@ const makeMessagesSocket = (config) => {
|
|
|
45
51
|
groupMetadata,
|
|
46
52
|
groupToggleEphemeral,
|
|
47
53
|
} = sock;
|
|
54
|
+
// Adapter: globalLidMapping uses sync getLidFromPn; tc-token utils expect async (jid) => Promise<string|null>
|
|
55
|
+
const _getLIDForPN = async (pn) => {
|
|
56
|
+
try {
|
|
57
|
+
const lid = lid_mapping_1.globalLidMapping.getLidFromPn(pn);
|
|
58
|
+
return lid || null;
|
|
59
|
+
} catch (_) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const _getPNForLID = async (lid) => {
|
|
64
|
+
try {
|
|
65
|
+
const pn = lid_mapping_1.globalLidMapping.getPnFromLid(lid);
|
|
66
|
+
return pn || null;
|
|
67
|
+
} catch (_) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
48
71
|
const userDevicesCache =
|
|
49
72
|
config.userDevicesCache ||
|
|
50
73
|
new node_cache_1.default({
|
|
@@ -755,6 +778,44 @@ const makeMessagesSocket = (config) => {
|
|
|
755
778
|
});
|
|
756
779
|
logger.debug({ jid }, "adding device identity");
|
|
757
780
|
}
|
|
781
|
+
// tc-token attachment: WA Web attaches a stored trusted-contact token to outgoing 1:1 messages
|
|
782
|
+
// WA Web never attaches tctoken to peer (AppStateSync) messages — server rejects with 479
|
|
783
|
+
const _isPeerMessage = (additionalAttributes && additionalAttributes["category"] === "peer");
|
|
784
|
+
const _isRetryResend = !!participant;
|
|
785
|
+
const _is1on1Send = !isGroup && !_isRetryResend && !isStatus && !isNewsletter && !_isPeerMessage;
|
|
786
|
+
let _tcTokenJid = destinationJid;
|
|
787
|
+
let _existingTokenEntry;
|
|
788
|
+
try {
|
|
789
|
+
if (_is1on1Send) {
|
|
790
|
+
_tcTokenJid = await (0, tc_token_utils_1.resolveTcTokenJid)(destinationJid, _getLIDForPN);
|
|
791
|
+
const _contactTcTokenData = await authState.keys.get("tctoken", [_tcTokenJid]);
|
|
792
|
+
_existingTokenEntry = _contactTcTokenData && _contactTcTokenData[_tcTokenJid];
|
|
793
|
+
let _tcTokenBuffer = _existingTokenEntry && _existingTokenEntry.token;
|
|
794
|
+
// Treat expired tokens the same as missing — clear from cache
|
|
795
|
+
if (_tcTokenBuffer && _tcTokenBuffer.length && (0, tc_token_utils_1.isTcTokenExpired)(_existingTokenEntry && _existingTokenEntry.timestamp)) {
|
|
796
|
+
logger.debug({ jid: destinationJid, timestamp: _existingTokenEntry && _existingTokenEntry.timestamp }, "tctoken expired, clearing");
|
|
797
|
+
_tcTokenBuffer = undefined;
|
|
798
|
+
// Preserve senderTimestamp so the fire-and-forget issuance dedupe survives cleanup.
|
|
799
|
+
const _cleared = _existingTokenEntry && _existingTokenEntry.senderTimestamp !== undefined
|
|
800
|
+
? { token: Buffer.alloc(0), senderTimestamp: _existingTokenEntry.senderTimestamp }
|
|
801
|
+
: null;
|
|
802
|
+
try {
|
|
803
|
+
await authState.keys.set({ tctoken: { [_tcTokenJid]: _cleared } });
|
|
804
|
+
} catch (err) {
|
|
805
|
+
logger.debug({ jid: destinationJid, err: err && err.message }, "failed to persist tctoken expiry cleanup");
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
if (_tcTokenBuffer && _tcTokenBuffer.length) {
|
|
809
|
+
stanza.content.push({
|
|
810
|
+
tag: "tctoken",
|
|
811
|
+
attrs: {},
|
|
812
|
+
content: _tcTokenBuffer
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
} catch (err) {
|
|
817
|
+
logger.debug({ jid: destinationJid, err: err && err.message }, "tctoken attachment failed");
|
|
818
|
+
}
|
|
758
819
|
if (additionalNodes && additionalNodes.length > 0) {
|
|
759
820
|
stanza.content.push(...additionalNodes);
|
|
760
821
|
}
|
|
@@ -763,6 +824,52 @@ const makeMessagesSocket = (config) => {
|
|
|
763
824
|
`sending message to ${participants.length} devices`,
|
|
764
825
|
);
|
|
765
826
|
await sendNode(stanza);
|
|
827
|
+
// Fire-and-forget: issue our token to the contact AFTER message send.
|
|
828
|
+
// WA Web skips protocol messages and PSA/bot contacts (TcTokenChatAction: isRegularUser)
|
|
829
|
+
try {
|
|
830
|
+
const _normalized = (0, Utils_1.normalizeMessageContent)(message);
|
|
831
|
+
const _isProtocolMsg = !!(_normalized && _normalized.protocolMessage);
|
|
832
|
+
const _isBotOrPSA = destinationJid === WABinary_1.PSA_WID
|
|
833
|
+
|| (0, WABinary_1.isJidBot)(destinationJid)
|
|
834
|
+
|| (0, WABinary_1.isJidMetaIa)(destinationJid);
|
|
835
|
+
if (
|
|
836
|
+
_is1on1Send &&
|
|
837
|
+
!_isProtocolMsg &&
|
|
838
|
+
!_isBotOrPSA &&
|
|
839
|
+
(0, tc_token_utils_1.shouldSendNewTcToken)(_existingTokenEntry && _existingTokenEntry.senderTimestamp) &&
|
|
840
|
+
!_inFlightTcTokenIssuance.has(_tcTokenJid)
|
|
841
|
+
) {
|
|
842
|
+
_inFlightTcTokenIssuance.add(_tcTokenJid);
|
|
843
|
+
const _issueTimestamp = (0, Utils_1.unixTimestampSeconds)();
|
|
844
|
+
(0, tc_token_utils_1.resolveIssuanceJid)(destinationJid, false, _getLIDForPN, _getPNForLID)
|
|
845
|
+
.then((issueJid) => issuePrivacyTokens([issueJid], _issueTimestamp))
|
|
846
|
+
.then(async (result) => {
|
|
847
|
+
await (0, tc_token_utils_1.storeTcTokensFromIqResult)({
|
|
848
|
+
result,
|
|
849
|
+
fallbackJid: _tcTokenJid,
|
|
850
|
+
keys: authState.keys,
|
|
851
|
+
getLIDForPN: _getLIDForPN
|
|
852
|
+
});
|
|
853
|
+
const _currentData = await authState.keys.get("tctoken", [_tcTokenJid]);
|
|
854
|
+
const _currentEntry = _currentData && _currentData[_tcTokenJid];
|
|
855
|
+
const _indexWrite = await (0, tc_token_utils_1.buildMergedTcTokenIndexWrite)(authState.keys, [_tcTokenJid]);
|
|
856
|
+
await authState.keys.set({
|
|
857
|
+
tctoken: Object.assign(
|
|
858
|
+
{ [_tcTokenJid]: Object.assign({ token: Buffer.alloc(0) }, _currentEntry, { senderTimestamp: _issueTimestamp }) },
|
|
859
|
+
_indexWrite
|
|
860
|
+
)
|
|
861
|
+
});
|
|
862
|
+
})
|
|
863
|
+
.catch((err) => {
|
|
864
|
+
logger.debug({ jid: destinationJid, err: err && err.message }, "fire-and-forget tctoken issuance failed");
|
|
865
|
+
})
|
|
866
|
+
.finally(() => {
|
|
867
|
+
_inFlightTcTokenIssuance.delete(_tcTokenJid);
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
} catch (err) {
|
|
871
|
+
logger.debug({ jid: destinationJid, err: err && err.message }, "tctoken issuance gate failed");
|
|
872
|
+
}
|
|
766
873
|
});
|
|
767
874
|
return msgId;
|
|
768
875
|
};
|
|
@@ -816,8 +923,10 @@ const makeMessagesSocket = (config) => {
|
|
|
816
923
|
return "text";
|
|
817
924
|
}
|
|
818
925
|
};
|
|
819
|
-
const
|
|
820
|
-
const t = (
|
|
926
|
+
const issuePrivacyTokens = async (jids, timestamp) => {
|
|
927
|
+
const t = (timestamp !== undefined && timestamp !== null
|
|
928
|
+
? timestamp
|
|
929
|
+
: (0, Utils_1.unixTimestampSeconds)()).toString();
|
|
821
930
|
const result = await query({
|
|
822
931
|
tag: "iq",
|
|
823
932
|
attrs: {
|
|
@@ -875,7 +984,8 @@ const makeMessagesSocket = (config) => {
|
|
|
875
984
|
return {
|
|
876
985
|
...sock,
|
|
877
986
|
pinMessage,
|
|
878
|
-
|
|
987
|
+
issuePrivacyTokens,
|
|
988
|
+
getPrivacyTokens: issuePrivacyTokens,
|
|
879
989
|
assertSessions,
|
|
880
990
|
relayMessage,
|
|
881
991
|
sendReceipt,
|
|
@@ -1002,6 +1112,7 @@ const makeMessagesSocket = (config) => {
|
|
|
1002
1112
|
}),
|
|
1003
1113
|
//TODO: CACHE
|
|
1004
1114
|
getProfilePicUrl: sock.profilePictureUrl,
|
|
1115
|
+
getCallLink: sock.createCallLink,
|
|
1005
1116
|
upload: waUploadToServer,
|
|
1006
1117
|
mediaCache: config.mediaCache,
|
|
1007
1118
|
options: config.options,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getPlatformId = exports.Browsers = void 0;
|
|
4
|
+
const os_1 = require("os");
|
|
5
|
+
const WAProto_1 = require("../../WAProto");
|
|
6
|
+
|
|
7
|
+
const PLATFORM_MAP = {
|
|
8
|
+
aix: 'AIX',
|
|
9
|
+
darwin: 'Mac OS',
|
|
10
|
+
win32: 'Windows',
|
|
11
|
+
android: 'Android',
|
|
12
|
+
freebsd: 'FreeBSD',
|
|
13
|
+
openbsd: 'OpenBSD',
|
|
14
|
+
sunos: 'Solaris',
|
|
15
|
+
linux: undefined,
|
|
16
|
+
haiku: undefined,
|
|
17
|
+
cygwin: undefined,
|
|
18
|
+
netbsd: undefined
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
exports.Browsers = {
|
|
22
|
+
ubuntu: browser => ['Ubuntu', browser, '22.04.4'],
|
|
23
|
+
macOS: browser => ['Mac OS', browser, '14.4.1'],
|
|
24
|
+
baileys: browser => ['Baileys', browser, '6.5.0'],
|
|
25
|
+
windows: browser => ['Windows', browser, '10.0.22631'],
|
|
26
|
+
appropriate: browser => [PLATFORM_MAP[(0, os_1.platform)()] || 'Ubuntu', browser, (0, os_1.release)()]
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const getPlatformId = (browser) => {
|
|
30
|
+
const platformType = WAProto_1.proto.DeviceProps.PlatformType[browser.toUpperCase()];
|
|
31
|
+
return platformType ? platformType.toString() : '1';
|
|
32
|
+
};
|
|
33
|
+
exports.getPlatformId = getPlatformId;
|
package/lib/Utils/chat-utils.js
CHANGED
|
@@ -55,7 +55,10 @@ const makeLtHashGenerator = ({ indexValueMap, hash }) => {
|
|
|
55
55
|
const prevOp = indexValueMap[indexMacBase64];
|
|
56
56
|
if (operation === WAProto_1.proto.SyncdMutation.SyncdOperation.REMOVE) {
|
|
57
57
|
if (!prevOp) {
|
|
58
|
-
|
|
58
|
+
// WA Web does not throw here — it logs a warning and skips the subtract.
|
|
59
|
+
// The missing REMOVE will cause an LTHash mismatch, which is handled
|
|
60
|
+
// by the MAC validation layer (snapshot recovery or retry).
|
|
61
|
+
return;
|
|
59
62
|
}
|
|
60
63
|
// remove from index value mac, since this mutation is erased
|
|
61
64
|
delete indexValueMap[indexMacBase64];
|
|
@@ -90,10 +93,18 @@ const generatePatchMac = (snapshotMac, valueMacs, version, type, key) => {
|
|
|
90
93
|
};
|
|
91
94
|
const newLTHashState = () => ({ version: 0, hash: Buffer.alloc(128), indexValueMap: {} });
|
|
92
95
|
exports.newLTHashState = newLTHashState;
|
|
96
|
+
const ensureLTHashStateVersion = (state) => {
|
|
97
|
+
if (typeof state.version !== 'number' || isNaN(state.version)) {
|
|
98
|
+
state.version = 0;
|
|
99
|
+
}
|
|
100
|
+
return state;
|
|
101
|
+
};
|
|
102
|
+
exports.ensureLTHashStateVersion = ensureLTHashStateVersion;
|
|
103
|
+
exports.MAX_SYNC_ATTEMPTS = 2;
|
|
93
104
|
const encodeSyncdPatch = async ({ type, index, syncAction, apiVersion, operation }, myAppStateKeyId, state, getAppStateSyncKey) => {
|
|
94
105
|
const key = !!myAppStateKeyId ? await getAppStateSyncKey(myAppStateKeyId) : undefined;
|
|
95
106
|
if (!key) {
|
|
96
|
-
throw new boom_1.Boom(`myAppStateKey ("${myAppStateKeyId}") not present`, {
|
|
107
|
+
throw new boom_1.Boom(`myAppStateKey ("${myAppStateKeyId}") not present`, { data: { isMissingKey: true } });
|
|
97
108
|
}
|
|
98
109
|
const encKeyId = Buffer.from(myAppStateKeyId, 'base64');
|
|
99
110
|
state = { ...state, indexValueMap: { ...state.indexValueMap } };
|
|
@@ -181,8 +192,7 @@ const decodeSyncdMutations = async (msgMutations, initialState, getAppStateSyncK
|
|
|
181
192
|
const keyEnc = await getAppStateSyncKey(base64Key);
|
|
182
193
|
if (!keyEnc) {
|
|
183
194
|
throw new boom_1.Boom(`failed to find key "${base64Key}" to decode mutation`, {
|
|
184
|
-
|
|
185
|
-
data: { msgMutations }
|
|
195
|
+
data: { isMissingKey: true, msgMutations }
|
|
186
196
|
});
|
|
187
197
|
}
|
|
188
198
|
return mutationKeys(keyEnc.keyData);
|
|
@@ -194,7 +204,7 @@ const decodeSyncdPatch = async (msg, name, initialState, getAppStateSyncKey, onM
|
|
|
194
204
|
const base64Key = Buffer.from(msg.keyId.id).toString('base64');
|
|
195
205
|
const mainKeyObj = await getAppStateSyncKey(base64Key);
|
|
196
206
|
if (!mainKeyObj) {
|
|
197
|
-
throw new boom_1.Boom(`failed to find key "${base64Key}" to decode patch`, {
|
|
207
|
+
throw new boom_1.Boom(`failed to find key "${base64Key}" to decode patch`, { data: { isMissingKey: true, msg } });
|
|
198
208
|
}
|
|
199
209
|
const mainKey = await mutationKeys(mainKeyObj.keyData);
|
|
200
210
|
const mutationmacs = msg.mutations.map(mutation => mutation.record.value.blob.slice(-32));
|
|
@@ -277,7 +287,7 @@ const decodeSyncdSnapshot = async (name, snapshot, getAppStateSyncKey, minimumVe
|
|
|
277
287
|
const base64Key = Buffer.from(snapshot.keyId.id).toString('base64');
|
|
278
288
|
const keyEnc = await getAppStateSyncKey(base64Key);
|
|
279
289
|
if (!keyEnc) {
|
|
280
|
-
throw new boom_1.Boom(`failed to find key "${base64Key}" to decode mutation
|
|
290
|
+
throw new boom_1.Boom(`failed to find key "${base64Key}" to decode mutation`, { data: { isMissingKey: true } });
|
|
281
291
|
}
|
|
282
292
|
const result = await mutationKeys(keyEnc.keyData);
|
|
283
293
|
const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey);
|
|
@@ -322,7 +332,7 @@ const decodePatches = async (name, syncds, initial, getAppStateSyncKey, options,
|
|
|
322
332
|
const base64Key = Buffer.from(keyId.id).toString('base64');
|
|
323
333
|
const keyEnc = await getAppStateSyncKey(base64Key);
|
|
324
334
|
if (!keyEnc) {
|
|
325
|
-
throw new boom_1.Boom(`failed to find key "${base64Key}" to decode mutation
|
|
335
|
+
throw new boom_1.Boom(`failed to find key "${base64Key}" to decode mutation`, { data: { isMissingKey: true } });
|
|
326
336
|
}
|
|
327
337
|
const result = await mutationKeys(keyEnc.keyData);
|
|
328
338
|
const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey);
|
|
@@ -595,16 +605,16 @@ const processSyncAction = (syncAction, ev, me, initialSyncOpts, logger) => {
|
|
|
595
605
|
// 1. if the account unarchiveChats setting is true
|
|
596
606
|
// a. if the chat is archived, and no further messages have been received -- simple, keep archived
|
|
597
607
|
// b. if the chat was archived, and the user received messages from the other person afterwards
|
|
598
|
-
//
|
|
599
|
-
//
|
|
608
|
+
// then the chat should be marked unarchved --
|
|
609
|
+
// we compare the timestamp of latest message from the other person to determine this
|
|
600
610
|
// 2. if the account unarchiveChats setting is false -- then it doesn't matter,
|
|
601
|
-
//
|
|
611
|
+
// it'll always take an app state action to mark in unarchived -- which we'll get anyway
|
|
602
612
|
const archiveAction = action === null || action === void 0 ? void 0 : action.archiveChatAction;
|
|
603
613
|
const isArchived = archiveAction ? archiveAction.archived : type === 'archive';
|
|
604
614
|
// // basically we don't need to fire an "archive" update if the chat is being marked unarchvied
|
|
605
615
|
// // this only applies for the initial sync
|
|
606
616
|
// if(isInitialSync && !isArchived) {
|
|
607
|
-
//
|
|
617
|
+
// isArchived = false
|
|
608
618
|
// }
|
|
609
619
|
const msgRange = !(accountSettings === null || accountSettings === void 0 ? void 0 : accountSettings.unarchiveChats) ? undefined : archiveAction === null || archiveAction === void 0 ? void 0 : archiveAction.messageRange;
|
|
610
620
|
// logger?.debug({ chat: id, syncAction }, 'message range archive')
|
|
@@ -642,11 +652,11 @@ const processSyncAction = (syncAction, ev, me, initialSyncOpts, logger) => {
|
|
|
642
652
|
});
|
|
643
653
|
}
|
|
644
654
|
else if (action === null || action === void 0 ? void 0 : action.contactAction) {
|
|
645
|
-
ev.emit('contacts.upsert', [{ id, name: action.contactAction.fullName, lid: action.contactAction.lidJid }]);
|
|
646
655
|
ev.emit('contacts.upsert', [
|
|
647
656
|
{
|
|
648
657
|
id: id,
|
|
649
658
|
name: action.contactAction.fullName,
|
|
659
|
+
username: action.contactAction.username || undefined,
|
|
650
660
|
lid: action.contactAction.lidJid || undefined,
|
|
651
661
|
jid: (0, WABinary_1.isJidUser)(id) ? id : undefined
|
|
652
662
|
}
|
|
@@ -170,15 +170,22 @@ function decodeMessageNode(stanza, meId, meLid) {
|
|
|
170
170
|
lid_mapping_1.globalLidMapping.set(participantLidValue, participantPnValue);
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
|
+
const isGroupChat = (0, WABinary_1.isJidGroup)(chatId);
|
|
174
|
+
const remoteJidUsername = !isGroupChat
|
|
175
|
+
? (stanza.attrs.peer_recipient_username || stanza.attrs.recipient_username)
|
|
176
|
+
: undefined;
|
|
177
|
+
const participantUsername = stanza.attrs.participant ? stanza.attrs.participant_username : undefined;
|
|
173
178
|
const key = {
|
|
174
179
|
remoteJid: chatId,
|
|
175
180
|
fromMe,
|
|
176
181
|
id: msgId,
|
|
182
|
+
...(remoteJidUsername ? { remoteJidUsername } : {}),
|
|
177
183
|
...(senderLidValue ? { senderLid: senderLidValue } : {}),
|
|
178
184
|
...(senderPnValue ? { senderPn: senderPnValue } : {}),
|
|
179
185
|
...(participant ? { participant } : {}),
|
|
180
186
|
...(participantPnValue ? { participantPn: participantPnValue } : {}),
|
|
181
187
|
...(participantLidValue ? { participantLid: participantLidValue } : {}),
|
|
188
|
+
...(participantUsername ? { participantUsername } : {}),
|
|
182
189
|
...(msgType === 'newsletter' && stanza.attrs.server_id ? { server_id: stanza.attrs.server_id } : {})
|
|
183
190
|
};
|
|
184
191
|
const fullMessage = {
|
|
@@ -182,6 +182,7 @@ eventData, logger) {
|
|
|
182
182
|
data.historySets.progress = eventData.progress;
|
|
183
183
|
data.historySets.peerDataRequestSessionId = eventData.peerDataRequestSessionId;
|
|
184
184
|
data.historySets.isLatest = eventData.isLatest || data.historySets.isLatest;
|
|
185
|
+
data.historySets.chunkOrder = eventData.chunkOrder !== undefined ? eventData.chunkOrder : data.historySets.chunkOrder;
|
|
185
186
|
break;
|
|
186
187
|
case 'chats.upsert':
|
|
187
188
|
for (const chat of eventData) {
|
|
@@ -446,7 +447,8 @@ function consolidateEvents(data) {
|
|
|
446
447
|
syncType: data.historySets.syncType,
|
|
447
448
|
progress: data.historySets.progress,
|
|
448
449
|
isLatest: data.historySets.isLatest,
|
|
449
|
-
peerDataRequestSessionId: data.historySets.peerDataRequestSessionId
|
|
450
|
+
peerDataRequestSessionId: data.historySets.peerDataRequestSessionId,
|
|
451
|
+
chunkOrder: data.historySets.chunkOrder
|
|
450
452
|
};
|
|
451
453
|
}
|
|
452
454
|
const chatUpsertList = Object.values(data.chatUpserts);
|
package/lib/Utils/generics.js
CHANGED
|
@@ -324,6 +324,9 @@ const getCallStatusFromNode = ({ tag, attrs }) => {
|
|
|
324
324
|
case 'offer_notice':
|
|
325
325
|
status = 'offer';
|
|
326
326
|
break;
|
|
327
|
+
case 'preaccept':
|
|
328
|
+
status = 'preaccept';
|
|
329
|
+
break;
|
|
327
330
|
case 'terminate':
|
|
328
331
|
if (attrs.reason === 'timeout') {
|
|
329
332
|
status = 'timeout';
|
|
@@ -339,6 +342,12 @@ const getCallStatusFromNode = ({ tag, attrs }) => {
|
|
|
339
342
|
case 'accept':
|
|
340
343
|
status = 'accept';
|
|
341
344
|
break;
|
|
345
|
+
case 'transport':
|
|
346
|
+
status = 'transport';
|
|
347
|
+
break;
|
|
348
|
+
case 'relaylatency':
|
|
349
|
+
status = 'relaylatency';
|
|
350
|
+
break;
|
|
342
351
|
default:
|
|
343
352
|
status = 'ringing';
|
|
344
353
|
break;
|
package/lib/Utils/history.js
CHANGED
|
@@ -12,11 +12,18 @@ const messages_media_1 = require("./messages-media");
|
|
|
12
12
|
const inflatePromise = (0, util_1.promisify)(zlib_1.inflate);
|
|
13
13
|
const downloadHistory = async (msg, options) => {
|
|
14
14
|
const stream = await (0, messages_media_1.downloadContentFromMessage)(msg, 'md-msg-hist', { options });
|
|
15
|
-
const
|
|
15
|
+
const chunks = [];
|
|
16
|
+
let totalLength = 0;
|
|
16
17
|
for await (const chunk of stream) {
|
|
17
|
-
|
|
18
|
+
chunks.push(chunk);
|
|
19
|
+
totalLength += chunk.length;
|
|
20
|
+
}
|
|
21
|
+
let buffer = Buffer.allocUnsafe(totalLength);
|
|
22
|
+
let offset = 0;
|
|
23
|
+
for (const chunk of chunks) {
|
|
24
|
+
chunk.copy(buffer, offset);
|
|
25
|
+
offset += chunk.length;
|
|
18
26
|
}
|
|
19
|
-
let buffer = Buffer.concat(bufferArray);
|
|
20
27
|
// decompress buffer
|
|
21
28
|
buffer = await inflatePromise(buffer);
|
|
22
29
|
const syncData = WAProto_1.proto.HistorySync.decode(buffer);
|
|
@@ -34,11 +41,11 @@ const processHistoryMessage = (item) => {
|
|
|
34
41
|
case WAProto_1.proto.HistorySync.HistorySyncType.FULL:
|
|
35
42
|
case WAProto_1.proto.HistorySync.HistorySyncType.ON_DEMAND:
|
|
36
43
|
for (const chat of item.conversations) {
|
|
37
|
-
contacts.push({ id: chat.id, name: chat.name || undefined, lid: chat.lidJid });
|
|
38
44
|
contacts.push({
|
|
39
45
|
id: chat.id,
|
|
40
46
|
name: chat.name || undefined,
|
|
41
47
|
lid: chat.lidJid || undefined,
|
|
48
|
+
username: chat.username || undefined,
|
|
42
49
|
jid: (0, WABinary_1.isJidUser)(chat.id) ? chat.id : undefined
|
|
43
50
|
});
|
|
44
51
|
const msgs = chat.messages || [];
|
package/lib/Utils/index.js
CHANGED
|
@@ -33,3 +33,10 @@ __exportStar(require("./event-buffer"), exports);
|
|
|
33
33
|
__exportStar(require("./process-message"), exports);
|
|
34
34
|
__exportStar(require("./lid-mapping"), exports);
|
|
35
35
|
__exportStar(require("./message-type-utils"), exports);
|
|
36
|
+
__exportStar(require("./stanza-ack"), exports);
|
|
37
|
+
__exportStar(require("./offline-node-processor"), exports);
|
|
38
|
+
__exportStar(require("./tc-token-utils"), exports);
|
|
39
|
+
__exportStar(require("./sync-action-utils"), exports);
|
|
40
|
+
__exportStar(require("./message-retry-manager"), exports);
|
|
41
|
+
__exportStar(require("./browser-utils"), exports);
|
|
42
|
+
__exportStar(require("./pre-key-manager"), exports);
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MessageRetryManager = void 0;
|
|
4
|
+
const lru_cache_1 = require("lru-cache");
|
|
5
|
+
|
|
6
|
+
const RECENT_MESSAGES_SIZE = 512;
|
|
7
|
+
const MESSAGE_KEY_SEPARATOR = '\u0000';
|
|
8
|
+
const RECREATE_SESSION_TIMEOUT = 60 * 60 * 1000;
|
|
9
|
+
const PHONE_REQUEST_DELAY = 3000;
|
|
10
|
+
|
|
11
|
+
class MessageRetryManager {
|
|
12
|
+
constructor(logger, maxMsgRetryCount) {
|
|
13
|
+
this.logger = logger;
|
|
14
|
+
this.recentMessagesMap = new lru_cache_1.LRUCache({
|
|
15
|
+
max: RECENT_MESSAGES_SIZE,
|
|
16
|
+
ttl: 5 * 60 * 1000,
|
|
17
|
+
ttlAutopurge: true,
|
|
18
|
+
dispose: (_value, key) => {
|
|
19
|
+
const separatorIndex = key.lastIndexOf(MESSAGE_KEY_SEPARATOR);
|
|
20
|
+
if (separatorIndex > -1) {
|
|
21
|
+
const messageId = key.slice(separatorIndex + MESSAGE_KEY_SEPARATOR.length);
|
|
22
|
+
this.messageKeyIndex.delete(messageId);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
this.messageKeyIndex = new Map();
|
|
27
|
+
this.sessionRecreateHistory = new lru_cache_1.LRUCache({
|
|
28
|
+
ttl: RECREATE_SESSION_TIMEOUT * 2,
|
|
29
|
+
ttlAutopurge: true
|
|
30
|
+
});
|
|
31
|
+
this.retryCounters = new lru_cache_1.LRUCache({
|
|
32
|
+
ttl: 15 * 60 * 1000,
|
|
33
|
+
ttlAutopurge: true,
|
|
34
|
+
updateAgeOnGet: true
|
|
35
|
+
});
|
|
36
|
+
this.pendingPhoneRequests = {};
|
|
37
|
+
this.maxMsgRetryCount = 5;
|
|
38
|
+
this.statistics = {
|
|
39
|
+
totalRetries: 0,
|
|
40
|
+
successfulRetries: 0,
|
|
41
|
+
failedRetries: 0,
|
|
42
|
+
mediaRetries: 0,
|
|
43
|
+
sessionRecreations: 0,
|
|
44
|
+
phoneRequests: 0
|
|
45
|
+
};
|
|
46
|
+
this.maxMsgRetryCount = maxMsgRetryCount;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
addRecentMessage(to, id, message) {
|
|
50
|
+
const key = { to, id };
|
|
51
|
+
const keyStr = this.keyToString(key);
|
|
52
|
+
this.recentMessagesMap.set(keyStr, {
|
|
53
|
+
message,
|
|
54
|
+
timestamp: Date.now()
|
|
55
|
+
});
|
|
56
|
+
this.messageKeyIndex.set(id, keyStr);
|
|
57
|
+
this.logger.debug(`Added message to retry cache: ${to}/${id}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getRecentMessage(to, id) {
|
|
61
|
+
const key = { to, id };
|
|
62
|
+
const keyStr = this.keyToString(key);
|
|
63
|
+
return this.recentMessagesMap.get(keyStr);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
shouldRecreateSession(jid, retryCount, hasSession) {
|
|
67
|
+
if (!hasSession) {
|
|
68
|
+
this.sessionRecreateHistory.set(jid, Date.now());
|
|
69
|
+
this.statistics.sessionRecreations++;
|
|
70
|
+
return {
|
|
71
|
+
reason: "we don't have a Signal session with them",
|
|
72
|
+
recreate: true
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (retryCount < 2) {
|
|
76
|
+
return { reason: '', recreate: false };
|
|
77
|
+
}
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
const prevTime = this.sessionRecreateHistory.get(jid);
|
|
80
|
+
if (!prevTime || now - prevTime > RECREATE_SESSION_TIMEOUT) {
|
|
81
|
+
this.sessionRecreateHistory.set(jid, now);
|
|
82
|
+
this.statistics.sessionRecreations++;
|
|
83
|
+
return {
|
|
84
|
+
reason: 'retry count > 1 and over an hour since last recreation',
|
|
85
|
+
recreate: true
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return { reason: '', recreate: false };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
incrementRetryCount(messageId) {
|
|
92
|
+
this.retryCounters.set(messageId, (this.retryCounters.get(messageId) || 0) + 1);
|
|
93
|
+
this.statistics.totalRetries++;
|
|
94
|
+
return this.retryCounters.get(messageId);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
getRetryCount(messageId) {
|
|
98
|
+
return this.retryCounters.get(messageId) || 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
hasExceededMaxRetries(messageId) {
|
|
102
|
+
return this.getRetryCount(messageId) >= this.maxMsgRetryCount;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
markRetrySuccess(messageId) {
|
|
106
|
+
this.statistics.successfulRetries++;
|
|
107
|
+
this.retryCounters.delete(messageId);
|
|
108
|
+
this.cancelPendingPhoneRequest(messageId);
|
|
109
|
+
this.removeRecentMessage(messageId);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
markRetryFailed(messageId) {
|
|
113
|
+
this.statistics.failedRetries++;
|
|
114
|
+
this.retryCounters.delete(messageId);
|
|
115
|
+
this.cancelPendingPhoneRequest(messageId);
|
|
116
|
+
this.removeRecentMessage(messageId);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
schedulePhoneRequest(messageId, callback, delay = PHONE_REQUEST_DELAY) {
|
|
120
|
+
this.cancelPendingPhoneRequest(messageId);
|
|
121
|
+
this.pendingPhoneRequests[messageId] = setTimeout(() => {
|
|
122
|
+
delete this.pendingPhoneRequests[messageId];
|
|
123
|
+
this.statistics.phoneRequests++;
|
|
124
|
+
callback();
|
|
125
|
+
}, delay);
|
|
126
|
+
this.logger.debug(`Scheduled phone request for message ${messageId} with ${delay}ms delay`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
cancelPendingPhoneRequest(messageId) {
|
|
130
|
+
const timeout = this.pendingPhoneRequests[messageId];
|
|
131
|
+
if (timeout) {
|
|
132
|
+
clearTimeout(timeout);
|
|
133
|
+
delete this.pendingPhoneRequests[messageId];
|
|
134
|
+
this.logger.debug(`Cancelled pending phone request for message ${messageId}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
keyToString(key) {
|
|
139
|
+
return `${key.to}${MESSAGE_KEY_SEPARATOR}${key.id}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
removeRecentMessage(messageId) {
|
|
143
|
+
const keyStr = this.messageKeyIndex.get(messageId);
|
|
144
|
+
if (!keyStr) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this.recentMessagesMap.delete(keyStr);
|
|
148
|
+
this.messageKeyIndex.delete(messageId);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
exports.MessageRetryManager = MessageRetryManager;
|
package/lib/Utils/messages.js
CHANGED
|
@@ -136,7 +136,7 @@ const prepareWAMessageMedia = async (message, options) => {
|
|
|
136
136
|
}
|
|
137
137
|
const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined';
|
|
138
138
|
const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined';
|
|
139
|
-
const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true;
|
|
139
|
+
const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true && typeof uploadData.waveform === 'undefined';
|
|
140
140
|
const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
|
|
141
141
|
const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation;
|
|
142
142
|
const { mediaKey, encFilePath, originalFilePath, fileEncSha256, fileSha256, fileLength } = await (0, messages_media_1.encryptedStream)(uploadData.media, options.mediaTypeOverride || mediaType, {
|
|
@@ -444,6 +444,18 @@ const generateWAMessageContent = async (message, options) => {
|
|
|
444
444
|
else if ('requestPhoneNumber' in message) {
|
|
445
445
|
m.requestPhoneNumberMessage = {};
|
|
446
446
|
}
|
|
447
|
+
else if ('album' in message && message.album) {
|
|
448
|
+
m.albumMessage = {
|
|
449
|
+
expectedImageCount: message.album.expectedImageCount,
|
|
450
|
+
expectedVideoCount: message.album.expectedVideoCount
|
|
451
|
+
};
|
|
452
|
+
if (message.albumParentKey) {
|
|
453
|
+
m.messageContextInfo = Object.assign(Object.assign({}, m.messageContextInfo), { messageAssociation: {
|
|
454
|
+
associationType: Types_1.WAProto.MessageAssociation ? Types_1.WAProto.MessageAssociation.AssociationType.MEDIA_ALBUM : 3,
|
|
455
|
+
parentMessageKey: message.albumParentKey
|
|
456
|
+
} });
|
|
457
|
+
}
|
|
458
|
+
}
|
|
447
459
|
else {
|
|
448
460
|
m = await (0, exports.prepareWAMessageMedia)(message, options);
|
|
449
461
|
}
|
|
@@ -452,8 +464,24 @@ const generateWAMessageContent = async (message, options) => {
|
|
|
452
464
|
}
|
|
453
465
|
if ('mentions' in message && ((_a = message.mentions) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
454
466
|
const [messageType] = Object.keys(m);
|
|
455
|
-
|
|
456
|
-
|
|
467
|
+
const key = m[messageType];
|
|
468
|
+
if (key) {
|
|
469
|
+
if (!key.contextInfo) {
|
|
470
|
+
key.contextInfo = {};
|
|
471
|
+
}
|
|
472
|
+
key.contextInfo.mentionedJid = message.mentions;
|
|
473
|
+
if (message.mentionAll) {
|
|
474
|
+
key.contextInfo.nonJidMentions = 1;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
else if (key !== undefined) {
|
|
478
|
+
m[messageType] = {
|
|
479
|
+
contextInfo: {
|
|
480
|
+
mentionedJid: message.mentions,
|
|
481
|
+
nonJidMentions: message.mentionAll ? 1 : 0
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
457
485
|
}
|
|
458
486
|
if ('edit' in message) {
|
|
459
487
|
m = {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeOfflineNodeProcessor = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Processes offline nodes sequentially.
|
|
6
|
+
* Nodes arriving while offline are queued and processed in order
|
|
7
|
+
* once a handler invokes enqueue().
|
|
8
|
+
*/
|
|
9
|
+
const makeOfflineNodeProcessor = (nodeProcessorMap, onUnexpectedError, ws) => {
|
|
10
|
+
const nodes = [];
|
|
11
|
+
let isProcessing = false;
|
|
12
|
+
const enqueue = (type, node) => {
|
|
13
|
+
nodes.push({ type, node });
|
|
14
|
+
if (isProcessing) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
isProcessing = true;
|
|
18
|
+
const promise = async () => {
|
|
19
|
+
while (nodes.length && ws.isOpen) {
|
|
20
|
+
const { type, node } = nodes.shift();
|
|
21
|
+
const nodeProcessor = nodeProcessorMap.get(type);
|
|
22
|
+
if (!nodeProcessor) {
|
|
23
|
+
onUnexpectedError(new Error(`unknown offline node type: ${type}`), 'processing offline node');
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
await nodeProcessor(node);
|
|
27
|
+
}
|
|
28
|
+
isProcessing = false;
|
|
29
|
+
};
|
|
30
|
+
promise().catch(error => onUnexpectedError(error, 'processing offline nodes'));
|
|
31
|
+
};
|
|
32
|
+
return { enqueue };
|
|
33
|
+
};
|
|
34
|
+
exports.makeOfflineNodeProcessor = makeOfflineNodeProcessor;
|