@nexustechpro/baileys 2.0.6 → 2.1.2

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,121 +1,214 @@
1
- import { Mutex } from 'async-mutex';
2
- import { mkdir, readFile, stat, unlink, writeFile } from 'fs/promises';
3
- import { join } from 'path';
4
- import { proto } from '../../WAProto/index.js';
5
- import { initAuthCreds } from './auth-utils.js';
6
- import { BufferJSON } from './generics.js';
7
- // We need to lock files due to the fact that we are using async functions to read and write files
8
- // https://github.com/WhiskeySockets/Baileys/issues/794
9
- // https://github.com/nodejs/node/issues/26338
10
- // Use a Map to store mutexes for each file path
11
- const fileLocks = new Map();
12
- // Get or create a mutex for a specific file path
1
+ import { Mutex } from 'async-mutex'
2
+ import { mkdir, readFile, rename, stat, unlink, writeFile, readdir } from 'fs/promises'
3
+ import { join } from 'path'
4
+ import { createHash } from 'crypto'
5
+ import { proto } from '../../WAProto/index.js'
6
+ import { initAuthCreds } from './auth-utils.js'
7
+ import { BufferJSON } from './generics.js'
8
+
9
+ // ─── Constants ────────────────────────────────────────────────────────────────
10
+ const CURRENT_VERSION = 1
11
+ const DEFAULT_PREKEY_RETENTION = 150
12
+ const DEFAULT_CLEANUP_THRESHOLD = 50
13
+ const CLEANUP_INTERVAL_MS = 10 * 60 * 1000
14
+
15
+ // ─── File Lock Registry ───────────────────────────────────────────────────────
16
+ const fileLocks = new Map()
13
17
  const getFileLock = (path) => {
14
- let mutex = fileLocks.get(path);
15
- if (!mutex) {
16
- mutex = new Mutex();
17
- fileLocks.set(path, mutex);
18
- }
19
- return mutex;
20
- };
18
+ if (!fileLocks.has(path)) fileLocks.set(path, new Mutex())
19
+ return fileLocks.get(path)
20
+ }
21
+ const releaseFileLock = (path) => {
22
+ if (fileLocks.has(path) && !fileLocks.get(path).isLocked()) fileLocks.delete(path)
23
+ }
24
+
25
+ // ─── Checksum ─────────────────────────────────────────────────────────────────
26
+ const computeChecksum = (data) => createHash('sha256').update(data).digest('hex')
27
+
21
28
  /**
22
- * stores the full authentication state in a single folder.
23
- * Far more efficient than singlefileauthstate
29
+ * Production-grade multi-file auth state for Baileys.
30
+ * Atomic writes, checksum integrity, and smart prekey cleanup.
31
+ * Compatible with standard Baileys auth folder format.
24
32
  *
25
- * Again, I wouldn't endorse this for any production level use other than perhaps a bot.
26
- * Would recommend writing an auth state for use with a proper SQL or No-SQL DB
27
- * */
28
- export const useMultiFileAuthState = async (folder) => {
29
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
+ * @param {string} folder - Directory to store auth files
34
+ * @param {object} [options] - Configuration options
35
+ * @param {number} [options.preKeyRetention=150] - How many recent prekeys to keep
36
+ * @param {number} [options.cleanupThreshold=50] - How many new prekeys trigger a cleanup
37
+ * @param {object} [options.logger] - Optional logger with .info/.warn methods
38
+ */
39
+ export const useMultiFileAuthState = async (folder, options = {}) => {
40
+ const {
41
+ preKeyRetention = DEFAULT_PREKEY_RETENTION,
42
+ cleanupThreshold = DEFAULT_CLEANUP_THRESHOLD,
43
+ logger,
44
+ } = options
45
+ const fixFileName = (file) => file?.replace(/\//g, '__')?.replace(/:/g, '-')
46
+ const filePath = (file) => join(folder, fixFileName(file))
47
+ const tmpPath = (file) => filePath(file) + '.tmp'
48
+
49
+ // ─── Folder Bootstrap ───────────────────────────────────────────────────────
50
+ const folderInfo = await stat(folder).catch(() => null)
51
+ if (folderInfo) {
52
+ if (!folderInfo.isDirectory()) throw new Error(`Path exists but is not a directory: ${folder}`)
53
+ } else {
54
+ await mkdir(folder, { recursive: true })
55
+ }
56
+
57
+ // ─── Atomic Write ────────────────────────────────────────────────────────────
30
58
  const writeData = async (data, file) => {
31
- const filePath = join(folder, fixFileName(file));
32
- const mutex = getFileLock(filePath);
33
- return mutex.acquire().then(async (release) => {
59
+ const fp = filePath(file)
60
+ const tp = tmpPath(file)
61
+ const mutex = getFileLock(fp)
62
+ const release = await mutex.acquire()
63
+ try {
64
+ const serialized = JSON.stringify(data, BufferJSON.replacer)
65
+ const checksum = computeChecksum(serialized)
66
+ const payload = JSON.stringify({ data: JSON.parse(serialized), __checksum: checksum })
67
+ await writeFile(tp, payload)
34
68
  try {
35
- await writeFile(filePath, JSON.stringify(data, BufferJSON.replacer));
69
+ await rename(tp, fp)
70
+ } catch {
71
+ await writeFile(fp, payload)
72
+ await unlink(tp).catch(() => { })
36
73
  }
37
- finally {
38
- release();
39
- }
40
- });
41
- };
74
+ } finally {
75
+ release()
76
+ releaseFileLock(fp)
77
+ }
78
+ }
79
+
80
+ // ─── Read with Checksum Verification ─────────────────────────────────────────
42
81
  const readData = async (file) => {
82
+ const fp = filePath(file)
83
+ const mutex = getFileLock(fp)
84
+ const release = await mutex.acquire()
43
85
  try {
44
- const filePath = join(folder, fixFileName(file));
45
- const mutex = getFileLock(filePath);
46
- return await mutex.acquire().then(async (release) => {
47
- try {
48
- const data = await readFile(filePath, { encoding: 'utf-8' });
49
- return JSON.parse(data, BufferJSON.reviver);
50
- }
51
- finally {
52
- release();
86
+ const raw = await readFile(fp, { encoding: 'utf-8' }).catch(() => null)
87
+ if (!raw) return null
88
+ try {
89
+ const parsed = JSON.parse(raw)
90
+ if (parsed.__checksum) {
91
+ const expected = computeChecksum(JSON.stringify(parsed.data))
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
+ }
97
+ return JSON.parse(JSON.stringify(parsed.data), BufferJSON.reviver)
53
98
  }
54
- });
55
- }
56
- catch (error) {
57
- return null;
58
- }
59
- };
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(() => { })
103
+ return JSON.parse(raw, BufferJSON.reviver)
104
+ } catch (err) {
105
+ logger?.warn({ file, err: err.message }, 'failed to read auth file — reinitializing')
106
+ await unlink(fp).catch(() => { })
107
+ return null
108
+ }
109
+ } finally { release(); releaseFileLock(fp) }
110
+ }
111
+
112
+ // ─── Remove File ────────────────────────────────────────────────────────────
60
113
  const removeData = async (file) => {
114
+ const fp = filePath(file)
115
+ const mutex = getFileLock(fp)
116
+ const release = await mutex.acquire()
61
117
  try {
62
- const filePath = join(folder, fixFileName(file));
63
- const mutex = getFileLock(filePath);
64
- return mutex.acquire().then(async (release) => {
65
- try {
66
- await unlink(filePath);
67
- }
68
- catch {
69
- }
70
- finally {
71
- release();
72
- }
73
- });
118
+ await unlink(fp).catch(() => { })
119
+ } finally {
120
+ release()
121
+ releaseFileLock(fp)
74
122
  }
75
- catch { }
76
- };
77
- const folderInfo = await stat(folder).catch(() => { });
78
- if (folderInfo) {
79
- if (!folderInfo.isDirectory()) {
80
- throw new Error(`found something that is not a directory at ${folder}, either delete it or specify a different location`);
123
+ }
124
+
125
+ // ─── Credentials Bootstrap ──────────────────────────────────────────────────
126
+ let creds = (await readData('creds.json')) || initAuthCreds()
127
+ if (!creds.__version) {
128
+ creds.__version = CURRENT_VERSION
129
+ } else if (creds.__version < CURRENT_VERSION) {
130
+ creds.__version = CURRENT_VERSION
131
+ }
132
+
133
+ // ─── Prekey Cleanup ─────────────────────────────────────────────────────────
134
+ let cleanupRunning = false
135
+ let lastCleanupAt = 0
136
+ let lastCleanedPreKeyId = creds.nextPreKeyId
137
+
138
+ const cleanOldPreKeys = async () => {
139
+ const now = Date.now()
140
+ if (cleanupRunning) return
141
+ if (now - lastCleanupAt < CLEANUP_INTERVAL_MS) return
142
+ cleanupRunning = true
143
+ try {
144
+ const minId = creds.nextPreKeyId - preKeyRetention
145
+ if (minId <= 0) return
146
+ const files = await readdir(folder)
147
+ const targets = []
148
+ for (const file of files) {
149
+ const match = file.match(/^pre-key-(\d+)\.json(\.tmp)?$/)
150
+ if (!match) continue
151
+ if (parseInt(match[1], 10) < minId) targets.push(join(folder, file))
152
+ }
153
+ if (!targets.length) return
154
+ await Promise.all(targets.map(f => unlink(f).catch(() => { })))
155
+ lastCleanupAt = Date.now()
156
+ lastCleanedPreKeyId = creds.nextPreKeyId
157
+ logger?.info({ deleted: targets.length, minId }, 'prekey cleanup complete')
158
+ } catch (err) {
159
+ logger?.warn({ err }, 'prekey cleanup failed')
160
+ } finally {
161
+ cleanupRunning = false
81
162
  }
82
163
  }
83
- else {
84
- await mkdir(folder, { recursive: true });
164
+
165
+ cleanOldPreKeys().catch(() => { })
166
+
167
+ // ─── Stats ──────────────────────────────────────────────────────────────────
168
+ const getStats = async () => {
169
+ const files = await readdir(folder).catch(() => [])
170
+ const preKeyFiles = files.filter(f => /^pre-key-\d+\.json$/.test(f))
171
+ return {
172
+ totalFiles: files.length,
173
+ preKeyCount: preKeyFiles.length,
174
+ nextPreKeyId: creds.nextPreKeyId,
175
+ lastCleanupAt: lastCleanupAt ? new Date(lastCleanupAt).toISOString() : null,
176
+ }
85
177
  }
86
- const fixFileName = (file) => file?.replace(/\//g, '__')?.replace(/:/g, '-');
87
- const creds = (await readData('creds.json')) || initAuthCreds();
178
+
88
179
  return {
89
180
  state: {
90
181
  creds,
91
182
  keys: {
92
183
  get: async (type, ids) => {
93
- const data = {};
184
+ const data = {}
94
185
  await Promise.all(ids.map(async (id) => {
95
- let value = await readData(`${type}-${id}.json`);
186
+ let value = await readData(`${type}-${id}.json`)
96
187
  if (type === 'app-state-sync-key' && value) {
97
- value = proto.Message.AppStateSyncKeyData.fromObject(value);
188
+ value = proto.Message.AppStateSyncKeyData.fromObject(value)
98
189
  }
99
- data[id] = value;
100
- }));
101
- return data;
190
+ data[id] = value
191
+ }))
192
+ return data
102
193
  },
103
194
  set: async (data) => {
104
- const tasks = [];
195
+ const tasks = []
105
196
  for (const category in data) {
106
197
  for (const id in data[category]) {
107
- const value = data[category][id];
108
- const file = `${category}-${id}.json`;
109
- tasks.push(value ? writeData(value, file) : removeData(file));
198
+ const value = data[category][id]
199
+ tasks.push(value ? writeData(value, `${category}-${id}.json`) : removeData(`${category}-${id}.json`))
110
200
  }
111
201
  }
112
- await Promise.all(tasks);
113
- }
114
- }
202
+ await Promise.all(tasks)
203
+ },
204
+ },
115
205
  },
