1/** 2 * Facebook Pixel Events - External JavaScript Handler 3 * 4 * This script fires pixel events in an isolated execution context, 5 * ensuring events are sent even if other plugins cause JavaScript errors. 6 * 7 * Supports WooCommerce Blocks via Store API fetch interception. 8 * The pixel event data is read from the Store API response extensions. 9 * 10 * @package FacebookCommerce 11 */ 12 13(function() { 14 'use strict'; 15 16 // Early exit if no data from PHP 17 if (typeof wc_facebook_pixel_data === 'undefined') { 18 return; 19 } 20 21 var data = wc_facebook_pixel_data; 22 var firedEvents = {}; 23 24 /** 25 * Build event data object for fbq() 26 * 27 * @param {Object} event Event object from PHP 28 * @return {Object} Prepared event data 29 */ 30 function buildEventData(event) { 31 return { 32 method: event.method || 'track', 33 name: event.name, 34 params: event.params || {}, 35 eventId: event.eventId || null 36 }; 37 } 38 39 /** 40 * Check if event should be skipped (already fired) 41 * 42 * @param {string|null} eventId Event ID for deduplication 43 * @return {boolean} True if should skip 44 */ 45 function shouldSkipEvent(eventId) { 46 return eventId && firedEvents[eventId]; 47 } 48 49 /** 50 * Mark event as fired for deduplication 51 * 52 * @param {string|null} eventId Event ID 53 */ 54 function markEventFired(eventId) { 55 if (eventId) { 56 firedEvents[eventId] = true; 57 } 58 } 59 60 /** 61 * Log warning to console (with safety check) 62 * 63 * @param {string} message Warning message 64 * @param {*} data Additional data to log 65 */ 66 function logWarning(message, data) { 67 if (typeof console !== 'undefined' && console.warn) { 68 console.warn('[FB Pixel]', message, data); 69 } 70 } 71 72 /** 73 * Fire a single event using fbq() 74 * 75 * @param {Object} event Event object with name, params, method, eventId 76 */ 77 function fireEvent(event) { 78 var eventData = buildEventData(event); 79 80 // Skip if already fired (deduplication) 81 if (shouldSkipEvent(eventData.eventId)) { 82 return; 83 } 84 85 // Skip if fbq not available 86 if (typeof fbq !== 'function') { 87 logWarning('fbq not available, skipping event:', eventData.name); 88 return; 89 } 90 91 try { 92 var params = eventData.params; 93 94 // Fire the event with eventID as 4th argument for deduplication 95 if (eventData.eventId) { 96 fbq(eventData.method, eventData.name, params, {eventID: eventData.eventId}); 97 } else { 98 fbq(eventData.method, eventData.name, params); 99 } 100 101 markEventFired(eventData.eventId); 102 103 } catch (e) { 104 logWarning('Event error: ' + eventData.name, e); 105 } 106 } 107 108 /** 109 * Fire all queued events from PHP 110 */ 111 function fireQueuedEvents() { 112 var events = data.eventQueue; 113 114 if (!events || !Array.isArray(events)) { 115 return; 116 } 117 118 for (var i = 0; i < events.length; i++) { 119 try { 120 fireEvent(events[i]); 121 } catch (e) { 122 logWarning('fireQueuedEvents loop error:', e); 123 } 124 } 125 126 // Clear events after firing to prevent duplicate firing 127 data.eventQueue = []; 128 } 129 130 // ========================================================================= 131 // WooCommerce Blocks: Store API approach 132 // ========================================================================= 133 134 /** 135 * Process pixel event data from Store API response. 136 * 137 * @param {Object} eventData Event data from Store API extensions 138 */ 139 function processStoreApiEvent(eventData) { 140 if (!eventData || !eventData.event) { 141 return; 142 } 143 144 var params = eventData.params || {}; 145 146 var event = { 147 method: 'track', 148 name: eventData.event, 149 params: params, 150 eventId: params.event_id || null 151 }; 152 153 fireEvent(event); 154 } 155 156 /** 157 * Set up fetch interceptor to capture Store API responses. 158 * Only intercepts cart/add-item requests to fire AddToCart pixel events. 159 */ 160 function setupFetchInterceptor() { 161 var originalFetch = window.fetch; 162 if (!originalFetch) { 163 return; 164 } 165 166 window.fetch = function() { 167 var args = arguments; 168 var url = args[0]; 169 170 // Only intercept add-item requests (not general cart requests) 171 var isAddToCartRequest = typeof url === 'string' && 172 (url.indexOf('/wc/store/v1/cart/add-item') !== -1 || 173 url.indexOf('/wc/store/cart/add-item') !== -1); 174 175 return originalFetch.apply(this, args).then(function(response) { 176 if (isAddToCartRequest && response.ok) { 177 // Clone response so we can read it without consuming 178 response.clone().json().then(function(responseData) { 179 if (responseData && responseData.extensions && responseData.extensions['facebook-for-woocommerce']) { 180 processStoreApiEvent(responseData.extensions['facebook-for-woocommerce']); 181 } 182 }).catch(function(e) { 183 logWarning('Store API JSON parse error:', e); 184 }); 185 } 186 return response; 187 }); 188 }; 189 } 190 191 /** 192 * Initialize pixel event handling. 193 * 194 * If fbq() is already available, fires queued events immediately. 195 * If not (e.g. consent manager blocking the SDK), uses Object.defineProperty 196 * to set a trap on window.fbq â our handler fires automatically the moment 197 * fbq is assigned, with zero overhead in between. No polling, no timers. 198 * 199 * Also sets up Store API interceptor for WooCommerce Blocks AJAX AddToCart. 200 */
201 function init() { 202 // Set up fetch interceptor for WooCommerce Blocks Store API 203 setupFetchInterceptor(); 204 205 if (typeof fbq === 'function') { 206 fireQueuedEvents(); 207 return; 208 } 209 210 // fbq doesn't exist yet â watch for it (zero overhead, no timers). 211 // Consent managers block fbq until the 212 // user accepts. This fires the moment they assign window.fbq. 213 var _fbq = window.fbq; 214 Object.defineProperty(window, 'fbq', { 215 configurable: true, 216 enumerable: true, 217 get: function() { return _fbq; }, 218 set: function(value) { 219 _fbq = value; 220 if (typeof value === 'function') { 221 // Restore normal property so FB SDK works normally 222 Object.defineProperty(window, 'fbq', { 223 configurable: true, 224 enumerable: true, 225 writable: true, 226 value: value 227 }); 228 setTimeout(fireQueuedEvents, 0); 229 } 230 } 231 }); 232 } 233 234 // Start 235 if (document.readyState === 'complete') { 236 init(); 237 } else { 238 window.addEventListener('load', init); 239 } 240 241})();
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.