Deploy to production #25
			
				
			
		
		
		
	| 
						 | 
				
			
			@ -6,7 +6,7 @@ import { Socket } from "socket.io-client";
 | 
			
		|||
import { getImagesWorking } from "../../apis/bid";
 | 
			
		||||
import { useStatusToolStore } from "../../lib/zustand/use-status-tool-store";
 | 
			
		||||
import { IBid, IWebBid } from "../../system/type";
 | 
			
		||||
import { cn, stringToColor } from "../../utils";
 | 
			
		||||
import { cn, extractDomainSmart, stringToColor } from "../../utils";
 | 
			
		||||
import ShowImageModal from "./show-image-modal";
 | 
			
		||||
import { isTimeReached, subtractSeconds } from "../../lib/table/ultils";
 | 
			
		||||
export interface IWorkingPageProps {
 | 
			
		||||
| 
						 | 
				
			
			@ -129,7 +129,7 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
 | 
			
		|||
          src={imageSrc}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        <Box className="absolute bg-black/60 inset-0 flex items-center justify-center text-white font-bold flex-col gap-3 p-4 rounded-lg transition duration-300 hover:bg-opacity-70">
 | 
			
		||||
        <Box className="absolute bg-black/60 inset-0 flex items-center justify-center text-white font-bold flex-col gap-2 p-4 rounded-lg transition duration-300 hover:bg-opacity-70">
 | 
			
		||||
          <Text className="text-lg tracking-wide text-center font-bold">
 | 
			
		||||
            {isIBid(data) ? data.name : "Tracking page"}
 | 
			
		||||
          </Text>
 | 
			
		||||
| 
						 | 
				
			
			@ -207,11 +207,11 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
 | 
			
		|||
 | 
			
		||||
          <Badge
 | 
			
		||||
            color={stringToColor(
 | 
			
		||||
              isIBid(data) ? data.web_bid.origin_url : data.origin_url
 | 
			
		||||
              isIBid(data) ? extractDomainSmart(data.web_bid.origin_url) : extractDomainSmart(data.origin_url)
 | 
			
		||||
            )}
 | 
			
		||||
            size="xs"
 | 
			
		||||
          >
 | 
			
		||||
            {isIBid(data) ? data.web_bid.origin_url : data.origin_url}
 | 
			
		||||
            {isIBid(data) ? extractDomainSmart(data.web_bid.origin_url) : extractDomainSmart(data.origin_url)}
 | 
			
		||||
          </Badge>
 | 
			
		||||
        </Box>
 | 
			
		||||
      </Box>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,13 +18,13 @@ import {
 | 
			
		|||
  ShowHistoriesBidPicklesApiModal,
 | 
			
		||||
  ShowHistoriesModal,
 | 
			
		||||
} from "../components/bid";
 | 
			
		||||
import constants, { haveHistories } from "../constant";
 | 
			
		||||
import Table from "../lib/table/table";
 | 
			
		||||
import { IColumn, TRefTableFn } from "../lib/table/type";
 | 
			
		||||
import { useConfirmStore } from "../lib/zustand/use-confirm";
 | 
			
		||||
import { mappingStatusColors } from "../system/constants";
 | 
			
		||||
import { IBid } from "../system/type";
 | 
			
		||||
import { formatTime } from "../utils";
 | 
			
		||||
import constants, { haveHistories } from "../constant";
 | 
			
		||||
import { extractDomainSmart, formatTime } from "../utils";
 | 
			
		||||
 | 
			
