Deploy to production #11
			
				
			
		
		
		
	| 
						 | 
				
			
			@ -5,14 +5,11 @@ import { createApiBid, createBidProduct, deleteProfile, shouldUpdateProductTab }
 | 
			
		|||
import browser from './system/browser.js';
 | 
			
		||||
import configs from './system/config.js';
 | 
			
		||||
import { delay, isTimeReached, safeClosePage } from './system/utils.js';
 | 
			
		||||
import pLimit from 'p-limit';
 | 
			
		||||
 | 
			
		||||
let MANAGER_BIDS = [];
 | 
			
		||||
 | 
			
		||||
let _INTERVAL_TRACKING_ID = null;
 | 
			
		||||
let _CLEAR_LAZY_TAB_ID = null;
 | 
			
		||||
let _WORK_TRACKING_ID = null;
 | 
			
		||||
 | 
			
		||||
global.IS_CLEANING = false;
 | 
			
		||||
const activeTasks = new Set();
 | 
			
		||||
 | 
			
		||||
const handleUpdateProductTabs = (data) => {
 | 
			
		||||
    if (!Array.isArray(data)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -181,58 +178,125 @@ const tracking = async () => {
 | 
			
		|||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const clearLazyTab = async () => {
 | 
			
		||||
    if (!global.IS_CLEANING) return;
 | 
			
		||||
// 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]);
 | 
			
		||||
 | 
			
		||||
//         for (const item of activeData) {
 | 
			
		||||
//             if (item.page_context && !item.page_context.isClosed()) {
 | 
			
		||||
//                 item.handleTakeWorkSnapshot();
 | 
			
		||||
//             }
 | 
			
		||||
//         }
 | 
			
		||||
//     } catch (error) {
 | 
			
		||||
//         console.log('Lỗi rồi:', error);
 | 
			
		||||
//     }
 | 
			
		||||
// };
 | 
			
		||||
 | 
			
		||||
const clearLazyTab = async () => {
 | 
			
		||||
    if (!browser) {
 | 
			
		||||
        console.warn('⚠️ Browser is not available or disconnected.');
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        const pages = await browser.pages();
 | 
			
		||||
        const contexts = browser.contexts(); // Lấy tất cả contexts
 | 
			
		||||
 | 
			
		||||
        // 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
 | 
			
		||||
        for (const context of contexts) {
 | 
			
		||||
            const pages = context.pages(); // Lấy tất cả pages trong context
 | 
			
		||||
 | 
			
		||||
        console.log(
 | 
			
		||||
            '🔍 Page URLs:',
 | 
			
		||||
            pages.map((page) => page.url()),
 | 
			
		||||
        );
 | 
			
		||||
            // Lấy danh sách URL từ flattenedArray
 | 
			
		||||
            const activeUrls = _.flatMap(MANAGER_BIDS, (item) => [item.url, ...item.children.map((child) => child.url)]).filter(Boolean);
 | 
			
		||||
 | 
			
		||||
        for (const page of pages) {
 | 
			
		||||
            const pageUrl = 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 (!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);
 | 
			
		||||
                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);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Nếu context không còn page nào thì đóng luôn
 | 
			
		||||
            if (context.pages().length === 0) {
 | 
			
		||||
                await context.close();
 | 
			
		||||
                console.log(`🗑️ Closing unused context`);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    } 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);
 | 
			
		||||
 | 
			
		||||
        for (const item of activeData) {
 | 
			
		||||
            if (item.page_context && !item.page_context.isClosed()) {
 | 
			
		||||
                item.handleTakeWorkSnapshot();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        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.log('Lỗi rồi:', error);
 | 
			
		||||
        console.error(`[❌ ERROR] Work tracking failed: ${error.message}\n`, error.stack);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,10 +19,11 @@ export class Bid {
 | 
			
		|||
        if (!this.page_context) return;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            console.log(`✅ Page loaded. Taking snapshot for Product ID: ${this.id}`);
 | 
			
		||||
            await this.page_context.waitForSelector('#pageContainer', { timeout: 10000 });
 | 
			
		||||
            console.log(`✅ Page fully loaded. Taking snapshot for Product ID: ${this.id}`);
 | 
			
		||||
            takeSnapshot(this.page_context, this, 'working', CONSTANTS.TYPE_IMAGE.WORK);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            console.error(`❌ Error taking snapshot for Product ID: ${this.id}:`, error.message);
 | 
			
		||||
        }
 | 
			
		||||
    }, 500);
 | 
			
		||||
    }, 1000);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -134,14 +134,12 @@ export class GrayApiBid extends ApiBid {
 | 
			
		|||
        // 🔍 Check if already logged in (login input should not be visible)
 | 
			
		||||
        if (!(await page.$('input[name="username"]')) || fs.existsSync(filePath)) {
 | 
			
		||||
            console.log('✅ Already logged in, skipping login.');
 | 
			
		||||
            global.IS_CLEANING = true;
 | 
			
		||||
 | 
			
		||||
            this.retry_login = 0; // Reset retry count
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        console.log('🔑 Starting login process...');
 | 
			
		||||
        global.IS_CLEANING = false;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await page.type('input[name="username"]', this.username, { delay: 100 });
 | 
			
		||||
| 
						 | 
				
			
			@ -156,7 +154,6 @@ export class GrayApiBid extends ApiBid {
 | 
			
		|||
            if (!(await page.$('input[name="username"]'))) {
 | 
			
		||||
                console.log('✅ Login successful!');
 | 
			
		||||
                this.retry_login = 0; // Reset retry count after success
 | 
			
		||||
                global.IS_CLEANING = true;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -211,7 +208,7 @@ export class GrayApiBid extends ApiBid {
 | 
			
		|||
            });
 | 
			
		||||
 | 
			
		||||
            page.on('load', async () => {
 | 
			
		||||
                console.log('🔄 Trang đã reload, khởi động lại polling...');
 | 
			
		||||
                console.log('🔄 The page has reloaded, restarting polling...');
 | 
			
		||||
 | 
			
		||||
                // await takeSnapshot(this.page_context, this, 'working', CONSTANTS.TYPE_IMAGE.WORK);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,7 @@
 | 
			
		|||
                "axios": "^1.8.2",
 | 
			
		||||
                "dotenv": "^16.4.7",
 | 
			
		||||
                "lodash": "^4.17.21",
 | 
			
		||||
                "p-limit": "^6.2.0",
 | 
			
		||||
                "puppeteer": "^24.4.0",
 | 
			
		||||
                "puppeteer-extra": "^3.3.6",
 | 
			
		||||
                "puppeteer-extra-plugin-stealth": "^2.11.2",
 | 
			
		||||
| 
						 | 
				
			
			@ -1294,6 +1295,21 @@
 | 
			
		|||
                "wrappy": "1"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/p-limit": {
 | 
			
		||||
            "version": "6.2.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz",
 | 
			
		||||
            "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==",
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "yocto-queue": "^1.1.1"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": ">=18"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": {
 | 
			
		||||
                "url": "https://github.com/sponsors/sindresorhus"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/pac-proxy-agent": {
 | 
			
		||||
            "version": "7.2.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -1992,6 +2008,18 @@
 | 
			
		|||
                "fd-slicer": "~1.1.0"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/yocto-queue": {
 | 
			
		||||
            "version": "1.2.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz",
 | 
			
		||||
            "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==",
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": ">=12.20"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": {
 | 
			
		||||
                "url": "https://github.com/sponsors/sindresorhus"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/zod": {
 | 
			
		||||
            "version": "3.24.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,7 @@
 | 
			
		|||
        "axios": "^1.8.2",
 | 
			
		||||
        "dotenv": "^16.4.7",
 | 
			
		||||
        "lodash": "^4.17.21",
 | 
			
		||||
        "p-limit": "^6.2.0",
 | 
			
		||||
        "puppeteer": "^24.4.0",
 | 
			
		||||
        "puppeteer-extra": "^3.3.6",
 | 
			
		||||
        "puppeteer-extra-plugin-stealth": "^2.11.2",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ import configs from '../system/config.js';
 | 
			
		|||
import CONSTANTS from '../system/constants.js';
 | 
			
		||||
import { sanitizeFileName } from '../system/utils.js';
 | 
			
		||||
import * as fs from 'fs';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
const ONE_MINUTE = 60 * 1000;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,7 +30,10 @@ const browser = await puppeteer.launch({
 | 
			
		|||
        '--disable-threaded-animation', // Giảm animation chạy trên nhiều thread
 | 
			
		||||
        '--disable-threaded-scrolling', // Tắt cuộn trang đa luồng
 | 
			
		||||
        '--disable-logging', // Tắt log debug
 | 
			
		||||
        '--blink-settings=imagesEnabled=false', // Không tải hình ảnh
 | 
			
		||||
        '--blink-settings=imagesEnabled=false', // Không tải hình ảnh,
 | 
			
		||||
        '--disable-background-timer-throttling', // Tránh việc throttling các timer khi chạy nền.
 | 
			
		||||
        '--disable-webrtc',
 | 
			
		||||
        '--disable-ipc-flooding-protection', // Nếu có extension cần IPC, cái này giúp tối ưu.
 | 
			
		||||
    ],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue