1/* International site integration. SDK owns transport, identity, sessions and durations. */ 2(function (window, document) { 3 'use strict'; 4 var site = window.IbiSite = {}; 5 var currentViewId, initialUrl, activeSearch, config, metadata, sourceViewId; 6 var memory = {}; 7 function safely(fn) { try { return fn(); } catch (_) { return undefined; } } 8 function diagnostic(code) { 9 safely(function () { if (window.console) window.console.warn('[IBI]', code); }); 10 } 11 function saved(key, value) { 12 key = 'ibi-site:' + (config && config.platformId || '') + ':' + key; 13 if (arguments.length > 1) { memory[key] = value; safely(function () { window.sessionStorage.setItem(key, value); }); return value; } 14 return safely(function () { return window.sessionStorage.getItem(key); }) || memory[key]; 15 } 16 function id() { 17 return window.crypto && window.crypto.randomUUID ? window.crypto.randomUUID() : 18 Date.now().toString(36) + '-' + Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2); 19 } 20 function url(value) { return new URL(value, window.location.href); } 21 function fingerprint(value) { 22 var target = url(value); 23 ['search_id', 'page', 'pageNumber', 'pageSize', 'order', 'sort', 'ibi_source_page_view_id'].forEach(function (key) { target.searchParams.delete(key); }); 24 target.searchParams.sort(); 25 return target.pathname.replace(/\/page\/\d+(?:\.html)?\/?$/, '').replace(/(?:\.html|\/)$/, '') + '?' + target.searchParams.toString(); 26 } 27 function searchContext(value, fresh) { 28 var target = url(value), key = fingerprint(value); 29 var searchId = fresh ? id() : target.searchParams.get('search_id') || saved('search:' + key) || id(); 30 saved('search:' + key, searchId); 31 return { search_id: searchId, key: key, offset: 0 }; 32 } 33 site.searchUrl = function (value, fresh) { 34 return safely(function () { 35 var target = url(value), context = searchContext(value, fresh); 36 target.searchParams.set('search_id', context.search_id); 37 return target.href; 38 }) || value; 39 }; 40 site.prepareSearchRequest = function (params, fresh) { 41 return safely(function () { 42 var target = url('/part-search.html'); 43 Object.keys(params).forEach(function (key) { target.searchParams.set(key, params[key]); }); 44 var context = searchContext(target.href, fresh); 45 params.search_id = context.search_id; 46 return params; 47 }) || params; 48 }; 49 site.flowUrl = function (value) { 50 safely(function () { 51 var target = url(value); 52 if (currentViewId && target.origin === window.location.origin) { 53 saved('source:' + target.href.split('#')[0], JSON.stringify({ id: currentViewId, from: window.location.href, at: Date.now() })); 54 } 55 }); 56 return value; 57 }; 58 site.track = function (name, properties, flush, options) { 59 safely(function () { 60 if (!window.tracker) return; 61 window.tracker.track(name, properties, options); 62 if (flush) Promise.resolve(window.tracker.flush()).catch(function () { }); 63 }); 64 }; 65 site.success = function (response, source) { 66 safely(function () { 67 // Bonded registration has its own string code, and is adapted explicitly below. 68 if (!response || response.code !== 200 || !response.ibi_tracking) return; 69 var payload = response.ibi_tracking, registration = payload.registration; 70 if (registration && registration.user_id && registration.registration_id && window.tracker) { 71 window.tracker.identify(String(registration.user_id)); 72 site.track('sign_up_success', { registration_id: String(registration.registration_id) }, true); 73 } 74 (payload.rfqs || []).forEach(function (rfq) { 75 if (rfq.rfq_id) site.track('rfq_submit_success', Object.assign({}, rfq, source ? { source_page_view_id: source } : {}), true); 76 }); 77 (payload.orders || []).forEach(function (order) { 78 if (order.order_id && Array.isArray(order.items) && order.items.length && order.items.every(function (item) { return item.product_id && item.category_id; })) { 79 site.track('order_created', Object.assign({}, order, source ? { source_page_view_id: source } : {}), true); 80 } 81 }); 82 }); 83 }; 84 site.searchResults = function (result, context) { 85 return safely(function () { 86 if (!result || !Number.isInteger(result.result_count) || result.result_count < 0 || !result.query || !context) return; 87 activeSearch = context; 88 activeSearch.offset = Number(result.offset) || 0; 89 // Preserve whole Unicode characters within the collector's 1024-byte limit. 90 var query = '', bytes = 0; 91 for (var character of String(result.query)) { 92 var size = new TextEncoder().encode(character).length; 93 if (bytes + size > 1024) break; 94 query += character; bytes += size; 95 } 96 // Repeated responses use SDK business-key deduplication and its original event snapshot. 97 site.track('search_results', { search_id: context.search_id, query: query, result_count: result.result_count }); 98 }); 99 }; 100 site.init = function (settings, data) { 101 config = settings; 102 metadata = data || { page: { page_type: 'other' } }; 103 safely(function () { 104 // Canonicalise search context BEFORE installing automatic PV/history listeners. 105 if (metadata.page.page_type === 'search') { 106 activeSearch = searchContext(window.location.href, !url(window.location.href).searchParams.has('search_id')); 107 var target = url(window.location.href); 108 target.searchParams.set('search_id', activeSearch.search_id); 109 window.history.replaceState(window.history.state, '', target.href); 110 } 111 }); 112 safely(function () { 113 var key = 'source:' + window.location.href.split('#')[0]; 114 var source = JSON.parse(saved(key) || 'null'); 115 saved(key, ''); 116 if (source && Date.now() - source.at >= 0 && Date.now() - source.at < 1800000 && 117 document.referrer && url(document.referrer).origin === window.location.origin && 118 url(document.referrer).href.split('#')[0] === url(source.from).href.split('#')[0]) sourceViewId = source.id; 119 }); 120 initialUrl = window.location.href; 121 safely(function () { 122 window.tracker = window.IbiTracker.init(Object.assign({ 123 engagementHeartbeat: false 124 }, settings, { 125 autoPageview: true, autoPerformance: true, 126 resolvePage: function () { 127 var page = Object.assign({}, window.location.href === initialUrl ? metadata.page : { page_type: 'other' }); 128 // search_id identifies an attempt, not a page asset. 129 if (!page.page_id && url(window.location.href).searchParams.has('search_id')) { 130 var target = url(window.location.href);
131 Array.from(target.searchParams.keys()).forEach(function (key) { if (key === 'search_id' || key.indexOf('utm_') === 0) target.searchParams.delete(key); }); 132 if (!/^#(?:\/|!\/)/.test(target.hash)) target.hash = ''; 133 page.page_id = target.href; 134 } 135 return page; 136 }, 137 onDiagnostic: function (event) { diagnostic(event.code); } 138 })); 139 currentViewId = window.tracker.getInitialPageViewId(); 140 var pageview = window.tracker.pageview; 141 window.tracker.pageview = function () { currentViewId = pageview.apply(this, arguments); return currentViewId; }; 142 if (metadata.search) site.searchResults(metadata.search, activeSearch); 143 }); 144 if (!window.tracker) diagnostic('sdk_unavailable'); 145 // Compatibility only: old dashboards retain their original one-shot wall duration. 146 var entered = window.performance.now(), pageUrl = window.location.href, left = false; 147 function leave() { 148 if (left) return; 149 left = true; 150 site.track('$page_leave', { page_url: pageUrl, duration_ms: Math.round(window.performance.now() - entered) }, true); 151 } 152 window.addEventListener('pagehide', leave); 153 document.addEventListener('visibilitychange', function () { if (document.visibilityState === 'hidden') leave(); }); 154 }; 155 site.currentPageViewId = function () { return currentViewId; }; 156 // Prefilter attaches ahead of per-page success handlers, including utils/formHelper wrappers. 157 if (window.jQuery) window.jQuery.ajaxPrefilter(function (options, original, xhr) { 158 var requestUrl = safely(function () { return url(options.url); }); 159 if (!requestUrl || requestUrl.origin !== window.location.origin) return; 160 var isPartSearch = /(?:\/part-search\.html|\/part\/search(?:\.html)?)$/.test(requestUrl.pathname); 161 var context; 162 var requestSource = sourceViewId; 163 if (isPartSearch) { 164 safely(function () { 165 var params = new URLSearchParams(typeof options.data === 'string' ? options.data : options.data || {}); 166 params.forEach(function (value, key) { requestUrl.searchParams.set(key, value); }); 167 context = searchContext(requestUrl.href, false); 168 }); 169 } 170 xhr.done(function (response) { 171 safely(function () { 172 if (response && response.ibi_tracking && /\/login\/bondedReg(?:\.html)?$/i.test(requestUrl.pathname)) { 173 site.success({ code: 200, ibi_tracking: response.ibi_tracking }); 174 } else site.success(response, requestSource); 175 if (context && response && response.code === 200 && response.ibi_tracking && response.ibi_tracking.search) { 176 site.searchResults(response.ibi_tracking.search, context); 177 } 178 }); 179 }); 180 }); 181 function onLinkClick(event) { 182 safely(function () { 183 var link = event.target.closest('a'); 184 if (!link) return; 185 if (link.href && /(?:member[./](?:inquiry\/send|order\/sub)|page-quote)/.test(url(link.href).pathname)) site.flowUrl(link.href); 186 var resultId = link.getAttribute('data-ibi-result-id'); 187 if (resultId && activeSearch) { 188 var rank = Number(link.getAttribute('data-ibi-rank')) + activeSearch.offset; 189 if (Number.isInteger(rank) && rank > 0) site.track('search_result_click', { search_id: activeSearch.search_id, result_id: resultId, rank: rank }, true); 190 } 191 if (activeSearch && link.href && !resultId) { 192 var target = url(link.href); 193 if (target.origin === window.location.origin && fingerprint(target.href) === activeSearch.key) { 194 target.searchParams.set('search_id', activeSearch.search_id); 195 link.href = target.href; 196 } 197 } 198 }); 199 } 200 document.addEventListener('click', onLinkClick, true); 201 document.addEventListener('auxclick', function (event) { if (event.button === 1) onLinkClick(event); }, true); 202 document.addEventListener('DOMContentLoaded', function () { 203 safely(function () { 204 if (!activeSearch) return; 205 document.querySelectorAll('a[href]').forEach(function (link) { 206 if (link.hasAttribute('data-ibi-result-id')) return; 207 var target = url(link.href); 208 if (target.origin === window.location.origin && fingerprint(target.href) === activeSearch.key) { 209 target.searchParams.set('search_id', activeSearch.search_id); 210 link.href = target.href; 211 } 212 }); 213 }); 214 }); 215})(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.