"use strict"; (function () { // const METRICS_ENDPOINT = "https://ao2oxq4k76.execute-api.us-east-1.amazonaws.com/prod/events"; const METRICS_ENDPOINT = "https://y4xych6i1a.execute-api.us-east-1.amazonaws.com/prod/events"; const ALLOWED_PAGES = ["product", "home", "collection", "searchresults"]; const DELIVERY_STREAM_NAME = "ShopifyFrontEndEventStream"; const MAX_QUEUE_SIZE = 100; // This is the upper boundary of maximum events that can be stored. // const API_KEY = "PaywN2lI1Cab0l7ygZHqH7ne0RSIbnty6WCE3blP"; const sessionId = getOrCreateSessionId(); const userId = getOrCreateUserId(); let selected_variant = null; let previous_selected_variant = null; let pageVisitStart = null; let sendingMetrics = false; let isUnloading = false; let addToCartInProgress = false; let queue = loadQueue(); function loadQueue() { return JSON.parse(localStorage.getItem("via_metrics")) || []; } function saveQueue() { localStorage.setItem("via_metrics", JSON.stringify(queue)); } function getOrCreateUserId() { let id = localStorage.getItem("via_metrics_uuid"); if (!id) { id = crypto.randomUUID(); localStorage.setItem("via_metrics_uuid", id); } return id; } function getOrCreateSessionId() { let id = sessionStorage.getItem("via_metrics_session_id"); if (!id) { id = crypto.randomUUID(); sessionStorage.setItem("via_metrics_session_id", id); } return id; } function getPageType() { return window.ShopifyAnalytics?.meta?.page?.pageType; } function getSelectedVariant(variantId = window.ShopifyAnalytics?.meta?.selectedVariantId) { return window.ShopifyAnalytics?.meta?.product?.variants?.find(v => v.id == variantId) || null; } function getEventDataJson({ eventType, action, eventDetails }) { const timestamp = Date.now(); return { event: eventType, action, session_id: sessionId, user_id: userId, pageVisitStartTime: pageVisitStart, via_improved_page: typeof window.variant_images === "object", event_details: typeof eventDetails === "string" ? eventDetails : JSON.stringify(eventDetails), product_id: window.ShopifyAnalytics?.meta?.product?.id || 0, page_type: getPageType(), page_url: window.location.href, screen_resolution: `${window.screen.width}x${window.screen.height}`, currency: window.ShopifyAnalytics?.meta?.currency, language: window.Shopify?.locale, store_domain: window.Shopify?.shop, theme_id: window.Shopify?.theme?.id, theme_type: window.Shopify?.theme?.schema_name, useragent: navigator.userAgent, timestamp, created_at: new Date(timestamp).toISOString(), }; } function enqueue(event) { try { queue.push(event); if (queue.length > MAX_QUEUE_SIZE) queue.shift(); saveQueue(); } catch (e) { console.warn("Failed to enqueue event:", e); } } function encodeBase64(obj) { const json = JSON.stringify(obj); return btoa(String.fromCharCode(...new TextEncoder().encode(json))); } function postEvents() { if (sendingMetrics || queue.length === 0) return; sendingMetrics = true; // Copy and send only this snapshot const sendingQueue = [...queue]; const records = sendingQueue.map(e => ({ Data: encodeBase64(e) })); const body = JSON.stringify({ DeliveryStreamName: DELIVERY_STREAM_NAME, Records: records }); fetch(METRICS_ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json", // "x-api-key": API_KEY, }, body, }).then(() => { queue = queue.slice(sendingQueue.length); saveQueue(); }).catch(err => console.warn("Error sending metrics:", err)) .finally(() => sendingMetrics = false); } // function getOptionDetails(elementId) { // const el = document.querySelector(`[id="${elementId}"]`); // return { // optionValue: el?.value || '', // optionName: el?.name?.split("-")[0] || '' // }; // } function handleChange() { const variant = getSelectedVariant(); if (!variant || variant.id === selected_variant?.id) return; previous_selected_variant = selected_variant; selected_variant = variant; const eventDetails = { previous_selected_variant: previous_selected_variant, current_selected_variant: selected_variant }; const event = getEventDataJson({ eventType: "click", action: "variant_change", eventDetails }); enqueue(event); } function handleAddToCart(response) { if (!response) return; const event = getEventDataJson({ eventType: "click", action: "add-to-cart", eventDetails: { current_selected_variant: getSelectedVariant(response.variant_id), quantity: response.quantity } }); event.product_id = response.product_id enqueue(event); } // function handleClick(event) { // const target = event.target.closest(ALLOWED_SELECTORS.join(",")); // if (!target) return; // const base = getEventDataJson({ eventType: "click", action: "select", eventDetails: "" }); // let optionDetails = {}, index = 0, count = 0; // if (target.tagName.toLowerCase() === "select") { // const name = target.getAttribute("name")?.match(/\[(.*?)\]/)?.[1]; // const selected = target.querySelector('[selected][data-option-value-id]'); // const options = Array.from(target.options); // index = options.indexOf(selected); // count = options.length; // optionDetails = { // optionValue: selected?.value, // optionName: name // }; // } // else { // const container = target.closest('label, input, button'); // const optId = container?.getAttribute("for") || container?.getAttribute("id"); // optionDetails = getOptionDetails(optId); // const label = target.closest("label"); // const siblings = Array.from(label?.closest("fieldset")?.children || []); // const labels = siblings.filter(n => n.tagName.toLowerCase() === "label"); // index = labels.indexOf(label); // count = labels.length; // } // base.option_name = optionDetails.optionName || ""; // base.option_value = optionDetails.optionValue || ""; // base.option_value_index = index; // base.option_value_count = count; // enqueue(base); // } function handleSearchSubmit(event) { const form = event.target; if (!form.matches('form[action*="/search"]')) return; const query = form.querySelector('input[name="q"]')?.value || ""; enqueue(getEventDataJson({ eventType: "search", action: "product_search", eventDetails: { query, page_url: window.location.href } })); } function handleUnload() { if (isUnloading) return; isUnloading = true; const timeSpent = Date.now() - pageVisitStart; enqueue(getEventDataJson({ eventType: "time", action: "time_spent", eventDetails: { time_spent_ms: timeSpent } })); postEvents(); } function handleAddToCartDebounced(response) { if (addToCartInProgress) return; addToCartInProgress = true; setTimeout(() => { handleAddToCart(response); setTimeout(() => { addToCartInProgress = false; }, 1000); }, 0); } // function attachClickListener() { // document.addEventListener("pointerdown", e => setTimeout(() => handleClick(e), 400), { passive: true }); // } function attachChangeListener() { var selectors = document.querySelectorAll('select, .product__info-wrapper, .panda-swatch, .single-option-selector, label, .swatches .swatch-selector, form[action="/cart/add"], product-info'); selectors.forEach(selector => { selector.addEventListener("change", e => setTimeout(() => handleChange(e), 400), { passive: true }); }); } function attachSearchSubmitListener() { document.addEventListener("submit", handleSearchSubmit, { passive: true }); } function attachUnloadListener() { window.addEventListener("beforeunload", handleUnload); // This is required for mobile devices, especially iphone document.addEventListener("visibilitychange", () => { if (document.visibilityState === "hidden") { handleUnload(); } }); } function attachLoadListener() { window.addEventListener("load", () => { pageVisitStart = Date.now(); }, { passive: true }); } function attachAddToCartListener() { listenToFetchRequest(); // Support for XHR requests listenToXHRrequest(); } function listenToFetchRequest() { const originalFetch = window.fetch; window.fetch = function (input, init) { const url = (typeof input === 'string') ? input : input.url; const method = init?.method || (input.method || 'GET'); if (url?.includes('/cart/add') && method.toUpperCase() === 'POST') { return originalFetch.apply(this, arguments).then(response => { // Clone response to read without consuming it const clonedResponse = response.clone(); clonedResponse.json().then(data => { handleAddToCartDebounced(data); }).catch(() => { handleAddToCartDebounced(null); }); return response; }); } return originalFetch.apply(this, arguments); }; } function listenToXHRrequest() { const originalXHRopen = window.XMLHttpRequest.prototype.open; const originalXHRsend = window.XMLHttpRequest.prototype.send; window.XMLHttpRequest.prototype.open = function (method, url) { this._trackedMethod = method; this._trackedUrl = url; return originalXHRopen.apply(this, arguments); }; window.XMLHttpRequest.prototype.send = function () { if (this._trackedUrl && this._trackedUrl.includes('/cart/add')) { this.addEventListener('load', function () { let response = captureAddToCartResponse(this); handleAddToCartDebounced(response); }); } return originalXHRsend.apply(this, arguments); }; function captureAddToCartResponse(xhrInstance) { const contentType = xhrInstance.getResponseHeader('Content-Type'); let responseText = xhrInstance.responseText; if (contentType && (contentType.includes('application/json') || contentType.includes('text/javascript'))) { try { responseText = JSON.parse(responseText); } catch (e) { console.warn("Error parsing JSON response:", e); } } if (responseText?.items?.length) { return responseText?.items[0]; } return responseText; } } function initListeners() { if (!ALLOWED_PAGES.includes(getPageType())) return; setInterval(attachChangeListener, 100); attachAddToCartListener(); // attachClickListener(); attachSearchSubmitListener(); attachUnloadListener(); attachLoadListener(); } // Init initListeners(); // Check and Send errors every 30 seconds setInterval(postEvents, 30000); })();