feat: module toggles in POS config and Instance Manager
- Add GET/PUT /pos/api/config/modules endpoints in POS config_bp.py - Update sidebar.js to filter nav items based on enabled modules - Add Modules section to POS config.html with toggles for WhatsApp, Marketplace, MercadoLibre - Add module load/save logic to POS config.js - Preload modules in app-init.js for sidebar caching - Add tenant module management to Instance Manager - get_tenant_modules / update_tenant_modules in tenant_service.py - GET/PUT /api/tenants/<id>/modules endpoints in tenants_bp.py - Add modules modal to manager index.html - Add module editing UI and logic to manager.js - Add toggle-switch CSS to manager.css
This commit is contained in:
@@ -28,8 +28,9 @@ const sendQueue = [];
|
||||
let queueTimer = null;
|
||||
let connectWatchdog = null;
|
||||
let staleWatchdog = null;
|
||||
const WATCHDOG_MS = 90000;
|
||||
const STALE_MS = 90000;
|
||||
let queueFlushInterval = null;
|
||||
const WATCHDOG_MS = 300000; // 5 minutos para dar tiempo al QR scanning
|
||||
const STALE_MS = 1800000; // 30 minutos (keepalive ya maneja pings, no forzamos reconexión por inactividad)
|
||||
let lastActivity = Date.now();
|
||||
|
||||
function updateActivity() {
|
||||
@@ -67,19 +68,14 @@ function flushSendQueue() {
|
||||
function clearWatchdog() {
|
||||
if (connectWatchdog) { clearTimeout(connectWatchdog); connectWatchdog = null; }
|
||||
if (staleWatchdog) { clearInterval(staleWatchdog); staleWatchdog = null; }
|
||||
if (queueFlushInterval) { clearInterval(queueFlushInterval); queueFlushInterval = null; }
|
||||
}
|
||||
|
||||
function scheduleStaleWatchdog() {
|
||||
if (staleWatchdog) clearInterval(staleWatchdog);
|
||||
staleWatchdog = setInterval(() => {
|
||||
if (connectionState === 'open' && (Date.now() - lastActivity > STALE_MS)) {
|
||||
console.log(`[Tenant ${TENANT_ID}] Stale watchdog: no activity for ${STALE_MS/1000}s while open, forcing reconnect`);
|
||||
try { sock?.ws?.close(); } catch (e) {}
|
||||
sock = null;
|
||||
connectionState = 'disconnected';
|
||||
setTimeout(connectWhatsApp, 30000);
|
||||
}
|
||||
}, 30000);
|
||||
// ELIMINADO: el stale watchdog forzaba reconexión cada 90s sin mensajes,
|
||||
// lo cual destruía la sesión de Baileys y provocaba 440 + limpieza de auth.
|
||||
// Baileys ya envía keepalive cada 15s (keepAliveIntervalMs). No es necesario.
|
||||
if (staleWatchdog) { clearInterval(staleWatchdog); staleWatchdog = null; }
|
||||
}
|
||||
|
||||
function scheduleWatchdog() {
|
||||
@@ -118,7 +114,7 @@ async function connectWhatsApp() {
|
||||
auth: state,
|
||||
logger,
|
||||
// Use a generic Ubuntu + Chrome fingerprint — less suspicious than raw Linux
|
||||
browser: ['Ubuntu', 'Chrome', '120.0.0.0'],
|
||||
browser: ['Chrome', 'Windows', '124.0.0.0'],
|
||||
defaultQueryTimeoutMs: 60000,
|
||||
keepAliveIntervalMs: 15000,
|
||||
markOnlineOnConnect: false,
|
||||
@@ -157,23 +153,24 @@ async function connectWhatsApp() {
|
||||
}
|
||||
|
||||
if (reason === 440) {
|
||||
// 440 = conflict/replaced. The session data is permanently invalid.
|
||||
// Clean auth immediately and wait 5 min so WhatsApp forgets the old session.
|
||||
console.log(`[Tenant ${TENANT_ID}] 440 Session replaced — clearing auth, waiting 5 min`);
|
||||
clearAuthState();
|
||||
sock = null;
|
||||
// 440 = conflict/replaced. WhatsApp reemplazó esta sesión (puede ser
|
||||
// por reconexión muy rápida, o porque el teléfono abrió otra sesión).
|
||||
// NUNCA limpiamos auth automáticamente — solo esperamos más tiempo.
|
||||
retry440Count++;
|
||||
const delay = retry440Count >= 3 ? 600000 : 300000; // 10 min after 3 failures, else 5 min
|
||||
const delay = retry440Count >= 3 ? 600000 : 120000; // 2min → 10min
|
||||
console.log(`[Tenant ${TENANT_ID}] 440 Session replaced — reconnecting in ${delay/1000}s with existing creds (attempt ${retry440Count})`);
|
||||
sock = null;
|
||||
setTimeout(connectWhatsApp, delay);
|
||||
return;
|
||||
}
|
||||
|
||||
if (reason === 515) {
|
||||
// 515 = stream error, often precedes 440. Treat same as 440.
|
||||
console.log(`[Tenant ${TENANT_ID}] 515 Stream error — clearing auth, waiting 5 min`);
|
||||
clearAuthState();
|
||||
// 515 = stream error / restart required. WhatsApp sends this after
|
||||
// successful pairing to force a reconnect with the new credentials.
|
||||
// DO NOT clear auth — the credentials were just saved by creds.update.
|
||||
console.log(`[Tenant ${TENANT_ID}] 515 Restart required — reconnecting in 5s with saved creds`);
|
||||
sock = null;
|
||||
setTimeout(connectWhatsApp, 300000);
|
||||
setTimeout(connectWhatsApp, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -185,19 +182,11 @@ async function connectWhatsApp() {
|
||||
}
|
||||
|
||||
if (reason === 408) {
|
||||
// 408 during init queries usually means the server is overloaded
|
||||
// or our auth is partially invalid. Clear auth if this happens repeatedly.
|
||||
console.log(`[Tenant ${TENANT_ID}] 408 Timeout — waiting 60s`);
|
||||
// 408 during init queries = rate-limit o auth parcialmente inválido.
|
||||
// No limpiamos auth automáticamente; esperamos más tiempo.
|
||||
console.log(`[Tenant ${TENANT_ID}] 408 Timeout — waiting 120s`);
|
||||
sock = null;
|
||||
retry440Count++;
|
||||
if (retry440Count >= 5) {
|
||||
console.log(`[Tenant ${TENANT_ID}] Too many timeouts — clearing auth for fresh QR`);
|
||||
clearAuthState();
|
||||
retry440Count = 0;
|
||||
setTimeout(connectWhatsApp, 300000);
|
||||
return;
|
||||
}
|
||||
setTimeout(connectWhatsApp, 60000);
|
||||
setTimeout(connectWhatsApp, 120000);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -208,20 +197,33 @@ async function connectWhatsApp() {
|
||||
}
|
||||
|
||||
if (connection === 'open') {
|
||||
// Race-condition guard: if sock was nulled by a concurrent disconnect,
|
||||
// ignore this stale 'open' event.
|
||||
if (!sock) {
|
||||
console.log(`[Tenant ${TENANT_ID}] Ignoring stale 'open' event (sock is null)`);
|
||||
return;
|
||||
}
|
||||
clearWatchdog();
|
||||
connectionState = 'open';
|
||||
qrCode = null;
|
||||
retry440Count = 0;
|
||||
updateActivity();
|
||||
scheduleStaleWatchdog();
|
||||
// Stale watchdog eliminado — Baileys ya mantiene keepalive.
|
||||
console.log(`[Tenant ${TENANT_ID}] Connected!`);
|
||||
flushSendQueue();
|
||||
if (!queueFlushInterval) {
|
||||
queueFlushInterval = setInterval(() => {
|
||||
if (connectionState === 'open') flushSendQueue();
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
sock.ev.on('messages.upsert', async ({ messages }) => {
|
||||
for (const msg of messages) {
|
||||
if (msg.key.fromMe) continue;
|
||||
// Skip system/receipt messages with no meaningful content
|
||||
if (!msg.message || Object.keys(msg.message).length === 0) continue;
|
||||
const phone = msg.key.remoteJid.replace('@s.whatsapp.net', '');
|
||||
const message = msg.message || {};
|
||||
|
||||
@@ -289,7 +291,8 @@ async function connectWhatsApp() {
|
||||
media_ptt,
|
||||
latitude,
|
||||
longitude,
|
||||
push_name: msg.pushName || ''
|
||||
push_name: msg.pushName || '',
|
||||
sender_pn: msg.key?.senderPn || ''
|
||||
}
|
||||
}),
|
||||
signal: controller.signal
|
||||
@@ -332,21 +335,22 @@ app.post('/send', async (req, res) => {
|
||||
return res.status(400).json({ error: 'phone and message required' });
|
||||
}
|
||||
const jid = phone.includes('@') ? phone : phone + '@s.whatsapp.net';
|
||||
console.log(`[Tenant ${TENANT_ID}] /send called for ${jid}. state=${connectionState}, sock=${!!sock}`);
|
||||
|
||||
if (connectionState !== 'open' || !sock) {
|
||||
sendQueue.push({ jid, message });
|
||||
console.log(`[Tenant ${TENANT_ID}] Message queued for ${jid} (state: ${connectionState}, queue size: ${sendQueue.length})`);
|
||||
return res.status(202).json({ queued: true, state: connectionState });
|
||||
if (connectionState === 'open' && sock) {
|
||||
try {
|
||||
const r = await sock.sendMessage(jid, { text: message });
|
||||
res.json({ success: true, id: r.key.id });
|
||||
return;
|
||||
} catch (e) {
|
||||
console.log(`[Tenant ${TENANT_ID}] Send failed, will queue:`, e.message);
|
||||
// fall through to queue
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const r = await sock.sendMessage(jid, { text: message });
|
||||
res.json({ success: true, id: r.key.id });
|
||||
} catch (e) {
|
||||
sendQueue.push({ jid, message });
|
||||
console.log(`[Tenant ${TENANT_ID}] Send failed, queued for retry:`, e.message);
|
||||
res.status(202).json({ queued: true, error: e.message });
|
||||
}
|
||||
sendQueue.push({ jid, message });
|
||||
console.log(`[Tenant ${TENANT_ID}] Message queued for ${jid} (state: ${connectionState}, queue size: ${sendQueue.length})`);
|
||||
res.status(202).json({ queued: true, state: connectionState });
|
||||
});
|
||||
app.post('/send-image', async (req, res) => {
|
||||
const { phone, caption, base64 } = req.body;
|
||||
|
||||
Reference in New Issue
Block a user