v3 · 37 files

ProjectElly/pin/pin.js 7.4 KB Raw
import {
    isAcceptableNewAccessPin,
    isForbiddenAccessPin,
    isValidAccessPin,
    normalizePinDigits
} from '../lib/ellyAccessPin.js';

document.addEventListener('DOMContentLoaded', () => {
    let enteredPIN = '';
    let confirmPIN = '';
    let setupStep = 'enter'; // enter | confirm (setup mode only)
    const pinDisplay = document.getElementById('pinDisplay');
    const errorMessage = document.getElementById('errorMessage');
    const titleEl = document.querySelector('h1');
    const subtitleEl = document.querySelector('.subtitle');

    const params = new URLSearchParams(window.location.search);
    const isSetupMode = params.get('mode') === 'setup';

    if (isSetupMode) {
        if (titleEl) {
            titleEl.textContent = 'Set Access PIN';
        }
        if (subtitleEl) {
            subtitleEl.textContent = 'Choose an 8-digit Access PIN (not 12345678)';
        }
    }

    // Initialize PIN display
    updatePinDisplay();

    // Number buttons
    document.querySelectorAll('.pin-btn[data-digit]').forEach(btn => {
        btn.addEventListener('click', (e) => {
            const target = /** @type {HTMLElement} */ (e.currentTarget);
            const digit = target?.dataset?.digit;
            if (!digit) {
                return;
            }
            if (enteredPIN.length < 8) {
                enteredPIN += digit;
                updatePinDisplay();
                if (errorMessage) {
                    errorMessage.textContent = '';
                }
            }
        });
    });

    // Backspace button
    document.getElementById('btnBackspace')?.addEventListener('click', () => {
        if (enteredPIN.length > 0) {
            enteredPIN = enteredPIN.slice(0, -1);
            updatePinDisplay();
            if (errorMessage) {
                errorMessage.textContent = '';
            }
        }
    });

    // Clear button
    document.getElementById('btnClear')?.addEventListener('click', () => {
        enteredPIN = '';
        updatePinDisplay();
        if (errorMessage) {
            errorMessage.textContent = '';
        }
    });

    // Submit button
    document.getElementById('btnSubmit')?.addEventListener('click', () => {
        submitPin().catch((err) => console.error(err));
    });

    // Enter key support
    document.addEventListener('keydown', (e) => {
        if (e.key >= '0' && e.key <= '9' && enteredPIN.length < 8) {
            enteredPIN += e.key;
            updatePinDisplay();
            if (errorMessage) {
                errorMessage.textContent = '';
            }
        } else if (e.key === 'Backspace') {
            enteredPIN = enteredPIN.slice(0, -1);
            updatePinDisplay();
            if (errorMessage) {
                errorMessage.textContent = '';
            }
        } else if (e.key === 'Enter') {
            submitPin().catch((err) => console.error(err));
        }
    });

    /**
     * Renders the 8 PIN digit slots.
     * @returns {void}
     */
    function updatePinDisplay() {
        if (!pinDisplay) {
            return;
        }
        pinDisplay.innerHTML = '';
        for (let i = 0; i < 8; i++) {
            const digitDiv = document.createElement('div');
            digitDiv.className = 'pin-digit';
            digitDiv.textContent = enteredPIN[i] || '•';
            pinDisplay.appendChild(digitDiv);
        }
    }

    /**
     * Opens the main popup window after successful Access PIN auth.
     * @returns {Promise<void>}
     */
    async function openMainPopupThenClose() {
        try {
            await chrome.windows.create({
                url: chrome.runtime.getURL('popup/popup.html'),
                type: 'popup',
                width: 560,
                height: 640,
                focused: true
            });
        } catch (err) {
            console.error('Elly pin: open popup failed', err);
        }
        window.close();
    }

    /**
     * Handles submit for unlock or setup (two-step confirm).
     * @returns {Promise<void>}
     */
    async function submitPin() {
        if (!errorMessage) {
            return;
        }

        if (enteredPIN.length !== 8) {
            errorMessage.textContent = 'Please enter 8 digits';
            return;
        }

        const pin = normalizePinDigits(enteredPIN);

        if (isSetupMode) {
            if (setupStep === 'enter') {
                if (!isAcceptableNewAccessPin(pin)) {
                    errorMessage.textContent = isForbiddenAccessPin(pin)
                        ? 'That PIN is not allowed. Choose a different 8-digit PIN.'
                        : 'PIN must be 8 digits';
                    enteredPIN = '';
                    updatePinDisplay();
                    return;
                }
                confirmPIN = pin;
                enteredPIN = '';
                setupStep = 'confirm';
                if (subtitleEl) {
                    subtitleEl.textContent = 'Confirm your 8-digit Access PIN';
                }
                updatePinDisplay();
                return;
            }

            if (pin !== confirmPIN) {
                errorMessage.textContent = 'PINs do not match. Try again.';
                enteredPIN = '';
                confirmPIN = '';
                setupStep = 'enter';
                if (subtitleEl) {
                    subtitleEl.textContent = 'Choose an 8-digit Access PIN (not 12345678)';
                }
                updatePinDisplay();
                return;
            }

            try {
                const response = await chrome.runtime.sendMessage({
                    action: 'setNewPIN',
                    newPin: pin
                });
                if (!response?.success) {
                    errorMessage.textContent = response?.error || 'Could not save Access PIN';
                    enteredPIN = '';
                    updatePinDisplay();
                    return;
                }
                await openMainPopupThenClose();
            } catch (error) {
                errorMessage.textContent = 'Error saving PIN. Please try again.';
                console.error('PIN setup error:', error);
            }
            return;
        }

        // Unlock mode — always verify via background (never client-only compare)
        if (!isValidAccessPin(pin) || isForbiddenAccessPin(pin)) {
            errorMessage.textContent = 'Invalid Access PIN';
            enteredPIN = '';
            updatePinDisplay();
            return;
        }

        try {
            const response = await chrome.runtime.sendMessage({
                action: 'verifyAccessPin',
                pin
            });

            if (response?.success) {
                await openMainPopupThenClose();
            } else {
                errorMessage.textContent = response?.error || 'Incorrect PIN. Please try again.';
                enteredPIN = '';
                updatePinDisplay();

                errorMessage.style.animation = 'none';
                setTimeout(() => {
                    errorMessage.style.animation = 'shake 0.5s';
                }, 10);
            }
        } catch (error) {
            errorMessage.textContent = 'Error verifying PIN. Please try again.';
            console.error('PIN verification error:', error);
        }
    }
});