1(function(window, document) { 2 'use strict'; 3 4 if (typeof window.WeakSet === 'undefined' || typeof window.WeakMap === 'undefined' || typeof window.Set === 'undefined') { 5 try { 6 if (window.console && typeof window.console.error === 'function') { 7 window.console.error('[BrandProductEventTracker]', { 8 reason: 'unknown_error', 9 stage: 'init', 10 sourceEvent: '', 11 widgetCode: '', 12 prodCode: '', 13 error: null 14 }); 15 } 16 } catch (_error) { 17 } 18 return; 19 } 20 21 class BrandProductEventTracker { 22 static init() { 23 if (BrandProductEventTracker.initialized) { 24 return; 25 } 26 27 BrandProductEventTracker.initialized = true; 28 BrandProductEventTracker.ensureObserver(); 29 BrandProductEventTracker.scan(); 30 BrandProductEventTracker.flushQueue(); 31 } 32 33 static scan(widgetCode) { 34 try { 35 const root = BrandProductEventTracker.getScanRoot(widgetCode); 36 if (!root) { 37 return; 38 } 39 40 const cards = root.querySelectorAll('._shop_item[data-brand-product-event-properties]'); 41 cards.forEach(function(card) { 42 BrandProductEventTracker.observeCard(card); 43 }); 44 } catch (error) { 45 BrandProductEventTracker.logFailure('scan_error', 'scan', '', widgetCode || '', '', error); 46 } 47 } 48 49 static getScanRoot(widgetCode) { 50 if (!widgetCode) { 51 return document; 52 } 53 54 return document.getElementById('container_' + widgetCode); 55 } 56 57 static observeCard(card) { 58 if (!card || BrandProductEventTracker.observedCards.has(card)) { 59 return; 60 } 61 62 BrandProductEventTracker.observedCards.add(card); 63 card.addEventListener('click', function(event) { 64 BrandProductEventTracker.handleClick(card, event); 65 }, true); 66 67 const observer = BrandProductEventTracker.ensureObserver(); 68 if (observer) { 69 try { 70 observer.observe(card); 71 } catch (error) { 72 BrandProductEventTracker.logFailure('observer_error', 'view', BrandProductEventTracker.VIEW_EVENT, '', BrandProductEventTracker.getProdCode(card), error); 73 } 74 } 75 } 76 77 static ensureObserver() { 78 if (BrandProductEventTracker.observer) { 79 return BrandProductEventTracker.observer; 80 } 81 82 if (typeof window.IntersectionObserver === 'undefined') { 83 BrandProductEventTracker.logFailure('intersection_observer_unavailable', 'view', BrandProductEventTracker.VIEW_EVENT, '', '', null); 84 return null; 85 } 86 87 try { 88 BrandProductEventTracker.observer = new window.IntersectionObserver(function(entries, observer) { 89 entries.forEach(function(entry) { 90 BrandProductEventTracker.handleIntersection(entry, observer); 91 }); 92 }, { 93 threshold: 0.8 94 }); 95 } catch (error) { 96 BrandProductEventTracker.logFailure('observer_error', 'view', BrandProductEventTracker.VIEW_EVENT, '', '', error); 97 BrandProductEventTracker.observer = null; 98 } 99 100 return BrandProductEventTracker.observer; 101 } 102 103 static handleIntersection(entry, observer) { 104 const card = entry.target; 105 try { 106 if (BrandProductEventTracker.viewedCards.has(card)) { 107 observer.unobserve(card); 108 return; 109 } 110 111 if (entry.isIntersecting) { 112 if (BrandProductEventTracker.intersectionTimers.has(card)) { 113 return; 114 } 115 116 const timer = window.setTimeout(function() { 117 BrandProductEventTracker.intersectionTimers.delete(card); 118 BrandProductEventTracker.fireView(card, BrandProductEventTracker.IMPRESSION_SOURCE_VIEWPORT); 119 observer.unobserve(card); 120 }, BrandProductEventTracker.VIEW_DELAY); 121 122 BrandProductEventTracker.intersectionTimers.set(card, timer); 123 return; 124 } 125 126 BrandProductEventTracker.clearIntersectionTimer(card); 127 } catch (error) { 128 BrandProductEventTracker.logFailure('observer_error', 'view', BrandProductEventTracker.VIEW_EVENT, '', BrandProductEventTracker.getProdCode(card), error); 129 } 130 } 131 132 static handleClick(card, event) { 133 try { 134 const cardActionElement = BrandProductEventTracker.getCardActionElement(card, event.target); 135 if (cardActionElement) { 136 BrandProductEventTracker.handleCardActionClick(card, cardActionElement); 137 return; 138 } 139 140 if (!BrandProductEventTracker.isProductDetailLinkClick(card, event.target)) { 141 return; 142 } 143 144 if (!BrandProductEventTracker.viewedCards.has(card)) { 145 BrandProductEventTracker.fireView(card, BrandProductEventTracker.IMPRESSION_SOURCE_CLICK); 146 } 147 148 BrandProductEventTracker.trackCardEvent(card, BrandProductEventTracker.CLICK_EVENT, 'click'); 149 } catch (error) { 150 BrandProductEventTracker.logFailure('unknown_error', 'click', BrandProductEventTracker.CLICK_EVENT, '', BrandProductEventTracker.getProdCode(card), error); 151 } 152 } 153 154 static handleCardActionClick(card, cardActionElement) { 155 const cardAction = cardActionElement.getAttribute(BrandProductEventTracker.CARD_ACTION_ATTRIBUTE); 156 const cardActionEvent = BrandProductEventTracker.getCardActionEvent(cardAction); 157 if (!cardActionEvent) { 158 return; 159 } 160 161 if (!BrandProductEventTracker.viewedCards.has(card)) { 162 BrandProductEventTracker.fireView(card, BrandProductEventTracker.IMPRESSION_SOURCE_CLICK); 163 } 164 165 const options = { 166 content: cardActionEvent.content 167 }; 168 169 if (cardAction === BrandProductEventTracker.CARD_ACTION_WISHLIST) { 170 options.extraProperties = { 171 wishlist_action: BrandProductEventTracker.getWishlistAction(cardActionElement) 172 }; 173 } 174 175 BrandProductEventTracker.trackCardEvent(card, cardActionEvent.eventName, 'click', options); 176 } 177 178 static getCardActionElement(card, target) { 179 if (!target || typeof target.closest !== 'function') { 180 return null; 181 } 182 183 const actionElement = target.closest('[' + BrandProductEventTracker.CARD_ACTION_ATTRIBUTE + ']'); 184 if (!actionElement || !card.contains(actionElement)) { 185 return null; 186 } 187 188 return actionElement; 189 } 190 191 static getCardActionEvent(cardAction) { 192 switch (cardAction) { 193 case BrandProductEventTracker.CARD_ACTION_REVIEW: 194 return { 195 eventName: BrandProductEventTracker.REVIEW_CLICK_EVENT, 196 content: 'view_review' 197 }; 198 case BrandProductEventTracker.CARD_ACTION_WISHLIST: 199 return { 200 eventName: BrandProductEventTracker.WISHLIST_CLICK_EVENT, 201 content: 'add_to_wishlist' 202 }; 203 case BrandProductEventTracker.CARD_ACTION_CART: 204 return { 205 eventName: BrandProductEventTracker.CART_CLICK_EVENT, 206 content: 'add_to_cart' 207 }; 208 default: 209 return null; 210 } 211 } 212
213 static getWishlistAction(cardActionElement) { 214 return cardActionElement.querySelector('.im-ico-liked') ? 'removed' : 'added'; 215 } 216 217 static isProductDetailLinkClick(card, target) { 218 if (!target || typeof target.closest !== 'function') { 219 return false; 220 } 221 222 const productLink = target.closest('a'); 223 if (!productLink || !card.contains(productLink)) { 224 return false; 225 } 226 227 return (productLink.getAttribute('href') || '').indexOf('idx=') !== -1; 228 } 229 230 static fireView(card, impressionSource) { 231 if (BrandProductEventTracker.viewedCards.has(card)) { 232 return true; 233 } 234 235 const tracked = BrandProductEventTracker.trackCardEvent(card, BrandProductEventTracker.VIEW_EVENT, 'view', { 236 extraProperties: { 237 impression_source: impressionSource || BrandProductEventTracker.IMPRESSION_SOURCE_VIEWPORT 238 } 239 }); 240 if (tracked) { 241 BrandProductEventTracker.viewedCards.add(card); 242 } 243 244 return tracked; 245 } 246 247 static trackCardEvent(card, sourceEvent, stage, options) { 248 const properties = BrandProductEventTracker.getEventProperties(card, stage, sourceEvent); 249 if (!properties) { 250 return false; 251 } 252 const eventProperties = BrandProductEventTracker.getEventPropertiesWithBrandScopeMeta(properties, stage, options); 253 254 if (typeof window.BrandScope === 'undefined' || typeof window.BrandScope.track !== 'function') { 255 BrandProductEventTracker.logFailure('brand_scope_unavailable', stage, sourceEvent, eventProperties.widget_code || '', eventProperties.prod_code || '', null); 256 return false; 257 } 258 259 try { 260 window.BrandScope.track(sourceEvent, eventProperties); 261 return true; 262 } catch (error) { 263 BrandProductEventTracker.logFailure('track_error', stage, sourceEvent, eventProperties.widget_code || '', eventProperties.prod_code || '', error); 264 } 265 266 return false; 267 } 268 269 static getEventPropertiesWithBrandScopeMeta(properties, stage, options) { 270 const eventOptions = options || {}; 271 const eventProperties = Object.assign({}, properties, { 272 action: stage, 273 content: eventOptions.content || 'product', 274 where: 'shopping_widget' 275 }); 276 277 if (stage === 'view') { 278 eventProperties.target = 'component'; 279 } 280 281 if (eventOptions.extraProperties) { 282 Object.assign(eventProperties, eventOptions.extraProperties); 283 } 284 285 return eventProperties; 286 } 287 288 static getEventProperties(card, stage, sourceEvent) { 289 let properties; 290 try { 291 properties = JSON.parse(card.getAttribute('data-brand-product-event-properties') || ''); 292 } catch (error) { 293 BrandProductEventTracker.logFailure('invalid_properties_json', stage, sourceEvent, '', BrandProductEventTracker.getProdCode(card), error); 294 return null; 295 } 296 297 if (!properties || typeof properties !== 'object') { 298 BrandProductEventTracker.logFailure('missing_required_properties', stage, sourceEvent, '', '', null); 299 return null; 300 } 301 302 const requiredKeys = [ 303 'prod_code', 304 'widget_code', 305 'sort_type', 306 'slot_index', 307 'page_no', 308 'prod_category_code', 309 'ranking_source', 310 'ranking_fallback_reason' 311 ]; 312 313 for (let i = 0; i < requiredKeys.length; i++) { 314 if (!Object.prototype.hasOwnProperty.call(properties, requiredKeys[i])) { 315 BrandProductEventTracker.logFailure('missing_required_properties', stage, sourceEvent, properties.widget_code || '', properties.prod_code || '', null); 316 return null; 317 } 318 } 319 320 if (properties.ranking_fallback_reason === null) { 321 properties.ranking_fallback_reason = ''; 322 } 323 324 return properties; 325 } 326 327 static clearIntersectionTimer(card) { 328 if (!BrandProductEventTracker.intersectionTimers.has(card)) { 329 return; 330 } 331 332 window.clearTimeout(BrandProductEventTracker.intersectionTimers.get(card)); 333 BrandProductEventTracker.intersectionTimers.delete(card); 334 } 335 336 static flushQueue() { 337 const queue = window.__brandProductEventTrackerQueue; 338 if (!Array.isArray(queue) || queue.length === 0) { 339 window.__brandProductEventTrackerQueue = []; 340 return; 341 } 342 343 window.__brandProductEventTrackerQueue = []; 344 queue.forEach(function(item) { 345 if (!item || item.type !== 'scan') { 346 return; 347 } 348 349 BrandProductEventTracker.scan(item.widgetCode); 350 }); 351 } 352 353 static getProdCode(card) { 354 try { 355 const properties = JSON.parse(card.getAttribute('data-brand-product-event-properties') || '{}'); 356 return properties && properties.prod_code ? properties.prod_code : ''; 357 } catch (error) { 358 return ''; 359 } 360 } 361 362 static logFailure(reason, stage, sourceEvent, widgetCode, prodCode, error) { 363 try {
364 const key = [reason, stage, sourceEvent, widgetCode].join(':'); 365 if (BrandProductEventTracker.loggedFailures.has(key)) { 366 return; 367 } 368 369 BrandProductEventTracker.loggedFailures.add(key); 370 if (window.console && typeof window.console.error === 'function') { 371 window.console.error('[BrandProductEventTracker]', { 372 reason: reason, 373 stage: stage, 374 sourceEvent: sourceEvent, 375 widgetCode: widgetCode, 376 prodCode: prodCode, 377 error: error 378 }); 379 } 380 } catch (_error) { 381 } 382 } 383 } 384 385 BrandProductEventTracker.VIEW_EVENT = 'view_component_product_shopping_widget'; 386 BrandProductEventTracker.CLICK_EVENT = 'click_product_shopping_widget'; 387 BrandProductEventTracker.CART_CLICK_EVENT = 'click_add_to_cart_shopping_widget'; 388 BrandProductEventTracker.WISHLIST_CLICK_EVENT = 'click_add_to_wishlist_shopping_widget'; 389 BrandProductEventTracker.REVIEW_CLICK_EVENT = 'click_view_review_shopping_widget'; 390 BrandProductEventTracker.CARD_ACTION_ATTRIBUTE = 'data-shopping-widget-card-action'; 391 BrandProductEventTracker.CARD_ACTION_REVIEW = 'review'; 392 BrandProductEventTracker.CARD_ACTION_WISHLIST = 'wishlist'; 393 BrandProductEventTracker.CARD_ACTION_CART = 'cart'; 394 BrandProductEventTracker.IMPRESSION_SOURCE_VIEWPORT = 'viewport'; 395 BrandProductEventTracker.IMPRESSION_SOURCE_CLICK = 'click'; 396 BrandProductEventTracker.VIEW_DELAY = 500; 397 BrandProductEventTracker.initialized = false; 398 BrandProductEventTracker.observedCards = new WeakSet(); 399 BrandProductEventTracker.viewedCards = new WeakSet(); 400 BrandProductEventTracker.intersectionTimers = new WeakMap(); 401 BrandProductEventTracker.loggedFailures = new Set(); 402 BrandProductEventTracker.observer = null; 403 404 window.BrandProductEventTracker = BrandProductEventTracker; 405 406 function bootstrapBrandProductEventTracker() { 407 try { 408 BrandProductEventTracker.init(); 409 } catch (error) { 410 BrandProductEventTracker.logFailure('unknown_error', 'init', '', '', '', error); 411 } 412 } 413 414 if (document.readyState === 'loading') { 415 document.addEventListener('DOMContentLoaded', bootstrapBrandProductEventTracker); 416 } else { 417 bootstrapBrandProductEventTracker(); 418 } 419})(window, document);
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.