@zervonex/baileys 3.0.6 → 3.0.7

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
@@ -350,15 +350,14 @@ Two adapters are available. They do **not** share an identical return shape —
350
350
 
351
351
  ### File-based (default)
352
352
 
353
- Atomic writes, sha256 checksum integrity, per-file mutex locking, corrupt file recovery, and legacy file upgrade. Best for single-instance bots or local development.
353
+ Atomic writes (tmp → rename with fallback), corrupt file self-healing, and automatic old pre-key cleanup. Best for single-instance bots or local development.
354
354
 
355
355
  ```javascript
356
356
  import { useMultiFileAuthState } from '@zervonex/baileys'
357
357
 
358
358
  const { state, saveCreds, getStats } = await useMultiFileAuthState('auth_session', {
359
- preKeyRetention: 150, // how many recent prekeys to keep (default: 150)
360
- cleanupThreshold: 50, // new prekeys before a cleanup runs (default: 50)
361
- logger, // optional — logs checksum mismatches and cleanup events
359
+ preKeyRetention: 150, // how many recent prekeys to keep (default: 150)
360
+ cleanupThreshold: 50, // new prekeys before a cleanup runs (default: 50)
362
361
  })
363
362
 
364
363
  const sock = makeWASocket({ auth: state })
@@ -369,15 +368,20 @@ const stats = await getStats()
369
368
  // { totalFiles: 42, preKeyCount: 150, nextPreKeyId: 3200, lastCleanupAt: '2026-07-05T...' }
370
369
  ```
371
370
 
372
- To fully log out and delete a session's files, remove the session folder directly (e.g. `fs.rm(folder, { recursive: true, force: true })`) — there is no `clearSession`/`close` method on this adapter.
371
+ To fully log out and delete a session, remove the session folder directly — there is no `clearSession` method on this adapter:
372
+
373
+ ```javascript
374
+ import { rm } from 'fs/promises'
375
+ await rm('auth_session', { recursive: true, force: true })
376
+ ```
373
377
 
374
378
  ### Keyv-based (production)
375
379
 
