1/** 2 * WizAnalytics Destination â thin adapter for WizAnalytics backend API. 3 * 4 * Receives enriched events from WizTracking pipeline. 5 * Normalizes payload per schema, builds wire format, POSTs to /raw-events/track. 6 * 7 * Handles ALL event types. 8 */ 9(function () { 10 'use strict'; 11 12 var CONFIG = { 13 TRACK_PATH: '/raw-events/track', 14 CHANNEL: 'Wizshop' 15 }; 16 17 /** 18 * Payload schema per event type: defaults + optional fields. 19 * Controls what goes in the wire payload to the analytics backend. 20 */ 21 var PAYLOAD_SCHEMAS = { 22 product_interaction: { 23 defaults: { type: undefined, variant_id: '', source: 'all_products' }, 24 optional: ['product_name', 'price', 'in_stock', 'inventory_available', 'discount_value', 'category_id', 'category_name', 'collection_id', 'collection_name', 'quantity', 'page_number', 'cart_id'] 25 }, 26 perform_search: { 27 defaults: { search_term: '', result_count: 0, type: 'keyword' }, 28 optional: [] 29 }, 30 cart_viewed: { 31 defaults: { cart_id: '', number_of_products: 0 }, 32 optional: ['items', 'currency'] 33 }, 34 checkout_started: { 35 defaults: { item_count: '', cart_value: '', checkout_source: '', coupon_code: '', cart_id: '', cart_grouping: '' }, 36 optional: ['meta', 'items', 'currency'] 37 }, 38 checkout_steps: { 39 defaults: { step_number: '', step_name: '', cart_id: '', document_id: '' }, 40 optional: ['meta', 'items', 'currency'] 41 }, 42 order_placed: { 43 defaults: { order_id: '', order_value: '', product_count: '', coupon_code: '' }, 44 optional: ['meta', 'items', 'currency'] 45 }, 46 device_id_mapping: { 47 defaults: { device_id: '', user_id: '' }, 48 optional: [] 49 }, 50 perform_suggested_search: { 51 defaults: { search_label: '' }, 52 optional: [] 53 }, 54 cancel_suggested_search: { 55 defaults: { search_label: '' }, 56 optional: [] 57 } 58 }; 59 60 function hasValue(v) { 61 return v !== undefined && v !== null && v !== ''; 62 } 63 64 var apiEndpoint = null; 65 66 /** 67 * Normalize payload per schema â apply defaults, strip unknown keys. 68 */ 69 function normalizePayload(evt) { 70 var schema = PAYLOAD_SCHEMAS[evt.type]; 71 if (!schema) return evt; 72 73 var p = evt.payload || {}; 74 var out = {}; 75 76 if (schema.defaults) { 77 for (var k in schema.defaults) { 78 if (Object.prototype.hasOwnProperty.call(schema.defaults, k)) { 79 out[k] = hasValue(p[k]) ? p[k] : schema.defaults[k]; 80 } 81 } 82 } 83 if (schema.optional) { 84 for (var i = 0; i < schema.optional.length; i++) { 85 var optKey = schema.optional[i]; 86 if (hasValue(p[optKey])) out[optKey] = p[optKey]; 87 } 88 } 89 90 return { type: evt.type, payload: out, context: evt.context, meta: evt.meta }; 91 } 92 93 /** 94 * Build wire-format payload for the WizAnalytics API. 95 */ 96 function buildWirePayload(evt) { 97 var context = evt.context || {}; 98 var fp = context.fingerprint || {}; 99 var ip = fp.ipLocation || {}; 100 var userDetails = context.userDetails; 101 var is_loggedin = !!(userDetails && (userDetails.id || userDetails.user_id)); 102 var customerMetadata = context.customerMetadata || {}; 103 104 var properties = { 105 time: Math.floor((evt.meta && evt.meta.ts) ? evt.meta.ts / 1000 : Date.now() / 1000), 106 $browser: fp.browserName || '', 107 $browser_version: fp.browserVersion || '', 108 $city: (ip.city && ip.city.name) || '', 109 $region: (ip.subdivisions && ip.subdivisions[0] && ip.subdivisions[0].name) || '', 110 $country: (ip.country && ip.country.name) || '', 111 $timezone: ip.timezone || '', 112 $continent: (ip.continent && ip.continent.name) || '', 113 $accuracy_radius: ip.accuracyRadius || '', 114 $current_url: context.currentUrl || '', 115 $visitor_id: fp.visitorId || '', 116 $postal_code: ip.postalCode || '', 117 $latitude: ip.latitude || '', 118 $longitude: ip.longitude || '', 119 $request_id: fp.requestId || '',
120 $initial_referrer: context.initialReferrer || '', 121 $initial_referring_domain: context.initialReferringDomain || '', 122 $os: fp.os || '', 123 $os_version: fp.osVersion || '', 124 $ip: fp.ip || '', 125 $screen_height: context.screenHeight || 0, 126 $screen_width: context.screenWidth || 0, 127 channel: CONFIG.CHANNEL, 128 customer_metadata: customerMetadata, 129 email: userDetails ? (userDetails.email || '') : '', 130 first_name: userDetails ? (userDetails.first_name || userDetails.user_first_name || '') : '', 131 is_web: true, 132 last_name: userDetails ? (userDetails.last_name || userDetails.user_last_name || '') : '', 133 phone: userDetails ? (userDetails.phone || '') : '', 134 tenant_id: (userDetails && userDetails.tenant_id) || localStorage.getItem('tenant_id') || '', 135 user_id: userDetails ? (userDetails.id || userDetails.user_id || '') : '', 136 is_loggedin: is_loggedin, 137 meta: evt.meta || {} 138 }; 139 140 if (evt.payload && typeof evt.payload === 'object') { 141 var payloadCopy = Object.assign({}, evt.payload); 142 delete payloadCopy.event; 143 Object.assign(properties, payloadCopy); 144 } 145 146 var snakeEventName = (evt.type || '').replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, ''); 147 var result = { event: snakeEventName, properties: properties }; 148 var payloadStr = JSON.stringify(result); 149 try { 150 result.properties.event_size = new TextEncoder().encode(payloadStr).length; 151 } catch (e) { 152 result.properties.event_size = payloadStr.length; 153 } 154 return result; 155 } 156 157 /** 158 * POST to WizAnalytics API. Fire-and-forget. 159 */ 160 function sendEvent(wirePayload) { 161 if (!apiEndpoint) return; 162 var tenantId = localStorage.getItem('tenant_id') || ''; 163 164 fetch(apiEndpoint + CONFIG.TRACK_PATH, { 165 method: 'POST', 166 headers: { 167 'Content-Type': 'application/json', 168 'channel': CONFIG.CHANNEL, 169 'tenant-id': tenantId 170 }, 171 body: JSON.stringify({ events: [wirePayload] }), 172 keepalive: true 173 }).catch(function () { 174 // Silently fail â don't break page 175 }); 176 } 177 178 /** 179 * Destination handler â receives enriched event from pipeline. 180 */ 181 function handle(enrichedEvent) { 182 if (!apiEndpoint) return; 183 var normalized = normalizePayload(enrichedEvent); 184 var wirePayload = buildWirePayload(normalized); 185 sendEvent(wirePayload); 186 } 187 188 // Register with pipeline when DOM is ready 189 function register() { 190 if (typeof wizTrackerConfig !== 'undefined') { 191 apiEndpoint = wizTrackerConfig.apiEndpoint || null; 192 } 193 if (!apiEndpoint) return; 194 195 if (window.WizTracking) { 196 window.WizTracking.addDestination('wizAnalytics', { 197 events: [ 198 'product_interaction', 199 'perform_search', 200 'cart_viewed', 201 'checkout_started', 202 'checkout_steps', 203 'order_placed', 204 'device_id_mapping', 205 'perform_suggested_search', 206 'cancel_suggested_search' 207 ], 208 handler: handle 209 }); 210 } 211 } 212 213 if (document.readyState === 'loading') { 214 document.addEventListener('DOMContentLoaded', register); 215 } else { 216 register(); 217 } 218 219})();
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.