@codexverified/baileys 2.12.13 → 2.13.13

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 CHANGED
@@ -40,7 +40,7 @@ The package is maintained by **Codex** under the **CodexVerified** organization.
40
40
  | Property | Value |
41
41
  | --- | --- |
42
42
  | Package | `@codexverified/baileys` |
43
- | Version | `2.12.13` |
43
+ | Version | `2.13.13` |
44
44
  | Runtime | Node.js 20 or newer |
45
45
  | Module format | ESM, with CommonJS compatibility where supported |
46
46
  | Type declarations | Included at `lib/index.d.ts` |
@@ -54,7 +54,7 @@ The package is maintained by **Codex** under the **CodexVerified** organization.
54
54
  | Method | Command | Notes |
55
55
  | --- | --- | --- |
56
56
  | From npm | `npm install @codexverified/baileys` | Standard install when published under your organization |
57
- | From local archive | `npm install ./codex-baileys-2.12.13.tgz` | Place the `.tgz` beside your application |
57
+ | From local archive | `npm install ./codex-baileys-2.13.13.tgz` | Place the `.tgz` beside your application |
58
58
  | Source audit (no scripts) | `npm install --ignore-scripts` | Skips install hooks for local review |
59
59
 
60
60
  Both npm and archive installs use the same import name:
@@ -2,7 +2,7 @@ import { proto } from '../../WAProto/index.js';
2
2
  import { makeLibSignalRepository } from '../Signal/libsignal.js';
3
3
  import { Browsers } from '../Utils/browser-utils.js';
4
4
  import logger from '../Utils/logger.js';
5
- const version = [2, 3000, 1040735178];
5
+ const version = [2, 3000, 1043857760];
6
6
  export const UNAUTHORIZED_CODES = [401, 403, 419];
7
7
  export const BIZ_BOT_SUPPORT_PAYLOAD = '{"version":1,"is_ai_message":true,"should_upload_client_logs":false,"should_show_system_message":false,"ticket_id":"7004947587700716","citation_items":[],"ticket_locale":"us"}';
8
8
  export const DEFAULT_ORIGIN = 'https://web.whatsapp.com';
@@ -48,13 +48,13 @@ export const DEFAULT_CONNECTION_CONFIG = {
48
48
  browser: Browsers.macOS('Chrome'),
49
49
  waWebSocketUrl: 'wss://web.whatsapp.com/ws/chat',
50
50
  connectTimeoutMs: 20000,
51
- keepAliveIntervalMs: 15000,
51
+ keepAliveIntervalMs: 30000,
52
52
  logger: logger.child({ class: 'baileys' }),
53
53
  emitOwnEvents: true,
54
54
  defaultQueryTimeoutMs: 60000,
55
55
  customUploadHosts: [],
56
56
  retryRequestDelayMs: 250,
57
- maxMsgRetryCount: 3,
57
+ maxMsgRetryCount: 5,
58
58
  fireInitQueries: true,
59
59
  auth: undefined,
60
60
  markOnlineOnConnect: true,
@@ -66,7 +66,7 @@ export const DEFAULT_CONNECTION_CONFIG = {
66
66
  shouldIgnoreJid: () => false,
67
67
  linkPreviewImageThumbnailWidth: 192,
68
68
  transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 3000 },
69
- generateHighQualityLinkPreview: true,
69
+ generateHighQualityLinkPreview: false,
70
70
  enableAutoSessionRecreation: true,
71
71
  enableRecentMessageCache: true,
72
72
  options: {},
@@ -1,5 +1,6 @@
1
1
  import { promisify } from 'util';
2
- import { inflate } from 'zlib';
2
+ import { pipeline } from 'stream/promises';
3
+ import { createInflate, inflate } from 'zlib';
3
4
  import { proto } from '../../WAProto/index.js';
4
5
  import { WAMessageStubType } from '../Types/index.js';
5
6
  import { isHostedLidUser, isHostedPnUser, isLidUser, isPnUser } from '../WABinary/index.js';
@@ -24,13 +25,13 @@ const extractPnFromMessages = (messages) => {
24
25
  };
25
26
  export const downloadHistory = async (msg, options) => {
26
27
  const stream = await downloadContentFromMessage(msg, 'md-msg-hist', { options });
27
- const bufferArray = [];
28
- for await (const chunk of stream) {
29
- bufferArray.push(chunk);
30
- }
31
- let buffer = Buffer.concat(bufferArray);
32
- // decompress buffer
33
- buffer = await inflatePromise(buffer);
28
+ // Pipe the decrypted stream directly through zlib inflate to avoid
29
+ // allocating an additional intermediate compressed-data buffer.
30
+ const inflater = createInflate();
31
+ const chunks = [];
32
+ inflater.on('data', (chunk) => chunks.push(chunk));
33
+ await pipeline(stream, inflater);
34
+ const buffer = Buffer.concat(chunks);
34
35
  const syncData = proto.HistorySync.decode(buffer);
35
36
  return syncData;
36
37
  };
@@ -55,6 +56,7 @@ export const processHistoryMessage = (item, logger) => {
55
56
  contacts.push({
56
57
  id: chat.id,
57
58
  name: chat.displayName || chat.name || chat.username || undefined,
59
+ username: chat.username || undefined,
58
60
  lid: chat.lidJid || chat.accountLid || undefined,
59
61
  phoneNumber: chat.pnJid || undefined
60
62
  });
@@ -95,7 +97,7 @@ export const processHistoryMessage = (item, logger) => {
95
97
  });
96
98
  }
