/*! Vambe Tracker v1 */ (function () { try { var script = document.currentScript || (function () { var s = document.getElementsByTagName("script"); return s[s.length - 1]; })(); // ===== Config from data-* with sane fallbacks ===== var CFG = { endpoint: "https://api.vambe.ai/frontend/api/redirect/create-event", clientId: script.getAttribute("data-client-id") || "unknown", cookiePrefix: "v_qp_", cookieDays: 7, }; // ===== Cookies / Params ===== function setCookie(k, v, days) { try { var t = new Date(); t.setTime(t.getTime() + days * 864e5); var parts = [ k + "=" + encodeURIComponent(v), "expires=" + t.toUTCString(), "path=/", "SameSite=Lax", ]; if (location.protocol === "https:") parts.push("Secure"); document.cookie = parts.join("; "); } catch (e) { // console.error("โŒ Failed to set cookie:", k, e); } } function getCookiesByPrefix(prefix) { var out = {}; try { (document.cookie || "").split(";").forEach(function (p) { var kv = p.split("="), k = (kv[0] || "").trim(); if (k && k.indexOf(prefix) === 0) { var value = decodeURIComponent(kv[1] || ""); out[k.slice(prefix.length)] = value; } }); } catch (e) { // console.error("โŒ Error reading cookies:", e); } return out; } function saveAllQueryParams() { try { var sp = new URLSearchParams(location.search || ""); var had = false; sp.forEach(function (v, k) { if (!k) return; had = true; setCookie(CFG.cookiePrefix + k, String(v), CFG.cookieDays); }); return had; } catch (e) { // console.error("โŒ Error saving query params:", e); return false; } } function isWhatsAppUrl(href) { try { // Check for native whatsapp:// protocol if (href && href.toLowerCase().indexOf("whatsapp://") === 0) { return true; } var u = new URL(href, location.href); var host = (u.hostname || "").replace(/^www\./, ""); var isWhatsApp = host === "wa.me" || host === "api.whatsapp.com" || host === "web.whatsapp.com"; return isWhatsApp; } catch (e) { return false; } } function buildDtoFromHref(href) { var target = ""; var identifier = ""; // Handle native whatsapp:// protocol if (href && href.toLowerCase().indexOf("whatsapp://") === 0) { try { // For whatsapp://send?phone=123&text=hello var queryString = href.split("?")[1] || ""; var params = new URLSearchParams(queryString); target = (params.get("phone") || "").replace(/[^0-9]/g, ""); identifier = params.get("text") || ""; } catch (e) { // Fallback parsing for malformed whatsapp:// URLs var phoneMatch = href.match(/phone=([^&]*)/); var textMatch = href.match(/text=([^&]*)/); target = phoneMatch ? decodeURIComponent(phoneMatch[1]).replace(/[^0-9]/g, "") : ""; identifier = textMatch ? decodeURIComponent(textMatch[1]) : ""; } } else { // Handle wa.me and api.whatsapp.com URLs try { var u = new URL(href, location.href); target = (u.pathname || "").replace(/^\//, "").replace(/[^0-9]/g, ""); identifier = u.searchParams.get("text") || ""; } catch (e) { // Silent fail } } var all = getCookiesByPrefix(CFG.cookiePrefix); var currentPageUrl = location.href; var dto = { clientId: CFG.clientId, destination: "whatsapp", target: target, identifier: identifier, originUrl: currentPageUrl, utm: { utm_source: all.utm_source, utm_medium: all.utm_medium, utm_campaign: all.utm_campaign, utm_term: all.utm_term, utm_content: all.utm_content, }, context: { user_agent: navigator.userAgent, allUtmParams: (function () { var o = {}; for (var k in all) if (Object.prototype.hasOwnProperty.call(all, k)) o[k] = String(all[k]); return o; })(), }, // ipAddress: omit; add on server via req.ip }; return dto; } function postJSON(url, data) { try { fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), keepalive: true, }).catch(function (error) { // Silent fail }); } catch (e) { // Silent fail } } saveAllQueryParams(); // ===== Phone Number Detection ===== var detectedPhones = new Set(); // Avoid duplicate events var phonePatterns = [ // International formats /^\+?[1-9]\d{1,14}$/, // Common formats with separators /^\+?[\d\s\-\(\)]{10,}$/, // Chilean format (your example was Chilean) /^(\+?56)?[\s\-]?[0-9][\s\-]?[0-9]{8}$/, // US format /^(\+?1)?[\s\-]?\(?[0-9]{3}\)?[\s\-]?[0-9]{3}[\s\-]?[0-9]{4}$/, // Generic international /^\+[1-9]\d{1,14}$/, ]; function isPhoneNumber(value) { if (!value || typeof value !== "string") return false; // Clean the value (remove spaces, dashes, parentheses) var cleaned = value.replace(/[\s\-\(\)]/g, ""); // Must be at least 8 digits and maximum 20 digits (international standard) if (cleaned.length < 7 || cleaned.length > 20) return false; // Must contain mostly digits var digitCount = (cleaned.match(/\d/g) || []).length; if (digitCount < 7 || digitCount > 20) return false; // Check against patterns return phonePatterns.some(function (pattern) { return pattern.test(cleaned); }); } function normalizePhone(phone) { // Remove all non-digit characters except + return phone.replace(/[^\d\+]/g, ""); } function createPhoneEvent(phone, element) { var normalizedPhone = normalizePhone(phone); // Avoid duplicates if (detectedPhones.has(normalizedPhone)) { return; } detectedPhones.add(normalizedPhone); console.log("๐Ÿ“ž New phone number detected:", normalizedPhone); var all = getCookiesByPrefix(CFG.cookiePrefix); // Function to send tracking data function sendTrackingData(ipAddress) { var currentPageUrl = location.href; var dto = { clientId: CFG.clientId, destination: "unknown", identifier: normalizedPhone, ip_address: ipAddress || "unknown", originUrl: currentPageUrl, utm: { utm_source: all.utm_source, utm_medium: all.utm_medium, utm_campaign: all.utm_campaign, utm_term: all.utm_term, utm_content: all.utm_content, }, context: { user_agent: navigator.userAgent, field_type: element.type || "unknown", field_name: element.name || "", field_id: element.id || "", field_placeholder: element.placeholder || "", allUtmParams: (function () { var o = {}; for (var k in all) if (Object.prototype.hasOwnProperty.call(all, k)) o[k] = String(all[k]); return o; })(), }, }; postJSON(CFG.endpoint, dto); } // Get IP address and send tracking data try { fetch("https://api.ipify.org?format=json") .then((response) => response.json()) .then((data) => { sendTrackingData(data.ip); }) .catch(() => { // Fallback: send without IP or with 'unknown' sendTrackingData("unknown"); }); } catch (e) { // Fallback for environments where fetch might not be available sendTrackingData("unknown"); } } function handleInputChange(e) { var element = e.target; var value = element.value; if (isPhoneNumber(value)) { console.log("โœ… Phone number pattern detected!"); createPhoneEvent(value, element); } } function handleInputBlur(e) { var element = e.target; var value = element.value; if (isPhoneNumber(value)) { console.log("โœ… Phone number confirmed on blur!"); createPhoneEvent(value, element); } } function findWhatsAppUrl(element) { // Check for href attribute (anchor tags) var href = element.getAttribute && element.getAttribute("href"); if (href && isWhatsAppUrl(href)) { return href; } // Check for data-href, data-url, data-link attributes var dataAttrs = ["data-href", "data-url", "data-link", "data-whatsapp"]; for (var i = 0; i < dataAttrs.length; i++) { var dataUrl = element.getAttribute && element.getAttribute(dataAttrs[i]); if (dataUrl && isWhatsAppUrl(dataUrl)) { return dataUrl; } } // Check onclick attribute for WhatsApp URLs var onclick = element.getAttribute && element.getAttribute("onclick"); if (onclick) { // Look for whatsapp:// URLs in onclick var whatsappMatch = onclick.match(/(whatsapp:\/\/[^'")\s]+)/); if (whatsappMatch) { return whatsappMatch[1]; } // Look for wa.me URLs in onclick var wameMatch = onclick.match(/(https?:\/\/wa\.me\/[^'")\s]+)/); if (wameMatch) { return wameMatch[1]; } // Look for api.whatsapp.com URLs in onclick var apiMatch = onclick.match( /(https?:\/\/api\.whatsapp\.com\/[^'")\s]+)/ ); if (apiMatch) { return apiMatch[1]; } } return null; } function handleClick(e) { var clickedElement = e.target; var whatsappUrl = null; // Check the clicked element and its parents for WhatsApp URLs var currentElement = clickedElement; var depth = 0; while (currentElement && depth < 5) { // Check up to 5 levels up whatsappUrl = findWhatsAppUrl(currentElement); if (whatsappUrl) { break; } currentElement = currentElement.parentElement; depth++; } // Fallback: original anchor tag detection if (!whatsappUrl) { var path = e.composedPath ? e.composedPath() : []; var a = (path.find && path.find(function (el) { return el && el.tagName === "A"; })) || (e.target.closest && e.target.closest("a")); if (a) { var href = a.getAttribute("href"); if (href && isWhatsAppUrl(href)) { whatsappUrl = href; } } } if (!whatsappUrl) { return; } console.log("โœ… WhatsApp URL detected, sending data..."); var dto = buildDtoFromHref(whatsappUrl); postJSON(CFG.endpoint, dto); } function handleFormSubmit(e) { var form = e.target; var action = form.getAttribute("action"); if (action && isWhatsAppUrl(action)) { console.log("โœ… WhatsApp URL detected, sending data..."); var dto = buildDtoFromHref(action); postJSON(CFG.endpoint, dto); } } function handleSubmitButtonClick(e) { var button = e.target; var form = button.form || button.closest("form"); if (!form) { return; } // Check if form action is WhatsApp URL var action = form.getAttribute("action"); if (action && isWhatsAppUrl(action)) { console.log("โœ… WhatsApp URL detected, sending data..."); var dto = buildDtoFromHref(action); postJSON(CFG.endpoint, dto); } // Also check if the button itself has WhatsApp-related attributes var buttonUrl = findWhatsAppUrl(button); if (buttonUrl) { console.log("โœ… WhatsApp URL detected, sending data..."); var dto = buildDtoFromHref(buttonUrl); postJSON(CFG.endpoint, dto); } } // Enhanced click handler to detect submit buttons function enhancedHandleClick(e) { var element = e.target; // Check if it's a submit button if ( element.type === "submit" || (element.tagName === "BUTTON" && !element.type) ) { handleSubmitButtonClick(e); } // Always run the original click handler too handleClick(e); } // Store last clicked element for navigation tracking var lastClickedElement = null; var lastClickTime = 0; function storeClickInfo(e) { lastClickedElement = e.target; lastClickTime = Date.now(); } // Track navigation attempts (beforeunload) function handleBeforeUnload(e) { console.log("๐Ÿš€ Page about to navigate/unload"); // Check if we have a recent click (within 2 seconds) var timeSinceClick = Date.now() - lastClickTime; if (timeSinceClick > 2000) { console.log("โŒ No recent click, ignoring navigation"); return; } console.log("๐Ÿš€ Recent click detected (" + timeSinceClick + "ms ago)"); // Try to find WhatsApp URL from the clicked element if (lastClickedElement) { var whatsappUrl = findWhatsAppUrl(lastClickedElement); // Check parent elements too if (!whatsappUrl) { var parent = lastClickedElement.parentElement; var depth = 0; while (parent && depth < 5) { whatsappUrl = findWhatsAppUrl(parent); if (whatsappUrl) break; parent = parent.parentElement; depth++; } } if (whatsappUrl) { console.log( "โœ… Found WhatsApp URL from clicked element:", whatsappUrl ); var dto = buildDtoFromHref(whatsappUrl); console.log("๐Ÿ“ค Sending navigation DTO:", dto); postJSON(CFG.endpoint, dto); } else { console.log("โŒ No WhatsApp URL found in clicked element"); } } } // Track window.open calls (many buttons use this) var originalWindowOpen = window.open; window.open = function (url, target, features) { if (url && isWhatsAppUrl(url)) { console.log("โœ… WhatsApp URL detected, sending data..."); var dto = buildDtoFromHref(url); postJSON(CFG.endpoint, dto); } return originalWindowOpen.call(this, url, target, features); }; // Track location changes (with error handling) try { var originalLocationAssign = location.assign; var originalLocationReplace = location.replace; if (originalLocationAssign) { location.assign = function (url) { if (url && isWhatsAppUrl(url)) { console.log("โœ… WhatsApp URL detected, sending data..."); var dto = buildDtoFromHref(url); postJSON(CFG.endpoint, dto); } return originalLocationAssign.call(this, url); }; } if (originalLocationReplace) { location.replace = function (url) { if (url && isWhatsAppUrl(url)) { console.log("โœ… WhatsApp URL detected, sending data..."); var dto = buildDtoFromHref(url); postJSON(CFG.endpoint, dto); } return originalLocationReplace.call(this, url); }; } } catch (e) { // Silent fail } // Track href changes (with error handling) try { var locationDescriptor = Object.getOwnPropertyDescriptor( Location.prototype, "href" ); if (locationDescriptor && locationDescriptor.set) { var originalLocationHrefSetter = locationDescriptor.set; Object.defineProperty(location, "href", { set: function (url) { if (url && isWhatsAppUrl(url)) { console.log("โœ… WhatsApp URL detected, sending data..."); var dto = buildDtoFromHref(url); postJSON(CFG.endpoint, dto); } return originalLocationHrefSetter.call(this, url); }, get: function () { return location.href; }, }); } } catch (e) { // Silent fail } // Event listeners document.addEventListener("click", storeClickInfo, true); document.addEventListener("auxclick", storeClickInfo, true); document.addEventListener( "keydown", function (e) { if (e.key === "Enter") { storeClickInfo(e); } }, true ); // Phone number detection listeners - ONLY on blur document.addEventListener("blur", handleInputBlur, true); document.addEventListener( "paste", function (e) { // Handle paste events with a small delay to get the pasted content setTimeout(function () { handleInputBlur(e); }, 10); }, true ); // Navigation tracking window.addEventListener("beforeunload", handleBeforeUnload, true); // Keep original handlers as backup document.addEventListener("click", enhancedHandleClick, true); document.addEventListener("auxclick", enhancedHandleClick, true); document.addEventListener("submit", handleFormSubmit, true); // Debug helpers window.VambeTracker = { previewDto: function (href) { try { console.log("๐Ÿ” Preview DTO for:", href); console.log(buildDtoFromHref(href)); } catch (e) { console.error("โŒ Error in previewDto:", e); } }, getParams: function () { return getCookiesByPrefix(CFG.cookiePrefix); }, testDeepLink: function () { // Test with the provided example var testLink = "whatsapp://send?phone=56999854377&text=%C2%A1Hola!%20Quiero%20saber%2C%20%C2%BFqu%C3%A9%20es%20lo%20que%20Vambe%20puede%20hacer%20por%20mi%20negocio%3F"; console.log("๐Ÿงช Testing deep link example:"); this.previewDto(testLink); }, testPhone: function (phone) { console.log("๐Ÿงช Testing phone number:", phone); console.log("๐Ÿ“ž Is phone number:", isPhoneNumber(phone)); console.log("๐Ÿ“ž Normalized:", normalizePhone(phone)); if (isPhoneNumber(phone)) { console.log("โœ… Would create phone event!"); } }, getDetectedPhones: function () { return Array.from(detectedPhones); }, testCommonPhones: function () { var testNumbers = [ "+56999854377", "56 9 9985 4377", "(555) 123-4567", "+1-555-123-4567", "555.123.4567", "5551234567", "+34 612 345 678", "123", // Should fail "abc123def", // Should fail ]; console.log("๐Ÿงช Testing common phone formats:"); testNumbers.forEach(function (num) { console.log( num + " -> " + (isPhoneNumber(num) ? "โœ… VALID" : "โŒ INVALID") ); }); }, }; } catch (e) { console.error("๐Ÿ’ฅ Fatal error in Vambe Tracker:", e); } })();