*/ (function() { 'use strict'; const CONFIG = { storageKey: 'aos_utm_data', storageDuration: 30, // days utmParams: ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'], // Shopify attribution mapping shopifyFields: { utm_source: 'source', utm_medium: 'medium', utm_campaign: 'campaign', utm_content: 'content', utm_term: 'term', landing_page: 'landing_site', referrer: 'referring_site' } }; // ============================================ // STEP 1: CAPTURE & STORE UTM PARAMETERS // ============================================ function captureUTMFromURL() { const params = new URLSearchParams(window.location.search); const utmData = {}; let hasUTM = false; CONFIG.utmParams.forEach(param => { if (params.has(param)) { utmData[param] = params.get(param); hasUTM = true; } }); return hasUTM ? utmData : null; } function getStoredUTMData() { try { const stored = localStorage.getItem(CONFIG.storageKey); if (stored) { const data = JSON.parse(stored); // Check if data is still valid (within duration) const timestamp = data.timestamp || 0; const now = Date.now(); const maxAge = CONFIG.storageDuration * 24 * 60 * 60 * 1000; if (now - timestamp < maxAge) { return data; } } } catch (e) { console.error('Error reading UTM data:', e); } return null; } function saveUTMData(utmData) { try { const dataToStore = { ...utmData, timestamp: Date.now(), landing_page: utmData.landing_page || window.location.href, referrer: utmData.referrer || document.referrer || 'direct' }; localStorage.setItem(CONFIG.storageKey, JSON.stringify(dataToStore)); // Also set as cookie for server-side access setCookie(CONFIG.storageKey, JSON.stringify(dataToStore), CONFIG.storageDuration); console.log('UTM data saved:', dataToStore); return dataToStore; } catch (e) { console.error('Error saving UTM data:', e); return null; } } function setCookie(name, value, days) { const date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); const expires = "expires=" + date.toUTCString(); document.cookie = name + "=" + encodeURIComponent(value) + ";" + expires + ";path=/;SameSite=Lax"; } // ============================================ // STEP 2: MANAGE UTM DATA (NEW OR STORED) // ============================================ function initializeUTMTracking() { // Check for new UTM parameters in current URL const newUTM = captureUTMFromURL(); if (newUTM) { // New UTM found - overwrite stored data console.log('New UTM parameters detected, updating storage'); return saveUTMData({ ...newUTM, landing_page: window.location.href, referrer: document.referrer || 'direct' }); } else { // No new UTM - get stored data const storedUTM = getStoredUTMData(); if (!storedUTM) { // First visit, no UTM - save as direct traffic console.log('First visit without UTM, marking as direct'); return saveUTMData({ utm_source: 'direct', utm_medium: 'none', landing_page: window.location.href, referrer: document.referrer || 'direct' }); } console.log('Using stored UTM data:', storedUTM); return storedUTM; } } // ============================================ // STEP 3: AUTO-APPEND UTM TO INTERNAL LINKS // ============================================ function appendUTMToURL(url, utmData) { try { const urlObj = new URL(url, window.location.origin); // Only add UTM if not already present CONFIG.utmParams.forEach(param => { if (utmData[param] && !urlObj.searchParams.has(param)) { urlObj.searchParams.set(param, utmData[param]); } }); return urlObj.toString(); } catch (e) { return url; // Return original if parsing fails } } function isInternalLink(url) { try { const urlObj = new URL(url, window.location.origin); return urlObj.hostname === window.location.hostname; } catch (e) { // Relative URLs are internal return !url.startsWith('http://') && !url.startsWith('https://'); } } function hasUTMParameters(url) { try { const urlObj = new URL(url, window.location.origin); return CONFIG.utmParams.some(param => urlObj.searchParams.has(param)); } catch (e) { return false; } } function addUTMToInternalLinks(utmData) { if (!utmData) return; // Function to update a single link function updateLink(link) { const href = link.getAttribute('href'); if (!href || href === '#' || href.startsWith('javascript:') || href.startsWith('mailto:') || href.startsWith('tel:')) { return; } // Only update internal links without UTM if (isInternalLink(href) && !hasUTMParameters(href)) { const newHref = appendUTMToURL(href, utmData); link.setAttribute('href', newHref); } } // Update all existing links document.querySelectorAll('a[href]').forEach(updateLink); // Watch for dynamically added links const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === 1) { // Element node if (node.tagName === 'A' && node.hasAttribute('href')) { updateLink(node); } // Also check children node.querySelectorAll && node.querySelectorAll('a[href]').forEach(updateLink); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); } // ============================================ // STEP 4: SEND TO SHOPIFY ATTRIBUTION // ============================================ function sendToShopifyAttribution(utmData) { if (!utmData) return; // Method 1: Use Shopify's analytics tracking if (window.ShopifyAnalytics && window.ShopifyAnalytics.lib) { try { const attributionData = {}; Object.keys(CONFIG.shopifyFields).forEach(key => { const shopifyField = CONFIG.shopifyFields[key]; if (utmData[key]) { attributionData[shopifyField] = utmData[key]; } }); // Set landing site if (utmData.landing_page) { attributionData.landing_site = utmData.landing_page; } // Set referring site if (utmData.referrer && utmData.referrer !== 'direct') { attributionData.referring_site = utmData.referrer; } console.log('Sending to Shopify Analytics:', attributionData); // This sets the attribution data for the session if (window.ShopifyAnalytics.lib.config) { window.ShopifyAnalytics.lib.config.Attribution = attributionData; } } catch (e) { console.error('Error setting Shopify attribution:', e); } } // Method 2: Add to checkout attributes (for order-level tracking) addToCheckoutAttributes(utmData); // Method 3: Add to cart note attributes addToCartAttributes(utmData); } function addToCheckoutAttributes(utmData) { // This will be picked up during checkout window._shopifyCheckoutAttributes = window._shopifyCheckoutAttributes || {}; CONFIG.utmParams.forEach(param => { if (utmData[param]) { window._shopifyCheckoutAttributes[param] = utmData[param]; } }); if (utmData.landing_page) { window._shopifyCheckoutAttributes['landing_page'] = utmData.landing_page; } if (utmData.referrer) { window._shopifyCheckoutAttributes['referrer'] = utmData.referrer; } } function addToCartAttributes(utmData) { // Hook into cart form submissions document.addEventListener('submit', function(e) { const form = e.target; // Check if it's a cart form if (form.action && (form.action.includes('/cart/add') || form.action.includes('/cart'))) { // Add hidden fields for UTM data CONFIG.utmParams.forEach(param => { if (utmData[param]) { addHiddenField(form, `attributes[${param}]`, utmData[param]); } }); if (utmData.landing_page) { addHiddenField(form, 'attributes[landing_page]', utmData.landing_page); } if (utmData.referrer) { addHiddenField(form, 'attributes[referrer]', utmData.referrer); } } }); } function addHiddenField(form, name, value) { // Check if field already exists let field = form.querySelector(`input[name="${name}"]`); if (!field) { field = document.createElement('input'); field.type = 'hidden'; field.name = name; form.appendChild(field); } field.value = value; } // ============================================ // STEP 5: AJAX CART HANDLING // ============================================ function interceptAjaxCalls(utmData) { // Intercept fetch calls const originalFetch = window.fetch; window.fetch = function(...args) { let [url, options] = args; if (typeof url === 'string' && url.includes('/cart/add')) { options = options || {}; // Add UTM data to body if it's a POST request if (options.method === 'POST' || !options.method) { try { const body = options.body ? JSON.parse(options.body) : {}; body.attributes = body.attributes || {}; CONFIG.utmParams.forEach(param => { if (utmData[param]) { body.attributes[param] = utmData[param]; } }); if (utmData.landing_page) body.attributes.landing_page = utmData.landing_page; if (utmData.referrer) body.attributes.referrer = utmData.referrer; options.body = JSON.stringify(body); } catch (e) { console.error('Error adding UTM to fetch:', e); } } } return originalFetch.apply(this, [url, options]); }; // Intercept jQuery AJAX if jQuery is loaded if (window.jQuery) { const originalAjax = jQuery.ajax; jQuery.ajax = function(url, options) { if (typeof url === 'object') { options = url; url = options.url; } if (url && url.includes('/cart/add')) { options.data = options.data || {}; if (typeof options.data === 'object') { options.data.attributes = options.data.attributes || {}; CONFIG.utmParams.forEach(param => { if (utmData[param]) { options.data.attributes[param] = utmData[param]; } }); if (utmData.landing_page) options.data.attributes.landing_page = utmData.landing_page; if (utmData.referrer) options.data.attributes.referrer = utmData.referrer; } } return originalAjax.call(this, url, options); }; } } // ============================================ // STEP 6: INITIALIZE EVERYTHING // ============================================ function init() { console.log('Initializing UTM Tracking System...'); // Initialize UTM tracking (capture new or get stored) const utmData = initializeUTMTracking(); if (utmData) { // Add UTM to all internal links if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => addUTMToInternalLinks(utmData)); } else { addUTMToInternalLinks(utmData); } // Send to Shopify attribution sendToShopifyAttribution(utmData); // Intercept AJAX cart calls interceptAjaxCalls(utmData); console.log('UTM Tracking System initialized successfully'); } } // Start the tracking system init(); // Expose utility function to get current UTM data window.getUTMData = function() { return getStoredUTMData(); }; // Expose function to manually clear UTM data window.clearUTMData = function() { localStorage.removeItem(CONFIG.storageKey); console.log('UTM data cleared'); }; })();
Skip to Main Content