var localstore = { s: window.localStorage, type: 'localStorage', set: function(key, val) { this.s.setItem(key, JSON.stringify(val)); return val; }, get: function(key) { var value = this.s.getItem(key); if (typeof value != 'string') { return undefined } try { return JSON.parse(value) } catch(e) { return value || undefined } }, remove: function(key) { this.s.removeItem(key) }, removeAll: function() { this.s.clear() }, getAll: function() { var ret = {}; for (var i=0; i * Licensed under the New BSD License. * https://github.com/stackp/promisejs */ (function(exports) { function Promise() { this._callbacks = []; } Promise.prototype.then = function(func, context) { var p; if (this._isdone) { p = func.apply(context, this.result); } else { p = new Promise(); this._callbacks.push(function () { var res = func.apply(context, arguments); if (res && typeof res.then === 'function') res.then(p.done, p); }); } return p; }; Promise.prototype.done = function() { this.result = arguments; this._isdone = true; for (var i = 0; i < this._callbacks.length; i++) { this._callbacks[i].apply(null, arguments); } this._callbacks = []; }; function join(promises) { var p = new Promise(); var results = []; if (!promises || !promises.length) { p.done(results); return p; } var numdone = 0; var total = promises.length; function notifier(i) { return function() { numdone += 1; results[i] = Array.prototype.slice.call(arguments); if (numdone === total) { p.done(results); } }; } for (var i = 0; i < total; i++) { promises[i].then(notifier(i)); } return p; } function chain(funcs, args) { var p = new Promise(); if (funcs.length === 0) { p.done.apply(p, args); } else { funcs[0].apply(null, args).then(function() { funcs.splice(0, 1); chain(funcs, arguments).then(function() { p.done.apply(p, arguments); }); }); } return p; } /* * AJAX requests */ function _encode(data) { var result = ""; if (typeof data === "string") { result = data; } else { var e = encodeURIComponent; for (var k in data) { if (data.hasOwnProperty(k)) { result += '&' + e(k) + '=' + e(data[k]); } } } return result; } function new_xhr() { var xhr; if (window.XDomainRequest) { xhr = new XDomainRequest(); } else if (window.XMLHttpRequest) { xhr = new XMLHttpRequest(); } else if (window.ActiveXObject) { try { xhr = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } } return xhr; } function ajax(method, url, data, headers) { var p = new Promise(); var xhr, payload; data = data || {}; headers = headers || {}; try { xhr = new_xhr(); } catch (e) { p.done(promise.ENOXHR, ""); return p; } payload = _encode(data); if (method === 'GET' && payload) { url += '?' + payload; payload = null; } xhr.open(method, url); if(typeof xhr.setRequestHeader != 'undefined') { xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); for (var h in headers) { if (headers.hasOwnProperty(h)) { xhr.setRequestHeader(h, headers[h]); } } } function onTimeout() { xhr.abort(); p.done(promise.ETIMEOUT, "", xhr); } var timeout = promise.ajaxTimeout; if (timeout) { var tid = setTimeout(onTimeout, timeout); } xhr.onreadystatechange = function() { if (timeout) { clearTimeout(tid); } if (xhr.readyState === 4) { var err = (!xhr.status || (xhr.status < 200 || xhr.status >= 300) && xhr.status !== 304); p.done(err, xhr.responseText, xhr); } }; xhr.send(payload); return p; } function _ajaxer(method) { return function(url, data, headers) { return ajax(method, url, data, headers); }; } var promise = { Promise: Promise, join: join, chain: chain, ajax: ajax, get: _ajaxer('GET'), post: _ajaxer('POST'), put: _ajaxer('PUT'), del: _ajaxer('DELETE'), /* Error codes */ ENOXHR: 1, ETIMEOUT: 2, /** * Configuration parameter: time in milliseconds after which a * pending AJAX request is considered unresponsive and is * aborted. Useful to deal with bad connectivity (e.g. on a * mobile network). A 0 value disables AJAX timeouts. * * Aborted requests resolve the promise with a ETIMEOUT error * code. */ ajaxTimeout: 0 }; if (typeof define === 'function' && define.amd) { /* AMD support */ define(function() { return promise; }); } else { exports.promise = promise; } })(this);/* * fingerprintJS 0.5.4 - Fast browser fingerprint library * https://github.com/Valve/fingerprintjs * Copyright (c) 2013 Valentin Vasilyev (valentin.vasilyev@outlook.com) * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ ;(function (name, context, definition) { if (typeof module !== 'undefined' && module.exports) { module.exports = definition(); } else if (typeof define === 'function' && define.amd) { define(definition); } else { context[name] = definition(); } })('Fingerprint', this, function () { 'use strict'; var Fingerprint = function (options) { var nativeForEach, nativeMap; nativeForEach = Array.prototype.forEach; nativeMap = Array.prototype.map; this.each = function (obj, iterator, context) { if (obj === null) { return; } if (nativeForEach && obj.forEach === nativeForEach) { obj.forEach(iterator, context); } else if (obj.length === +obj.length) { for (var i = 0, l = obj.length; i < l; i++) { if (iterator.call(context, obj[i], i, obj) === {}) return; } } else { for (var key in obj) { if (obj.hasOwnProperty(key)) { if (iterator.call(context, obj[key], key, obj) === {}) return; } } } }; this.map = function(obj, iterator, context) { var results = []; // Not using strict equality so that this acts as a // shortcut to checking for `null` and `undefined`. if (obj == null) return results; if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); this.each(obj, function(value, index, list) { results[results.length] = iterator.call(context, value, index, list); }); return results; }; if (typeof options == 'object'){ this.hasher = options.hasher; this.screen_resolution = options.screen_resolution; this.canvas = options.canvas; this.ie_activex = options.ie_activex; } else if(typeof options == 'function'){ this.hasher = options; } }; Fingerprint.prototype = { get: function(){ var keys = []; keys.push(navigator.userAgent); keys.push(navigator.language); keys.push(screen.colorDepth); if (this.screen_resolution) { var resolution = this.getScreenResolution(); if (typeof resolution !== 'undefined'){ // headless browsers, such as phantomjs keys.push(this.getScreenResolution().join('x')); } } keys.push(new Date().getTimezoneOffset()); keys.push(this.hasSessionStorage()); keys.push(this.hasLocalStorage()); keys.push(!!window.indexedDB); //body might not be defined at this point or removed programmatically if(document.body){ keys.push(typeof(document.body.addBehavior)); } else { keys.push(typeof undefined); } keys.push(typeof(window.openDatabase)); keys.push(navigator.cpuClass); keys.push(navigator.platform); keys.push(navigator.doNotTrack); keys.push(this.getPluginsString()); if(this.canvas && this.isCanvasSupported()){ keys.push(this.getCanvasFingerprint()); } if(this.hasher){ return this.hasher(keys.join('###'), 31); } else { return this.murmurhash3_32_gc(keys.join('###'), 31); } }, /** * JS Implementation of MurmurHash3 (r136) (as of May 20, 2011) * * @author Gary Court * @see http://github.com/garycourt/murmurhash-js * @author Austin Appleby * @see http://sites.google.com/site/murmurhash/ * * @param {string} key ASCII only * @param {number} seed Positive integer only * @return {number} 32-bit positive integer hash */ murmurhash3_32_gc: function(key, seed) { var remainder, bytes, h1, h1b, c1, c2, k1, i; remainder = key.length & 3; // key.length % 4 bytes = key.length - remainder; h1 = seed; c1 = 0xcc9e2d51; c2 = 0x1b873593; i = 0; while (i < bytes) { k1 = ((key.charCodeAt(i) & 0xff)) | ((key.charCodeAt(++i) & 0xff) << 8) | ((key.charCodeAt(++i) & 0xff) << 16) | ((key.charCodeAt(++i) & 0xff) << 24); ++i; k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff; k1 = (k1 << 15) | (k1 >>> 17); k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff; h1 ^= k1; h1 = (h1 << 13) | (h1 >>> 19); h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff; h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16)); } k1 = 0; switch (remainder) { case 3: k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16; case 2: k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8; case 1: k1 ^= (key.charCodeAt(i) & 0xff); k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff; k1 = (k1 << 15) | (k1 >>> 17); k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff; h1 ^= k1; } h1 ^= key.length; h1 ^= h1 >>> 16; h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff; h1 ^= h1 >>> 13; h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff; h1 ^= h1 >>> 16; return h1 >>> 0; }, // https://bugzilla.mozilla.org/show_bug.cgi?id=781447 hasLocalStorage: function () { try{ return !!window.localStorage; } catch(e) { return true; // SecurityError when referencing it means it exists } }, hasSessionStorage: function () { try{ return !!window.sessionStorage; } catch(e) { return true; // SecurityError when referencing it means it exists } }, isCanvasSupported: function () { var elem = document.createElement('canvas'); return !!(elem.getContext && elem.getContext('2d')); }, isIE: function () { if(navigator.appName === 'Microsoft Internet Explorer') { return true; } else if(navigator.appName === 'Netscape' && /Trident/.test(navigator.userAgent)){// IE 11 return true; } return false; }, getPluginsString: function () { if(this.isIE() && this.ie_activex){ return this.getIEPluginsString(); } else { return this.getRegularPluginsString(); } }, getRegularPluginsString: function () { return this.map(navigator.plugins, function (p) { var mimeTypes = this.map(p, function(mt){ return [mt.type, mt.suffixes].join('~'); }).join(','); return [p.name, p.description, mimeTypes].join('::'); }, this).join(';'); }, getIEPluginsString: function () { if(window.ActiveXObject){ var names = ['ShockwaveFlash.ShockwaveFlash',//flash plugin 'AcroPDF.PDF', // Adobe PDF reader 7+ 'PDF.PdfCtrl', // Adobe PDF reader 6 and earlier, brrr 'QuickTime.QuickTime', // QuickTime // 5 versions of real players 'rmocx.RealPlayer G2 Control', 'rmocx.RealPlayer G2 Control.1', 'RealPlayer.RealPlayer(tm) ActiveX Control (32-bit)', 'RealVideo.RealVideo(tm) ActiveX Control (32-bit)', 'RealPlayer', 'SWCtl.SWCtl', // ShockWave player 'WMPlayer.OCX', // Windows media player 'AgControl.AgControl', // Silverlight 'Skype.Detection']; // starting to detect plugins in IE return this.map(names, function(name){ try{ new ActiveXObject(name); return name; } catch(e){ return null; } }).join(';'); } else { return ""; // behavior prior version 0.5.0, not breaking backwards compat. } }, getScreenResolution: function () { return [screen.height, screen.width]; }, getCanvasFingerprint: function () { var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); // https://www.browserleaks.com/canvas#how-does-it-work var txt = 'http://valve.github.io'; ctx.textBaseline = "top"; ctx.font = "14px 'Arial'"; ctx.textBaseline = "alphabetic"; ctx.fillStyle = "#f60"; ctx.fillRect(125,1,62,20); ctx.fillStyle = "#069"; ctx.fillText(txt, 2, 15); ctx.fillStyle = "rgba(102, 204, 0, 0.7)"; ctx.fillText(txt, 4, 17); return canvas.toDataURL(); } }; return Fingerprint; }); (function(){ var roxanne = { handlers: {}, completed_actions: {}, scheme: 'https', host: 'ref.dealerinspire.com:', token: 'Nzk4MzY0NjM3', site_id: '11000709', visit_id_ref: 'di_roxanne[visit_id]', visitor_id_ref: 'di_roxanne[visitor_id]', roxanne_ref: 'Roxanne_B', init: function(){ //console.log('Roxanne Tracking', 'Start'); this.visit_tracking.init(); }, visit_tracking: { mixpanel: '', init: function(){ //console.log('Roxanne Visit Tracking', 'Start'); if(!this.exists()) { this.create(); } else { this.update(); } }, exists: function(){ var roxie = localstore.get(roxanne.roxanne_ref); var currTime = Math.round((new Date()).getTime() / 1000); return !(typeof roxie == 'undefined' || ( (currTime - roxie) > (60 /*seconds*/ * 60 /*minutes*/ * 1 /*hour*/) ) ); }, create: function(){ //console.log('Roxanne Visit Tracking', 'Create'); // We haven't sent in the referrer for this visit just yet. var data = {}; // Send back the basics data.referrer = document.referrer; data.browser_fingerprint = new Fingerprint().get(); data.user_agent = navigator.userAgent; // Look for the TPReferral ID, which we can ping for later. var tp = cookiestore.get('tp_referral'); if(typeof tp != 'undefined') { data.tp_referral_id = tp; } // Look for Clicky cookie, and grab the ID var cl = cookiestore.get('_jsuid'); if(typeof cl != 'undefined') { data.clicky_jsuid = cl; } // Look for Mixpanel cookie, and grab the distinct_id value. if (this.mixpanel){ var mp = cookiestore.get('mp_' + this.mixpanel + '_mixpanel'); if(typeof mp != 'undefined') { data.mixpanel_id = mp.distinct_id; } } this.track(data); }, update: function(){ // Update the Roxanne timestamp, until we need to send in a new referrer. localstore.set('Roxanne_B', Math.round((new Date()).getTime() / 1000)); this.on_ready(); //console.log('Roxanne Visit Tracking Update Localstore', localstore); }, track: function(data){ //console.log('Roxanne Visit Tracking', 'Track', promise); var self = this; var url = roxanne.scheme+'://'+roxanne.host+'/site/'+roxanne.site_id+'/visitors/'+roxanne.token; promise.post(url, data).then(function(error, text, xhr) { //console.log('Roxanne Tracking', 'Post', url, data); if(!error) { self.on_success(text, xhr); } else { self.on_failure(error, text, xhr); } }); }, on_ready: function(){ roxanne.do_actions('ready'); }, on_success: function(text, xhr){ var jtext = JSON.parse(text); localstore.set(roxanne.roxanne_ref, Math.round((new Date()).getTime() / 1000)); cookiestore.set(roxanne.visit_id_ref, jtext.id); cookiestore.set(roxanne.visitor_id_ref, jtext.visitor_id); this.on_ready(); //console.log('Create Localstore', localstore); }, on_failure: function(error, text, xhr){ //console.log('Roxanne Visit Tracking', 'Failure'); } }, event_tracking: { track: function(data){ //console.log("roxanne_event_tracking", 'track_event', data); var self = this; var vid = cookiestore.get(roxanne.visit_id_ref); var url = roxanne.scheme+'://'+roxanne.host+'/visit/'+vid+"/events/"+roxanne.token; promise.post(url, data).then(function(error, text, xhr) { if(!error) { self.on_success(text, xhr); } else { self.on_failure(error, text, xhr); } }); }, on_success: function(text, xhr){ //var jtext = JSON.parse(text); //console.log("Roxanne Event Tracking Success", text, xhr); }, on_failure: function(error, text, xhr){ //console.log('Roxanne Event Tracking Failure', error, text, xhr); } }, add_action: function(hook, func){ //console.log('Roxanne Tracking', 'add_action', hook, func); if (hook in this.completed_actions){ return func(); } if (typeof this.handlers[hook] == 'undefined'){ this.handlers[hook] = []; } this.handlers[hook].push(func); }, do_actions: function(hook){ //console.log('Roxanne Tracking', 'do_actions', hook, this.handlers); if (typeof this.handlers[hook] != 'undefined'){ for(var f in this.handlers[hook]){ this.handlers[hook][f](); } } this.handlers[hook] = []; this.completed_actions[hook] = true; }, }; if (jQuery){ jQuery(document).bind('di_ev', function(eve, e_data){ roxanne.add_action('ready', function(){ roxanne.event_tracking.track(e_data); }); }); jQuery(document).trigger('di_ev', { 'name': 'page_view', 'label': window.location.href, 'default_score': 1 }); jQuery(document).ready(function(){ /* HOMEPAGE Search Anything Magnifying Glass (btn search-anything-submit-btn) 8 Filter Search Button (btn advanced-search-btn) 8 Clicks on New Nav Only (matches css selector= .menu-item-number a) 6 Clicks on Used (/used-vehicles) 6 Clicks on Finance (/finance) 6 Clicks on Contact Us (/contact) 5 */ jQuery("body.home .search-anything-submit-btn").click(function(){ jQuery(document).trigger('di_ev', { 'name': 'search', 'label': 'Search Anything Magnifiying Glass', 'default_score': 8 }); }); jQuery("body.home .advanced-search-btn").click(function(){ jQuery(document).trigger('di_ev', { 'name': 'search', 'label': 'Filter Search Button', 'default_score': 8 }); }); jQuery("body.home li.newVehicles a").click(function(){ jQuery(document).trigger('di_ev', { 'name': 'click', 'label': 'New Vehicles in Navigation', 'default_score': 6 }); }); jQuery("body.home .menu-item-number a").click(function(){ jQuery(document).trigger('di_ev', { 'name': 'click', 'label': 'Used Vehicles in Navigation', 'default_score': 6 }); }); jQuery("body.home .menu-item-number a").click(function(){ jQuery(document).trigger('di_ev', { 'name': 'click', 'label': 'Finance in Navigation', 'default_score': 6 }); }); jQuery("body.home .menu-item-number a").click(function(){ jQuery(document).trigger('di_ev', { 'name': 'click', 'label': 'Contact Us in Navigation', 'default_score': 5 }); }); /* VRP VRP search button (id=start-search) 8 Clicks on main CTA (matches css selector= .button-bar .primary-cta .cta-button) 8 Clicks on save - add css attribute to backend (matches css selector= .gtm-save) 6 Clicks on details add css attribute to backend (matches css selector= .gtm-details) 8 */ jQuery("body.page-vehicle-results-page #start_search").click(function(){ jQuery(document).trigger('di_ev', { 'name': 'search', 'label': 'VRP Search', 'default_score': 8 }); }); jQuery("body.page-vehicle-results-page .button-bar .primary-cta .cta-button").click(function(){ jQuery(document).trigger('di_ev', { 'name': 'click', 'label': 'VRP Main CTA', 'default_score': 8 }); }); jQuery("body.page-vehicle-results-page a.save-things").click(function(){ jQuery(document).trigger('di_ev', { 'name': 'save', 'label': 'Save Vehicle', 'default_score': 8 }); }); //Need a better identifier for this one jQuery("body.page-vehicle-results-page .first-button a").click(function(){ jQuery(document).trigger('di_ev', { 'name': 'search', 'label': 'VRP Details', 'default_score': 8 }); }); /* VDP Clicks on main CTA (gtm-data-event= vdpPrimaryCTA) 8 Clicks on VDP Schedule Test Drive Shopping Tool (class=schedule-testdrive) 8 Clicks on VDP Check Availabilty Shopping Tool (class=availability) 8 */ jQuery("body.page-vehicle-display-page a[data-gtm-event='vdpPrimaryCTA']").click(function(){ jQuery(document).trigger('di_ev',{ 'name': 'click', 'label': 'VDP Main CTA', 'default_score': 8 }); }); jQuery("body.page-vehicle-display-page .more-action .schedule-testdrive").click(function(){ jQuery(document).trigger('di_ev',{ 'name': 'click', 'label': 'VDP Schedule Test Drive Shopping Tool', 'default_score': 8 }); }); jQuery("body.page-vehicle-display-page .more-action .availability").click(function(){ jQuery(document).trigger('di_ev',{ 'name': 'click', 'label': 'VDP Check Availabilty Shopping Tool', 'default_score': 8 }); }); /* MOBILE/TABLET Click for directions on mobile (data-gtm-event= mobileHeaderDirections) 8 Click to call on mobile (mobileHeaderPhone) 8 Click for directions on tablet (tabletHeaderDirections) 8 Clicks to call on tablet (tabletHeaderPhone) 8 */ jQuery("[data-gtm-event='mobileHeaderDirections']").click(function(){ jQuery(document).trigger('di_ev', { 'name': 'click', 'label': 'Directions for Mobile', 'default_score': 8 }); }); jQuery(".mobileHeaderPhone").click(function(){ jQuery(document).trigger('di_ev', { 'name': 'click', 'label': 'Call on Mobile', 'default_score': 8 }); }); jQuery(".tabletHeaderDirections").click(function(){ jQuery(document).trigger('di_ev', { 'name': 'click', 'label': 'Directions on Tablet', 'default_score': 8 }); }); jQuery(".tabletHeaderPhone").click(function(){ jQuery(document).trigger('di_ev', { 'name': 'click', 'label': 'Call on Tablet', 'default_score': 8 }); }); //This event isn't needed yet, but could be useful /* jQuery(document).bind('gform_page_loaded', function(event, formId){ jQuery(document).trigger('di_ev', { 'name': 'form_paging', 'label': 'Loaded the next form page '+formId, 'default_score': 0 }); });*/ }); } roxanne.init(); })();