Source: functions/initializer.js

/**
* simplePass - A JavaScript password generator.
* Copyright (C) 2023  Jordan Vezina(staticBanter)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/
'use strict';
import config from "../simplePass.config.js";
import simplePass from "../simplePass.js";
import messageHandler from "./messageHandler.js";
/**
 * @file
 * @module initializer
 */
/**
 * Sets the ```innerText``` or ```value``` of an element.
 *
 * @function setInnerTextOrValue
 * @param {HTMLElement|HTMLInputElement} element The element that the string will be set in
 * @param {string} value The string to set inside of the element.
 * @returns {void}
 */
function setInnerTextOrValue(element, value) {
    if ((element instanceof HTMLInputElement)) {
        element.value = value;
    }
    else {
        element.innerText = value;
    }
}
/**
 * Gets the ``innerText``` or ```value``` of an element.
 *
 * @function getInnerTextOrValue
 * @param {HTMLInputElement|HTMLElement} element The element to get the string from.
 * @returns {string} The string inside of the element
 */
function getInnerTextOrValue(element) {
    if ((element instanceof HTMLInputElement)) {
        return element.value;
    }
    else {
        return element.innerText;
    }
}
/**
 * Injects the generated password and if necessary the corresponding password stats.
 *
 * @function injectSimplePass
 * @param {HTMLElement|HTMLInputElement} passwordTarget The element that the password will be injected into.
 * @param {string|strengthCheckedPassword} password The password or [Strength Check Password object]{@link module:strengthCheckedPassword} to inject into the password target.
 * @param {HTMLElement|HTMLInputElement|null} [entropyTarget] The element that the entropy calculation will be inserted into.
 * @requires setInnerTextOrValue
 * @requires injectStrengthStats
 * @returns {void}
 */
function injectSimplePass(passwordTarget, password, strengthChecks) {
    if (typeof (password) === 'string') {
        setInnerTextOrValue(passwordTarget, password);
    }
    else {
        setInnerTextOrValue(passwordTarget, password.password);
        if (strengthChecks) {
            injectStrengthStats(password, strengthChecks);
        }
    }
}
/**
 * Injects password strength stats into provided elements.
 *
 * @function injectStrengthStats
 * @param {strengthCheckedPassword} password The strength checked password object
 * @param {object} elements An object defining the HTML Elements to inject the stats into.
 * @requires setInnerTextOrValue
 */
function injectStrengthStats(password, elements) {
    Object.entries(elements).forEach(([key, element]) => {
        if (element) {
            key = key.replace('Target', '');
            if (password.compressionStats
                && Object.keys(password.compressionStats).includes(key)
                && password.compressionStats[key]) {
                setInnerTextOrValue(element, password.compressionStats[key]);
            }
            else if (Object.keys(password).includes(key)) {
                setInnerTextOrValue(element, password[key]);
            }
        }
    });
}
/**
 *
 * Initialization function used to automate the setup of inject passwords and registering events for various elements used by the program.
 * This function is currently also needed to preform batch password generation.
 *
 * @function initializer
 * @param cFig A [simplePass configuration]{@link config} object
 * @requires messageHandler
 * @requires injectSimplePass
 * @requires simplePass
 * @requires injectStrengthStats
 * @requires getInnerTextOrValue
 * @returns {void}
 */
