@nexustechpro/baileys 2.0.6 → 2.1.0

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,204 @@
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) => {
34
- try {
35
- await writeFile(filePath, JSON.stringify(data, BufferJSON.replacer));
36
- }
37
- finally {
38
- release();
39
- }
40
- });
41
- };
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)
68
+ await rename(tp, fp)
69
+ } finally {
70
+ release()
71
+ releaseFileLock(fp)
72
+ }
73
+ }
74
+
75
+ // ─── Read with Checksum Verification ─────────────────────────────────────────
42
76
  const readData = async (file) => {
77
+ const fp = filePath(file)
78
+ const mutex = getFileLock(fp)
79
+ const release = await mutex.acquire()
43
80
  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();
81
+ const raw = await readFile(fp, { encoding: 'utf-8' }).catch(() => null)
82
+ if (!raw) return null
83
+ try {
84
+ const parsed = JSON.parse(raw)
85
+ if (parsed.__checksum) {
86
+ const expected = computeChecksum(JSON.stringify(parsed.data))
87
+ if (expected !== parsed.__checksum) throw new Error('checksum mismatch')
88
+ return JSON.parse(JSON.stringify(parsed.data), BufferJSON.reviver)
53
89
  }
54
- });
55
- }
56
- catch (error) {
57
- return null;
90
+ // legacy file without checksum — read as-is for compatibility
91
+ return JSON.parse(raw, BufferJSON.reviver)
92
+ } catch (err) {
93
+ logger?.warn({ file, err: err.message }, 'failed to read auth file')
94
+ return null
95
+ }
96
+ } finally {
97
+ release()
98
+ releaseFileLock(fp)
58
99
  }
59
- };
100
+ }
101
+
102
+ // ─── Remove File ────────────────────────────────────────────────────────────
60
103
  const removeData = async (file) => {
104
+ const fp = filePath(file)
105
+ const mutex = getFileLock(fp)
106
+ const release = await mutex.acquire()
61
107
  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
- });
108
+ await unlink(fp).catch(() => { })
109
+ } finally {
110
+ release()
111
+ releaseFileLock(fp)
74
112
  }
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`);
113
+ }
114
+
115
+ // ─── Credentials Bootstrap ──────────────────────────────────────────────────
116
+ let creds = (await readData('creds.json')) || initAuthCreds()
117
+ if (!creds.__version) {
118
+ creds.__version = CURRENT_VERSION
119
+ } else if (creds.__version < CURRENT_VERSION) {
120
+ creds.__version = CURRENT_VERSION
121
+ }
122
+
123
+ // ─── Prekey Cleanup ─────────────────────────────────────────────────────────
124
+ let cleanupRunning = false
125
+ let lastCleanupAt = 0
126
+ let lastCleanedPreKeyId = creds.nextPreKeyId
127
+
128
+ const cleanOldPreKeys = async () => {
129
+ const now = Date.now()
130
+ if (cleanupRunning) return
131
+ if (now - lastCleanupAt < CLEANUP_INTERVAL_MS) return
132
+ cleanupRunning = true
133
+ try {
134
+ const minId = creds.nextPreKeyId - preKeyRetention
135
+ if (minId <= 0) return
136
+ const files = await readdir(folder)
137
+ const targets = []
138
+ for (const file of files) {
139
+ const match = file.match(/^pre-key-(\d+)\.json(\.tmp)?$/)
140
+ if (!match) continue
141
+ if (parseInt(match[1], 10) < minId) targets.push(join(folder, file))
142
+ }
143
+ if (!targets.length) return
144
+ await Promise.all(targets.map(f => unlink(f).catch(() => { })))
145
+ lastCleanupAt = Date.now()
146
+ lastCleanedPreKeyId = creds.nextPreKeyId
147
+ logger?.info({ deleted: targets.length, minId }, 'prekey cleanup complete')
148
+ } catch (err) {
149
+ logger?.warn({ err }, 'prekey cleanup failed')
150
+ } finally {
151
+ cleanupRunning = false
81
152
  }
82
153
  }
83
- else {
84
- await mkdir(folder, { recursive: true });
154
+
155
+ cleanOldPreKeys().catch(() => { })
156
+
157
+ // ─── Stats ──────────────────────────────────────────────────────────────────
158
+ const getStats = async () => {
159
+ const files = await readdir(folder).catch(() => [])
160
+ const preKeyFiles = files.filter(f => /^pre-key-\d+\.json$/.test(f))
161
+ return {
162
+ totalFiles: files.length,
163
+ preKeyCount: preKeyFiles.length,
164
+ nextPreKeyId: creds.nextPreKeyId,
165
+ lastCleanupAt: lastCleanupAt ? new Date(lastCleanupAt).toISOString() : null,
166
+ }
85
167
  }
86
- const fixFileName = (file) => file?.replace(/\//g, '__')?.replace(/:/g, '-');
87
- const creds = (await readData('creds.json')) || initAuthCreds();
168
+
88
169
  return {
89
170
  state: {
90
171
  creds,
91
172
  keys: {
92
173
  get: async (type, ids) => {
93
- const data = {};
174
+ const data = {}
94
175
  await Promise.all(ids.map(async (id) => {
95
- let value = await readData(`${type}-${id}.json`);
176
+ let value = await readData(`${type}-${id}.json`)
96
177
  if (type === 'app-state-sync-key' && value) {
97
- value = proto.Message.AppStateSyncKeyData.fromObject(value);
178
+ value = proto.Message.AppStateSyncKeyData.fromObject(value)
98
179
  }
99
- data[id] = value;
100
- }));
101
- return data;
180
+ data[id] = value
181
+ }))
182
+ return data
102
183
  },
103
184
  set: async (data) => {
104
- const tasks = [];
185
+ const tasks = []
105
186
  for (const category in data) {
106
187
  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));
188
+ const value = data[category][id]
189
+ tasks.push(value ? writeData(value, `${category}-${id}.json`) : removeData(`${category}-${id}.json`))
110
190
  }
111
191
  }
112
- await Promise.all(tasks);
113
- }
114
- }
192
+ await Promise.all(tasks)
193
+ },
194
+ },
115
195
  },
116
196
  saveCreds: async () => {
117
- return writeData(creds, 'creds.json');
118
- }
119
- };
120
- };
121
- //# sourceMappingURL=use-multi-file-auth-state.js.map
197
+ if (creds.nextPreKeyId - lastCleanedPreKeyId >= cleanupThreshold) {
198
+ cleanOldPreKeys().catch(() => { })
199
+ }
200
+ return writeData(creds, 'creds.json')
201
+ },
202
+ getStats,
203
+ }
204
+ }
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.0
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.0",
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",
103
+ "@microlink/mql": "^0.16.1",
102
104
  "@hapi/boom": "^9.1.3",
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": {