@nexustechpro/baileys 2.1.0 → 2.1.3

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.
@@ -1,7 +1,10 @@
1
1
  import { getBinaryNodeChild, getBinaryNodeChildren, isHostedLidUser, isHostedPnUser, isJidMetaAI, isLidUser, isPnUser, jidNormalizedUser } from '../WABinary/index.js';
2
+ import { migrateIndexKey, TC_TOKEN_INDEX_KEY } from '../Utils/index.js';
3
+
2
4
  // Same phone-number pattern as WABinary's isJidBot, applied against the user
3
5
  // part so the check is invariant to @c.us ↔ @s.whatsapp.net normalization.
4
6
  const BOT_PHONE_REGEX = /^1313555\d{4}$|^131655500\d{2}$/;
7
+
5
8
  /**
6
9
  * Mirrors WA Web's `Wid.isRegularUser()` (user ∧ ¬PSA ∧ ¬Bot). Used to gate tctoken
7
10
  * storage against malformed notifications — WA Web filters server-side but we
@@ -9,93 +12,73 @@ const BOT_PHONE_REGEX = /^1313555\d{4}$|^131655500\d{2}$/;
9
12
  * Works for both pre- and post-normalized JIDs (`@c.us` vs `@s.whatsapp.net`).
10
13
  */
11
14
  function isRegularUser(jid) {
12
- if (!jid)
13
- return false;
15
+ if (!jid) return false;
14
16
  const user = jid.split('@')[0] ?? '';
15
- if (user === '0')
16
- return false; // PSA
17
- if (BOT_PHONE_REGEX.test(user))
18
- return false; // Bot by phone pattern
19
- if (isJidMetaAI(jid))
20
- return false; // MetaAI (@bot server)
17
+ if (user === '0') return false; // PSA
18
+ if (BOT_PHONE_REGEX.test(user)) return false; // Bot by phone pattern
19
+ if (isJidMetaAI(jid)) return false; // MetaAI (@bot server)
21
20
  return !!(isPnUser(jid) || isLidUser(jid) || isHostedPnUser(jid) || isHostedLidUser(jid) || jid.endsWith('@c.us'));
22
21
  }
22
+
23
23
  const TC_TOKEN_BUCKET_DURATION = 604800; // 7 days
24
24
  const TC_TOKEN_NUM_BUCKETS = 4; // ~28-day rolling window
25
- /** Sentinel key under `tctoken` store holding a JSON array of tracked storage JIDs for cross-session pruning. */
26
- export const TC_TOKEN_INDEX_KEY = '__index';
27
- /** Read the persisted tctoken JID index and return its entries (never contains the sentinel key itself). */
25
+
26
+ /** Read the persisted tctoken JID index and return its entries. */
28
27
  export async function readTcTokenIndex(keys) {
29
- const data = await keys.get('tctoken', [TC_TOKEN_INDEX_KEY]);
30
- const entry = data[TC_TOKEN_INDEX_KEY];
31
- if (!entry?.token?.length)
32
- return [];
33
- try {
34
- const parsed = JSON.parse(Buffer.from(entry.token).toString());
35
- if (!Array.isArray(parsed))
36
- return [];
37
- return parsed.filter((j) => typeof j === 'string' && j.length > 0 && j !== TC_TOKEN_INDEX_KEY);
38
- }
39
- catch {
40
- return [];
41
- }
28
+ const batch = await migrateIndexKey(keys, 'tctoken');
29
+ return Object.keys(batch).filter(j => j && j !== TC_TOKEN_INDEX_KEY);
42
30
  }
43
- /** Build a SignalDataSet fragment that writes the merged index (persisted ∪ added) under the sentinel key. */
31
+
32
+ /** Build a SignalDataSet fragment that writes the merged index (persisted ∪ added) under the index key. */
44
33
  export async function buildMergedTcTokenIndexWrite(keys, addedJids) {
45
- const persisted = await readTcTokenIndex(keys);
46
- const merged = new Set(persisted);
47
- for (const jid of addedJids) {
48
- if (jid && jid !== TC_TOKEN_INDEX_KEY)
49
- merged.add(jid);
50
- }
51
- return {
52
- [TC_TOKEN_INDEX_KEY]: { token: Buffer.from(JSON.stringify([...merged])) }
53
- };
34
+ const batch = await migrateIndexKey(keys, 'tctoken');
35
+ const merged = { ...batch };
36
+ for (const jid of addedJids) { if (jid && jid !== TC_TOKEN_INDEX_KEY) merged[jid] = merged[jid] || true; }
37
+ return { [TC_TOKEN_INDEX_KEY]: merged };
54
38
  }
39
+
55
40
  // WA Web has separate sender/receiver AB props for these but they're identical today
