1/* 2 * Shared form client for cosmoswebtech.com.au. 3 * Every lead form posts to /api/submit, which verifies Turnstile and hands the submission to the 4 * shared forms Worker. Nothing here talks to Mautic, and nothing reports success until the server 5 * has actually accepted the submission. 6 */ 7(function () { 8 'use strict'; 9 10 var ATTR_KEY = 'cosmos_attr'; 11 var ATTR_PARAMS = ['utm_source', 'utm_medium', 'utm_campaign', 'gclid', 'li_fat_id']; 12 var PENDING_MESSAGE = 'Almost done. Check your inbox and click the confirmation link to complete this.'; 13 14 function readAttr() { 15 try { return JSON.parse(sessionStorage.getItem(ATTR_KEY) || '{}'); } catch (e) { return {}; } 16 } 17 18 // First touch wins for the session. sessionStorage only, so nothing persists past the tab. 19 function captureAttribution() { 20 try { 21 var qs = new URLSearchParams(window.location.search); 22 var cur = readAttr(); 23 var changed = false; 24 ATTR_PARAMS.forEach(function (p) { 25 var v = qs.get(p); 26 if (v && !cur[p]) { cur[p] = v.slice(0, 200); changed = true; } 27 }); 28 if (changed) sessionStorage.setItem(ATTR_KEY, JSON.stringify(cur)); 29 } catch (e) { /* storage blocked: attribution is best effort */ } 30 } 31 captureAttribution(); 32 33 function turnstileToken(form) { 34 var widget = form.querySelector('.cf-turnstile'); 35 if (!widget || !window.turnstile) return null; 36 return window.turnstile.getResponse(widget) || null; 37 } 38 39 function resetTurnstile(form) { 40 var widget = form.querySelector('.cf-turnstile'); 41 if (widget && window.turnstile) window.turnstile.reset(widget); 42 } 43 44 function showError(form, message) { 45 var el = form.querySelector('.form-error'); 46 if (!el) { 47 el = document.createElement('p'); 48 el.className = 'form-error'; 49 el.setAttribute('role', 'alert'); 50 el.style.cssText = 'color:#b91c1c;font-size:0.9rem;margin:0.5rem 0 0;'; 51 var btn = form.querySelector('button[type="submit"]'); 52 if (btn && btn.parentNode) btn.parentNode.insertBefore(el, btn.nextSibling); 53 else form.appendChild(el); 54 } 55 el.textContent = message; 56 } 57 58 function clearError(form) { 59 var el = form.querySelector('.form-error'); 60 if (el) el.textContent = ''; 61 } 62 63 /** 64 * Submit a form. 65 * kind: one of the server-side allowlisted kinds (see functions/api/submit.ts) 66 * form: the <form> element (holds the Turnstile widget and the honeypot input) 67 * fields: plain object of the visitor's values 68 * Resolves { ok, status, message, error, code }. 69 */ 70 function submit(kind, form, fields) { 71 clearError(form); 72 var honeypot = form.querySelector('[name="hp_website"]'); 73 var token = turnstileToken(form); 74 if (!token) { 75 return Promise.resolve({ ok: false, error: 'Please complete the security check and try again.', code: 'no_token' }); 76 } 77 78 var attribution = {}; 79 var stored = readAttr(); 80 ATTR_PARAMS.forEach(function (p) { if (stored[p]) attribution[p] = stored[p]; }); 81 82 return fetch('/api/submit', { 83 method: 'POST', 84 headers: { 'Content-Type': 'application/json' }, 85 body: JSON.stringify({ 86 kind: kind, 87 turnstileToken: token, 88 hp: honeypot ? honeypot.value : '', 89 fields: fields, 90 page_url: window.location.origin + window.location.pathname, 91 attribution: attribution 92 }) 93 }).then(function (res) { 94 return res.json().catch(function () { return {}; }).then(function (json) { 95 if (res.ok && json.success) { 96 try { 97 window.dataLayer = window.dataLayer || []; 98 window.dataLayer.push({ event: 'generate_lead', brand: 'cosmos', form_kind: kind, verification: 'pending' }); 99 } catch (e) { /* analytics must never block the form */ } 100 return { ok: true, status: json.status || 'pending_verification', message: json.message || PENDING_MESSAGE }; 101 } 102 resetTurnstile(form); 103 return { ok: false, error: json.error || 'Something went wrong. Please try again, or contact us directly.', code: json.code }; 104 }); 105 }).catch(function () { 106 resetTurnstile(form); 107 return { ok: false, error: 'We could not reach the server. Please check your connection and try again.', code: 'network' }; 108 }); 109 } 110 111 window.CosmosForms = { 112 submit: submit, 113 showError: showError, 114 clearError: clearError, 115 resetTurnstile: resetTurnstile, 116 PENDING_MESSAGE: PENDING_MESSAGE 117 }; 118})();
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.