Files
nabeh/whatsapp-gateway/baileys-client.js
2026-05-21 18:36:08 +03:00

174 lines
6.3 KiB
JavaScript

const baileys = require('@whiskeysockets/baileys');
const makeWASocket = baileys.default || baileys.makeWASocket || baileys;
const { useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion } = baileys;
const pino = require('pino');
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const sessions = new Map(); // Store active sockets in memory
const retryCounters = new Map(); // Track reconnection attempts per session
const MAX_RETRIES = 5; // Maximum reconnection attempts before giving up
// Local folder for saving auth keys
const SESSIONS_DIR = path.join(__dirname, 'sessions');
if (!fs.existsSync(SESSIONS_DIR)) {
fs.mkdirSync(SESSIONS_DIR, { recursive: true });
}
async function sendWebhook(webhook_url, payload) {
try {
console.log(`[Webhook] Sending to ${webhook_url} | state=${payload.state}`);
const response = await axios.post(webhook_url, payload, {
headers: {
'Content-Type': 'application/json',
'X-Webhook-Secret': process.env.WEBHOOK_SECRET || ''
},
timeout: 10000
});
console.log(`[Webhook] ✅ Success | HTTP ${response.status}`);
} catch (err) {
if (err.response) {
console.error(`[Webhook] ❌ HTTP ${err.response.status} | ${JSON.stringify(err.response.data)}`);
} else {
console.error(`[Webhook] ❌ Network Error: ${err.message}`);
}
}
}
async function startSession(session_key, webhook_url) {
// Return existing socket if it's already active
if (sessions.has(session_key)) {
console.log(`[Session] ${session_key} already active, reusing`);
return sessions.get(session_key);
}
console.log(`[Session] Starting ${session_key} → webhook: ${webhook_url}`);
const sessionFolder = path.join(SESSIONS_DIR, session_key);
const { state, saveCreds } = await useMultiFileAuthState(sessionFolder);
// Fetch the latest WhatsApp Web version to avoid 405 rejection
let version;
try {
const versionInfo = await fetchLatestBaileysVersion();
version = versionInfo.version;
console.log(`[Baileys] Using WA version: ${version}`);
} catch (e) {
console.warn(`[Baileys] Could not fetch version, using default`);
}
const socketConfig = {
auth: state,
printQRInTerminal: false,
logger: pino({ level: 'silent' }),
browser: ['Nabeh Gateway', 'Chrome', '120.0.0']
};
if (version) socketConfig.version = version;
const sock = makeWASocket(socketConfig);
console.log(`[Session] Socket created for ${session_key}`);
sessions.set(session_key, sock);
sock.ev.on('creds.update', saveCreds);
sock.ev.on('connection.update', async (update) => {
const { connection, lastDisconnect, qr } = update;
if (qr) {
console.log(`[QR] Generated for ${session_key}`);
// Reset retry counter on QR generation (session is alive)
retryCounters.set(session_key, 0);
await sendWebhook(webhook_url, {
session_key,
state: 'waiting_qr',
qr_code: qr
});
}
if (connection === 'close') {
const statusCode = lastDisconnect?.error?.output?.statusCode;
const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
const retries = (retryCounters.get(session_key) || 0) + 1;
retryCounters.set(session_key, retries);
console.log(`[Connection] ${session_key} closed | code=${statusCode} | retry=${retries}/${MAX_RETRIES} | shouldReconnect=${shouldReconnect}`);
if (shouldReconnect && retries <= MAX_RETRIES) {
// Try reconnecting with exponential backoff
sessions.delete(session_key);
const delay = Math.min(retries * 3000, 15000); // 3s, 6s, 9s, 12s, 15s
console.log(`[Connection] Reconnecting ${session_key} in ${delay}ms...`);
setTimeout(() => startSession(session_key, webhook_url), delay);
} else {
// Either logged out, banned, or max retries exceeded
console.log(`[Connection] ${session_key} permanently closed. Cleaning up.`);
await cleanupSession(session_key);
await sendWebhook(webhook_url, {
session_key,
state: 'disconnected'
});
}
} else if (connection === 'open') {
console.log(`[Connection] ✅ ${session_key} connected successfully!`);
retryCounters.set(session_key, 0); // Reset on successful connection
// Parse phone number from the JID (e.g. 9665XXXXXXX@s.whatsapp.net)
const phone = sock.user.id.split(':')[0];
await sendWebhook(webhook_url, {
session_key,
state: 'connected',
phone: phone
});
}
});
return sock;
}
/**
* Cleanup session: remove from memory and delete auth files
*/
async function cleanupSession(session_key) {
const sock = sessions.get(session_key);
if (sock) {
try { sock.end(); } catch (e) { } // Gracefully close socket without logout
sessions.delete(session_key);
}
retryCounters.delete(session_key);
// Wipe the auth directory so a fresh session can be created next time
const sessionFolder = path.join(SESSIONS_DIR, session_key);
if (fs.existsSync(sessionFolder)) {
fs.rmSync(sessionFolder, { recursive: true, force: true });
console.log(`[Cleanup] Deleted session folder for ${session_key}`);
}
}
/**
* Disconnect session: logout from WhatsApp and cleanup
*/
async function disconnectSession(session_key) {
const sock = sessions.get(session_key);
if (sock) {
try { sock.logout(); } catch (e) { } // best effort logout
sessions.delete(session_key);
}
retryCounters.delete(session_key);
// Completely wipe the auth directory
const sessionFolder = path.join(SESSIONS_DIR, session_key);
if (fs.existsSync(sessionFolder)) {
fs.rmSync(sessionFolder, { recursive: true, force: true });
console.log(`[Disconnect] Deleted session folder for ${session_key}`);
}
}
module.exports = {
startSession,
disconnectSession
};