245 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			245 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
import 'dotenv/config';
 | 
						|
import _ from 'lodash';
 | 
						|
import pLimit from 'p-limit';
 | 
						|
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 { delay, isTimeReached, safeClosePage } from './system/utils.js';
 | 
						|
 | 
						|
let MANAGER_BIDS = [];
 | 
						|
 | 
						|
const activeTasks = new Set();
 | 
						|
 | 
						|
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 () => {
 | 
						|
    console.log('🚀 Tracking process started...');
 | 
						|
 | 
						|
    while (true) {
 | 
						|
        try {
 | 
						|
            console.log('🔍 Scanning active bids...');
 | 
						|
            const productTabs = _.flatMap(MANAGER_BIDS, 'children');
 | 
						|
 | 
						|
            await Promise.allSettled(
 | 
						|
                MANAGER_BIDS.filter((bid) => !bid.page_context).map((apiBid) => {
 | 
						|
                    console.log(`🎧 Listening to events for API Bid ID: ${apiBid.id}`);
 | 
						|
                    return apiBid.listen_events();
 | 
						|
                }),
 | 
						|
            );
 | 
						|
 | 
						|
            Promise.allSettled(
 | 
						|
                productTabs.map(async (productTab) => {
 | 
						|
                    console.log(`📌 Processing Product ID: ${productTab.id}`);
 | 
						|
 | 
						|
                    // Xác định parent context
 | 
						|
                    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... (Product ID: ${productTab.id})`);
 | 
						|
                            return;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
 | 
						|
                    // 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();
 | 
						|
                    }
 | 
						|
 | 
						|
                    // Kiểm tra URL và điều hướng nếu cần
 | 
						|
                    if ((await productTab.page_context.url()) !== productTab.url) {
 | 
						|
                        console.log(`🔄 Redirecting to new URL for Product ID: ${productTab.id}`);
 | 
						|
                        await productTab.gotoLink();
 | 
						|
                    }
 | 
						|
 | 
						|
                    // Cập nhật nếu cần thiết
 | 
						|
                    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.`);
 | 
						|
                    }
 | 
						|
 | 
						|
                    // Chờ first bid
 | 
						|
                    if (!productTab.first_bid) {
 | 
						|
                        console.log(`🎯 Waiting for first bid for Product ID: ${productTab.id}`);
 | 
						|
                        return;
 | 
						|
                    }
 | 
						|
 | 
						|
                    // Kiểm tra thời gian bid
 | 
						|
                    if (productTab.start_bid_time && !isTimeReached(productTab.start_bid_time)) {
 | 
						|
                        console.log(`⏳ Not yet time to bid. Skipping Product ID: ${productTab.id}`);
 | 
						|
                        return;
 | 
						|
                    }
 | 
						|
 | 
						|
                    // Thực thi hành động
 | 
						|
                    console.log(`🚀 Executing action for Product ID: ${productTab.id}`);
 | 
						|
                    await productTab.action();
 | 
						|
                }),
 | 
						|
            );
 | 
						|
 | 
						|
            // Dọn dẹp tab không dùng
 | 
						|
            console.log('🧹 Cleaning up unused tabs...');
 | 
						|
            clearLazyTab();
 | 
						|
 | 
						|
            // Cập nhật trạng thái tracking
 | 
						|
            console.log('📊 Tracking work status...');
 | 
						|
            workTracking();
 | 
						|
        } catch (error) {
 | 
						|
            console.error('❌ Error in tracking loop:', error);
 | 
						|
        }
 | 
						|
 | 
						|
        console.log(`⏳ Waiting ${configs.AUTO_TRACKING_DELAY / 1000} seconds before the next iteration...`);
 | 
						|
        await delay(configs.AUTO_TRACKING_DELAY);
 | 
						|
    }
 | 
						|
};
 | 
						|
 | 
						|
const clearLazyTab = async () => {
 | 
						|
    if (!browser) {
 | 
						|
        console.warn('⚠️ Browser is not available or disconnected.');
 | 
						|
        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);
 | 
						|
    }
 | 
						|
};
 | 
						|
 | 
						|
const workTracking = async () => {
 | 
						|
    try {
 | 
						|
        const activeData = _.flatMap(MANAGER_BIDS, (item) => [item, ...item.children]);
 | 
						|
        const limit = pLimit(5);
 | 
						|
 | 
						|
        await Promise.allSettled(
 | 
						|
            activeData
 | 
						|
                .filter((item) => item.page_context && !item.page_context.isClosed())
 | 
						|
                .filter((item) => !activeTasks.has(item.id))
 | 
						|
                .map((item) =>
 | 
						|
                    limit(async () => {
 | 
						|
                        activeTasks.add(item.id);
 | 
						|
                        try {
 | 
						|
                            await item.handleTakeWorkSnapshot();
 | 
						|
                        } catch (error) {
 | 
						|
                            console.error(`[❌ ERROR] Snapshot failed for Product ID: ${item.id}`, error);
 | 
						|
                        } finally {
 | 
						|
                            activeTasks.delete(item.id);
 | 
						|
                        }
 | 
						|
                    }),
 | 
						|
                ),
 | 
						|
        );
 | 
						|
    } catch (error) {
 | 
						|
        console.error(`[❌ ERROR] Work tracking failed: ${error.message}\n`, error.stack);
 | 
						|
    }
 | 
						|
};
 | 
						|
 | 
						|
(async () => {
 | 
						|
    const socket = io(`${configs.SOCKET_URL}/bid-ws`, {
 | 
						|
        transports: ['websocket'],
 | 
						|
        reconnection: true,
 | 
						|
        extraHeaders: {
 | 
						|
            Authorization: process.env.CLIENT_KEY,
 | 
						|
        },
 | 
						|
    });
 | 
						|
 | 
						|
    // listen connect
 | 
						|
    socket.on('connect', () => {
 | 
						|
        console.log('✅ Connected to WebSocket server');
 | 
						|
        console.log('🔗 Socket ID:', socket.id);
 | 
						|
    });
 | 
						|
 | 
						|
    // listen connect
 | 
						|
    socket.on('disconnect', () => {
 | 
						|
        console.log('❌Client key is valid. Disconnected');
 | 
						|
    });
 | 
						|
 | 
						|
    // listen event
 | 
						|
    socket.on('bidsUpdated', async (data) => {
 | 
						|
        console.log('📢 Bids Data:', data);
 | 
						|
 | 
						|
        handleUpdateProductTabs(data);
 | 
						|
    });
 | 
						|
 | 
						|
    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)));
 | 
						|
        } else {
 | 
						|
            console.log('⚠️ No profile found to delete.');
 | 
						|
        }
 | 
						|
    });
 | 
						|
 | 
						|
    // AUTO TRACKING
 | 
						|
    tracking();
 | 
						|
})();
 |