@shopgate/tracking-core
Version:
Tracking core library for the Shopgate Connect PWA.
165 lines • 13.7 kB
JavaScript
function _extends(){_extends=Object.assign||function(target){for(var i=1;i<arguments.length;i++){var source=arguments[i];for(var key in source){if(Object.prototype.hasOwnProperty.call(source,key)){target[key]=source[key];}}}return target;};return _extends.apply(this,arguments);}function _typeof(obj){if(typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"){_typeof=function _typeof(obj){return typeof obj;};}else{_typeof=function _typeof(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};}return _typeof(obj);}import{hex2bin,SGLink}from"./helper";import sgTrackingUrlMapper from"./urlMapping";import{customEvents}from"./events";/**
* Gets the value at path of object. If the resolved value is undefined,
* the defaultValue is used in its place.
*
* @param {Object} object The object to query
* @param {string} path The path of the property to get
* @param {*} [defaultValue] The value returned for undefined resolved values
* @returns {*} Returns the resolved value
*/function get(object,path,defaultValue){// Initialize the parameters
var data=object||{};var dataPath=path||'';var defaultReturnValue=defaultValue;// Get the segments of the path
var pathSegments=dataPath.split('.');if(!dataPath||!pathSegments.length){// No path or path segments where determinable - return the default value
return defaultReturnValue;}/**
* Recursive callable function to traverse through a complex object
*
* @param {Object} currentData The current data that shall be investigated
* @param {number} currentPathSegmentIndex The current index within the path segment list
* @returns {*} The value at the end of the path or the default one
*/function checkPathSegment(currentData,currentPathSegmentIndex){// Get the current segment within the path
var currentPathSegment=pathSegments[currentPathSegmentIndex];var nextPathSegmentIndex=currentPathSegmentIndex+1;/**
* Prepare the default value as return value for the case that no matching property was
* found for the current path segment. In that case the path must be wrong.
*/var result=defaultReturnValue;if(currentData&¤tData.hasOwnProperty(currentPathSegment)){if(_typeof(currentData[currentPathSegment])!=='object'||pathSegments.length===nextPathSegmentIndex){// A final value was found
result=currentData[currentPathSegment];}else{// The value at the current step within the path is another object. Traverse through it
result=checkPathSegment(currentData[currentPathSegment],nextPathSegmentIndex);}}return result;}// Start traversing the path within the data object
return checkPathSegment(data,0);}/**
* Converts a numeric value into a suitable one for the unified tracking data
*
* @param {*} numericValue The value that shall be converted
* @returns {number|undefined} The converted value
* @private
*/function getUnifiedNumber(numericValue){// Convert the value
var convertedValue=parseFloat(numericValue);// Check if the converted value is numeric. If it's not, return "undefined" instead of it
// eslint-disable-next-line no-restricted-globals
return!isNaN(convertedValue)?convertedValue:undefined;}/**
* Converts a value to a string. It returns an empty string for null or undefined.
* @param {*} value The value.
* @return {string}
*/function getStringValue(value){return value||value===0?"".concat(value):'';}/**
* Returns the productNumber or uid from a product
*
* @param {Object} product Object with product data
* @returns {string} productNumber or uid of the product
* @private
*/function getProductIdentifier(product){return get(product,'productNumber')||get(product,'uid');}/**
* Removes shop names out of the page title
* @param {string} title The page title
* @param {string} shopName The shop name
* @returns {string} The sanitized page title
*/function sanitizeTitle(title,shopName){// Take care that the parameters don't contain leading or trailing spaces
var trimmedTitle=title.trim();var trimmedShopName=shopName.trim();if(!trimmedShopName){/**
* If no shop name is available, it doesn't make sense to replace it.
* So we return the the trimmed title directly.
*/return trimmedTitle;}/**
* Setup the RegExp. It matches leading and trailing occurrences
* of known patterns for generically added shop names within page title
*/var shopNameRegExp=new RegExp("((^".concat(trimmedShopName,":)|(- ").concat(trimmedShopName,"$))+"),'ig');if(trimmedTitle===trimmedShopName){// Clear the page title if it only contains the shop name
trimmedTitle='';}// Remove the shop name from the page title
return trimmedTitle.replace(shopNameRegExp,'').trim();}/**
* Convert sgData product to unified item
*
* @param {Object} product Item from sgData
* @returns {Object} Data for the unified item
*/function formatSgDataProduct(product){return{id:getStringValue(getProductIdentifier(product)),type:'product',name:get(product,'name'),priceNet:getUnifiedNumber(get(product,'amount.net')),priceGross:getUnifiedNumber(get(product,'amount.gross')),quantity:getUnifiedNumber(get(product,'quantity')),currency:get(product,'amount.currency'),brand:get(product,'manufacturer')};}/**
* Convert sgData products to unified items
*
* @param {Array} products Items from sgData
* @returns {UnifiedPurchaseItem[] | UnifiedAddToCartItem[]} Data for the unified items
*/function formatSgDataProducts(products){// TODO: Error handling for malformed data
if(!products||!Array.isArray(products)){return[];}return products.map(formatSgDataProduct);}/**
* Convert products from the favouriteListItemAdded event to unified items
*
* @param {Array} products Items from sgData
* @returns {UnifiedAddToWishlistItem[]} Data for the unified items
*/function formatFavouriteListItems(products){if(!products||!Array.isArray(products)){return[];}return products.map(function(product){return{id:getStringValue(get(product,'product_number_public')||get(product,'product_number')||get(product,'uid')),type:'product',name:get(product,'name'),priceNet:getUnifiedNumber(get(product,'unit_amount_net'))/100,priceGross:getUnifiedNumber(get(product,'unit_amount_with_tax'))/100,currency:get(product,'currency_id')};});}/**
* Storage for helper functions that transforms raw data
* into the unified format for the tracking plugins
*/var dataFormatHelpers={};/**
* Converter for the custom events
*
* @param {Object} data Raw data from the core
* @returns {UnifiedCustomEvent} Data for the unified custom event
* @private
*/var formatEventData=function formatEventData(data){return _extends({eventCategory:'',eventAction:'',eventLabel:null,eventValue:null,nonInteraction:false},data);};// Assign the format helper to all custom events
customEvents.forEach(function(event){dataFormatHelpers[event]=formatEventData;});/**
* Converter for the purchase event
*
* @param {Object} rawData Raw data from the core
* @returns {UnifiedPurchase} Data for the unified purchase event
*/dataFormatHelpers.purchase=function(rawData){return{id:getStringValue(get(rawData,'order.number')),type:'product',affiliation:get(rawData,'shop.name',''),revenueGross:getUnifiedNumber(get(rawData,'order.amount.gross')),revenueNet:getUnifiedNumber(get(rawData,'order.amount.net')),tax:getUnifiedNumber(get(rawData,'order.amount.tax')),shippingGross:getUnifiedNumber(get(rawData,'order.shipping.amount.gross')),shippingNet:getUnifiedNumber(get(rawData,'order.shipping.amount.net')),currency:get(rawData,'order.amount.currency'),items:formatSgDataProducts(get(rawData,'order.products'))};};/**
* Converter for the pageview event
*
* @param {Object} rawData Raw data from the core
* @returns {{page: {merchantUrl: string, shopgateUrl: string}}} Formatted data
*/dataFormatHelpers.pageview=function(rawData){var mappedUrls=sgTrackingUrlMapper(get(rawData,'page.link'),rawData);return{page:{merchantUrl:mappedUrls["public"],shopgateUrl:mappedUrls["private"]}};};/**
* Converter for the viewContent event
*
* @param {Object} rawData Raw data from the core
* @returns {UnifiedPageview} Data for the unified page view event
*/dataFormatHelpers.viewContent=function(rawData){var link=get(rawData,'page.link');if(link.indexOf('sg_app_resources')!==-1){/**
* Check if the link is formatted in the app style
* and reformat it do make it parsable with SGLink
* Note: this will be removed when CON-410 is done
*/link="http://dummy.com".concat(link.replace(/(.*sg_app_resources\/\d*)/i,''));}link=new SGLink(link);/**
* In splittedPath we have the action as the first array entry
* to get the id/action params we remove the action from the array
*/var splittedPath=[].concat(link.splittedPath);splittedPath.shift();/**
* All pages have action as type and a parsed page title without shop name as name
* fb content ID = 'id / name'
*/var id=splittedPath.join('/');var type=link.action||'index';var name=sanitizeTitle(get(rawData,'page.title',''),get(rawData,'shop.name',''))||get(rawData,'page.name');/**
* Category, product and product related pages should have productnumber/categorynumber as id
* they should have product name / category name as name
*/if(rawData.hasOwnProperty('product')&&type==='item'){type='product';id=getProductIdentifier(rawData.product);}else if(rawData.hasOwnProperty('category')&&type==='category'){id=get(rawData,'category.uid');if(splittedPath.indexOf('all')!==-1){id+='/all';}}else if(rawData.hasOwnProperty('search')&&type==='search'){id=get(rawData,'search.query');name=name.substring(0,name.indexOf(':')).trim();}else if(['product_info','reviews','add_review'].indexOf(type)!==-1){id=hex2bin(link.splittedPath[1])||'';}else if(type==='payment_success'){type='checkout_success';}return{id:id,type:type,name:name};};/**
* Converter for the addToCart event
*
* @param {Object} rawData Raw data from the core
* @returns {UnifiedAddToCart} data for the addToCart event
*/dataFormatHelpers.addToCart=function(rawData){return{type:'product',items:formatSgDataProducts(get(rawData,'products'))};};/**
* Converter for the variantSelected event
*
* @param {Object} rawData {variant:{}, baseProduct:{}} Raw data from the core
* @returns {Object} data for the addToCart event
*/dataFormatHelpers.variantSelected=function(rawData){return{variant:formatSgDataProduct(rawData.variant),baseProduct:formatSgDataProduct(rawData.baseProduct)};};/**
* Converter for the addToWishlist event
*
* @param {Object} rawData Raw data from the core
* @returns {UnifiedAddToWishlist} data for the addToWishlist event
*/dataFormatHelpers.addToWishlist=function(rawData){return{type:'product',items:formatFavouriteListItems(get(rawData,'favouriteListProducts'))};};/**
* Converter for the initiatedCheckout event
*
* @param {Object} rawData Raw data from the core
* @returns {UnifiedInitiatedCheckout} data for the addToCart event
*/dataFormatHelpers.initiatedCheckout=function(rawData){var checkoutType=get(rawData,'checkoutType','default');// Get amount, depending if pp express on cart or item page was clicked
var amount=get(rawData,'product.amount')||get(rawData,'cart.amount');return{type:checkoutType,valueNet:getUnifiedNumber(get(amount,'net')),valueGross:getUnifiedNumber(get(amount,'gross')),// PP express on item page sends the quantity directly
numItems:getUnifiedNumber(get(rawData,'quantity')||get(rawData,'cart.productsCount')),currency:get(amount,'currency'),paymentInfoAvailable:checkoutType!=='default'};};/**
* Converter for the completedRegistration event
* @param {Object} rawData rawData Raw data from the core
* @returns {UnifiedCompletedRegistration} Information about the registration type
*/dataFormatHelpers.completedRegistration=function(rawData){return{registrationMethod:get(rawData,'registrationType')};};/**
* Converter for the search event
*
* @param {Object} rawData Raw data from the core
* @returns {UnifiedSearched} data for the search event
*/dataFormatHelpers.search=function(rawData){var hits=get(rawData,'search.resultCount');return{type:'product',query:get(rawData,'search.query'),hits:getUnifiedNumber(hits),success:!!hits};};/**
* Return the url from the rawData
*
* @param {Object} rawData Raw data from the core
* @returns {Object} data for the setCampaignWithUrl event
*/dataFormatHelpers.setCampaignWithUrl=function(rawData){return{url:rawData.url};};/**
* Converter for the addedPaymentInfo event
*
* @param {Object} rawData Raw data from the core
* @returns {UnifiedPaymentInfo} Data for the AddedPaymentInfo event
*/dataFormatHelpers.addedPaymentInfo=function(rawData){return{success:get(rawData,'paymentMethodAdded.success'),name:get(rawData,'paymentMethodAdded.name')};};/**
* Converter for the selectedPaymentInfo event. It's compatible to the addedPaymentInfo event, but
* other than this, it's also triggered when a payment method was selected which doesn't have
* configurable entities, like "credit card".
*
* @param {Object} rawData Raw data from the core
* @returns {UnifiedPaymentInfo} Data for the SelectedPaymentInfo event
*/dataFormatHelpers.selectedPaymentInfo=function(rawData){return{success:get(rawData,'paymentMethodSelected.success'),name:get(rawData,'paymentMethodSelected.name')};};/**
* Converter for the logItemView event.
*
* @param {Object} rawData Raw data from the core
* @returns {UnifiedItemView} Data for the logItemView event
*/dataFormatHelpers.itemView=function(rawData){var product;if(rawData.product){product=rawData.product;}else if(rawData.variant){product=rawData.variant;}product=formatSgDataProduct(product);delete product.quantity;return _extends({},product,{type:''});};export default dataFormatHelpers;