1(function(window, document) { 2 'use strict'; 3 4 // 기íì (shop_showcase) ìì ¯ brandscope ì´ë²¤í¸ í¸ë커. 5 // ì¼í ìì ¯ í¸ë커(brand_product_event_tracker.js)ì ëì¼í í¸ì¶ ë©ì»¤ëì¦ 6 // (ì¹´ëë³ IntersectionObserver 0.8 + 500ms, í´ë¦ backfill)ì ì¬ì¬ì©íë, 7 // ì´ë²¤í¸ëª /where/ìì± ì¸í¸ë 기íì ì ì©(events.yaml: *_showcase_widget)ì ë°ë¥¸ë¤. 8 if (typeof window.WeakSet === 'undefined' || typeof window.WeakMap === 'undefined' || typeof window.Set === 'undefined') { 9 try { 10 if (window.console && typeof window.console.error === 'function') { 11 window.console.error('[ShowcaseProductEventTracker]', { 12 reason: 'unknown_error', 13 stage: 'init', 14 sourceEvent: '', 15 widgetCode: '', 16 prodCode: '', 17 error: null 18 }); 19 } 20 } catch (_error) { 21 } 22 return; 23 } 24 25 class ShowcaseProductEventTracker { 26 static init() { 27 if (ShowcaseProductEventTracker.initialized) { 28 return; 29 } 30 31 ShowcaseProductEventTracker.initialized = true; 32 ShowcaseProductEventTracker.ensureObserver(); 33 ShowcaseProductEventTracker.scan(); 34 ShowcaseProductEventTracker.flushQueue(); 35 } 36 37 static scan(widgetCode) { 38 try { 39 const root = ShowcaseProductEventTracker.getScanRoot(widgetCode); 40 if (!root) { 41 return; 42 } 43 44 const cards = root.querySelectorAll('._shop_item[data-showcase-product-event-properties]'); 45 cards.forEach(function(card) { 46 ShowcaseProductEventTracker.observeCard(card); 47 }); 48 } catch (error) { 49 ShowcaseProductEventTracker.logFailure('scan_error', 'scan', '', widgetCode || '', '', error); 50 } 51 } 52 53 static getScanRoot(widgetCode) { 54 if (!widgetCode) { 55 return document; 56 } 57 58 return document.getElementById('container_' + widgetCode); 59 } 60 61 static observeCard(card) { 62 if (!card || ShowcaseProductEventTracker.observedCards.has(card)) { 63 return; 64 } 65 66 // ì¬ë¼ì´ë(owl carousel) 모ëë 무í 루íì©ì¼ë¡ ì¹´ë를 ë³µì (.owl-item.cloned)íë¤. 67 // ë³µì ì¹´ëë ëì¼ ìíì ì¤ë³µ DOMì´ë¯ë¡ ë ¸ì¶Â·í´ë¦ ì§ê³ìì ì ì¸íë¤(ì¹´ë 1ê°ë¹ 1í ìì¹). 68 if (ShowcaseProductEventTracker.isClonedCard(card)) { 69 return; 70 } 71 72 ShowcaseProductEventTracker.observedCards.add(card); 73 card.addEventListener('click', function(event) { 74 ShowcaseProductEventTracker.handleClick(card, event); 75 }, true); 76 77 const observer = ShowcaseProductEventTracker.ensureObserver(); 78 if (observer) { 79 try { 80 observer.observe(card); 81 } catch (error) { 82 ShowcaseProductEventTracker.logFailure('observer_error', 'view', ShowcaseProductEventTracker.VIEW_EVENT, '', ShowcaseProductEventTracker.getProdCode(card), error); 83 } 84 } 85 } 86 87 static ensureObserver() { 88 if (ShowcaseProductEventTracker.observer) { 89 return ShowcaseProductEventTracker.observer; 90 } 91 92 if (typeof window.IntersectionObserver === 'undefined') { 93 ShowcaseProductEventTracker.logFailure('intersection_observer_unavailable', 'view', ShowcaseProductEventTracker.VIEW_EVENT, '', '', null); 94 return null; 95 } 96 97 try { 98 ShowcaseProductEventTracker.observer = new window.IntersectionObserver(function(entries, observer) { 99 entries.forEach(function(entry) { 100 ShowcaseProductEventTracker.handleIntersection(entry, observer); 101 }); 102 }, { 103 threshold: 0.8 104 }); 105 } catch (error) { 106 ShowcaseProductEventTracker.logFailure('observer_error', 'view', ShowcaseProductEventTracker.VIEW_EVENT, '', '', error); 107 ShowcaseProductEventTracker.observer = null; 108 } 109 110 return ShowcaseProductEventTracker.observer; 111 } 112 113 static handleIntersection(entry, observer) { 114 const card = entry.target; 115 try { 116 if (ShowcaseProductEventTracker.viewedCards.has(card)) { 117 observer.unobserve(card); 118 return; 119 } 120 121 if (entry.isIntersecting) { 122 if (ShowcaseProductEventTracker.intersectionTimers.has(card)) { 123 return; 124 } 125 126 const timer = window.setTimeout(function() { 127 ShowcaseProductEventTracker.intersectionTimers.delete(card); 128 ShowcaseProductEventTracker.fireView(card, ShowcaseProductEventTracker.IMPRESSION_SOURCE_VIEWPORT); 129 observer.unobserve(card); 130 }, ShowcaseProductEventTracker.VIEW_DELAY); 131 132 ShowcaseProductEventTracker.intersectionTimers.set(card, timer); 133 return; 134 } 135 136 ShowcaseProductEventTracker.clearIntersectionTimer(card); 137 } catch (error) { 138 ShowcaseProductEventTracker.logFailure('observer_error', 'view', ShowcaseProductEventTracker.VIEW_EVENT, '', ShowcaseProductEventTracker.getProdCode(card), error); 139 } 140 } 141 142 static handleClick(card, event) { 143 try { 144 if (!ShowcaseProductEventTracker.isProductDetailLinkClick(card, event.target)) { 145 return; 146 } 147 148 if (!ShowcaseProductEventTracker.viewedCards.has(card)) { 149 ShowcaseProductEventTracker.fireView(card, ShowcaseProductEventTracker.IMPRESSION_SOURCE_CLICK); 150 } 151 152 ShowcaseProductEventTracker.trackCardEvent(card, ShowcaseProductEventTracker.CLICK_EVENT, 'click'); 153 } catch (error) { 154 ShowcaseProductEventTracker.logFailure('unknown_error', 'click', ShowcaseProductEventTracker.CLICK_EVENT, '', ShowcaseProductEventTracker.getProdCode(card), error); 155 } 156 } 157 158 static isClonedCard(card) { 159 if (!card || typeof card.closest !== 'function') { 160 return false;
161 } 162 163 return card.closest('.owl-item.cloned') !== null; 164 } 165 166 static isProductDetailLinkClick(card, target) { 167 if (!target || typeof target.closest !== 'function') { 168 return false; 169 } 170 171 const productLink = target.closest('a'); 172 if (!productLink || !card.contains(productLink)) { 173 return false; 174 } 175 176 return (productLink.getAttribute('href') || '').indexOf('idx=') !== -1; 177 } 178 179 static fireView(card, impressionSource) { 180 if (ShowcaseProductEventTracker.viewedCards.has(card)) { 181 return true; 182 } 183 184 const tracked = ShowcaseProductEventTracker.trackCardEvent(card, ShowcaseProductEventTracker.VIEW_EVENT, 'view', { 185 extraProperties: { 186 impression_source: impressionSource || ShowcaseProductEventTracker.IMPRESSION_SOURCE_VIEWPORT 187 } 188 }); 189 if (tracked) { 190 ShowcaseProductEventTracker.viewedCards.add(card); 191 } 192 193 return tracked; 194 } 195 196 static trackCardEvent(card, sourceEvent, stage, options) { 197 const properties = ShowcaseProductEventTracker.getEventProperties(card, stage, sourceEvent); 198 if (!properties) { 199 return false; 200 } 201 const eventProperties = ShowcaseProductEventTracker.getEventPropertiesWithBrandScopeMeta(properties, stage, options); 202 203 if (typeof window.BrandScope === 'undefined' || typeof window.BrandScope.track !== 'function') { 204 ShowcaseProductEventTracker.logFailure('brand_scope_unavailable', stage, sourceEvent, eventProperties.widget_code || '', eventProperties.prod_code || '', null); 205 return false; 206 } 207 208 try { 209 window.BrandScope.track(sourceEvent, eventProperties); 210 return true; 211 } catch (error) { 212 ShowcaseProductEventTracker.logFailure('track_error', stage, sourceEvent, eventProperties.widget_code || '', eventProperties.prod_code || '', error); 213 } 214 215 return false; 216 } 217 218 static getEventPropertiesWithBrandScopeMeta(properties, stage, options) { 219 const eventOptions = options || {}; 220 const eventProperties = Object.assign({}, properties, { 221 action: stage, 222 content: eventOptions.content || 'product', 223 where: 'showcase_widget' 224 }); 225 226 if (stage === 'view') { 227 eventProperties.target = 'component'; 228 } 229 230 if (eventOptions.extraProperties) { 231 Object.assign(eventProperties, eventOptions.extraProperties); 232 } 233 234 return eventProperties; 235 } 236 237 static getEventProperties(card, stage, sourceEvent) { 238 let properties; 239 try { 240 properties = JSON.parse(card.getAttribute('data-showcase-product-event-properties') || ''); 241 } catch (error) { 242 ShowcaseProductEventTracker.logFailure('invalid_properties_json', stage, sourceEvent, '', ShowcaseProductEventTracker.getProdCode(card), error); 243 return null; 244 } 245 246 if (!properties || typeof properties !== 'object') { 247 ShowcaseProductEventTracker.logFailure('missing_required_properties', stage, sourceEvent, '', '', null); 248 return null; 249 } 250 251 const requiredKeys = [ 252 'prod_code', 253 'menu_code', 254 'widget_code', 255 'showcase_code', 256 'sort_type', 257 'slot_index', 258 'page_no', 259 'ranking_source', 260 'ranking_fallback_reason', 261 'policy_snapshot_id' 262 ]; 263 264 for (let i = 0; i < requiredKeys.length; i++) { 265 if (!Object.prototype.hasOwnProperty.call(properties, requiredKeys[i])) { 266 ShowcaseProductEventTracker.logFailure('missing_required_properties', stage, sourceEvent, properties.widget_code || '', properties.prod_code || '', null); 267 return null; 268 } 269 } 270 271 if (properties.ranking_fallback_reason === null) { 272 properties.ranking_fallback_reason = ''; 273 } 274 275 return properties; 276 } 277 278 static clearIntersectionTimer(card) { 279 if (!ShowcaseProductEventTracker.intersectionTimers.has(card)) { 280 return; 281 } 282 283 window.clearTimeout(ShowcaseProductEventTracker.intersectionTimers.get(card)); 284 ShowcaseProductEventTracker.intersectionTimers.delete(card); 285 } 286 287 static flushQueue() { 288 const queue = window.__showcaseProductEventTrackerQueue; 289 if (!Array.isArray(queue) || queue.length === 0) { 290 window.__showcaseProductEventTrackerQueue = []; 291 return; 292 } 293 294 window.__showcaseProductEventTrackerQueue = []; 295 queue.forEach(function(item) { 296 if (!item || item.type !== 'scan') { 297 return; 298 } 299 300 ShowcaseProductEventTracker.scan(item.widgetCode); 301 }); 302 } 303 304 static getProdCode(card) { 305 try { 306 const properties = JSON.parse(card.getAttribute('data-showcase-product-event-properties') || '{}'); 307 return properties && properties.prod_code ? properties.prod_code : ''; 308 } catch (error) { 309 return ''; 310 } 311 } 312 313 static logFailure(reason, stage, sourceEvent, widgetCode, prodCode, error) { 314 try {
315 const key = [reason, stage, sourceEvent, widgetCode].join(':'); 316 if (ShowcaseProductEventTracker.loggedFailures.has(key)) { 317 return; 318 } 319 320 ShowcaseProductEventTracker.loggedFailures.add(key); 321 if (window.console && typeof window.console.error === 'function') { 322 window.console.error('[ShowcaseProductEventTracker]', { 323 reason: reason, 324 stage: stage, 325 sourceEvent: sourceEvent, 326 widgetCode: widgetCode, 327 prodCode: prodCode, 328 error: error 329 }); 330 } 331 } catch (_error) { 332 } 333 } 334 } 335 336 ShowcaseProductEventTracker.VIEW_EVENT = 'view_component_product_showcase_widget'; 337 ShowcaseProductEventTracker.CLICK_EVENT = 'click_product_showcase_widget'; 338 ShowcaseProductEventTracker.IMPRESSION_SOURCE_VIEWPORT = 'viewport'; 339 ShowcaseProductEventTracker.IMPRESSION_SOURCE_CLICK = 'click'; 340 ShowcaseProductEventTracker.VIEW_DELAY = 500; 341 ShowcaseProductEventTracker.initialized = false; 342 ShowcaseProductEventTracker.observedCards = new WeakSet(); 343 ShowcaseProductEventTracker.viewedCards = new WeakSet(); 344 ShowcaseProductEventTracker.intersectionTimers = new WeakMap(); 345 ShowcaseProductEventTracker.loggedFailures = new Set(); 346 ShowcaseProductEventTracker.observer = null; 347 348 window.ShowcaseProductEventTracker = ShowcaseProductEventTracker; 349 350 function bootstrapShowcaseProductEventTracker() { 351 try { 352 ShowcaseProductEventTracker.init(); 353 } catch (error) { 354 ShowcaseProductEventTracker.logFailure('unknown_error', 'init', '', '', '', error); 355 } 356 } 357 358 if (document.readyState === 'loading') { 359 document.addEventListener('DOMContentLoaded', bootstrapShowcaseProductEventTracker); 360 } else { 361 bootstrapShowcaseProductEventTracker(); 362 } 363})(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.