97
99
  }
98
- chats.push({ ...chat });
100
+ chats.push(chat);
99
101
  }
100
102
  break;
101
103
  case proto.HistorySync.HistorySyncType.PUSH_NAME:
@@ -109,6 +111,7 @@ export const processHistoryMessage = (item, logger) => {
109
111
  contacts,
110
112
  messages,
111
113
  lidPnMappings,
114
+ pastParticipants: item.pastParticipants,
112
115
  syncType: item.syncType,
113
116
  progress: item.progress
114
117
  };
@@ -127,4 +130,4 @@ export const getHistoryMsg = (message) => {
127
130
  const normalizedContent = !!message ? normalizeMessageContent(message) : undefined;
128
131
  const anyHistoryMsg = normalizedContent?.protocolMessage?.historySyncNotification;
129
132
  return anyHistoryMsg;
130
- };
133
+ };
@@ -52,6 +52,13 @@ export class MessageRetryManager {
52
52
  ttlAutopurge: true,
53
53
  updateAgeOnGet: true
54
54
  }); // 15 minutes TTL
55
+ // Cache Signal base keys briefly so duplicate retry receipts can be
56
+ // recognized without repeatedly recreating an otherwise valid session.
57
+ this.baseKeys = new LRUCache({
58
+ max: 1024,
59
+ ttl: 15 * 60 * 1000,
60
+ ttlAutopurge: true
61
+ });
55
62
  this.pendingPhoneRequests = {};
56
63
  this.maxMsgRetryCount = 5;
