import 'dotenv/config'; import _ from 'lodash'; import { io } from 'socket.io-client'; import { createApiBid, createBidProduct, deleteProfile, shouldUpdateProductTab } from './service/app-service.js'; import browser from './system/browser.js'; import configs from './system/config.js'; import { isTimeReached, safeClosePage } from './system/utils.js'; let MANAGER_BIDS = []; let _INTERVAL_TRACKING_ID = null; let _CLEAR_LAZY_TAB_ID = null; let _WORK_TRACKING_ID = null; global.IS_CLEANING = false; const handleUpdateProductTabs = (data) => { if (!Array.isArray(data)) { console.log('Data must be array'); return; } const managerBidMap = new Map(MANAGER_BIDS.map((bid) => [bid.id, bid])); const newDataManager = data.map(({ children, ...web }) => { const prevApiBid = managerBidMap.get(web.id); const newChildren = children.map((item) => { const prevProductTab = prevApiBid?.children.find((i) => i.id === item.id); if (prevProductTab) { prevProductTab.setNewData(item); return prevProductTab; } return createBidProduct(web, item); }); if (prevApiBid) { prevApiBid.setNewData({ children: newChildren, ...web }); return prevApiBid; } return createApiBid({ ...web, children: newChildren }); }); MANAGER_BIDS = newDataManager; }; // const tracking = async () => { // if (_INTERVAL_TRACKING_ID) { // clearInterval(_INTERVAL_TRACKING_ID); // _INTERVAL_TRACKING_ID = null; // } // _INTERVAL_TRACKING_ID = setInterval(async () => { // const productTabs = _.flatMap(MANAGER_BIDS, 'children'); // for (const productTab of productTabs) { // if (!productTab.parent_browser_context) { // const parent = _.find(MANAGER_BIDS, { id: productTab.web_bid.id }); // productTab.parent_browser_context = parent.browser_context; // if (!productTab.parent_browser_context) { // console.log(`🔄 Waiting for parent process to start... (Product ID: ${productTab.id})`); // continue; // } // } // if (!productTab.first_bid) { // console.log(`🎯 Tracking out-bid event for Product ID: ${productTab.id}`); // const updatedAt = new Date(productTab.updated_at).getTime(); // const now = Date.now(); // if (!productTab.page_context) { // await productTab.puppeteer_connect(); // } // if (productTab.page_context.url() !== productTab.url) { // await productTab.gotoLink(); // } // if (now - updatedAt < ONE_MINUTE) { // console.log(`⏳ Product ID: ${productTab.id} was updated recently. Skipping update.`); // } // await productTab.update(); // console.log(`🔄 Updating Product ID: ${productTab.id}...`); // continue; // } // if (productTab.start_bid_time && !isTimeReached(productTab.start_bid_time)) { // console.log(`⏳ Not yet time to bid. Skipping Product ID: ${productTab.id}`); // const updatedAt = new Date(productTab.updated_at).getTime(); // const now = Date.now(); // if (!productTab.page_context) { // await productTab.puppeteer_connect(); // } // if (productTab.page_context.url() !== productTab.url) { // await productTab.gotoLink(); // } // if (now - updatedAt < ONE_MINUTE) { // console.log(`⏳ Product ID: ${productTab.id} was updated recently. Skipping update.`); // } // await productTab.update(); // continue; // } // if (!productTab.page_context) { // console.log(`🔌 Connecting to page for Product ID: ${productTab.id}`); // await productTab.puppeteer_connect(); // } // console.log(`🚀 Executing action for Product ID: ${productTab.id}`); // await productTab.action(); // } // }, configs.AUTO_TRACKING_DELAY); // }; const tracking = async () => { if (_INTERVAL_TRACKING_ID) { clearInterval(_INTERVAL_TRACKING_ID); _INTERVAL_TRACKING_ID = null; } _INTERVAL_TRACKING_ID = setInterval(async () => { const productTabs = _.flatMap(MANAGER_BIDS, 'children'); for (const productTab of productTabs) { // Tìm parent context nếu chưa có if (!productTab.parent_browser_context) { const parent = _.find(MANAGER_BIDS, { id: productTab.web_bid.id }); productTab.parent_browser_context = parent?.browser_context; if (!productTab.parent_browser_context) { console.log(`🔄 Waiting for parent process to start... (Product ID: ${productTab.id})`); continue; } } // Kết nối Puppeteer nếu chưa có page_context if (!productTab.page_context) { console.log(`🔌 Connecting to page for Product ID: ${productTab.id}`); await productTab.puppeteer_connect(); } // Nếu URL thay đổi, điều hướng đến URL mới if (productTab.page_context.url() !== productTab.url) { await productTab.gotoLink(); } // Kiểm tra nếu cần cập nhật trước khi gọi update() if (shouldUpdateProductTab(productTab)) { console.log(`🔄 Updating Product ID: ${productTab.id}...`); await productTab.update(); } else { console.log(`⏳ Product ID: ${productTab.id} was updated recently. Skipping update.`); } // Nếu chưa có first_bid (trạng thái chưa đặt giá) if (!productTab.first_bid) { console.log(`🎯 Tracking out-bid event for Product ID: ${productTab.id}`); continue; } // Nếu chưa đến giờ bid if (productTab.start_bid_time && !isTimeReached(productTab.start_bid_time)) { console.log(`⏳ Not yet time to bid. Skipping Product ID: ${productTab.id}`); continue; } console.log(`🚀 Executing action for Product ID: ${productTab.id}`); await productTab.action(); } }, configs.AUTO_TRACKING_DELAY); }; const clearLazyTab = async () => { if (_CLEAR_LAZY_TAB_ID) { clearInterval(_CLEAR_LAZY_TAB_ID); _CLEAR_LAZY_TAB_ID = null; } try { _CLEAR_LAZY_TAB_ID = setInterval(async () => { if (!global.IS_CLEANING) return; if (!browser) { console.warn('⚠️ Browser is not available or disconnected.'); clearInterval(_CLEAR_LAZY_TAB_ID); _CLEAR_LAZY_TAB_ID = null; return; } try { const pages = await browser.pages(); // Lấy danh sách URL từ flattenedArray const activeUrls = _.flatMap(MANAGER_BIDS, (item) => [item.url, ...item.children.map((child) => child.url)]).filter(Boolean); // Lọc bỏ null hoặc undefined console.log( '🔍 Page URLs:', pages.map((page) => page.url()), ); for (const page of pages) { const pageUrl = page.url(); // 🔥 Bỏ qua tab 'about:blank' hoặc tab không có URL if (!pageUrl || pageUrl === 'about:blank') continue; if (!activeUrls.includes(pageUrl)) { if (!page.isClosed() && browser.isConnected()) { try { await page.close(); console.log(`🛑 Closing unused tab: ${pageUrl}`); } catch (err) { console.warn(`⚠️ Error closing tab ${pageUrl}:`, err.message); } } } } } catch (err) { console.error('❌ Error in clearLazyTab:', err.message); } }, configs.AUTO_TRACKING_CLEANING); } catch (error) { console.log('CLEAR LAZY TAB ERROR: ', error.message); } }; const workTracking = () => { try { if (_WORK_TRACKING_ID) { clearInterval(_WORK_TRACKING_ID); _WORK_TRACKING_ID = null; } _WORK_TRACKING_ID = setInterval(() => { const activeData = _.flatMap(MANAGER_BIDS, (item) => [item, ...item.children]); for (const item of activeData) { if (item.page_context && !item.page_context.isClosed()) { item.handleTakeWorkSnapshot(); } } }, 10000); } catch (error) { console.log('Loi oi day'); } }; (async () => { const socket = io(configs.SOCKET_URL, { transports: ['websocket'], reconnection: true, }); // listen connect socket.on('connect', () => { console.log('✅ Connected to WebSocket server'); console.log('🔗 Socket ID:', socket.id); }); // listen event socket.on('bidsUpdated', async (data) => { console.log('📢 Bids Data:', data); handleUpdateProductTabs(data); await Promise.all(MANAGER_BIDS.map((apiBid) => apiBid.listen_events())); }); socket.on('webUpdated', async (data) => { console.log('📢 Account was updated:', data); const isDeleted = deleteProfile(data); if (isDeleted) { console.log('✅ Profile deleted successfully!'); const tabs = MANAGER_BIDS.filter((item) => item.url === data.url || item?.web_bid.url === data.url); if (tabs.length <= 0) return; await Promise.all(tabs.map((tab) => safeClosePage(tab))); await Promise.all(MANAGER_BIDS.map((apiBid) => apiBid.listen_events())); } else { console.log('⚠️ No profile found to delete.'); } }); // AUTO TRACKING tracking(); clearLazyTab(); workTracking(); })();