!function(e,t){function n(e){return e&&t.XDomainRequest&&!/MSIE 1/.test(navigator.userAgent)?new XDomainRequest:t.XMLHttpRequest?new XMLHttpRequest:void 0}function o(e,t,n){e[t]=e[t]||n}var r=["responseType","withCredentials","timeout","onprogress"];e.ajax=function(e,a){function u(e,t){return function(){c||a(void 0===f.status?e:f.status,f.response||f.responseText||t,f),c=!0}}var s=e.headers||{},i=e.body,d=e.method||(i?"POST":"GET"),c=!1,f=n(e.cors);f.open(d,e.url,!0);var l=f.onload=u(200);f.onreadystatechange=function(){4===f.readyState&&l()},f.onerror=u(null,"Error"),f.ontimeout=u(null,"Timeout"),f.onabort=u(null,"Abort"),i&&(o(s,"X-Requested-With","XMLHttpRequest"),t.FormData&&i instanceof t.FormData||o(s,"Content-Type","application/x-www-form-urlencoded"));for(var p,m=0,v=r.length;v>m;m++)p=r[m],void 0!==e[p]&&(f[p]=e[p]);for(var p in s)f.setRequestHeader(p,s[p]);return f.send(i),f},t.nanoajax=e}({},function(){return this}()); var sbgm = sbgm || {}; /** suji: possible race condition adding two events back-to-back prior to the first beacon sending suji: config options passed into beacon.init() don: exponential backoff don: look into ajax error suji: more unit tests! (check beacon, data, xhr, etc.) suji: gulp task: build -- refactor into smaller tasks suji: sendBeacon() callbacks need to do something other than write to the console :) */ /** * IIFE to setup namespace: sbgm.utils and bootstrap itself with an extend method */ (function () { var extend = function (destination, source) { var toString = Object.prototype.toString, objTest = toString.call({}); for (var property in source) { if (source[property] && objTest === toString.call(source[property])) { destination[property] = destination[property] || {}; extend(destination[property], source[property]); } else { destination[property] = source[property]; } } return destination; }; extend(sbgm, {utils: {extend: extend}}); })(); /** * * * * * * * * * * Add utility functions below * */ /** * namespace: sbgm.utils * */ (function (utils) { /** * * @param errCode * @param stop * @param msgOverride */ utils.error = function (errCode, stop, msgOverride) { /* TODO: Setup WIKI/End-pint for docs/errors */ var msg = 'SBGM API Error #' + errCode + ': wiki.intuit.com/sbgm-sdk/errors/' + errCode + '?=' + encodeURIComponent(msgOverride); if (stop) { throw new Error(msg); } else if (window.console) { if (!msgOverride) { console.log(msg); } else { console.log(msgOverride); } } }; utils.events = (function () { var topics = {}; var hOP = topics.hasOwnProperty; return { subscribe: function (topic, listener) { // Create the topic's object if not yet created if (!hOP.call(topics, topic)) { topics[topic] = []; } // Add the listener to queue var index = topics[topic].push(listener) - 1; // Provide handle back for removal of topic return { remove: function () { delete topics[topic][index]; } }; }, publish: function (topic, info) { // If the topic doesn't exist, or there's no listeners in queue, just leave if (!hOP.call(topics, topic)) { return; } // Cycle through topics queue, fire! topics[topic].forEach(function (item) { item(info !== undefined ? info : {}); }); } }; })(); /** * * @param url * @param data * @param options */ utils.ajax = function (url, data, options) { var opts = { method: options.method || 'POST', success: options.success || undefined, // status 2xx fail: options.fail || undefined, // status 5xx other: options.other || undefined // other status }; var req; if (window.XMLHttpRequest) { req = new window.XMLHttpRequest(); } else { req = new window.ActiveXObject('Microsoft.XMLHTTP'); } req.onreadystatechange = function () { if (req.readyState === 4) { if (/^2/.test(req.status) && opts.success) { opts.success(req); } else if (/^5/.test(req.status) && opts.fail) { opts.fail(req); } else if (opts.other) { opts.other(req); } } }; req.open(opts.method, url, true); req.setRequestHeader('Content-Type', 'application/json'); req.setRequestHeader('Authorization', 'Intuit_APIKey intuit_apikey=prdakyresfsWwwDOBJFu0iasToyULgEx1PyFohAy, intuit_apkey_version=1.0'); //For sending the browser cookies with the request req.withCredentials = true; req.send(data); }; /** * * @param elem * @param event * @param fn */ utils.addEventListener = function (elem, event, fn) { // avoid memory overhead of new anonymous functions for every event handler that's installed // by using local functions function listenHandler(e) { var ret = fn.apply(this, arguments); if (ret === false) { e.stopPropagation(); e.preventDefault(); } return (ret); } function attachHandler() { // set the this pointer same as addEventListener when fn is called // and make sure the event is passed to the fn also so that works the same too var ret = fn.call(elem, window.event); if (ret === false) { window.event.returnValue = false; window.event.cancelBubble = true; } return (ret); } if (elem.addEventListener) { elem.addEventListener(event, listenHandler, false); } else { elem.attachEvent('on' + event, attachHandler); } }; utils.readIdFromCookie = function () { var name = "ivid"; var c = document.cookie.split('; '); var __cookies = {}; for(var i=c.length-1; i>=0; i--){ var C = c[i].split('='); __cookies[C[0]] = C[1]; } return __cookies[name]; }; //Beacon should not be setting anymore cookies // utils.setIDCookie = function() { // var domain = utils.getCookieDomain(window.document.location.hostname); // var id = utils.generateUUID(); // var d = new Date(); // d.setTime(d.getTime() + (90 * 24 * 60 * 60 * 1000)); // //max-age=90 * 24 * 60 * 60 in seconds // var expires = ";expires="+ d.toUTCString(); // document.cookie = 'id='+id+';path=/;domain='+domain+expires; // }; // // utils.generateUUID = function() { // return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { // var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); // return v.toString(16); // }); // }; // // utils.getCookieDomain = function(hostname) { // //assign proper domain based on the location // if (/^.+\.(intuit\.com)(:\d+)?$/.test(hostname)) { // return '.intuit.com'; // } else if (/^.+\.(quickbooks\.co\.uk)(:\d+)?$/.test(hostname)) { // return '.quickbooks.co.uk'; // } else if (/^.+\.(quickbooks\.intuit\.fr)(:\d+)?$/.test(hostname)) { // return '.quickbooks.intuit.fr'; // } else if (/^.+\.(quickbooks\.intuit\.ca)(:\d+)?$/.test(hostname)) { // return '.quickbooks.intuit.ca'; // } else if (/^.+\.(quickbooks\.com\.br)(:\d+)?$/.test(hostname)) { // return '.quickbooks.com.br'; // } else if (/^.+\.(quickbooks\.in)(:\d+)?$/.test(hostname)) { // return '.quickbooks.in'; // } else if (/^.+\.(intuit\.com\.au)(:\d+)?$/.test(hostname)) { // return '.intuit.com.au'; // } // }; utils.getPageViewEvent = function () { return { 'data': { 'page_url': document.URL, 'event_name': 'page_view' } }; }; utils.getSiteSpectEvent = function (evar71) { return { 'data': { 'page_url': document.URL, 'event_name': 'sitespect_testid', 'event_value': evar71 } }; }; utils.getQBDTLicenseIDEvent = function (qbdtUtils) { return { 'data': { 'page_url': document.URL, 'event_name': 'qbdt_licenseid', 'event_value': qbdtUtils.getQBLicense() } }; }; utils.addEvenListenerForSUIEvents = function () { document.addEventListener('onCompanyCreationSuccess', function(event) { // fire beacon to capture company details sbgm.beacon.addEvent({ 'data': { 'page_url': document.URL, 'event_name': 'company_created', 'event_value': { 'authId': event.detail[0].authId, 'companyId': event.detail[0].companyId, 'suiType': intuit.sbg.wwsui.getSuiType(), 'suiFlowType': intuit.sbg.wwsui.getSuiFlowType(), 'offerId': intuit.sbg.wwsui.offerId } } }); }, false); // Add wwSuiWidgetCompanyCreationSuccess; SUI Team is in process of renaming events - Listening to both events. //Check if the div exist on this page if ( document.querySelector('div[id="wwsui-main-container"]') ) { document.getElementById('wwsui-main-container').addEventListener("wwSuiWidgetCompanyCreationSuccess", function(event) { // fire beacon to capture company details sbgm.beacon.addEvent({ 'data': { 'page_url': document.URL, 'event_name': 'company_created', 'event_value': { 'authId': event.detail.data.authId, 'companyId': event.detail.data.companyId, 'suiType': intuit.sbg.wwsui.getSuiType(), 'suiFlowType': intuit.sbg.wwsui.getSuiFlowType(), 'offerId': intuit.sbg.wwsui.getConfig().cqData.bcData.billing_code } } }); }, false); } }; utils.addEventListenersToElements = function () { //Add beacon click handlers to all links tracked today by analytics tool var elements = document.querySelectorAll('*[data-wa-link]'); for(var i=0; i < elements.length; i++) { var element = elements[i]; function clickHandler(event) { // fire beacon: event click sbgm.beacon.addEvent({ 'data': { 'page_url': document.URL, 'event_name': 'link_click', //We need to get the attributes out of the parent tag and not the tag where event occurred 'event_value': event.currentTarget.getAttribute('data-wa-link') } }); } //useCapture flag has to be set true to make sure our listener gets called first element.addEventListener('click', clickHandler, true); } }; })(sbgm.utils = sbgm.utils || {}); /** * namespace: sbgm.beacon */ (function (beacon) { var __url = 'https://marketdataservice.uk.api.intuit.com/v2/saveUserData'; var __DEBUG_ENABLED = true; var __events = []; var __configs = {}; beacon.version = "1.0"; /** * Public * * */ beacon.init = function () { // TODO: Wire config options //sbgm.utils.events.publish('beacon:beforeInit'); if(__DEBUG_ENABLED) console.log('MDS Url to connect:', __url); // NOT needed anymore since we are moving to IVID // //If sbm_intuit_id is not present in the cookie, generate one // var sbmIntuitCookie = sbgm.utils.readIdFromCookie(); // if(sbmIntuitCookie === null || !sbmIntuitCookie) { // sbgm.utils.setIDCookie(); // } // subscribe to events being added; fire beacon sbgm.utils.events.subscribe('events:afterAdded', sbgm.beacon.sendBeacon); sbgm.utils.events.publish('beacon:afterInit'); beacon.setupEvents(); }; beacon.setupEvents = function () { //fire beacon: page view as soon as the beacon inits for first time sbgm.beacon.addEvent(sbgm.utils.getPageViewEvent()); sbgm.utils.addEventListenersToElements(); //For further page loads window.addEventListener('DOMContentLoaded', function() { // fire beacon: page load sbgm.beacon.addEvent(sbgm.utils.getPageViewEvent()); sbgm.utils.addEventListenersToElements(); }, false); beacon.sendSiteSpectTestID(); beacon.sendQBDTLicenseID(); //Add event listener for SUI flow completion sbgm.utils.addEvenListenerForSUIEvents(); }; beacon.sendSiteSpectTestID = function () { if(typeof s !== "undefined" && typeof s === "object") { sbgm.beacon.addEvent(sbgm.utils.getSiteSpectEvent(s.eVar71)); } else { if(__DEBUG_ENABLED) console.log('TestID is not available'); } }; beacon.sendQBDTLicenseID = function () { if(typeof $UTILS !== "undefined" && typeof $UTILS === "object") { sbgm.beacon.addEvent(sbgm.utils.getQBDTLicenseIDEvent($UTILS)); } else { if(__DEBUG_ENABLED) console.log('QBDT LicenseID is not available'); } }; beacon.addEvent = function (obj) { if (typeof obj === 'object') { // TODO: Future: scrub data inside of PII? e.g. email addresses, credit card numbers -- obvious bad things sbgm.utils.events.publish('events:beforeAdded'); //Set sbm_intuit_id as id in the event to be pushed obj.id = sbgm.utils.readIdFromCookie(); obj.page_url = document.URL; // push event onto our stack __events.push(obj); sbgm.utils.events.publish('events:afterAdded'); } else { // TODO: Check obj type; reject or normalize if not an object? } }; beacon.clearEvents = function () { sbgm.utils.events.publish('events:beforeCleared'); // clear event stack __events.length = 0; sbgm.utils.events.publish('events:afterCleared'); }; beacon.sendBeacon = function () { sbgm.utils.events.publish('events:beforeSend'); var beaconOpts = { success: function (req) { if(__DEBUG_ENABLED) console.log('successfully sent data to MDS'); }, fail: function (req) { if(__DEBUG_ENABLED) console.log('failed to send data to MDS'); }, other: function (req) { if(__DEBUG_ENABLED) console.log('other exception happened', req); } }; __events.forEach(function(event) { sbgm.utils.ajax(__url, JSON.stringify(event), beaconOpts); }); sbgm.utils.events.publish('events:afterSend'); beacon.clearEvents(); }; beacon.init(); })(sbgm.beacon = sbgm.beacon || {});