(function(){ const zoovuId = 'CyzaA6QChFlGdVynxrCCAtAOVozmZWRF1lR0+5Bz4WM='; const domainId = '31444e33-847e-4855-aef2-68a6d7de0729'; const variables = [{"id":"1eec2be6-dc91-4c4e-a443-da2f051baf5b","name":"Add to cart","type":"CONSTANT","valueType":"TEXT","value":"Add to cart","scope":"GLOBAL"},{"id":"26e045ae-0482-453b-bad1-f0265d34da9d","name":"addToCart_price","type":"FUNCTION","valueType":"DECIMAL","function":function(event){const clickedElement = event.target.closest('button.atc'); if (!clickedElement) return null; const isSearchPage = window.location.search.includes('zQuery'); const isHomePage = window.location.pathname === '/' let ancestor, rawPrice; if (isSearchPage) { ancestor = clickedElement?.closest('.result-card__actions, .zv-modal__content') || null; rawPrice = ancestor ?.querySelector('.price-wrapper .sell-value') ?.textContent?.trim() } else if (isHomePage) { ancestor = clickedElement.closest('.product-card'); rawPrice = ancestor ?.querySelector('.price-wrapper input') ?.value; } else { ancestor = clickedElement.parentElement.parentElement; rawPrice = ancestor ?.querySelector('.price-wrapper .price > .price:last-of-type') ?.textContent?.trim(); } const cleanPrice = parseFloat(rawPrice?.replace(/[^\d.,-]/g, "")); return isNaN(cleanPrice) ? 0 : cleanPrice; /* */},"scope":"LOCAL"},{"id":"70fb9e0a-634b-4937-9147-2cec6b573c98","name":"addToCart_quantity","type":"FUNCTION","valueType":"INTEGER","function":function(event){const clickedElement = event.target.closest('button.atc'); const isSearchPage = window.location.search.includes('zQuery'); let quantity = 1; if (clickedElement) { const ancestor = isSearchPage ? clickedElement.parentElement : clickedElement.parentElement.parentElement; const quantityInput = ancestor?.querySelector('input[type="number"]'); if (quantityInput) { quantity = +quantityInput.value || 1; } } return quantity; /* */},"scope":"LOCAL"},{"id":"81ab552d-d8db-456d-9618-fcaf74d934e0","name":"addToCart_sku","type":"FUNCTION","valueType":"TEXT","function":function(event){const clickedElement = event.target.closest('button.atc'); let sku = ''; if (clickedElement) { const elementId = clickedElement.id; sku = elementId?.split('#')[1] || ''; } return sku; /* */},"scope":"LOCAL"},{"id":"3be07ee8-8a02-4588-ac5c-c775f9486959","name":"autoSuggestionClickout","type":"FUNCTION","valueType":"BOOLEAN","function":function(event){const clickedElement = event.target.closest('[data-product-identifier]'); if (clickedElement && clickedElement.closest('.unibox')) { return true; } return false; /* */},"scope":"LOCAL"},{"id":"40aaafa6-0969-4236-ad9e-be68fb3319d3","name":"cartUpdated_price","type":"FUNCTION","valueType":"DECIMAL","function":function(event){const updateCartBtn = event.target.closest('.quantity-wrapper button:not([formaction*="/cart/remove"])'); if(!updateCartBtn) return; const quantityWrapper = updateCartBtn?.parentElement; const ancestor = quantityWrapper?.parentElement?.parentElement; const price = ancestor.querySelector('.price'); const priceInputValue = price?.querySelector('input')?.value; return getNumericPriceFromString(priceInputValue) || null; /* */},"scope":"LOCAL"},{"id":"22cc39a7-af86-4b04-b501-f6f110b649bf","name":"cartUpdated_quantity","type":"FUNCTION","valueType":"INTEGER","function":function(event){const updateCartBtn = event.target.closest('.quantity-wrapper button:not([formaction*="/cart/remove"])'); if(!updateCartBtn) return; const quantityWrapper = updateCartBtn.parentElement; const input = quantityWrapper?.querySelector('input') return parseFloat(input?.value) || 1 /* */},"scope":"LOCAL"},{"id":"6f23f86a-92fe-46f8-8091-5e6a4593a8c7","name":"cartUpdated_sku","type":"FUNCTION","valueType":"TEXT","function":function(event){const updateCartBtn = event.target.closest('.quantity-wrapper button:not([formaction*="/cart/remove"])'); if(!updateCartBtn) return; const ancestor = updateCartBtn.parentElement.parentElement; const input = ancestor?.querySelector(':scope > input') const sku = input?.id.replace('_Quantity', '') ?? null; return sku ? sku.trim() : null; /* */},"scope":"LOCAL"},{"id":"335558f6-3f0a-433c-bb59-711f7cf5d9c1","name":"category_pdp_plp","type":"FUNCTION","valueType":"TEXT","function":function(event){const nav = document.querySelector('nav[aria-label="breadcrumb"]'); const breadcrumbElements = nav?.querySelectorAll('li'); let category = ""; if (breadcrumbElements?.length > 1) { const secondBreadcrumb = breadcrumbElements[1]; const innerLink = secondBreadcrumb?.querySelector('a') if (innerLink) { category = innerLink?.innerText?.toLowerCase(); } else { category = secondBreadcrumb?.innerText?.toLowerCase(); } } return category; /* */},"scope":"LOCAL"},{"id":"b73b1d5c-2232-410e-be49-48ec6609f4d7","name":"Clickout, add product from search result","type":"CONSTANT","valueType":"TEXT","value":"Clickout, add product from search result","scope":"GLOBAL"},{"id":"e836ec60-0a63-47c8-b89f-80b62b01b6af","name":"currency","type":"FUNCTION","valueType":"TEXT","function":function(event){const hostname = window.location.hostname; const currencyMap = { "jp.nobleknight.com": "JPY", "test.jp.nobleknight.com": "JPY", "au.nobleknight.com": "AUD", "test.au.nobleknight.com": "AUD", "eu.nobleknight.com": "EUR", "test.eu.nobleknight.com": "EUR", "ca.nobleknight.com": "CAD", "test.ca.nobleknight.com": "CAD", "uk.nobleknight.com": "GBP", "test.uk.nobleknight.com": "GBP", }; const currency = currencyMap[hostname] || "USD"; return currency; /* */},"scope":"LOCAL"},{"id":"ad6080ea-719b-4732-952f-ef4f1ce45e02","name":"Event target","type":"FUNCTION","valueType":"EVENT","function":function(event){return event.target /* */},"scope":"GLOBAL"},{"id":"6df04a75-ec03-4c54-8ec9-094004ab9fbd","name":"getPurchaseData","type":"FUNCTION","valueType":"LIST","function":function(event){const rawData = localStorage.getItem('zvCartData') ?? '[]'; try { const data = JSON.parse(rawData); console.log(data) return Array.isArray(data) ? data : []; } catch { return []; } /* */},"scope":"LOCAL"},{"id":"2e141436-b43c-40d6-bbed-64cfdca7f222","name":"Go to PDP - search clickout","type":"CONSTANT","valueType":"TEXT","value":"Go to PDP - search clickout","scope":"GLOBAL"},{"id":"6c77635c-e95a-4eca-8086-f6be1b1193e8","name":"Named referral: Search","type":"CONSTANT","valueType":"TEXT","value":"Search","scope":"GLOBAL"},{"id":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","name":"Page URL","type":"FUNCTION","valueType":"TEXT","function":function(event){return window.location.href; /* */},"scope":"GLOBAL"},{"id":"2e81a7d7-6a26-4203-b02a-a890d1ed4eb6","name":"pdp_sku_from_zoe","type":"FUNCTION","valueType":"TEXT","function":function(event){const sku = document.querySelector('zoovu-zoe')?.getAttribute('sku'); return sku; /* */},"scope":"LOCAL"},{"id":"b12f3744-de4e-487e-b45c-494d0ecd4b3b","name":"Product details page visit","type":"CONSTANT","valueType":"TEXT","value":"Product details page visit","scope":"GLOBAL"},{"id":"907985d3-9828-4823-ab71-0b6b472bd6a4","name":"productImageClicked","type":"FUNCTION","valueType":"BOOLEAN","function":function(event){return !!event.target?.closest('.result-card__image-wrapper'); /* */},"scope":"LOCAL"},{"id":"3f31b345-4f62-42ce-9cd4-888f458cb6e9","name":"productTitleClicked","type":"FUNCTION","valueType":"BOOLEAN","function":function(event){return !!event.target.closest('.result-card__link'); /* */},"scope":"LOCAL"},{"id":"279b8532-1de4-4eca-bbfa-75f167a01a3c","name":"removedFromCart_price","type":"FUNCTION","valueType":"DECIMAL","function":function(event){const removeFromCartBtn = event.target.closest('button[formaction*="/cart/remove"]'); if(!removeFromCartBtn) return; const quantityWrapper = removeFromCartBtn?.parentElement; const ancestor = quantityWrapper?.parentElement?.parentElement; const price = ancestor.querySelector('.price'); const priceInputValue = price?.querySelector('input')?.value; return getNumericPriceFromString(priceInputValue) || null; /* */},"scope":"LOCAL"},{"id":"cc121fc5-1528-4995-8a6f-a24d8e2370ea","name":"removedFromCart_quantity","type":"FUNCTION","valueType":"INTEGER","function":function(event){const removeFromCartBtn = event.target.closest('button[formaction*="/cart/remove"]'); if(!removeFromCartBtn) return; const quantityWrapper = removeFromCartBtn?.parentElement; const quantityInput = quantityWrapper?.querySelector('.quantity-input'); return quantityInput ? parseFloat(quantityInput.value) : 1 /* */},"scope":"LOCAL"},{"id":"4e56850d-0176-4de4-88ed-9eb668a7752a","name":"removedFromCart_sku","type":"FUNCTION","valueType":"TEXT","function":function(event){const removeFromCartBtn = event.target.closest('button[formaction*="/cart/remove"]'); if(!removeFromCartBtn) return; const formActionAttribute = removeFromCartBtn.getAttribute('formaction'); return formActionAttribute ? formActionAttribute.replace(/[^\d].+\//, '') : null /* */},"scope":"LOCAL"},{"id":"42c22679-b3f7-4003-8975-c3e179e21d90","name":"Remove from cart","type":"CONSTANT","valueType":"TEXT","value":"Remove from cart","scope":"GLOBAL"},{"id":"e5e4af14-ca7b-478f-8863-3b67c53bf1e4","name":"searchInput","type":"FUNCTION","valueType":"BOOLEAN","function":function(event){if (event.target && event.target.id === "site-search") { return true; } return false; /* */},"scope":"LOCAL"},{"id":"ca544c79-ff6e-412a-a0b5-2bf963c493fd","name":"Search phrase typed","type":"CONSTANT","valueType":"TEXT","value":"Search phrase typed","scope":"GLOBAL"},{"id":"67ad9faf-3f5a-42a3-879a-964988bd9137","name":"searchProductUrl","type":"FUNCTION","valueType":"TEXT","function":function(event){const element = event.target.closest( 'a.result-card__link, a.result-card__image-wrapper, .unibox__selectable' ); if (!element) return ''; const dataLink = element.getAttribute('data-link'); if (dataLink !== null && dataLink !== undefined) { return dataLink.trim(); } const href = element.getAttribute('href'); return href ? href.trim() : ''; /* */},"scope":"LOCAL"},{"id":"c0fe42da-f640-40d9-8e2e-9e39ea0c8dfe","name":"Search results","type":"CONSTANT","valueType":"TEXT","value":"Search results","scope":"GLOBAL"},{"id":"295a8dec-ac3b-4ab5-93a7-178409836a98","name":"searchResults_noResults","type":"FUNCTION","valueType":"BOOLEAN","function":function(event){const searchSuggestions = document.querySelectorAll('.z-search-suggests'); return searchSuggestions.length === 0; /* */},"scope":"LOCAL"},{"id":"79ea7cc4-d2c1-4825-ae29-2a67c53b9593","name":"searchResults_skus","type":"FUNCTION","valueType":"LIST","function":function(event){ const skuLinks = document.querySelectorAll('.result-card__link'); const skus = Array.from(skuLinks).map((link) => { const match = link.href.match(/(?<=\/P\/)[^/]+/i); return match ? match[0] : null; }).filter(Boolean); return skus; /* */},"scope":"LOCAL"},{"id":"a2f2d33e-ff0f-4c53-ab03-950ca11e0940","name":"Search term Lower case","type":"FUNCTION","valueType":"TEXT","function":function(event){const urlParams = new URLSearchParams(window.location.search); const searchTerm = urlParams.get('zQuery'); let term = searchTerm ?? ''; return term.toLowerCase(); /* */},"scope":"LOCAL"},{"id":"9ec04551-6b11-4101-b769-b08ee5148b1d","name":"Update cart","type":"CONSTANT","valueType":"TEXT","value":"Update cart","scope":"GLOBAL"},{"id":"ebfd679f-3e55-46a7-b5cc-e450d90d294d","name":"updateCartButtonClicked","type":"FUNCTION","valueType":"BOOLEAN","function":function(event){return !!event.target.closest('.quantity-wrapper button:not([formaction*="/cart/remove"])'); /* */},"scope":"LOCAL"},{"id":"2908e0da-045c-4940-84bb-aa1e36f43551","name":"zoovuConsent","type":"FUNCTION","valueType":"BOOLEAN","function":function(event){return !!(window?.ketchConsent?.analytics ?? true); /* */},"scope":"LOCAL"},{"id":"d1f18334-3294-4223-b55f-5c3328fbe65b","name":"zvLocale","type":"FUNCTION","valueType":"TEXT","function":function(event){const hostname = window.location.hostname; const localeMap = { "jp.nobleknight.com": "en-JP", "test.jp.nobleknight.com": "en-JP", "au.nobleknight.com": "en-AU", "test.au.nobleknight.com": "en-AU", "eu.nobleknight.com": "en-DE", "test.eu.nobleknight.com": "en-DE", "ca.nobleknight.com": "en-CA", "test.ca.nobleknight.com": "en-CA", "uk.nobleknight.com": "en-GB", "test.uk.nobleknight.com": "en-GB", }; const locale = localeMap[hostname] || "en-US"; return locale; /* */},"scope":"LOCAL"}]; const script = {"id":"2fad5e03-9342-4a18-a81e-24bcb7e4a367","rows":[{"id":"e96315cd-0dcc-4fcf-9fc5-637daa430a0d","rowType":"EVENT","trigger":{"id":"eb67aea3-d60d-4791-8f05-eaf44119887a","name":"Add to Cart - Search Clickout","type":"CLICK","groups":[{"id":"66eebb7d-5cba-49aa-a4a3-a0256ac41728","rows":[{"valueType":"EVENT","id":"939831bf-411b-48ec-8555-d52525cedd66","variableId":"ad6080ea-719b-4732-952f-ef4f1ce45e02","value":"atc","operator":"CONTAINS_CLASS"},{"valueType":"TEXT","id":"d9a072e7-fbbf-4406-b503-911b75cbc0f5","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"?zQuery","operator":"CONTAINS"}]}]},"action":{"type":"CLICKOUT"},"fields":[{"id":"66528a5d-3fd6-4be1-bdd1-02e327c01b3d","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","fieldName":"targetUrl"},{"id":"97a0f978-50b2-4b48-87e9-cd414983fbab","variableId":"d1f18334-3294-4223-b55f-5c3328fbe65b","fieldName":"locale"},{"id":"d7cf41de-d015-4691-8203-4fc57fd4bcb5","variableId":"6c77635c-e95a-4eca-8086-f6be1b1193e8","fieldName":"namedReferral"},{"id":"33b3e568-7ab4-4bec-9616-094656c23199","variableId":"b73b1d5c-2232-410e-be49-48ec6609f4d7","fieldName":"eventLabel"}]},{"id":"ab4b29f0-bbc3-4c26-a147-5f6521cd6100","rowType":"EVENT","trigger":{"id":"7ca7b0dd-6e06-4ac2-95b2-dc173cc1eed5","name":"isThankYouPage","type":"PAGE_LOAD","groups":[{"id":"05eac16d-2bce-4381-bdd2-cb58df67402e","rows":[{"valueType":"TEXT","id":"99e60c89-6923-45f1-b37f-2858867efd82","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"NKGThankYou","operator":"CONTAINS"}]}]},"action":{"type":"PURCHASED"},"fields":[{"id":"f17a0311-3656-49ff-b65e-288d8fab9c2a","variableId":"e836ec60-0a63-47c8-b89f-80b62b01b6af","fieldName":"currencyCode"},{"id":"bb4c8043-44f4-4c6f-a96f-4a30297f28e8","variableId":"6df04a75-ec03-4c54-8ec9-094004ab9fbd","fieldName":"products"}]},{"id":"97848b55-c8ef-4cfe-b2ee-5d4b6f30bc49","rowType":"CUSTOM_ACTION","trigger":{"id":"34082dff-b14f-4130-8d2c-446eb4a4a637","name":"checkoutTrigger","type":"CLICK","groups":[{"id":"aaa549d0-603f-4054-aaea-424b8f7d32b4","rows":[{"valueType":"TEXT","id":"e3dfb0f1-c741-470f-b2c7-09dc0c82aee5","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"/Cart","operator":"CONTAINS"},{"valueType":"EVENT","id":"3b361b55-b200-4dd7-b46b-ca4f980fc4fa","variableId":"ad6080ea-719b-4732-952f-ef4f1ce45e02","value":"[href=\"/Information\"]","operator":"MATCHES_CSS_SELECTOR"}]}]},"action":{"id":"3724f4e3-cae3-49d9-b7e4-f165d8d8e616","type":"CUSTOM","code":function(event){const cartItems = document.querySelectorAll('.container[name="cart"] form[action*="/cart/update"]'); const mappedItems = Array.from(cartItems).map((item) => { const actionAttribute = item.getAttribute('action') || ''; const skuMatch = actionAttribute?.match(/(\d+)\/?$/); const sku = skuMatch ? skuMatch[1] : null; const name = item.querySelector('.product-details h3')?.textContent.trim() || ''; const quantity = Number.parseInt(item.querySelector('.quantity > input')?.value ?? '', 10) || 0; const pricePerUnit = Number.parseFloat(item.querySelector('.price input')?.value ?? '') || 0; return {name, sku, quantity, pricePerUnit} }) localStorage.setItem('zvCartData', JSON.stringify(mappedItems)) /* */}},"fields":[]},{"id":"63cef9af-be3b-4909-8361-6f2e59b0d0bc","rowType":"EVENT","trigger":{"id":"4eec6c64-b4ec-449c-8811-b044fae7b1b1","name":"Products In Search Clicked","type":"CLICK","groups":[{"id":"9f0eac52-818f-4e86-a3fb-3c2c5463844e","rows":[{"valueType":"BOOLEAN","id":"21cb33b7-4622-4b5d-8fb8-027583023f97","variableId":"3f31b345-4f62-42ce-9cd4-888f458cb6e9","value":"true","operator":"EQUALS"}]},{"id":"9291a200-7812-48fd-96d6-0cac7bca8222","rows":[{"valueType":"BOOLEAN","id":"f4668e9c-ac70-406e-98db-aa772ae4eaa1","variableId":"907985d3-9828-4823-ab71-0b6b472bd6a4","value":"true","operator":"EQUALS"}]},{"id":"720a164d-7de2-41b9-9f94-1e0db32671ec","rows":[{"valueType":"BOOLEAN","id":"19515653-f68e-4265-9a55-f112542193bd","variableId":"3be07ee8-8a02-4588-ac5c-c775f9486959","value":"true","operator":"EQUALS"}]}]},"action":{"type":"CLICKOUT"},"fields":[{"id":"ea8612a0-f472-4de7-a8d1-b4037e03e6b5","variableId":"6c77635c-e95a-4eca-8086-f6be1b1193e8","fieldName":"namedReferral"},{"id":"542ed7ff-82e2-42bc-b033-d07add763996","variableId":"67ad9faf-3f5a-42a3-879a-964988bd9137","fieldName":"targetUrl"},{"id":"439662cc-ee4a-4360-b8a2-bb712759bc97","variableId":"d1f18334-3294-4223-b55f-5c3328fbe65b","fieldName":"locale"},{"id":"fd84078c-ac26-4c6b-8612-19109335e55e","variableId":"2e141436-b43c-40d6-bbed-64cfdca7f222","fieldName":"eventLabel"}]},{"id":"8075dc4b-5d8a-436e-8959-bf205b91a410","rowType":"EVENT","trigger":{"id":"801377f5-96d9-4bfe-b7a8-84036626165d","name":"Analytics Consent Declined","type":"PAGE_LOAD","groups":[{"id":"3c784802-d740-471f-b9d9-376ce424d857","rows":[{"valueType":"BOOLEAN","id":"c905c54e-f47f-4dda-a4e2-5bd141819b11","variableId":"2908e0da-045c-4940-84bb-aa1e36f43551","value":"false","operator":"EQUALS"}]}]},"action":{"type":"DECLINE_TRACKING"},"fields":[{"id":"7c39cbc6-aa4d-416f-a718-0281d566f729","variableId":"d1f18334-3294-4223-b55f-5c3328fbe65b","fieldName":"locale"}]},{"id":"1843d3b2-948c-42f8-912c-2cd650857852","rowType":"EVENT","trigger":{"id":"a14a7b17-8a49-45bb-b7cf-701a72408118","name":"updateCart Trigger","type":"CLICK","groups":[{"id":"0db85688-128f-4bbc-8a49-31d8b472abc2","rows":[{"valueType":"TEXT","id":"214d3411-6766-4795-b4b7-f829f4572d86","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"/Cart","operator":"CONTAINS"},{"valueType":"BOOLEAN","id":"1fbd5ae1-f8f2-4557-946b-d2349226852a","variableId":"ebfd679f-3e55-46a7-b5cc-e450d90d294d","value":"true","operator":"EQUALS"}]}]},"action":{"type":"UPDATE_CART"},"fields":[{"id":"e078e7e5-6672-4ad5-b93a-cb0cf7c50008","variableId":"d1f18334-3294-4223-b55f-5c3328fbe65b","fieldName":"locale"},{"id":"7a30d6e8-96e0-461e-8afb-b6db623d9d1d","variableId":"6f23f86a-92fe-46f8-8091-5e6a4593a8c7","fieldName":"sku"},{"id":"a257e18a-b219-4082-accc-76ab3f81383e","variableId":"40aaafa6-0969-4236-ad9e-be68fb3319d3","fieldName":"price"},{"id":"f6f5f055-bbf3-4dca-9831-5aa302ff11fe","variableId":"e836ec60-0a63-47c8-b89f-80b62b01b6af","fieldName":"currencyCode"},{"id":"53758163-8a3e-43de-9451-82e59cd2ed0c","variableId":"22cc39a7-af86-4b04-b501-f6f110b649bf","fieldName":"quantity"},{"id":"46b7813e-8d6a-4f89-8a46-56c61fa25fda","variableId":"9ec04551-6b11-4101-b769-b08ee5148b1d","fieldName":"eventLabel"}]},{"id":"7de9edcf-f528-4822-aa7a-b5fef219e49b","rowType":"EVENT","trigger":{"id":"8a80c42a-a5c9-4a36-acf2-3f9ae7c28057","name":"removeFromCart Trigger","type":"CLICK","groups":[{"id":"71e70fde-4690-485a-a4e1-d6821b55d284","rows":[{"valueType":"EVENT","id":"43436c63-efa9-4d21-993c-858fd33d72e5","variableId":"ad6080ea-719b-4732-952f-ef4f1ce45e02","value":"[formaction*=\"/cart/remove\"]","operator":"MATCHES_CSS_SELECTOR"}]}]},"action":{"type":"REMOVE_FROM_CART"},"fields":[{"id":"c0f1ad12-b111-4c14-b54e-60c39d9c3997","variableId":"d1f18334-3294-4223-b55f-5c3328fbe65b","fieldName":"locale"},{"id":"0dfea810-c633-43f2-a4be-c908d1d5ef3b","variableId":"4e56850d-0176-4de4-88ed-9eb668a7752a","fieldName":"sku"},{"id":"d8eceef4-501b-4db5-ae63-0fd011a329ef","variableId":"279b8532-1de4-4eca-bbfa-75f167a01a3c","fieldName":"price"},{"id":"d6e16fc3-934d-4c93-b7fb-747a25269a03","variableId":"e836ec60-0a63-47c8-b89f-80b62b01b6af","fieldName":"currencyCode"},{"id":"f4f6d049-c6ed-4af0-a0b4-815251869433","variableId":"cc121fc5-1528-4995-8a6f-a24d8e2370ea","fieldName":"quantity"},{"id":"0b1edcda-de6c-4cf1-946b-845e16c34e98","variableId":"42c22679-b3f7-4003-8975-c3e179e21d90","fieldName":"eventLabel"}]},{"id":"c5da9afc-b2a8-4211-9786-b6590b080089","rowType":"EVENT","trigger":{"id":"1ebf3c7e-fe51-43a8-af0f-3aea99425f12","name":"isSearchPage Trigger","type":"PAGE_LOAD","groups":[{"id":"8bbdf8c6-271f-4559-a2d5-1a4a1fb040f1","rows":[{"valueType":"TEXT","id":"c33b0913-13ea-4b4a-b210-870fc9f4de6f","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"zQuery=","operator":"CONTAINS"}]}]},"action":{"type":"SEARCH_RESULT"},"fields":[{"id":"b0ab331f-c207-4481-a77c-0686a6292b96","variableId":"d1f18334-3294-4223-b55f-5c3328fbe65b","fieldName":"locale"},{"id":"00a8c52b-920f-4e6f-b335-2c5f6acb1fed","variableId":"295a8dec-ac3b-4ab5-93a7-178409836a98","fieldName":"isEmpty"},{"id":"2b3f0fa5-0259-42a4-8540-530d0e5886da","variableId":"79ea7cc4-d2c1-4825-ae29-2a67c53b9593","fieldName":"skus"},{"id":"0ee8c253-b686-4f96-b65f-cfba510ad486","variableId":"c0fe42da-f640-40d9-8e2e-9e39ea0c8dfe","fieldName":"eventLabel"},{"id":"7929aec0-88f1-4e65-97a3-dffedb4b7b1a","variableId":"a2f2d33e-ff0f-4c53-ab03-950ca11e0940","fieldName":"category"}]},{"id":"9c239d96-5fd9-4007-bb88-851309f4cd99","rowType":"EVENT","trigger":{"id":"4f68e3d9-ead6-4684-bf15-55b527b33ded","name":"Search Input Trigger","type":"INPUT","groups":[{"id":"cee528f6-72d9-4ed1-a69b-1a75a0fd0238","rows":[{"valueType":"BOOLEAN","id":"b45ef6e3-09fd-43a0-9961-b8d54695dc0a","variableId":"e5e4af14-ca7b-478f-8863-3b67c53bf1e4","value":"true","operator":"EQUALS"}]}]},"action":{"type":"SEARCH"},"fields":[{"id":"6843e49c-21bf-40be-9bba-16f2afc4bf0d","variableId":"d1f18334-3294-4223-b55f-5c3328fbe65b","fieldName":"locale"},{"id":"85b5213b-c065-4424-b655-2b54781f3348","variableId":"ca544c79-ff6e-412a-a0b5-2bf963c493fd","fieldName":"eventLabel"}]},{"id":"e7a3aaa9-34d9-473f-9784-505d6adc87fd","rowType":"EVENT","trigger":{"id":"2d1c94a3-cfc6-40b4-a1a2-5aa7c4c8d997","name":"Add to Cart Trigger","type":"CLICK","groups":[{"id":"325a2ab8-33fd-45db-b889-896ae265cd8b","rows":[{"valueType":"EVENT","id":"24b9848f-ac22-41fd-83a7-c0778f9ecb49","variableId":"ad6080ea-719b-4732-952f-ef4f1ce45e02","value":"atc","operator":"CONTAINS_CLASS"}]}]},"action":{"type":"ADD_TO_CART"},"fields":[{"id":"08c40503-3391-462a-bd7b-cec756a74e85","variableId":"d1f18334-3294-4223-b55f-5c3328fbe65b","fieldName":"locale"},{"id":"df1141ff-c4e2-409d-ae13-d4e2d5f7f9f7","variableId":"81ab552d-d8db-456d-9618-fcaf74d934e0","fieldName":"sku"},{"id":"21fa9ed4-775f-4345-88e4-2d5895d87ecd","variableId":"e836ec60-0a63-47c8-b89f-80b62b01b6af","fieldName":"currencyCode"},{"id":"6bad8837-8908-47ba-93db-8296954c0982","variableId":"26e045ae-0482-453b-bad1-f0265d34da9d","fieldName":"price"},{"id":"4a6d43a1-d926-4182-8e6e-90501803ff80","variableId":"70fb9e0a-634b-4937-9147-2cec6b573c98","fieldName":"quantity"},{"id":"b9367db8-ab03-4090-aab0-993d22ebc7ca","variableId":"1eec2be6-dc91-4c4e-a443-da2f051baf5b","fieldName":"eventLabel"}]},{"id":"f85e3f64-113a-4d56-b1b6-3ed6b05ff65f","rowType":"EVENT","trigger":{"id":"b869581e-9d4a-4f7b-bce7-c20df25519e0","name":"PDP Visit","type":"PAGE_LOAD","groups":[{"id":"c3836e89-33b2-4962-805b-f293d87ea48b","rows":[{"valueType":"TEXT","id":"d9e86de5-7dff-4660-9d7e-fd8307096e52","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"/P/","operator":"CONTAINS"}]}]},"action":{"type":"PDP_VISITED"},"fields":[{"id":"f7e22219-43bb-4ba6-a2d3-6fe3f0405881","variableId":"d1f18334-3294-4223-b55f-5c3328fbe65b","fieldName":"locale"},{"id":"d5a7c98a-e3e7-4bb0-b498-885774273ee3","variableId":"2e81a7d7-6a26-4203-b02a-a890d1ed4eb6","fieldName":"sku"},{"id":"9172f14b-53c9-4bb6-ad64-60e54c5b028c","variableId":"b12f3744-de4e-487e-b45c-494d0ecd4b3b","fieldName":"eventLabel"},{"id":"8be3d627-3def-4a5c-8e97-fdee0ea830f6","variableId":"335558f6-3f0a-433c-bb59-711f7cf5d9c1","fieldName":"category"}]},{"id":"15054ea6-3c06-44fb-8f1f-c9fc001a9561","rowType":"EVENT","trigger":{"id":"4a560114-ae6a-4d17-9f57-849b081a7e6d","name":"Page Load","type":"PAGE_LOAD","groups":[{"id":"2007244e-c679-4fce-86a3-d3fec9f97c18","rows":[{"valueType":"TEXT","id":"7b55efa4-79f9-4a26-821c-25265bf8787d","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"/P/","operator":"DOES_NOT_CONTAIN"}]}]},"action":{"type":"PAGE_VISITED"},"fields":[{"id":"2b095104-25c4-4c35-af05-c72657b68f5f","variableId":"d1f18334-3294-4223-b55f-5c3328fbe65b","fieldName":"locale"},{"id":"18c4dcb1-54fe-400b-b655-dc1e1bcf039a","variableId":"335558f6-3f0a-433c-bb59-711f7cf5d9c1","fieldName":"category"}]}]}; const advancedCode = function(){}; const url = 'https://queue-propagator.zoovu.com'; const currentEnvironment = 'barracuda'; const currentAccountId = 1782; const currencies = ['FJD', 'MXN', 'STD', 'LVL', 'SCR', 'CDF', 'BBD', 'HNL', 'UGX', 'MXV', 'ZAR', 'STN', 'CUC', 'BSD', 'SDD', 'SDG', 'IQD', 'GMD', 'CUP', 'TWD', 'RSD', 'UYI', 'MYR', 'FKP', 'XOF', 'UYU', 'CVE', 'OMR', 'KES', 'SEK', 'BTN', 'GNF', 'MZN', 'SVC', 'MZM', 'ARS', 'QAR', 'IRR', 'NLG', 'XPD', 'XPF', 'THB', 'UZS', 'BDT', 'LYD', 'KWD', 'XPT', 'RUB', 'ISK', 'BEF', 'MKD', 'RUR', 'DZD', 'PAB', 'SGD', 'KGS', 'XAF', 'XAG', 'ITL', 'ATS', 'CHF', 'HRK', 'DJF', 'CHE', 'TZS', 'VND', 'XAU', 'ADP', 'AUD', 'CHW', 'KHR', 'XBA', 'IDR', 'KYD', 'XBC', 'XBB', 'BWP', 'SHP', 'XBD', 'CYP', 'TJS', 'AED', 'RWF', 'DKK', 'BGL', 'ZWD', 'BGN', 'MMK', 'SYP', 'NOK', 'ZWL', 'ZWN', 'YUM', 'LKR', 'ZWR', 'CZK', 'IEP', 'GRD', 'XCD', 'HTG', 'XSU', 'AFA', 'BHD', 'SIT', 'PTE', 'KZT', 'SZL', 'YER', 'AFN', 'BYB', 'AWG', 'NPR', 'MNT', 'GBP', 'BYN', 'XTS', 'HUF', 'BYR', 'BIF', 'XUA', 'XDR', 'BZD', 'MOP', 'NAD', 'SKK', 'TMM', 'PEN', 'WST', 'TMT', 'FRF', 'CLF', 'GTQ', 'CLP', 'TND', 'SLL', 'AYM', 'XFO', 'DOP', 'KMF', 'XFU', 'GEL', 'MAD', 'AZM', 'TOP', 'AZN', 'PGK', 'UAH', 'ERN', 'TPE', 'MRO', 'CNY', 'MRU', 'BMD', 'PHP', 'XXX', 'PYG', 'JMD', 'GWP', 'ESP', 'COP', 'USD', 'COU', 'USN', 'VEB', 'ETB', 'USS', 'SOS', 'VUV', 'VEF', 'LAK', 'ZMK', 'BND', 'LRD', 'ALL', 'GHC', 'MTL', 'ZMW', 'VES', 'TRL', 'ILS', 'KPW', 'GHS', 'GYD', 'MDL', 'BOB', 'AMD', 'TRY', 'LBP', 'JOD', 'HKD', 'EUR', 'LSL', 'CAD', 'BOV', 'EEK', 'MUR', 'ROL', 'GIP', 'RON', 'NGN', 'CRC', 'PKR', 'ANG', 'SRD', 'TTD', 'LTL', 'SAR', 'MVR', 'SRG', 'INR', 'KRW', 'JPY', 'PLN', 'AOA', 'SBD', 'CSD', 'LUF', 'MWK', 'MGA', 'FIM', 'MGF', 'DEM', 'BAM', 'EGP', 'SSP', 'NIO', 'NZD', 'BRL'] // ------------------------- API ------------------------- let trackingEnabled = true; function disableTracking() { trackingEnabled = false; } function enableTracking() { trackingEnabled = true; } function getNumericPriceFromString(stringPrice) { const parsedPrice = stringPrice .replace(/\.$/, '') .replace(/([^.',\s\d])*/g, '') .replace(/([.',\s](?=\d{3}))/g, '') .replace(/([.',](?=\d{2}))/g, '.'); return Number(parsedPrice); } class TrackingExecutionError extends Error { constructor(message, trackingEntityType, variableId, triggerId, scriptId, trackingErrorType) { super(message); this.trackingEntityType = trackingEntityType; this.variableId = variableId; this.triggerId = triggerId; this.scriptId = scriptId; this.trackingErrorType = trackingErrorType; } } // ------------------------- helpers ------------------------- const eventTypes = Object.freeze({ PAGE_VISITED: "PAGE_VISITED", ADD_TO_CART: "ADD_TO_CART", REMOVE_FROM_CART: "REMOVE_FROM_CART", PDP_VISITED: "PDP_VISITED", PURCHASED: "PURCHASED", UPDATE_CART: "UPDATE_CART", DECLINE_TRACKING: "DECLINE_TRACKING", SEARCH: "SEARCH", SEARCH_RESULT: "SEARCH_RESULT", CLICKOUT: "CLICKOUT", LEAD_GEN: "LEAD_GEN" }); const trackingEventTypes = Object.freeze({ TRACKING_SUCCESSFUL_EXECUTION: 'TRACKING_SUCCESSFUL_EXECUTION', TRACKING_EXECUTION_FAILURE: 'TRACKING_EXECUTION_FAILURE' }) const actionTypes = Object.freeze({ ...eventTypes, CUSTOM: 'CUSTOM' }) const triggerTypes = Object.freeze({ CLICK: 'CLICK', INPUT: 'INPUT', PAGE_LOAD: 'PAGE_LOAD' }); const variableTypes = Object.freeze({ FUNCTION: 'FUNCTION', CONSTANT: 'CONSTANT' }); const trackingEntityTypes = Object.freeze({ VARIABLE: 'VARIABLE', TRIGGER: 'TRIGGER', CUSTOM_ACTION: 'CUSTOM_ACTION' }); const trackingErrorTypes = Object.freeze({ MISSING_VALUE: 'MISSING_VALUE', TYPE_MISMATCH: 'TYPE_MISMATCH', UNSUPPORTED_VARIABLE: 'UNSUPPORTED_VARIABLE', MISSING_VARIABLE: 'MISSING_VARIABLE', OTHER: 'OTHER' }); const trackingVariableScopes = Object.freeze({ GLOBAL: 'GLOBAL', LOCAL: 'LOCAL' }); const namedReferrals = Object.freeze({ SEARCH: 'SEARCH' }) const matchers = Object.freeze({ EQUALS: (a, b) => a === b, DOES_NOT_EQUAL: (a, b) => a !== b, CONTAINS: (a, b) => a.indexOf(b) >= 0, DOES_NOT_CONTAIN: (a, b) => a.indexOf(b) === -1, GREATER_THAN: (a, b) => a > b, LESS_THAN: (a, b) => a < b, GREATER_THAN_OR_EQUAL: (a, b) => a >= b, LESS_THAN_OR_EQUAL: (a, b) => a <= b, MATCHES_CSS_SELECTOR: (target, test) => target.matches(test), MATCHES_ID: (target, test) => target.id === test, CONTAINS_ID: (target, test) => target.id.includes(test), DOES_NOT_CONTAIN_ID: (target, test) => !target.id.includes(test), MATCHES_CLASS: (target, test) => target.className === test, CONTAINS_CLASS: (target, test) => target.className.includes(test), DOES_NOT_CONTAIN_CLASS: (target, test) => !target.className.includes(test), }); const trackingFieldName = `${domainId}_${zoovuId}_trackingExecutions` const MAX_RECORDS = 100 const DEBOUNCE_TIME = 1500 // helper to retrieve variableId later let eventFields = [] if (!JSON.parse(localStorage.getItem(trackingFieldName))) { localStorage.setItem(trackingFieldName, JSON.stringify([])) } function checkValue(type, value) { switch (type) { case 'BOOLEAN': return value === true || value === 'true'; case 'TEXT': return value; case 'DECIMAL': return typeof value === 'string' ? parseFloat(value) : value; case 'INTEGER': return typeof value === 'string' ? parseInt(value) : value; default: return value; } } function cast(type, value) { switch (type) { case 'BOOLEAN': return value === true || value === 'true'; case 'TEXT': return value; case 'DECIMAL': return typeof value === 'string' ? parseFloat(value) : value; case 'INTEGER': return typeof value === 'string' ? parseInt(value) : value; default: return value; } } function debounce(fn, delay) { let lastCallTime = 0; let animationFrameId = null; let isScheduled = false; function execute(context, args, currentTime) { if (currentTime - lastCallTime >= delay) { fn.apply(context, args); isScheduled = false; } else { animationFrameId = requestAnimationFrame((newTime) => { execute(context, args, newTime); }); } } return function () { const context = this, args = arguments; lastCallTime = performance.now(); if (!isScheduled) { isScheduled = true; animationFrameId = requestAnimationFrame((currentTime) => { execute(context, args, currentTime); }); } }; } function getOrganization() { return zoovuId; } function getDomainId() { return domainId; } function getTimezone() { return Intl.DateTimeFormat().resolvedOptions().timeZone; } function getReferrer() { const referrer = document.referrer; if (/^(https?|android-app):\/\//i.test(referrer)) { return referrer; } else { return undefined; } } function getPath() { return window.location.href.replace(window.location.origin, ''); } function getCookieValue(cookieName) { const cookies = document.cookie.split('; '); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].split('='); if (cookie[0] === cookieName) { return cookie[1]; } } return null; } function generateUuid() { return self.crypto.randomUUID(); } function addZoovuCidToCookies() { const uuid = generateUuid(); const zoovuCid = `zoovu-cid=${uuid}; path=/`; document.cookie = zoovuCid; return uuid; } function getCID() { return getCookieValue(`zoovu-cid`) !== null ? getCookieValue(`zoovu-cid`) : addZoovuCidToCookies(); } function getEnvironment() { return currentEnvironment; } function getAccountId() { return currentAccountId; } function getPropertyTypeError(propertyName, expectedType, currentType) { return `TYPE MISMATCH: ${propertyName}: should be ${expectedType} but is ${currentType}` } function typeCheckValue(value, variableType, propertyName, variableId, triggerId) { let errorMessage = ''; if (value === undefined || value === null) { throw new TrackingExecutionError(`${propertyName}: value is not defined.`, trackingEntityTypes.VARIABLE, variableId, triggerId, script.id, trackingErrorTypes.MISSING_VALUE) } switch (variableType) { case "TEXT": if (typeof value !== 'string') errorMessage = getPropertyTypeError(propertyName, 'string', typeof value); break; case "INTEGER": case "DECIMAL": if (typeof value !== 'number') errorMessage = getPropertyTypeError(propertyName, 'number', typeof value); break; case "BOOLEAN": if (typeof value !== 'boolean') errorMessage = getPropertyTypeError(propertyName, 'boolean', typeof value); break; case 'LIST': if (!Array.isArray(value)) errorMessage = getPropertyTypeError(propertyName, 'list', typeof value); break; case 'EVENT': break; default: throw new TrackingExecutionError(`Not supported variable type: ${variableType}`, trackingEntityTypes.VARIABLE, variableId, triggerId, script.id, trackingErrorTypes.UNSUPPORTED_VARIABLE) } if (errorMessage) { throw new TrackingExecutionError(errorMessage, trackingEntityTypes.VARIABLE, variableId, triggerId, script.id, trackingErrorTypes.TYPE_MISMATCH) } } function ensureRequiredFieldsPresent(fields, eventType) { return Object.entries(fields).every(([key, value]) => { if (!value) { const variableId = eventFields.find(field => field.fieldName === key).variableId; // empty eventFields eventFields = []; throw new TrackingExecutionError( `Required property ${key} missing value for ${eventType}.`, trackingEntityTypes.VARIABLE, variableId, null, script.id, trackingErrorTypes.MISSING_VALUE ) } return true; }); } function getBaseEventBody(eventType) { return { origin: 'CLIENT', queryParams: {}, organization: getOrganization(), domainId: getDomainId(), path: getPath(), referrer: getReferrer(), cid: getCID(), timezone: getTimezone(), // dynamic eventType: eventType ?? '', eventLabel: '', }; } function getEventExecutable(actionType) { switch (actionType) { case actionTypes.PAGE_VISITED: return sendPageVisitedEvent; case actionTypes.ADD_TO_CART: return sendAddToCartEvent; case actionTypes.CLICKOUT: return sendClickoutEvent; case actionTypes.REMOVE_FROM_CART: return sendRemoveFromCartEvent; case actionTypes.PDP_VISITED: return sendPdpVisitedEvent; case actionTypes.PURCHASED: return sendPurchaseEvent; case actionTypes.DECLINE_TRACKING: return sendDeclineTrackingEvent; case actionTypes.UPDATE_CART: return sendUpdateCartEvent; case actionTypes.SEARCH: return sendSearchEvent; case actionTypes.SEARCH_RESULT: return sendSearchResultEvent; case actionTypes.LEAD_GEN: return sendLeadGenEvent; } } function checkSpecificFieldValues(fieldName, value, variableId) { if (fieldName === 'currencyCode' && !currencies.includes(value)) { throw new TrackingExecutionError( `Value ${value} is not compatible with currencyCode.`, trackingEntityTypes.VARIABLE, variableId, null, script.id, trackingErrorTypes.OTHER ) } if (fieldName === 'namedReferral' && !namedReferrals[value.toUpperCase()]) { throw new TrackingExecutionError( `Value ${value} is not compatible with namedReferral.`, trackingEntityTypes.VARIABLE, variableId, null, script.id, trackingErrorTypes.OTHER ) } } function getVariableValueById(variableId, event, fieldName, triggerId) { const foundVariable = variables.find(v => v.id === variableId); let variableValue = null; if (foundVariable) { if (foundVariable.type === variableTypes.CONSTANT) { variableValue = cast(foundVariable.valueType, foundVariable.value); } else if (foundVariable.type === variableTypes.FUNCTION && typeof foundVariable.function === 'function') { try { variableValue = foundVariable.function(event); } catch (e) { throw new TrackingExecutionError( e.message, trackingEntityTypes.VARIABLE, variableId, triggerId, script.id, trackingErrorTypes.OTHER ) } } else { throw new TrackingExecutionError( `${foundVariable.type} is not supported.`, trackingEntityTypes.VARIABLE, variableId, triggerId, script.id, trackingErrorTypes.UNSUPPORTED_VARIABLE ) } typeCheckValue(variableValue, foundVariable.valueType, fieldName ? fieldName : foundVariable.name, variableId, triggerId); if (fieldName === 'currencyCode' || fieldName === 'namedReferral') { checkSpecificFieldValues(fieldName, variableValue, variableId) } return variableValue; } else { throw new TrackingExecutionError( `No variable with ID ${variableId}`, trackingEntityTypes.VARIABLE, variableId, triggerId, script.id, trackingErrorTypes.MISSING_VARIABLE ) } } function evaluateSingleTriggerRow(row, event, triggerId) { const target = getVariableValueById(row.variableId, event, undefined, triggerId); const matcher = matchers[row.operator]; checkValue(row.valueType, row.value); const value = cast(row.valueType, row.value); if (matcher && typeof matcher === 'function') { return matcher(target, value); } return false; } function preparePayloadFromFields(fields, event) { const payload = {}; fields.forEach(f => { payload[f.fieldName] = getVariableValueById(f.variableId, event, f.fieldName) }) return payload; } function evaluateTriggerConditions(trigger, event) { // OR between groups return trigger.groups.some(group => // AND between rows group.rows.every(row => evaluateSingleTriggerRow(row, event, trigger.id))) } function runAction(action, fields, event, trigger) { const trackingExecutions = jsonParser(trackingFieldName) if (action.type === actionTypes.CUSTOM && typeof action.code === 'function') { // add script custom action trackingExecutions.push(createTrackingSuccessfulExecutionRecord(trackingEntityTypes.CUSTOM_ACTION, action.id)) try { action.code(event); } catch (e) { throw new TrackingExecutionError( e.message, trackingEntityTypes.CUSTOM_ACTION, action.id, null, script.id, trackingErrorTypes.OTHER ) } localStorage.setItem(trackingFieldName, JSON.stringify(trackingExecutions)); } else { const sendEventFunction = getEventExecutable(action.type); eventFields = [...fields]; if (sendEventFunction && typeof sendEventFunction === 'function') { const payload = preparePayloadFromFields(fields, event) sendEventFunction(payload); addSuccessfulExecutions(trigger, fields) } } } function evaluateSingleRule(rule, event) { if (evaluateTriggerConditions(rule.trigger, event)) { runAction(rule.action, rule.fields, event, rule.trigger); } } function evaluateRules(rules, event) { try { rules.forEach(rule => { evaluateSingleRule(rule, event) }); } catch (error) { const trackingExecutions = jsonParser(trackingFieldName); const {message, trackingEntityType, variableId, triggerId, scriptId, trackingErrorType} = error; const failedExecution = createTrackingFailedExecutionRecord( trackingEntityType, variableId, triggerId, scriptId, trackingErrorType, message ) trackingExecutions.push(failedExecution) localStorage.setItem(trackingFieldName, JSON.stringify(trackingExecutions)); console.debug(error) } } function observeAndReactOnPageChange(callback) { let oldHref; if (oldHref === undefined) { callback(); oldHref = document.location.href; } const body = document.querySelector('body'); const observer = new MutationObserver(() => { if (oldHref !== document.location.href) { oldHref = document.location.href; callback(); } }); observer.observe(body, {childList: true, subtree: true}); } function executeAdvancedCode() { if (typeof advancedCode === 'function') { advancedCode(); } } function jsonParser(key) { const foundValue = JSON.parse(localStorage.getItem(key)) if (!Array.isArray(foundValue)) { throw Error('Invalid value in localStorage for key ' + key) } return foundValue } function createTrackingSuccessfulExecutionRecord(entityType, entityId) { const executionTime = Date.now(); return { organization: getOrganization(), domainId: getDomainId(), eventType: trackingEventTypes.TRACKING_SUCCESSFUL_EXECUTION, trackingEntityType: entityType, trackingEntityId: entityId, executionTime } } function createTrackingFailedExecutionRecord(entityType, entityId, triggerId, scriptId, errorType, message) { const executionTime = Date.now(); return { organization: getOrganization(), domainId: getDomainId(), eventType: trackingEventTypes.TRACKING_EXECUTION_FAILURE, trackingEntityType: entityType, trackingEntityId: entityId, executionTime, triggerId: triggerId || null, scriptId: scriptId || null, error: { type: errorType, message } } } function addSuccessfulExecutions(trigger, fields) { const successfulExecutions = jsonParser(trackingFieldName) // add script trigger successfulExecutions.push(createTrackingSuccessfulExecutionRecord(trackingEntityTypes.TRIGGER, trigger.id)) // add script function variables fields.forEach(field => { const foundVariableInField = variables.find(variable => variable.id === field.variableId) if (foundVariableInField.type === variableTypes.FUNCTION && foundVariableInField.scope === trackingVariableScopes.LOCAL) { successfulExecutions.push(createTrackingSuccessfulExecutionRecord(trackingEntityTypes.VARIABLE, foundVariableInField.id)) } }) // add trigger function variables trigger.groups.forEach(groups => { groups.rows.forEach(row => { const foundVariable = variables.find(variable => variable.id === row.variableId) if (foundVariable.type === variableTypes.FUNCTION && foundVariable.scope === trackingVariableScopes.LOCAL) { successfulExecutions.push(createTrackingSuccessfulExecutionRecord(trackingEntityTypes.VARIABLE, foundVariable.id)) } }) }) localStorage.setItem(trackingFieldName, JSON.stringify(successfulExecutions)); } // ------------------------- events ------------------------- async function sendEvent(body) { if (trackingEnabled) { await fetch(`${url}/fact`, { method: 'POST', mode: 'cors', cache: 'no-cache', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', }, redirect: 'follow', referrerPolicy: 'no-referrer', body: JSON.stringify(body), }); } else { console.debug("Tracking disabled - no permission to track"); } } async function sendSSTRPurchaseEvent(body) { if (trackingEnabled) { const purchaseUrl = 'https://ev-co.zoovu.com/v1/CollectSalesEvent'; await fetch(purchaseUrl, { method: 'POST', mode: 'cors', cache: 'no-cache', headers: { 'Content-Type': 'application/json', }, redirect: 'follow', referrerPolicy: 'no-referrer', body: JSON.stringify(body), }); } else { console.debug("Tracking disabled - no permission to track"); } } async function sendTrackingSuccessfulExecution(body) { if (trackingEnabled) { await fetch(`${url}/tracking/executions`, { method: 'POST', mode: 'cors', cache: 'no-cache', headers: { 'Content-Type': 'application/json', }, redirect: 'follow', referrerPolicy: 'no-referrer', body: JSON.stringify(body), }); } else { console.debug("Tracking disabled - no permission to track"); } } function sendFactEventBase(eventType, eventSpecificBody) { const body = { ...getBaseEventBody(eventType), // event label + custom per-event fields ...eventSpecificBody, }; sendEvent(body); }; function sendPageVisitedEvent({locale, category, eventLabel = 'Page visit'}) { const body = { locale, eventLabel, // optional category, }; if (ensureRequiredFieldsPresent({locale}, eventTypes.PAGE_VISITED)) { sendFactEventBase(eventTypes.PAGE_VISITED, body); } }; function sendPdpVisitedEvent({ locale, sku, quantity = 1, currencyCode, eventLabel = "Product details page visit", price, category }) { const body = { locale, sku, eventLabel, // optional category, price, currencyCode, quantity, }; if (ensureRequiredFieldsPresent({locale, sku}, eventTypes.PDP_VISITED)) { sendFactEventBase(eventTypes.PDP_VISITED, body); } }; function sendAddToCartEvent({ locale, sku, quantity = 1, currencyCode, eventLabel = "Add to cart", price, category }) { const body = { locale, sku, eventLabel, // optional category, price, currencyCode, quantity, }; if (ensureRequiredFieldsPresent({locale, sku}, eventTypes.ADD_TO_CART)) { sendFactEventBase(eventTypes.ADD_TO_CART, body); } }; function sendRemoveFromCartEvent({ locale, sku, quantity = 1, currencyCode, eventLabel = "Remove from cart", price, category }) { const body = { locale, sku, eventLabel, // optional category, price, currencyCode, quantity, }; if (ensureRequiredFieldsPresent({locale, sku}, eventTypes.REMOVE_FROM_CART)) { sendFactEventBase(eventTypes.REMOVE_FROM_CART, body); } }; function sendUpdateCartEvent({ locale, sku, quantity = 1, currencyCode, eventLabel = "Update cart", price, category }) { const body = { locale, sku, eventLabel, // optional category, price, currencyCode, quantity, }; if (ensureRequiredFieldsPresent({locale, sku}, eventTypes.UPDATE_CART)) { sendFactEventBase(eventTypes.UPDATE_CART, body); } }; function sendDeclineTrackingEvent({ locale, eventLabel = "No permission to track", category }) { const body = { locale, eventLabel, // optional category }; if (ensureRequiredFieldsPresent({locale}, eventTypes.DECLINE_TRACKING)) { sendFactEventBase(eventTypes.DECLINE_TRACKING, body); disableTracking(); } }; function sendSearchEvent({ locale, eventLabel = "Search phrase typed", category }) { const body = { locale, eventLabel, // optional category, }; if (ensureRequiredFieldsPresent({locale}, eventTypes.SEARCH)) { sendFactEventBase(eventTypes.SEARCH, body); } }; function sendSearchResultEvent({ locale, isEmpty = false, eventLabel = "Search results", skus, category }) { const body = { locale, isEmpty, skus, eventLabel, // optional category }; if (ensureRequiredFieldsPresent({locale, skus}, eventTypes.SEARCH_RESULT)) { sendFactEventBase(eventTypes.SEARCH_RESULT, body); } }; function sendClickoutEvent({ locale, targetUrl, namedReferral, eventLabel = "Clickout", category }) { const body = { locale, targetUrl, namedReferral, eventLabel, // optional category }; if (ensureRequiredFieldsPresent({locale, targetUrl, namedReferral}, eventTypes.CLICKOUT)) { sendFactEventBase(eventTypes.CLICKOUT, body); } }; function sendPurchaseEvent({ currencyCode, transactionId, products }) { const sstrBody = { transactionId, products, currency: currencyCode, browserTimestamp: Date.now(), env: getEnvironment(), accountId: getAccountId(), clientId: getCID(), }; if (ensureRequiredFieldsPresent({products, currencyCode,}, eventTypes.PURCHASED)) { sendSSTRPurchaseEvent(sstrBody); } }; function sendLeadGenEvent({ locale, currencyCode, products, leadId, leadType, category, eventLabel = "Lead gen sent" }) { const body = { locale, eventLabel, // optional currencyCode, products, leadId, leadType, category }; if (ensureRequiredFieldsPresent({locale}, eventTypes.LEAD_GEN)) { sendFactEventBase(eventTypes.LEAD_GEN, body); } }; function sendSuccessfulExecutionEvent(forceSend) { const successfulExecutions = jsonParser(trackingFieldName) if (successfulExecutions.length === MAX_RECORDS || (forceSend && successfulExecutions.length > 0)) { sendTrackingSuccessfulExecution(successfulExecutions) localStorage.setItem(trackingFieldName, JSON.stringify([])) } } // ------------------------- core ------------------------- function reactOnLoad() { const pageLoadRules = script.rows.filter(row => row.trigger.type === triggerTypes.PAGE_LOAD); window.addEventListener("load", (event) => { observeAndReactOnPageChange(() => evaluateRules(pageLoadRules, event) && sendSuccessfulExecutionEvent()) }, {capture: true}); } function reactOnClick() { const clickRules = script.rows.filter(row => row.trigger.type === triggerTypes.CLICK); // click via mouse document.querySelector('body').addEventListener('mousedown', (event) => { evaluateRules(clickRules, event); sendSuccessfulExecutionEvent() }, {capture: true}); // click via enter button document.querySelector('body').addEventListener('keydown', (event) => { if (event.code === "Enter" || event.code === "NumpadEnter") { evaluateRules(clickRules, event); sendSuccessfulExecutionEvent() } }, {capture: true}); } function reactOnInput() { const inputRules = script.rows.filter(row => row.trigger.type === triggerTypes.INPUT); document.querySelector('body').addEventListener('input', debounce(function (event) { evaluateRules(inputRules, event); sendSuccessfulExecutionEvent() }, DEBOUNCE_TIME), {capture: true}); } function reactOnMouseLeave() { document.addEventListener("mouseleave", function (event) { if (event.clientY <= 0 || event.clientX <= 0 || (event.clientX >= window.innerWidth || event.clientY >= window.innerHeight)) { sendSuccessfulExecutionEvent(true) } }); } function track() { try { executeAdvancedCode(); reactOnLoad(); reactOnClick(); reactOnInput(); reactOnMouseLeave(); } catch (error) { console.debug("Tracking disabled", error); } } // RUN SCRIPT AFTER PAGE LOAD if (document.readyState === "loading" || document.readyState === "interactive") { // Loading hasn't finished yet document.addEventListener("readystatechange", (event) => { if (event.target.readyState === "complete") { track(); } }); } else { track(); } })();