analytics/AdobeAnalyticsHelper.js

import customEvent from '../utils/customEvent';

/**
 * Module for handling Adobe Analytics
 *
 * @module AdobeAnalyticsHelper
 */
const AdobeAnalyticsHelper = {
	account: 'corus-testing-globalnews',
	trackingServer: 'metrics.corus.ca',
	securedTrackingServer: 'smetrics.corus.ca',
	visitorId: '5F34123F5245B4A70A490D45@AdobeOrg',
	convertToLowercase: true,
	contentNamespace: 'content',
	pageSections: [],
	pageData: {},
	pageTrackingDisabled: false,
	s: {},

	/*
	 * Initialize and configure AppMeasurement for adobe analytics.
	 * Fires a page tracking call when executed.
	 *
	 * @method init
	 */
	init() {
		/* global gnAnalyticsSettings */
		// Get configuration settings from page
		if ( 'undefined' !== typeof ( gnAnalyticsSettings ) && 'undefined' !== typeof ( gnAnalyticsSettings.adobe ) ) {
			if ( gnAnalyticsSettings.adobe.accounts ) {
				this.account = gnAnalyticsSettings.adobe.accounts;
			}

			if ( gnAnalyticsSettings.adobe.trackingDomain ) {
				this.trackingServer = gnAnalyticsSettings.adobe.trackingDomain;
			}

			if ( gnAnalyticsSettings.adobe.StrackingDomain ) {
				this.securedTrackingServer = gnAnalyticsSettings.adobe.StrackingDomain;
			}

			if ( gnAnalyticsSettings.adobe.disablePageTracking ) {
				this.pageTrackingDisabled = true;
			}
		}

		// Initialize AppMeasurement
		/* eslint-disable camelcase */
		const s = 'undefined' !== typeof ( s_gi ) ? s_gi( this.account ) : null; // eslint-disable-line no-undef, s_gi is AppMeasurement function
		if ( this.isUndefinedOrEmpty( s ) ) {
			return;
		}
		/* eslint-enable camelcase */

		// Configure AppMeasurement
		s.trackingServer = this.trackingServer;
		s.trackingServerSecure = this.securedTrackingServer;
		s.currencyCode = 'CAD';
		s.trackDownloadLinks = true;
		s.trackExternalLinks = true;
		s.trackInlineStats = true;
		s.linkDownloadFileTypes = 'exe,zip,wav,mp3,mov,mpg,avi,wmv,pdf,doc,docx,xls,xlsx,ppt,pptx';
		s.linkInternalFilters = 'globalnews.ca,globalnews-ca-develop.go-vip.net';
		s.linkLeaveQueryString = false;
		s.linkTrackEvents = 'None';
		s.linkTrackVars = 'None';
		s.pathConcatDelim = '|';
		s.usePlugins = true;
		s.doPlugins = () => {
			this.doPlugins();
		};

		/* global gnPageData */
		const gnPageDataExists = 'undefined' !== typeof ( gnPageData );
		const pageData = gnPageDataExists ? gnPageData : {};
		pageData.context_data = gnPageDataExists ? gnPageData.context_data : {};
		pageData.props = gnPageDataExists ? gnPageData.props : {};
		pageData.sections = gnPageDataExists ? gnPageData.sections : [];

		// Set other default page data
		pageData.context_data.sitename = 'globalnews';
		pageData.context_data.server = document.domain;

		// Set content title if not already set
		if ( ! pageData.context_data.title ) {
			pageData.context_data.title = document.title;
		}

		// Page referrer
		let { referrer } = pageData.context_data;
		if ( ! referrer ) {
			if ( document.location.href.toLowerCase().indexOf( 'campaign_id=a100' ) > 0 ) {
				referrer = 'https://apple-news-article.apple.com/';
			} else {
				({ referrer } = document );
			}
			pageData.context_data.referrer = referrer;
		}
		s.referrer = referrer;

		// Copy props set up before s was initialized by external scripts
		const { s: sObj } = window;
		if ( sObj ) {
			const sKeys = Object.keys( sObj );
			[].forEach.call( sKeys, ( key ) => {
				const val = sObj[key];
				if ( 0 === key.indexOf( 'prop' ) ) {
					if ( 'prop71' === key ) {
						pageData.context_data['watson.top'] = val;
					} else if ( 'prop72' === key ) {
						pageData.context_data['watson.category'] = val;
					} else {
						s[key] = val;
					}
				} else if ( key.indexOf( '.' ) > 0 && 'string' === typeof ( val ) ) {
					// Assign context data set prior to s object instantiation
					pageData.context_data[key] = val;
				} else if ( 'contextData' === key ) {
					const { contextData: cd } = sObj;
					Object.assign( pageData.context_data, cd );
				}
			});
		}

		// Pick up context data assigned to global variable CorusContextData
		const { CorusContextData: corusData } = window;
		if ( corusData ) {
			Object.assign( pageData.context_data, corusData );
		}

		// Load video mopdule if required
		if ( 'video' === pageData.context_data.contenttype ) {
			s.loadModule( 'Media' );
			s.Media.autoTrack = false;
			s.Media.trackWhilePlaying = true;
			s.Media.trackVars = 'None';
			s.Media.trackEvents = 'None';
			s.Media.playerName = 'CorusVideoPlayer-html5';
		}

		/* global Visitor */
		// Adding VisitorAPI to AppMeasurement
		if ( 'undefined' !== typeof ( Visitor ) ) {
			const visitor = Visitor.getInstance( this.visitorId );
			s.visitor = visitor;
			s.prop1 = 'VisitorAPI Present';
			pageData.context_data['adobe.mid'] = visitor.getMarketingCloudVisitorID();
		} else {
			s.prop1 = 'VisitorAPI Missing';
		}

		// Get PWA browser mode
		let displayMode = 'browser';
		const isStandalone = window.matchMedia( '(display-mode: standalone)' ).matches;

		if ( /^android-app:\/\//.test( document.referrer ) ) {
			displayMode = 'twa';
		} else if ( navigator.standalone || isStandalone ) {
			displayMode = 'standalone';
		}

		pageData.context_data['pwa.mode'] = displayMode;

		this.s = s;
		this.addPlugins();
		this.applyUtmCode();
		this.setPageData( pageData );
		this.setupActivityMap();

		customEvent.fire( document, 'pageDataReady', {
			data: this.s.contextData,
		});

		// trigger page tracking call, unless disabled for specific pages.
		if ( true !== this.pageTrackingDisabled ) {
			this.s.tl();
		}
	},

	/*
	 * Called by AppMeasurement when a tracking call is made
	 *
	 * @method doPlugins
	 */
	doPlugins() {
	},

	/*
	 * Generate link tracking call
	 *
	 * @param {Object|boolean} clickedTarget - HTML element clicked or 'true'
	 * @param {String} linkType - 'o' for custom link tracking, 'e' for exit links
	 * @param {Object} data - optional. key value pairs to be tracked as context data
	 * @param {Boolean} convertToLower - optional. if 'true', convert context data to lower case
	 * @method trackLink
	 */
	trackLink( clickedTarget, linkType, linkName, data, convertToLower ) {
		const keys = data ? Object.keys( data ) : false;
		const shouldConvert = 'undefined' !== typeof ( convertToLower ) ? convertToLower : this.convertToLowercase;

		if ( keys && keys.length > 0 ) {
			this.s.linkTrackVars = '';
			this.s.contextData = this.s.contextData || {};

			[].forEach.call( keys, ( key ) => {
				const lcKey = key.toLowerCase(); // Key always reported in lower case.
				this.s.contextData[lcKey] = shouldConvert ? data[key].toLowerCase() : data[key];
				this.s.linkTrackVars = `${this.s.linkTrackVars},contextData.${lcKey}`;
			});
		}

		let action = null;
		const name = shouldConvert ? linkName.toLowerCase() : linkName;

		// only navigate to target URL if clickedTarget is a link and has target _blank
		if ( clickedTarget
			&& 'boolean' !== typeof ( clickedTarget )
			&& clickedTarget.getAttribute( 'href' )
			&& '_blank' !== clickedTarget.getAttribute( 'target' ) ) {
			action = 'navigate';
		}

		// when clickedTarget is 'true', no delay will be applied as user isn't leaving current page
		// otherwise, tracking call will redirect to clickedTarget's link after 500ms
		// to ensure the tracking event got fired before exiting the page
		this.s.tl( clickedTarget, linkType, name, null, action );
		this.s.linkTrackVars = 'None';
	},

	/*
	 * Set contextData in AppMeasurement to be tracked
	 * e.g. s.contextData[namespace.key] = value
	 *
	 * @param {String} key - Key of context data. if key contains '.', the prefix is used as namespace
	 * @param {String} value - Value of context data
	 * @param {String} namespace - Optional. Defaults to 'content'
	 * @method setContextData
	 */
	setContextData( key, value, namespace ) {
		if ( this.isUndefinedOrEmpty( this.s ) || ! key || ! value ) {
			return;
		}

		let ns = namespace;
		let convertedKey = key;
		let convertedVal = value.toString();

		if ( ! ns ) {
			ns = this.contentNamespace;
			const match = new RegExp( '([^\\.]+)\\.(.+)' ).exec( key );
			if ( match && match.length > 2 ) {
				[, ns, convertedKey] = match;
			}
		}

		if ( this.convertToLowercase ) {
			convertedVal = convertedVal.toLowerCase();
		}

		convertedKey = convertedKey.toLowerCase();
		this.s.contextData[`${ns}.${convertedKey}`] = convertedVal;
	},

	/*
	 * Processes page data spit out by the page as a global JS variable
	 * and sets the appropriate tracking props and context data accordingly
	 *
	 * @param {Object} pageData
	 * @method setPageData
	 */
	setPageData( pageData ) {
		this.pageData = pageData;

		// Set page sections
		this.setPageSections( pageData.sections );

		// Set context data variables
		[].forEach.call( Object.keys( pageData.context_data ), ( key ) => {
			this.setContextData( key, pageData.context_data[key]);
		});

		// Set props
		[].forEach.call( Object.keys( pageData.props ), ( key ) => {
			this.s[key] = pageData.props[key];
		});
	},

	/*
	 * Set page name, sitesection, sitesubsection properties
	 *
	 * @param {Array} sections - page name broken down into array of strings
	 * @param {Number} numLevels - maxnimum number of subsection levels to report
	 * @method setPageSections
	 */
	setPageSections( sections, numLevels ) {
		if ( this.isUndefinedOrEmpty( this.s ) ) {
			return;
		}

		if ( ! this.pageSections ) {
			this.pageSections = sections;
		}

		const levels = numLevels || 5;

		if ( sections && sections.length > 0 ) {
			this.setContextData( 'sitesection', sections[0]);
			this.setContextData( 'pagename', sections.join( this.s.pathConcatDelim ) );
			for ( let i = 0; i <= levels; i += 1 ) {
				let sectionName = 'sitesubsection';
				if ( i > 1 ) {
					sectionName += i;
				}
				const val = sections.slice( 0, i + 1 ).join( this.s.pathConcatDelim );
				this.setContextData( sectionName, val );
			}
		}
	},

	/*
	 * Customize activity map region and link reports
	 *
	 * @method setupActivityMap
	 */
	setupActivityMap() {
		if ( 'undefined' !== typeof ( this.s.ActivityMap ) ) {
			const originalRegionFunc = this.s.ActivityMap.region;
			const originalLinkFunc = this.s.ActivityMap.link;

			this.s.ActivityMap.region = ( $elem ) => {
				let region = originalRegionFunc( $elem );
				if ( $elem && $elem.dataset.trackRegion ) {
					region = $elem.dataset.trackRegion;
				} else if ( region && /player_.+__sticky/.test( region ) ) {
					region = 'stickyVideo';
				}

				const group = this.s.contextData && '1' === this.s.contextData['content.beta'] ? '-beta' : '';
				return `${region}${group}`;
			};
			this.s.ActivityMap.link = ( $elem ) => {
				let link = originalLinkFunc( $elem );
				if ( $elem ) {
					if ( $elem.dataset.trackLink ) {
						link = $elem.dataset.trackLink;
					} else if ( 'a' === $elem.nodeName.toLowerCase() ) {
						const title = $elem.getAttribute( 'title' );
						if ( title ) {
							link = title;
						}
					}
				}
				return link;
			};
		}
	},

	/*
	 * Sets appropriate props and campaign values for UTM tracking
	 *
	 * @method applyUtmCode
	 */
	applyUtmCode() {
		// UTM tracking
		let campaign = '';
		let trackVal = this.s.Util.getQueryParam( 'utm_medium' );

		if ( trackVal ) {
			this.s.eVar18 = trackVal;
			campaign += trackVal + this.s.pathConcatDelim;
		}

		trackVal = this.s.Util.getQueryParam( 'utm_source' );
		if ( trackVal ) {
			this.s.eVar19 = trackVal;
			campaign += trackVal + this.s.pathConcatDelim;
		}

		trackVal = this.s.Util.getQueryParam( 'utm_campaign' );
		if ( trackVal ) {
			this.s.eVar20 = trackVal;
			campaign += trackVal + this.s.pathConcatDelim;
		}

		trackVal = this.s.Util.getQueryParam( 'utm_term' );
		if ( trackVal ) {
			this.s.eVar21 = trackVal;
			campaign += trackVal + this.s.pathConcatDelim;
		}

		trackVal = this.s.Util.getQueryParam( 'utm_content' );
		if ( trackVal ) {
			this.s.eVar22 = trackVal;
			campaign += trackVal + this.s.pathConcatDelim;
		}

		if ( '' !== campaign ) {
			this.s.campaign = campaign;
		}
	},

	/*
	 * Add vendor plugins
	 *
	 * @method addPlugins
	 */
	addPlugins() {
		/* eslint-disable */
		/*
		 * Plugin: getValOnce 0.2 - get a value once per session or number of days
		 */
		this.s.getValOnce=new Function("v","c","e",""
		+"var s=this,k=s.c_r(c),a=new Date;e=e?e:0;if(v){a.setTime(a.getTime("
		+")+e*86400000);s.c_w(c,v,e?a:0);}return v==k?'':v");
		/*
		 * Plugin: getTimeParting 3.4
		 */
		this.s.getTimeParting=new Function("h","z",""
		+"var s=this,od;od=new Date('1/1/2000');if(od.getDay()!=6||od.getMont"
		+"h()!=0){return'Data Not Available';}else{var H,M,D,U,ds,de,tm,da=['"
		+"Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturda"
		+"y'],d=new Date();z=z?z:0;z=parseFloat(z);if(s._tpDST){var dso=s._tp"
		+"DST[d.getFullYear()].split(/,/);ds=new Date(dso[0]+'/'+d.getFullYea"
		+"r());de=new Date(dso[1]+'/'+d.getFullYear());if(h=='n'&&d>ds&&d<de)"
		+"{z=z+1;}else if(h=='s'&&(d>de||d<ds)){z=z+1;}}d=d.getTime()+(d.getT"
		+"imezoneOffset()*60000);d=new Date(d+(3600000*z));H=d.getHours();M=d"
		+".getMinutes();M=(M<10)?'0'+M:M;D=d.getDay();U=' AM';if(H>=12){U=' P"
		+"M';H=H-12;}if(H==0){H=12;}D=da[D];tm=H+':'+M+U;return(tm+'|'+D);}");

		/*
		 * Plugin Utility: apl v1.1
		 */
		this.s.apl=new Function("l","v","d","u",""
		+"var s=this,m=0;if(!l)l='';if(u){var i,n,a=s.split(l,d);for(i=0;i<a."
		+"length;i++){n=a[i];m=m||(u==1?(n==v):(n.toLowerCase()==v.toLowerCas"
		+"e()));}}if(!m)l=l?l+d+v:v;return l");

		/*
		 * Utility Function: split v1.5 - split a string (JS 1.0 compatible)
		 */
		this.s.split=new Function("l","d",""
		+"var i,x=0,a=new Array;while(l){i=l.indexOf(d);i=i>-1?i:l.length;a[x"
		+"++]=l.substring(0,i);l=l.substring(i+d.length);}return a");

		/*
		 * Plugin: linkHandler 0.5 - identify and report custom links
		 */
		this.s.linkHandler=new Function("p","t",""
		+"var s=this,h=s.p_gh(),i,l;t=t?t:'o';if(!h||(s.linkType&&(h||s.linkN"
		+"ame)))return '';i=h.indexOf('?');h=s.linkLeaveQueryString||i<0?h:h."
		+"substring(0,i);l=s.pt(p,'|','p_gn',h.toLowerCase());if(l){s.linkNam"
		+"e=l=='[['?'':l;s.linkType=t;return h;}return '';");

		this.s.p_gn=new Function("t","h",""
		+"var i=t?t.indexOf('~'):-1,n,x;if(t&&h){n=i<0?'':t.substring(0,i);x="
		+"t.substring(i+1);if(h.indexOf(x.toLowerCase())>-1)return n?n:'[[';}"
		+"return 0;");

		/*
		 * Utility Function: p_gh
		 */
		this.s.p_gh=new Function(""
		+"var s=this;if(!s.eo&&!s.lnk)return '';var o=s.eo?s.eo:s.lnk,y=s.ot("
		+"o),n=s.oid(o),x=o.s_oidt;if(s.eo&&o==s.eo){while(o&&!n&&y!='BODY'){"
		+"o=o.parentElement?o.parentElement:o.parentNode;if(!o)return '';y=s."
		+"ot(o);n=s.oid(o);x=o.s_oidt}}return o.href?o.href:'';");
	
		/*
		 * Utility Function: p_c
		 */
		this.s.p_c=new Function("v","c",""
		+"var x=v.indexOf('=');return c.toLowerCase()==v.substring(0,x<0?v.le"
		+"ngth:x).toLowerCase()?v:0");

		/* eslint-enable */
	},

	/*
	 * Check if AppMeasurement is defined, it may not be when ad blocker is enabled
	 *
	 * @param {Object} s - AppMeasurement object to check
	 * @return {Boolean} true if s is defined, not empty, not null
	 * @method isUndefinedOrEmpty
	 */
	isUndefinedOrEmpty( s ) {
		return ( 'undefined' === typeof ( s ) ) || null === s || 0 === s.length;
	},
};

export default AdobeAnalyticsHelper;