(function () { // Initialize digitalData IMMEDIATELY — before Launch reads it if (!window.digitalData) { window.digitalData = { page: {}, component: [], search: {}, user: { consent: {} }, event: {} }; } function init() { if (document.readyState === 'complete') { onDomReady(); } else { window.addEventListener('load', onDomReady); } // Declare components var componentList = {}; const components = [] function shouldExcludeComponent(el, name) { if (el.closest('#onetrust-consent-sdk')) return true if (el.classList.contains('phs-location-link')) return true return false } function detectComponents() { /** * ✅ CHANGE: Added a `newCount` counter to track how many NEW components * were discovered in this pass. This is used by tryDetectComponentsWithDelay * to know if the DOM is still rendering new elements, and also helps with * debugging in the console. */ let newCount = 0; document.querySelectorAll('a, button').forEach((el) => { const ariaLabel = el.getAttribute('aria-label') || '' const textContent = el.textContent.trim().replace(/\s+/g, ' ') let name = ariaLabel || textContent || 'Unnamed' name = name.replace(/^\d+\s+/, '').trim() if (shouldExcludeComponent(el, name)) return let type = 'Unknown' if (el.classList.contains('phw-btn')) { type = 'Button' } else if (el.classList.contains('phw-link')) { type = 'Link' } else if (el.classList.contains('phw-header-logo')) { type = 'Image' } else { type = el.tagName.toLowerCase() === 'button' ? 'Button' : 'Link' } const inHeader = el.closest('header') const inFooter = el.closest('footer') let prefix = '' if (inHeader) prefix = 'header_' else if (inFooter) prefix = 'footer_' let componentId = prefix + name componentId = componentId.replace(/^\d+\s+/, '').trim() if (componentList[componentId]) return componentList[componentId] = { type: type, name: name, linkId: el.getAttribute('href') || 'None', } components.push({ componentInfo: { componentId: componentId, name: name, type: type, }, }) /** * ✅ CHANGE: Increment counter for each newly registered component */ newCount++; }) if (components.length > 0) { window.digitalData.component = components } /** * ✅ CHANGE: Added console log showing how many new vs total components found. * Helps debug production issues without needing a debugger. */ console.log('[Tracking] detectComponents found', newCount, 'new components. Total:', components.length); return newCount; } /** * ✅ NEW FUNCTION: MutationObserver-based component detection. * * WHY: The original code only scanned for components at fixed intervals (retries). * In production, content is often lazy-loaded or rendered by a JS framework AFTER * those retries have finished. This observer watches for ANY new DOM nodes being * added and re-runs detectComponents() when that happens. * * DEBOUNCE: Uses a 500ms debounce so rapid DOM changes (e.g., a framework rendering * 50 elements at once) only trigger one scan, not 50. * * AUTO-DISCONNECT: Stops observing after 60 seconds to avoid long-term performance impact. */ function startComponentObserver() { var observer = new MutationObserver(function (mutations) { var hasNewNodes = false; for (var i = 0; i < mutations.length; i++) { if (mutations[i].addedNodes.length > 0) { hasNewNodes = true; break; } } if (hasNewNodes) { clearTimeout(startComponentObserver._timer); startComponentObserver._timer = setTimeout(function () { detectComponents(); }, 500); } }); observer.observe(document.body, { childList: true, subtree: true }); setTimeout(function () { observer.disconnect(); console.log('[Tracking] Component observer disconnected after 60s'); }, 60000); } /** * ✅ CHANGE: Reworked retry logic. * * ORIGINAL BUG: The old code checked: * if (!window.digitalData.component || window.digitalData.component.length === 0) * This meant if even ONE component was found on the first pass, it stopped retrying. * Any components rendered after that first pass were never captured. * * FIX: Now always calls detectComponents() on every retry pass regardless of * current count. The deduplication in detectComponents() (via componentList[componentId]) * ensures no duplicates. Increased retries from 5 to 10 for production environments * with slower rendering. */ function tryDetectComponentsWithDelay(retries = 10, delay = 1000) { if (retries <= 0) return if (document.readyState !== 'complete') { return setTimeout(() => tryDetectComponentsWithDelay(retries - 1, delay), delay) } // ✅ CHANGE: Removed the "only retry if empty" condition. // Now always re-scans to catch newly rendered components. detectComponents(); setTimeout(() => tryDetectComponentsWithDelay(retries - 1, delay), delay); } // Safety net — fix tracker ready state after page load beacons function fixTrackerReadyState() { try { var tracker = Object.values(window.s_c_il).find(function (o) { return o._c === 's_c'; }); if (tracker && typeof tracker.isReadyToTrack === 'function' && !tracker.isReadyToTrack()) { var originalIsReady = tracker.isReadyToTrack.bind(tracker); tracker.isReadyToTrack = function () { if (!originalIsReady() && tracker.visitor && tracker.visitor.getMarketingCloudVisitorID()) { return true; } return originalIsReady(); }; console.log('[Tracking] ✅ Tracker ready state fix applied'); } } catch (e) { console.warn('[Tracking] Could not apply tracker fix:', e); } } /** * ✅ NEW FUNCTION: On-demand component registration. * * WHY: This is the CRITICAL safety net. Even with MutationObserver and retries, * there's always a chance a component renders after all detection has finished. * * HOW: When a user clicks an or