56
41
  export function isTcTokenExpired(timestamp) {
57
- if (timestamp === null || timestamp === undefined)
58
- return true;
42
+ if (timestamp === null || timestamp === undefined) return true;
59
43
  const ts = typeof timestamp === 'string' ? parseInt(timestamp) : timestamp;
60
- if (isNaN(ts))
61
- return true;
44
+ if (isNaN(ts)) return true;
62
45
  const now = Math.floor(Date.now() / 1000);
63
46
  const currentBucket = Math.floor(now / TC_TOKEN_BUCKET_DURATION);
64
47
  const cutoffBucket = currentBucket - (TC_TOKEN_NUM_BUCKETS - 1);
65
48
  const cutoffTimestamp = cutoffBucket * TC_TOKEN_BUCKET_DURATION;
66
49
  return ts < cutoffTimestamp;
67
50
  }
51
+
68
52
  export function shouldSendNewTcToken(senderTimestamp) {
69
- if (senderTimestamp === undefined)
70
- return true;
53
+ if (senderTimestamp === undefined) return true;
71
54
  const now = Math.floor(Date.now() / 1000);
72
55
  const currentBucket = Math.floor(now / TC_TOKEN_BUCKET_DURATION);
73
56
  const senderBucket = Math.floor(senderTimestamp / TC_TOKEN_BUCKET_DURATION);
74
57
  return currentBucket > senderBucket;
75
58
  }
59
+
76
60
  /** Resolve JID to LID for tctoken storage (WA Web stores under LID) */
77
61
  export async function resolveTcTokenJid(jid, getLIDForPN) {
78
- if (isLidUser(jid))
79
- return jid;
62
+ if (isLidUser(jid)) return jid;
80
63
  const lid = await getLIDForPN(jid);
81
64
  return lid ?? jid;
82
65
  }
66
+
83
67
  /** Resolve target JID for issuing privacy token based on AB prop 14303 */
84
68
  export async function resolveIssuanceJid(jid, issueToLid, getLIDForPN, getPNForLID) {
85
69
  if (issueToLid) {
86
- if (isLidUser(jid))
87
- return jid;
70
+ if (isLidUser(jid)) return jid;
88
71
  const lid = await getLIDForPN(jid);
89
72
  return lid ?? jid;
90
73
  }
91
- if (!isLidUser(jid))
92
- return jid;
74
+ if (!isLidUser(jid)) return jid;
93
75
  if (getPNForLID) {
94
76
  const pn = await getPNForLID(jid);
95
77
  return pn ?? jid;
96
78
  }
97
79
  return jid;
98
80
  }
81
+
99
82
  export async function buildTcTokenFromJid({ authState, jid, baseContent = [], getLIDForPN }) {
100
83
  try {
101
84
  const storageJid = await resolveTcTokenJid(jid, getLIDForPN);
@@ -114,49 +97,31 @@ export async function buildTcTokenFromJid({ authState, jid, baseContent = [], ge
114
97
  }
115
98
  return baseContent.length > 0 ? baseContent : undefined;
116
99
  }
117
- baseContent.push({
118
- tag: 'tctoken',
119
- attrs: {},
120
- content: tcTokenBuffer
121
- });
100
+ baseContent.push({ tag: 'tctoken', attrs: {}, content: tcTokenBuffer });
122
101
  return baseContent;
123
- }
124
- catch (error) {
102
+ } catch (error) {
125
103
  return baseContent.length > 0 ? baseContent : undefined;
126
104
  }
127
105
  }
106
+
128
107
  export async function storeTcTokensFromIqResult({ result, fallbackJid, keys, getLIDForPN, onNewJidStored }) {
129
108
  const tokensNode = getBinaryNodeChild(result, 'tokens');
130
- if (!tokensNode)
131
- return;
109
+ if (!tokensNode) return;
132
110
  const tokenNodes = getBinaryNodeChildren(tokensNode, 'token');
133
111
  for (const tokenNode of tokenNodes) {
134
- if (tokenNode.attrs.type !== 'trusted_contact' || !(tokenNode.content instanceof Uint8Array)) {
135
- continue;
136
- }
112
+ if (tokenNode.attrs.type !== 'trusted_contact' || !(tokenNode.content instanceof Uint8Array)) continue;
137
113
  // In notifications tokenNode.attrs.jid is your own device JID, not the sender's
138
114
  const rawJid = jidNormalizedUser(fallbackJid || tokenNode.attrs.jid);
139
- if (!isRegularUser(rawJid))
140
- continue;
115
+ if (!isRegularUser(rawJid)) continue;
141
116
  const storageJid = await resolveTcTokenJid(rawJid, getLIDForPN);
142
117
  const existingTcData = await keys.get('tctoken', [storageJid]);
143
118
  const existingEntry = existingTcData[storageJid];
144
119
  const existingTs = existingEntry?.timestamp ? Number(existingEntry.timestamp) : 0;
145
120
  const incomingTs = tokenNode.attrs.t ? Number(tokenNode.attrs.t) : 0;
146
121
  // timestamp-less tokens would be immediately expired
147
- if (!incomingTs)
148
- continue;
149
- if (existingTs > 0 && existingTs > incomingTs)
150
- continue;
151
- await keys.set({
152
- tctoken: {
153
- [storageJid]: {
154
- ...existingEntry,
155
- token: Buffer.from(tokenNode.content),
156
- timestamp: tokenNode.attrs.t
157
- }
158
- }
159
- });
122
+ if (!incomingTs) continue;
123
+ if (existingTs > 0 && existingTs > incomingTs) continue;
124
+ await keys.set({ tctoken: { [storageJid]: { ...existingEntry, token: Buffer.from(tokenNode.content), timestamp: tokenNode.attrs.t } } });
160
125
  onNewJidStored?.(storageJid);
161
126
  }
162
127
  }
