import 'dotenv/config'; import _ from 'lodash'; import { io } from 'socket.io-client'; import { createApiBid, createBidProduct, deleteProfile } 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}`); if (!productTab.page_context) { console.log(`🔌 Establishing connection for Product ID: ${productTab.id}`); await productTab.puppeteer_connect(); await productTab.handleTakeWorkSnapshot(); } continue; } if (productTab.start_bid_time && !isTimeReached(productTab.start_bid_time)) { console.log(`⏳ Not yet time to bid. Skipping Product ID: ${productTab.id}`); 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 clearLazyTab = async () => { // if (_CLEAR_LAZY_TAB_ID) { // clearInterval(_CLEAR_LAZY_TAB_ID); // _CLEAR_LAZY_TAB_ID = null; // Reset tránh memory leak // } // try { // _CLEAR_LAZY_TAB_ID = setInterval(async () => { // if (!browser) { // console.warn('⚠️ Browser is not available.'); // 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(); // if (!activeUrls.includes(pageUrl)) { // if (!page.isClosed()) { // 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.message); // } // }; 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(); })();