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 // Data from PHP (may be missing if no events were queued for initial render). 17 // Keep Store API interception active even when eventQueue is empty/undefined. 18 var data = (typeof wc_facebook_pixel_data !== 'undefined' && wc_facebook_pixel_data) ? 19 wc_facebook_pixel_data : 20 { eventQueue: [] }; 21 var firedEvents = {}; 22 23 /** 24 * Build event data object for fbq() 25 * 26 * @param {Object} event Event object from PHP 27 * @return {Object} Prepared event data 28 */ 29 function buildEventData(event) { 30 return { 31 method: event.method || 'track', 32 name: event.name, 33 params: event.params || {}, 34 eventId: event.eventId || null 35 }; 36 } 37 38 /** 39 * Check if event should be skipped (already fired) 40 * 41 * @param {string|null} eventId Event ID for deduplication 42 * @return {boolean} True if should skip 43 */ 44 function shouldSkipEvent(eventId) { 45 return eventId && firedEvents[eventId]; 46 } 47 48 /** 49 * Mark event as fired for deduplication 50 * 51 * @param {string|null} eventId Event ID 52 */ 53 function markEventFired(eventId) { 54 if (eventId) { 55 firedEvents[eventId] = true; 56 } 57 } 58 59 /** 60 * Log warning to console (with safety check) 61 * 62 * @param {string} message Warning message 63 * @param {*} data Additional data to log 64 */ 65 function logWarning(message, data) { 66 if (typeof console !== 'undefined' && console.warn) { 67 console.warn('[FB Pixel]', message, data); 68 } 69 } 70 71 /** 72 * Fire a single event using fbq() 73 * 74 * @param {Object} event Event object with name, params, method, eventId 75 */ 76 function fireEvent(event) { 77 var eventData = buildEventData(event); 78 79 // Skip if already fired (deduplication) 80 if (shouldSkipEvent(eventData.eventId)) { 81 return; 82 } 83 84 try { 85 var params = eventData.params; 86 87 if (window.FacebookSignals && typeof window.FacebookSignals.trackEvent === 'function') { 88 window.FacebookSignals.trackEvent(eventData.name, params, null, eventData.method, eventData.eventId); 89 } else { 90 if (typeof fbq !== 'function') { 91 logWarning('fbq not available, skipping event:', eventData.name); 92 return; 93 } 94 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 102 markEventFired(eventData.eventId); 103 104 } catch (e) { 105 logWarning('Event error: ' + eventData.name, e); 106 } 107 } 108 109 /** 110 * Fire all queued events from PHP 111 */ 112 function fireQueuedEvents() { 113 var events = data.eventQueue; 114 115 if (!events || !Array.isArray(events)) { 116 return; 117 } 118 119 for (var i = 0; i < events.length; i++) { 120 try { 121 fireEvent(events[i]); 122 } catch (e) { 123 logWarning('fireQueuedEvents loop error:', e); 124 } 125 } 126 127 // Clear events after firing to prevent duplicate firing 128 data.eventQueue = []; 129 } 130 131 // ========================================================================= 132 // WooCommerce Blocks: Store API approach 133 // ========================================================================= 134 135 /** 136 * Process pixel event data from Store API response. 137 * 138 * @param {Object} eventData Event data from Store API extensions 139 */ 140 function processStoreApiEvent(eventData) { 141 if (!eventData || !eventData.event) { 142 return; 143 } 144 145 var params = eventData.params || {}; 146 147 var event = { 148 method: 'track', 149 name: eventData.event, 150 params: params, 151 eventId: params.event_id || null 152 }; 153 154 fireEvent(event); 155 } 156 157 /** 158 * Returns the request URL from a fetch() input. 159 * 160 * @param {*} input fetch() input argument 161 * @return {string} 162 */ 163 function getRequestUrl(input) { 164 if (typeof input === 'string') { 165 return input; 166 } 167 168 if (input && typeof input.url === 'string') { 169 return input.url; 170 } 171 172 return ''; 173 } 174 175 /** 176 * Checks whether a URL targets the Store API add-item endpoint. 177 * 178 * @param {string} url Request URL 179 * @return {boolean} 180 */ 181 function isStoreApiAddItemRequest(url) { 182 return typeof url === 'string' && 183 (url.indexOf('/wc/store/v1/cart/add-item') !== -1 || 184 url.indexOf('/wc/store/cart/add-item') !== -1); 185 } 186 187 /** 188 * Checks whether a URL targets the Store API batch endpoint. 189 * 190 * @param {string} url Request URL 191 * @return {boolean} 192 */ 193 function isStoreApiBatchRequest(url) { 194 return typeof url === 'string' && 195 (url.indexOf('/wc/store/v1/batch') !== -1 || 196 url.indexOf('/wc/store/batch') !== -1); 197 } 198 199 /** 200 * Process Store API response payload and extract plugin extension data. 201 * Handles both direct cart responses and batch responses. 202 * 203 * @param {Object} responseData Parsed Store API response payload 204 */ 205 function processStoreApiResponseData(responseData) { 206 if (!responseData || typeof responseData !== 'object') { 207 return; 208 } 209 210 // Direct response shape: { extensions: { 'facebook-for-woocommerce': ... } } 211 if (responseData.extensions && responseData.extensions['facebook-for-woocommerce']) { 212 processStoreApiEvent(responseData.extensions['facebook-for-woocommerce']); 213 } else if (Array.isArray(responseData.responses)) { 214 // Batch response shape: { responses: [ { body: { extensions: ... } } ] } 215 for (var i = 0; i < responseData.responses.length; i++) { 216 var item = responseData.responses[i]; 217 if (item && item.body && item.body.extensions && item.body.extensions['facebook-for-woocommerce'] && 218 item.status >= 200 && item.status < 300) { 219 processStoreApiEvent(item.body.extensions['facebook-for-woocommerce']); 220 } 221 } 222 } 223 } 224 225 /** 226 * Set up fetch interceptor to capture Store API responses. 227 * Intercepts cart/add-item and batch requests to fire AddToCart pixel events. 228 */ 229 function setupFetchInterceptor() { 230 var originalFetch = window.fetch; 231 if (!originalFetch) { 232 return; 233 } 234 235 window.fetch = function() { 236 var args = arguments; 237 var url = getRequestUrl(args[0]); 238 239 var isAddToCartRequest = isStoreApiAddItemRequest(url); 240 var isBatchRequest = isStoreApiBatchRequest(url); 241 242 return originalFetch.apply(this, args).then(function(response) { 243 if ((isAddToCartRequest || isBatchRequest) && response.ok) { 244 // Clone response so we can read it without consuming 245 response.clone().json().then(function(responseData) { 246 processStoreApiResponseData(responseData); 247 }).catch(function(e) { 248 logWarning('Store API JSON parse error:', e); 249 }); 250 } 251 return response; 252 }); 253 }; 254 } 255 256 /** 257 * Initialize pixel event handling. 258 * 259 * If fbq() is already available, fires queued events immediately. 260 * If not (e.g. consent manager blocking the SDK), uses Object.defineProperty 261 * to set a trap on window.fbq â our handler fires automatically the moment 262 * fbq is assigned, with zero overhead in between. No polling, no timers. 263 * 264 * Also sets up Store API interceptor for WooCommerce Blocks AJAX AddToCart. 265 */
266 function init() { 267 // Set up fetch interceptor for WooCommerce Blocks Store API 268 setupFetchInterceptor(); 269 270 if (typeof fbq === 'function') { 271 fireQueuedEvents(); 272 return; 273 } 274 275 // fbq doesn't exist yet â watch for it (zero overhead, no timers). 276 // Consent managers block fbq until the 277 // user accepts. This fires the moment they assign window.fbq. 278 var _fbq = window.fbq; 279 Object.defineProperty(window, 'fbq', { 280 configurable: true, 281 enumerable: true, 282 get: function() { return _fbq; }, 283 set: function(value) { 284 _fbq = value; 285 if (typeof value === 'function') { 286 // Restore normal property so FB SDK works normally 287 Object.defineProperty(window, 'fbq', { 288 configurable: true, 289 enumerable: true, 290 writable: true, 291 value: value 292 }); 293 setTimeout(fireQueuedEvents, 0); 294 } 295 } 296 }); 297 } 298 299 // Start 300 if (document.readyState === 'complete') { 301 init(); 302 } else { 303 window.addEventListener('load', init); 304 } 305 306})();
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.