export default function initializer(cFig = config) {
    /**
     * Ensure these default values are always present.
     */
    if (!cFig.defaultPasswordModifier) {
        cFig.defaultPasswordModifier = Object.assign(config.defaultPasswordModifier, cFig.defaultPasswordModifier);
    }
    let messageBoard = null;
    if (cFig.messages
        && cFig.messages.messageBoard) {
        messageBoard = document.body.querySelector(cFig.messages.messageBoard);
    }
    /**
     * These elements get used repeatedly throughout the function.
     * We will query for them here and initialize their variables.
     */
    const passwordTarget = document.body.querySelector(cFig.elements.passwordTarget);
    /**
     * If we have no target stop here.
     * We have nothing else to do.
     */
    if (!passwordTarget) {
        messageHandler(`ERROR.simplePass-I.1: Could not find a password target`, {
            htmlMessage: (messageBoard ? {
                messageBoard: messageBoard
            } :
                undefined),
            consoleMessage: true,
            level: "ERROR",
        }, cFig);
        /**
         * Because `messageHandler` might not throw an error
         * we have to explicitly declare a return here so we can bail out.
         * We need a password target to continue.
         */
        return;
    }
    let targetElements = {};
    if (cFig.strengthCheck
        && typeof (cFig.strengthCheck) === 'object') {
        function getTargetElements(strengthCheck) {
            Object.entries(strengthCheck).forEach(([propertyName, queryString]) => {
                if (typeof (queryString) === 'string') {
                    const target = document.body.querySelector(queryString);
                    if (target) {
                        targetElements[propertyName] = target;
                    }
                }
                else if (typeof (queryString) === 'object') {
                    getTargetElements(queryString);
                }
            });
        }
        getTargetElements(cFig.strengthCheck);
    }
    const password = simplePass(cFig.defaultPasswordModifier, cFig);
    /**
     * Inject our initial password
     */
    if (!(password instanceof Promise)) {
        injectSimplePass(passwordTarget, password, targetElements);
    }
    else {
        password.then((password) => {
            injectSimplePass(passwordTarget, password, targetElements);
        });
    }
    /**
     * If we have any action elements set,
     * we need to preform more work.
     */
    if (cFig.elements.actions) {
        /**
         * Generate Action Button
         */
        if (cFig.elements.actions.generate) {
            document.body.querySelector(cFig.elements.actions.generate)?.addEventListener('click', function () {
                /**
                 * If this is a submit button and it has been clicked.
                 * we do not need to generate another password so bail out here.
                 */
                if ((this instanceof HTMLButtonElement)
                    && this.form
                    && this.type === 'submit') {
                    return;
                }
                const password = simplePass(cFig.defaultPasswordModifier, cFig);
                if (!(password instanceof Promise)) {
                    injectSimplePass(passwordTarget, password, targetElements);
                }
                else {
                    password.then((password) => {
                        injectSimplePass(passwordTarget, password, targetElements);
                    });
                }
            });
        }
        /**
         * Password Modifier Form
         */
        if (cFig.elements.actions.form) {
            const passwordForm = document.body.querySelector(cFig.elements.actions.form);
            if (passwordForm) {
                passwordForm.addEventListener('submit', function (event) {
                    event.preventDefault();
                    const passwordModifiers = new FormData(this);
                    let batchAmount = (parseInt(passwordModifiers.get('passwordBatchAmount')?.toString() ?? '0') - 1);
                    const passwordContainer = document.body.querySelector(cFig.elements.passwordContainer ?? '.simplePass_passwordContainer');
                    if (!passwordContainer) {
                        messageHandler(`ERROR.simplePass-I.3: Use of batch password generation without a password container.`, {
                            htmlMessage: (messageBoard ? {
                                messageBoard: messageBoard
                            } :
                                undefined),
                            consoleMessage: true,
                            level: "ERROR",
                        }, cFig);
                        return;
                    }
                    if (batchAmount
                        && batchAmount > 0) {
                        const statIndex = document.body.querySelector('.simplePass_passwordStatsIndex');
                        const batchTotal = batchAmount + 1;
                        if (statIndex) {
                            statIndex.innerText = `(For Password: ${batchAmount + 1})`;
                        }
                        passwordContainer.innerHTML = '';
                        const OL = document.createElement('ol');
                        while (batchAmount--) {
                            const LI = document.createElement('li');
                            LI.dataset.simplePassIndex = (batchAmount + 1).toString();
                            Object.assign(LI.style, { cursor: "pointer" });
                            const password_LABEL = document.createElement('label');
                            password_LABEL.innerText = "Password:";
                            const password_INPUT = document.createElement('input');
                            password_INPUT.type = 'text';
                            password_INPUT.classList.add(cFig.elements.passwordTarget.substring(1));
                            password_INPUT.setAttribute('readonly', 'readonly');
                            password_LABEL.appendChild(password_INPUT);
                            LI.appendChild(password_LABEL);
                            OL.prepend(LI);
                            const password = simplePass(passwordModifiers);
                            if (!(password instanceof Promise)) {
                                injectSimplePass(password_INPUT, password);
                            }
                            else {
                                password.then((password) => {
                                    injectSimplePass(password_INPUT, password);
                                });
                            }
                            if (typeof (password) !== 'string') {
                                LI.addEventListener('click', function () {
                                    if (!(password instanceof Promise)) {
                                        injectStrengthStats(password, targetElements);
                                    }
                                    else {
                                        password.then((password) => {
                                            injectStrengthStats(password, targetElements);
                                        });
                                    }
                                    if (statIndex) {
                                        statIndex.innerText = `(For Password: ${LI.dataset.simplePassIndex})`;
                                    }
                                });
                            }
                        }
                        const LI = document.createElement('li');
                        LI.dataset.simplePassIndex = batchTotal.toString();
                        const password_LABEL = document.createElement('label');
                        password_LABEL.innerText = "Password:";
                        const password_INPUT = document.createElement('input');
                        password_INPUT.type = 'text';
                        password_INPUT.classList.add(cFig.elements.passwordTarget.substring(1));
                        password_INPUT.setAttribute('readonly', 'readonly');
                        password_LABEL.appendChild(password_INPUT);
                        LI.appendChild(password_LABEL);
                        OL.appendChild(LI);
                        const password = simplePass(passwordModifiers);
                        if (!(password instanceof Promise)) {
                            injectSimplePass(password_INPUT, password, targetElements);
                        }
                        else {
                            password.then((password) => {
                                injectSimplePass(password_INPUT, password, targetElements);
                            });
                        }
                        passwordContainer.prepend(OL);
                        if (typeof (password) !== 'string') {
                            LI.addEventListener('click', function () {
                                if (!(password instanceof Promise)) {
                                    injectStrengthStats(password, targetElements);
                                }
                                else {
                                    password.then((password) => {
                                        injectStrengthStats(password, targetElements);
                                    });
                                }
                                if (statIndex) {
                                    statIndex.innerText = `(For Password: ${LI.dataset.simplePassIndex})`;
                                }
                            });
                        }
                    }
                    else if (passwordContainer.querySelector('ol')) {
                        passwordContainer.innerHTML = '';
                        const password_LABEL = document.createElement('label');
                        password_LABEL.innerText = "Password:";
                        const password_INPUT = document.createElement('input');
                        password_INPUT.classList.add(cFig.elements.passwordTarget.substring(1));
                        password_INPUT.type = 'text';
                        password_INPUT.setAttribute('readonly', 'readonly');
                        password_LABEL.appendChild(password_INPUT);
                        passwordContainer.appendChild(password_LABEL);
                        const password = simplePass(passwordModifiers, cFig);
                        if (!(password instanceof Promise)) {
                            injectSimplePass(password_INPUT, password, targetElements);
                        }
                        else {
                            password.then((password) => {
                                injectSimplePass(password_INPUT, password, targetElements);
                            });
                        }
                    }
                    else {
                        const passwordTarget = document.body.querySelector(cFig.elements.passwordTarget);
                        if (!passwordTarget) {
                            messageHandler(`ERROR.simplePass-I.2: Could not find a password target`, {
                                htmlMessage: (messageBoard ? {
                                    messageBoard: messageBoard
                                } :
                                    undefined),
                                consoleMessage: true,
                                level: "ERROR",
                            }, cFig);
                            return;
                        }
                        const password = simplePass(passwordModifiers, cFig);
                        if (!(password instanceof Promise)) {
                            injectSimplePass(passwordTarget, password, targetElements);
                        }
                        else {
                            password.then((password) => {
                                injectSimplePass(passwordTarget, password, targetElements);
                            });
                        }
                    }
                });
                if (cFig.defaultPasswordModifier) {
                    for (const [key, value] of Object.entries(cFig.defaultPasswordModifier)) {
                        const input = passwordForm.querySelector(`[name=${key}]`);
                        if (input
                            && value) {
                            if (input.type === 'checkbox'
                                || input.type === 'radio') {
                                input.checked = true;
                            }
                            else {
                                input.value = value.toString();
                            }
                        }
                    }
                }
                /**
                * Event listeners for "Radio Checkboxes".
                * Just loops through all the checkboxes and unchecks and box that does not have the same value as the
                * currently clicked box.
                */
                const radioCheckboxes = passwordForm.querySelectorAll('input[data-radioCheckbox]');
                radioCheckboxes.forEach((input) => {
                    input.addEventListener('click', function () {
                        radioCheckboxes.forEach((input) => {
                            if (input.value !== this.value) {
                                if (input.checked) {
                                    input.checked = false;
                                }
                            }
                        });
                    });
                });
            }
        }
        /**
         * Copy Action Button
         */
        if (cFig.elements.actions.copy) {
            document.body.querySelector(cFig.elements.actions.copy)?.addEventListener(('click'), function () {
                let copiedPassword = '';
                // Why are we not finding our new elements?
                document.body.querySelectorAll(cFig.elements.passwordTarget).forEach((password, passwordIndex) => {
                    if (!passwordIndex) {
                        copiedPassword += `${getInnerTextOrValue(password)}`;
                        return;
                    }
                    copiedPassword += `\n${getInnerTextOrValue(password)}`;
                });
                navigator.clipboard.writeText(copiedPassword).then(() => {
                    this.style.backgroundColor = `green`;
                    this.innerText = 'Copied!';
                    setTimeout(() => {
                        this.toggleAttribute('style');
                        this.innerText = 'Copy';
                    }, 5000);
                })
                    .catch((error) => {
                    messageHandler(`ERROR.simplePass-Ic.1: Unable to copy to clipboard! Environment Error: ${error.message}.`, {
                        htmlMessage: (messageBoard ? {
                            messageBoard: messageBoard
                        } :
                            undefined),
                        consoleMessage: true,
                        level: "ERROR",
                    }, cFig);
                });
            });
        }
    }
}