116
206
  saveCreds: async () => {
117
- return writeData(creds, 'creds.json');
118
- }
119
- };
120
- };
121
- //# sourceMappingURL=use-multi-file-auth-state.js.map
207
+ if (creds.nextPreKeyId - lastCleanedPreKeyId >= cleanupThreshold) {
208
+ cleanOldPreKeys().catch(() => { })
209
+ }
210
+ return writeData(creds, 'creds.json')
211
+ },
212
+ getStats,
213
+ }
214
+ }
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.0.6
34
+ │ 🔖 Version: 2.1.2
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.0.6",
5
+ "version": "2.1.2",
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",
@@ -49,7 +49,7 @@
49
49
  "events",
50
50
  "payments",
51
51
  "polls",
52
- "album",
52
+ "albums",
53
53
  "sticker-pack",
54
54
  "nodejs",
55
55
  "typescript",
@@ -98,27 +98,29 @@
98
98
  "engine-requirements.js"
99
99
  ],
100
100
  "dependencies": {
101
+ "@adiwajshing/keyed-db": "^0.2.4",
101
102
  "@cacheable/node-cache": "^1.4.0",
102
103
  "@hapi/boom": "^9.1.3",
104
+ "@microlink/mql": "^0.16.1",
103
105
  "async-mutex": "^0.5.0",
104
106
  "axios": "^1.6.0",
105
107
  "cache-manager": "latest",
106
108
  "chalk": "^4.1.2",
107
109
  "fflate": "^0.8.2",
108
- "ffmpeg-static": "^5.2.0",
109
110
  "gradient-string": "^2.0.2",
110
111
  "libphonenumber-js": "^1.13.3",
111
- "link-preview-js": "latest",
112
+ "link-preview-js": "^4.0.3",
112
113
  "lru-cache": "^11.1.0",
113
114
  "music-metadata": "^11.7.0",
114
115
  "p-queue": "^9.0.0",
115
116
  "pino": "^9.6",
116
117
  "protobufjs": "latest",
118
+ "qrcode-terminal": "^0.12.0",
117
119
  "sharp": "^0.32.0",
120
+ "url-regex-safe": "^4.0.0",
118
121
  "uuid": "latest",
119
122
  "whatsapp-rust-bridge": "latest",
120
- "ws": "latest",
121
- "yarn": "^1.22.22"
123
+ "ws": "latest"
122
124
  },
123
125
  "devDependencies": {
124
126
  "@eslint/eslintrc": "^3.3.1",
@@ -139,7 +141,7 @@
139
141
  "jimp": "^1.6.0",
140
142
  "jiti": "^2.4.2",
141
143
  "json": "^11.0.0",
142
- "link-preview-js": "^3.0.4",
144
+ "link-preview-js": "^4.0.3",
143
145
  "lru-cache": "^11.1.0",
144
146
  "open": "^8.4.2",
145
147
  "pino-pretty": "^13.1.1",
@@ -156,7 +158,7 @@
156
158
  "peerDependencies": {
157
159
  "audio-decode": "^2.1.3",
158
160
  "jimp": "^1.6.0",
159
- "link-preview-js": "^3.0.4"
161
+ "link-preview-js": "^4.0.3"
160
162
  },
161
163
  "peerDependenciesMeta": {
162
164
  "audio-decode": {