chunk-048-security-ui.js

I’ll build this as a complete advanced chunk-048-security-ui.js file for RX Theme: safe UI protections, copy/link/form hardening, external-link handling, bot-trap helpers, and WordPress-friendly initialization.

Yes friend — create this file:

/assets/static-js/static-chunks-js/chunk-048-security-ui.js

This file is frontend security UI helper only. Real security must also be done from WordPress/PHP/server side. But this JS will help RX Theme with safe external links, copy protection options, form protection, spam traps, iframe defense UI, suspicious URL warning, and secure UX.

/*!
 * RX Theme - Chunk 048 Security UI
 * File: /assets/static-js/static-chunks-js/chunk-048-security-ui.js
 * Version: 1.0.0
 * Author: RxHarun
 *
 * Purpose:
 * Frontend security and safety UI helpers for RX Theme.
 *
 * Important:
 * This file improves user interface security behavior.
 * It does NOT replace real server-side WordPress security.
 */

(function () {
  "use strict";

  /**
   * ============================================================
   * RX SECURITY UI CONFIG
   * ============================================================
   */

  const RXSecurityUI = {
    version: "1.0.0",

    config: {
      debug: false,

      selectors: {
        root: "html",
        body: "body",
        links: "a[href]",
        forms: "form",
        searchForms: 'form[role="search"], form.search-form',
        commentsForm: "#commentform",
        protectedContent: "[data-rx-protected-content]",
        noCopy: "[data-rx-no-copy]",
        safeCopy: "[data-rx-safe-copy]",
        externalWarning: "[data-rx-external-warning]",
        csrfField: 'input[name="_wpnonce"], input[name="rx_nonce"]',
        honeypotField: ".rx-honeypot, [data-rx-honeypot]",
        passwordFields: 'input[type="password"]',
        emailFields: 'input[type="email"]',
        urlFields: 'input[type="url"]',
        telFields: 'input[type="tel"]',
        fileFields: 'input[type="file"]',
        submitButtons: 'button[type="submit"], input[type="submit"]'
      },

      features: {
        secureExternalLinks: true,
        externalLinkWarning: true,
        suspiciousLinkDetection: true,
        preventReverseTabnabbing: true,
        formSpamProtection: true,
        formDoubleSubmitProtection: true,
        formInputSanitizeUI: true,
        passwordVisibilityToggle: true,
        passwordStrengthUI: true,
        copyProtection: true,
        safeCopyAttribution: true,
        rightClickProtection: false,
        devToolsWarning: false,
        iframeBreakoutUI: true,
        referrerPolicyMeta: true,
        mixedContentWarning: true,
        fileUploadGuard: true,
        emailObfuscationDecode: true,
        keyboardProtection: false,
        printWatermark: true,
        sessionIdleWarning: false
      },

      externalLinks: {
        addTargetBlank: true,
        allowedInternalHosts: [],
        allowedProtocols: ["http:", "https:", "mailto:", "tel:"],
        blockedProtocols: ["javascript:", "data:", "vbscript:", "file:"],
        suspiciousWords: [
          "login",
          "verify",
          "wallet",
          "bonus",
          "free-money",
          "claim",
          "airdrop",
          "password-reset",
          "account-security",
          "wp-admin",
          "wp-login"
        ],
        suspiciousTlds: [
          ".zip",
          ".mov",
          ".click",
          ".top",
          ".xyz",
          ".loan",
          ".work"
        ]
      },

      forms: {
        minSubmitTimeMs: 1200,
        doubleSubmitLockMs: 6000,
        maxTextLength: 10000,
        maxSearchLength: 120,
        maxUrlLength: 2048,
        blockedInputPatterns: [
          /<script[\s\S]*?>[\s\S]*?<\/script>/gi,
          /javascript:/gi,
          /vbscript:/gi,
          /onerror\s*=/gi,
          /onload\s*=/gi,
          /onclick\s*=/gi,
          /<iframe[\s\S]*?>/gi,
          /<object[\s\S]*?>/gi,
          /<embed[\s\S]*?>/gi
        ],
        suspiciousCommentPatterns: [
          /\[url=/gi,
          /casino/gi,
          /crypto\s*bonus/gi,
          /buy\s*followers/gi,
          /free\s*traffic/gi,
          /viagra/gi
        ]
      },

      files: {
        maxFileSizeMB: 8,
        allowedExtensions: [
          "jpg",
          "jpeg",
          "png",
          "webp",
          "gif",
          "pdf",
          "doc",
          "docx"
        ],
        dangerousExtensions: [
          "exe",
          "bat",
          "cmd",
          "com",
          "scr",
          "pif",
          "js",
          "vbs",
          "jar",
          "php",
          "phtml",
          "sh",
          "py",
          "pl",
          "rb",
          "msi",
          "dll",
          "hta",
          "svg"
        ]
      },

      messages: {
        externalTitle: "External Link Warning",
        externalText:
          "You are leaving this website. Please make sure the destination is safe before continuing.",
        suspiciousTitle: "Suspicious Link Detected",
        suspiciousText:
          "This link looks unusual. For your safety, check the address carefully before opening.",
        blockedProtocol:
          "This link was blocked because it uses an unsafe protocol.",
        formTooFast:
          "Please wait a moment before submitting. This helps us prevent spam.",
        formBlocked:
          "Your form contains unsafe or suspicious content. Please review and try again.",
        doubleSubmit:
          "Please wait. Your form is already being submitted.",
        fileBlocked:
          "This file type is not allowed for security reasons.",
        fileTooLarge:
          "The selected file is too large.",
        copied:
          "Copied safely.",
        copyBlocked:
          "Copying is disabled for this content.",
        passwordWeak:
          "Weak password",
        passwordMedium:
          "Medium password",
        passwordStrong:
          "Strong password"
      }
    },

    state: {
      initialized: false,
      formStartTimes: new WeakMap(),
      formLocks: new WeakMap(),
      modalOpen: false,
      idleTimer: null,
      lastActivityTime: Date.now()
    },

    /**
     * ============================================================
     * UTILITIES
     * ============================================================
     */

    log(...args) {
      if (this.config.debug) {
        console.log("[RXSecurityUI]", ...args);
      }
    },

    warn(...args) {
      if (this.config.debug) {
        console.warn("[RXSecurityUI]", ...args);
      }
    },

    qs(selector, context = document) {
      return context.querySelector(selector);
    },

    qsa(selector, context = document) {
      return Array.prototype.slice.call(context.querySelectorAll(selector));
    },

    isElement(node) {
      return node && node.nodeType === 1;
    },

    ready(callback) {
      if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", callback, {
          once: true
        });
      } else {
        callback();
      }
    },

    debounce(fn, delay) {
      let timer = null;

      return function (...args) {
        clearTimeout(timer);
        timer = setTimeout(() => {
          fn.apply(this, args);
        }, delay);
      };
    },

    throttle(fn, wait) {
      let last = 0;
      let timer = null;

      return function (...args) {
        const now = Date.now();
        const remaining = wait - (now - last);

        if (remaining <= 0) {
          clearTimeout(timer);
          timer = null;
          last = now;
          fn.apply(this, args);
        } else if (!timer) {
          timer = setTimeout(() => {
            last = Date.now();
            timer = null;
            fn.apply(this, args);
          }, remaining);
        }
      };
    },

    escapeHTML(value) {
      const div = document.createElement("div");
      div.textContent = String(value || "");
      return div.innerHTML;
    },

    normalizeText(value) {
      return String(value || "")
        .replace(/\u0000/g, "")
        .replace(/[\u200B-\u200D\uFEFF]/g, "")
        .trim();
    },

    removeUnsafeHTML(value) {
      let clean = String(value || "");

      this.config.forms.blockedInputPatterns.forEach((pattern) => {
        clean = clean.replace(pattern, "");
      });

      return clean;
    },

    hasUnsafePattern(value) {
      const text = String(value || "");

      return this.config.forms.blockedInputPatterns.some((pattern) => {
        pattern.lastIndex = 0;
        return pattern.test(text);
      });
    },

    hasSuspiciousCommentPattern(value) {
      const text = String(value || "");

      return this.config.forms.suspiciousCommentPatterns.some((pattern) => {
        pattern.lastIndex = 0;
        return pattern.test(text);
      });
    },

    createEl(tag, className, text) {
      const el = document.createElement(tag);

      if (className) {
        el.className = className;
      }

      if (text) {
        el.textContent = text;
      }

      return el;
    },

    addBodyClass(className) {
      if (document.body) {
        document.body.classList.add(className);
      }
    },

    removeBodyClass(className) {
      if (document.body) {
        document.body.classList.remove(className);
      }
    },

    getSiteHost() {
      return window.location.hostname.toLowerCase();
    },

    getURL(value) {
      try {
        return new URL(value, window.location.href);
      } catch (error) {
        return null;
      }
    },

    isSameHost(url) {
      if (!url) {
        return false;
      }

      const host = url.hostname.toLowerCase();
      const currentHost = this.getSiteHost();
      const allowed = this.config.externalLinks.allowedInternalHosts.map((h) =>
        String(h).toLowerCase()
      );

      return host === currentHost || allowed.includes(host);
    },

    isExternalURL(url) {
      if (!url) {
        return false;
      }

      if (url.protocol === "mailto:" || url.protocol === "tel:") {
        return false;
      }

      return !this.isSameHost(url);
    },

    isBlockedProtocol(url) {
      if (!url) {
        return true;
      }

      const protocol = url.protocol.toLowerCase();

      if (this.config.externalLinks.blockedProtocols.includes(protocol)) {
        return true;
      }

      return !this.config.externalLinks.allowedProtocols.includes(protocol);
    },

    isSuspiciousURL(url) {
      if (!url) {
        return true;
      }

      const href = url.href.toLowerCase();
      const host = url.hostname.toLowerCase();

      const hasSuspiciousWord =
        this.config.externalLinks.suspiciousWords.some((word) =>
          href.includes(String(word).toLowerCase())
        );

      const hasSuspiciousTld =
        this.config.externalLinks.suspiciousTlds.some((tld) =>
          host.endsWith(String(tld).toLowerCase())
        );

      const hasPunycode = host.includes("xn--");

      const hasManySubdomains = host.split(".").length >= 5;

      const hasEncodedTricks =
        href.includes("%00") ||
        href.includes("%2f%2f") ||
        href.includes("@") ||
        href.includes("\\") ||
        href.includes("..");

      return (
        hasSuspiciousWord ||
        hasSuspiciousTld ||
        hasPunycode ||
        hasManySubdomains ||
        hasEncodedTricks
      );
    },

    /**
     * ============================================================
     * TOAST SYSTEM
     * ============================================================
     */

    ensureToastContainer() {
      let container = this.qs("#rx-security-toast-container");

      if (!container) {
        container = this.createEl("div", "rx-security-toast-container");
        container.id = "rx-security-toast-container";
        container.setAttribute("aria-live", "polite");
        container.setAttribute("aria-atomic", "true");
        document.body.appendChild(container);
      }

      return container;
    },

    toast(message, type = "info", timeout = 3500) {
      if (!document.body) {
        return;
      }

      const container = this.ensureToastContainer();
      const toast = this.createEl(
        "div",
        `rx-security-toast rx-security-toast-${type}`
      );

      toast.textContent = message;
      container.appendChild(toast);

      requestAnimationFrame(() => {
        toast.classList.add("is-visible");
      });

      setTimeout(() => {
        toast.classList.remove("is-visible");

        setTimeout(() => {
          if (toast.parentNode) {
            toast.parentNode.removeChild(toast);
          }
        }, 250);
      }, timeout);
    },

    /**
     * ============================================================
     * MODAL WARNING SYSTEM
     * ============================================================
     */

    openConfirmModal(options) {
      const title = options.title || "Warning";
      const text = options.text || "";
      const url = options.url || "";
      const onConfirm = options.onConfirm || function () {};

      if (this.state.modalOpen) {
        return;
      }

      this.state.modalOpen = true;

      const overlay = this.createEl("div", "rx-security-modal-overlay");
      overlay.setAttribute("role", "dialog");
      overlay.setAttribute("aria-modal", "true");
      overlay.setAttribute("aria-labelledby", "rx-security-modal-title");

      const modal = this.createEl("div", "rx-security-modal");

      const heading = this.createEl("h2", "rx-security-modal-title", title);
      heading.id = "rx-security-modal-title";

      const paragraph = this.createEl("p", "rx-security-modal-text", text);

      const urlBox = this.createEl("div", "rx-security-modal-url");
      urlBox.textContent = url;

      const actions = this.createEl("div", "rx-security-modal-actions");

      const cancel = this.createEl(
        "button",
        "rx-security-btn rx-security-btn-secondary",
        "Cancel"
      );
      cancel.type = "button";

      const confirm = this.createEl(
        "button",
        "rx-security-btn rx-security-btn-primary",
        "Continue"
      );
      confirm.type = "button";

      actions.appendChild(cancel);
      actions.appendChild(confirm);

      modal.appendChild(heading);
      modal.appendChild(paragraph);

      if (url) {
        modal.appendChild(urlBox);
      }

      modal.appendChild(actions);
      overlay.appendChild(modal);
      document.body.appendChild(overlay);

      const close = () => {
        this.state.modalOpen = false;
        overlay.classList.remove("is-visible");

        setTimeout(() => {
          if (overlay.parentNode) {
            overlay.parentNode.removeChild(overlay);
          }
        }, 200);
      };

      cancel.addEventListener("click", close);

      confirm.addEventListener("click", () => {
        close();
        onConfirm();
      });

      overlay.addEventListener("click", (event) => {
        if (event.target === overlay) {
          close();
        }
      });

      document.addEventListener(
        "keydown",
        (event) => {
          if (event.key === "Escape") {
            close();
          }
        },
        { once: true }
      );

      requestAnimationFrame(() => {
        overlay.classList.add("is-visible");
        cancel.focus();
      });
    },

    /**
     * ============================================================
     * EXTERNAL LINK SECURITY
     * ============================================================
     */

    secureSingleLink(link) {
      if (!link || link.dataset.rxSecurityLinkDone === "1") {
        return;
      }

      const rawHref = link.getAttribute("href");

      if (!rawHref || rawHref.startsWith("#")) {
        return;
      }

      const url = this.getURL(rawHref);

      if (!url) {
        link.dataset.rxSecurityInvalid = "1";
        link.setAttribute("aria-disabled", "true");
        return;
      }

      if (this.isBlockedProtocol(url)) {
        link.dataset.rxSecurityBlocked = "1";
        link.setAttribute("aria-disabled", "true");
        link.addEventListener("click", (event) => {
          event.preventDefault();
          this.toast(this.config.messages.blockedProtocol, "error");
        });
        return;
      }

      const external = this.isExternalURL(url);
      const suspicious = this.isSuspiciousURL(url);

      if (external) {
        link.dataset.rxExternalLink = "1";
        link.classList.add("rx-external-link");

        if (this.config.externalLinks.addTargetBlank) {
          link.setAttribute("target", "_blank");
        }

        if (this.config.features.preventReverseTabnabbing) {
          const rel = new Set(
            String(link.getAttribute("rel") || "")
              .split(/\s+/)
              .filter(Boolean)
          );

          rel.add("noopener");
          rel.add("noreferrer");
          rel.add("external");

          link.setAttribute("rel", Array.from(rel).join(" "));
        }

        if (!link.getAttribute("aria-label")) {
          link.setAttribute(
            "aria-label",
            `${link.textContent.trim() || "External link"} opens in a new tab`
          );
        }
      }

      if (suspicious) {
        link.dataset.rxSuspiciousLink = "1";
        link.classList.add("rx-suspicious-link");
      }

      if (
        this.config.features.externalLinkWarning &&
        external &&
        !link.hasAttribute("data-rx-no-warning")
      ) {
        link.addEventListener("click", (event) => {
          const isModifiedClick =
            event.metaKey ||
            event.ctrlKey ||
            event.shiftKey ||
            event.altKey ||
            event.button !== 0;

          if (isModifiedClick) {
            return;
          }

          event.preventDefault();

          this.openConfirmModal({
            title: suspicious
              ? this.config.messages.suspiciousTitle
              : this.config.messages.externalTitle,
            text: suspicious
              ? this.config.messages.suspiciousText
              : this.config.messages.externalText,
            url: url.href,
            onConfirm: () => {
              window.open(url.href, "_blank", "noopener,noreferrer");
            }
          });
        });
      }

      link.dataset.rxSecurityLinkDone = "1";
    },

    secureExternalLinks(context = document) {
      if (!this.config.features.secureExternalLinks) {
        return;
      }

      this.qsa(this.config.selectors.links, context).forEach((link) => {
        this.secureSingleLink(link);
      });
    },

    /**
     * ============================================================
     * FORM SECURITY UI
     * ============================================================
     */

    initForms(context = document) {
      if (!this.config.features.formSpamProtection) {
        return;
      }

      this.qsa(this.config.selectors.forms, context).forEach((form) => {
        this.initSingleForm(form);
      });
    },

    initSingleForm(form) {
      if (!form || form.dataset.rxSecurityFormDone === "1") {
        return;
      }

      form.dataset.rxSecurityFormDone = "1";
      form.dataset.rxSecurityStarted = String(Date.now());

      this.state.formStartTimes.set(form, Date.now());

      this.addHoneypot(form);
      this.addTimestampField(form);
      this.bindFormInputSanitizer(form);
      this.bindFileGuard(form);

      form.addEventListener("submit", (event) => {
        const result = this.validateFormBeforeSubmit(form);

        if (!result.valid) {
          event.preventDefault();
          this.toast(result.message, "error");
          return;
        }

        if (!this.lockFormSubmit(form)) {
          event.preventDefault();
          this.toast(this.config.messages.doubleSubmit, "warning");
          return;
        }
      });
    },

    addHoneypot(form) {
      if (form.querySelector('[name="rx_hp_field"]')) {
        return;
      }

      const wrapper = this.createEl("div", "rx-security-hp-wrap");
      wrapper.setAttribute("aria-hidden", "true");

      const label = this.createEl("label", "", "Leave this field empty");
      const input = document.createElement("input");

      input.type = "text";
      input.name = "rx_hp_field";
      input.tabIndex = -1;
      input.autocomplete = "off";
      input.className = "rx-honeypot";
      input.setAttribute("data-rx-honeypot", "1");

      wrapper.appendChild(label);
      wrapper.appendChild(input);
      form.appendChild(wrapper);
    },

    addTimestampField(form) {
      if (form.querySelector('[name="rx_form_started_at"]')) {
        return;
      }

      const input = document.createElement("input");
      input.type = "hidden";
      input.name = "rx_form_started_at";
      input.value = String(Date.now());
      form.appendChild(input);
    },

    bindFormInputSanitizer(form) {
      if (!this.config.features.formInputSanitizeUI) {
        return;
      }

      const fields = this.qsa("input, textarea", form);

      fields.forEach((field) => {
        if (
          field.type === "password" ||
          field.type === "file" ||
          field.type === "hidden" ||
          field.type === "checkbox" ||
          field.type === "radio"
        ) {
          return;
        }

        field.addEventListener(
          "input",
          this.debounce(() => {
            this.sanitizeFieldUI(field, form);
          }, 250)
        );
      });
    },

    sanitizeFieldUI(field, form) {
      const type = String(field.type || "").toLowerCase();
      const value = field.value || "";

      let maxLength = this.config.forms.maxTextLength;

      if (type === "search" || form.matches(this.config.selectors.searchForms)) {
        maxLength = this.config.forms.maxSearchLength;
      }

      if (type === "url") {
        maxLength = this.config.forms.maxUrlLength;
      }

      if (value.length > maxLength) {
        field.value = value.slice(0, maxLength);
        this.markFieldWarning(field, `Maximum ${maxLength} characters allowed.`);
      }

      if (this.hasUnsafePattern(field.value)) {
        field.value = this.removeUnsafeHTML(field.value);
        this.markFieldWarning(field, "Unsafe code was removed.");
      }
    },

    markFieldWarning(field, message) {
      field.classList.add("rx-field-warning");
      field.setAttribute("aria-invalid", "true");

      let help = field.parentNode
        ? field.parentNode.querySelector(".rx-field-security-help")
        : null;

      if (!help && field.parentNode) {
        help = this.createEl("small", "rx-field-security-help");
        field.parentNode.appendChild(help);
      }

      if (help) {
        help.textContent = message;
      }

      setTimeout(() => {
        field.classList.remove("rx-field-warning");
      }, 3000);
    },

    validateFormBeforeSubmit(form) {
      const startedAt = this.state.formStartTimes.get(form) || Date.now();
      const elapsed = Date.now() - startedAt;

      if (elapsed < this.config.forms.minSubmitTimeMs) {
        return {
          valid: false,
          message: this.config.messages.formTooFast
        };
      }

      const honeypot = form.querySelector('[name="rx_hp_field"]');

      if (honeypot && honeypot.value.trim() !== "") {
        return {
          valid: false,
          message: this.config.messages.formBlocked
        };
      }

      const fields = this.qsa("input, textarea", form);

      for (const field of fields) {
        if (
          field.type === "password" ||
          field.type === "file" ||
          field.type === "hidden" ||
          field.type === "checkbox" ||
          field.type === "radio"
        ) {
          continue;
        }

        const value = field.value || "";

        if (this.hasUnsafePattern(value)) {
          return {
            valid: false,
            message: this.config.messages.formBlocked
          };
        }

        if (
          form.matches(this.config.selectors.commentsForm) &&
          this.hasSuspiciousCommentPattern(value)
        ) {
          return {
            valid: false,
            message: this.config.messages.formBlocked
          };
        }
      }

      return {
        valid: true,
        message: ""
      };
    },

    lockFormSubmit(form) {
      const lockedUntil = this.state.formLocks.get(form);

      if (lockedUntil && lockedUntil > Date.now()) {
        return false;
      }

      this.state.formLocks.set(
        form,
        Date.now() + this.config.forms.doubleSubmitLockMs
      );

      const buttons = this.qsa(this.config.selectors.submitButtons, form);

      buttons.forEach((button) => {
        button.dataset.rxOriginalText = button.value || button.textContent || "";
        button.disabled = true;

        if (button.tagName.toLowerCase() === "input") {
          button.value = "Submitting...";
        } else {
          button.textContent = "Submitting...";
        }
      });

      setTimeout(() => {
        buttons.forEach((button) => {
          button.disabled = false;

          const original = button.dataset.rxOriginalText || "";

          if (button.tagName.toLowerCase() === "input") {
            button.value = original;
          } else {
            button.textContent = original;
          }
        });
      }, this.config.forms.doubleSubmitLockMs);

      return true;
    },

    /**
     * ============================================================
     * FILE UPLOAD GUARD
     * ============================================================
     */

    bindFileGuard(form) {
      if (!this.config.features.fileUploadGuard) {
        return;
      }

      this.qsa(this.config.selectors.fileFields, form).forEach((input) => {
        if (input.dataset.rxFileGuardDone === "1") {
          return;
        }

        input.dataset.rxFileGuardDone = "1";

        input.addEventListener("change", () => {
          this.validateFileInput(input);
        });
      });
    },

    validateFileInput(input) {
      const files = Array.prototype.slice.call(input.files || []);

      for (const file of files) {
        const name = String(file.name || "");
        const ext = name.includes(".")
          ? name.split(".").pop().toLowerCase()
          : "";

        const sizeMB = file.size / 1024 / 1024;

        if (this.config.files.dangerousExtensions.includes(ext)) {
          input.value = "";
          this.toast(this.config.messages.fileBlocked, "error");
          return false;
        }

        if (
          this.config.files.allowedExtensions.length &&
          !this.config.files.allowedExtensions.includes(ext)
        ) {
          input.value = "";
          this.toast(this.config.messages.fileBlocked, "error");
          return false;
        }

        if (sizeMB > this.config.files.maxFileSizeMB) {
          input.value = "";
          this.toast(this.config.messages.fileTooLarge, "error");
          return false;
        }
      }

      return true;
    },

    /**
     * ============================================================
     * PASSWORD UI
     * ============================================================
     */

    initPasswordTools(context = document) {
      this.qsa(this.config.selectors.passwordFields, context).forEach((field) => {
        this.addPasswordVisibilityToggle(field);
        this.addPasswordStrengthMeter(field);
      });
    },

    addPasswordVisibilityToggle(field) {
      if (!this.config.features.passwordVisibilityToggle) {
        return;
      }

      if (field.dataset.rxPasswordToggleDone === "1") {
        return;
      }

      field.dataset.rxPasswordToggleDone = "1";

      const wrapper = this.createEl("span", "rx-password-wrap");
      field.parentNode.insertBefore(wrapper, field);
      wrapper.appendChild(field);

      const button = this.createEl(
        "button",
        "rx-password-toggle",
        "Show"
      );

      button.type = "button";
      button.setAttribute("aria-label", "Show password");

      wrapper.appendChild(button);

      button.addEventListener("click", () => {
        const hidden = field.type === "password";
        field.type = hidden ? "text" : "password";
        button.textContent = hidden ? "Hide" : "Show";
        button.setAttribute(
          "aria-label",
          hidden ? "Hide password" : "Show password"
        );
      });
    },

    addPasswordStrengthMeter(field) {
      if (!this.config.features.passwordStrengthUI) {
        return;
      }

      if (field.dataset.rxPasswordStrengthDone === "1") {
        return;
      }

      field.dataset.rxPasswordStrengthDone = "1";

      const meter = this.createEl("div", "rx-password-strength");
      const bar = this.createEl("span", "rx-password-strength-bar");
      const text = this.createEl("small", "rx-password-strength-text");

      meter.appendChild(bar);
      meter.appendChild(text);

      const parent = field.closest(".rx-password-wrap") || field.parentNode;

      if (parent) {
        parent.appendChild(meter);
      }

      field.addEventListener("input", () => {
        const score = this.getPasswordScore(field.value);

        meter.dataset.score = String(score);

        if (score <= 2) {
          text.textContent = this.config.messages.passwordWeak;
        } else if (score <= 4) {
          text.textContent = this.config.messages.passwordMedium;
        } else {
          text.textContent = this.config.messages.passwordStrong;
        }
      });
    },

    getPasswordScore(password) {
      let score = 0;
      const value = String(password || "");

      if (value.length >= 8) score++;
      if (value.length >= 12) score++;
      if (/[a-z]/.test(value)) score++;
      if (/[A-Z]/.test(value)) score++;
      if (/[0-9]/.test(value)) score++;
      if (/[^a-zA-Z0-9]/.test(value)) score++;

      return score;
    },

    /**
     * ============================================================
     * COPY PROTECTION AND SAFE COPY
     * ============================================================
     */

    initCopyProtection(context = document) {
      if (!this.config.features.copyProtection) {
        return;
      }

      this.qsa(this.config.selectors.noCopy, context).forEach((el) => {
        el.addEventListener("copy", (event) => {
          event.preventDefault();
          this.toast(this.config.messages.copyBlocked, "warning");
        });

        el.addEventListener("cut", (event) => {
          event.preventDefault();
          this.toast(this.config.messages.copyBlocked, "warning");
        });
      });

      if (this.config.features.safeCopyAttribution) {
        document.addEventListener("copy", (event) => {
          this.handleSafeCopy(event);
        });
      }
    },

    handleSafeCopy(event) {
      const selection = window.getSelection();

      if (!selection || selection.rangeCount === 0) {
        return;
      }

      const selectedText = selection.toString();

      if (!selectedText || selectedText.length < 40) {
        return;
      }

      const anchorNode = selection.anchorNode;
      const parent =
        anchorNode && anchorNode.nodeType === 3
          ? anchorNode.parentElement
          : anchorNode;

      if (
        parent &&
        parent.closest &&
        parent.closest(this.config.selectors.noCopy)
      ) {
        event.preventDefault();
        this.toast(this.config.messages.copyBlocked, "warning");
        return;
      }

      const source = `\n\nSource: ${document.title} - ${window.location.href}`;
      const finalText = selectedText + source;

      if (event.clipboardData) {
        event.clipboardData.setData("text/plain", finalText);
        event.preventDefault();
        this.toast(this.config.messages.copied, "success", 1500);
      }
    },

    initRightClickProtection() {
      if (!this.config.features.rightClickProtection) {
        return;
      }

      document.addEventListener("contextmenu", (event) => {
        const target = event.target;

        if (
          target &&
          target.closest &&
          target.closest("[data-rx-allow-context-menu]")
        ) {
          return;
        }

        event.preventDefault();
        this.toast("Right click is disabled on this content.", "warning");
      });
    },

    initKeyboardProtection() {
      if (!this.config.features.keyboardProtection) {
        return;
      }

      document.addEventListener("keydown", (event) => {
        const key = event.key.toLowerCase();

        const blocked =
          event.ctrlKey &&
          event.shiftKey &&
          ["i", "j", "c"].includes(key);

        const blocked2 = event.ctrlKey && ["u", "s"].includes(key);

        if (blocked || blocked2 || event.key === "F12") {
          event.preventDefault();
          this.toast("This keyboard action is disabled.", "warning");
        }
      });
    },

    /**
     * ============================================================
     * EMAIL OBFUSCATION
     * ============================================================
     */

    initEmailObfuscation(context = document) {
      if (!this.config.features.emailObfuscationDecode) {
        return;
      }

      this.qsa("[data-rx-email]", context).forEach((el) => {
        if (el.dataset.rxEmailDone === "1") {
          return;
        }

        const encoded = el.getAttribute("data-rx-email");

        if (!encoded) {
          return;
        }

        try {
          const email = atob(encoded);
          const safeEmail = this.normalizeText(email);

          if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(safeEmail)) {
            el.textContent = safeEmail;

            if (el.tagName.toLowerCase() === "a") {
              el.href = `mailto:${safeEmail}`;
            }
          }

          el.dataset.rxEmailDone = "1";
        } catch (error) {
          this.warn("Email decode failed", error);
        }
      });
    },

    /**
     * ============================================================
     * IFRAME AND MIXED CONTENT UI
     * ============================================================
     */

    initIframeBreakoutUI() {
      if (!this.config.features.iframeBreakoutUI) {
        return;
      }

      try {
        if (window.top !== window.self) {
          document.documentElement.classList.add("rx-in-iframe");

          const banner = this.createEl(
            "div",
            "rx-frame-warning",
            "For your safety, open this page directly on the original website."
          );

          const button = this.createEl(
            "button",
            "rx-frame-warning-btn",
            "Open safely"
          );

          button.type = "button";

          button.addEventListener("click", () => {
            window.top.location = window.self.location.href;
          });

          banner.appendChild(button);

          this.ready(() => {
            document.body.prepend(banner);
          });
        }
      } catch (error) {
        this.warn("Iframe check failed", error);
      }
    },

    initMixedContentWarning() {
      if (!this.config.features.mixedContentWarning) {
        return;
      }

      if (window.location.protocol !== "https:") {
        return;
      }

      const insecure = this.qsa(
        'img[src^="http:"], script[src^="http:"], link[href^="http:"], iframe[src^="http:"]'
      );

      if (insecure.length) {
        document.documentElement.classList.add("rx-has-mixed-content");
        this.warn("Mixed content found:", insecure.length);
      }
    },

    initReferrerPolicyMeta() {
      if (!this.config.features.referrerPolicyMeta) {
        return;
      }

      if (document.querySelector('meta[name="referrer"]')) {
        return;
      }

      const meta = document.createElement("meta");
      meta.name = "referrer";
      meta.content = "strict-origin-when-cross-origin";
      document.head.appendChild(meta);
    },

    /**
     * ============================================================
     * PRINT WATERMARK
     * ============================================================
     */

    initPrintWatermark() {
      if (!this.config.features.printWatermark) {
        return;
      }

      window.addEventListener("beforeprint", () => {
        let watermark = this.qs("#rx-print-watermark");

        if (!watermark) {
          watermark = this.createEl("div", "rx-print-watermark");
          watermark.id = "rx-print-watermark";
          watermark.textContent = `${document.title} | ${window.location.href}`;
          document.body.appendChild(watermark);
        }
      });
    },

    /**
     * ============================================================
     * SESSION IDLE WARNING UI
     * ============================================================
     */

    initIdleWarning() {
      if (!this.config.features.sessionIdleWarning) {
        return;
      }

      const updateActivity = () => {
        this.state.lastActivityTime = Date.now();
      };

      ["mousemove", "keydown", "click", "scroll", "touchstart"].forEach(
        (eventName) => {
          document.addEventListener(eventName, updateActivity, {
            passive: true
          });
        }
      );

      this.state.idleTimer = setInterval(() => {
        const idleMs = Date.now() - this.state.lastActivityTime;

        if (idleMs > 15 * 60 * 1000) {
          this.toast(
            "You have been inactive for a while. Please refresh before submitting sensitive forms.",
            "warning",
            6000
          );
          this.state.lastActivityTime = Date.now();
        }
      }, 60 * 1000);
    },

    /**
     * ============================================================
     * DEVTOOLS WARNING UI
     * ============================================================
     */

    initDevToolsWarning() {
      if (!this.config.features.devToolsWarning) {
        return;
      }

      const threshold = 160;

      setInterval(() => {
        const widthDiff = window.outerWidth - window.innerWidth;
        const heightDiff = window.outerHeight - window.innerHeight;

        if (widthDiff > threshold || heightDiff > threshold) {
          document.documentElement.classList.add("rx-devtools-maybe-open");
        } else {
          document.documentElement.classList.remove("rx-devtools-maybe-open");
        }
      }, 1500);
    },

    /**
     * ============================================================
     * MUTATION OBSERVER FOR DYNAMIC CONTENT
     * ============================================================
     */

    initMutationObserver() {
      if (!window.MutationObserver) {
        return;
      }

      const observer = new MutationObserver(
        this.debounce((mutations) => {
          mutations.forEach((mutation) => {
            mutation.addedNodes.forEach((node) => {
              if (!this.isElement(node)) {
                return;
              }

              this.secureExternalLinks(node);
              this.initForms(node);
              this.initPasswordTools(node);
              this.initEmailObfuscation(node);
              this.initCopyProtection(node);
            });
          });
        }, 100)
      );

      observer.observe(document.documentElement, {
        childList: true,
        subtree: true
      });
    },

    /**
     * ============================================================
     * CSS INJECTION FOR SECURITY UI
     * ============================================================
     */

    injectSecurityStyles() {
      if (document.getElementById("rx-security-ui-styles")) {
        return;
      }

      const style = document.createElement("style");
      style.id = "rx-security-ui-styles";

      style.textContent = `
        .rx-security-toast-container {
          position: fixed;
          right: 16px;
          bottom: 16px;
          z-index: 999999;
          display: flex;
          flex-direction: column;
          gap: 10px;
          max-width: min(360px, calc(100vw - 32px));
          pointer-events: none;
        }

        .rx-security-toast {
          opacity: 0;
          transform: translateY(12px);
          padding: 12px 14px;
          border-radius: 12px;
          background: #111827;
          color: #ffffff;
          font-size: 14px;
          line-height: 1.45;
          box-shadow: 0 12px 30px rgba(0,0,0,.22);
          transition: opacity .22s ease, transform .22s ease;
          pointer-events: auto;
        }

        .rx-security-toast.is-visible {
          opacity: 1;
          transform: translateY(0);
        }

        .rx-security-toast-success {
          background: #065f46;
        }

        .rx-security-toast-warning {
          background: #92400e;
        }

        .rx-security-toast-error {
          background: #991b1b;
        }

        .rx-external-link::after {
          content: "↗";
          display: inline-block;
          margin-left: .25em;
          font-size: .85em;
          opacity: .75;
        }

        .rx-suspicious-link {
          text-decoration-style: wavy;
        }

        .rx-security-modal-overlay {
          position: fixed;
          inset: 0;
          z-index: 999998;
          display: grid;
          place-items: center;
          padding: 20px;
          background: rgba(15, 23, 42, .66);
          opacity: 0;
          visibility: hidden;
          transition: opacity .2s ease, visibility .2s ease;
        }

        .rx-security-modal-overlay.is-visible {
          opacity: 1;
          visibility: visible;
        }

        .rx-security-modal {
          width: min(520px, 100%);
          padding: 22px;
          border-radius: 18px;
          background: #ffffff;
          color: #111827;
          box-shadow: 0 24px 80px rgba(0,0,0,.32);
        }

        .rx-security-modal-title {
          margin: 0 0 10px;
          font-size: 22px;
          line-height: 1.25;
        }

        .rx-security-modal-text {
          margin: 0 0 14px;
          color: #374151;
          font-size: 15px;
          line-height: 1.6;
        }

        .rx-security-modal-url {
          overflow-wrap: anywhere;
          margin: 0 0 18px;
          padding: 10px 12px;
          border-radius: 10px;
          background: #f3f4f6;
          color: #1f2937;
          font-size: 13px;
          line-height: 1.45;
        }

        .rx-security-modal-actions {
          display: flex;
          justify-content: flex-end;
          gap: 10px;
        }

        .rx-security-btn {
          cursor: pointer;
          border: 0;
          border-radius: 10px;
          padding: 10px 14px;
          font-weight: 700;
          line-height: 1;
        }

        .rx-security-btn-primary {
          background: #0f766e;
          color: #ffffff;
        }

        .rx-security-btn-secondary {
          background: #e5e7eb;
          color: #111827;
        }

        .rx-security-hp-wrap {
          position: absolute !important;
          left: -10000px !important;
          top: auto !important;
          width: 1px !important;
          height: 1px !important;
          overflow: hidden !important;
        }

        .rx-field-warning {
          outline: 2px solid #f59e0b;
          outline-offset: 2px;
        }

        .rx-field-security-help {
          display: block;
          margin-top: 4px;
          color: #92400e;
          font-size: 12px;
        }

        .rx-password-wrap {
          position: relative;
          display: block;
        }

        .rx-password-toggle {
          position: absolute;
          right: 8px;
          top: 50%;
          transform: translateY(-50%);
          cursor: pointer;
          border: 0;
          border-radius: 8px;
          padding: 6px 9px;
          background: #e5e7eb;
          color: #111827;
          font-size: 12px;
          font-weight: 700;
        }

        .rx-password-strength {
          margin-top: 8px;
        }

        .rx-password-strength-bar {
          display: block;
          width: 100%;
          height: 6px;
          border-radius: 999px;
          background: #e5e7eb;
          overflow: hidden;
          position: relative;
        }

        .rx-password-strength-bar::before {
          content: "";
          display: block;
          height: 100%;
          width: 0;
          border-radius: inherit;
          background: currentColor;
          transition: width .2s ease;
        }

        .rx-password-strength[data-score="1"] .rx-password-strength-bar::before,
        .rx-password-strength[data-score="2"] .rx-password-strength-bar::before {
          width: 33%;
        }

        .rx-password-strength[data-score="3"] .rx-password-strength-bar::before,
        .rx-password-strength[data-score="4"] .rx-password-strength-bar::before {
          width: 66%;
        }

        .rx-password-strength[data-score="5"] .rx-password-strength-bar::before,
        .rx-password-strength[data-score="6"] .rx-password-strength-bar::before {
          width: 100%;
        }

        .rx-password-strength-text {
          display: block;
          margin-top: 4px;
          font-size: 12px;
        }

        .rx-frame-warning {
          position: fixed;
          left: 0;
          right: 0;
          top: 0;
          z-index: 999997;
          display: flex;
          align-items: center;
          justify-content: center;
          gap: 12px;
          padding: 10px 14px;
          background: #991b1b;
          color: #ffffff;
          font-size: 14px;
          line-height: 1.4;
        }

        .rx-frame-warning-btn {
          cursor: pointer;
          border: 0;
          border-radius: 8px;
          padding: 6px 10px;
          background: #ffffff;
          color: #991b1b;
          font-weight: 700;
        }

        .rx-print-watermark {
          display: none;
        }

        @media print {
          .rx-print-watermark {
            display: block;
            position: fixed;
            left: 0;
            right: 0;
            bottom: 0;
            padding: 8px;
            border-top: 1px solid #999;
            background: #fff;
            color: #111;
            font-size: 11px;
            text-align: center;
            z-index: 999999;
          }

          .rx-security-toast-container,
          .rx-security-modal-overlay,
          .rx-frame-warning {
            display: none !important;
          }
        }
      `;

      document.head.appendChild(style);
    },

    /**
     * ============================================================
     * PUBLIC API
     * ============================================================
     */

    scan(context = document) {
      this.secureExternalLinks(context);
      this.initForms(context);
      this.initPasswordTools(context);
      this.initEmailObfuscation(context);
      this.initCopyProtection(context);
    },

    updateConfig(newConfig) {
      if (!newConfig || typeof newConfig !== "object") {
        return;
      }

      this.config = this.deepMerge(this.config, newConfig);
    },

    deepMerge(target, source) {
      const output = Object.assign({}, target);

      Object.keys(source).forEach((key) => {
        if (
          source[key] &&
          typeof source[key] === "object" &&
          !Array.isArray(source[key])
        ) {
          output[key] = this.deepMerge(target[key] || {}, source[key]);
        } else {
          output[key] = source[key];
        }
      });

      return output;
    },

    init() {
      if (this.state.initialized) {
        return;
      }

      this.state.initialized = true;

      if (window.RX_SECURITY_UI_CONFIG) {
        this.updateConfig(window.RX_SECURITY_UI_CONFIG);
      }

      this.injectSecurityStyles();
      this.initReferrerPolicyMeta();
      this.initIframeBreakoutUI();

      this.ready(() => {
        this.addBodyClass("rx-security-ui-ready");
        this.scan(document);
        this.initRightClickProtection();
        this.initKeyboardProtection();
        this.initMixedContentWarning();
        this.initPrintWatermark();
        this.initIdleWarning();
        this.initDevToolsWarning();
        this.initMutationObserver();

        document.dispatchEvent(
          new CustomEvent("rxSecurityUIReady", {
            detail: {
              version: this.version
            }
          })
        );

        this.log("Initialized");
      });
    }
  };

  window.RXSecurityUI = RXSecurityUI;
  RXSecurityUI.init();
})();

Add it in your theme enqueue file like this:

wp_enqueue_script(
    'rx-security-ui',
    get_template_directory_uri() . '/assets/static-js/static-chunks-js/chunk-048-security-ui.js',
    array(),
    wp_get_theme()->get('Version'),
    true
);

Optional: before loading the file, you can add custom settings:

wp_add_inline_script(
    'rx-security-ui',
    'window.RX_SECURITY_UI_CONFIG = {
        debug: false,
        features: {
            rightClickProtection: false,
            externalLinkWarning: true,
            passwordStrengthUI: true,
            formSpamProtection: true
        }
    };',
    'before'
);

Best setting for RX Theme now: keep rightClickProtection, keyboardProtection, and devToolsWarning as false. They can annoy real users and do not create real security. Keep the form, link, file, password, and external warning features active.

Leave a Reply

Your email address will not be published. Required fields are marked *