usfedgov_google_analytics-8.x-1.2/js/8.5.0/Universal-Federated-Analytics.js
js/8.5.0/Universal-Federated-Analytics.js
/***********************************************************************************************************
U.S. General Services Administration (GSA).
Digital Analytics Program Government Wide Site Usage Measurement and Tracking.
18/12/2024 Version: 8.5
***********************************************************************************************************/
/**
* The Universal-Federated-Analytics.js file is part of the Digital Analytics
* Program (DAP), designed to help US federal agencies implement a unified web
* analytics solution. This script integrates Google Analytics (GA4) tracking
* into government websites, ensuring data consistency and centralized reporting
* across agencies.
*
* This code provides a robust and customizable solution for tracking user
* interactions and page views on a website using GA4. It includes various
* features and functions to ensure accurate and reliable data collection, while
* also protecting user privacy by redacting any potential PII.
*/
/**
* Defines a configuration object oCONFIG which contains various settings and
* parameters for the tracking script. This includes the GA4 property ID,
* whether to force SSL, anonymize IP addresses, and other tracking and data
* collection settings.
*/
(function () {
var isSearch = false,
_allowedQuerystrings = [],
oCONFIG = {
GWT_GA4ID: ["G-CSLL4ZEK4L"],
FORCE_SSL: !0,
ANONYMIZE_IP: !0,
AGENCY: "",
SUB_AGENCY: "",
VERSION: "20241218 v8.5 - GA4",
SITE_TOPIC: "",
SITE_PLATFORM: "",
SCRIPT_SOURCE: "",
URL_PROTOCOL: location.protocol,
USE_MAIN_CUSTOM_DIMENSIONS: !0,
MAIN_AGENCY_DIMENSION: "agency",
MAIN_SUBAGENCY_DIMENSION: "subagency",
MAIN_CODEVERSION_DIMENSION: "version",
MAIN_SITE_TOPIC_DIMENSION: "site_topic",
MAIN_SITE_PLATFORM_DIMENSION: "site_platform",
MAIN_SCRIPT_SOURCE_URL_DIMENSION: "script_source",
MAIN_URL_PROTOCOL_DIMENSION: "protocol",
MAIN_INTERACTION_TYPE_DIMENSION: "interaction_type",
MAIN_USING_PARALLEL_DIMENSION: "using_parallel_tracker",
USE_PARALLEL_CUSTOM_DIMENSIONS: !1,
PARALLEL_AGENCY_DIMENSION: "agency",
PARALLEL_SUBAGENCY_DIMENSION: "subagency",
PARALLEL_CODEVERSION_DIMENSION: "version",
PARALLEL_SITE_TOPIC_DIMENSION: "site_topic",
PARALLEL_SITE_PLATFORM_DIMENSION: "site_platform",
PARALLEL_SCRIPT_SOURCE_URL_DIMENSION: "script_source",
PARALLEL_URL_PROTOCOL_DIMENSION: "protocol",
PARALLEL_INTERACTION_TYPE_DIMENSION: "interaction_type",
PARALLEL_USING_PARALLEL_DIMENSION: "using_parallel_tracker",
COOKIE_DOMAIN: location.hostname.replace(/^www\./, "").toLowerCase(),
COOKIE_TIMEOUT: 63072e3,
SEARCH_PARAMS: "q|query|nasaInclude|k|querytext|keys|qt|search_input|search|globalSearch|goog|s|gsearch|search_keywords|SearchableText|sp_q|qs|psnetsearch|locate|lookup|search_api_views_fulltext|keywords|request|_3_keywords|searchString",
YOUTUBE: !1,
HTMLVIDEO: !0,
YT_MILESTONE: 25, //accepts 10, 20, and 25
AUTOTRACKER: !0,
WEBVITALS: !1,
EXTS: "doc|docx|xls|xlsx|xlsm|ppt|pptx|exe|zip|pdf|js|txt|csv|dxf|dwgd|rfa|rvt|dwfx|dwg|wmv|jpg|msi|7z|gz|tgz|wma|mov|avi|mp3|mp4|csv|mobi|epub|swf|rar",
SUBDOMAIN_BASED: !0,
GA4_NAME: "GSA_GA4_ENOR",
USE_CUSTOM_URL: !1,
USE_CUSTOM_TITLE: !1,
USING_PARALLEL_TRACKER: "no",
ACTIVATE_DEV: !1
};
_updateConfig();
_setEnvironment();
_initWebvitals();
//*********GA4************
var dap_head = document.getElementsByTagName("head").item(0);
var GA4Object = document.createElement("script");
GA4Object.setAttribute("type", "text/javascript");
GA4Object.setAttribute(
"src",
"https://www.googletagmanager.com/gtag/js?id=" + oCONFIG.GWT_GA4ID[0]
);
dap_head.appendChild(GA4Object);
window.dataLayer = window.dataLayer || [];
/**
* Pushes commands to the data layer array which is used by the Google gtag.js
* library. More details are provided here:
* https://developers.google.com/tag-platform/gtagjs/reference
*
* @param {string} command the name of the gtag command. Valid commands are:
* "js", "config", "get", "set", "event", "consent"
* @param {...*} commandParameters parameters passed to the gtag command. These
* vary according to the command.
*/
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag('set', { 'cookie_flags': 'SameSite=Strict;Secure', 'transport_type': 'beacon' });
//*********GA4************
/**
* This function is an interface function that enables DAP users to send their
* custom GA4 events with the specified event name and parameters to the DAP GA4
* property and all other parallel properties set through the DAP. This is an
* older version of the gas4() function, which is kept for backward
* compatibility. Some agencies are still using this function since it was the
* older version created for the same purpose but mainly for Universal
* Analytics.
*
* @param {*} a unknown
* @param {*} b unknown
* @param {*} c unknown
* @param {*} d unknown
* @param {*} f unknown
* @param {*} e unknown
* @param {*} h unknown
*/
window.gas = function (a, b, c, d, f, e, h) {
if (
void 0 !== a &&
"" !== a &&
void 0 !== b &&
"" !== b &&
void 0 !== c &&
"" !== c
)
if ("pageview" === b.toLowerCase())
try {
c = _URIHandler(_scrubbedURL(c)).split(/[#]/)[0];
_sendEvent("page_view", { page_location: c, page_title: void 0 === d || "" === d ? document.title : d }),
((isSearch) ? _sendViewSearchResult({ search_term: isSearch }) : '');
} catch (n) { }
else if ("event" === b.toLowerCase() && void 0 !== d && "" !== d)
try {
var g = !1;
void 0 !== h &&
"boolean" === typeof _cleanBooleanParam(h) &&
(g = _cleanBooleanParam(h));
_sendEvent("dap_event", {
event_category: c,
event_action: d,
event_label: void 0 === f ? "" : f,
event_value: void 0 === e || "" === e || isNaN(e) ? 0 : parseInt(e),
non_interaction: g,
});
} catch (n) { }
else if (-1 != b.toLowerCase().indexOf("dimension"))
try {
} catch (n) { }
else if (-1 != b.toLowerCase().indexOf("metric"))
try {
} catch (n) { }
};
/**
* This function is a public interface function that enables sites which include
* the DAP JavaScript to send custom GA4 events with the specified event name
* and parameters to the DAP GA4 property and all other parallel properties set
* as query parameters to the DAP JavaScript source URL.
*
* @param {string} a the event name to be sent. This will default to 'dap_event'
* if the event name is not in a hard-coded list of supported event names.
* @param {object} b the event parameters to be sent. These parameters are
* modified for the 'page_view' event to include page_location, page_title, and
* search_term
*/
window.gas4 = function (a, b) {
if (void 0 !== a && "" !== a && void 0 !== b && 'object' === typeof b) {
a = _cleanGA4Value("e", a);
if ("page_view" === a.toLowerCase())
try {
if (Object.keys(b).length !== 0) {
var ur = ((b.page_location) ? b.page_location : location.href);
b.page_location = _URIHandler(_scrubbedURL(ur)).split(/[#]/)[0];
b.page_title = ((b.page_title) ? b.page_title : document.title);
_sendEvent("page_view", b), ((isSearch) ? (_sendViewSearchResult({ search_term: isSearch })) : '');
}
} catch (n) { }
else
try {
var e_n = ((/^(((email|telephone|image|cta|navigation|faq|accordion|social)_)?click|file_download|view_search_results|video_(start|pause|progress|complete|play)|official_USA_site_banner_click|form_(start|submit|progress)|content_view|social_share|error|sort|filter|was_this_helpful_submit)$/gi.test(a)) ? a : 'dap_event');
if (Object.keys(b).length !== 0) { _sendEvent(e_n, b); }
else { _sendEvent(e_n); }
} catch (n) { }
}
};
/**
* This function initializes the payload interceptor, defines the cookie domain,
* defines agency custom dimensions values, sets allowed query strings, and
* creates trackers. This function should be called immediately when every page
* is loaded in order to begin tracking analytics data.
*/
function _onEveryPage() {
_payloadInterceptor();
_defineCookieDomain();
_defineAgencyCDsValues();
_setAllowedQS();
createTracker();
}
_onEveryPage();
/**
* This function configures the cookie domain. The oCONFIG.COOKIE_DOMAIN value
* is set based on the oCONFIG.SUBDOMAIN_BASED configuration value.
*/
function _defineCookieDomain() {
/(([^.\/]+\.[^.\/]{2,3}\.[^.\/]{2})|(([^.\/]+\.)[^.\/]{2,4}))(\/.*)?$/.test(
oCONFIG.SUBDOMAIN_BASED.toString()
)
? ((oCONFIG.COOKIE_DOMAIN = oCONFIG.SUBDOMAIN_BASED.toLowerCase().replace(
/^www\./i,
""
)),
(oCONFIG.SUBDOMAIN_BASED = !0))
: !1 === oCONFIG.SUBDOMAIN_BASED
? ((oCONFIG.COOKIE_DOMAIN = document.location.hostname.match(
/(([^.\/]+\.[^.\/]{2,3}\.[^.\/]{2})|(([^.\/]+\.)[^.\/]{2,4}))(\/.*)?$/
)[1]),
(oCONFIG.SUBDOMAIN_BASED = !0))
: ((oCONFIG.COOKIE_DOMAIN = location.hostname
.toLowerCase()
.replace(/^www\./i, "")),
(oCONFIG.SUBDOMAIN_BASED = !1));
}
/**
* This function sets configuration values of agency custom dimensions.
*/
function _defineAgencyCDsValues() {
oCONFIG.AGENCY = oCONFIG.AGENCY || "unspecified:" + oCONFIG.COOKIE_DOMAIN;
oCONFIG.SUB_AGENCY = oCONFIG.SUB_AGENCY || "" + oCONFIG.COOKIE_DOMAIN;
oCONFIG.SITE_TOPIC =
oCONFIG.SITE_TOPIC || "unspecified:" + oCONFIG.COOKIE_DOMAIN;
oCONFIG.SITE_PLATFORM =
oCONFIG.SITE_PLATFORM || "unspecified:" + oCONFIG.COOKIE_DOMAIN;
}
/**
* This function sets the environment to production or dev, and drives the
* traffic to the respective GA4 data stream based on the presence of
* dap-dev-env query parameter in the page URL or dapdev parameter in the DAP
* code script reference.
*/
function _setEnvironment() {
if (document.location.href.match(/([?&])(dap-dev-env)([^&$]*)/i) || oCONFIG.ACTIVATE_DEV) {
oCONFIG.GWT_GA4ID[0] = "G-9TNNMGP8WJ"; //Test Digital Analytics Program GA4
}
}
/**
* This function intialize Core Web Vitals, and reports the performance
* metrics of only the home page to GA4. This function should be called immediately when every page
* is loaded in order to begin tracking performance data.
*/
function _initWebvitals(){
(/^(\/((index|home(page)?)(\.[a-zA-Z]{2,5})?)?)$/i.test(location.pathname)? oCONFIG.WEBVITALS = !0 : oCONFIG.WEBVITALS = !1)
if(oCONFIG.WEBVITALS){
/**
* This function is called on every page to inject the core webvitals script and add listerners to web-vitals events.
*/
(function () {
var WVscript = document.createElement('script');
WVscript.src = 'https://dap.digitalgov.gov/web-vitals/dist/web-vitals.attribution.iife.js';
/**
* Adds listeners for web vitals events
*/
WVscript.onload = function () {
webVitals.onCLS(sendToGoogleAnalytics);
webVitals.onFID(sendToGoogleAnalytics);
webVitals.onLCP(sendToGoogleAnalytics);
webVitals.onFCP(sendToGoogleAnalytics);
webVitals.onTTFB(sendToGoogleAnalytics);
webVitals.onINP(sendToGoogleAnalytics);
};
document.head.appendChild(WVscript);
})();
/**
* This function send core web vitals data to GA4 4 using _sendEvent funtion.
*/
function sendToGoogleAnalytics({name, delta, value, id, entries, rating, attribution}) {
var debugTarget = attribution ? attribution.largestShiftTarget||attribution.element||attribution.eventTarget||'' : '(not set)';
_sendEvent(name, {
// Built-in params:
value: delta, // Use `delta` so the value can be summed.
// Custom params:
metric_id: id, // Needed to aggregate events.
metric_value: value, // Optional.
metric_delta: delta, // Optional.
// OPTIONAL: any additional params or debug info here.
// See: https://web.dev/debug-web-vitals-in-the-field/
// metric_rating: 'good' | 'needs-improvement' | 'poor'. 'needs-improvement' was 'ni'
metric_rating: rating,
// debug_info
debug_target: debugTarget,
debug_event_type: attribution ? attribution.eventType||'' : '',
debug_timing: attribution ? attribution.loadState||'' : '',
event_time: attribution ? attribution.largestShiftTime||(attribution.lcpEntry&&attribution.lcpEntry.startTime)||attribution.eventTime||'': ''
});
}
}
}
/**
* @param {string} a a string to convert to a boolean value.
* @returns {boolean} true or false based on parsing the string parameter.
*/
function _cleanBooleanParam(a) {
switch (a.toString().toLowerCase()) {
case "true":
case "on":
case "yes":
case "1":
return !0;
case "false":
case "off":
case "no":
case "0":
return !1;
default:
return a;
}
}
/**
* @param {string} a a GA4 measurement ID
* @returns {boolean} true if the GA4 measurement ID is valid, false otherwise.
*/
function _isValidGA4Num(a) {
a = a.toLowerCase();
a = a.match(/^g\-([0-9a-z])+$/);
return null !== a && 0 < a.length && a[0] !== oCONFIG.GWT_GA4ID[0].toLowerCase();
}
var d_c = 1;
/**
* This function cleans a GA4 value of event names and custom dimension names by
* replacing whitespace and non-alphanumeric characters with underscores.
*
* @param {string} t a key name for the event
* @param {string} a the event name or custom dimension name
* @returns {string|undefined} the event name or custom dimension name formatted
* with underscores. Returns undefined if an error occurs while processing the
* params.
*/
function _cleanGA4Value(t, a) {
try {
a = a.replace(/\s/g, '_').replace(/([^\w]+)/g, '').match(/[A-Za-z]\w*$/ig);
return ((null !== a) ? a[0].toLowerCase() : t === "d" ? "custom_dimension_" + d_c++ : "dap_event");
} catch (c) { }
}
/**
* This function updates oCONFIG configuration values based on the query
* parameters of the DAP script element.
*/
function _updateConfig() {
if ("undefined" !== typeof _fedParmsGTM) {
var a = _fedParmsGTM.toLowerCase().split("&");
oCONFIG.SCRIPT_SOURCE = "GTM";
} else {
var b = document.getElementById("_fed_an_ua_tag");
_fullParams = b.src.match(/^([^\?]*)(.*)$/i)[2].replace("?", "");
a = _fullParams.split("&");
oCONFIG.SCRIPT_SOURCE = b.src.split("?")[0];
}
for (b = 0; b < a.length; b++)
switch (
((_keyValuePair = decodeURIComponent(a[b].toLowerCase())),
(_key = _keyValuePair.split("=")[0]),
(_value = _keyValuePair.split("=")[1]),
_key)
) {
case "pua":
for (var c = _value.split(","), d = 0; d < c.length; d++)
_isValidGA4Num(c[d]) && (oCONFIG.GWT_GA4ID.push(c[d].toUpperCase()), oCONFIG.USING_PARALLEL_TRACKER = "pua");
break;
case "pga4":
for (var c = _value.split(","), d = 0; d < c.length; d++)
_isValidGA4Num(c[d]) && (oCONFIG.GWT_GA4ID.push(c[d].toUpperCase()), oCONFIG.USING_PARALLEL_TRACKER = "pga4");
break;
case "agency":
oCONFIG.AGENCY = _value.toUpperCase();
break;
case "subagency":
oCONFIG.SUB_AGENCY = _value.toUpperCase();
break;
case "sitetopic":
oCONFIG.SITE_TOPIC = _value;
break;
case "siteplatform":
oCONFIG.SITE_PLATFORM = _value;
break;
case "parallelcd":
_value = _cleanBooleanParam(_value);
if (!0 === _value || !1 === _value)
oCONFIG.USE_PARALLEL_CUSTOM_DIMENSIONS = _value;
break;
case "custurl":
_value = _cleanBooleanParam(_value);
if (!0 === _value || !1 === _value)
oCONFIG.USE_CUSTOM_URL = _value;
break;
case "custitle":
_value = _cleanBooleanParam(_value);
if (!0 === _value || !1 === _value)
oCONFIG.USE_CUSTOM_TITLE = _value;
break;
case "dapdev":
_value = _cleanBooleanParam(_value);
if (!0 === _value || !1 === _value)
oCONFIG.ACTIVATE_DEV = _value;
break;
case "palagencydim":
_value = _cleanGA4Value("d", _value);
"" !== _value &&
(oCONFIG.PARALLEL_AGENCY_DIMENSION = _value);
break;
case "palsubagencydim":
_value = _cleanGA4Value("d", _value);
"" !== _value &&
(oCONFIG.PARALLEL_SUBAGENCY_DIMENSION = _value);
break;
case "palversiondim":
_value = _cleanGA4Value("d", _value);
"" !== _value &&
(oCONFIG.PARALLEL_CODEVERSION_DIMENSION = _value);
break;
case "paltopicdim":
_value = _cleanGA4Value("d", _value);
"" !== _value &&
(oCONFIG.PARALLEL_SITE_TOPIC_DIMENSION = _value);
break;
case "palplatformdim":
_value = _cleanGA4Value("d", _value);
"" !== _value &&
(oCONFIG.PARALLEL_SITE_PLATFORM_DIMENSION = _value);
break;
case "palscriptsrcdim":
_value = _cleanGA4Value("d", _value);
"" !== _value &&
(oCONFIG.PARALLEL_SCRIPT_SOURCE_URL_DIMENSION = _value);
break;
case "palurlprotocoldim":
_value = _cleanGA4Value("d", _value);
"" !== _value &&
(oCONFIG.PARALLEL_URL_PROTOCOL_DIMENSION = _value);
break;
case "palinteractiontypedim":
_value = _cleanGA4Value("d", _value);
"" !== _value &&
(oCONFIG.PARALLEL_INTERACTION_TYPE_DIMENSION = _value);
break;
case "cto":
oCONFIG.COOKIE_TIMEOUT = parseInt(_value) * 2628000; // = 60 * 60 * 24 * 30.4166666666667;
break;
case "sp":
oCONFIG.SEARCH_PARAMS += "|" + _value.replace(/,/g, "|");
break;
case "exts":
oCONFIG.EXTS += "|" + _value.replace(/,/g, "|");
break;
case "htmlvideo":
_value = _cleanBooleanParam(_value);
if (!0 === _value || !1 === _value) oCONFIG.HTMLVIDEO = _value;
break;
case "yt":
_value = _cleanBooleanParam(_value);
if (!0 === _value || !1 === _value) oCONFIG.YOUTUBE = _value;
break;
case "ytm":
oCONFIG.YT_MILESTONE = ((/^(10|20|25)$/.test(_value) ? parseInt(_value) : 25));
break;
case "autotracker":
_value = _cleanBooleanParam(_value);
if (!0 === _value || !1 === _value) oCONFIG.AUTOTRACKER = _value;
break;
case "webvitals":
_value = _cleanBooleanParam(_value);
if (!0 === _value || !1 === _value) oCONFIG.WEBVITALS = _value;
break;
case "sdor":
oCONFIG.SUBDOMAIN_BASED = _cleanBooleanParam(_value);
break;
default:
break;
}
}
/**
* This function sends an event command to the Google gtag.js library with the
* specified event name and parameters. PII is redacted from the event
* parameters.
*
* @param {string} a the event name
* @param {object} b the event parameters. The parameters are modified to add
* 'send_to' and 'event_name_dimension' keys before sending the parameters to
* the gtag.js library's event command
*/
function _sendEvent(a, b) {
var send_to = "";
for (var g = 0; g < oCONFIG.GWT_GA4ID.length; g++) {
try {
send_to += oCONFIG.GA4_NAME + g + ",";
}
catch (er) { }
}
var c = _piiRedactor(_objToQuery(b), "json");
c = _queryToJSON(c);
c = _unflattenJSON(c);
c.send_to = send_to.replace(/.$/, "");
c.event_name_dimension = a;
gtag("event", a, c);
}
/**
* This function sends a GA4 view_search_results event with the specified search
* term.
*
* @param {string} a the search term.
*/
function _sendViewSearchResult(a) {
_sendEvent("view_search_results", a), isSearch = !1;
}
/**
* This function checks for self-referrals to check if the referrer should be
* excluded based on the SUBDOMAIN_BASED configuration.
*
* @returns {boolean|undefined} true if the referrer should be excluded,
* undefined if the document.referrer is empty, false otherwise.
*/
function _isExcludedReferrer() {
if ("" !== document.referrer) {
var a = document.referrer
.replace(/https?:\/\//i, "")
.split("/")[0]
.replace(/^www\./i, "");
return oCONFIG.SUBDOMAIN_BASED
? -1 != a.indexOf(oCONFIG.COOKIE_DOMAIN)
? !0
: !1
: a === oCONFIG.COOKIE_DOMAIN
? !0
: !1;
}
}
/**
* This function creates a GA4 tracker with the configuration set in the oCONFIG
* object. A "config" command is sent to the gtag.js library for the DAP GA4
* configuration. A "config" command is also sent to the gtag.js library for
* each configured parallel tracker.
*
* @param {*} a this parameter isn't used
*/
function createTracker(a) {
var m, n, o = /^\/.*$/i;
try { m = ((oCONFIG.USE_CUSTOM_URL && o.test(custom_dap_data.url)) ? location.protocol + "//" + location.hostname + custom_dap_data.url.replace(location.protocol + "//" + location.hostname, "") : document.location.href); n = ((oCONFIG.USE_CUSTOM_TITLE) ? custom_dap_data.title : document.title); } catch (error) { m = document.location.href; n = document.title; }
var c = m.split(document.location.hostname)[1];
-1 !== document.title.search(/404|not found/i) &&
(c = ("/vpv404/" + c).replace(/\/\//g, "/") + ((document.referrer) ? "/" + document.referrer : document.referrer));
var p = ((-1 !== document.title.search(/404|not found/ig)) ? document.location.protocol + "//" + document.location.hostname + c : m);
var ur = _URIHandler(_scrubbedURL(p));
var r = {};
for (var b = 0; b < oCONFIG.GWT_GA4ID.length; b++) {
if (b === 0) {
r = {
groups: oCONFIG.GA4_NAME + b,
cookie_expires: parseInt(oCONFIG.COOKIE_TIMEOUT),
//ignore_referrer: (_isExcludedReferrer() ? true : false),
page_location: ur,
page_title: n,
[oCONFIG.MAIN_AGENCY_DIMENSION]: oCONFIG.AGENCY.toUpperCase(),
[oCONFIG.MAIN_SUBAGENCY_DIMENSION]: oCONFIG.SUB_AGENCY.toUpperCase(),
[oCONFIG.MAIN_SITE_TOPIC_DIMENSION]: oCONFIG.SITE_TOPIC.toLowerCase(),
[oCONFIG.MAIN_SITE_PLATFORM_DIMENSION]: oCONFIG.SITE_PLATFORM.toLowerCase(),
[oCONFIG.MAIN_SCRIPT_SOURCE_URL_DIMENSION]: oCONFIG.SCRIPT_SOURCE.toLowerCase(),
[oCONFIG.MAIN_CODEVERSION_DIMENSION]: oCONFIG.VERSION.toLowerCase(),
[oCONFIG.MAIN_URL_PROTOCOL_DIMENSION]: oCONFIG.URL_PROTOCOL.toLowerCase(),
[oCONFIG.MAIN_USING_PARALLEL_DIMENSION]: oCONFIG.USING_PARALLEL_TRACKER.toLowerCase()
};
((document.referrer && -1 !== document.referrer.search(location.hostname)) ? (r.page_referrer = _scrubbedURL(document.referrer)) : document.referrer);
var rr = _piiRedactor(_objToQuery(r), "default");
rr = _queryToJSON(rr);
rr = _unflattenJSON(rr);
gtag("config", oCONFIG.GWT_GA4ID[b], rr);
}
else if (b > 0 && oCONFIG.USE_PARALLEL_CUSTOM_DIMENSIONS) {
r = {
groups: oCONFIG.GA4_NAME + b,
cookie_expires: parseInt(oCONFIG.COOKIE_TIMEOUT),
//ignore_referrer: (_isExcludedReferrer() ? true : false),
page_location: ur,
page_title: n,
[oCONFIG.PARALLEL_AGENCY_DIMENSION]: oCONFIG.AGENCY.toUpperCase(),
[oCONFIG.PARALLEL_SUBAGENCY_DIMENSION]: oCONFIG.SUB_AGENCY.toUpperCase(),
[oCONFIG.PARALLEL_SITE_TOPIC_DIMENSION]: oCONFIG.SITE_TOPIC.toLowerCase(),
[oCONFIG.PARALLEL_SITE_PLATFORM_DIMENSION]: oCONFIG.SITE_PLATFORM.toLowerCase(),
[oCONFIG.PARALLEL_SCRIPT_SOURCE_URL_DIMENSION]: oCONFIG.SCRIPT_SOURCE.toLowerCase(),
[oCONFIG.PARALLEL_CODEVERSION_DIMENSION]: oCONFIG.VERSION.toLowerCase(),
[oCONFIG.PARALLEL_URL_PROTOCOL_DIMENSION]: oCONFIG.URL_PROTOCOL.toLowerCase(),
[oCONFIG.PARALLEL_USING_PARALLEL_DIMENSION]: oCONFIG.USING_PARALLEL_TRACKER.toLowerCase()
};
((document.referrer && -1 !== document.referrer.search(location.hostname)) ? (r.page_referrer = _scrubbedURL(document.referrer)) : document.referrer);
var rr = _piiRedactor(_objToQuery(r), "default");
rr = _queryToJSON(rr);
rr = _unflattenJSON(rr);
gtag("config", oCONFIG.GWT_GA4ID[b], rr);
}
else {
r = {
groups: oCONFIG.GA4_NAME + b,
cookie_expires: parseInt(oCONFIG.COOKIE_TIMEOUT),
//ignore_referrer: (_isExcludedReferrer() ? true : false),
page_location: ur,
page_title: n
};
((document.referrer && -1 !== document.referrer.search(location.hostname)) ? (r.page_referrer = _scrubbedURL(document.referrer)) : document.referrer);
var rr = _piiRedactor(_objToQuery(r), "default");
rr = _queryToJSON(rr);
rr = _unflattenJSON(rr);
gtag("config", oCONFIG.GWT_GA4ID[b], rr);
}
}
((isSearch) ? _sendViewSearchResult({ search_term: isSearch }) : "");
}
/**
* This function initializes the auto-tracker for links and downloads. It sets
* event listeners to the document for mousedown and keydown events.
*/
function _initAutoTracker() {
/**
* This function checks if a link is a download based on the file extension.
*
* @param {string} a the link URL
* @returns {boolean} true if the URL is a download link, false otherwise.
*/
var _isDownload = function (a) {
var ex = a.href.toLowerCase().replace(/[#?&].*/, '').split(a.hostname)[1].split("."); var ext = ex[ex.length - 1];
if (ext.match(new RegExp("^(" + oCONFIG.EXTS + ")$")) != null) {
return ext;
}
else {
return false;
}
};
/**
* @param {object} j the object to modify.
* @returns {object|undefined} the object with all keys and values converted
* to lower case, undefined if there is an error processing the object.
*/
/* var _enforeLower = function (j) {
try {
var d = JSON.stringify(j);
return JSON.parse(d.toLowerCase());
} catch (error) { }
};
*/
/**
* This function handles events for the auto-tracker. It sends event commands
* to the Google gtag.js library when links are clicked by the user. The event
* name is determined by the type of link that was clicked.
*
* @param {object} event the browser event to handle
*/
var _eventHandler = function (event) {
try {
if ("mousedown" === event.type || ("keydown" === event.type && 13 === event.keyCode)) {
if (event.target.nodeName === 'A' || event.target.closest('a') !== null) {
var b = oCONFIG.COOKIE_DOMAIN, c = "";
var d = "",
f = "",
e = /^mailto:[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}/i,
h =
/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/i,
i = "",
t = "",
l = {},
g = /^(tel:)(.*)$/i;
var a = event.target.closest('a');
if ("mousedown" === event.type) {
t = "Mouse Click";
}
else if ("keydown" === event.type && 13 === event.keyCode) {
t = "Enter Key Keystroke";
}
if (e.test(a.href) || h.test(a.href) || g.test(a.href)) {
try {
h.test(a.href)
? ((f = a.hostname.toLowerCase().replace(/^www\./i, "")), (i = "l"))
: e.test(a.href)
? ((f = a.href.split("@")[1].toLowerCase()), (i = "m"))
: g.test(a.href) && ((f = a.href), (f = f.toLowerCase()), (i = "t"));
} catch (k) {
//continue;
}
}
if (oCONFIG.SUBDOMAIN_BASED ? -1 !== f.indexOf(b) : f === b) {
if ("m" === i) {
c = a.href.match(/[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}/);
l = { link_id: a.id, link_url: c[0], link_domain: c[0].split("@")[1], link_text: a.text.replace(/(?:[\r\n]+)+/g, "").trim(), link_classes: a.className, interaction_type: t };
_sendEvent("email_click", l);
}
/*else if("t"===i){
l = {link_id: a.id, link_url: a.href.split("tel:")[1], link_text: a.text.replace(/(?:[\r\n]+)+/g, "").trim(), link_classes: a.className, interaction_type: t};
_sendEvent("telephone_click", l);
}*/
else {
if ("l" === i && _isDownload(a)) {
c = a.pathname.split(/[#?&?]/)[0];
d = _isDownload(a);
l = { file_name: c, file_extension: d, link_text: a.text.replace(/(?:[\r\n]+)+/g, "").trim(), link_id: a.id, link_url: a.href.replace(/[#?&].*/, ""), link_domain: a.hostname.replace(/^www\./i, ""), interaction_type: t };
_sendEvent("file_download", l);
}
else if ("l" === i && !_isDownload(a)) {
//internal link tracking;
/*c = a.closest('section'); var s_n = (('object' === typeof c)? (c.id? c.id : c.className) : '');
l = { link_id: a.id, link_url: a.href, link_domain: a.hostname.replace(/^www\./i, ""), link_text: a.text.replace(/(?:[\r\n]+)+/g, "").trim(), link_classes: a.className, interaction_type: t, section: s_n, menu_type: 'all' };
_sendEvent("navigation_click", l);*/
}
}
}
else {
if ("l" === i && _isDownload(a)) {
c = a.pathname.split(/[#?&?]/)[0];
d = _isDownload(a);
l = { file_name: c, file_extension: d, link_text: a.text.replace(/(?:[\r\n]+)+/g, "").trim(), link_id: a.id, link_url: a.href.replace(/[#?&].*/, ""), link_domain: a.hostname.replace(/^www\./i, ""), outbound: true, interaction_type: t };
_sendEvent("file_download", l);
}
else if ("l" === i && !_isDownload(a)) {
l = { link_id: a.id, link_url: a.href.replace(/[#?&].*/, ""), link_domain: a.hostname.replace(/^www\./i, ""), link_text: a.text.replace(/(?:[\r\n]+)+/g, "").trim(), link_classes: a.className, outbound: true, interaction_type: t };
_sendEvent("click", l);
}
else if ("m" === i) {
c = a.href.match(/[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}/);
l = { link_id: a.id, link_url: c[0], link_domain: c[0].split("@")[1], link_text: a.text.replace(/(?:[\r\n]+)+/g, "").trim(), link_classes: a.className, outbound: true, interaction_type: t };
_sendEvent("email_click", l);
}
else if ("t" === i) {
l = { link_id: a.id, link_url: a.href.split("tel:")[1], link_text: a.text.replace(/(?:[\r\n]+)+/g, "").trim(), link_classes: a.className, interaction_type: t };
_sendEvent("telephone_click", l);
}
}
}
}
} catch (error) {
}
};
(document.addEventListener ? document.addEventListener("mousedown", _eventHandler, false) : (document.attachEvent && document.attachEvent("onmousedown", _eventHandler)));
(document.addEventListener ? document.addEventListener("keydown", _eventHandler, false) : (document.attachEvent && document.attachEvent("onkeydown", _eventHandler)));
}
// START YT TRACKER //
if (oCONFIG.YOUTUBE) {
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var videoArray = [];
var playerArray = [];
var _buckets = [];
// accepted values for milestone 10, 20 or 25
var _milestoneController = oCONFIG.YT_MILESTONE;
var ytUtils = [];
/**
* Initializes the YouTube API
*/
onYouTubeIframeAPIReady = function () {
for (var i = 0; i < videoArray.length; i++) {
playerArray[i] = new YT.Player(videoArray[i], {
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange,
'onError': onPlayerError
}
});
}
};
/**
* Called when the YouTube player is ready
*
* @param {object} event the ready event.
*/
onPlayerReady = function (event) { };
/**
* Called when the YouTube player has an error. Sends a 'video_error' event
* command to the Google gtag.js library.
*
* @param {object} event the error event.
*/
onPlayerError = function (event) {
_sendEvent('video_error', { videotitle: ((event.target.playerInfo !== undefined) ? event.target.playerInfo.videoData.title : event.target.getVideoData().title) });
};
/**
* Called when the YouTube player state changes. Sends a 'video_start',
* "video_play", "video_complete", and "video_pause" event commands to the
* Google gtag.js library based on the state of the player.
*
* @param {object} event the state change event.
*/
onPlayerStateChange = function (event) {
try {
var videoIndex = 0, video_id = ((event.target.playerInfo !== undefined) ? event.target.playerInfo.videoData.video_id : event.target.getVideoData().video_id);
for (var o = 0; o < videoArray.length; o++) {
if (videoArray[o] == video_id) {
videoIndex = o;
}
}
var cTime = ((playerArray[videoIndex].playerInfo !== undefined) ? Math.round(playerArray[videoIndex].playerInfo.currentTime) : Math.round(playerArray[videoIndex].getCurrentTime()));
var vDuration = ((playerArray[videoIndex].playerInfo !== undefined) ? Math.round(playerArray[videoIndex].playerInfo.duration) : Math.round(playerArray[videoIndex].getDuration()));
var p = {
video_current_time: cTime,
video_duration: vDuration,
video_percent: ((cTime / vDuration) * 100).toFixed(),
video_provider: "youtube",
video_title: ((playerArray[videoIndex].playerInfo !== undefined) ? playerArray[videoIndex].playerInfo.videoData.title : playerArray[videoIndex].getVideoData().title),
video_id: ((playerArray[videoIndex].playerInfo !== undefined) ? playerArray[videoIndex].playerInfo.videoData.video_id : playerArray[videoIndex].getVideoData().video_id),
video_url: ((playerArray[videoIndex].playerInfo !== undefined) ? playerArray[videoIndex].playerInfo.videoUrl : playerArray[videoIndex].getVideoUrl())
};
if (event.data == YT.PlayerState.PLAYING && p.video_percent == 0) {
_sendEvent('video_start', p);
cCi = 0;
if (_milestoneController) {
ytUtils.push([videoIndex, function (videx) {
for (var b = 1; b <= (100 / _milestoneController); b++) {
((100 / _milestoneController === 4 && b === 100 / _milestoneController) ? _buckets[b - 1] = { id: videoIndex, milestone: 95, triggered: false } : ((_milestoneController * b !== 100) ? _buckets[b - 1] = { id: videoIndex, milestone: _milestoneController * b, triggered: false } : ''));
}
setInterval(function () {
var cTimeP = ((playerArray[videoIndex].playerInfo !== undefined) ? Math.round(playerArray[videoIndex].playerInfo.currentTime) : Math.round(playerArray[videoIndex].getCurrentTime()));
var vDurationP = ((playerArray[videoIndex].playerInfo !== undefined) ? Math.round(playerArray[videoIndex].playerInfo.duration) : Math.round(playerArray[videoIndex].getDuration()));
var y = {
video_current_time: cTimeP,
video_duration: vDurationP,
video_percent: ((cTimeP / vDurationP) * 100).toFixed(),
video_provider: "youtube",
video_title: ((playerArray[videoIndex].playerInfo !== undefined) ? playerArray[videoIndex].playerInfo.videoData.title : playerArray[videoIndex].getVideoData().title),
video_id: ((playerArray[videoIndex].playerInfo !== undefined) ? playerArray[videoIndex].playerInfo.videoData.video_id : playerArray[videoIndex].getVideoData().video_id),
video_url: ((playerArray[videoIndex].playerInfo !== undefined) ? playerArray[videoIndex].playerInfo.videoUrl : playerArray[videoIndex].getVideoUrl())
};
if (y.video_percent <= _buckets[_buckets.length - 1] && cCi < _buckets.length) {
if (y.video_percent >= _buckets[cCi].milestone && !_buckets[cCi].triggered && _buckets[videoIndex].id === videoIndex) {
_buckets[cCi].triggered = true; y.video_percent = _buckets[cCi].milestone; y.video_current_time = Math.round((y.video_duration / _buckets.length) * (cCi + 1)); _sendEvent("video_progress", y); cCi++;
}
}
}, ((playerArray[videoIndex].playerInfo !== undefined) ? Math.round(playerArray[videoIndex].playerInfo.duration) : Math.round(playerArray[videoIndex].getDuration())) / _buckets.length);
}]);
ytUtils[ytUtils.length - 1][1](videoIndex);
}
}
else if (event.data == YT.PlayerState.PLAYING) { _sendEvent("video_play", p); }
if (event.data == YT.PlayerState.ENDED) { _sendEvent("video_complete", p); }
if (event.data == YT.PlayerState.PAUSED) { _sendEvent("video_pause", p); }
} catch (error) {
}
};
/**
* @param {string} e a URL
* @returns {string|undefined} the YouTube video ID if the URL is a YouTube
* video URL, returns undefined otherwise.
*/
youtube_parser = function (e) { var t = e.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/); if (t && 11 == t[2].length) return t[2] };
/**
* @param {string} u a URL
* @returns {boolean} true if the URL is a YouTube video URL, false otherwise.
*/
IsYouTube = function (u) { var e = u.match(/(.*)(youtu\.be\/|youtube(\-nocookie)?\.([A-Za-z]{2,4}|[A-Za-z]{2,3}\.[A-Za-z]{2})\/)(watch|embed\/|vi?\/)?(\?vi?=)?([^#&\?\/]{11}).*/); return null != e && e.length > 0 };
/**
* @param {string} t a URL
* @returns {string} the URL with 'origin', 'protocol', and 'enablejsapi'
* query params altered.
*/
YTUrlHandler = function (t) { return t = t.replace(/origin\=(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})\&?/gi, "origin=" + document.location.protocol + "//" + document.location.host), stAdd = "", adFlag = !1, -1 == t.indexOf("https") && (t = t.replace("http", "https")), -1 == t.indexOf("?") && (stAdd = "?flag=1"), -1 == t.indexOf("enablejsapi") && (stAdd += "&enablejsapi=1", adFlag = !0), -1 == t.indexOf("origin") && (stAdd += "&origin=" + document.location.protocol + "//" + document.location.host, adFlag = !0), 1 == adFlag ? t + stAdd : t };
/**
* Selects every iframe element in the page and modifies those which are
* YouTube players with a parsed 'src' field and 'id' field.
*/
_initYouTubeTracker = function () {
var i = 0;
var allIframes = document.getElementsByTagName('iframe');
for (var iframe = 0; iframe < allIframes.length; iframe++) {
var video = allIframes[iframe];
var _thisSrc = video.src;
if (IsYouTube(_thisSrc)) {
allIframes[iframe].src = YTUrlHandler(_thisSrc);
var youtubeid = youtube_parser(_thisSrc);
videoArray[i] = youtubeid;
allIframes[iframe].setAttribute('id', youtubeid);
i++;
}
}
};
}
// END YT TRACKER //
// HTML5 VIDEO TRACKER //
/**
* Selects every video or audio element in the page and modifies those which
* don't have ID attributes to add a random ID. Then adds an event listener
* to the elements for each type of event where metrics should be sent.
*/
function _initHTMLVideoTracker() {
var _milestone = oCONFIG.YT_MILESTONE;
var media_status = {};
/**
* Sends audio and video metrics for start, play, progress, pause, and
* complete based on the event's target element's state.
*
* @param {object} e an element's event
*/
function eventHandler(e) {
var media_type = ((e.target.nodeName === 'VIDEO') ? 'video' : 'audio');
var mObj = {};
((media_type === 'video') ? (mObj = {
video_provider: "html5 video",
video_title: decodeURIComponent(e.target.currentSrc.split('/')[e.target.currentSrc.split('/').length - 1]),
video_id: e.target.id,
video_url: decodeURIComponent(e.target.currentSrc)
}) : (mObj = {
audio_provider: "html5 audio",
audio_title: decodeURIComponent(e.target.currentSrc.split('/')[e.target.currentSrc.split('/').length - 1]),
audio_id: e.target.id,
audio_url: decodeURIComponent(e.target.currentSrc)
}));
switch (e.type) {
case 'timeupdate':
media_status[e.target.id].current = Math.round(e.target.currentTime);
var percentage = Math.floor(100 * media_status[e.target.id].current / Math.round(e.target.duration));
for (var j in media_status[e.target.id]._progress_milestones) {
if (percentage >= j && j > media_status[e.target.id].latest_milestone) {
media_status[e.target.id].latest_milestone = j;
}
}
if (media_status[e.target.id].latest_milestone && !media_status[e.target.id]._progress_milestones[media_status[e.target.id].latest_milestone]) {
media_status[e.target.id]._progress_milestones[media_status[e.target.id].latest_milestone] = true;
((media_type === 'video') ? (mObj.video_current_time = media_status[e.target.id].current, mObj.video_duration = Math.round(e.target.duration), mObj.video_percent = media_status[e.target.id].latest_milestone) :
(mObj.audio_current_time = media_status[e.target.id].current, mObj.audio_duration = Math.round(e.target.duration), mObj.audio_percent = media_status[e.target.id].latest_milestone));
_sendEvent(media_type + '_progress', mObj);
}
break;
case 'play':
((media_type === 'video') ? (mObj.video_current_time = media_status[e.target.id].current, mObj.video_duration = Math.round(e.target.duration), mObj.video_percent = media_status[e.target.id].latest_milestone) :
(mObj.audio_current_time = media_status[e.target.id].current, mObj.audio_duration = Math.round(e.target.duration), mObj.audio_percent = media_status[e.target.id].latest_milestone)); var e_n = ((media_status[e.target.id].current === 0) ? media_type + '_start' : media_type + '_play');
_sendEvent(e_n, mObj);
break;
case 'pause':
if (media_status[e.target.id].current !== Math.round(e.target.duration)) {
((media_type === 'video') ? (mObj.video_current_time = media_status[e.target.id].current, mObj.video_duration = Math.round(e.target.duration), mObj.video_percent = media_status[e.target.id].latest_milestone) :
(mObj.audio_current_time = media_status[e.target.id].current, mObj.audio_duration = Math.round(e.target.duration), mObj.audio_percent = media_status[e.target.id].latest_milestone));
_sendEvent(media_type + '_pause', mObj);
}
break;
case 'ended':
((media_type === 'video') ? (mObj.video_current_time = media_status[e.target.id].current, mObj.video_duration = Math.round(e.target.duration), mObj.video_percent = "100") :
(mObj.audio_current_time = media_status[e.target.id].current, mObj.audio_duration = Math.round(e.target.duration), mObj.audio_percent = "100"));
_sendEvent(media_type + '_complete', mObj);
media_status[e.target.id].current = 0;
media_status[e.target.id].latest_milestone = 0;
for (var b = 1; b <= (100 / _milestone); b++) {
((100 / _milestone === 4 && b === 100 / _milestone) ? media_status[e.target.id].progress_point = 95 : ((_milestone * b !== 100) ? media_status[e.target.id].progress_point = _milestone * b : ''));
media_status[e.target.id]._progress_milestones[media_status[e.target.id].progress_point] = false
}
break;
default:
break;
}
}
var htmlMedia = document.querySelectorAll('video,audio');
for (var i = 0; i < htmlMedia.length; i++) {
var mediaTagId;
((!htmlMedia[i].getAttribute('id')) ? (mediaTagId = 'html5_media_' + Math.random().toString(36).slice(2), htmlMedia[i].setAttribute('id', mediaTagId)) : mediaTagId = htmlMedia[i].getAttribute('id'));
media_status[mediaTagId] = {};
media_status[mediaTagId].latest_milestone = 0;
media_status[mediaTagId]._progress_milestones = {};
for (var b = 1; b <= (100 / _milestone); b++) {
((100 / _milestone === 4 && b === 100 / _milestone) ? media_status[mediaTagId].progress_point = 95 : ((_milestone * b !== 100) ? media_status[mediaTagId].progress_point = _milestone * b : ''));
media_status[mediaTagId]._progress_milestones[media_status[mediaTagId].progress_point] = false
}
media_status[mediaTagId].current = 0;
htmlMedia[i].addEventListener("play", eventHandler, false);
htmlMedia[i].addEventListener("pause", eventHandler, false);
htmlMedia[i].addEventListener("ended", eventHandler, false);
htmlMedia[i].addEventListener("timeupdate", eventHandler, false);
htmlMedia[i].addEventListener("ended", eventHandler, false);
}
}
// END HTML5 VIDEO TRACKER
/**
* GA4 Payload Interceptor. This function intercepts the GA4 beacon requests to
* redact personally identifiable information from the beacon request's
* arguments.
*
* @returns {boolean|undefined} the result of the window.navigator.sendBeacon
* function call if there is an error during this method's processing, returns
* undefined otherwise.
*/
function _payloadInterceptor() {
window._isRedacted = window._isRedacted || false;
if (!window._isRedacted) {
window._isRedacted = !0;
try {
var pl = window.navigator.sendBeacon;
var ga4_props = oCONFIG.GWT_GA4ID.join("|");
/**
* Attempts to redact PII from the arguments to beacon requests that are
* directed to google analytics APIs. The beacon requests are then sent
* with the modified arguments.
*
* @param {string} url the URL of the beacon request
* @param {string|undefined} data the data for the beacon request.
* @returns {boolean} true if the beacon request was queued successfully,
* false otherwise.
*/
window.navigator.sendBeacon = function () {
if (arguments && arguments[0].match(/google-analytics\.com.*v\=2\&/i) && arguments[0].match(new RegExp(ga4_props))) {
var endpoint = arguments[0].split('?')[0], query = arguments[0].split('?')[1];
var beacon = {
endpoint: endpoint, query: _piiRedactor(query, "ga4"), events: []
};
if (arguments[1]) {
arguments[1].split("\r\n").forEach(function (event) {
beacon.events.push(_piiRedactor(event, "ga4"));
});
}
arguments[0] = [beacon.endpoint, beacon.query].join('?');
if (arguments[1] && beacon.events.length > 0) {
beacon.events.join("\r\n");
arguments[1] = beacon.events.join("\r\n");
}
}
return pl.apply(this, arguments);
};
} catch (e) { return pl.apply(this, arguments); }
}
}
// End GA4 Payload Interceptor
/**
* @param {object} data a JSON object
* @returns {object|undefined} the JSON object with nested objects restored if
* nested keys exist in the object. Returns undefined if there is an error
* during processing.
*/
function _unflattenJSON(data) {
try {
if (Object(data) !== data || Array.isArray(data))
return data;
var result = {}, cur, prop, idx, last, temp;
for (var p in data) {
cur = result, prop = "", last = 0;
do {
idx = p.indexOf(".", last);
temp = p.substring(last, idx !== -1 ? idx : undefined);
cur = cur[prop] || (cur[prop] = (!isNaN(parseInt(temp)) ? [] : {}));
prop = temp;
last = idx + 1;
} while (idx >= 0);
cur[prop] = data[p];
}
return result[""];
} catch (error) {
}
}
/**
* @param {object} data a JSON object
* @returns {object|undefined} the JSON object with nested objects flattenned to
* be at a single key depth in the main object. Returns undefined if there is an
* error during processing.
*/
function _flattenJSON(data) {
try {
var result = {};
/**
* Recursively sets keys on an external "result" object to a flattenned
* version of the cur object.
*
* @param {object} cur the object to flatten
* @param {string} prop a key name
*/
function recurse(cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for (var i = 0, l = cur.length; i < l; i++)
recurse(cur[i], prop ? prop + "." + i : "" + i);
if (l == 0)
result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop + "." + p : p);
}
if (isEmpty)
result[prop] = {};
}
}
recurse(data, "");
return result;
} catch (error) {
}
}
/**
* @param {object} obj the object
* @returns {string} the object converted to a URL query string
*/
function _objToQuery(obj) {
return Object.keys(obj).reduce(function (str, key, i) {
var delimiter, val;
delimiter = (i === 0) ? '' : '&';
key = encodeURIComponent(key);
val = encodeURIComponent(obj[key]);
return [str, delimiter, key, '=', val].join('');
}, '');
}
/**
* @param {string} qs a URL querystring
* @returns {object} the query string converted to a JSON object
*/
function _queryToJSON(qs) {
var pairs = qs.split('&');
var result = {};
pairs.forEach(function (p) {
var pair = p.split('=');
var key = pair[0];
var value = decodeURIComponent(pair[1] || '');
if(!isNaN(Number(value)) && /^(value|metric_(value|delta)|event_time|(video|audio)_(duration|percent|current_time))$/i.test(key)){
value = Number(value);
}
if (result[key]) {
if (Object.prototype.toString.call(result[key]) === '[object Array]') {
result[key].push(value);
} else {
result[key] = [result[key], value];
}
} else {
result[key] = value;
}
});
return JSON.parse(JSON.stringify(result));
};
/**
* @returns {object[]} an array of objects with name and regex for redacting
* PII
*/
function _piiRegexReset() {
return [{
name: 'EMAIL',
regex: /[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}/gi
}, {
name: 'TEL',
regex: /((tel|(tele)?phone|mob(ile)?|cell(ular)?)\=)?((\+\d{1,2}[\s\.\-]?)?\d{3}[\s\.\-]\d{3}[\s\.\-]\d{4})([^\&\s\?\/]*)/gi
}, {
name: 'SSN',
regex: /((full)?(([\-\_])?)?ssn\=)?(\d{3}([\s\.\-\+]|%20)\d{2}([\s\.\-\+]|%20)\d{4})([^\&\s\?\/]*)/ig
}, {
name: 'NAME',
regex: /((first|last|middle|sur|f|l|user)([\-\_])?)?name\=([^\&\s\?\/]*)/ig
}, {
name: 'PASSWORD',
regex: /(((confirm([\-\_])?)?password)|passwd|pwd)\=([^\&\s\?\/]*)/ig
}, {
name: 'ZIP',
regex: /(post(al)?[\s]?code|zip[\s]?code|zip)\=([^\&\s\?\/]*)/gi
}, {
name: 'ADDRESS',
regex: /add(ress)?([1-2])?\=([^\&\s\?\/]*)/ig
}];
}
// Payload Redactor
/**
* @param {*} payload event parameters to be redacted
* @param {string} type the type of payload data
* @returns {string|undefined} the payload converted to a query string and with
* PII redacted. Returns undefined if there is an error during processing.
*/
function _piiRedactor(payload, type) {
try {
var checkParams = "dl|dr|dt|dt|en|ep.|up.|uid";
var UncheckParams = "ep.agency||ep.subagency|ep.site_topic|ep.site_platform|ep.script_source|ep.version|ep.protocol";
var piiRegex = _piiRegexReset();
payload = (("object" === typeof payload && /json|default/.test(type)) ? (_flattenJSON(payload), payload = _objToQuery(payload)) : payload);
_piiRegexReset();
var _allowedQs = _allowedQuerystrings.toString().toLowerCase().replace(/\,/g, "=|") + "=";
var _hitPayloadParts = payload.split('&');
for (var i = 0; i < _hitPayloadParts.length; i++) {
var newQueryString = '';
var _param = _hitPayloadParts[i].split('=');
var _para = (_param.length > 2) ? _param.slice(1).join("=") : _param[1]; _param.splice(2); _param[1] = _para;
var _val;
try {
_val = decodeURIComponent(decodeURIComponent(_param[1]));
} catch (e) {
_val = decodeURIComponent(_param[1]);
}
if ((_param[0].match(new RegExp(checkParams)) != null || /query|json/ig.test(type)) && _val.indexOf('?') > -1) {
var paramArray = _val.split('?').splice(1).join('&').split('&');
var paramSubArray = [];
// loop through the parameters in the search query string to see if there are sub-parameters, and build the paramSubArray
for (pa = 0; pa < paramArray.length; pa++) {
// account for sub-parameters within parameters in the URL
if (paramArray[pa].indexOf('?') > -1) {
paramSubArray.push(paramArray[pa].split('?')[1]);
}
}
paramArray = paramArray.concat(paramSubArray);
// Build a new query string out of all allowed parameters
for (var ix = 0; ix < paramArray.length; ix++) {
if (paramArray[ix].toLowerCase().match(new RegExp(_allowedQs)) != null) {
newQueryString += paramArray[ix] + '&';
}
}
_val = _val.replace(/\?.*/, '?' + newQueryString.replace(/\&$/, ''));
}
if (type === 'json') {
piiRegex.push(
{
name: 'DOB',
regex: /(((birth)?date|dob)\=)(19|20)\d\d([\s\.\/\-]|%20)(0?[1-9]|1[012])([\s\.\/\-]|%20)(0?[1-9]|[12][0-9]|3[01])([^\&\s\?\/]*)/ig,
format: 'YYYY-MM-DD'
}, {
name: 'DOB',
regex: /(((birth)?date|dob)\=)(19|20)\d\d([\s\.\/\-]|%20)(0?[1-9]|[12][0-9]|3[01])([\s\.\/\-]|%20)(0?[1-9]|1[012])([^\&\s\?\/]*)/ig,
format: 'YYYY-DD-MM'
}, {
name: 'DOB',
regex: /(((birth)?date|dob)\=)(0?[1-9]|[12][0-9]|3[01])([\s\.\/\-]|%20)(0?[1-9]|1[012])([\s\.\/\-]|%20)(19|20)\d\d([^\&\s\?\/]*)/ig,
format: 'DD-MM-YYYY'
}, {
name: 'DOB',
regex: /(((birth)?date|dob)\=)(0?[1-9]|1[012])([\s\.\/\-]|%20)(0?[1-9]|[12][0-9]|3[01])([\s\.\/\-]|%20)(19|20)\d\d([^\&\s\?\/]*)/ig,
format: 'MM-DD-YYYY'
});
}
else if (type === 'query' || (type === 'json' && /[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/.test(_val))) {
piiRegex.push(
{
name: 'TEL',
regex: /((tel|(tele)?phone|mob(ile)?|cell(ular)?)\=)?((\+\d{1,2}[\s\.\-]?)?\d{3}[\s\.\-]?\d{3}[\s\.\-]?\d{4})([^\&\s\?\/]*)/gi
}, {
name: 'SSN',
regex: /((full)?(([\-\_])?)?ssn\=)?(\d{3}([\s\.\-\+]|%20)?\d{2}([\s\.\-\+]|%20)?\d{4})([^\&\s\?\/]*)/ig
}, {
name: 'DOB',
regex: /(((birth)?date|dob)\=)?(19|20)\d\d([\s\.\/\-]|%20)(0?[1-9]|1[012])([\s\.\/\-]%20)(0?[1-9]|[12][0-9]|3[01])([^\&\s\?\/]*)/ig,
format: 'YYYY-MM-DD'
}, {
name: 'DOB',
regex: /(((birth)?date|dob)\=)?(19|20)\d\d([\s\.\/\-]|%20)(0?[1-9]|[12][0-9]|3[01])([\s\.\/\-]|%20)(0?[1-9]|1[012])([^\&\s\?\/]*)/ig,
format: 'YYYY-DD-MM'
}, {
name: 'DOB',
regex: /(((birth)?date|dob)\=)?(0?[1-9]|[12][0-9]|3[01])([\s\.\/\-]|%20)(0?[1-9]|1[012])([\s\.\/\-]|%20)(19|20)\d\d([^\&\s\?\/]*)/ig,
format: 'DD-MM-YYYY'
}, {
name: 'DOB',
regex: /(((birth)?date|dob)\=)?(0?[1-9]|1[012])([\s\.\/\-]|%20)(0?[1-9]|[12][0-9]|3[01])([\s\.\/\-]%20)(19|20)\d\d([^\&\s\?\/]*)/ig,
format: 'MM-DD-YYYY'
});
}
if ((_param[0].match(new RegExp(checkParams)) != null && _param[0].match(new RegExp(UncheckParams)) != null) || /query|json|default/ig.test(type)) {
piiRegex.forEach(function (pii) {
if (!((/^lat$/i.test(_param[0]) && /^(-?[1-8]?\d(?:\.\d{1,18})?|90(?:\.0{1,18})?)$/.test(_val)) || (/^lon$/i.test(_param[0]) && /^(-?(?:1[0-7]|[1-9])?\d(?:\.\d{1,18})?|180(?:\.0{1,18})?)$/.test(_val)))) {
_val = _val.replace(pii.regex, '[REDACTED_' + pii.name + ']');
}
else { return; }
});
_param[1] = encodeURIComponent(_val.replace(/\?$/, '')) || _val.replace(/\?$/, '');
_hitPayloadParts[i] = _param.join('=');
}
}
_piiRegexReset();
return _hitPayloadParts.join("&");
} catch (error) {
}
}
// End Payload Redactor
/**
* Adds a click handler to track 'Official USA Site' banner clicks.
*/
function _initBannerTracker() {
try {
var acord = document.querySelector('section.usa-banner button.usa-accordion__button');
if (acord) {
acord.addEventListener('click', function (e) {
gas4("official_usa_site_banner_click", { link_text: e.target.textContent.trim(), section: "header" });
});
}
} catch (error) {
}
}
/**
* @param {string} a a URL
* @returns {string} the URL modified by replacing certain query parameters with
* the string "query" before sending it to the Google Analytics server.
* Specifically, it replaces query parameters that match a regular expression.
*/
function _URIHandler(a) {
var b = new RegExp("([?&])(" + oCONFIG.SEARCH_PARAMS + ")(=[^&]+)", "i");
b.test(a) && (a = a.replace(b, "$1query$3"), isSearch = a.match(/([?&])(query\=)([^&#?]*)/i)[3]);
return a;
}
/**
* @param {string} z a URL
* @returns {string} a modified version of the URL with certain query parameters
* removed. It first defines a regular expression that matches the domain name
* of the current page. It then extracts the protocol, domain name, and path
* from the input URL, and constructs a new URL by concatenating the protocol,
* domain name, and path. If the input URL contains any query parameters that
* are not allowed (i.e., not in the _allowedQuerystrings array), it removes
* those query parameters from the new URL.
*/
function _scrubbedURL(z) {
/**
* @param {string} s a URL
* @returns {string} the URL with special characters escaped
*/
RegExp.escape = function (s) { return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); };
var n = new RegExp(`^(https?:\\/\\/(www\\.)?)?${RegExp.escape(document.location.hostname.replace(/^www\\./, ""))}`, "ig"),
t = "",
o = ((n.test(z)) ? z : document.location.protocol + "//" + document.location.hostname + z).toLowerCase(),
a = o.split("?")[0],
r = o.split("?").length > 1
? (o
.split("?")[1]
.split("&")
.forEach(function (o, i) {
_allowedQuerystrings.toString().toLowerCase().indexOf(o.split("=")[0]) > -1 && (t = t + "&" + o);
}),
t.length > 0 ? a + "?" + _piiRedactor(t.substring(1), "query") : a)
: a;
return r;
}
/**
* This function sets the default query parameters, as well as query
* parameters that are specific to the configured agency. The default query
* parameters include those used by Google Analytics, as well as some common
* query parameters used by government websites. The agency-specific query
* parameters are determined by the value of the oCONFIG.AGENCY variable.
* {string[]} an array of allowed querystring parameter strings is set to _allowedQuerystrings variable.
*/
function _setAllowedQS() {
var queries = {
"default": ["utm_id", "utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content", "utm_source_platform", "utm_creative_format", "utm_marketing_tactic", "gbraid", "wbraid", "_gl", "gclid", "dclid", "gclsrc", "affiliate", "dap-dev-env", "v"],
"gsa": ["challenge", "state"],
"dhs": ["appreceiptnum"],
"doc": ["station", "meas", "start", "atlc", "epac", "cpac", "basin", "fdays", "cone", "tswind120", "gm_track", "50wind120", "hwind120", "mltoa34", "swath", "radii", "wsurge", "key_messages", "inundation", "rainqpf", "ero", "gage", "wfo", "spanish_key_messages", "key_messages", "sid", "lan", "office", "pil", "product", "site", "lat", "lon", "issuedby", "wwa"],
"hhs": ["s_cid", "selectedfacets"],
"hud": ["postid"],
"nasa": ["feature", "productid", "selectedfacets"],
"nps": ["gid", "mapid", "site", "webcam", "id"],
"nsf": ["meas", "start", "atlc", "epac", "cpac", "basin", "fdays", "cone", "tswind120", "gm_track", "50wind120", "hwind120", "mltoa34", "swath", "radii", "wsurge", "key_messages", "inundation", "rainqpf", "ero", "gage", "wfo", "spanish_key_messages", "key_messages", "sid"],
"va": ["id"],
"dod": ["p"],
"opm": ["l", "soc", "jt", "j", "rmi", "smin", "hp", "g", "d", "a"]
};
_allowedQuerystrings = queries.default.concat(queries[oCONFIG.AGENCY.toLowerCase()]).concat(oCONFIG.SEARCH_PARAMS.toLowerCase().split("|"));
}
/**
* Creates the Auto tracker, YouTube tracker, HTML Video tracker, and USA Site
* banner tracker.
*/
function _setUpTrackers() {
oCONFIG.AUTOTRACKER ? _initAutoTracker() : "";
oCONFIG.YOUTUBE ? _initYouTubeTracker() : "";
oCONFIG.HTMLVIDEO ? _initHTMLVideoTracker() : "";
_initBannerTracker();
}
/**
* calls _setUpTrackers() if the document is loaded.
* @returns {boolean} true if the document was loaded when this function was
* called.
*/
function _setUpTrackersIfReady() {
return (("interactive" === document.readyState || "complete" === document.readyState) ? (_setUpTrackers(), !0) : !1);
}
_setUpTrackersIfReady() || (document.addEventListener ? document.addEventListener("DOMContentLoaded", _setUpTrackers) : document.attachEvent && document.attachEvent("onreadystatechange", _setUpTrackersIfReady));
})();
