PageSourceSearch

https://dwvil.com/js/brand_product_event_tracker.js?1783051298

js dwvil.com collected 2026-10-02 00:31:23 UTC 13,071 bytes, 419 lines download raw bytes

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.