1document.addEventListener("DOMContentLoaded", function () { 2 if (typeof MktoForms2 === "undefined") return; 3 4 // === CONFIGURATION === 5 const CONFIG = { 6 ENABLED: true, // This is a global on/off switch for the script's core functionality. 7 FEATURE_TOGGLES: { 8 enableValidation: true, 9 enableAnalytics: true 10 }, 11 DEFAULT_SCRIPT_VERSION: "v1.0.0", 12 RETRY_LIMIT: 3, 13 RETRY_DELAY_MS: 500, 14 DEBUG_DEFAULT_ENVIRONMENTS: ["DEV", "STAGE"] 15 }; 16 17 let scriptVersion = CONFIG.DEFAULT_SCRIPT_VERSION; 18 19 const currentScript = document.currentScript || (function () { 20 const scripts = document.getElementsByTagName('script'); 21 return scripts[scripts.length - 1]; 22 })(); 23 24 const scriptLocation = currentScript?.src || 'unknown'; 25 const scriptName = scriptLocation.split('/').pop() || 'unknown'; 26 27 try { 28 const scriptUrl = new URL(scriptLocation); 29 const scriptVerParam = scriptUrl.searchParams.get("ver"); 30 if (scriptVerParam) { 31 scriptVersion = scriptVerParam; 32 } 33 } catch (e) { 34 if (typeof debugMode !== "undefined" && debugMode) 35 console.warn("Could not parse scriptLocation for version override:", e); 36 } 37 38 function detectEnvironment() { 39 const hostname = window.location.hostname.toLowerCase(); 40 if (hostname.includes("dev.")) return "DEV"; 41 if (hostname.includes("stage.")) return "STAGE"; 42 if (hostname.includes("www.") || hostname.includes("prod.")) return "PROD"; 43 console.warn("[ZB Email Validator] Environment could not be determined from hostname:", hostname); 44 return "UNKNOWN"; 45 } 46 47 const environment = detectEnvironment(); 48 49// === Email Validation Rule Configuration === 50// This object defines what is considered a "valid" email address based on 51// ZeroBounce API results. The structure allows for unconditional and conditional logic: 52// 53// - valid_statuses: Email statuses that are always accepted. 54// - conditional_statuses: Emails with specific statuses are only accepted if they meet 55// additional criteria like allowed sub_status, MX presence, and free provider rules. 56// - require_mx: If true, all accepted emails must have a valid MX record. 57// - allow_free: If false, emails from free providers (e.g., gmail.com) are rejected. 58// Exit early if disabled by config 59 60// Example of conditional_status configuration 61 /* conditional_statuses: { 62 "unknown": { 63 sub_statuses: ["antispam_system", "greylisted"], 64 require_mx: false, 65 allow_free: true 66 } 67 */ 68 69 if (!CONFIG.ENABLED || !CONFIG.FEATURE_TOGGLES.enableValidation) return; 70 71 const ZBValidationRules = { 72 valid_statuses: ["valid", "catch-all"], 73 require_mx: false, 74 allow_free: true, 75 conditional_statuses: {} 76 } 77 78 const debugMode = (() => { 79 const urlParams = new URLSearchParams(window.location.search); 80 const debugFlag = urlParams.get("debug") === "true" || window.ZB_DEBUG_MODE === true; 81 if (CONFIG.DEBUG_DEFAULT_ENVIRONMENTS.includes(environment)) return true; 82 return debugFlag; 83 })(); 84 85 const timestamp = new Date().toISOString(); 86 // console.group(`[${timestamp}] ZB Email Validator Diagnostics`); 87 // console.info("Debug Mode:", debugMode); 88 // console.info("Environment:", environment); 89 // console.info("Script Version:", scriptVersion); 90 // console.info("Script Name:", scriptName); 91 // console.info("Script Location:", scriptLocation); 92 // console.groupEnd(); 93 94 function pushValidationEvent(data) { 95 window.dataLayer = window.dataLayer || []; 96 window.dataLayer.push(data); 97 } 98 99 function isEmailValid(data) { 100 const rules = ZBValidationRules; 101 const unconditional = rules.valid_statuses.includes(data.status); 102 const conditionalRule = rules.conditional_statuses[data.status]; 103 const conditional = conditionalRule && 104 conditionalRule.sub_statuses.includes(data.sub_status) && 105 (!conditionalRule.requires_mx || data.mx_found) && 106 (conditionalRule.allow_free || !data.free_email); 107 const passes_mx = !rules.require_mx || data.mx_found; 108 const passes_free = rules.allow_free || !data.free_email; 109 return (unconditional || conditional) && passes_mx && passes_free; 110 } 111 112 function getEmailDomain(email) { 113 return email.includes("@") ? email.split("@")[1].toLowerCase() : "invali
113d"; 114 } 115 116 const ZBUtils = { 117 getEmailDomain, 118 buildValidationURL, 119 isEmailValid, 120 reportValidationResult, 121 reportFallbackUX 122 }; 123 124 function buildValidationURL(email) { 125 return `https://api.zerobounce.net/v2/validate?api_key=${zeroBounceAPI.apiKey}&email=${encodeURIComponent(email)}`; 126 } 127 128 function reportValidationResult(data, isValid, emailDomain) { 129 let evaluationMode = "unknown"; 130 if (data.fallback) { 131 evaluationMode = "accepted_fallback"; 132 } else if (ZBValidationRules.valid_statuses.includes(data.status)) { 133 evaluationMode = "accepted_unconditionally"; 134 } else if (ZBValidationRules.conditional_statuses[data.status]) { 135 const rule = ZBValidationRules.conditional_statuses[data.status]; 136 const subMatch = rule.sub_statuses.includes(data.sub_status); 137 const mxOk = !rule.requires_mx || data.mx_found; 138 const freeOk = rule.allow_free || !data.free_email; 139 evaluationMode = (subMatch && mxOk && freeOk) ? "accepted_conditionally" : "rejected_conditionally"; 140 } 141 const uxString = `${emailDomain} | ${data.status} | ${data.sub_status || "none"} | ${isValid ? "clearValidationError" : "showValidationError"} | ${evaluationMode} | attempt_${data.attempt || 1}`; 142 const uxConfig = JSON.stringify({ 143 valid_statuses: ZBValidationRules.valid_statuses, 144 conditional_statuses: ZBValidationRules.conditional_statuses, 145 require_mx: ZBValidationRules.require_mx, 146 allow_free: ZBValidationRules.allow_free 147 }); 148 if (CONFIG.FEATURE_TOGGLES.enableAnalytics) pushValidationEvent({ 149 hostname: window.location.hostname, 150 pageURL: window.location.href, 151 event: "formValidation_zerobounce", 152 scriptName, 153 scriptVersion, 154 scriptLocation, 155 debugMode, 156 UX_Type: uxString, 157 UX_Config: uxConfig, 158 environment 159 }); 160 } 161 162 function reportFallbackUX(emailDomain) { 163 const fallbackData = {status: "error", sub_status: "no_response", fallback: true}; 164 reportValidationResult(fallbackData, false, emailDomain); 165 showFallbackNotice(); 166 } 167 168 MktoForms2.whenReady(function (form) { 169 const formElem = form.getFormElem(); 170 const emailField = formElem.find("input[name='Email']"); 171 const submitButton = formElem.find("button[type='submit']"); 172 173 submitButton.prop("disabled", true).css("opacity", "0.5"); 174 175 function showValidationError(message) { 176 emailField.css("border", "2px solid red"); 177 formElem.find(".email-validation-error").remove(); 178 emailField.after(`<div class="email-validation-error" style="color: red; font-size: 12px; margin-top: 5px;">${message}</div>`); 179 submitButton.prop("disabled", true).css("opacity", "0.5"); 180 } 181 182 function clearValidationError() { 183 emailField.css("border", ""); 184 formElem.find(".email-validation-error").remove(); 185 submitButton.prop("disabled", false).css("opacity", "1"); 186 } 187 188 function showFallbackNotice() { 189 emailField.css("border", ""); 190 formElem.find(".email-validation-error").remove(); 191 if (debugMode) { 192 emailField.after(`<div class="email-validation-error" style="color: orange; font-size: 12px; margin-top: 5px;">Validation service unavailable. Proceeding without verification.</div>`); 193 } 194 submitButton.prop("disabled", false).css("opacity", "1"); 195 } 196 197 function validateEmail(email, attempt = 1) { 198 const apiUrl = ZBUtils.buildValidationURL(email); 199 const emailDomain = ZBUtils.getEmailDomain(email); 200 201 if (debugMode) { 202 console.log(`[${scriptName} ${scriptVersion}] Attempt ${attempt}: Validating "${email}"`); 203 } 204 205 fetch(apiUrl) 206 .then(response => response.json()) 207 .then(data => { 208 data.attempt = attempt; 209 const isValid = ZBUtils.isEmailValid(data); 210 ZBUtils.reportValidationResult(data, isValid, emailDomain); 211 isValid ? clearValidationError() : showValidationError("Invalid Email Address. Please enter a valid email."); 212 }) 213 .catch(error => { 214 if (debugMode) { 215 console.warn(`[${scriptName} ${scriptVersion}] Attempt ${attempt} failed:`, error); 216 } 217 218 if (attempt < CONFIG.RETRY_LIMIT) { 219 setTimeout(() => validateEmail(email, attempt + 1), CONFIG.RETRY_DELAY_MS); 220 } else { 221 ZBUtils.reportFallbackUX(emailDomain); 222 } 223 }); 224 } 225 226 emailField.on("keyup", function () { 227 const email = emailField.val().trim(); 228 if (email.length > 5) { 229 validateEmail(email); 230 } else { 231 showValidationError("Please enter a valid email address."); 232 } 233 }); 234 235 form.onSubmit(function (e) { 236 if (submitButton.prop("disabled")) { 237 e.preventDefault(); 238 return false;
239 } 240 }); 241 242 form.onSuccess(function (values, followUpUrl) { 243 window.location.href = followUpUrl; 244 return false; 245 }); 246 }); 247});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.