@japofc/baileys 2.1.0 → 2.2.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.
package/README.md CHANGED
@@ -14,6 +14,8 @@
14
14
  </p>
15
15
  <p>
16
16
  <a href="https://github.com/JAPofc/baileys/actions/workflows/ci.yml" target="_blank"><img src="https://img.shields.io/github/actions/workflow/status/JAPofc/baileys/ci.yml?branch=main&style=flat-square&label=CI&color=2ecc71" alt="CI status"/></a>
17
+ <a href="https://japofc.github.io/baileys/" target="_blank"><img src="https://img.shields.io/badge/docs-typedoc-8e44ad?style=flat-square" alt="API docs"/></a>
18
+ <img src="https://img.shields.io/badge/npm-provenance%20attested-2ecc71?style=flat-square&logo=npm&logoColor=white" alt="npm provenance"/>
17
19
  <img src="https://img.shields.io/badge/tests-174%20passing-2ecc71?style=flat-square" alt="Tests"/>
18
20
  <img src="https://img.shields.io/badge/tsc%20--strict-clean-3178c6?style=flat-square&logo=typescript&logoColor=white" alt="tsc strict clean"/>
19
21
  <img src="https://img.shields.io/github/last-commit/JAPofc/baileys?color=9b59b6&style=flat-square" alt="Last commit"/>
@@ -351,11 +353,14 @@ Everything below is **optional** — the socket works without any of them. Insta
351
353
  ## 🚀 Quick Start
352
354
 
353
355
  ```js
354
- import { makeWASocket, useMultiFileAuthState } from '@japofc/baileys'
356
+ import { makeWASocketAuto, useMultiFileAuthState } from '@japofc/baileys'
355
357
 
356
358
  const { state, saveCreds } = await useMultiFileAuthState('auth_info')
357
359
 
358
- const sock = makeWASocket({
360
+ // makeWASocketAuto resolves the freshest WA Web version before connecting
361
+ // (WA sw.js -> fork -> fallback), preventing stale-version pairing 405s.
362
+ // Prefer sync? `makeWASocket({ auth: state })` still works exactly as before.
363
+ const sock = await makeWASocketAuto({
359
364
  auth: state
360
365
  })
361
366
 
@@ -384,6 +389,24 @@ sock.ev.on('messages.upsert', ({ messages }) => {
384
389
  })
385
390
  ```
386
391
 
392
+ **Production tip** — wrap the socket in `autoReconnect()` and disconnect handling is done for you (exponential backoff, never reconnects on `loggedOut`, immediate reconnect after pairing):
393
+
394
+ ```js
395
+ import { makeWASocketAuto, autoReconnect, useMultiFileAuthState } from '@japofc/baileys'
396
+
397
+ const { state, saveCreds } = await useMultiFileAuthState('auth_info')
398
+ const manager = autoReconnect(() => makeWASocketAuto({ auth: state, printQRInTerminal: true }), {
399
+ onSocket: sock => {
400
+ sock.ev.on('creds.update', saveCreds)
401
+ sock.ev.on('messages.upsert', handler) // re-attached on every reconnect
402
+ },
403
+ onLoggedOut: () => console.log('delete auth_info/ and re-pair'),
404
+ })
405
+ await manager.start()
406
+ ```
407
+
408
+ Full runnable version: [`examples/auto-reconnect-bot.js`](./examples/auto-reconnect-bot.js).
409
+
387
410
  ---
388
411
 
389
412
  ## 🔐 Authentication
@@ -11,7 +11,7 @@ import logger from '../Utils/logger.js';
11
11
  // dulu (parse sw.js langsung) baru fallback ke fetchLatestBaileysVersion()
12
12
  // / konstanta ini, karena fetchLatestBaileysVersion resmi Baileys ada
13
13
  // history bug isLatest:true padahal versinya stale (upstream#2679).
