bank/BCA.class.js

const ScraperBank = require("../BrowserClasses");
const BCASelectors = require("../helper/selector/BCASelector");
const { BCAParser } = require("../helper/utils/Parser");
const log = require("../helper/utils/Logger");

/**
 * Scraper for BCA (Bank Central Asia) that extends ScraperBank class.
 * @class ScrapBCA
 * @author fdciabdul
 * @memberof Bank
 */

class ScrapBCA extends ScraperBank {
    /**
     * Constructor for ScrapBCA class.
     * @constructor
     * @param {string} user - BCA username.
     * @param {string} pass - BCA password.
     * @param {string} norek - BCA account number.
     * @param {object} args - Additional arguments.
     * @param {boolean} useFingerprintInjector - Flag to use fingerprint injector.
     * 
     * @example new ScrapBCA('sukianti1080','654321',true);
     */
    constructor(user, pass, norek, args, useFingerprintInjector = false) {
        super(user, pass, args, useFingerprintInjector);
        this.norek = norek;
        this.log = log;
    }

    /**
     * Logs in to the BCA website.
     * @async
     */
    async loginToBCA() {
        this.page = await this.launchBrowser();
        await this.page.goto(BCASelectors.LOGIN_PAGE.url, {
            waitUntil: "domcontentloaded"
        });

        this.page.on("dialog", async dialog => {
            await dialog.accept();
            this.log("[ LOG ] [" + this.user + "] " + dialog.message());
            this.loginError = dialog.message();
        });
        this.log("[ LOG ] [" + this.user + "] [" + this.user + "] Login to BCA .. ")
        await this.page.type(BCASelectors.LOGIN_PAGE.userField, this.user, {
            delay: 100
        });
        await this.page.type(BCASelectors.LOGIN_PAGE.passField, this.pass);
        await this.page.click(BCASelectors.LOGIN_PAGE.submitButton, {
            delay: 200
        });

        await this.page.waitForTimeout(2000);
        if (this.loginError) {
            throw new Error(this.loginError);
        }
    }

    /**
     * Processes getting the balance.
     * @async
     * @param {Object} page - Page object.
     * @returns {Object} - New page object.
     */
    async processGetBalance(page) {
        await this.page.goto(BCASelectors.SETTLEMENT_PAGE.url, {
            waitUntil: "domcontentloaded"
        });
        this.log("[ LOG ] [" + this.user + "] Success login ..");
        await this.page.click(BCASelectors.SETTLEMENT_PAGE.balanceLink);

        const newPage = await this.createTargetPage(page);
        this.log("[ LOG ] [" + this.user + "] Select date ..");


        return newPage;
    }

    /**
     * Selects account and sets date for transactions.
     * @async
     * @param {number} tglawal - Start date.
     * @param {number} blnawal - Start month.
     * @param {number} tglakhir - End date.
     * @param {number} blnakhir - End month.
     * @returns {Object} - New page object.
     */
    async selectAccountAndSetDates(tglawal, blnawal, tglakhir, blnakhir) {
        await this.page.goto(BCASelectors.SETTLEMENT_PAGE.url, {
            waitUntil: "domcontentloaded"
        });
        this.log("[ LOG ] [" + this.user + "] Success login ..")
        await this.page.click(BCASelectors.SETTLEMENT_PAGE.settlementLink);

        const newPage = await this.createTargetPage();
        const padStart2 = num => num.toString().padStart(2, "0");
        if (tglawal || blnawal || tglakhir || blnakhir) {
            this.log("[ LOG ] [" + this.user + "] Select date ..")
            await newPage.waitForSelector(BCASelectors.SETTLEMENT_PAGE.startDateField);
            await newPage.select(BCASelectors.SETTLEMENT_PAGE.startDateField, padStart2(tglawal));
            await newPage.select(BCASelectors.SETTLEMENT_PAGE.startMonthField, blnawal.toString());
            await newPage.select(BCASelectors.SETTLEMENT_PAGE.endDateField, padStart2(tglakhir));
            await newPage.select(BCASelectors.SETTLEMENT_PAGE.endMonthField, blnakhir.toString());
            this.log("[ LOG ] [" + this.user + "] Get mutasi ..")
            await newPage.click(BCASelectors.SETTLEMENT_PAGE.submitButton, {
                delay: 1500
            });

            await this.page.waitForTimeout(3000);
            return newPage;
        } else {
            this.log("[ LOG ] [" + this.user + "] Get latest mutasi ..")
            await newPage.click(BCASelectors.SETTLEMENT_PAGE.submitButton, {
                delay: 1500
            });
            await this.page.waitForTimeout(3000);
            return newPage;
        }

    }