376
- Universal adapter for any database backend via [Keyv](https://github.com/jaredwray/keyv). Pass a connection string — the matching backend driver is resolved and imported automatically, no manual adapter imports needed.
380
+ Universal adapter for any database backend via [Keyv](https://www.npmjs.com/package/keyv). Pass a connection string — the matching backend driver is resolved and cached automatically, no manual adapter imports needed.
377
381
 
378
- Storage format matches `useMultiFileAuthState` exactly: every value is wrapped as `{ data, __checksum }` with a sha256 integrity check, so corruption is detected and self-healed the same way on both adapters. Reads also auto-detect and repair values that were written by an older/buggy version of this adapter (including arbitrarily nested `{ value: { value: ... } }` wrapping) — no manual data-fixing needed if you're upgrading from an earlier release.
382
+ Both adapters use the same `BufferJSON` serialization format and auto-detect legacy `{ value: { value: ... } }` double-wrapping from older versions — no manual data migration needed when upgrading.
379
383
 
380
- The first argument is the **session ID** — equivalent to the folder name in `useMultiFileAuthState`. It's used as a Keyv namespace, so multiple bots can safely share one backend. The second argument is a connection string, a pre-built `Keyv` instance, or a raw `KeyvStoreAdapter`:
384
+ The first argument is the **session ID** — equivalent to the folder name in `useMultiFileAuthState`. It is used as a Keyv namespace so multiple bots can safely share one backend without key collisions. The second argument is a connection string, a pre-built `Keyv` instance, or a raw `KeyvStoreAdapter`:
381
385
 
382
386
  ```javascript
383
387
  import { useKeyvAuthState } from '@zervonex/baileys'
@@ -407,52 +411,56 @@ const { state, saveCreds } = await useKeyvAuthState('bot1', 'memcache://localhos
407
411
  const { state, saveCreds } = await useKeyvAuthState('test')
408
412
  ```
409
413
 
410
- Supported connection string protocols: `redis:`/`rediss:`, `mongodb:`/`mongodb+srv:`, `postgres:`/`postgresql:`, `mysql:`, `sqlite:`, `etcd:`, `memcache:`/`memcached:`. All matching driver packages (`@keyv/redis`, `@keyv/mongo`, `@keyv/postgres`, `@keyv/mysql`, `@keyv/sqlite`, `@keyv/etcd`, `@keyv/memcache`) ship as regular dependencies of this package — nothing extra to install.
414
+ Supported protocols: `redis:`/`rediss:`, `mongodb:`/`mongodb+srv:`, `postgres:`/`postgresql:`, `mysql:`, `sqlite:`, `etcd:`, `memcache:`/`memcached:`. All matching driver packages ship as dependencies — nothing extra to install.
411
415
 
412
- For backends without a connection string (e.g. DynamoDB), or to reuse one connection across multiple `useKeyvAuthState` calls yourself, pass a pre-built adapter or `Keyv` instance instead of a string:
416
+ For backends without a connection string (e.g. DynamoDB), or to share one connection across multiple sessions, pass a pre-built adapter or `Keyv` instance:
413
417
 
414
418
  ```javascript
415
- import { useKeyvAuthState } from '@zervonex/baileys'
416
419
  import KeyvMongo from '@keyv/mongo'
420
+ import KeyvDynamo from '@keyv/dynamo'
421
+
422
+ // share one connection across sessions
423
+ const mongo = new KeyvMongo('mongodb://localhost/baileys')
424
+ const { state: s1 } = await useKeyvAuthState('bot1', mongo)
425
+ const { state: s2 } = await useKeyvAuthState('bot2', mongo)
417
426
 
418
- const backend = new KeyvMongo('mongodb://localhost/baileys') // build once, share across sessions
419
- const { state, saveCreds } = await useKeyvAuthState('bot1', backend)
427
+ // DynamoDB (no connection string protocol)
428
+ const dynamo = new KeyvDynamo({ region: 'us-east-1', table: 'baileys_auth' })
429
+ const { state, saveCreds } = await useKeyvAuthState('bot1', dynamo)
420
430
  ```
421
431
 
422
- Same connection string passed as a plain string is automatically cached and reused across every `useKeyvAuthState` call in the process — you don't need to build the instance yourself just to avoid opening duplicate connections, but you can if you want the reference for other purposes.
432
+ Connection strings passed as plain strings are automatically cached and reused across every `useKeyvAuthState` call in the process — no need to build the instance yourself just to avoid duplicate connections.
423
433
 
424
- By default, Mongo/Postgres/MySQL/SQLite store everything in a collection/table named `keyv` (each adapter's own default). Set `collectionName` to use your own name instead — it's mapped to whatever each backend actually calls it (`collection` for Mongo, `table` for Postgres/MySQL/SQLite):
434
+ By default, Mongo/Postgres/MySQL/SQLite store everything in a collection/table named `keyv`. Pass `collectionName` to use your own:
425
435
 
426
436
  ```javascript
427
437
  const { state, saveCreds } = await useKeyvAuthState('bot1', 'mongodb://localhost/baileys', {
428
- collectionName: 'baileys_auth', // instead of the default "keyv" collection
438
+ collectionName: 'baileys_auth',
429
439
  })
430
440
  ```
431
441
 
432
- > Per-type backend routing (e.g. prekeys on SQLite, sessions on Postgres) is not currently supported — one `useKeyvAuthState` call uses one backend for every key type.
442
+ > Per-type backend routing (e.g. prekeys on SQLite, sessions on Postgres) is not currently supported — one `useKeyvAuthState` call uses one backend for all key types.
433
443
 
434
444
  #### Clearing a session (logout)
435
445
 
436
446
  ```javascript
437
447
  const { state, saveCreds, clearSession } = await useKeyvAuthState('bot1', 'redis://localhost:6379')
438
448
 
439
- // on logout / when you want to wipe this session's auth entirely:
440
- await clearSession()
449
+ await clearSession() // wipes all keys for this session, disconnects the store
441
450
  ```
442
451
 
443
- `clearSession()` wipes every key belonging to that `sessionId` — creds, prekeys, sessions, sender keys, everything — by calling `keyv.clear()` which removes all keys scoped to that session's namespace. Only this session's keys are removed; other sessions sharing the same backend are untouched. There is no equivalent method on `useMultiFileAuthState` — delete the session folder instead.
452
+ `clearSession()` removes every key scoped to that session's namespace — creds, prekeys, sessions, sender keys, everything. Other sessions sharing the same backend are untouched.
444
453
 
445
454
  #### Checking auth without loading full state
446
455
 
447
456
  ```javascript
448
457
  import { hasKeyvAuth } from '@zervonex/baileys'
449
458
 
450
- // Check if a session has valid auth (lightweight — no full state load)
451
459
  const valid = await hasKeyvAuth('bot1', backend)
452
460
  // true if creds exist and contain noiseKey + signedIdentityKey
453
461
  ```
454
462
 
455
- `hasKeyvAuth()` queries the backend using the correct key prefix — the same way `useKeyvAuthState` does internally — so it always returns the right answer regardless of your backend. Useful for checking session ownership before deciding whether to restore a connection, without the overhead of initializing a full auth state.
463
+ Lightweight — opens no full auth state, just checks for valid creds under the session's namespace.
456
464
 
457
465
  #### Options
458
466
 
@@ -460,8 +468,7 @@ const valid = await hasKeyvAuth('bot1', backend)
460
468
  |--------|------|---------|-------------|
461
469
  | `preKeyRetention` | `number` | `150` | Number of recent prekeys to keep |
462
470
  | `cleanupThreshold` | `number` | `50` | New prekeys generated before cleanup runs |
463
- | `logger` | `object` | `undefined` | Logger with `.info` / `.warn` methods |
464
- | `collectionName` | `string` | adapter's own default (`"keyv"`) | Custom collection/table name — Keyv-based adapter only, ignored for connection-string-less (in-memory) and pre-built adapter/`Keyv` instances |
471
+ | `collectionName` | `string` | `"keyv"` | Custom collection/table name (Keyv adapter only, ignored for in-memory and pre-built instances) |
465
472
 
466
473
  #### Return values
467
474
 
@@ -471,7 +478,6 @@ const valid = await hasKeyvAuth('bot1', backend)
471
478
  | `saveCreds` | ✅ | ✅ |
472
479
  | `getStats` | ✅ | ❌ |
473
480
  | `clearSession` | ❌ (delete the folder instead) | ✅ |
474
- | `close` | ❌ | ✅ (drops local listeners; safe to call, does not disconnect a shared backend) |
475
481
 
476
482
  ---
477
483
 
@@ -1,6 +1,6 @@
1
1
  import { mkdir, readFile, rename, stat, unlink, writeFile, readdir } from 'fs/promises'
2
2
  import { join } from 'path'
3
- import { Keyv } from 'keyv'
3
+ import { Keyv, detectKeyvStorage } from 'keyv'
4
4
  import { proto } from '../../WAProto/index.js'
5
5
  import { initAuthCreds } from './auth-utils.js'
6
6
  import { BufferJSON } from './generics.js'
@@ -11,21 +11,16 @@ const DEFAULT_PREKEY_RETENTION = 150
11
11
  const DEFAULT_CLEANUP_THRESHOLD = 50
12
12
  const CLEANUP_INTERVAL_MS = 10 * 60 * 1000
13
13
 
14
- // ── Shared helpers ────────────────────────────────────────────────────────────
14
+ // ── Helpers ───────────────────────────────────────────────────────────────────
15
15
 
16
16
  const bootstrapCreds = raw => raw ?? initAuthCreds()
17
-
18
- const patchAppStateKey = (type, v) =>
19
- type === 'app-state-sync-key' && v ? proto.Message.AppStateSyncKeyData.fromObject(v) : v
20
-
17
+ const patchAppStateKey = (type, v) => type === 'app-state-sync-key' && v ? proto.Message.AppStateSyncKeyData.fromObject(v) : v
21
18
  const ser = v => v == null ? null : JSON.parse(JSON.stringify(v, BufferJSON.replacer))
22
-
23
19
  const deser = raw => {
24
20
  if (raw == null) return null
25
21
  let p = typeof raw === 'string'
26
- ? (() => { try { return JSON.parse(raw) } catch (e) { logger.warn({ err: e.message }, '[Auth] failed to parse stored value — treating as missing'); return null } })()
22
+ ? (() => { try { return JSON.parse(raw) } catch (e) { logger.warn({ err: e.message }, '[Auth] failed to parse stored value'); return null } })()
27
23
  : raw
28
- // unwrap adapter double-wrapping e.g. { value: { value: actual } }
29
24
  while (p && typeof p === 'object' && !Array.isArray(p) && Object.keys(p).length === 1 && 'value' in p) p = p.value
30
25
  if (p == null) return null
31
26
  if (typeof p === 'object' && '__checksum' in p && 'data' in p) p = p.data
@@ -34,7 +29,9 @@ const deser = raw => {
34
29
 
35
30
  // ── Adapter resolution ────────────────────────────────────────────────────────
36
31
 
32
+ // Only adapters that accept a named collection/table — redis, valkey, etcd, memcache, dynamo do not
37
33
  const COLLECTION_KEY = { '@keyv/mongo': 'collection', '@keyv/postgres': 'table', '@keyv/mysql': 'table', '@keyv/sqlite': 'table' }
34
+
38
35
  const _adapterCache = new Map()
39
36
  export const clearAdapterCache = () => _adapterCache.clear()
40
37
 
@@ -44,7 +41,7 @@ const resolveAdapter = async (conn, col) => {
44
41
  let protocol
45
42
  try { protocol = new URL(conn).protocol } catch { throw new Error(`[Auth] Invalid connection string: "${conn}"`) }
46
43
  const pkg = PROTOCOL_ADAPTERS[protocol]
47
- if (!pkg) throw new Error(`[Auth] Unsupported protocol "${protocol}" — supported: redis, mongodb, postgresql, mysql, sqlite`)
44
+ if (!pkg) throw new Error(`[Auth] Unsupported protocol "${protocol}" — supported: ${Object.keys(PROTOCOL_ADAPTERS).map(p => p.replace(':', '')).join(', ')}`)
48
45
  let mod
49
46
  try { mod = await import(pkg) } catch (e) {
50
47
  if (e.code === 'ERR_MODULE_NOT_FOUND') throw new Error(`[Auth] "${pkg}" required — npm i ${pkg}`)
@@ -58,12 +55,25 @@ const resolveAdapter = async (conn, col) => {
58
55
  return adapter
59
56
  }
60
57
 
61
- const resolveStore = async (backend, col) => {
58
+ // Object.create scoping only needed for mapLike stores — keyvStorage and asyncMap have namespace managed by Keyv/bridge
59
+ const resolveStore = async (backend, col, namespace) => {
62
60
  if (backend == null) return null
63
61
  if (typeof backend === 'string') return resolveAdapter(backend, col)
64
62
  if (backend instanceof Keyv) return backend.opts?.store ?? backend.store ?? null
65
- if (backend != null && typeof backend === 'object' && ['get', 'set', 'delete', 'clear'].every(m => typeof backend[m] === 'function')) return backend
66
- throw new Error('[Auth] backend must be a connection string, Keyv instance, or KeyvStoreAdapter')
63
+ const { compatible, store } = detectKeyvStorage(backend)
64
+ if (!compatible) throw new Error('[Auth] backend must be a connection string, Keyv instance, or KeyvStoreAdapter')
65
+ if (store === 'mapLike') {
66
+ const scoped = Object.create(backend)
67
+ if (namespace) scoped.namespace = namespace
68
+ return scoped
69
+ }
70
+ return backend
71
+ }
72
+
73
+ const makeKeyv = (store, namespace) => {
74
+ const keyv = store ? new Keyv({ store, namespace }) : new Keyv({ namespace })
75
+ keyv.setMaxListeners?.(0)
76
+ return keyv
67
77
  }
68
78
 
69
79
  // ── File-based auth state ─────────────────────────────────────────────────────
@@ -77,13 +87,12 @@ export const useMultiFileAuthState = async (folder, opts = {}) => {
77
87
  if (info) { if (!info.isDirectory()) throw new Error(`Not a directory: ${folder}`) }
78
88
  else await mkdir(folder, { recursive: true })
79
89
 
80
- // atomic write: tmp → rename, fallback to direct write
90
+ // Atomic write: tmp → rename, fallback to direct write
81
91
  const write = async (data, file) => {
82
92
  const [p, t, payload] = [fp(file), fp(file) + '.tmp', JSON.stringify(data, BufferJSON.replacer)]
83
93
  await writeFile(t, payload)
84
94
  try { await rename(t, p) } catch { await writeFile(p, payload); await unlink(t).catch(() => { }) }
85
95
  }
86
-
87
96
  const read = async file => {
88
97
  const raw = await readFile(fp(file), 'utf-8').catch(() => null)
89
98
  if (!raw) return null
@@ -96,7 +105,6 @@ export const useMultiFileAuthState = async (folder, opts = {}) => {
96
105
  return null
97
106
  }
98
107
  }
99
-
100
108
  const remove = f => unlink(fp(f)).catch(() => { })
101
109
 
102
110
  let creds = bootstrapCreds(await read('creds.json'))
@@ -114,11 +122,9 @@ export const useMultiFileAuthState = async (folder, opts = {}) => {
114
122
  .map(m => join(folder, m[0]))
115
123
  if (targets.length) await Promise.all(targets.map(f => unlink(f).catch(() => { })))
116
124
  lastCleanupAt = Date.now(); lastCleanedId = creds.nextPreKeyId
117
- } catch (e) {
118
- logger.warn({ err: e.message }, 'prekey cleanup failed')
119
- } finally { cleanupRunning = false }
125
+ } catch (e) { logger.warn({ err: e.message }, 'prekey cleanup failed') }
126
+ finally { cleanupRunning = false }
120
127
  }
121
-
122
128
  cleanOldPreKeys().catch(() => { })
123
129
 
124
130
  return {
@@ -153,19 +159,14 @@ export const useMultiFileAuthState = async (folder, opts = {}) => {
153
159
 
154
160
  export const useKeyvAuthState = async (sessionId, backend, opts = {}) => {
155
161
  const { preKeyRetention = DEFAULT_PREKEY_RETENTION, cleanupThreshold = DEFAULT_CLEANUP_THRESHOLD, collectionName } = opts
156
-
157
- const store = await resolveStore(backend, collectionName)
158
- const keyv = store ? new Keyv({ store, namespace: sessionId }) : new Keyv({ namespace: sessionId })
159
- keyv.setMaxListeners?.(0)
162
+ const store = await resolveStore(backend, collectionName, sessionId)
163
+ const keyv = makeKeyv(store, sessionId)
160
164
  keyv.on('error', err => logger.warn({ err: err?.message, sessionId }, '[Auth] store error'))
161
165
 
162
166
  const kn = (type, id) => `${type}-${id}`
163
167
  const credsKey = 'creds'
164
-
165
168
  let creds = bootstrapCreds(deser(await keyv.get(credsKey)))
166
169
  let cleanupRunning = false, lastCleanupAt = 0, lastCleanedId = creds.nextPreKeyId
167
- // tracks every key ever written — fallback clearSession uses this to wipe all types including JID-based IDs
168
- const _writtenKeys = new Set([credsKey])
169
170
 
170
171
  const cleanOldPreKeys = async () => {
171
172
  if (cleanupRunning || Date.now() - lastCleanupAt < CLEANUP_INTERVAL_MS) return
@@ -175,13 +176,11 @@ export const useKeyvAuthState = async (sessionId, backend, opts = {}) => {
175
176
  if (minId <= 0) return
176
177
  const ids = []
177
178
  for (let id = Math.max(0, lastCleanedId - preKeyRetention); id < minId; id++) ids.push(kn('pre-key', id))
178
- if (ids.length) await Promise.all(ids.map(k => keyv.delete(k).catch(() => { })))
179
+ if (ids.length) await keyv.deleteMany(ids)
179
180
  lastCleanupAt = Date.now(); lastCleanedId = creds.nextPreKeyId
180
- } catch (e) {
181
- logger.warn({ err: e.message, sessionId }, '[Auth] prekey cleanup failed')
182
- } finally { cleanupRunning = false }
181
+ } catch (e) { logger.warn({ err: e.message, sessionId }, '[Auth] prekey cleanup failed') }
182
+ finally { cleanupRunning = false }
183
183
  }
184
-
185
184
  cleanOldPreKeys().catch(() => { })
186
185
 
187
186
  const keys = {
@@ -195,35 +194,34 @@ export const useKeyvAuthState = async (sessionId, backend, opts = {}) => {
195
194
  for (const type in data)
196
195
  for (const id in data[type]) {
197
196
  const v = data[type][id], k = kn(type, id)
198
- if (v != null) { sets.push({ key: k, value: ser(v) }); _writtenKeys.add(k) }
199
- else { dels.push(k); _writtenKeys.delete(k) }
197
+ if (v != null) sets.push({ key: k, value: ser(v) })
198
+ else dels.push(k)
200
199
  }
201
- await Promise.all([
202
- sets.length ? keyv.setMany(sets) : null,
203
- dels.length ? keyv.deleteMany(dels) : null
204
- ])
200
+ await Promise.all([sets.length ? keyv.setMany(sets) : null, dels.length ? keyv.deleteMany(dels) : null])
205
201
  }
206
202
  }
207
203
 
208
204
  const saveCreds = async () => {
209
205
  if (creds.nextPreKeyId - lastCleanedId >= cleanupThreshold) cleanOldPreKeys().catch(() => { })
210
- _writtenKeys.add(credsKey)
211
206
  await keyv.set(credsKey, ser(creds))
212
207
  }
213
208
 
214
209
  const clearSession = async () => {
215
- await keyv.clear()
210
+ try {
211
+ const isolated = makeKeyv(store, sessionId)
212
+ await isolated.clear()
213
+ isolated.removeAllListeners?.()
214
+ } catch (e) { logger.warn({ err: e.message, sessionId }, '[Auth] clearSession failed') }
216
215
  keyv.removeAllListeners?.()
217
216
  }
218
217
 
219
218
  return { state: { creds, keys }, saveCreds, clearSession }
220
219
  }
221
220
 
222
- // check if valid creds exist for a session without fully loading it
221
+ // Checks if valid creds exist for a session without fully loading it
223
222
  export const hasKeyvAuth = async (sessionId, backend, opts = {}) => {
224
223
  try {
225
- const store = await resolveStore(backend, opts.collectionName)
226
- const keyv = store ? new Keyv({ store, namespace: sessionId }) : new Keyv({ namespace: sessionId })
224
+ const keyv = makeKeyv(await resolveStore(backend, opts.collectionName, sessionId), sessionId)
227
225
  const parsed = deser(await keyv.get('creds'))
228
226
  return !!(parsed?.noiseKey && parsed?.signedIdentityKey)
229
227
  } catch { return false }
package/lib/index.js CHANGED
@@ -29,7 +29,7 @@ const info = [
29
29
  '╭──────────────────────────────────────────────────────────────╮',
30
30
  '│ │',
31
31
  '│ ◆ Package › @zervonex/baileys │',
32
- '│ ◆ Version › 3.0.6 │',
32
+ '│ ◆ Version › 3.0.7 │',
33
33
  '│ ◆ Status › Production Ready │',
34
34
  '│ │',
35
35
  '├──────────────────────────── ◇◆◇ ─────────────────────────────┤',
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@zervonex/baileys",
4
4
  "type": "module",
5
- "version": "3.0.6",
5
+ "version": "3.0.7",
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
  "baileys",