@aunucore/baileys 1.0.78 → 1.0.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/Socket/chats.js +29 -3
- package/lib/Socket/communities.js +5 -0
- package/lib/Socket/groups.js +7 -0
- package/lib/Socket/index.js +26 -0
- package/lib/Socket/messages-recv.js +56 -4
- package/lib/Utils/auth-utils.js +43 -3
- package/lib/Utils/bounded-cache.js +22 -5
- package/lib/Utils/lid-store.js +21 -2
- package/lib/Utils/noise-handler.js +27 -3
- package/lib/Utils/process-message.js +7 -1
- package/lib/Utils/sent-message-cache.js +10 -2
- package/lib/Utils/signal.js +19 -0
- package/lib/Utils/use-multi-file-auth-state.js +30 -1
- package/lib/Utils/validate-connection.js +44 -1
- package/package.json +1 -1
package/lib/Socket/chats.js
CHANGED
|
@@ -189,7 +189,15 @@ export const makeChatsSocket = (config) => {
|
|
|
189
189
|
if (!jid) {
|
|
190
190
|
throw new Boom('Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update');
|
|
191
191
|
}
|
|
192
|
-
|
|
192
|
+
// AunuBaileys (1.0.79, HB-125): creds.me is undefined until
|
|
193
|
+
// pairing completes (and again after a pairing rollback) —
|
|
194
|
+
// the bare deref below threw a raw TypeError for a host
|
|
195
|
+
// calling too early. Fail like the guarded send paths.
|
|
196
|
+
const meId = authState.creds.me?.id;
|
|
197
|
+
if (!meId) {
|
|
198
|
+
throw new Boom('Not authenticated');
|
|
199
|
+
}
|
|
200
|
+
if (jidNormalizedUser(jid) !== jidNormalizedUser(meId)) {
|
|
193
201
|
targetJid = jidNormalizedUser(jid); // in case it is someone other than us
|
|
194
202
|
}
|
|
195
203
|
else {
|
|
@@ -219,7 +227,13 @@ export const makeChatsSocket = (config) => {
|
|
|
219
227
|
if (!jid) {
|
|
220
228
|
throw new Boom('Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update');
|
|
221
229
|
}
|
|
222
|
-
|
|
230
|
+
// AunuBaileys (1.0.79, HB-125): same bare creds.me.id deref
|
|
231
|
+
// as updateProfilePicture — same guard.
|
|
232
|
+
const meId = authState.creds.me?.id;
|
|
233
|
+
if (!meId) {
|
|
234
|
+
throw new Boom('Not authenticated');
|
|
235
|
+
}
|
|
236
|
+
if (jidNormalizedUser(jid) !== jidNormalizedUser(meId)) {
|
|
223
237
|
targetJid = jidNormalizedUser(jid); // in case it is someone other than us
|
|
224
238
|
}
|
|
225
239
|
else {
|
|
@@ -529,6 +543,13 @@ export const makeChatsSocket = (config) => {
|
|
|
529
543
|
};
|
|
530
544
|
const sendPresenceUpdate = async (type, toJid) => {
|
|
531
545
|
const me = authState.creds.me;
|
|
546
|
+
// AunuBaileys (1.0.79, HB-125): me is undefined until pairing
|
|
547
|
+
// completes; both branches below deref it bare (me.name in
|
|
548
|
+
// this one, me.lid/me.id in the chatstate branch) and threw
|
|
549
|
+
// a raw TypeError for an early host call.
|
|
550
|
+
if (!me?.id) {
|
|
551
|
+
throw new Boom('Not authenticated');
|
|
552
|
+
}
|
|
532
553
|
if (type === 'available' || type === 'unavailable') {
|
|
533
554
|
if (!me.name) {
|
|
534
555
|
logger.warn('no name present, ignoring presence update request...');
|
|
@@ -851,7 +872,12 @@ export const makeChatsSocket = (config) => {
|
|
|
851
872
|
}
|
|
852
873
|
ev.emit('messages.upsert', { messages: [msg], type });
|
|
853
874
|
if (!!msg.pushName) {
|
|
854
|
-
|
|
875
|
+
// AunuBaileys (1.0.79, HB-125): me?.id — a fromMe message
|
|
876
|
+
// with a pushName processed before pairing completes
|
|
877
|
+
// threw a raw TypeError here; jidNormalizedUser maps an
|
|
878
|
+
// undefined jid to '' and the fromMe path does not use
|
|
879
|
+
// the computed jid for anything but normalisation.
|
|
880
|
+
let jid = msg.key.fromMe ? authState.creds.me?.id : msg.key.participant || msg.key.remoteJid;
|
|
855
881
|
jid = jidNormalizedUser(jid);
|
|
856
882
|
if (!msg.key.fromMe) {
|
|
857
883
|
ev.emit('contacts.update', [{ id: jid, notify: msg.pushName, verifiedName: msg.verifiedBizName }]);
|
|
@@ -246,6 +246,11 @@ export const makeCommunitiesSocket = (config) => {
|
|
|
246
246
|
*/
|
|
247
247
|
communityAcceptInviteV4: ev.createBufferedFunction(async (key, inviteMessage) => {
|
|
248
248
|
key = typeof key === 'string' ? { remoteJid: key } : key;
|
|
249
|
+
// AunuBaileys (1.0.79, HB-125): same bare creds.me.id
|
|
250
|
+
// deref as groupAcceptInviteV4 — same guard.
|
|
251
|
+
if (!authState.creds.me?.id) {
|
|
252
|
+
throw new Boom('Not authenticated');
|
|
253
|
+
}
|
|
249
254
|
const results = await communityQuery(inviteMessage.groupJid, 'set', [
|
|
250
255
|
{
|
|
251
256
|
tag: 'accept',
|
package/lib/Socket/groups.js
CHANGED
|
@@ -207,6 +207,13 @@ export const makeGroupsSocket = (config) => {
|
|
|
207
207
|
*/
|
|
208
208
|
groupAcceptInviteV4: ev.createBufferedFunction(async (key, inviteMessage) => {
|
|
209
209
|
key = typeof key === 'string' ? { remoteJid: key } : key;
|
|
210
|
+
// AunuBaileys (1.0.79, HB-125): creds.me.id is
|
|
211
|
+
// dereferenced for the stub message below (and inside
|
|
212
|
+
// processMessage); before pairing completes it threw a
|
|
213
|
+
// raw TypeError. Fail before any network work.
|
|
214
|
+
if (!authState.creds.me?.id) {
|
|
215
|
+
throw new Boom('Not authenticated');
|
|
216
|
+
}
|
|
210
217
|
const results = await groupQuery(inviteMessage.groupJid, 'set', [
|
|
211
218
|
{
|
|
212
219
|
tag: 'accept',
|
package/lib/Socket/index.js
CHANGED
|
@@ -25,6 +25,32 @@ const makeWASocket = (config) => {
|
|
|
25
25
|
...DEFAULT_CONNECTION_CONFIG.transactionOpts,
|
|
26
26
|
...(config?.transactionOpts ?? {})
|
|
27
27
|
};
|
|
28
|
+
// AunuBaileys (1.0.80, HB-139 + HB-174 + addendum HB-139): the
|
|
29
|
+
// numeric connection config was spread raw above — an explicit
|
|
30
|
+
// undefined/NaN from the host (classically `Number(env.X)` of an
|
|
31
|
+
// unset variable) silently replaced the defaults, and every
|
|
32
|
+
// failure mode is silent: promiseTimeout(NaN) never settles (a
|
|
33
|
+
// half-open socket hangs forever), a NaN/0/negative
|
|
34
|
+
// keepAliveIntervalMs collapses setInterval to 1 ms (~850 ping
|
|
35
|
+
// IQs per second, and the dead-connection watchdog compares
|
|
36
|
+
// against NaN so it never fires), and a NaN/Infinity
|
|
37
|
+
// maxMsgRetryCount breaks both retry gates in opposite
|
|
38
|
+
// directions at once (retry receipts never capped, resends
|
|
39
|
+
// never sent). Sanitise once, here at the boundary: timeouts
|
|
40
|
+
// and intervals must be positive finite (else the documented
|
|
41
|
+
// default), the retry count must be finite (else the default),
|
|
42
|
+
// and qrTimeout — which has no Defaults entry (genPairQR falls
|
|
43
|
+
// back with `|| 60000` / `|| 20000`) — must be positive finite
|
|
44
|
+
// or absent (a negative value is truthy and would expire every
|
|
45
|
+
// QR instantly).
|
|
46
|
+
const positiveFiniteOr = (value, fallback) => (Number.isFinite(value) && value > 0 ? value : fallback);
|
|
47
|
+
newConfig.connectTimeoutMs = positiveFiniteOr(newConfig.connectTimeoutMs, DEFAULT_CONNECTION_CONFIG.connectTimeoutMs);
|
|
48
|
+
newConfig.defaultQueryTimeoutMs = positiveFiniteOr(newConfig.defaultQueryTimeoutMs, DEFAULT_CONNECTION_CONFIG.defaultQueryTimeoutMs);
|
|
49
|
+
newConfig.keepAliveIntervalMs = positiveFiniteOr(newConfig.keepAliveIntervalMs, DEFAULT_CONNECTION_CONFIG.keepAliveIntervalMs);
|
|
50
|
+
newConfig.maxMsgRetryCount = Number.isFinite(newConfig.maxMsgRetryCount) ? newConfig.maxMsgRetryCount : DEFAULT_CONNECTION_CONFIG.maxMsgRetryCount;
|
|
51
|
+
if (newConfig.qrTimeout !== undefined && !(Number.isFinite(newConfig.qrTimeout) && newConfig.qrTimeout > 0)) {
|
|
52
|
+
newConfig.qrTimeout = undefined;
|
|
53
|
+
}
|
|
28
54
|
// If the user hasn't provided their own history sync function,
|
|
29
55
|
// let's create a default one that respects the syncFullHistory flag.
|
|
30
56
|
if (config.shouldSyncHistoryMessage === undefined) {
|
|
@@ -12,6 +12,10 @@ import { createOfflineNodeQueue } from '../Utils/offline-queue.js';
|
|
|
12
12
|
import { areJidsSameUser, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, isJidGroup, isJidStatusBroadcast, isJidUser, isLidUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
13
13
|
import { extractGroupMetadata } from './groups.js';
|
|
14
14
|
import { makeMessagesSocket } from './messages-send.js';
|
|
15
|
+
/** AunuBaileys (1.0.79, HB-201): the HB-98 key-material test — key
|
|
16
|
+
* material is a Buffer (or a base64 string in a hand-edited creds
|
|
17
|
+
* file); anything else is a wrong-typed corrupt value. */
|
|
18
|
+
const isKeyMaterial = (value) => Buffer.isBuffer(value) || typeof value === 'string';
|
|
15
19
|
/** AunuBaileys: classify an <unavailable/> placeholder — 'bot' when a
|
|
16
20
|
* <bot/> child rides along, 'hosted' when the node says so, otherwise
|
|
17
21
|
* the unavailable type attribute (e.g. 'view_once'). */
|
|
@@ -82,6 +86,13 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
82
86
|
// AunuBaileys: terima panggilan masuk (node kontrol <accept>) — pasangan
|
|
83
87
|
// rejectCall bawaan; media VOIP-nya sendiri di luar cakupan protokol chat.
|
|
84
88
|
const acceptCall = async (callId, callFrom) => {
|
|
89
|
+
// AunuBaileys (1.0.79, HB-172): creds.me.id was dereferenced
|
|
90
|
+
// bare in the stanza literal — a host auto-accepting a call
|
|
91
|
+
// before pairing completes got a raw TypeError instead of
|
|
92
|
+
// the guarded failure the neighbouring helpers give.
|
|
93
|
+
if (!authState.creds.me?.id) {
|
|
94
|
+
throw new Boom('Not authenticated');
|
|
95
|
+
}
|
|
85
96
|
await query(buildCallControlStanza({
|
|
86
97
|
from: authState.creds.me.id,
|
|
87
98
|
to: callFrom,
|
|
@@ -92,6 +103,11 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
92
103
|
};
|
|
93
104
|
// AunuBaileys: akhiri panggilan yang sedang berjalan (<terminate>).
|
|
94
105
|
const terminateCall = async (callId, callFrom, reason = 'user-terminated') => {
|
|
106
|
+
// AunuBaileys (1.0.79, HB-172): same bare creds.me.id deref
|
|
107
|
+
// as acceptCall — guard first.
|
|
108
|
+
if (!authState.creds.me?.id) {
|
|
109
|
+
throw new Boom('Not authenticated');
|
|
110
|
+
}
|
|
95
111
|
await query(buildCallControlStanza({
|
|
96
112
|
from: authState.creds.me.id,
|
|
97
113
|
to: callFrom,
|
|
@@ -102,6 +118,11 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
102
118
|
}));
|
|
103
119
|
};
|
|
104
120
|
const rejectCall = async (callId, callFrom) => {
|
|
121
|
+
// AunuBaileys (1.0.79, HB-172): same bare creds.me.id deref
|
|
122
|
+
// as acceptCall — guard first.
|
|
123
|
+
if (!authState.creds.me?.id) {
|
|
124
|
+
throw new Boom('Not authenticated');
|
|
125
|
+
}
|
|
105
126
|
const stanza = {
|
|
106
127
|
tag: 'call',
|
|
107
128
|
attrs: {
|
|
@@ -144,9 +165,28 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
144
165
|
logger.debug({ retryCount, msgId }, 'reached retry limit');
|
|
145
166
|
return;
|
|
146
167
|
}
|
|
168
|
+
const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds;
|
|
169
|
+
// AunuBaileys (1.0.79, HB-201 + addendum HB-80): the receipt
|
|
170
|
+
// built below always embeds creds.registrationId, and from
|
|
171
|
+
// the second retry on it also embeds the identity and
|
|
172
|
+
// signed-pre-key material — all of it dereferenced bare
|
|
173
|
+
// inside the transaction callback. With a partial creds
|
|
174
|
+
// restore those derefs threw TypeError AFTER the retry
|
|
175
|
+
// counter above had already been consumed: the receipt was
|
|
176
|
+
// never sent, the counter was burned, and every later retry
|
|
177
|
+
// failed at the same spot until the cap left the message a
|
|
178
|
+
// stub forever. (A missing registrationId was worse — it
|
|
179
|
+
// silently encoded as 00000000.) Validate BEFORE the
|
|
180
|
+
// increment and fail descriptively: the counter is only
|
|
181
|
+
// spent on a receipt that can actually be built.
|
|
182
|
+
if (!Number.isFinite(authState.creds.registrationId)) {
|
|
183
|
+
throw new Error('AunuBaileys: creds.registrationId is missing or invalid — cannot send a retry receipt; restore a full backup or pair again');
|
|
184
|
+
}
|
|
185
|
+
if ((retryCount >= 1 || forceIncludeKeys) && (!isKeyMaterial(identityKey?.public) || !isKeyMaterial(signedPreKey?.keyPair?.public) || !isKeyMaterial(signedPreKey?.signature) || typeof signedPreKey?.keyId === 'undefined')) {
|
|
186
|
+
throw new Error('AunuBaileys: creds.signedIdentityKey / creds.signedPreKey are missing or incomplete — cannot include keys in the retry receipt; restore a full backup or pair again');
|
|
187
|
+
}
|
|
147
188
|
retryCount += 1;
|
|
148
189
|
msgRetryCache.set(key, retryCount);
|
|
149
|
-
const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds;
|
|
150
190
|
if (retryCount === 1) {
|
|
151
191
|
// AunuBaileys (1.0.70): requestPlaceholderResend waits 5s
|
|
152
192
|
// for the message to arrive by other means — awaiting it
|
|
@@ -206,7 +246,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
206
246
|
attrs: {},
|
|
207
247
|
content: [
|
|
208
248
|
{ tag: 'type', attrs: {}, content: Buffer.from(KEY_BUNDLE_TYPE) },
|
|
209
|
-
{ tag: 'identity', attrs: {}, content: identityKey.public },
|
|
249
|
+
{ tag: 'identity', attrs: {}, content: keyMaterialToBuffer(identityKey.public) },
|
|
210
250
|
xmppPreKey(key, +keyId),
|
|
211
251
|
xmppSignedPreKey(signedPreKey),
|
|
212
252
|
{ tag: 'device-identity', attrs: {}, content: deviceIdentity }
|
|
@@ -1042,7 +1082,14 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1042
1082
|
// vanished silently if the retry also failed
|
|
1043
1083
|
// (HB-90; upstream v7 falls through to the
|
|
1044
1084
|
// upsert for exactly this reason).
|
|
1045
|
-
|
|
1085
|
+
// AunuBaileys (1.0.79, HB-131): me?.id — in
|
|
1086
|
+
// the pre-success window creds.me is still
|
|
1087
|
+
// undefined and the bare deref threw AFTER the
|
|
1088
|
+
// retry receipt had already been fired: the
|
|
1089
|
+
// stub was never upserted and the stanza was
|
|
1090
|
+
// NACKed 500 (delivered + failed). cleanMessage
|
|
1091
|
+
// tolerates an undefined meId.
|
|
1092
|
+
cleanMessage(msg, authState.creds.me?.id);
|
|
1046
1093
|
await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
|
|
1047
1094
|
}
|
|
1048
1095
|
else {
|
|
@@ -1080,7 +1127,12 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1080
1127
|
await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync');
|
|
1081
1128
|
}
|
|
1082
1129
|
}
|
|
1083
|
-
|
|
1130
|
+
// AunuBaileys (1.0.79, HB-131): me?.id — the
|
|
1131
|
+
// bare deref threw here AFTER sendReceipt had
|
|
1132
|
+
// already gone out, NACKing a delivered message
|
|
1133
|
+
// 500 and bursting redeliveries until CB:success
|
|
1134
|
+
// landed. cleanMessage tolerates undefined.
|
|
1135
|
+
cleanMessage(msg, authState.creds.me?.id);
|
|
1084
1136
|
await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
|
|
1085
1137
|
})
|
|
1086
1138
|
]);
|
package/lib/Utils/auth-utils.js
CHANGED
|
@@ -96,8 +96,16 @@ export const addTransactionCapability = (state, logger, { maxCommitRetries, dela
|
|
|
96
96
|
// commit attempt. Fall back per-field to the Defaults values, and
|
|
97
97
|
// clamp retries to at least 1 so an explicit 0 means exactly one
|
|
98
98
|
// attempt, matching host intent.
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
// AunuBaileys (1.0.79, addendum HB-104): the ?? fallback lets NaN
|
|
100
|
+
// through (Math.max(1, NaN) is NaN — the commit loop below then
|
|
101
|
+
// runs ZERO times and every mutating transaction throws without
|
|
102
|
+
// a single attempt) and lets Infinity through (the loop never
|
|
103
|
+
// ends on a failing store and wedges the transaction mutex —
|
|
104
|
+
// the whole key store deadlocks until restart). Sanitise at the
|
|
105
|
+
// boundary: retries must be a finite integer, clamped to 1..10;
|
|
106
|
+
// a non-finite or negative delay falls back to the default.
|
|
107
|
+
const commitRetries = Number.isFinite(maxCommitRetries) ? Math.min(10, Math.max(1, Math.floor(maxCommitRetries))) : 10;
|
|
108
|
+
const commitDelayMs = Number.isFinite(delayBetweenTriesMs) && delayBetweenTriesMs >= 0 ? delayBetweenTriesMs : 3000;
|
|
101
109
|
// number of queries made to the DB during the transaction
|
|
102
110
|
// only there for logging purposes
|
|
103
111
|
let dbQueriesInTransaction = 0;
|
|
@@ -114,6 +122,20 @@ export const addTransactionCapability = (state, logger, { maxCommitRetries, dela
|
|
|
114
122
|
const transactionContext = new AsyncLocalStorage();
|
|
115
123
|
const runTransaction = async (work) => {
|
|
116
124
|
let result;
|
|
125
|
+
// AunuBaileys (1.0.79, HB-236): a nested transaction joins the
|
|
126
|
+
// running one and SHARES its cache/mutations — snapshot both
|
|
127
|
+
// when entering a nested frame. If the nested work throws,
|
|
128
|
+
// the snapshot is restored before the error propagates (see
|
|
129
|
+
// the catch below): without it, an outer frame that catches
|
|
130
|
+
// the throw would commit the inner frame's partial writes as
|
|
131
|
+
// if nothing had failed — the only "rollback" was a throw
|
|
132
|
+
// escaping the OUTERMOST frame.
|
|
133
|
+
const snapshot = transactionsInProgress > 0
|
|
134
|
+
? {
|
|
135
|
+
cache: Object.fromEntries(Object.entries(transactionCache).map(([type, dict]) => [type, { ...dict }])),
|
|
136
|
+
mutations: Object.fromEntries(Object.entries(mutations).map(([type, dict]) => [type, { ...dict }]))
|
|
137
|
+
}
|
|
138
|
+
: undefined;
|
|
117
139
|
transactionsInProgress += 1;
|
|
118
140
|
if (transactionsInProgress === 1) {
|
|
119
141
|
logger.trace('entering transaction');
|
|
@@ -159,6 +181,17 @@ export const addTransactionCapability = (state, logger, { maxCommitRetries, dela
|
|
|
159
181
|
}
|
|
160
182
|
}
|
|
161
183
|
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
// AunuBaileys (1.0.79, HB-236): roll a failed NESTED frame
|
|
186
|
+
// back to its entry snapshot (see above); the outermost
|
|
187
|
+
// frame needs no restore — the finally below resets the
|
|
188
|
+
// shared state outright.
|
|
189
|
+
if (snapshot) {
|
|
190
|
+
transactionCache = snapshot.cache;
|
|
191
|
+
mutations = snapshot.mutations;
|
|
192
|
+
}
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
162
195
|
finally {
|
|
163
196
|
transactionsInProgress -= 1;
|
|
164
197
|
if (transactionsInProgress === 0) {
|
|
@@ -173,7 +206,14 @@ export const addTransactionCapability = (state, logger, { maxCommitRetries, dela
|
|
|
173
206
|
get: async (type, ids) => {
|
|
174
207
|
if (isInTransaction()) {
|
|
175
208
|
const dict = transactionCache[type];
|
|
176
|
-
|
|
209
|
+
// AunuBaileys (1.0.79, HB-233): cache PRESENCE — not
|
|
210
|
+
// the value — decides whether a fetch is needed. A
|
|
211
|
+
// key explicitly set to undefined in this transaction
|
|
212
|
+
// is a deletion marker; the typeof test could not
|
|
213
|
+
// tell it from "never fetched", so get re-read the
|
|
214
|
+
// stale persisted value and resurrected a record the
|
|
215
|
+
// transaction itself had just deleted.
|
|
216
|
+
const idsRequiringFetch = dict ? ids.filter(item => !Object.prototype.hasOwnProperty.call(dict, item)) : ids;
|
|
177
217
|
// only fetch if there are any items to fetch
|
|
178
218
|
if (idsRequiringFetch.length) {
|
|
179
219
|
dbQueriesInTransaction += 1;
|
|
@@ -13,7 +13,15 @@
|
|
|
13
13
|
* the receive path uses.
|
|
14
14
|
*/
|
|
15
15
|
export const makeBoundedTTLCache = ({ ttlSeconds = 3600, maxEntries = 1000 } = {}) => {
|
|
16
|
-
|
|
16
|
+
// AunuBaileys (1.0.80, HB-145): NaN/Infinity config defeated
|
|
17
|
+
// both bounds — `size > NaN` is always false (no eviction,
|
|
18
|
+
// ever) and a NaN ttlMs made expiresAt NaN, which never
|
|
19
|
+
// compares <= now, so entries lived forever. Sanitise at the
|
|
20
|
+
// boundary: finite values keep their exact meaning (counts
|
|
21
|
+
// are floored and clamped at 0), non-finite values fall back
|
|
22
|
+
// to the documented defaults (3600 s / 1000 entries).
|
|
23
|
+
const ttlMs = Number.isFinite(ttlSeconds) ? Math.max(1, ttlSeconds * 1000) : 3600 * 1000;
|
|
24
|
+
const limit = Number.isFinite(maxEntries) ? Math.max(0, Math.floor(maxEntries)) : 1000;
|
|
17
25
|
const map = new Map();
|
|
18
26
|
return {
|
|
19
27
|
get(key) {
|
|
@@ -28,11 +36,20 @@ export const makeBoundedTTLCache = ({ ttlSeconds = 3600, maxEntries = 1000 } = {
|
|
|
28
36
|
},
|
|
29
37
|
set(key, value) {
|
|
30
38
|
map.set(key, { value, expiresAt: Date.now() + ttlMs });
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
// AunuBaileys (1.0.80, HB-226): the old guard broke out
|
|
40
|
+
// when the oldest KEY was `undefined` — but a Map can
|
|
41
|
+
// legitimately HOLD undefined as a key (two in-tree
|
|
42
|
+
// callers can produce one: a call offer without a
|
|
43
|
+
// call-id, a placeholder resend without a message id),
|
|
44
|
+
// and one such entry silently disabled the ceiling
|
|
45
|
+
// until it expired. The iterator's `done` flag is the
|
|
46
|
+
// real "map is empty" signal; deleting an undefined
|
|
47
|
+
// key is perfectly safe.
|
|
48
|
+
while (map.size > limit) {
|
|
49
|
+
const oldest = map.keys().next();
|
|
50
|
+
if (oldest.done)
|
|
34
51
|
break;
|
|
35
|
-
map.delete(oldest);
|
|
52
|
+
map.delete(oldest.value);
|
|
36
53
|
}
|
|
37
54
|
},
|
|
38
55
|
del(key) {
|
package/lib/Utils/lid-store.js
CHANGED
|
@@ -15,7 +15,16 @@ import { jidNormalizedUser, isJidUser, isLidUser } from '../WABinary/jid-utils.j
|
|
|
15
15
|
* fed manually via `learnFromMessage` / `addPair`.
|
|
16
16
|
*/
|
|
17
17
|
export const makeLIDStore = (options = {}) => {
|
|
18
|
-
|
|
18
|
+
// AunuBaileys (1.0.80, HB-190 + addendum HB-145): `?? 5000`
|
|
19
|
+
// let NaN through (the eviction comparison `size > NaN` is
|
|
20
|
+
// always false — the store grew without bound) and let
|
|
21
|
+
// negatives through (evictIfNeeded's while loop then spun
|
|
22
|
+
// forever on an empty map: `size > -1` stays true while
|
|
23
|
+
// delete(undefined) is a no-op — a wedged process, no error).
|
|
24
|
+
// Sanitise at the boundary: finite values are floored and
|
|
25
|
+
// clamped at 0 (0 = store nothing, which terminates),
|
|
26
|
+
// non-finite values fall back to the default 5000.
|
|
27
|
+
const maxEntries = Number.isFinite(options.maxEntries) ? Math.max(0, Math.floor(options.maxEntries)) : 5000;
|
|
19
28
|
// AunuBaileys (1.0.71): optional listener fired when a genuinely
|
|
20
29
|
// NEW or changed PN<->LID pair is learned (re-learning an already
|
|
21
30
|
// known pair does not fire). The socket wires it to 'lid-mapping.update'.
|
|
@@ -29,7 +38,17 @@ export const makeLIDStore = (options = {}) => {
|
|
|
29
38
|
};
|
|
30
39
|
const evictIfNeeded = () => {
|
|
31
40
|
while (pnToLid.size > maxEntries) {
|
|
32
|
-
|
|
41
|
+
// AunuBaileys (1.0.80, HB-190 defence in depth): with a
|
|
42
|
+
// sanitised maxEntries (>= 0) this loop always
|
|
43
|
+
// terminates, but never trust a loop whose only exit is
|
|
44
|
+
// a size comparison — bail out if the map is somehow
|
|
45
|
+
// already empty (the iterator's `done`, not the key's
|
|
46
|
+
// value, is the empty signal).
|
|
47
|
+
const oldest = pnToLid.keys().next();
|
|
48
|
+
if (oldest.done) {
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
const oldestPn = oldest.value;
|
|
33
52
|
const lid = pnToLid.get(oldestPn);
|
|
34
53
|
pnToLid.delete(oldestPn);
|
|
35
54
|
if (lid) {
|
|
@@ -2,7 +2,7 @@ import { Boom } from '@hapi/boom';
|
|
|
2
2
|
import { proto } from '../../WAProto/index.js';
|
|
3
3
|
import { NOISE_MODE, WA_CERT_DETAILS } from '../Defaults/index.js';
|
|
4
4
|
import { decodeBinaryNode } from '../WABinary/index.js';
|
|
5
|
-
import { aesDecryptGCM, aesEncryptGCM, Curve, hkdf, sha256 } from './crypto.js';
|
|
5
|
+
import { aesDecryptGCM, aesEncryptGCM, Curve, hkdf, keyMaterialToBuffer, sha256 } from './crypto.js';
|
|
6
6
|
const generateIV = (counter) => {
|
|
7
7
|
const iv = new ArrayBuffer(12);
|
|
8
8
|
new DataView(iv).setUint32(8, counter);
|
|
@@ -94,6 +94,30 @@ export const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publi
|
|
|
94
94
|
if (!noiseKey?.public || !noiseKey?.private) {
|
|
95
95
|
throw new Error('AunuBaileys: creds.noiseKey is missing or incomplete — the auth state is incomplete or corrupt; restore a full backup or pair again');
|
|
96
96
|
}
|
|
97
|
+
// AunuBaileys (1.0.79, addendum HB-102): the truthiness
|
|
98
|
+
// check above lets a WRONG-TYPED noiseKey through — a
|
|
99
|
+
// number/object died as a bare TypeError inside
|
|
100
|
+
// aesEncryptGCM, and a base64 string was silently
|
|
101
|
+
// encrypted as UTF-8 bytes, so the server received a
|
|
102
|
+
// garbage public key and the handshake failed remotely
|
|
103
|
+
// with no local diagnosis. Normalise with
|
|
104
|
+
// keyMaterialToBuffer (the HB-157 house rule) and
|
|
105
|
+
// require real 32-byte keys.
|
|
106
|
+
const noisePublic = keyMaterialToBuffer(noiseKey.public);
|
|
107
|
+
const noisePrivate = keyMaterialToBuffer(noiseKey.private);
|
|
108
|
+
if (!Buffer.isBuffer(noisePublic) || noisePublic.length !== 32 || !Buffer.isBuffer(noisePrivate) || noisePrivate.length !== 32) {
|
|
109
|
+
throw new Error('AunuBaileys: creds.noiseKey is malformed (public/private must be 32-byte keys) — the auth state is incomplete or corrupt; restore a full backup or pair again');
|
|
110
|
+
}
|
|
111
|
+
// AunuBaileys (1.0.79, HB-238): the first handshake frame
|
|
112
|
+
// is plaintext, so an intercepting middlebox (ISP
|
|
113
|
+
// intercept page, broken proxy) can deliver a decoded
|
|
114
|
+
// handshake with NO serverHello at all — the derefs
|
|
115
|
+
// below then threw a bare TypeError and the host
|
|
116
|
+
// reconnect-looped on it. Fail descriptively, in the
|
|
117
|
+
// HB-80/HB-102 style.
|
|
118
|
+
if (!serverHello || typeof serverHello !== 'object' || !(serverHello.ephemeral instanceof Uint8Array) || !(serverHello.static instanceof Uint8Array) || !(serverHello.payload instanceof Uint8Array)) {
|
|
119
|
+
throw new Error('AunuBaileys: the server handshake is malformed — serverHello or its encrypted parts are missing (the connection may have been intercepted); reconnecting will retry');
|
|
120
|
+
}
|
|
97
121
|
authenticate(serverHello.ephemeral);
|
|
98
122
|
await mixIntoKey(Curve.sharedKey(privateKey, serverHello.ephemeral));
|
|
99
123
|
const decStaticContent = decrypt(serverHello.static);
|
|
@@ -104,8 +128,8 @@ export const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publi
|
|
|
104
128
|
if (issuerSerial !== WA_CERT_DETAILS.SERIAL) {
|
|
105
129
|
throw new Boom('certification match failed', { statusCode: 400 });
|
|
106
130
|
}
|
|
107
|
-
const keyEnc = encrypt(
|
|
108
|
-
await mixIntoKey(Curve.sharedKey(
|
|
131
|
+
const keyEnc = encrypt(noisePublic);
|
|
132
|
+
await mixIntoKey(Curve.sharedKey(noisePrivate, serverHello.ephemeral));
|
|
109
133
|
return keyEnc;
|
|
110
134
|
},
|
|
111
135
|
encodeFrame: (data) => {
|
|
@@ -100,7 +100,13 @@ export function decryptPollVote({ encPayload, encIv }, { pollCreatorJid, pollMsg
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
const processMessage = async (message, { shouldProcessHistoryMsg, placeholderResendCache, ev, creds, keyStore, logger, options }) => {
|
|
103
|
-
|
|
103
|
+
// AunuBaileys (1.0.79, HB-131): creds.me is undefined until
|
|
104
|
+
// pairing completes; the bare deref threw a TypeError for any
|
|
105
|
+
// message/notification processed in that window (reached via
|
|
106
|
+
// upsertMessage, which does not pass cleanMessage first). Every
|
|
107
|
+
// consumer of meId below (isRealMessage, areJidsSameUser,
|
|
108
|
+
// jidNormalizedUser) already tolerates undefined.
|
|
109
|
+
const meId = creds.me?.id;
|
|
104
110
|
const { accountSettings } = creds;
|
|
105
111
|
const chat = { id: jidNormalizedUser(getChatId(message.key)) };
|
|
106
112
|
const isRealMsg = isRealMessage(message, meId);
|
|
@@ -15,6 +15,14 @@
|
|
|
15
15
|
* `sentMessagesCacheSize: 0` in the socket config to disable.
|
|
16
16
|
*/
|
|
17
17
|
export const makeSentMessageCache = (maxEntries = 256, resolveAlternateJid) => {
|
|
18
|
+
// AunuBaileys (1.0.80, HB-145): a NaN cap (host config from an
|
|
19
|
+
// unset env var — sentMessageCacheFor's typeof check passes NaN
|
|
20
|
+
// straight through) made `cache.size > NaN` false forever, so
|
|
21
|
+
// the cache grew without bound; Infinity did the same.
|
|
22
|
+
// Sanitise at the boundary: finite values are floored and
|
|
23
|
+
// clamped at 0 (0 keeps its documented meaning — the cache is
|
|
24
|
+
// disabled), non-finite values fall back to the default 256.
|
|
25
|
+
const limit = Number.isFinite(maxEntries) ? Math.max(0, Math.floor(maxEntries)) : 256;
|
|
18
26
|
const cache = new Map();
|
|
19
27
|
const keyOf = (remoteJid, id) => `${remoteJid}:${id}`;
|
|
20
28
|
let altResolver = typeof resolveAlternateJid === 'function' ? resolveAlternateJid : undefined;
|
|
@@ -30,13 +38,13 @@ export const makeSentMessageCache = (maxEntries = 256, resolveAlternateJid) => {
|
|
|
30
38
|
}
|
|
31
39
|
},
|
|
32
40
|
add: (remoteJid, id, message) => {
|
|
33
|
-
if (!remoteJid || !id || !message ||
|
|
41
|
+
if (!remoteJid || !id || !message || limit <= 0) {
|
|
34
42
|
return;
|
|
35
43
|
}
|
|
36
44
|
const key = keyOf(remoteJid, id);
|
|
37
45
|
cache.delete(key);
|
|
38
46
|
cache.set(key, message);
|
|
39
|
-
while (cache.size >
|
|
47
|
+
while (cache.size > limit) {
|
|
40
48
|
cache.delete(cache.keys().next().value);
|
|
41
49
|
}
|
|
42
50
|
},
|
package/lib/Utils/signal.js
CHANGED
|
@@ -35,6 +35,19 @@ export const generateOrGetPreKeys = (creds, range) => {
|
|
|
35
35
|
throw new Error('AunuBaileys: creds.nextPreKeyId / creds.firstUnuploadedPreKeyId are missing — the auth state is incomplete or corrupt; restore a full backup or pair again');
|
|
36
36
|
}
|
|
37
37
|
const avaliable = creds.nextPreKeyId - creds.firstUnuploadedPreKeyId;
|
|
38
|
+
// AunuBaileys (1.0.80, HB-140 + HB-225): finite-but-INVERTED
|
|
39
|
+
// counters (firstUnuploadedPreKeyId > nextPreKeyId — hand-edited
|
|
40
|
+
// or partially restored creds) made `remaining` exceed `range`
|
|
41
|
+
// by the size of the gap, so the loop below generated keys for
|
|
42
|
+
// the GAP, not the request: gap 50k ≈ 5 s and +48 MB, gap 200k
|
|
43
|
+
// ≈ 21 s and +238 MB, and a ~2^31 gap would freeze the process
|
|
44
|
+
// outright — synchronously, inside the keys transaction, at
|
|
45
|
+
// every login. Same class as HB-84 above (corrupt counters),
|
|
46
|
+
// same answer: refuse descriptively. With consistent counters
|
|
47
|
+
// `remaining` can never exceed `range` by construction.
|
|
48
|
+
if (avaliable < 0) {
|
|
49
|
+
throw new Error('AunuBaileys: creds pre-key counters are inverted (firstUnuploadedPreKeyId > nextPreKeyId) — the auth state is corrupt; restore a full backup or pair again');
|
|
50
|
+
}
|
|
38
51
|
const remaining = range - avaliable;
|
|
39
52
|
const lastPreKeyId = creds.nextPreKeyId + remaining - 1;
|
|
40
53
|
const newPreKeys = {};
|
|
@@ -231,6 +244,12 @@ export const getNextPreKeysNode = async (state, count) => {
|
|
|
231
244
|
if (!isKeyMaterial(creds.signedIdentityKey?.public) || !isKeyMaterial(creds.signedPreKey?.keyPair?.public) || !isKeyMaterial(creds.signedPreKey?.signature) || typeof creds.signedPreKey?.keyId === 'undefined') {
|
|
232
245
|
throw new Error('AunuBaileys: creds.signedIdentityKey / creds.signedPreKey are missing or incomplete — the auth state is incomplete or corrupt; restore a full backup or pair again');
|
|
233
246
|
}
|
|
247
|
+
// AunuBaileys (1.0.79, addendum HB-80): same gap as
|
|
248
|
+
// generateRegistrationNode — a missing registrationId silently
|
|
249
|
+
// became registration 00000000 in the node below.
|
|
250
|
+
if (!Number.isFinite(creds.registrationId)) {
|
|
251
|
+
throw new Error('AunuBaileys: creds.registrationId is missing or invalid — the auth state is incomplete or corrupt; restore a full backup or pair again');
|
|
252
|
+
}
|
|
234
253
|
const { update, preKeys } = await getNextPreKeys(state, count);
|
|
235
254
|
const node = {
|
|
236
255
|
tag: 'iq',
|
|
@@ -36,6 +36,21 @@ const getFileLock = (path) => {
|
|
|
36
36
|
* Again, I wouldn't endorse this for any production level use other than perhaps a bot.
|
|
37
37
|
* Would recommend writing an auth state for use with a proper SQL or No-SQL DB
|
|
38
38
|
* */
|
|
39
|
+
// AunuBaileys (1.0.79, HB-151): the shape a complete creds state
|
|
40
|
+
// must have at load. Key material follows the HB-98 test (a Buffer,
|
|
41
|
+
// or a base64 string in a hand-edited file); the ids must be finite
|
|
42
|
+
// numbers. Anything else is a partial/corrupt restore, not a state
|
|
43
|
+
// the socket can run on.
|
|
44
|
+
const isCredsKeyPair = (kp) => !!kp && typeof kp === 'object' && (Buffer.isBuffer(kp.private) || typeof kp.private === 'string') && (Buffer.isBuffer(kp.public) || typeof kp.public === 'string');
|
|
45
|
+
const isValidCredsShape = (creds) => !!creds &&
|
|
46
|
+
typeof creds === 'object' &&
|
|
47
|
+
!Array.isArray(creds) &&
|
|
48
|
+
isCredsKeyPair(creds.noiseKey) &&
|
|
49
|
+
isCredsKeyPair(creds.signedIdentityKey) &&
|
|
50
|
+
isCredsKeyPair(creds.signedPreKey?.keyPair) &&
|
|
51
|
+
(Buffer.isBuffer(creds.signedPreKey?.signature) || typeof creds.signedPreKey?.signature === 'string') &&
|
|
52
|
+
Number.isFinite(creds.signedPreKey?.keyId) &&
|
|
53
|
+
Number.isFinite(creds.registrationId);
|
|
39
54
|
export const useMultiFileAuthState = async (folder, logger) => {
|
|
40
55
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
56
|
const writeData = async (data, file) => {
|
|
@@ -118,7 +133,21 @@ export const useMultiFileAuthState = async (folder, logger) => {
|
|
|
118
133
|
await mkdir(folder, { recursive: true });
|
|
119
134
|
}
|
|
120
135
|
const fixFileName = (file) => file?.replace(/\//g, '__')?.replace(/:/g, '-');
|
|
121
|
-
const
|
|
136
|
+
const storedCreds = await readData('creds.json');
|
|
137
|
+
// AunuBaileys (1.0.79, HB-151): a creds.json that parses to {} /
|
|
138
|
+
// a partial object / a non-object used to be accepted as a
|
|
139
|
+
// complete auth state (it is truthy), and every downstream
|
|
140
|
+
// consumer then failed in its own way — reconnect loops with
|
|
141
|
+
// misleading TypeErrors, indistinguishable from a lib bug.
|
|
142
|
+
// Validate at this one load choke point; a present-but-invalid
|
|
143
|
+
// creds is discarded loudly, exactly like a corrupt-JSON file
|
|
144
|
+
// since 1.0.63.
|
|
145
|
+
const creds = isValidCredsShape(storedCreds) ? storedCreds : (() => {
|
|
146
|
+
if (storedCreds) {
|
|
147
|
+
logger?.error({ file: 'creds.json' }, 'aunu: creds.json does not have the shape of a complete auth state — discarding it and starting fresh; restore a full backup or pair again');
|
|
148
|
+
}
|
|
149
|
+
return initAuthCreds();
|
|
150
|
+
})();
|
|
122
151
|
let credsWriteInFlight = null;
|
|
123
152
|
let credsWriteQueued = false;
|
|
124
153
|
return {
|
|
@@ -60,7 +60,21 @@ export const generateLoginNode = (userJid, config) => {
|
|
|
60
60
|
if (!decoded) {
|
|
61
61
|
throw new Boom(`Invalid own JID for login node: ${userJid}`, { statusCode: 400 });
|
|
62
62
|
}
|
|
63
|
-
const { user, device } = decoded;
|
|
63
|
+
const { user, device, server } = decoded;
|
|
64
|
+
// AunuBaileys (1.0.79, HB-234): the HB-43 guard above only
|
|
65
|
+
// checks that the JID decodes — a non-numeric user
|
|
66
|
+
// ('abc@s.whatsapp.net') was then silently coerced by +user to
|
|
67
|
+
// username 0 (or clamped to int64-max on a 26-digit overflow)
|
|
68
|
+
// and the login node went out carrying a WRONG identity,
|
|
69
|
+
// looping on server rejects. Validate the parts: the user must
|
|
70
|
+
// be all digits, non-zero and within int64 range; the server
|
|
71
|
+
// must be the user server; and a present device must be a
|
|
72
|
+
// finite number (jidDecode's +device turns 'xx' into NaN,
|
|
73
|
+
// which protobuf then silently encoded as 0).
|
|
74
|
+
const userValue = /^\d+$/.test(user) ? BigInt(user) : 0n;
|
|
75
|
+
if (userValue <= 0n || userValue > 9223372036854775807n || server !== 's.whatsapp.net' || (typeof device !== 'undefined' && !Number.isFinite(device))) {
|
|
76
|
+
throw new Boom(`Invalid own JID for login node: ${userJid}`, { statusCode: 400 });
|
|
77
|
+
}
|
|
64
78
|
const payload = {
|
|
65
79
|
...getClientPayload(config),
|
|
66
80
|
passive: false,
|
|
@@ -83,6 +97,14 @@ export const generateRegistrationNode = ({ registrationId, signedPreKey, signedI
|
|
|
83
97
|
if (!isKeyMaterial(signedIdentityKey?.public) || !isKeyMaterial(signedPreKey?.keyPair?.public) || typeof signedPreKey?.keyId === 'undefined' || !isKeyMaterial(signedPreKey?.signature)) {
|
|
84
98
|
throw new Error('AunuBaileys: creds.signedIdentityKey / creds.signedPreKey are missing or incomplete — the auth state is incomplete or corrupt; restore a full backup or pair again');
|
|
85
99
|
}
|
|
100
|
+
// AunuBaileys (1.0.79, addendum HB-80): registrationId was NOT
|
|
101
|
+
// part of the guard — encodeBigEndian(undefined) does not
|
|
102
|
+
// throw, it silently produces eRegid 00000000, so a partial
|
|
103
|
+
// creds restore registered with a bogus registration id and
|
|
104
|
+
// no local signal at all.
|
|
105
|
+
if (!Number.isFinite(registrationId)) {
|
|
106
|
+
throw new Error('AunuBaileys: creds.registrationId is missing or invalid — the auth state is incomplete or corrupt; restore a full backup or pair again');
|
|
107
|
+
}
|
|
86
108
|
// the app version needs to be md5 hashed
|
|
87
109
|
// and passed in
|
|
88
110
|
const safeVersion = Array.isArray(config.version) && config.version.length ? config.version : [2, 3000, 1043857760];
|
|
@@ -128,6 +150,14 @@ export const generateRegistrationNode = ({ registrationId, signedPreKey, signedI
|
|
|
128
150
|
};
|
|
129
151
|
export const configureSuccessfulPairing = (stanza, { advSecretKey, signedIdentityKey, signalIdentities }) => {
|
|
130
152
|
const msgId = stanza.attrs.id;
|
|
153
|
+
// AunuBaileys (1.0.79, HB-80 sub-instance): the creds fields are
|
|
154
|
+
// dereferenced bare below (Buffer.from(advSecretKey, 'base64'),
|
|
155
|
+
// the identity concat/sign) — a partial creds died as a bare
|
|
156
|
+
// TypeError inside the pair-success handler. Fail descriptively,
|
|
157
|
+
// like the registration guards.
|
|
158
|
+
if (typeof advSecretKey !== 'string' || !advSecretKey || !isKeyMaterial(signedIdentityKey?.public) || !isKeyMaterial(signedIdentityKey?.private)) {
|
|
159
|
+
throw new Error('AunuBaileys: creds.advSecretKey / creds.signedIdentityKey are missing or incomplete — the auth state is incomplete or corrupt; restore a full backup or pair again');
|
|
160
|
+
}
|
|
131
161
|
const pairSuccessNode = getBinaryNodeChild(stanza, 'pair-success');
|
|
132
162
|
const deviceIdentityNode = getBinaryNodeChild(pairSuccessNode, 'device-identity');
|
|
133
163
|
const platformNode = getBinaryNodeChild(pairSuccessNode, 'platform');
|
|
@@ -138,6 +168,19 @@ export const configureSuccessfulPairing = (stanza, { advSecretKey, signedIdentit
|
|
|
138
168
|
}
|
|
139
169
|
const bizName = businessNode?.attrs.name;
|
|
140
170
|
const jid = deviceNode.attrs.jid;
|
|
171
|
+
// AunuBaileys (1.0.79, HB-239): a pair-success stanza without a
|
|
172
|
+
// device jid used to store me.id = undefined SILENTLY — pairing
|
|
173
|
+
// "succeeded", and the NEXT boot took the login path and looped
|
|
174
|
+
// on the HB-43 Boom 400 forever. Reject the stanza here, where
|
|
175
|
+
// the defect is. The addendum: a device-identity node without
|
|
176
|
+
// content died as protobufjs' bare 'Error: illegal buffer' —
|
|
177
|
+
// same missing-guard class, same fix.
|
|
178
|
+
if (typeof jid !== 'string' || !jid) {
|
|
179
|
+
throw new Boom('Missing jid on the device node in pair success stanza', { data: stanza });
|
|
180
|
+
}
|
|
181
|
+
if (!deviceIdentityNode.content?.length) {
|
|
182
|
+
throw new Boom('Missing content on the device-identity node in pair success stanza', { data: stanza });
|
|
183
|
+
}
|
|
141
184
|
const { details, hmac, accountType } = proto.ADVSignedDeviceIdentityHMAC.decode(deviceIdentityNode.content);
|
|
142
185
|
const isHostedAccount = accountType !== undefined && accountType === proto.ADVEncryptionType.HOSTED;
|
|
143
186
|
const hmacPrefix = isHostedAccount ? Buffer.from([6, 5]) : Buffer.alloc(0);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aunucore/baileys",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.80",
|
|
5
5
|
"description": "AunuBaileys — the self-healing WhatsApp Web API for Node.js. Bad MAC and \"2000 messages into the future\" errors repair themselves, LID and phone-number identities stay in sync, anti-ban is built in, pairing codes are custom — plus 29 experimental features no other Baileys fork ships: invisible watermarks, per-person tag-all cards, side-profile text, ghost mentions and more. ESM, CJS and full TypeScript types.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"whatsapp",
|