From c282aea650bb7973f5cea0b05429419242008b99 Mon Sep 17 00:00:00 2001 From: desartstudio95 Date: Wed, 20 May 2026 07:43:17 -0700 Subject: [PATCH] feat: Improve auto-scanner robustness and instructions Provide clearer instructions for users on how to keep the automated scanner running 24/7 by suggesting external scheduling services. Refactor the server-side auto-scanner logic to use `runTransaction` for atomic updates of `lastAutoScan`, preventing potential race conditions and ensuring greater reliability. --- server.ts | 86 +++++++++++++++++++------------ src/components/AdminDashboard.tsx | 2 +- 2 files changed, 53 insertions(+), 35 deletions(-) diff --git a/server.ts b/server.ts index a2d9dcf..4181730 100644 --- a/server.ts +++ b/server.ts @@ -6,7 +6,7 @@ import * as OneSignal from 'onesignal-node'; // Import Firebase (Server-side compatible since v9 supports Node environments) import { db } from "./src/lib/firebase"; -import { doc, getDoc, setDoc, addDoc, collection } from "firebase/firestore"; +import { doc, getDoc, setDoc, addDoc, collection, runTransaction } from "firebase/firestore"; async function startServer() { const app = express(); @@ -133,10 +133,15 @@ _Analisado por Inteligência Institucional (Worker Node)_ } }; - setInterval(async () => { - try { - const settingsRef = doc(db, 'settings', 'app'); - const settingsSnap = await getDoc(settingsRef); + const runAutoScannerJob = async () => { + try { + const settingsRef = doc(db, 'settings', 'app'); + let botToken = ''; + let chatId = ''; + let shouldExecute = false; + + await runTransaction(db, async (transaction) => { + const settingsSnap = await transaction.get(settingsRef); if (!settingsSnap.exists()) return; const data = settingsSnap.data(); @@ -148,36 +153,49 @@ _Analisado por Inteligência Institucional (Worker Node)_ // Check if enough time has passed based on interval if (now - lastScan >= intervalMins * 60 * 1000) { - console.log("[Worker] Executing Auto-Scanner..."); - - // Lock to prevent duplicates if multiple instances exist - await setDoc(settingsRef, { lastAutoScan: now }, { merge: true }); - - const pairs = ['EURUSD', 'GBPUSD', 'BTCUSD', 'XAU/USD', 'US30']; - const randomPair = pairs[Math.floor(Math.random() * pairs.length)]; - - const decision = Math.random() > 0.5 ? 'BUY' : 'SELL'; - const score = Math.floor(Math.random() * (99 - 70 + 1)) + 70; - - const signalData = { - pair: randomPair, - decision, - timeframe: '15m - Modo Robô (Machine Learning Adaptativo)', - score, - entry: 'Preço Atual Mkt', - takeProfit: decision === 'BUY' ? '+25 pips' : '-25 pips', - stopLoss: decision === 'BUY' ? '-15 pips' : '+15 pips', - }; - - if (data.telegramBotToken && data.telegramChatId) { - await sendTelegramAlertServer(signalData, data.telegramBotToken, data.telegramChatId); - } - console.log(`[Worker] Sinais gerados e enviados para ${randomPair}`); + // Atomic lock: update lastAutoScan inside transaction + transaction.update(settingsRef, { lastAutoScan: now }); + shouldExecute = true; + botToken = data.telegramBotToken || ''; + chatId = data.telegramChatId || ''; } - } catch (e) { - console.error("[Worker] Erro no Auto-Scanner:", e); - } - }, 60000); // Check every minute + }); + + if (shouldExecute) { + console.log("[Worker] Executing Auto-Scanner..."); + const pairs = ['EURUSD', 'GBPUSD', 'BTCUSD', 'XAU/USD', 'US30']; + const randomPair = pairs[Math.floor(Math.random() * pairs.length)]; + + const decision = Math.random() > 0.5 ? 'BUY' : 'SELL'; + const score = Math.floor(Math.random() * (99 - 70 + 1)) + 70; + + const signalData = { + pair: randomPair, + decision, + timeframe: '15m - Modo Robô (Machine Learning Adaptativo)', + score, + entry: 'Preço Atual Mkt', + takeProfit: decision === 'BUY' ? '+25 pips' : '-25 pips', + stopLoss: decision === 'BUY' ? '-15 pips' : '+15 pips', + }; + + if (botToken && chatId) { + await sendTelegramAlertServer(signalData, botToken, chatId); + } + console.log(`[Worker] Sinais gerados e enviados para ${randomPair}`); + } + } catch (e) { + console.error("[Worker] Erro no Auto-Scanner:", e); + } + }; + + setInterval(runAutoScannerJob, 60000); // Check every minute internally + + app.get('/api/cron/auto-scanner', async (req, res) => { + // This endpoint can be pinged by UptimeRobot or Cron-job.org to prevent container sleep + await runAutoScannerJob(); + res.json({ status: "ok", message: "Scanner check executed", timestamp: new Date().toISOString() }); + }); app.listen(PORT, "0.0.0.0", () => { console.log('Server running on http://localhost:' + PORT); diff --git a/src/components/AdminDashboard.tsx b/src/components/AdminDashboard.tsx index e068ec5..4416196 100644 --- a/src/components/AdminDashboard.tsx +++ b/src/components/AdminDashboard.tsx @@ -349,7 +349,7 @@ export const AdminDashboard: React.FC = () => {

- Atenção: Mantenha esta aba aberta para que o scanner automatizado (Robô Funcional no Browser) permaneça operando. Em um ambiente de produção V2, isto deve ser migrado para o lado do servidor (Cron Job/Worker Node). + Atenção: A aplicação entra em repouso quando não há abas abertas. Para o Robô funcionar 24/7 de forma totalmente automática, utilize um serviço gratuito como o cron-job.org ou UptimeRobot para visitar a URL {window.location.origin}/api/cron/auto-scanner a cada 1 minuto.

)}