		||||
export default function Bids() {
 | 
			
		||||
  const refTableFn: TRefTableFn<IBid> = useRef({});
 | 
			
		||||
| 
						 | 
				
			
			@ -57,7 +57,7 @@ export default function Bids() {
 | 
			
		|||
      title: "Web",
 | 
			
		||||
      typeFilter: "text",
 | 
			
		||||
      renderRow(row) {
 | 
			
		||||
        return <span>{row.web_bid.origin_url}</span>;
 | 
			
		||||
        return <span>{extractDomainSmart(row.web_bid.origin_url)}</span>;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -157,14 +157,18 @@ export function findEarlyLoginTime(webBid: IWebBid): string | null {
 | 
			
		|||
  const now = new Date();
 | 
			
		||||
 | 
			
		||||
  // Bước 1: Lọc ra những bid có close_time hợp lệ
 | 
			
		||||
  const validChildren = webBid.children.filter(child => child.close_time);
 | 
			
		||||
  const validChildren = webBid.children.filter((child) => child.close_time);
 | 
			
		||||
 | 
			
		||||
  if (validChildren.length === 0) return null;
 | 
			
		||||
 | 
			
		||||
  // Bước 2: Tìm bid có close_time gần hiện tại nhất
 | 
			
		||||
  const closestBid = validChildren.reduce((closest, current) => {
 | 
			
		||||
    const closestDiff = Math.abs(new Date(closest.close_time!).getTime() - now.getTime());
 | 
			
		||||
    const currentDiff = Math.abs(new Date(current.close_time!).getTime() - now.getTime());
 | 
			
		||||
    const closestDiff = Math.abs(
 | 
			
		||||
      new Date(closest.close_time!).getTime() - now.getTime()
 | 
			
		||||
    );
 | 
			
		||||
    const currentDiff = Math.abs(
 | 
			
		||||
      new Date(current.close_time!).getTime() - now.getTime()
 | 
			
		||||
    );
 | 
			
		||||
    return currentDiff < closestDiff ? current : closest;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -172,7 +176,28 @@ export function findEarlyLoginTime(webBid: IWebBid): string | null {
 | 
			
		|||
 | 
			
		||||
  // Bước 3: Tính toán thời gian login sớm
 | 
			
		||||
  const closeTime = new Date(closestBid.close_time);
 | 
			
		||||
  closeTime.setSeconds(closeTime.getSeconds() - (webBid.early_tracking_seconds || 0));
 | 
			
		||||
  closeTime.setSeconds(
 | 
			
		||||
    closeTime.getSeconds() - (webBid.early_tracking_seconds || 0)
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return closeTime.toISOString();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function extractDomainSmart(url: string) {
 | 
			
		||||
  const PUBLIC_SUFFIXES = ["com.au", "co.uk", "com.vn", "org.au", "gov.uk"];
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    const hostname = new URL(url).hostname.replace(/^www\./, ""); // remove "www."
 | 
			
		||||
    const parts = hostname.split(".");
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < PUBLIC_SUFFIXES.length; i++) {
 | 
			
		||||
      if (hostname.endsWith(PUBLIC_SUFFIXES[i])) {
 | 
			
		||||
        return parts[parts.length - PUBLIC_SUFFIXES[i].split(".").length - 1];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return parts[parts.length - 2];
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    return url;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@
 | 
			
		|||
/public
 | 
			
		||||
/system/error-images
 | 
			
		||||
/system/profiles
 | 
			
		||||
/system/local-data
 | 
			
		||||
 | 
			
		||||
# Logs
 | 
			
		||||
logs
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -269,6 +269,20 @@ const clearLazyTab = async () => {
 | 
			
		|||
    // product tabs
 | 
			
		||||
    const productTabs = _.flatMap(MANAGER_BIDS, "children");
 | 
			
		||||
 | 
			
		||||
    for (const item of [...productTabs, ...MANAGER_BIDS]) {
 | 
			
		||||
      if (!item.page_context) continue;
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        if (!(await item.page_context.title())) {
 | 
			
		||||
          // await vì title() là async
 | 
			
		||||
          await safeClosePage(item);
 | 
			
		||||
        }
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        console.warn("⚠️ Error checking page_context.title()", e.message);
 | 
			
		||||
        await safeClosePage(item);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const page of pages) {
 | 
			
		||||
      try {
 | 
			
		||||
        if (page.isClosed()) continue; // Trang đã đóng thì bỏ qua
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ import browser from "../system/browser.js";
 | 
			
		|||
import CONSTANTS from "../system/constants.js";
 | 
			
		||||
import {
 | 
			
		||||
  findEarlyLoginTime,
 | 
			
		||||
  getPathLocalData,
 | 
			
		||||
  getPathProfile,
 | 
			
		||||
  isTimeReached,
 | 
			
		||||
  sanitizeFileName,
 | 
			
		||||
| 
						 | 
				
			
			@ -141,4 +142,102 @@ export class ApiBid extends Bid {
 | 
			
		|||
 | 
			
		||||
    return earlyLoginTime && isTimeReached(earlyLoginTime);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async saveCodeToLocal({ name, code }) {
 | 
			
		||||
    try {
 | 
			
		||||
      const filePath = getPathLocalData(this.origin_url); // file path
 | 
			
		||||
      const dirPath = path.dirname(filePath); // lấy thư mục cha
 | 
			
		||||
 | 
			
		||||
      // kiểm tra folder chứa file đã tồn tại chưa
 | 
			
		||||
      if (!fs.existsSync(dirPath)) {
 | 
			
		||||
        fs.mkdirSync(dirPath, { recursive: true });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // ghi file
 | 
			
		||||
      fs.writeFileSync(
 | 
			
		||||
        filePath,
 | 
			
		||||
        JSON.stringify({ name, code, time: Date.now() }, null, 2) // format JSON đẹp
 | 
			
		||||
      );
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.log(
 | 
			
		||||
        `%cerror [${this.id}] models/api-bid.js line:149`,
 | 
			
		||||
        "color: red; display: block; width: 100%;",
 | 
			
		||||
        error
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async clearCodeFromLocal() {
 | 
			
		||||
    try {
 | 
			
		||||
      const filePath = getPathLocalData(this.origin_url); // file path
 | 
			
		||||
      const dirPath = path.dirname(filePath); // lấy thư mục cha
 | 
			
		||||
 | 
			
		||||
      // kiểm tra folder chứa file đã tồn tại chưa
 | 
			
		||||
      if (!fs.existsSync(dirPath)) {
 | 
			
		||||
        fs.mkdirSync(dirPath, { recursive: true });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // ghi file
 | 
			
		||||
      fs.writeFileSync(
 | 
			
		||||
        filePath,
 | 
			
		||||
        JSON.stringify({}, null, 2) // format JSON đẹp
 | 
			
		||||
      );
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.log(
 | 
			
		||||
        `%cerror [${this.id}] models/api-bid.js line:187`,
 | 
			
		||||
        "color: red; display: block; width: 100%;",
 | 
			
		||||
        error
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async loadCodeFromLocal() {
 | 
			
		||||
    try {
 | 
			
		||||
      const filePath = getPathLocalData(this.origin_url);
 | 
			
		||||
 | 
			
		||||
      if (!fs.existsSync(filePath)) {
 | 
			
		||||
        console.warn(
 | 
			
		||||
          `%cwarn [${this.id}] models/api-bid.js`,
 | 
			
		||||
          "color: orange; display: block; width: 100%;",
 | 
			
		||||
          `File not found: ${filePath}`
 | 
			
		||||
        );
 | 
			
		||||
        return null; // hoặc {} tùy bạn muốn trả gì
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const fileContent = fs.readFileSync(filePath, "utf-8");
 | 
			
		||||
      const data = JSON.parse(fileContent);
 | 
			
		||||
 | 
			
		||||
      return data; // { name, code, time }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error(
 | 
			
		||||
        `%cerror [${this.id}] models/api-bid.js`,
 | 
			
		||||
        "color: red; display: block; width: 100%;",
 | 
			
		||||
        error
 | 
			
		||||
      );
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async isCodeValid(minutes = 8) {
 | 
			
		||||
    try {
 | 
			
		||||
      const data = await this.loadCodeFromLocal();
 | 
			
		||||
      if (!data || !data.time) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const now = Date.now();
 | 
			
		||||
      const timeDiff = now - data.time; // tính chênh lệch thời gian (ms)
 | 
			
		||||
 | 
			
		||||
      const validDuration = minutes * 60 * 1000; // phút -> mili giây
 | 
			
		||||
 | 
			
		||||
      return timeDiff <= validDuration;
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error(
 | 
			
		||||
        `%cerror [${this.id}] models/api-bid.js`,
 | 
			
		||||
        "color: red; display: block; width: 100%;",
 | 
			
		||||
        error
 | 
			
		||||
      );
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,10 @@
 | 
			
		|||
import fs from "fs";
 | 
			
		||||
import configs from "../../system/config.js";
 | 
			
		||||
import { getPathProfile, safeClosePage } from "../../system/utils.js";
 | 
			
		||||
import {
 | 
			
		||||
  getPathLocalData,
 | 
			
		||||
  getPathProfile,
 | 
			
		||||
  safeClosePage,
 | 
			
		||||
} from "../../system/utils.js";
 | 
			
		||||
import { ApiBid } from "../api-bid.js";
 | 
			
		||||
import _ from "lodash";
 | 
			
		||||
import { updateStatusByPrice } from "../../system/apis/bid.js";
 | 
			
		||||
| 
						 | 
				
			
			@ -42,6 +46,169 @@ export class LangtonsApiBid extends ApiBid {
 | 
			
		|||
    );
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  async callSubmitPrevCodeApi(code, csrfToken) {
 | 
			
		||||
    if (!this.page_context) return;
 | 
			
		||||
    try {
 | 
			
		||||
      const result = await this.page_context.evaluate(
 | 
			
		||||
        async (code, csrfToken) => {
 | 
			
		||||
          const formData = new FormData();
 | 
			
		||||
          formData.append("dwfrm_profile_login_code", code);
 | 
			
		||||
          formData.append("csrf_token", csrfToken);
 | 
			
		||||
 | 
			
		||||
          try {
 | 
			
		||||
            const response = await fetch(
 | 
			
		||||
              "https://www.langtons.com.au/on/demandware.store/Sites-langtons-Site/en_AU/Login-VerifyOtpForLogin",
 | 
			
		||||
              {
 | 
			
		||||
                method: "POST",
 | 
			
		||||
                body: formData,
 | 
			
		||||
              }
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            const data = await response.json();
 | 
			
		||||
            return { success: true, data };
 | 
			
		||||
          } catch (error) {
 | 
			
		||||
            return { success: false, error: error.toString() };
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        code,
 | 
			
		||||
        csrfToken
 | 
			
		||||
      ); // truyền biến vào page context
 | 
			
		||||
 | 
			
		||||
      if (result.success) {
 | 
			
		||||
        console.log(`[${this.id}] callInsideApi API response:`, result.data);
 | 
			
		||||
        return result.data;
 | 
			
		||||
      } else {
 | 
			
		||||
        console.error(`[${this.id}] callInsideApi API error:`, result.error);
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error(`[${this.id}] Puppeteer evaluate error:`, error);
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async callSubmitAccountApi(csrfToken) {
 | 
			
		||||
    if (!this.page_context) return;
 | 
			
		||||
    try {
 | 
			
		||||
      const result = await this.page_context.evaluate(
 | 
			
		||||
        async ({ username, password }, csrfToken) => {
 | 
			
		||||
          const formData = new FormData();
 | 
			
		||||
          formData.append("loginEmail", username);
 | 
			
		||||
          formData.append("loginPassword", password);
 | 
			
		||||
          formData.append("rememberMe", true);
 | 
			
		||||
          formData.append("csrf_token", csrfToken);
 | 
			
		||||
 | 
			
		||||
          try {
 | 
			
		||||
            const response = await fetch(
 | 
			
		||||
              "https://www.langtons.com.au/on/demandware.store/Sites-langtons-Site/en_AU/Account-Login?rurl=5",
 | 
			
		||||
              {
 | 
			
		||||
                method: "POST",
 | 
			
		||||
                body: formData,
 | 
			
		||||
              }
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            const data = await response.json();
 | 
			
		||||
            return { success: true, data };
 | 
			
		||||
          } catch (error) {
 | 
			
		||||
            return { success: false, error: error.toString() };
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        { usernam: this.username, password: this.password },
 | 
			
		||||
        csrfToken
 | 
			
		||||
      ); // truyền biến vào page context
 | 
			
		||||
 | 
			
		||||
      if (result.success) {
 | 
			
		||||
        console.log(
 | 
			
		||||
          `[${this.id}] callSubmitAccountApi API response:`,
 | 
			
		||||
          result.data
 | 
			
		||||
        );
 | 
			
		||||
        return result.data;
 | 
			
		||||
      } else {
 | 
			
		||||
        console.error(
 | 
			
		||||
          `[${this.id}] callSubmitAccountApi API error:`,
 | 
			
		||||
          result.error
 | 
			
		||||
        );
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error(`[${this.id}] Puppeteer evaluate error:`, error);
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getCsrfToken() {
 | 
			
		||||
    try {
 | 
			
		||||
      const csrfToken = await this.page_context.evaluate(() => {
 | 
			
		||||
        const csrfInput = document.querySelector(
 | 
			
		||||
          'input[name*="csrf"], input[name="_token"]'
 | 
			
		||||
        );
 | 
			
		||||
        return csrfInput ? csrfInput.value : null;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      if (csrfToken) {
 | 
			
		||||
        console.log(`✅ [${this.id}] CSRF token: ${csrfToken}`);
 | 
			
		||||
        return csrfToken;
 | 
			
		||||
      } else {
 | 
			
		||||
        console.warn(`⚠️ [${this.id}] No CSRF token found.`);
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error(`❌ [${this.id}] Error getting CSRF token:`, error);
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async submitPrevCode() {
 | 
			
		||||
    try {
 | 
			
		||||
      const prevCodeData = await this.loadCodeFromLocal();
 | 
			
		||||
 | 
			
		||||
      if (!prevCodeData) return false;
 | 
			
		||||
 | 
			
		||||
      await this.page_context.goto(
 | 
			
		||||
        "https://www.langtons.com.au/account/mfalogin?rurl=5"
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      await this.page_context.waitForNavigation({
 | 
			
		||||
        timeout: 8000,
 | 
			
		||||
        waitUntil: "domcontentloaded",
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      if (!(await page.type("#code", code, { delay: 120 }))) {
 | 
			
		||||
        await this.clearCodeFromLocal();
 | 
			
		||||
 | 
			
		||||
        await this.page_context.goto(configs.WEB_URLS.LANGTONS.LOGIN_URL);
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const csrfToken = await this.getCsrfToken();
 | 
			
		||||
 | 
			
		||||
      if (!csrfToken) return false;
 | 
			
		||||
 | 
			
		||||
      const responsePrevCode = await this.callSubmitPrevCodeApi(
 | 
			
		||||
        prevCodeData.code,
 | 
			
		||||
        csrfToken
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      if (!responsePrevCode || !responsePrevCode.success) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const responseAccount = await this.callSubmitAccountApi(csrfToken);
 | 
			
		||||
 | 
			
		||||
      if (!responseAccount || !responseAccount.success) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // submit tiếp api login....
 | 
			
		||||
      await this.page_context.goto(this.url);
 | 
			
		||||
 | 
			
		||||
      return true;
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error(`❌ [${this.id}] Error submitPrevCode:`, error);
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async handleLogin() {
 | 
			
		||||
    const page = this.page_context;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -60,6 +227,13 @@ export class LangtonsApiBid extends ApiBid {
 | 
			
		|||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // // check valid prev code
 | 
			
		||||
    // if (this.isCodeValid()) {
 | 
			
		||||
    //   const result = await this.submitPrevCode();
 | 
			
		||||
 | 
			
		||||
    //   if (result) return;
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    if (fs.existsSync(filePath)) {
 | 
			
		||||
      console.log(`🗑 [${this.id}] Deleting existing file: ${filePath}`);
 | 
			
		||||
      fs.unlinkSync(filePath);
 | 
			
		||||
| 
						 | 
				
			
			@ -133,6 +307,9 @@ export class LangtonsApiBid extends ApiBid {
 | 
			
		|||
        code,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      // save code to local
 | 
			
		||||
      // await this.saveCodeToLocal({ name, code });
 | 
			
		||||
 | 
			
		||||
      // ⌨ Enter verification code
 | 
			
		||||
      console.log(`✍ [${this.id}] Entering verification code...`);
 | 
			
		||||
      await page.type("#code", code, { delay: 120 });
 | 
			
		||||
| 
						 | 
				
			
			@ -160,6 +337,9 @@ export class LangtonsApiBid extends ApiBid {
 | 
			
		|||
 | 
			
		||||
      // await page.goto(this.url);
 | 
			
		||||
      console.log(`✅ [${this.id}] Navigation successful!`);
 | 
			
		||||
 | 
			
		||||
      // clear code
 | 
			
		||||
      // this.clearCodeFromLocal();
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error(
 | 
			
		||||
        `❌ [${this.id}] Error during login process:`,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,17 +1,18 @@
 | 
			
		|||
import * as path from 'path';
 | 
			
		||||
import { fileURLToPath } from 'url'; // ✅ Cần import từ 'url'
 | 
			
		||||
import * as path from "path";
 | 
			
		||||
import { fileURLToPath } from "url"; // ✅ Cần import từ 'url'
 | 
			
		||||
 | 
			
		||||
const __filename = fileURLToPath(import.meta.url);
 | 
			
		||||
const __dirname = path.dirname(__filename);
 | 
			
		||||
 | 
			
		||||
const CONSTANTS = {
 | 
			
		||||
    PROFILE_PATH: path.join(__dirname, 'profiles'),
 | 
			
		||||
    ERROR_IMAGES_PATH: path.join(__dirname, 'error-images'),
 | 
			
		||||
    TYPE_IMAGE: {
 | 
			
		||||
        ERRORS: 'errors',
 | 
			
		||||
        SUCCESS: 'success',
 | 
			
		||||
        WORK: 'work',
 | 
			
		||||
    },
 | 
			
		||||
  PROFILE_PATH: path.join(__dirname, "profiles"),
 | 
			
		||||
  LOCAL_DATA_PATH: path.join(__dirname, "local-data"),
 | 
			
		||||
  ERROR_IMAGES_PATH: path.join(__dirname, "error-images"),
 | 
			
		||||
  TYPE_IMAGE: {
 | 
			
		||||
    ERRORS: "errors",
 | 
			
		||||
    SUCCESS: "success",
 | 
			
		||||
    WORK: "work",
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default CONSTANTS;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -133,6 +133,13 @@ export const getPathProfile = (origin_url) => {
 | 
			
		|||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getPathLocalData = (origin_url) => {
 | 
			
		||||
  return path.join(
 | 
			
		||||
    CONSTANTS.LOCAL_DATA_PATH,
 | 
			
		||||
    sanitizeFileName(origin_url) + ".json"
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function removeFalsyValues(obj, excludeKeys = []) {
 | 
			
		||||
  return Object.entries(obj).reduce((acc, [key, value]) => {
 | 
			
		||||
    if (value || excludeKeys.includes(key)) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue