update time tracking for api bid #29
			
				
			
		
		
		
	| 
						 | 
					@ -2,9 +2,8 @@
 | 
				
			||||||
import { LoadingOverlay, Modal, ModalProps, Table } from '@mantine/core';
 | 
					import { LoadingOverlay, Modal, ModalProps, Table } from '@mantine/core';
 | 
				
			||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
 | 
					import { useCallback, useEffect, useMemo, useState } from 'react';
 | 
				
			||||||
import { getDetailBidHistories } from '../../apis/bid-histories';
 | 
					import { getDetailBidHistories } from '../../apis/bid-histories';
 | 
				
			||||||
import { extractNumber } from '../../lib/table/ultils';
 | 
					 | 
				
			||||||
import { IBid } from '../../system/type';
 | 
					import { IBid } from '../../system/type';
 | 
				
			||||||
import { formatTime } from '../../utils';
 | 
					import { extractNumber, formatTime } from '../../utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IShowHistoriesBidGraysApiModalProps extends ModalProps {
 | 
					export interface IShowHistoriesBidGraysApiModalProps extends ModalProps {
 | 
				
			||||||
    data: IBid | null;
 | 
					    data: IBid | null;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,9 +6,8 @@ import { Socket } from "socket.io-client";
 | 
				
			||||||
import { getImagesWorking } from "../../apis/bid";
 | 
					import { getImagesWorking } from "../../apis/bid";
 | 
				
			||||||
import { useStatusToolStore } from "../../lib/zustand/use-status-tool-store";
 | 
					import { useStatusToolStore } from "../../lib/zustand/use-status-tool-store";
 | 
				
			||||||
import { IBid, IWebBid } from "../../system/type";
 | 
					import { IBid, IWebBid } from "../../system/type";
 | 
				
			||||||
import { cn, extractDomainSmart, stringToColor } from "../../utils";
 | 
					import { cn, extractDomainSmart, findNearestClosingChild, isTimeReached, stringToColor, subtractSeconds } from "../../utils";
 | 
				
			||||||
import ShowImageModal from "./show-image-modal";
 | 
					import ShowImageModal from "./show-image-modal";
 | 
				
			||||||
import { isTimeReached, subtractSeconds } from "../../lib/table/ultils";
 | 
					 | 
				
			||||||
export interface IWorkingPageProps {
 | 
					export interface IWorkingPageProps {
 | 
				
			||||||
  data: (IBid | IWebBid) & { type: string };
 | 
					  data: (IBid | IWebBid) & { type: string };
 | 
				
			||||||
  socket: Socket;
 | 
					  socket: Socket;
 | 
				
			||||||
| 
						 | 
					@ -111,6 +110,11 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
 | 
				
			||||||
    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
					    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
				
			||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if(!isIBid(data)){
 | 
				
			||||||
 | 
					    console.log(data)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <Box
 | 
					      <Box
 | 
				
			||||||
| 
						 | 
					@ -140,10 +144,17 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
 | 
				
			||||||
            <Text className="text-xs tracking-wide">{`Current price: $${data.current_price}`}</Text>
 | 
					            <Text className="text-xs tracking-wide">{`Current price: $${data.current_price}`}</Text>
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <Text className="text-sm italic opacity-80">
 | 
					          <Text className="text-sm italic opacity-80">
 | 
				
			||||||
            {moment(lastUpdate).format("HH:mm:ss DD/MM/YYYY")}
 | 
					            {moment(lastUpdate).format("HH:mm:ss DD/MM/YYYY")}
 | 
				
			||||||
          </Text>
 | 
					          </Text>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          {!isIBid(data) && <Tooltip label={'Time to tracking'}><Text>{`TT: ${moment(subtractSeconds(findNearestClosingChild(data)?.close_time || '', data.early_tracking_seconds)).format(
 | 
				
			||||||
 | 
					                  "HH:mm:ss DD/MM/YYYY"
 | 
				
			||||||
 | 
					                )}`}</Text></Tooltip>}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <Box className="flex items-center gap-3">
 | 
					          <Box className="flex items-center gap-3">
 | 
				
			||||||
            {isIBid(data) && (
 | 
					            {isIBid(data) && (
 | 
				
			||||||
              <Tooltip label={'Close time'}>
 | 
					              <Tooltip label={'Close time'}>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ import { z } from "zod";
 | 
				
			||||||
import { createWebBid, updateWebBid } from "../../apis/web-bid";
 | 
					import { createWebBid, updateWebBid } from "../../apis/web-bid";
 | 
				
			||||||
import { useConfirmStore } from "../../lib/zustand/use-confirm";
 | 
					import { useConfirmStore } from "../../lib/zustand/use-confirm";
 | 
				
			||||||
import { IWebBid } from "../../system/type";
 | 
					import { IWebBid } from "../../system/type";
 | 
				
			||||||
import { extractDomain } from "../../utils";
 | 
					import { extractDomain, formatTimeFromMinutes } from "../../utils";
 | 
				
			||||||
export interface IWebBidModelProps extends ModalProps {
 | 
					export interface IWebBidModelProps extends ModalProps {
 | 
				
			||||||
  data: IWebBid | null;
 | 
					  data: IWebBid | null;
 | 
				
			||||||
  onUpdated?: () => void;
 | 
					  onUpdated?: () => void;
 | 
				
			||||||
| 
						 | 
					@ -158,8 +158,8 @@ export default function WebBidModal({
 | 
				
			||||||
          className="col-span-2"
 | 
					          className="col-span-2"
 | 
				
			||||||
          size="sm"
 | 
					          size="sm"
 | 
				
			||||||
          label={`Arrival offset seconds (${
 | 
					          label={`Arrival offset seconds (${
 | 
				
			||||||
            form.getValues()["arrival_offset_seconds"] / 60
 | 
					             formatTimeFromMinutes(form.getValues()["arrival_offset_seconds"] / 60)
 | 
				
			||||||
          } minutes)`}
 | 
					          })`}
 | 
				
			||||||
          placeholder="msg: 300"
 | 
					          placeholder="msg: 300"
 | 
				
			||||||
          {...form.getInputProps("arrival_offset_seconds")}
 | 
					          {...form.getInputProps("arrival_offset_seconds")}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
| 
						 | 
					@ -168,8 +168,8 @@ export default function WebBidModal({
 | 
				
			||||||
          className="col-span-2"
 | 
					          className="col-span-2"
 | 
				
			||||||
          size="sm"
 | 
					          size="sm"
 | 
				
			||||||
          label={`Early tracking seconds (${
 | 
					          label={`Early tracking seconds (${
 | 
				
			||||||
            form.getValues()["early_tracking_seconds"] / 60
 | 
					             formatTimeFromMinutes(form.getValues()["early_tracking_seconds"] / 60)
 | 
				
			||||||
          } minutes)`}
 | 
					          })`}
 | 
				
			||||||
          placeholder="msg: 600"
 | 
					          placeholder="msg: 600"
 | 
				
			||||||
          {...form.getInputProps("early_tracking_seconds")}
 | 
					          {...form.getInputProps("early_tracking_seconds")}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -117,28 +117,4 @@ export const removeFalsy = (data: { [key: string]: string | number }) => {
 | 
				
			||||||
  }, {} as { [key: string]: string | number });
 | 
					  }, {} as { [key: string]: string | number });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function extractNumber(str: string) {
 | 
					 | 
				
			||||||
  const match = str.match(/\d+(\.\d+)?/);
 | 
					 | 
				
			||||||
  return match ? parseFloat(match[0]) : null;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function subtractMinutes(time: string, minutes: number) {
 | 
					 | 
				
			||||||
  const date = new Date(time);
 | 
					 | 
				
			||||||
  date.setMinutes(date.getMinutes() - minutes);
 | 
					 | 
				
			||||||
  return date.toUTCString();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function subtractSeconds(time: string, seconds: number) {
 | 
					 | 
				
			||||||
  const date = new Date(time);
 | 
					 | 
				
			||||||
  date.setSeconds(date.getSeconds() - seconds);
 | 
					 | 
				
			||||||
  return date.toUTCString();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function isTimeReached(targetTime: string) {
 | 
					 | 
				
			||||||
  if (!targetTime) return false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const targetDate = new Date(targetTime);
 | 
					 | 
				
			||||||
  const now = new Date();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return now >= targetDate;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
import { ActionIcon, Badge, Box, Menu, Text, Tooltip } from "@mantine/core";
 | 
					import { ActionIcon, Anchor, Badge, Box, Menu, Text, Tooltip } from "@mantine/core";
 | 
				
			||||||
import { useDisclosure } from "@mantine/hooks";
 | 
					import { useDisclosure } from "@mantine/hooks";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  IconAd,
 | 
					  IconAd,
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,7 @@ import {
 | 
				
			||||||
  IconHammer,
 | 
					  IconHammer,
 | 
				
			||||||
  IconHistory,
 | 
					  IconHistory,
 | 
				
			||||||
  IconMenu,
 | 
					  IconMenu,
 | 
				
			||||||
  IconTrash,
 | 
					  IconTrash
 | 
				
			||||||
} from "@tabler/icons-react";
 | 
					} from "@tabler/icons-react";
 | 
				
			||||||
import _ from "lodash";
 | 
					import _ from "lodash";
 | 
				
			||||||
import { useMemo, useRef, useState } from "react";
 | 
					import { useMemo, useRef, useState } from "react";
 | 
				
			||||||
| 
						 | 
					@ -51,6 +51,11 @@ export default function Bids() {
 | 
				
			||||||
      key: "name",
 | 
					      key: "name",
 | 
				
			||||||
      title: "Name",
 | 
					      title: "Name",
 | 
				
			||||||
      typeFilter: "text",
 | 
					      typeFilter: "text",
 | 
				
			||||||
 | 
					      renderRow(row) {
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return <Anchor className="text-[14px]"  href={row.url} size="sm" style={{color: 'inherit'}}>{row.name}</Anchor>
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      key: "web_bid",
 | 
					      key: "web_bid",
 | 
				
			||||||
| 
						 | 
					@ -187,9 +192,7 @@ export default function Bids() {
 | 
				
			||||||
  const table = useMemo(() => {
 | 
					  const table = useMemo(() => {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <Table
 | 
					      <Table
 | 
				
			||||||
        onClickRow={(row) => {
 | 
					        
 | 
				
			||||||
          window.open(row.url, "_blank");
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
        tableChildProps={{
 | 
					        tableChildProps={{
 | 
				
			||||||
          trbody: {
 | 
					          trbody: {
 | 
				
			||||||
            className: "cursor-pointer",
 | 
					            className: "cursor-pointer",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,6 +44,7 @@ export interface IWebBid extends ITimestamp {
 | 
				
			||||||
    active: boolean;
 | 
					    active: boolean;
 | 
				
			||||||
    arrival_offset_seconds: number;
 | 
					    arrival_offset_seconds: number;
 | 
				
			||||||
    early_tracking_seconds: number;
 | 
					    early_tracking_seconds: number;
 | 
				
			||||||
 | 
					    snapshot_at: string | null
 | 
				
			||||||
    children: IBid[];
 | 
					    children: IBid[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,8 @@ import { clsx, type ClassValue } from "clsx";
 | 
				
			||||||
import { twMerge } from "tailwind-merge";
 | 
					import { twMerge } from "tailwind-merge";
 | 
				
			||||||
import moment from "moment";
 | 
					import moment from "moment";
 | 
				
			||||||
import { IWebBid } from "../system/type";
 | 
					import { IWebBid } from "../system/type";
 | 
				
			||||||
 | 
					import _ from 'lodash'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function cn(...args: ClassValue[]) {
 | 
					export function cn(...args: ClassValue[]) {
 | 
				
			||||||
  return twMerge(clsx(args));
 | 
					  return twMerge(clsx(args));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -201,3 +203,63 @@ export function extractDomainSmart(url: string) {
 | 
				
			||||||
    return url;
 | 
					    return url;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function findNearestClosingChild(webBid: IWebBid) {
 | 
				
			||||||
 | 
					  const now = Date.now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const validChildren = webBid.children.filter(
 | 
				
			||||||
 | 
					    (child) => child.close_time && !isNaN(new Date(child.close_time).getTime())
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (validChildren.length === 0) {
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const nearestChild = _.minBy(validChildren, (child) => {
 | 
				
			||||||
 | 
					    return Math.abs(new Date(child.close_time!).getTime() - now);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return nearestChild || null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function extractNumber(str: string) {
 | 
				
			||||||
 | 
					  const match = str.match(/\d+(\.\d+)?/);
 | 
				
			||||||
 | 
					  return match ? parseFloat(match[0]) : null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function subtractMinutes(time: string, minutes: number) {
 | 
				
			||||||
 | 
					  const date = new Date(time);
 | 
				
			||||||
 | 
					  date.setMinutes(date.getMinutes() - minutes);
 | 
				
			||||||
 | 
					  return date.toUTCString();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function subtractSeconds(time: string, seconds: number) {
 | 
				
			||||||
 | 
					  const date = new Date(time);
 | 
				
			||||||
 | 
					  date.setSeconds(date.getSeconds() - seconds);
 | 
				
			||||||
 | 
					  return date.toUTCString();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function isTimeReached(targetTime: string) {
 | 
				
			||||||
 | 
					  if (!targetTime) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const targetDate = new Date(targetTime);
 | 
				
			||||||
 | 
					  const now = new Date();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return now >= targetDate;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function formatTimeFromMinutes(minutes: number): string {
 | 
				
			||||||
 | 
					  // Tính ngày, giờ, phút từ số phút
 | 
				
			||||||
 | 
					  const days = Math.floor(minutes / (60 * 24));
 | 
				
			||||||
 | 
					  const hours = Math.floor((minutes % (60 * 24)) / 60);
 | 
				
			||||||
 | 
					  const mins = minutes % 60;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let result = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (days > 0) result += `${days} ${days > 1? 'days' :'day'} `;
 | 
				
			||||||
 | 
					  if (hours > 0) result += `${hours} ${hours > 1 ? 'hours' : 'hour'} `;
 | 
				
			||||||
 | 
					  if (mins > 0 || result === '') result += `${mins} minutes`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return result.trim();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@
 | 
				
			||||||
/node_modules
 | 
					/node_modules
 | 
				
			||||||
/build
 | 
					/build
 | 
				
			||||||
/public
 | 
					/public
 | 
				
			||||||
 | 
					/bot-data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Logs
 | 
					# Logs
 | 
				
			||||||
logs
 | 
					logs
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1 +1 @@
 | 
				
			||||||
{"createdAt":1746603511532}
 | 
					{"createdAt":1747011314493}
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,9 @@ export class WebBid extends Timestamp {
 | 
				
			||||||
  @Column({ default: 600 })
 | 
					  @Column({ default: 600 })
 | 
				
			||||||
  early_tracking_seconds: number;
 | 
					  early_tracking_seconds: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Column({ default: null })
 | 
				
			||||||
 | 
					  snapshot_at: Date | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Column({ default: null, nullable: true })
 | 
					  @Column({ default: null, nullable: true })
 | 
				
			||||||
  @Exclude()
 | 
					  @Exclude()
 | 
				
			||||||
  password: string;
 | 
					  password: string;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -228,7 +228,10 @@ export class BidsService {
 | 
				
			||||||
    if (!bid.close_time && !bid.start_bid_time) {
 | 
					    if (!bid.close_time && !bid.start_bid_time) {
 | 
				
			||||||
      // Thiết lập thời gian bắt đầu là 5 phút trước khi đóng
 | 
					      // Thiết lập thời gian bắt đầu là 5 phút trước khi đóng
 | 
				
			||||||
      // bid.start_bid_time = new Date().toUTCString();
 | 
					      // bid.start_bid_time = new Date().toUTCString();
 | 
				
			||||||
      bid.start_bid_time = subtractMinutes(close_time, bid.web_bid.arrival_offset_seconds/ 60);
 | 
					      bid.start_bid_time = subtractMinutes(
 | 
				
			||||||
 | 
					        close_time,
 | 
				
			||||||
 | 
					        bid.web_bid.arrival_offset_seconds / 60,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Kiểm tra nếu thời gian đóng bid đã đạt tới (tức phiên đấu giá đã kết thúc)
 | 
					    // Kiểm tra nếu thời gian đóng bid đã đạt tới (tức phiên đấu giá đã kết thúc)
 | 
				
			||||||
| 
						 | 
					@ -422,6 +425,11 @@ export class BidsService {
 | 
				
			||||||
        }),
 | 
					        }),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // update time snapshot for API BID
 | 
				
			||||||
 | 
					    if (type === 'API_BID') {
 | 
				
			||||||
 | 
					      this.webBidsService.webBidRepo.update(id, { snapshot_at: new Date() });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.eventEmitter.emit(`working`, {
 | 
					    this.eventEmitter.emit(`working`, {
 | 
				
			||||||
      status: 're-update',
 | 
					      status: 're-update',
 | 
				
			||||||
      id,
 | 
					      id,
 | 
				
			||||||
| 
						 | 
					@ -510,11 +518,9 @@ export class BidsService {
 | 
				
			||||||
    return AppResponse.toResponse(files);
 | 
					    return AppResponse.toResponse(files);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async emitLoginStatus(data: ClientUpdateLoginStatusDto){
 | 
					  async emitLoginStatus(data: ClientUpdateLoginStatusDto) {
 | 
				
			||||||
 | 
					    this.eventEmitter.emit(Event.statusLogin(data.data), data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.eventEmitter.emit(Event.statusLogin(data.data), data)
 | 
					    return AppResponse.toResponse(true);
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return AppResponse.toResponse(true)
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@ import browser from "./system/browser.js";
 | 
				
			||||||
import configs from "./system/config.js";
 | 
					import configs from "./system/config.js";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  delay,
 | 
					  delay,
 | 
				
			||||||
  isPageAvailable,
 | 
					  findNearestClosingChild,
 | 
				
			||||||
  isTimeReached,
 | 
					  isTimeReached,
 | 
				
			||||||
  safeClosePage,
 | 
					  safeClosePage,
 | 
				
			||||||
  subtractSeconds,
 | 
					  subtractSeconds,
 | 
				
			||||||
| 
						 | 
					@ -270,20 +270,20 @@ const clearLazyTab = async () => {
 | 
				
			||||||
    // product tabs
 | 
					    // product tabs
 | 
				
			||||||
    const productTabs = _.flatMap(MANAGER_BIDS, "children");
 | 
					    const productTabs = _.flatMap(MANAGER_BIDS, "children");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const item of [...productTabs, ...MANAGER_BIDS]) {
 | 
					    // for (const item of [...productTabs, ...MANAGER_BIDS]) {
 | 
				
			||||||
      if (!item.page_context) continue;
 | 
					    //   if (!item.page_context) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      try {
 | 
					    //   try {
 | 
				
			||||||
        const avalableResult = await isPageAvailable(item.page_context);
 | 
					    //     const avalableResult = await isPageAvailable(item.page_context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!avalableResult) {
 | 
					    //     if (!avalableResult) {
 | 
				
			||||||
          await safeClosePage(item);
 | 
					    //       await safeClosePage(item);
 | 
				
			||||||
        }
 | 
					    //     }
 | 
				
			||||||
      } catch (e) {
 | 
					    //   } catch (e) {
 | 
				
			||||||
        console.warn("⚠️ Error checking page_context.title()", e.message);
 | 
					    //     console.warn("⚠️ Error checking page_context.title()", e.message);
 | 
				
			||||||
        await safeClosePage(item);
 | 
					    //     await safeClosePage(item);
 | 
				
			||||||
      }
 | 
					    //   }
 | 
				
			||||||
    }
 | 
					    // }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const page of pages) {
 | 
					    for (const page of pages) {
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
| 
						 | 
					@ -303,13 +303,6 @@ const clearLazyTab = async () => {
 | 
				
			||||||
            productTab?.web_bid?.early_tracking_seconds || 0
 | 
					            productTab?.web_bid?.early_tracking_seconds || 0
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          console.log("%cindex.js:291 {object}", "color: #007acc;", {
 | 
					 | 
				
			||||||
            earlyTrackingTime,
 | 
					 | 
				
			||||||
            it_time: isTimeReached(earlyTrackingTime),
 | 
					 | 
				
			||||||
            close_time: productTab.close_time,
 | 
					 | 
				
			||||||
            tracking_time: productTab?.web_bid?.early_tracking_seconds,
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          if (!isTimeReached(earlyTrackingTime)) {
 | 
					          if (!isTimeReached(earlyTrackingTime)) {
 | 
				
			||||||
            await safeClosePage(productTab);
 | 
					            await safeClosePage(productTab);
 | 
				
			||||||
            console.log(`🛑 Unused page detected: ${pageUrl}`);
 | 
					            console.log(`🛑 Unused page detected: ${pageUrl}`);
 | 
				
			||||||
| 
						 | 
					@ -354,6 +347,15 @@ const clearLazyTab = async () => {
 | 
				
			||||||
        console.warn(`⚠️ Error handling page: ${pageErr.message}`);
 | 
					        console.warn(`⚠️ Error handling page: ${pageErr.message}`);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Delete lazy tracking page
 | 
				
			||||||
 | 
					    Promise.allSettled(
 | 
				
			||||||
 | 
					      MANAGER_BIDS.map(async (item) => {
 | 
				
			||||||
 | 
					        if (await item.isLazy()) {
 | 
				
			||||||
 | 
					          safeClosePage(item);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  } catch (err) {
 | 
					  } catch (err) {
 | 
				
			||||||
    console.error("❌ Error in clearLazyTab:", err.message);
 | 
					    console.error("❌ Error in clearLazyTab:", err.message);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,10 +5,12 @@ import browser from "../system/browser.js";
 | 
				
			||||||
import CONSTANTS from "../system/constants.js";
 | 
					import CONSTANTS from "../system/constants.js";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  findEarlyLoginTime,
 | 
					  findEarlyLoginTime,
 | 
				
			||||||
 | 
					  findNearestClosingChild,
 | 
				
			||||||
  getPathLocalData,
 | 
					  getPathLocalData,
 | 
				
			||||||
  getPathProfile,
 | 
					  getPathProfile,
 | 
				
			||||||
  isTimeReached,
 | 
					  isTimeReached,
 | 
				
			||||||
  sanitizeFileName,
 | 
					  sanitizeFileName,
 | 
				
			||||||
 | 
					  subtractSeconds,
 | 
				
			||||||
} from "../system/utils.js";
 | 
					} from "../system/utils.js";
 | 
				
			||||||
import { Bid } from "./bid.js";
 | 
					import { Bid } from "./bid.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,6 +26,8 @@ export class ApiBid extends Bid {
 | 
				
			||||||
  // browser_context;
 | 
					  // browser_context;
 | 
				
			||||||
  username;
 | 
					  username;
 | 
				
			||||||
  password;
 | 
					  password;
 | 
				
			||||||
 | 
					  early_tracking_seconds;
 | 
				
			||||||
 | 
					  snapshot_at;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor({
 | 
					  constructor({
 | 
				
			||||||
    url,
 | 
					    url,
 | 
				
			||||||
| 
						 | 
					@ -35,6 +39,8 @@ export class ApiBid extends Bid {
 | 
				
			||||||
    updated_at,
 | 
					    updated_at,
 | 
				
			||||||
    origin_url,
 | 
					    origin_url,
 | 
				
			||||||
    active,
 | 
					    active,
 | 
				
			||||||
 | 
					    early_tracking_seconds,
 | 
				
			||||||
 | 
					    snapshot_at,
 | 
				
			||||||
  }) {
 | 
					  }) {
 | 
				
			||||||
    super(BID_TYPE.API_BID, url);
 | 
					    super(BID_TYPE.API_BID, url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,6 +51,8 @@ export class ApiBid extends Bid {
 | 
				
			||||||
    this.active = active;
 | 
					    this.active = active;
 | 
				
			||||||
    this.username = username;
 | 
					    this.username = username;
 | 
				
			||||||
    this.password = password;
 | 
					    this.password = password;
 | 
				
			||||||
 | 
					    this.early_tracking_seconds = early_tracking_seconds;
 | 
				
			||||||
 | 
					    this.snapshot_at = snapshot_at;
 | 
				
			||||||
    this.id = id;
 | 
					    this.id = id;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -58,6 +66,8 @@ export class ApiBid extends Bid {
 | 
				
			||||||
    updated_at,
 | 
					    updated_at,
 | 
				
			||||||
    origin_url,
 | 
					    origin_url,
 | 
				
			||||||
    active,
 | 
					    active,
 | 
				
			||||||
 | 
					    early_tracking_seconds,
 | 
				
			||||||
 | 
					    snapshot_at,
 | 
				
			||||||
  }) {
 | 
					  }) {
 | 
				
			||||||
    this.created_at = created_at;
 | 
					    this.created_at = created_at;
 | 
				
			||||||
    this.updated_at = updated_at;
 | 
					    this.updated_at = updated_at;
 | 
				
			||||||
| 
						 | 
					@ -67,6 +77,8 @@ export class ApiBid extends Bid {
 | 
				
			||||||
    this.username = username;
 | 
					    this.username = username;
 | 
				
			||||||
    this.password = password;
 | 
					    this.password = password;
 | 
				
			||||||
    this.url = url;
 | 
					    this.url = url;
 | 
				
			||||||
 | 
					    this.early_tracking_seconds = early_tracking_seconds;
 | 
				
			||||||
 | 
					    this.snapshot_at = snapshot_at;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  puppeteer_connect = async () => {
 | 
					  puppeteer_connect = async () => {
 | 
				
			||||||
| 
						 | 
					@ -79,12 +91,81 @@ export class ApiBid extends Bid {
 | 
				
			||||||
    await this.restoreContext();
 | 
					    await this.restoreContext();
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async handlePrevListen() {
 | 
				
			||||||
 | 
					    console.log(`👂 [${this.id}] Start handlePrevListen...`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Chỉ bắt đầu check khi ảnh đã được chụp
 | 
				
			||||||
 | 
					    if (this.snapshot_at) {
 | 
				
			||||||
 | 
					      const nearestCloseTime = findNearestClosingChild(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!nearestCloseTime) {
 | 
				
			||||||
 | 
					        console.log(`❌ [${this.id}] No nearest closing child found.`);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const { close_time } = nearestCloseTime;
 | 
				
			||||||
 | 
					      console.log(`📅 [${this.id}] Nearest close_time: ${close_time}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const timeToTracking = subtractSeconds(
 | 
				
			||||||
 | 
					        close_time,
 | 
				
			||||||
 | 
					        this.early_tracking_seconds || 0
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      console.log(
 | 
				
			||||||
 | 
					        `🕰️ [${this.id}] Time to tracking: ${new Date(
 | 
				
			||||||
 | 
					          timeToTracking
 | 
				
			||||||
 | 
					        ).toISOString()}`
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!isTimeReached(timeToTracking)) {
 | 
				
			||||||
 | 
					        console.log(`⏳ [${this.id}] Not time to track yet.`);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log(`🔌 [${this.id}] Connecting to puppeteer...`);
 | 
				
			||||||
 | 
					    await this.puppeteer_connect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log(`✅ [${this.id}] Connected. Executing actions...`);
 | 
				
			||||||
 | 
					    await this.action();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log(`🎯 [${this.id}] handlePrevListen completed.`);
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async isLazy() {
 | 
				
			||||||
 | 
					    // Nếu chưa có ảnh chụp working => tab not lazy
 | 
				
			||||||
 | 
					    if (!this.snapshot_at) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const nearestCloseTime = findNearestClosingChild(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Nếu không có nearest close => tab lazy
 | 
				
			||||||
 | 
					    if (!nearestCloseTime) return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { close_time } = nearestCloseTime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const timeToTracking = subtractSeconds(
 | 
				
			||||||
 | 
					      close_time,
 | 
				
			||||||
 | 
					      this.early_tracking_seconds || 0
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Nếu chưa đến giờ tracking => tab lazy
 | 
				
			||||||
 | 
					    if (!isTimeReached(timeToTracking)) return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Các trường hợp còn lại => not lazy
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  listen_events = async () => {
 | 
					  listen_events = async () => {
 | 
				
			||||||
    if (this.page_context) return;
 | 
					    if (this.page_context) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await this.puppeteer_connect();
 | 
					    // await this.puppeteer_connect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await this.action();
 | 
					    // await this.action();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const results = await this.handlePrevListen();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!results) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await this.saveContext();
 | 
					    await this.saveContext();
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
| 
						 | 
					@ -107,16 +188,16 @@ export class ApiBid extends Bid {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (!fs.existsSync(dirPath)) {
 | 
					      if (!fs.existsSync(dirPath)) {
 | 
				
			||||||
        fs.mkdirSync(dirPath, { recursive: true });
 | 
					        fs.mkdirSync(dirPath, { recursive: true });
 | 
				
			||||||
        console.log(`📂 Save at folder: ${dirPath}`);
 | 
					        console.log(`📂 [${this.id}] Save at folder: ${dirPath}`);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      fs.writeFileSync(
 | 
					      fs.writeFileSync(
 | 
				
			||||||
        path.join(dirPath, sanitizeFileName(this.origin_url) + ".json"),
 | 
					        path.join(dirPath, sanitizeFileName(this.origin_url) + ".json"),
 | 
				
			||||||
        JSON.stringify(contextData, null, 2)
 | 
					        JSON.stringify(contextData, null, 2)
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      console.log("✅ Context saved!");
 | 
					      console.log(`✅ [${this.id}] Context saved!`);
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
      console.log("Save Context: ", error.message);
 | 
					      console.log(`[${this.id}] Save Context: , ${error.message}`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -132,7 +213,7 @@ export class ApiBid extends Bid {
 | 
				
			||||||
    // Restore Cookies
 | 
					    // Restore Cookies
 | 
				
			||||||
    await this.page_context.setCookie(...contextData.cookies);
 | 
					    await this.page_context.setCookie(...contextData.cookies);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    console.log("🔄 Context restored!");
 | 
					    console.log(`🔄 [${this.id}] Context restored!`);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async onCloseLogin() {}
 | 
					  async onCloseLogin() {}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -113,7 +113,7 @@ export class LangtonsApiBid extends ApiBid {
 | 
				
			||||||
            return { success: false, error: error.toString() };
 | 
					            return { success: false, error: error.toString() };
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        { usernam: this.username, password: this.password },
 | 
					        { username: this.username, password: this.password },
 | 
				
			||||||
        csrfToken
 | 
					        csrfToken
 | 
				
			||||||
      ); // truyền biến vào page context
 | 
					      ); // truyền biến vào page context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -158,34 +158,14 @@ export class LangtonsApiBid extends ApiBid {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async submitPrevCode() {
 | 
					  async submitCode({ name, code }) {
 | 
				
			||||||
    try {
 | 
					    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();
 | 
					      const csrfToken = await this.getCsrfToken();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (!csrfToken) return false;
 | 
					      if (!csrfToken) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const responsePrevCode = await this.callSubmitPrevCodeApi(
 | 
					      const responsePrevCode = await this.callSubmitPrevCodeApi(
 | 
				
			||||||
        prevCodeData.code,
 | 
					        code,
 | 
				
			||||||
        csrfToken
 | 
					        csrfToken
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -199,9 +179,6 @@ export class LangtonsApiBid extends ApiBid {
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // submit tiếp api login....
 | 
					 | 
				
			||||||
      await this.page_context.goto(this.url);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return true;
 | 
					      return true;
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
      console.error(`❌ [${this.id}] Error submitPrevCode:`, error);
 | 
					      console.error(`❌ [${this.id}] Error submitPrevCode:`, error);
 | 
				
			||||||
| 
						 | 
					@ -310,24 +287,31 @@ export class LangtonsApiBid extends ApiBid {
 | 
				
			||||||
      // save code to local
 | 
					      // save code to local
 | 
				
			||||||
      // await this.saveCodeToLocal({ name, code });
 | 
					      // await this.saveCodeToLocal({ name, code });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // ⌨ Enter verification code
 | 
					      // // ⌨ Enter verification code
 | 
				
			||||||
      console.log(`✍ [${this.id}] Entering verification code...`);
 | 
					      // console.log(`✍ [${this.id}] Entering verification code...`);
 | 
				
			||||||
      await page.type("#code", code, { delay: 120 });
 | 
					      // await page.type("#code", code, { delay: 120 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // 🚀 Click the verification confirmation button
 | 
					      // // 🚀 Click the verification confirmation button
 | 
				
			||||||
      console.log(
 | 
					      // console.log(
 | 
				
			||||||
        `🔘 [${this.id}] Clicking the verification confirmation button`
 | 
					      //   `🔘 [${this.id}] Clicking the verification confirmation button`
 | 
				
			||||||
      );
 | 
					      // );
 | 
				
			||||||
      await page.click(".btn.btn-block.btn-primary", { delay: 90 });
 | 
					      // await page.click(".btn.btn-block.btn-primary", { delay: 90 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // ⏳ Wait for navigation after verification
 | 
					      const reuslt = await this.submitCode({ name, code });
 | 
				
			||||||
      console.log(
 | 
					
 | 
				
			||||||
        `⏳ [${this.id}] Waiting for navigation after verification...`
 | 
					      if (!reuslt) {
 | 
				
			||||||
      );
 | 
					        console.log(`[${this.id}] Wrote verifi code failure`);
 | 
				
			||||||
      await page.waitForNavigation({
 | 
					        return;
 | 
				
			||||||
        timeout: 15000,
 | 
					      }
 | 
				
			||||||
        waitUntil: "domcontentloaded",
 | 
					
 | 
				
			||||||
      });
 | 
					      // // ⏳ Wait for navigation after verification
 | 
				
			||||||
 | 
					      // console.log(
 | 
				
			||||||
 | 
					      //   `⏳ [${this.id}] Waiting for navigation after verification...`
 | 
				
			||||||
 | 
					      // );
 | 
				
			||||||
 | 
					      // await page.waitForNavigation({
 | 
				
			||||||
 | 
					      //   timeout: 15000,
 | 
				
			||||||
 | 
					      //   waitUntil: "domcontentloaded",
 | 
				
			||||||
 | 
					      // });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await page.goto(this.url, { waitUntil: "networkidle2" });
 | 
					      await page.goto(this.url, { waitUntil: "networkidle2" });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -441,8 +425,11 @@ export class LangtonsApiBid extends ApiBid {
 | 
				
			||||||
  listen_events = async () => {
 | 
					  listen_events = async () => {
 | 
				
			||||||
    if (this.page_context) return;
 | 
					    if (this.page_context) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await this.puppeteer_connect();
 | 
					    // await this.puppeteer_connect();
 | 
				
			||||||
    await this.action();
 | 
					    // await this.action();
 | 
				
			||||||
 | 
					    const results = await this.handlePrevListen();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!results) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.reloadInterval = setInterval(async () => {
 | 
					    this.reloadInterval = setInterval(async () => {
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
import fs from 'fs';
 | 
					import fs from "fs";
 | 
				
			||||||
import configs from '../../system/config.js';
 | 
					import configs from "../../system/config.js";
 | 
				
			||||||
import { delay, getPathProfile, safeClosePage } from '../../system/utils.js';
 | 
					import { delay, getPathProfile, safeClosePage } from "../../system/utils.js";
 | 
				
			||||||
import { ApiBid } from '../api-bid.js';
 | 
					import { ApiBid } from "../api-bid.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class LawsonsApiBid extends ApiBid {
 | 
					export class LawsonsApiBid extends ApiBid {
 | 
				
			||||||
  reloadInterval = null;
 | 
					  reloadInterval = null;
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,11 @@ export class LawsonsApiBid extends ApiBid {
 | 
				
			||||||
      // Tạo timeout để reject sau 1 phút nếu không có phản hồi
 | 
					      // Tạo timeout để reject sau 1 phút nếu không có phản hồi
 | 
				
			||||||
      const timeout = setTimeout(() => {
 | 
					      const timeout = setTimeout(() => {
 | 
				
			||||||
        global.socket.off(`verify-code.${this.origin_url}`); // Xóa listener tránh rò rỉ bộ nhớ
 | 
					        global.socket.off(`verify-code.${this.origin_url}`); // Xóa listener tránh rò rỉ bộ nhớ
 | 
				
			||||||
                rej(new Error(`[${this.id}] Timeout: No verification code received within 2 minute.`));
 | 
					        rej(
 | 
				
			||||||
 | 
					          new Error(
 | 
				
			||||||
 | 
					            `[${this.id}] Timeout: No verification code received within 2 minute.`
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
      }, 120 * 1000); // 60 giây
 | 
					      }, 120 * 1000); // 60 giây
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      global.socket.on(`verify-code.${this.origin_url}`, async (data) => {
 | 
					      global.socket.on(`verify-code.${this.origin_url}`, async (data) => {
 | 
				
			||||||
| 
						 | 
					@ -30,7 +34,9 @@ export class LawsonsApiBid extends ApiBid {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const filePath = getPathProfile(this.origin_url);
 | 
					    const filePath = getPathProfile(this.origin_url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return !(await this.page_context.$('#emailLogin')) && fs.existsSync(filePath);
 | 
					    return (
 | 
				
			||||||
 | 
					      !(await this.page_context.$("#emailLogin")) && fs.existsSync(filePath)
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  waitVerifyData = async () =>
 | 
					  waitVerifyData = async () =>
 | 
				
			||||||
| 
						 | 
					@ -38,7 +44,11 @@ export class LawsonsApiBid extends ApiBid {
 | 
				
			||||||
      // Tạo timeout để reject sau 1 phút nếu không có phản hồi
 | 
					      // Tạo timeout để reject sau 1 phút nếu không có phản hồi
 | 
				
			||||||
      const timeout = setTimeout(() => {
 | 
					      const timeout = setTimeout(() => {
 | 
				
			||||||
        global.socket.off(`verify-code.${this.origin_url}`); // Xóa listener tránh rò rỉ bộ nhớ
 | 
					        global.socket.off(`verify-code.${this.origin_url}`); // Xóa listener tránh rò rỉ bộ nhớ
 | 
				
			||||||
                rej(new Error(`[${this.id}] Timeout: No verification code received within 1 minute.`));
 | 
					        rej(
 | 
				
			||||||
 | 
					          new Error(
 | 
				
			||||||
 | 
					            `[${this.id}] Timeout: No verification code received within 1 minute.`
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
      }, 60 * 1000); // 60 giây
 | 
					      }, 60 * 1000); // 60 giây
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      global.socket.on(`verify-code.${this.origin_url}`, async (data) => {
 | 
					      global.socket.on(`verify-code.${this.origin_url}`, async (data) => {
 | 
				
			||||||
| 
						 | 
					@ -52,7 +62,7 @@ export class LawsonsApiBid extends ApiBid {
 | 
				
			||||||
  async enterOTP(otp) {
 | 
					  async enterOTP(otp) {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      // Selector cho tất cả các input OTP
 | 
					      // Selector cho tất cả các input OTP
 | 
				
			||||||
            const inputSelector = '.MuiDialog-container .container input';
 | 
					      const inputSelector = ".MuiDialog-container .container input";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Chờ cho các input OTP xuất hiện
 | 
					      // Chờ cho các input OTP xuất hiện
 | 
				
			||||||
      await this.page_context.waitForSelector(inputSelector, { timeout: 8000 });
 | 
					      await this.page_context.waitForSelector(inputSelector, { timeout: 8000 });
 | 
				
			||||||
| 
						 | 
					@ -68,10 +78,10 @@ export class LawsonsApiBid extends ApiBid {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        console.log(`✅ OTP entered successfully: ${otp}`);
 | 
					        console.log(`✅ OTP entered successfully: ${otp}`);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
                console.error('❌ Invalid OTP or input fields count');
 | 
					        console.error("❌ Invalid OTP or input fields count");
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
            console.error('❌ Error entering OTP:', error);
 | 
					      console.error("❌ Error entering OTP:", error);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -80,13 +90,15 @@ export class LawsonsApiBid extends ApiBid {
 | 
				
			||||||
      if (!this.page_context) return false;
 | 
					      if (!this.page_context) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Selector của các phần tử trên trang
 | 
					      // Selector của các phần tử trên trang
 | 
				
			||||||
            const button = '.form-input-wrapper.form-group > .btn.btn-primary'; // Nút để tiếp tục quá trình xác minh
 | 
					      const button = ".form-input-wrapper.form-group > .btn.btn-primary"; // Nút để tiếp tục quá trình xác minh
 | 
				
			||||||
            const remember = '.PrivateSwitchBase-input'; // Checkbox "Remember me"
 | 
					      const remember = ".PrivateSwitchBase-input"; // Checkbox "Remember me"
 | 
				
			||||||
      const continueButton =
 | 
					      const continueButton =
 | 
				
			||||||
                '.MuiButtonBase-root.MuiButton-root.MuiButton-contained.MuiButton-containedPrimary.MuiButton-sizeMedium.MuiButton-containedSizeMedium.MuiButton-colorPrimary.MuiButton-root'; // Nút "Continue"
 | 
					        ".MuiButtonBase-root.MuiButton-root.MuiButton-contained.MuiButton-containedPrimary.MuiButton-sizeMedium.MuiButton-containedSizeMedium.MuiButton-colorPrimary.MuiButton-root"; // Nút "Continue"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Chờ cho nút xác minh xuất hiện
 | 
					      // Chờ cho nút xác minh xuất hiện
 | 
				
			||||||
            console.log(`🔎 [${this.id}] Waiting for the button with selector: ${button}`);
 | 
					      console.log(
 | 
				
			||||||
 | 
					        `🔎 [${this.id}] Waiting for the button with selector: ${button}`
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
      await this.page_context.waitForSelector(button, { timeout: 8000 });
 | 
					      await this.page_context.waitForSelector(button, { timeout: 8000 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      console.log(`✅ [${this.id}] Button found, clicking the first button.`);
 | 
					      console.log(`✅ [${this.id}] Button found, clicking the first button.`);
 | 
				
			||||||
| 
						 | 
					@ -104,7 +116,9 @@ export class LawsonsApiBid extends ApiBid {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Nhận mã OTP để nhập vào form
 | 
					      // Nhận mã OTP để nhập vào form
 | 
				
			||||||
      const { name, code } = await this.waitVerifyData();
 | 
					      const { name, code } = await this.waitVerifyData();
 | 
				
			||||||
            console.log(`🔎 [${this.id}] Waiting for OTP input, received code: ${code}`);
 | 
					      console.log(
 | 
				
			||||||
 | 
					        `🔎 [${this.id}] Waiting for OTP input, received code: ${code}`
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Nhập mã OTP vào form
 | 
					      // Nhập mã OTP vào form
 | 
				
			||||||
      await this.enterOTP(code);
 | 
					      await this.enterOTP(code);
 | 
				
			||||||
| 
						 | 
					@ -112,22 +126,30 @@ export class LawsonsApiBid extends ApiBid {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Chờ cho checkbox "Remember me" xuất hiện
 | 
					      // Chờ cho checkbox "Remember me" xuất hiện
 | 
				
			||||||
      await this.page_context.waitForSelector(remember, { timeout: 8000 });
 | 
					      await this.page_context.waitForSelector(remember, { timeout: 8000 });
 | 
				
			||||||
            console.log(`🔎 [${this.id}] Waiting for remember me checkbox with selector: ${remember}`);
 | 
					      console.log(
 | 
				
			||||||
 | 
					        `🔎 [${this.id}] Waiting for remember me checkbox with selector: ${remember}`
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Click vào checkbox "Remember me"
 | 
					      // Click vào checkbox "Remember me"
 | 
				
			||||||
      await this.page_context.click(remember, { delay: 92 });
 | 
					      await this.page_context.click(remember, { delay: 92 });
 | 
				
			||||||
      console.log(`✅ [${this.id}] Remember me checkbox clicked.`);
 | 
					      console.log(`✅ [${this.id}] Remember me checkbox clicked.`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Chờ cho nút "Continue" xuất hiện
 | 
					      // Chờ cho nút "Continue" xuất hiện
 | 
				
			||||||
            await this.page_context.waitForSelector(continueButton, { timeout: 8000 });
 | 
					      await this.page_context.waitForSelector(continueButton, {
 | 
				
			||||||
            console.log(`🔎 [${this.id}] Waiting for continue button with selector: ${continueButton}`);
 | 
					        timeout: 8000,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      console.log(
 | 
				
			||||||
 | 
					        `🔎 [${this.id}] Waiting for continue button with selector: ${continueButton}`
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Click vào nút "Continue"
 | 
					      // Click vào nút "Continue"
 | 
				
			||||||
      await this.page_context.click(continueButton, { delay: 100 });
 | 
					      await this.page_context.click(continueButton, { delay: 100 });
 | 
				
			||||||
      console.log(`✅ [${this.id}] Continue button clicked.`);
 | 
					      console.log(`✅ [${this.id}] Continue button clicked.`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Chờ cho trang tải hoàn tất sau khi click "Continue"
 | 
					      // Chờ cho trang tải hoàn tất sau khi click "Continue"
 | 
				
			||||||
            await this.page_context.waitForNavigation({ waitUntil: 'domcontentloaded' });
 | 
					      await this.page_context.waitForNavigation({
 | 
				
			||||||
 | 
					        waitUntil: "domcontentloaded",
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
      console.log(`✅ [${this.id}] Navigation completed.`);
 | 
					      console.log(`✅ [${this.id}] Navigation completed.`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return true;
 | 
					      return true;
 | 
				
			||||||
| 
						 | 
					@ -144,10 +166,10 @@ export class LawsonsApiBid extends ApiBid {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const filePath = getPathProfile(this.origin_url);
 | 
					    const filePath = getPathProfile(this.origin_url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await page.waitForNavigation({ waitUntil: 'domcontentloaded' });
 | 
					    await page.waitForNavigation({ waitUntil: "domcontentloaded" });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 🛠 Check if already logged in (login input should not be visible or profile exists)
 | 
					    // 🛠 Check if already logged in (login input should not be visible or profile exists)
 | 
				
			||||||
        if (!(await page.$('#emailLogin')) && fs.existsSync(filePath)) {
 | 
					    if (!(await page.$("#emailLogin")) && fs.existsSync(filePath)) {
 | 
				
			||||||
      console.log(`✅ [${this.id}] Already logged in, skipping login process.`);
 | 
					      console.log(`✅ [${this.id}] Already logged in, skipping login process.`);
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -158,18 +180,24 @@ export class LawsonsApiBid extends ApiBid {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const children = this.children.filter((item) => item.page_context);
 | 
					    const children = this.children.filter((item) => item.page_context);
 | 
				
			||||||
        console.log(`🔍 [${this.id}] Found ${children.length} child pages to close.`);
 | 
					    console.log(
 | 
				
			||||||
 | 
					      `🔍 [${this.id}] Found ${children.length} child pages to close.`
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (children.length > 0) {
 | 
					    if (children.length > 0) {
 | 
				
			||||||
      console.log(`🛑 [${this.id}] Closing child pages...`);
 | 
					      console.log(`🛑 [${this.id}] Closing child pages...`);
 | 
				
			||||||
      await Promise.all(
 | 
					      await Promise.all(
 | 
				
			||||||
        children.map((item) => {
 | 
					        children.map((item) => {
 | 
				
			||||||
                    console.log(`➡ [${this.id}] Closing child page with context: ${item.page_context}`);
 | 
					          console.log(
 | 
				
			||||||
 | 
					            `➡ [${this.id}] Closing child page with context: ${item.page_context}`
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
          return safeClosePage(item);
 | 
					          return safeClosePage(item);
 | 
				
			||||||
                }),
 | 
					        })
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            console.log(`➡ [${this.id}] Closing main page context: ${this.page_context}`);
 | 
					      console.log(
 | 
				
			||||||
 | 
					        `➡ [${this.id}] Closing main page context: ${this.page_context}`
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
      await safeClosePage(this);
 | 
					      await safeClosePage(this);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -178,22 +206,25 @@ export class LawsonsApiBid extends ApiBid {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      // ⌨ Enter email
 | 
					      // ⌨ Enter email
 | 
				
			||||||
      console.log(`✍ [${this.id}] Entering email:`, this.username);
 | 
					      console.log(`✍ [${this.id}] Entering email:`, this.username);
 | 
				
			||||||
            await page.type('#emailLogin', this.username, { delay: 100 });
 | 
					      await page.type("#emailLogin", this.username, { delay: 100 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // ⌨ Enter password
 | 
					      // ⌨ Enter password
 | 
				
			||||||
      console.log(`✍ [${this.id}] Entering password...`);
 | 
					      console.log(`✍ [${this.id}] Entering password...`);
 | 
				
			||||||
            await page.type('#passwordLogin', this.password, { delay: 150 });
 | 
					      await page.type("#passwordLogin", this.password, { delay: 150 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // 🚀 Click the login button
 | 
					      // 🚀 Click the login button
 | 
				
			||||||
      console.log(`🔘 [${this.id}] Clicking the "Login" button`);
 | 
					      console.log(`🔘 [${this.id}] Clicking the "Login" button`);
 | 
				
			||||||
            await page.click('#signInBtn', { delay: 92 });
 | 
					      await page.click("#signInBtn", { delay: 92 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const result = await this.waitToTwoVerify();
 | 
					      const result = await this.waitToTwoVerify();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // ⏳ Wait for navigation after login
 | 
					      // ⏳ Wait for navigation after login
 | 
				
			||||||
      if (!result) {
 | 
					      if (!result) {
 | 
				
			||||||
        console.log(`⏳ [${this.id}] Waiting for navigation after login...`);
 | 
					        console.log(`⏳ [${this.id}] Waiting for navigation after login...`);
 | 
				
			||||||
                await page.waitForNavigation({ timeout: 8000, waitUntil: 'domcontentloaded' });
 | 
					        await page.waitForNavigation({
 | 
				
			||||||
 | 
					          timeout: 8000,
 | 
				
			||||||
 | 
					          waitUntil: "domcontentloaded",
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (this.page_context.url() == this.url) {
 | 
					      if (this.page_context.url() == this.url) {
 | 
				
			||||||
| 
						 | 
					@ -204,7 +235,10 @@ export class LawsonsApiBid extends ApiBid {
 | 
				
			||||||
        console.log(`❌ [${this.id}] Login Failure!`);
 | 
					        console.log(`❌ [${this.id}] Login Failure!`);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
            console.error(`❌ [${this.id}] Error during login process:`, error.message);
 | 
					      console.error(
 | 
				
			||||||
 | 
					        `❌ [${this.id}] Error during login process:`,
 | 
				
			||||||
 | 
					        error.message
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    } finally {
 | 
					    } finally {
 | 
				
			||||||
      global.IS_CLEANING = true;
 | 
					      global.IS_CLEANING = true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -214,7 +248,7 @@ export class LawsonsApiBid extends ApiBid {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const page = this.page_context;
 | 
					      const page = this.page_context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            page.on('response', async (response) => {
 | 
					      page.on("response", async (response) => {
 | 
				
			||||||
        const request = response.request();
 | 
					        const request = response.request();
 | 
				
			||||||
        if (request.redirectChain().length > 0) {
 | 
					        if (request.redirectChain().length > 0) {
 | 
				
			||||||
          if (response.url().includes(configs.WEB_CONFIGS.LAWSONS.LOGIN_URL)) {
 | 
					          if (response.url().includes(configs.WEB_CONFIGS.LAWSONS.LOGIN_URL)) {
 | 
				
			||||||
| 
						 | 
					@ -223,31 +257,39 @@ export class LawsonsApiBid extends ApiBid {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await page.goto(this.url, { waitUntil: 'networkidle2' });
 | 
					      await page.goto(this.url, { waitUntil: "networkidle2" });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await page.bringToFront();
 | 
					      await page.bringToFront();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Set userAgent
 | 
					      // Set userAgent
 | 
				
			||||||
            await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
 | 
					      await page.setUserAgent(
 | 
				
			||||||
 | 
					        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
            console.log('Error [action]: ', error.message);
 | 
					      console.log("Error [action]: ", error.message);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  listen_events = async () => {
 | 
					  listen_events = async () => {
 | 
				
			||||||
    if (this.page_context) return;
 | 
					    if (this.page_context) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await this.puppeteer_connect();
 | 
					    // await this.puppeteer_connect();
 | 
				
			||||||
        await this.action();
 | 
					    // await this.action();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const results = await this.handlePrevListen();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!results) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.reloadInterval = setInterval(async () => {
 | 
					    this.reloadInterval = setInterval(async () => {
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        if (this.page_context && !this.page_context.isClosed()) {
 | 
					        if (this.page_context && !this.page_context.isClosed()) {
 | 
				
			||||||
          console.log(`🔄 [${this.id}] Reloading page...`);
 | 
					          console.log(`🔄 [${this.id}] Reloading page...`);
 | 
				
			||||||
                    await this.page_context.reload({ waitUntil: 'networkidle2' });
 | 
					          await this.page_context.reload({ waitUntil: "networkidle2" });
 | 
				
			||||||
          console.log(`✅ [${this.id}] Page reloaded successfully.`);
 | 
					          console.log(`✅ [${this.id}] Page reloaded successfully.`);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
                    console.log(`❌ [${this.id}] Page context is closed. Stopping reload.`);
 | 
					          console.log(
 | 
				
			||||||
 | 
					            `❌ [${this.id}] Page context is closed. Stopping reload.`
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
          clearInterval(this.reloadInterval);
 | 
					          clearInterval(this.reloadInterval);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      } catch (error) {
 | 
					      } catch (error) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -128,8 +128,12 @@ export class PicklesApiBid extends ApiBid {
 | 
				
			||||||
  listen_events = async () => {
 | 
					  listen_events = async () => {
 | 
				
			||||||
    if (this.page_context) return;
 | 
					    if (this.page_context) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await this.puppeteer_connect();
 | 
					    // await this.puppeteer_connect();
 | 
				
			||||||
    await this.action();
 | 
					    // await this.action();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const results = await this.handlePrevListen();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!results) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.reloadInterval = setInterval(async () => {
 | 
					    this.reloadInterval = setInterval(async () => {
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@ import CONSTANTS from "./constants.js";
 | 
				
			||||||
import fs from "fs";
 | 
					import fs from "fs";
 | 
				
			||||||
import path from "path";
 | 
					import path from "path";
 | 
				
			||||||
import { updateStatusWork } from "./apis/bid.js";
 | 
					import { updateStatusWork } from "./apis/bid.js";
 | 
				
			||||||
 | 
					import _ from "lodash";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const isNumber = (value) => !isNaN(value) && !isNaN(parseFloat(value));
 | 
					export const isNumber = (value) => !isNaN(value) && !isNaN(parseFloat(value));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -303,3 +304,21 @@ export async function isPageAvailable(page) {
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function findNearestClosingChild(webBid) {
 | 
				
			||||||
 | 
					  const now = Date.now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const validChildren = webBid.children.filter(
 | 
				
			||||||
 | 
					    (child) => child.close_time && !isNaN(new Date(child.close_time).getTime())
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (validChildren.length === 0) {
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const nearestChild = _.minBy(validChildren, (child) => {
 | 
				
			||||||
 | 
					    return Math.abs(new Date(child.close_time).getTime() - now);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return nearestChild || null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue