1// Store original referrer 2if (!sessionStorage.getItem("original_referrer")) { 3 sessionStorage.setItem("original_referrer", document.referrer || "direct"); 4} 5 6// ââ Helpers âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 7 8function getHubspotForms() { 9 return document.querySelectorAll(".hbspt-form form, .hs-form-html form"); 10} 11 12function getCookie(name) { 13 const match = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)")); 14 return match ? match[2] : null; 15} 16 17// ââ Geo âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 18 19let resolvedGeo = null; 20 21function parseGeoCookie(raw) { 22 try { 23 const decoded = decodeURIComponent(raw); 24 const geoObj = JSON.parse(decoded); 25 const str = typeof geoObj === "object" 26 ? `${geoObj.city}, ${geoObj.region}, ${geoObj.country}` 27 : String(geoObj); 28 return str.includes("undefined") ? null : str; 29 } catch { 30 const cleaned = decodeURIComponent(raw).replace(/"/g, ""); 31 return cleaned.includes("undefined") ? null : cleaned; 32 } 33} 34 35function waitForGeoCookie(callback, attempts = 20) { 36 const raw = getCookie("orases_geoip_data"); 37 if (raw) { 38 callback(parseGeoCookie(raw)); 39 } else if (attempts > 0) { 40 setTimeout(() => waitForGeoCookie(callback, attempts - 1), 500); 41 } 42} 43 44// ââ reCAPTCHA v3 ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 45 46const RECAPTCHA_SITE_KEY = orasesConfig.recaptchaSiteKey; 47const RECAPTCHA_VERIFY_ENDPOINT = orasesConfig.recaptchaEndpoint; 48 49// api.js is loaded once, server-side, by wp_enqueue_script() in 50// includes/hubspot-recaptcha.php (declared as this script's dependency) â 51// this file must not load it a second time. 52 53// Generates a fresh token on demand, right before it's needed, rather than 54// polling grecaptcha.execute() in the background every ~2 minutes forever 55// regardless of whether the visitor ever submits a form. Since api.js is 56// only requested when the page actually needs it (no longer preloaded 57// unconditionally), it may not have finished loading/parsing yet if the 58// visitor submits quickly â so this waits (briefly, bounded) for 59// `grecaptcha` to become available rather than failing instantly. 60function getRecaptchaToken() { 61 return new Promise(function(resolve) { 62 function execute() { 63 grecaptcha.ready(function() { 64 grecaptcha.execute(RECAPTCHA_SITE_KEY, { action: "submit" }) 65 .then(function(token) { resolve(token); }) 66 .catch(function() { resolve(null); }); 67 }); 68 } 69 70 if (typeof grecaptcha !== "undefined") { 71 execute(); 72 return; 73 } 74 75 var waitedMs = 0; 76 var MAX_WAIT_MS = 10000; 77 var interval = setInterval(function() { 78 waitedMs += 250; 79 if (typeof grecaptcha !== "undefined") { 80 clearInterval(interval); 81 execute(); 82 } else if (waitedMs >= MAX_WAIT_MS) { 83 clearInterval(interval); 84 resolve(null); 85 } 86 }, 250); 87 }); 88} 89 90// Generates a fresh token and verifies it against the WordPress endpoint â 91// returns a Promise<boolean> 92function verifyRecaptchaToken() { 93 return getRecaptchaToken().then(function(token) { 94 if (!token) return false; 95 96 return fetch(RECAPTCHA_VERIFY_ENDPOINT, { 97 method: "POST", 98 headers: { "Content-Type": "application/json" }, 99 body: JSON.stringify({ token: token }), 100 }) 101 .then(function(res) { return res.json(); }) 102 .then(function(data) { return data.success === true; }) 103 .catch(function() { return false; }); 104 }); 105} 106 107// ââ Block restricted countries ââââââââââââââââââââââââââââââââââââââââââââââââ 108 109function blockRestrictedCountries(locationStr) { 110 const blockedCountries = ["ME","ZA","IN","NG","BG","PK","BD","TR","SG","LK","PH","VN","OM","IQ","ZM"]; 111 const parts = locationStr.split(","); 112 const countryCode = parts.length ? parts[parts.length - 1].trim() : ""; 113 114 if (blockedCountries.includes(countryCode)) {
115 getHubspotForms().forEach(form => { 116 const submitBtn = form.querySelector("input[type='submit'], button[type='submit']"); 117 if (submitBtn) { 118 submitBtn.disabled = true; 119 submitBtn.style.opacity = "0.5"; 120 submitBtn.style.cursor = "not-allowed"; 121 } 122 if (!form.querySelector(".geo-block-msg")) { 123 const msg = document.createElement("p"); 124 msg.className = "geo-block-msg"; 125 msg.innerText = "Sorry, we do not accept submissions from your location."; 126 form.appendChild(msg); 127 } 128 }); 129 } 130} 131 132// ââ Phone mask ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 133 134function applyPhoneMask() { 135 document.querySelectorAll("input[name$='phone']").forEach(function(phoneInput) { 136 if (phoneInput.dataset.maskApplied) return; 137 phoneInput.dataset.maskApplied = "true"; 138 phoneInput.setAttribute("placeholder", "(___) ___-____"); 139 phoneInput.setAttribute("maxlength", "14"); 140 phoneInput.addEventListener("input", function(e) { 141 let value = e.target.value.replace(/\D/g, "").substring(0, 10); 142 let formatted = ""; 143 if (value.length > 0) formatted = "(" + value.substring(0, 3); 144 if (value.length >= 3) formatted = "(" + value.substring(0, 3) + ") "; 145 if (value.length >= 4) formatted += value.substring(3, 6); 146 if (value.length >= 7) formatted += "-" + value.substring(6, 10); 147 e.target.value = formatted; 148 }); 149 }); 150} 151 152// ââ Active fields âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 153 154function markFieldsActive() { 155 const form = document.querySelector('.hs-form-html form'); 156 if (!form) return; 157 158 const containers = form.querySelectorAll('.hsfc-Row > div'); 159 Array.prototype.forEach.call(containers, function (container) { 160 const isPhoneField = container.classList.contains('hsfc-PhoneField'); 161 if (isPhoneField) { 162 const phoneInput = container.querySelector('.hsfc-PhoneInput'); 163 phoneInput.addEventListener('click', function () { 164 container.classList.add('active'); 165 }); 166 return; 167 } 168 169 // Find the wrapper (field) and the actual value-holding input 170 const field = container.querySelector( 171 ':scope > .hsfc-TextInput, :scope > .hsfc-DropdownInput, :scope > .hsfc-TextareaInput, :scope > .hsfc-PhoneField' 172 ); 173 if (!field) return; 174 175 // The element that actually holds the value 176 const input = field.matches('.hsfc-DropdownInput') 177 ? field.querySelector('input, select') 178 : field.querySelector('input, textarea') || field; 179 180 // focusin/focusout bubble, so they catch focus on any descendant 181 // (e.g. dropdown options, combobox listbox items) 182 container.addEventListener('focusin', function () { 183 container.classList.add('active'); 184 }); 185 186 container.addEventListener('focusout', function (e) { 187 // If focus is moving to another element inside this container, 188 // don't deactivate â the user is still interacting with the field 189 if (container.contains(e.relatedTarget)) return; 190 191 if (!input || !input.value) { 192 container.classList.remove('active'); 193 } 194 }); 195 196 // Pre-filled on load 197 if (input && input.value) { 198 container.classList.add('active'); 199 } 200 }); 201} 202 203// ââ Tracking values âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 204 205function getTrackingValues() { 206 return { 207 landing_page: sessionStorage.getItem("landing_url") || window.location.pathname + window.location.search, 208 conversion_page: window.location.pathname, 209 orases_referrer: sessionStorage.getItem("original_referrer") === "direct" 210 ? "" : (sessionStorage.getItem("original_referrer") || ""), 211 geolocated: resolvedGeo || "", 212 }; 213} 214 215// ââ Stamp DOM fields (belt) âââââââââââââââââââââââââââââââââââââââââââââââââââ 216 217function stampDOMFields() { 218 const tracking = getTrackingValues();
219 getHubspotForms().forEach(form => { 220 Object.entries(tracking).forEach(([name, value]) => { 221 form.querySelectorAll(`[name$="${name}"]`).forEach(el => { 222 el.value = value ?? ""; 223 }); 224 }); 225 }); 226} 227 228// ââ Network interception (suspenders) ââââââââââââââââââââââââââââââââââââââââ 229// Injects values at the wire level â HubSpot DOM resets cannot affect this 230 231function isHubSpotRequest(body) { 232 if (typeof body === "string") { 233 return body.includes("hs_context") || body.includes("submittedAt"); 234 } 235 if (body instanceof FormData) { 236 return body.has("hs_context") || body.has("submittedAt"); 237 } 238 return false; 239} 240 241function injectTrackingIntoBody(body) { 242 const tracking = getTrackingValues(); 243 244 if (typeof body === "string") { 245 const extra = Object.entries(tracking) 246 .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) 247 .join("&"); 248 return body + (body ? "&" : "") + extra; 249 } 250 251 if (body instanceof FormData) { 252 Object.entries(tracking).forEach(([k, v]) => body.set(k, v)); 253 return body; 254 } 255 256 return body; 257} 258 259function showRecaptchaError() {
260 getHubspotForms().forEach(function(form) { 261 if (form.querySelector(".recaptcha-error-msg")) return; 262 const msg = document.createElement("p"); 263 msg.className = "recaptcha-error-msg"; 264 msg.style.color = "red"; 265 msg.innerText = "Your submission could not be verified. Please try again."; 266 form.appendChild(msg); 267 // Auto-remove after 5 seconds 268 setTimeout(function() { msg.remove(); }, 5000); 269 }); 270} 271 272function patchXHR() { 273 const OriginalXHR = window.XMLHttpRequest; 274 275 function PatchedXHR() { 276 const xhr = new OriginalXHR(); 277 const originalSend = xhr.send.bind(xhr); 278 279 xhr.send = function(body) { 280 if (!isHubSpotRequest(body)) { 281 originalSend(body); 282 return; 283 } 284 285 // Verify reCAPTCHA before allowing HubSpot submission through 286 verifyRecaptchaToken().then(function(passed) { 287 if (!passed) { 288 showRecaptchaError(); 289 return; // block â never calls originalSend 290 } 291 originalSend(injectTrackingIntoBody(body)); 292 }); 293 }; 294 295 return xhr; 296 } 297 298 Object.setPrototypeOf(PatchedXHR, OriginalXHR); 299 PatchedXHR.prototype = OriginalXHR.prototype; 300 window.XMLHttpRequest = PatchedXHR; 301} 302 303function patchFetch() { 304 const originalFetch = window.fetch.bind(window); 305 306 window.fetch = function(input, init = {}) { 307 if (!init.body || !isHubSpotRequest(init.body)) { 308 return originalFetch(input, init); 309 } 310 311 // Verify reCAPTCHA before allowing HubSpot submission through 312 return verifyRecaptchaToken().then(function(passed) { 313 if (!passed) { 314 showRecaptchaError(); 315 return Promise.reject(new Error("reCAPTCHA verification failed")); 316 } 317 init.body = injectTrackingIntoBody(init.body); 318 return originalFetch(input, init); 319 }); 320 }; 321} 322 323// ââ Only load the form when ready to go ââââââââââââââââââââââââââââââââââââââ 324 325function revealFormWhenReady() { 326 document.querySelectorAll('.hs-form-html').forEach(host => { 327 if (host.dataset.readyWatched) return; 328 host.dataset.readyWatched = 'true'; 329 330 let quietTimer = null; 331 const QUIET_MS = 300; // how long of no mutations = "done" 332 const MAX_WAIT = 5000; // absolute failsafe 333 334 const markReady = () => { 335 if (host.classList.contains('form-ready')) return; 336 // Run all your setup once, right before revealing 337 applyPhoneMask(); 338 markFieldsActive(); 339 stampDOMFields(); 340 host.classList.add('form-ready'); 341 }; 342 343 const observer = new MutationObserver(() => { 344 clearTimeout(quietTimer); 345 quietTimer = setTimeout(markReady, QUIET_MS); 346 }); 347 348 observer.observe(host, { childList: true, subtree: true }); 349 350 // Kick off initial timer in case nothing mutates 351 quietTimer = setTimeout(markReady, QUIET_MS); 352 353 // Failsafe â reveal no matter what after MAX_WAIT 354 setTimeout(markReady, MAX_WAIT); 355 }); 356} 357 358// ââ MutationObserver â re-stamp on HubSpot re-renders ââââââââââââââââââââââââ 359 360function watchForReRenders() { 361 document.querySelectorAll(".hbspt-form, .hs-form-html").forEach(container => { 362 new MutationObserver(() => { 363 applyPhoneMask(); 364 stampDOMFields(); 365 }).observe(container, { childList: true, subtree: true }); 366 }); 367} 368 369// ââ Init ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 370 371// Must patch network IMMEDIATELY â before any form loads or submits 372patchXHR(); 373patchFetch(); 374 375window.addEventListener("load", function() { 376 377 waitForGeoCookie(function(geo) { 378 resolvedGeo = geo; 379 380 setTimeout(function() { 381 revealFormWhenReady(); 382 watchForReRenders(); 383 if (geo) blockRestrictedCountries(geo); 384 }, 2000); 385 }); 386});
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.