Greasy Fork is available in English.

HKJC eWin: Password Manager Compatible Login Form

Modifies the login form in HKJC eWin so that it can be auto-filled by a password manager.

// ==UserScript==
// @name               HKJC eWin: Password Manager Compatible Login Form
// @name:zh-TW         香港賽馬會 eWin:兼容密碼管理器的登入表單
// @description        Modifies the login form in HKJC eWin so that it can be auto-filled by a password manager.
// @description:zh-TW  修改香港賽馬會 eWin 的登入表單以致能夠使用密碼管理器的自動填入功能。
// @icon               https://icons.duckduckgo.com/ip3/bet.hkjc.com.ico
// @author             Jason Kwok
// @namespace          https://jasonhk.dev/
// @version            1.0.0
// @license            MIT
// @match              https://bet.hkjc.com/*
// @run-at             document-end
// @grant              none
// @require            https://unpkg.com/uuid-random@1.3.2/uuid-random.min.js
// @supportURL         https://greasyfork.org/scripts/491438/feedback
// ==/UserScript==

const EVENT_KEY = uuid();


const PageScript = ({ EVENT_KEY }) =>
{
    const usernameField = document.getElementById("account");

    const interval = setInterval(() =>
    {
        if (usernameField.value !== "")
        {
            clearInterval(interval);
            window.dispatchEvent(new CustomEvent(`${EVENT_KEY}:ready`));
        }
    }, 100);

    window.addEventListener(`${EVENT_KEY}:getLocalizedText`, ({ detail }) =>
    {
        const { RETURN_KEY, name } = JSON.parse(detail);
        window.dispatchEvent(new CustomEvent(RETURN_KEY, { detail: JSON.stringify(GetText(name)) }));
    });
};

const scriptWrapper = document.createElement("div");
scriptWrapper.style.display = "none";
const shadowRoot = scriptWrapper.attachShadow({ mode: "closed" });
const script = document.createElement("script");
script.textContent = `(${PageScript})(${JSON.stringify({ EVENT_KEY })}); //# sourceURL=userscript://${encodeURI(GM.info.script.name)}/Page.js`;
shadowRoot.append(script);
(document.body ?? document.head ?? document.documentElement).append(scriptWrapper);


window.addEventListener(`${EVENT_KEY}:ready`, async () =>
{
    const usernameField = document.getElementById("account");
    usernameField.autocomplete = "username";
    usernameField.placeholder = await getLocalizedText("fld_account_no");
    if (usernameField.value === usernameField.placeholder) { usernameField.value = ""; }
    usernameField.addEventListener("blur", () =>
    {
        setTimeout(() =>
        {
            if (usernameField.value === usernameField.placeholder) { usernameField.value = ""; }
        }, 0);
    });

    const fakePasswordField = document.getElementById("passwordInput1");
    fakePasswordField.style.display = "none";

    const realPasswordField = document.getElementById("password");
    realPasswordField.placeholder = await getLocalizedText("fld_password");
    realPasswordField.style.display = "";
    realPasswordField.addEventListener("blur", () =>
    {
        realPasswordField.style.display = "";
        fakePasswordField.style.display = "none";
    });
}, { once: true });


const observer = new MutationObserver((records) =>
{
    for (const record of records)
    {
        if (record.oldValue === "display: none;")
        {
            const usernameField = document.getElementById("account");
            if (usernameField.value === usernameField.placeholder) { usernameField.value = ""; }

            const fakePasswordField = document.getElementById("passwordInput1");
            fakePasswordField.style.display = "none";

            const realPasswordField = document.getElementById("password");
            realPasswordField.style.display = "";
        }
    }
});

observer.observe(document.getElementById("divAccInfoLogout"), { attributes: true, attributeFilter: ["style"], attributeOldValue: true });


function getLocalizedText(name)
{
    return new Promise((resolve) =>
    {
        const RETURN_KEY = uuid();

        window.addEventListener(RETURN_KEY, ({ detail: text }) => resolve(JSON.parse(text)), { once: true });
        window.dispatchEvent(new CustomEvent(`${EVENT_KEY}:getLocalizedText`, { detail: JSON.stringify({ RETURN_KEY, name }) }));
    });
}