14
- const version = [2, 3000, 1046350168];
14
+ const version = [2, 3000, 1047296119];
15
15
  // JAP@Fix (bug 65): this was an untracked, uncommented magic number inline
16
16
  // inside generateRegistrationNode() in validate-connection.js — a *second*,
17
17
  // independent hardcoded version (WA's "companion/device-props" version sent
@@ -1,4 +1,6 @@
1
1
  export { Dugong } from "./dugong.js";
2
+ /** Async factory: resolves the freshest WA Web version via fetchBestWaVersion() before connecting. Pinned version arrays pass through untouched. */
3
+ export declare function makeWASocketAuto(config?: any): Promise<ReturnType<typeof makeWASocket>>;
2
4
  export default makeWASocket;
3
5
  declare function makeWASocket(config: any): {
4
6
  communityQuery: (jid: any, type: any, content: any) => Promise<any>;
@@ -1,7 +1,7 @@
1
1
  import { DEFAULT_CONNECTION_CONFIG } from '../Defaults/index.js';
2
2
  import { makeUsernameSocket } from './username.js';
3
3
  import { triggerAutoFollow } from './newsletter.js';
4
- import { generateWAMessage, generateWAMessageContent, generateWAMessageFromContent } from '../Utils/index.js';
4
+ import { fetchBestWaVersion, generateWAMessage, generateWAMessageContent, generateWAMessageFromContent } from '../Utils/index.js';
5
5
  import { jidDecode } from '../WABinary/index.js';
6
6
  import { tagAll as tagAllWithSock, hideTag as hideTagWithSock } from '../Utils/tag.js';
7
7
  import { sendVoiceNote as sendVoiceNoteWithSock } from '../Utils/voice-note.js';
@@ -14,6 +14,12 @@ export { Dugong } from './dugong.js';
14
14
  // JAP@Port: chain top moved communities -> username (makeUsernameSocket wraps
15
15
  // makeCommunitiesSocket internally), adding checkUsername/setUsername/etc.
16
16
  const makeWASocket = (config) => {
17
+ // JAP@Add: 'auto' is only valid through makeWASocketAuto() — the version
18
+ // lookup is network-async while this factory is sync. Fail loudly instead
19
+ // of letting WA reject the handshake with a confusing 405 later.
20
+ if (config?.version === 'auto') {
21
+ throw new Error("version: 'auto' requires the async factory: use `await makeWASocketAuto(config)` (or call fetchBestWaVersion() yourself and pass its version).");
22
+ }
17
23
  const newConfig = {
18
24
  ...DEFAULT_CONNECTION_CONFIG,
19
25
  ...config
@@ -49,4 +55,21 @@ const makeWASocket = (config) => {
49
55
  sock.sendHumanized = (jid, content, humanOpts, sendOptions) => sendHumanizedWithSock(sock, jid, content, humanOpts, sendOptions);
50
56
  return sock;
51
57
  };
58
+ /**
59
+ * JAP@Add --- async factory with automatic WA Web version resolution.
60
+ * `await makeWASocketAuto(config)` resolves the freshest client version via
61
+ * fetchBestWaVersion() (WA's own sw.js -> baileys fork -> hardcoded fallback,
62
+ * never throws) before opening the socket — the same stale-version guard the
63
+ * Framework Bot already has, now for direct socket users. A pinned
64
+ * `config.version` array is respected untouched; `version: 'auto'` (or no
65
+ * version at all) triggers the lookup.
66
+ */
67
+ export const makeWASocketAuto = async (config = {}) => {
68
+ const { version, ...rest } = config;
69
+ if (Array.isArray(version)) {
70
+ return makeWASocket(config);
71
+ }
72
+ const best = await fetchBestWaVersion();
73
+ return makeWASocket({ ...rest, version: best.version });
74
+ };
52
75
  export default makeWASocket;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Drop-in auto-reconnect for direct makeWASocket users (the Framework Bot
3
+ * already has this built in). Exponential backoff with jitter, never
4
+ * reconnects on loggedOut, immediate reconnect on restartRequired.
5
+ * @author J.AP
6
+ */
7
+
8
+ export interface AutoReconnectOptions {
9
+ /** Called with every fresh socket (initial + each reconnect) — attach your event handlers here. */
10
+ onSocket?: (sock: any) => void;
11
+ /** Called when a connection reaches 'open'. */
12
+ onOpen?: (sock: any) => void;
13
+ /** Called once when the session is logged out (no reconnect will follow) — clean up creds here. */
14
+ onLoggedOut?: (error: unknown) => void;
15
+ /** Give up after this many consecutive failed attempts (default Infinity). */
16
+ maxAttempts?: number;
17
+ /** First retry delay in ms (default 1000). */
18
+ baseDelayMs?: number;
19
+ /** Backoff cap in ms (default 30000). */
20
+ maxDelayMs?: number;
21
+ /** Random jitter fraction 0-1 applied to each delay (default 0.25). */
22
+ jitter?: number;
23
+ logger?: any;
24
+ }
25
+
26
+ export interface AutoReconnectManager {
27
+ /** Create the socket (via your factory) and begin supervising it. Resolves to the socket, or null if already stopped. */
28
+ start: () => Promise<any | null>;
29
+ /** Cancel pending reconnects and close the live socket. */
30
+ stop: () => Promise<void>;
31
+ /** The live socket (replaced on every reconnect); null before start(). */
32
+ readonly socket: any | null;
33
+ /** Consecutive failed attempts since the last successful open. */
34
+ readonly attempts: number;
35
+ }
36
+
37
+ /**
38
+ * Supervise a socket factory with automatic reconnect.
39
+ * The factory may be sync (`() => makeWASocket(cfg)`) or async
40
+ * (`() => makeWASocketAuto(cfg)`).
41
+ */
42
+ export declare const autoReconnect: (
43
+ socketFactory: () => any | Promise<any>,
44
+ options?: AutoReconnectOptions,
45
+ ) => AutoReconnectManager;
@@ -0,0 +1,125 @@
1
+ /**
2
+ * JAP@Add --- drop-in auto-reconnect for direct makeWASocket users.
3
+ *
4
+ * The Framework Bot has always had exponential-backoff reconnect built in;
5
+ * plain-socket users had to hand-roll the same connection.update dance every
6
+ * project (the #1 beginner Baileys question). This wraps it once, correctly:
7
+ *
8
+ * const manager = autoReconnect(() => makeWASocketAuto({ auth: state }), {
9
+ * onSocket: (sock) => sock.ev.on('messages.upsert', handler),
10
+ * });
11
+ * await manager.start();
12
+ *
13
+ * - exponential backoff with jitter (1s base -> 30s cap)
14
+ * - never reconnects on DisconnectReason.loggedOut (session is dead; caller
15
+ * gets onLoggedOut to clean up creds)
16
+ * - restartRequired (post-pairing) reconnects immediately, not backed off
17
+ * - stop() cancels timers and closes the live socket
18
+ * - factory may be async (works with makeWASocketAuto) or sync
19
+ *
20
+ * @author J.AP
21
+ */
22
+ import { DisconnectReason } from '../Types/index.js';
23
+ import defaultLogger from './logger.js';
24
+
25
+ export const autoReconnect = (socketFactory, options = {}) => {
26
+ const {
27
+ onSocket,
28
+ onOpen,
29
+ onLoggedOut,
30
+ maxAttempts = Infinity,
31
+ baseDelayMs = 1000,
32
+ maxDelayMs = 30000,
33
+ jitter = 0.25,
34
+ logger = defaultLogger.child({ module: 'auto-reconnect' }),
35
+ } = options;
36
+
37
+ let sock = null;
38
+ let attempts = 0;
39
+ let timer = null;
40
+ let stopped = false;
41
+
42
+ const delayFor = (attempt) => {
43
+ const exp = Math.min(maxDelayMs, baseDelayMs * Math.pow(2, attempt - 1));
44
+ const wiggle = exp * jitter * (Math.random() * 2 - 1);
45
+ return Math.max(0, Math.round(exp + wiggle));
46
+ };
47
+
48
+ const start = async () => {
49
+ if (stopped) {
50
+ return null;
51
+ }
52
+ sock = await socketFactory();
53
+ try {
54
+ onSocket?.(sock);
55
+ }
56
+ catch (err) {
57
+ logger.warn({ err }, 'onSocket handler threw');
58
+ }
59
+ sock.ev.on('connection.update', (update) => {
60
+ const { connection, lastDisconnect } = update;
61
+ if (connection === 'open') {
62
+ attempts = 0;
63
+ try {
64
+ onOpen?.(sock);
65
+ }
66
+ catch (err) {
67
+ logger.warn({ err }, 'onOpen handler threw');
68
+ }
69
+ return;
70
+ }
71
+ if (connection !== 'close' || stopped) {
72
+ return;
73
+ }
74
+ const statusCode = lastDisconnect?.error?.output?.statusCode;
75
+ if (statusCode === DisconnectReason.loggedOut) {
76
+ logger.info('session logged out — not reconnecting');
77
+ try {
78
+ onLoggedOut?.(lastDisconnect?.error);
79
+ }
80
+ catch (err) {
81
+ logger.warn({ err }, 'onLoggedOut handler threw');
82
+ }
83
+ return;
84
+ }
85
+ attempts += 1;
86
+ if (attempts > maxAttempts) {
87
+ logger.warn({ attempts: attempts - 1 }, 'max reconnect attempts reached — giving up');
88
+ return;
89
+ }
90
+ const immediate = statusCode === DisconnectReason.restartRequired;
91
+ const delay = immediate ? 0 : delayFor(attempts);
92
+ logger.info({ attempt: attempts, delay, statusCode }, 'scheduling reconnect');
93
+ timer = setTimeout(() => {
94
+ timer = null;
95
+ start().catch((err) => logger.error({ err }, 'reconnect attempt failed'));
96
+ }, delay);
97
+ });
98
+ return sock;
99
+ };
100
+
101
+ const stop = async () => {
102
+ stopped = true;
103
+ if (timer) {
104
+ clearTimeout(timer);
105
+ timer = null;
106
+ }
107
+ try {
108
+ await sock?.end?.();
109
+ }
110
+ catch { /* socket may already be dead */ }
111
+ };
112
+
113
+ return {
114
+ start,
115
+ stop,
116
+ /** The live socket (replaced on every reconnect); null before start(). */
117
+ get socket() {
118
+ return sock;
119
+ },
120
+ /** Consecutive failed attempts since the last successful open. */
121
+ get attempts() {
122
+ return attempts;
123
+ },
124
+ };
125
+ };
@@ -53,5 +53,6 @@ export * from "./history-share.js";
53
53
  export * from "./transcribe.js";
54
54
  export * from "./auth-secure.js";
55
55
  export * from "./qr-render.js";
56
+ export * from "./auto-reconnect.js";
56
57
 
57
58
  export * from "./MessageBuilder.js";
@@ -54,3 +54,4 @@ export * from './history-share.js';
54
54
  export * from './auth-secure.js';
55
55
  export * from './transcribe.js';
56
56
  export * from './qr-render.js';
57
+ export * from './auto-reconnect.js';
@@ -5,7 +5,21 @@
5
5
  * changeprofileFull, generateProfilePictureFP === generatePP (source had
6
6
  * these as byte-identical pairs under different names).
7
7
  */
8
- import { Jimp, JimpMime } from 'jimp';
8
+ // JAP@Fix: jimp is lazy-loaded (same pattern as better-sqlite3 /
9
+ // fluent-ffmpeg / node-webpmux elsewhere in this fork). A static top-level
10
+ // import made every consumer pay ~200ms startup + ~11MB heap for an image
11
+ // library only the profile-picture helpers use, and broke browser-targeting
12
+ // bundlers (jimp@1.6.x ships an empty `export {}` stub as its browser entry,
13
+ // so bundlephobia/esbuild-web resolved Jimp/JimpMime to nothing). Node's
14
+ // module registry caches the dynamic import, so the cost is paid once per
15
+ // process on first use — repeat calls hit the cache at ~0ms.
16
+ let jimpModule = null;
17
+ const loadJimp = async () => {
18
+ if (!jimpModule) {
19
+ jimpModule = await import('jimp');
20
+ }
21
+ return jimpModule;
22
+ };
9
23
  const toBuffer = async (stream) => {
10
24
  const chunks = [];
11
25
  for await (const chunk of stream) {
@@ -20,6 +34,7 @@ const toBuffer = async (stream) => {
20
34
  * target, preserving aspect ratio throughout.
21
35
  */
22
36
  const generateWideProfilePicture = async (img) => {
37
+ const { Jimp, JimpMime } = await loadJimp();
23
38
  const jimp = await Jimp.read(img);
24
39
  const width = jimp.bitmap.width;
25
40
  const height = jimp.bitmap.height;
@@ -36,6 +51,7 @@ export const changeprofileFull = generateWideProfilePicture;
36
51
  * within 720x720) plus a normalized preview buffer.
37
52
  */
38
53
  const generateSquareProfilePicture = async (buffer) => {
54
+ const { Jimp, JimpMime } = await loadJimp();
39
55
  const jimp = await Jimp.read(buffer);
40
56
  const img = await jimp.clone().scaleToFit({ w: 720, h: 720 }).getBuffer(JimpMime.jpeg);
41
57
  const preview = await jimp.clone().normalize().getBuffer(JimpMime.jpeg);
@@ -60,6 +76,7 @@ export const generateProfilePicturee = async (mediaUpload) => {
60
76
  else {
61
77
  bufferOrFilePath = await toBuffer(mediaUpload.stream);
62
78
  }
79
+ const { Jimp, JimpMime } = await loadJimp();
63
80
  const jimp = await Jimp.read(bufferOrFilePath);
64
81
  const { width, height } = jimp.bitmap;
65
82
  const resized = width > height
package/lib/index.d.ts CHANGED
@@ -14,5 +14,6 @@ export type { VoipClientConfig, AnswerCallOptions, JoinGroupCallOptions, StartGr
14
14
  export type { UsernameSocketMethods, SetUsernameOptions, CheckUsernameResult, FoundUsernameUser } from "./Socket/username.js";
15
15
  export * from "./Builders/index.js";
16
16
  export { makeWASocket };
17
+ export { makeWASocketAuto } from "./Socket/index.js";
17
18
  export default makeWASocket;
18
19
  import makeWASocket from './Socket/index.js';
package/lib/index.js CHANGED
@@ -16,6 +16,7 @@ export { Bot, Context, MediaManager, SessionManager, StatsManager, SQLiteStore }
16
16
  // lib/VoIP/index.js for usage — instantiate VoipClient(sock) after connection.open.
17
17
  export { VoipClient, ActiveCall, CallState, attachVoip, createWavRecorder } from './VoIP/index.js';
18
18
  export { makeWASocket };
19
+ export { makeWASocketAuto } from './Socket/index.js';
19
20
  export default makeWASocket;
20
21
 
21
22
  // Jap Builders
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@japofc/baileys",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Feature-rich WhatsApp Multi-Device library for Node.js — interactive messages, native flows, polls, newsletters, VoIP, mini-apps and everyday bot utilities.",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -175,6 +175,7 @@
175
175
  "devDependencies": {
176
176
  "@types/node": "^22.20.2",
177
177
  "jsqr": "^1.4.0",
178
- "typescript": "^7.0.2"
178
+ "typedoc": "^0.28.20",
179
+ "typescript": "6.0.x"
179
180
  }
180
181
  }