@@ -65,7 +65,12 @@ export const useMultiFileAuthState = async (folder, options = {}) => {
65
65
  const checksum = computeChecksum(serialized)
66
66
  const payload = JSON.stringify({ data: JSON.parse(serialized), __checksum: checksum })
67
67
  await writeFile(tp, payload)
68
- await rename(tp, fp)
68
+ try {
69
+ await rename(tp, fp)
70
+ } catch {
71
+ await writeFile(fp, payload)
72
+ await unlink(tp).catch(() => { })
73
+ }
69
74
  } finally {
70
75
  release()
71
76
  releaseFileLock(fp)
@@ -84,19 +89,24 @@ export const useMultiFileAuthState = async (folder, options = {}) => {
84
89
  const parsed = JSON.parse(raw)
85
90
  if (parsed.__checksum) {
86
91
  const expected = computeChecksum(JSON.stringify(parsed.data))
87
- if (expected !== parsed.__checksum) throw new Error('checksum mismatch')
92
+ if (expected !== parsed.__checksum) {
93
+ logger?.warn({ file }, 'checksum mismatch — reinitializing file')
94
+ await writeFile(fp, JSON.stringify({ data: parsed.data, __checksum: computeChecksum(JSON.stringify(parsed.data)) })).catch(() => { })
95
+ return JSON.parse(JSON.stringify(parsed.data), BufferJSON.reviver)
96
+ }
88
97
  return JSON.parse(JSON.stringify(parsed.data), BufferJSON.reviver)
89
98
  }
90
- // legacy file without checksum read as-is for compatibility
99
+ // legacy file — rewrite with checksum
100
+ const reserialized = JSON.stringify(JSON.parse(raw, BufferJSON.reviver), BufferJSON.replacer)
101
+ const checksum = computeChecksum(reserialized)
102
+ await writeFile(fp, JSON.stringify({ data: JSON.parse(reserialized), __checksum: checksum })).catch(() => { })
91
103
  return JSON.parse(raw, BufferJSON.reviver)
92
104
  } catch (err) {
93
- logger?.warn({ file, err: err.message }, 'failed to read auth file')
105
+ logger?.warn({ file, err: err.message }, 'failed to read auth file — reinitializing')
106
+ await unlink(fp).catch(() => { })
94
107
  return null
95
108
  }
96
- } finally {
97
- release()
98
- releaseFileLock(fp)
99
- }
109
+ } finally { release(); releaseFileLock(fp) }
100
110
  }
101
111
 
102
112
  // ─── Remove File ────────────────────────────────────────────────────────────
package/lib/index.js CHANGED
@@ -31,7 +31,7 @@ const banner = `
31
31
  const info = `
32
32
  ┌───────────────────────────────────────────────────────────────────────┐
33
33
  │ 📦 Package: @nexustechpro/baileys │
34
- │ 🔖 Version: 2.1.0
34
+ │ 🔖 Version: 2.1.3
35
35
  │ ⚡ Status: Production Ready │
36
36
  ├───────────────────────────────────────────────────────────────────────┤
37
37
  │ 🚀 Advanced WhatsApp Web API Client │
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@nexustechpro/baileys",
4
4
  "type": "module",
5
- "version": "2.1.0",
5
+ "version": "2.1.3",
6
6
  "description": "Advanced WhatsApp Web API built on Baileys — interactive messages, buttons, carousels, products, events, payments, polls, albums, sticker packs and more.",
7
7
  "keywords": [
8
8
  "whatsapp",
@@ -100,8 +100,8 @@
100
100
  "dependencies": {
101
101
  "@adiwajshing/keyed-db": "^0.2.4",
102
102
  "@cacheable/node-cache": "^1.4.0",
103
- "@microlink/mql": "^0.16.1",
104
103
  "@hapi/boom": "^9.1.3",
104
+ "@microlink/mql": "^0.16.1",
105
105
  "async-mutex": "^0.5.0",
106
106
  "axios": "^1.6.0",
107
107
  "cache-manager": "latest",