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.