diff --git a/media/js/base/stub-attribution.js b/media/js/base/stub-attribution.js index e094cd215b4..30062095350 100644 --- a/media/js/base/stub-attribution.js +++ b/media/js/base/stub-attribution.js @@ -586,7 +586,115 @@ if (typeof window.Mozilla === 'undefined') { } } }); + + // Wait for GTM to load the GTAG GET API so that we can pass along client and session ID + StubAttribution.waitForGTagAPI(function (data) { + var gtagData = data; + + if (gtagData && StubAttribution.withinAttributionRate()) { + // GA4 - send an event indicating we did or did not get session ID from GTM + if (gtagData.client_id && gtagData.session_id) { + window.dataLayer.push({ + event: 'stub_session_test', + label: 'session found' + }); + } else { + window.dataLayer.push({ + event: 'stub_session_test', + label: 'session not-found' + }); + } + } + }); + } + }; + + /** + * Attempts to retrieve the GA4 client and session ID from the dataLayer + * The GTAG GET API tag will write it to the dataLayer once GTM has loaded it + * https://www.simoahava.com/gtmtips/write-client-id-other-gtag-fields-datalayer/ + */ + StubAttribution.getGtagData = function (dataLayer) { + dataLayer = + typeof dataLayer !== 'undefined' ? dataLayer : window.dataLayer; + + var result = { + client_id: null, + session_id: null + }; + + var _findObject = function (name, obj) { + for (var key in obj) { + if ( + typeof obj[key] === 'object' && + Object.prototype.hasOwnProperty.call(obj, key) + ) { + if (key === name) { + if ( + typeof obj[key].client_id === 'string' && + typeof obj[key].session_id === 'string' + ) { + result = obj[key]; + } else { + return result; + } + break; + } else { + _findObject(name, obj[key]); + } + } + } + }; + + try { + if (typeof dataLayer === 'object') { + dataLayer.forEach(function (layer) { + _findObject('gtagApiResult', layer); + }); + } + } catch (error) { + // GA4 + window.dataLayer.push({ + event: 'stub_session_test', + label: 'error' + }); + } + + return result; + }; + + /** + * A crude check to see if GTAG GET API has been loaded by GTM. Periodically + * attempts to retrieve the client and session ID. + * @param {Function} callback + */ + StubAttribution.waitForGTagAPI = function (callback) { + var timeout; + var pollRetry = 0; + var interval = 100; + var limit = 20; // (100 x 20) / 1000 = 2 seconds + + function _checkGtagAPI() { + clearTimeout(timeout); + var data = StubAttribution.getGtagData(); + + if ( + typeof data === 'object' && + typeof data.client_id === 'string' && + typeof data.session_id === 'string' + ) { + callback(data); + } else { + if (pollRetry <= limit) { + pollRetry += 1; + timeout = window.setTimeout(_checkGtagAPI, interval); + } else { + callback(false); + } + } } + + _checkGtagAPI(); }; window.Mozilla.StubAttribution = StubAttribution; diff --git a/tests/unit/spec/base/stub-attribution.js b/tests/unit/spec/base/stub-attribution.js index 6216f281dc2..0f3edab0578 100644 --- a/tests/unit/spec/base/stub-attribution.js +++ b/tests/unit/spec/base/stub-attribution.js @@ -13,6 +13,7 @@ describe('stub-attribution.js', function () { const GA_CLIENT_ID = '1456954538.1610960957'; + const GA_SESSION_ID = '1668161374'; const STUB_SESSION_ID = '1234567890'; const DLSOURCE = 'mozorg'; @@ -1197,4 +1198,86 @@ describe('stub-attribution.js', function () { expect(/^\d{10}$/.test(result)).toBeTruthy(); }); }); + + describe('getGtagData', function () { + it('should return a valid Google Analytics client ID and session ID', function () { + const dataLayer = [ + { + event: 'page-id-loaded', + pageId: 'Homepage', + 'gtm.uniqueEventId': 1 + }, + { + 'gtm.start': 1678700450438, + event: 'gtm.js', + 'gtm.uniqueEventId': 2 + }, + { + h: { + 0: 'get', + 1: 'G-YBFC8BJZW8', + 2: 'client_id' + } + }, + { + h: { + 0: 'get', + 1: 'G-YBFC8BJZW8', + 2: 'session_id' + } + }, + { + h: { + event: 'gtagApiGet', + gtagApiResult: { + client_id: GA_CLIENT_ID, + session_id: GA_SESSION_ID + }, + 'gtm.uniqueEventId': 11 + } + }, + { + event: 'gtm.dom', + 'gtm.uniqueEventId': 12 + }, + { + event: 'gtm.load', + 'gtm.uniqueEventId': 13 + } + ]; + + expect(Mozilla.StubAttribution.getGtagData(dataLayer)).toEqual({ + client_id: GA_CLIENT_ID, + session_id: GA_SESSION_ID + }); + }); + }); + + it('should return null values if client ID and session ID are not found', function () { + const dataLayer = [ + { + event: 'page-id-loaded', + pageId: 'Homepage', + 'gtm.uniqueEventId': 1 + }, + { + 'gtm.start': 1678700450438, + event: 'gtm.js', + 'gtm.uniqueEventId': 2 + }, + { + event: 'gtm.dom', + 'gtm.uniqueEventId': 12 + }, + { + event: 'gtm.load', + 'gtm.uniqueEventId': 13 + } + ]; + + expect(Mozilla.StubAttribution.getGtagData(dataLayer)).toEqual({ + client_id: null, + session_id: null + }); + }); });