    /**
     * Creates a new target page (get new window).
     * @async
     * @returns {Object} - New page object.
     */
    async createTargetPage() {
        const pageTarget = this.page.target();
        const newTarget = await this.page.browser().waitForTarget(target => target.opener() === pageTarget);
        const newPage = await newTarget.page();

        await this.handleDialogAndLogout(newPage);

        await newPage.reload({
            waitUntil: "domcontentloaded"
        });

        return newPage;
    }



    /**
     * Gets the statement for a specified date range.
     * @async
     * @param {number} tglawal - Start date.
     * @param {number} blnawal - Start month.
     * @param {number} tglakhir - End date.
     * @param {number} blnakhir - End month.
     * @returns {Object} - Statement information. / if not set it will return latest statement
     */
    async getStatement(tglawal, blnawal, tglakhir, blnakhir) {
        this.dialogMessage = null;

        try {
            await this.loginToBCA();

            const newPage = await this.selectAccountAndSetDates(tglawal, blnawal, tglakhir, blnakhir);

            const result = await newPage.evaluate(() => document.body.innerHTML);
            let parser;
            if (result.includes("Account Number")) {
                parser = new BCAParser(result, BCASelectors.PARSING_FIELD_ENG);
            } else {
                parser = new BCAParser(result, BCASelectors.PARSING_FIELD);
            }
            let resultsettlement = parser.parse();
            this.log("[ LOG ] [" + this.user + "] Success get mutasi for (" + this.user + ")" + " Jumlah mutasi (" + resultsettlement.length + ")");
            const exists = await this.checkIfReturnToLogin(newPage, BCASelectors.LOGIN_PAGE.userField);

            if (exists) {
                throw new Error("Loopback detected");
            }

            await this.logoutAndClose();
            newPage.on("dialog", async dialog => {
                await dialog.accept();
                this.log("[ LOG ] [" + this.user + "] " + dialog.message());
                dialogMessage = dialog.message();
            });

            return resultsettlement;

        } catch (error) {
            this.log("[ LOG ] [" + this.user + "] " + error);
            await this.logoutAndClose();
            return {
                status: false,
                error: this.dialogMessage ?? error
            };
        }
    }

    /**
     * Logs out and closes the session.
     * @async
     */
    async logoutAndClose() {
        try {
            this.log("[ LOG ] [" + this.user + "] Logout..");
            await this.page.goto(BCASelectors.LOGOUT_PAGE.url, {
                waitUntil: "domcontentloaded"
            });
            await this.browser.close();
        } catch {
            return null;
        }
    }

    /**
     * Handles dialog and logs out the page.
     * @async
     * @param {Object} newPage - New page object.
     */

    async handleDialogAndLogout(newPage) {
        newPage.on('dialog', async (dialog) => {
            this.dialogMessage = dialog.message();
            this.log('Dialog message: ', this.dialogMessage);
            await dialog.dismiss();
            await this.logoutAndClose(newPage);
        });
    }

   /**
     * Checks if it returns to the login page.
     * @async
     * @param {Object} page - Page object.
     * @param {string} selector - Selector to check.
     * @returns {boolean} - Returns true if it returns to the login page.
     */
    async checkIfReturnToLogin(page, selector) {
        try {
            const element = await this.page.$(selector);
            return element ?? null;
        } catch (e) {
            this.log("[ LOG ] [" + this.user + "] " + e);
            return false;
        }
    }
}

module.exports = ScrapBCA;