260 lines
9.0 KiB
JavaScript
260 lines
9.0 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';
|
|
|
|
global.IS_CLEANING = true;
|
|
|
|
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 (!global.IS_CLEANING) {
|
|
console.log('🚀 Cleaning flag is OFF. Proceeding with operation.');
|
|
return;
|
|
}
|
|
|
|
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,
|
|
},
|
|
});
|
|
|
|
// set socket on global app
|
|
global.socket = socket;
|
|
|
|
// 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 tab = MANAGER_BIDS.find((item) => item.url === data.url);
|
|
|
|
if (!tab) return;
|
|
|
|
global.IS_CLEANING = false;
|
|
await Promise.all(tab.children.map((tab) => safeClosePage(tab)));
|
|
|
|
await safeClosePage(tab);
|
|
|
|
global.IS_CLEANING = true;
|
|
} else {
|
|
console.log('⚠️ No profile found to delete.');
|
|
}
|
|
});
|
|
|
|
// AUTO TRACKING
|
|
tracking();
|
|
})();
|