57
64
  this.statistics = {
@@ -215,6 +222,7 @@ export class MessageRetryManager {
215
222
  this.messageKeyIndex.clear();
216
223
  this.sessionRecreateHistory.clear();
217
224
  this.retryCounters.clear();
225
+ this.baseKeys.clear();
218
226
  for (const messageId of Object.keys(this.pendingPhoneRequests)) {
219
227
  this.cancelPendingPhoneRequest(messageId);
220
228
  }
@@ -227,6 +235,24 @@ export class MessageRetryManager {
227
235
  phoneRequests: 0
228
236
  };
229
237
  }
238
+ saveBaseKey(addr, msgId, baseKey) {
239
+ this.baseKeys.set(`${addr}:${msgId}`, baseKey);
240
+ }
241
+ hasSameBaseKey(addr, msgId, baseKey) {
242
+ const stored = this.baseKeys.get(`${addr}:${msgId}`);
243
+ if (!stored || stored.length !== baseKey.length) {
244
+ return false;
245
+ }
246
+ for (let i = 0; i < stored.length; i++) {
247
+ if (stored[i] !== baseKey[i]) {
248
+ return false;
249
+ }
250
+ }
251
+ return true;
252
+ }
253
+ deleteBaseKey(addr, msgId) {
254
+ this.baseKeys.delete(`${addr}:${msgId}`);
255
+ }
230
256
  keyToString(key) {
231
257
  return `${key.to}${MESSAGE_KEY_SEPARATOR}${key.id}`;
232
258
  }
@@ -238,4 +264,4 @@ export class MessageRetryManager {
238
264
  this.recentMessagesMap.delete(keyStr);
239
265
  this.messageKeyIndex.delete(messageId);
240
266
  }
241
- }
267
+ }
@@ -5,6 +5,7 @@ import { areJidsSameUser, isHostedLidUser, isHostedPnUser, isJidBroadcast, isJid
5
5
  import { aesDecryptGCM, hmacSign } from './crypto.js';
6
6
  import { getKeyAuthor, toNumber } from './generics.js';
7
7
  import { downloadAndProcessHistorySyncNotification } from './history.js';
8
+ import { buildMergedTcTokenIndexWrite, resolveTcTokenJid } from './tc-token-utils.js';
8
9
  const REAL_MSG_STUB_TYPES = new Set([
9
10
  WAMessageStubType.CALL_MISSED_GROUP_VIDEO,
10
11
  WAMessageStubType.CALL_MISSED_GROUP_VOICE,
@@ -12,6 +13,52 @@ const REAL_MSG_STUB_TYPES = new Set([
12
13
  WAMessageStubType.CALL_MISSED_VOICE
13
14
  ]);
14
15
  const REAL_MSG_REQ_ME_STUB_TYPES = new Set([WAMessageStubType.GROUP_PARTICIPANT_ADD]);
16
+ async function storeTcTokensFromHistorySync(chats, signalRepository, keyStore, logger) {
17
+ const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
18
+ const candidates = [];
19
+ for (const chat of chats || []) {
20
+ const ts = chat.tcTokenTimestamp ? toNumber(chat.tcTokenTimestamp) : 0;
21
+ if (chat.tcToken?.length && ts > 0) {
22
+ const jid = jidNormalizedUser(chat.id);
23
+ const storageJid = await resolveTcTokenJid(jid, getLIDForPN);
24
+ candidates.push({
25
+ storageJid,
26
+ token: Buffer.from(chat.tcToken),
27
+ ts,
28
+ senderTs: chat.tcTokenSenderTimestamp ? toNumber(chat.tcTokenSenderTimestamp) : undefined
29
+ });
30
+ }
31
+ }
32
+ if (!candidates.length) {
33
+ return;
34
+ }
35
+ const jids = candidates.map(candidate => candidate.storageJid);
36
+ const existing = await keyStore.get('tctoken', jids);
37
+ const entries = {};
38
+ for (const candidate of candidates) {
39
+ const existingEntry = existing[candidate.storageJid];
40
+ const existingTs = existingEntry?.timestamp ? Number(existingEntry.timestamp) : 0;
41
+ if (existingTs > 0 && existingTs >= candidate.ts) {
42
+ continue;
43
+ }
44
+ entries[candidate.storageJid] = {
45
+ ...existingEntry,
46
+ token: candidate.token,
47
+ timestamp: String(candidate.ts),
48
+ ...(candidate.senderTs !== undefined ? { senderTimestamp: candidate.senderTs } : {})
49
+ };
50
+ }
51
+ if (Object.keys(entries).length) {
52
+ logger?.debug({ count: Object.keys(entries).length }, 'storing tctokens from history sync');
53
+ try {
54
+ const indexWrite = await buildMergedTcTokenIndexWrite(keyStore, Object.keys(entries));
55
+ await keyStore.set({ tctoken: { ...entries, ...indexWrite } });
56
+ }
57
+ catch (err) {
58
+ logger?.warn({ err }, 'failed to store tctokens from history sync');
59
+ }
60
+ }
61
+ }
15
62
  /** Cleans a received message to further processing */
16
63
  export const cleanMessage = (message, meId, meLid) => {
17
64
  // ensure remoteJid and participant doesn't have device or agent in it
@@ -146,6 +193,22 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
146
193
  }
147
194
  const protocolMsg = content?.protocolMessage;
148
195
  if (protocolMsg) {
196
+ // These protocol messages are generated by the user’s primary device and
197
+ // must not be accepted from an arbitrary remote sender. Other protocol
198
+ // types, such as revokes and edits, legitimately originate from peers.
199
+ const SELF_ONLY_TYPES = new Set([
200
+ proto.Message.ProtocolMessage.Type.HISTORY_SYNC_NOTIFICATION,
201
+ proto.Message.ProtocolMessage.Type.APP_STATE_SYNC_KEY_SHARE,
202
+ proto.Message.ProtocolMessage.Type.LID_MIGRATION_MAPPING_SYNC,
203
+ proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_RESPONSE_MESSAGE
204
+ ]);
205
+ if (protocolMsg.type !== null &&
206
+ protocolMsg.type !== undefined &&
207
+ SELF_ONLY_TYPES.has(protocolMsg.type) &&
208
+ !message.key.fromMe) {
209
+ logger?.warn({ msgId: message.key.id, type: protocolMsg.type, from: message.key.participant || message.key.remoteJid }, 'dropping spoofed self-only protocolMessage from non-self origin');
210
+ return;
211
+ }
149
212
  switch (protocolMsg.type) {
150
213
  case proto.Message.ProtocolMessage.Type.HISTORY_SYNC_NOTIFICATION:
151
214
  const histNotification = protocolMsg.historySyncNotification;
@@ -174,6 +237,7 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
174
237
  .storeLIDPNMappings(data.lidPnMappings)
175
238
  .catch(err => logger?.warn({ err }, 'failed to store LID-PN mappings from history sync'));
176
239
  }
240
+ await storeTcTokensFromHistorySync(data.chats, signalRepository, keyStore, logger);
177
241
  ev.emit('messaging-history.set', {
178
242
  ...data,
179
243
  isLatest: histNotification.syncType !== proto.HistorySync.HistorySyncType.ON_DEMAND ? isLatest : undefined,
@@ -383,12 +447,19 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
383
447
  id: jid,
384
448
  author: message.key.participant,
385
449
  authorPn: message.key.participantAlt,
450
+ authorUsername: message.key.participantUsername,
386
451
  participants,
387
452
  action
388
453
  });
389
454
  const emitGroupUpdate = (update) => {
390
455
  ev.emit('groups.update', [
391
- { id: jid, ...update, author: message.key.participant ?? undefined, authorPn: message.key.participantAlt }
456
+ {
457
+ id: jid,
458
+ ...update,
459
+ author: message.key.participant ?? undefined,
460
+ authorPn: message.key.participantAlt,
461
+ authorUsername: message.key.participantUsername
462
+ }
392
463
  ]);
393
464
  };
394
465
  const emitGroupRequestJoin = (participant, action, method) => {
@@ -396,6 +467,7 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
396
467
  id: jid,
397
468
  author: message.key.participant,
398
469
  authorPn: message.key.participantAlt,
470
+ authorUsername: message.key.participantUsername,
399
471
  participant: participant.lid,
400
472
  participantPn: participant.pn,
401
473
  action,
@@ -524,4 +596,4 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
524
596
  ev.emit('chats.update', [chat]);
525
597
  }
526
598
  };
527
- export default processMessage;
599
+ export default processMessage;
@@ -102,7 +102,8 @@ export async function buildTcTokenFromJid({ authState, jid, baseContent = [], ge
102
102
  const tcTokenData = await authState.keys.get('tctoken', [storageJid]);
103
103
  const entry = tcTokenData?.[storageJid];
104
104
  const tcTokenBuffer = entry?.token;
105
- if (!tcTokenBuffer?.length || isTcTokenExpired(entry?.timestamp)) {
105
+ const timestamp = entry?.timestamp;
106
+ if (!tcTokenBuffer?.length || timestamp === undefined || isTcTokenExpired(timestamp)) {
106
107
  if (tcTokenBuffer) {
107
108
  // Preserve senderTimestamp so shouldSendNewTcToken() keeps its dedupe state
108
109
  // after we drop the unusable peer token. Only wipe the record entirely when
@@ -116,7 +117,7 @@ export async function buildTcTokenFromJid({ authState, jid, baseContent = [], ge
116
117
  }
117
118
  baseContent.push({
118
119
  tag: 'tctoken',
119
- attrs: {},
120
+ attrs: { t: String(timestamp) },
120
121
  content: tcTokenBuffer
121
122
  });
122
123
  return baseContent;
@@ -1,5 +1,5 @@
1
1
  import { Mutex } from 'async-mutex';
2
- import { mkdir, readFile, stat, unlink, writeFile } from 'fs/promises';
2
+ import { mkdir, readFile, rename, stat, unlink, writeFile } from 'fs/promises';
3
3
  import { join } from 'path';
4
4
  import { proto } from '../../WAProto/index.js';
5
5
  import { initAuthCreds } from './auth-utils.js';
@@ -31,10 +31,14 @@ export const useMultiFileAuthState = async (folder) => {
31
31
  const filePath = join(folder, fixFileName(file));
32
32
  const mutex = getFileLock(filePath);
33
33
  return mutex.acquire().then(async (release) => {
34
+ const tempPath = `${filePath}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
34
35
  try {
35
- await writeFile(filePath, JSON.stringify(data, BufferJSON.replacer));
36
+ const serialized = JSON.stringify(data, BufferJSON.replacer);
37
+ await writeFile(tempPath, serialized, { encoding: 'utf8', mode: 0o600 });
38
+ await rename(tempPath, filePath);
36
39
  }
37
40
  finally {
41
+ await unlink(tempPath).catch(() => undefined);
38
42
  release();
39
43
  }
40
44
  });
@@ -81,7 +85,7 @@ export const useMultiFileAuthState = async (folder) => {
81
85
  }
82
86
  }
83
87
  else {
84
- await mkdir(folder, { recursive: true });
88
+ await mkdir(folder, { recursive: true, mode: 0o700 });
85
89
  }
86
90
  const fixFileName = (file) => file?.replace(/\//g, '__')?.replace(/:/g, '-');
87
91
  const creds = (await readData('creds.json')) || initAuthCreds();
@@ -117,4 +121,4 @@ export const useMultiFileAuthState = async (folder) => {
117
121
  return writeData(creds, 'creds.json');
118
122
  }
119
123
  };
120
- };
124
+ };
@@ -28,7 +28,7 @@ const getUserAgent = (config) => {
28
28
  };
29
29
  const PLATFORM_MAP = {
30
30
  'Mac OS': proto.ClientPayload.WebInfo.WebSubPlatform.DARWIN,
31
- Windows: proto.ClientPayload.WebInfo.WebSubPlatform.WIN32
31
+ Windows: proto.ClientPayload.WebInfo.WebSubPlatform.WIN_HYBRID
32
32
  };
33
33
  const getWebInfo = (config) => {
34
34
  let webSubPlatform = proto.ClientPayload.WebInfo.WebSubPlatform.WEB_BROWSER;
@@ -48,6 +48,9 @@ const getClientPayload = (config) => {
48
48
  if (!config.browser[1].toLocaleLowerCase().includes('android')) {
49
49
  payload.webInfo = getWebInfo(config);
50
50
  }
51
+ if (config.pushName) {
52
+ payload.pushName = config.pushName;
53
+ }
51
54
  return payload;
52
55
  };
53
56
  export const generateLoginNode = (userJid, config) => {
@@ -203,4 +206,4 @@ export const encodeSignedDeviceIdentity = (account, includeSignatureKey) => {
203
206
  account.accountSignatureKey = null;
204
207
  }
205
208
  return proto.ADVSignedDeviceIdentity.encode(account).finish();
206
- };
209
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codexverified/baileys",
3
- "version": "2.12.13",
3
+ "version": "2.13.13",
4
4
  "description": "CODEX TECHNOLOGY a premium Baileys developed and built by Dev Codex, with Meta compositing, bot planning replay, welcome flow, rich messages, interactive messages, albums, and more.",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",