From 488ac476902079c855abb2dbfc0f57f20e1632c9 Mon Sep 17 00:00:00 2001 From: Alex S <49695018+alexs-mparticle@users.noreply.github.com> Date: Tue, 5 Mar 2024 13:13:14 -0500 Subject: [PATCH] feat: Add Consent Support to Doubleclick Kit (#44) --- src/common.js | 24 +- src/consent.js | 110 ++++++ src/event-handler.js | 30 +- src/initialization.js | 41 ++- test/tests.js | 758 +++++++++++++++++++++++++++++++++++++++++- 5 files changed, 954 insertions(+), 9 deletions(-) create mode 100644 src/consent.js diff --git a/src/common.js b/src/common.js index 2772b62..8c8d772 100644 --- a/src/common.js +++ b/src/common.js @@ -1,4 +1,11 @@ -function Common() {} +var ConsentHandler = require('./consent'); +function Common() { + this.consentMappings = []; + this.consentPayloadDefaults = {}; + this.consentPayloadAsString = ''; + + this.consentHandler = new ConsentHandler(this); +} Common.prototype.eventMapping = {}; Common.prototype.customVariablesMappings = {}; @@ -39,6 +46,21 @@ Common.prototype.sendGtag = function(type, properties, isInitialization) { } }; +Common.prototype.sendGtagConsent = function (type, payload) { + function gtag() { + window.dataLayer.push(arguments); + } + gtag('consent', type, payload); +}; + +Common.prototype.cloneObject = function (obj) { + return JSON.parse(JSON.stringify(obj)); +}; + +Common.prototype.isEmpty = function isEmpty(value) { + return value == null || !(Object.keys(value) || value).length; +}; + module.exports = Common; function findValueInMapping(jsHash, mapping) { diff --git a/src/consent.js b/src/consent.js new file mode 100644 index 0000000..38c1c3b --- /dev/null +++ b/src/consent.js @@ -0,0 +1,110 @@ +var googleConsentValues = { + // Server Integration uses 'Unspecified' as a value when the setting is 'not set'. + // However, this is not used by Google's Web SDK. We are referencing it here as a comment + // as a record of this distinction and for posterity. + // If Google ever adds this for web, the line can just be uncommented to support this. + // + // Docs: + // Web: https://developers.google.com/tag-platform/gtagjs/reference#consent + // S2S: https://developers.google.com/google-ads/api/reference/rpc/v15/ConsentStatusEnum.ConsentStatus + // + // Unspecified: 'unspecified', + Denied: 'denied', + Granted: 'granted', +}; + +// Declares list of valid Google Consent Properties +var googleConsentProperties = [ + 'ad_storage', + 'ad_user_data', + 'ad_personalization', + 'analytics_storage', +]; + +function ConsentHandler(common) { + this.common = common || {}; +} + +ConsentHandler.prototype.getUserConsentState = function () { + var userConsentState = {}; + + if (mParticle.Identity && mParticle.Identity.getCurrentUser) { + var currentUser = mParticle.Identity.getCurrentUser(); + + if (!currentUser) { + return {}; + } + + var consentState = + mParticle.Identity.getCurrentUser().getConsentState(); + + if (consentState && consentState.getGDPRConsentState) { + userConsentState = consentState.getGDPRConsentState(); + } + } + + return userConsentState; +}; + +ConsentHandler.prototype.getEventConsentState = function (eventConsentState) { + return eventConsentState && eventConsentState.getGDPRConsentState + ? eventConsentState.getGDPRConsentState() + : {}; +}; + +ConsentHandler.prototype.getConsentSettings = function () { + var consentSettings = {}; + + var googleToMpConsentSettingsMapping = { + ad_user_data: 'defaultAdUserDataConsent', + ad_personalization: 'defaultAdPersonalizationConsent', + ad_storage: 'defaultAdStorageConsentWeb', + analytics_storage: 'defaultAnalyticsStorageConsentWeb', + }; + + var settings = this.common.settings; + + Object.keys(googleToMpConsentSettingsMapping).forEach(function ( + googleConsentKey + ) { + var mpConsentSettingKey = + googleToMpConsentSettingsMapping[googleConsentKey]; + var googleConsentValuesKey = settings[mpConsentSettingKey]; + + if (googleConsentValuesKey && mpConsentSettingKey) { + consentSettings[googleConsentKey] = + googleConsentValues[googleConsentValuesKey]; + } + }); + + return consentSettings; +}; + +ConsentHandler.prototype.generateConsentStatePayloadFromMappings = function ( + consentState, + mappings +) { + if (!mappings) return {}; + + var payload = this.common.cloneObject(this.common.consentPayloadDefaults); + + for (var i = 0; i <= mappings.length - 1; i++) { + var mappingEntry = mappings[i]; + var mpMappedConsentName = mappingEntry.map; + var googleMappedConsentName = mappingEntry.value; + + if ( + consentState[mpMappedConsentName] && + googleConsentProperties.indexOf(googleMappedConsentName) !== -1 + ) { + payload[googleMappedConsentName] = consentState[mpMappedConsentName] + .Consented + ? googleConsentValues.Granted + : googleConsentValues.Denied; + } + } + + return payload; +}; + +module.exports = ConsentHandler; diff --git a/src/event-handler.js b/src/event-handler.js index 749bb18..dee9b50 100644 --- a/src/event-handler.js +++ b/src/event-handler.js @@ -10,7 +10,35 @@ function EventHandler(common) { this.common = common || {}; } -EventHandler.prototype.logEvent = function(event) { +EventHandler.prototype.maybeSendConsentUpdateToGa4 = function (event) { + // If consent payload is empty, + // we never sent an initial default consent state + // so we shouldn't send an update. + if (this.common.consentPayloadAsString && this.common.consentMappings) { + var eventConsentState = this.common.consentHandler.getEventConsentState( + event.ConsentState + ); + + if (!this.common.isEmpty(eventConsentState)) { + var updatedConsentPayload = + this.common.consentHandler.generateConsentStatePayloadFromMappings( + eventConsentState, + this.common.consentMappings + ); + + var eventConsentAsString = JSON.stringify(updatedConsentPayload); + + if (eventConsentAsString !== this.common.consentPayloadAsString) { + this.common.sendGtagConsent('update', updatedConsentPayload); + this.common.consentPayloadAsString = eventConsentAsString; + } + } + } +}; + +EventHandler.prototype.logEvent = function (event) { + this.maybeSendConsentUpdateToGa4(event); + var gtagProperties = {}; this.common.setCustomVariables(event, gtagProperties); var eventMapping = this.common.getEventMapping(event); diff --git a/src/initialization.js b/src/initialization.js index ec9308e..4285a43 100644 --- a/src/initialization.js +++ b/src/initialization.js @@ -4,6 +4,18 @@ var initialization = { initForwarder: function(settings, testMode, userAttributes, userIdentities, processEvent, eventQueue, isInitialized, common) { common.settings = settings; + if (common.settings.consentMappingWeb) { + common.consentMappings = parseSettingsString( + common.settings.consentMappingWeb + ); + } else { + // Ensures consent mappings is an empty array + // for future use + common.consentMappings = []; + common.consentPayloadDefaults = {}; + common.consentPayloadAsString = ''; + } + window.dataLayer = window.dataLayer || []; if (!testMode) { var url = 'https://www.googletagmanager.com/gtag/js?id=' + settings.advertiserId; @@ -30,13 +42,32 @@ var initialization = { } else { initializeGoogleDFP(common, settings, isInitialized); } - } + + common.consentPayloadDefaults = + common.consentHandler.getConsentSettings(); + var initialConsentState = common.consentHandler.getUserConsentState(); + var defaultConsentPayload = + common.consentHandler.generateConsentStatePayloadFromMappings( + initialConsentState, + common.consentMappings + ); + + if (!common.isEmpty(defaultConsentPayload)) { + common.consentPayloadAsString = JSON.stringify( + defaultConsentPayload + ); + + common.sendGtagConsent('default', defaultConsentPayload); + } + }, }; function initializeGoogleDFP(common, settings, isInitialized) { - common.eventMapping = JSON.parse(settings.eventMapping.replace(/"/g, '\"')); + common.eventMapping = parseSettingsString(settings.eventMapping); - common.customVariablesMappings = JSON.parse(settings.customVariables.replace(/"/g, '\"')).reduce(function(a, b) { + common.customVariablesMappings = parseSettingsString( + settings.customVariables + ).reduce(function (a, b) { a[b.map] = b.value; return a; }, {}); @@ -46,4 +77,8 @@ function initializeGoogleDFP(common, settings, isInitialized) { isInitialized = true; } +function parseSettingsString(settingsString) { + return JSON.parse(settingsString.replace(/"/g, '"')); +} + module.exports = initialization; diff --git a/test/tests.js b/test/tests.js index bdbe1a2..7fb1412 100644 --- a/test/tests.js +++ b/test/tests.js @@ -10,7 +10,22 @@ describe('DoubleClick', function () { OptOut: 6, AppStateTransition: 10, Profile: 14, - Commerce: 16 + Commerce: 16, + }, + EventType = { + Unknown: 0, + Navigation: 1, + Location: 2, + Search: 3, + Transaction: 4, + UserContent: 5, + UserPreference: 6, + Social: 7, + Other: 8, + Media: 9, + getName: function () { + return 'blahblah'; + }, }, CommerceEventType = { ProductAddToCart: 10, @@ -45,9 +60,50 @@ describe('DoubleClick', function () { }, reportService = new ReportingService(); -// -------------------DO NOT EDIT ANYTHING ABOVE THIS LINE----------------------- -// -------------------START EDITING BELOW:----------------------- - var DoubleClickMockForwarder = function() { + // -------------------DO NOT EDIT ANYTHING ABOVE THIS LINE----------------------- + // -------------------START EDITING BELOW:----------------------- + // -------------------mParticle stubs - Add any additional stubbing to our methods as needed----------------------- + mParticle.Identity = { + getCurrentUser: function () { + return { + getMPID: function () { + return '123'; + }, + getAllUserAttributes: function () { + return {}; + }, + getUserIdentities: function () { + return { + userIdentities: { + customerid: 'abc', + }, + }; + }, + getConsentState: function () { + return { + getGDPRConsentState: function () { + return { + some_consent: { + Consented: false, + Timestamp: 1, + Document: 'some_consent', + }, + test_consent: { + Consented: false, + Timestamp: 1, + Document: 'test_consent', + }, + }; + }, + }; + }, + }; + }, + }; + + // -------------------DO NOT EDIT ANYTHING ABOVE THIS LINE----------------------- + // -------------------START EDITING BELOW:----------------------- + var DoubleClickMockForwarder = function () { var self = this; this.trackCustomEventCalled = false; @@ -447,4 +503,698 @@ describe('DoubleClick', function () { done(); }); + + describe('Consent State', function () { + var consentMap = [ + { + jsmap: null, + map: 'some_consent', + maptype: 'ConsentPurposes', + value: 'ad_user_data', + }, + { + jsmap: null, + map: 'storage_consent', + maptype: 'ConsentPurposes', + value: 'analytics_storage', + }, + { + jsmap: null, + map: 'other_test_consent', + maptype: 'ConsentPurposes', + value: 'ad_storage', + }, + { + jsmap: null, + map: 'test_consent', + maptype: 'ConsentPurposes', + value: 'ad_personalization', + }, + ]; + + beforeEach(function () { + window.dataLayer = []; + }); + + it('should construct a Default Consent State Payload from Mappings', function (done) { + mParticle.forwarder.init( + { + conversionId: 'AW-123123123', + consentMappingWeb: + '[{"jsmap":null,"map":"some_consent","maptype":"ConsentPurposes","value":"ad_user_data"},{"jsmap":null,"map":"storage_consent","maptype":"ConsentPurposes","value":"analytics_storage"},{"jsmap":null,"map":"other_test_consent","maptype":"ConsentPurposes","value":"ad_storage"},{"jsmap":null,"map":"test_consent","maptype":"ConsentPurposes","value":"ad_personalization"}]', + eventMapping: '[]', + customVariables: '[]', + }, + reportService.cb, + true + ); + + var expectedDataLayer = [ + 'consent', + 'default', + { + ad_user_data: 'denied', + ad_personalization: 'denied', + }, + ]; + + // Initial elements of Data Layer are setup for gtag. + // Consent state should be on the bottom + window.dataLayer.length.should.eql(4); + window.dataLayer[3][0].should.equal('consent'); + window.dataLayer[3][1].should.equal('default'); + window.dataLayer[3][2].should.deepEqual(expectedDataLayer[2]); + done(); + }); + + it('should merge Consent Setting Defaults with User Consent State to construct a Default Consent State', (done) => { + mParticle.forwarder.init( + { + conversionId: 'AW-123123123', + enableGtag: 'True', + consentMappingWeb: JSON.stringify(consentMap), + defaultAdUserDataConsent: 'Granted', // Will be overriden by User Consent State + defaultAdPersonalizationConsent: 'Granted', // Will be overriden by User Consent State + defaultAdStorageConsentWeb: 'Granted', + defaultAnalyticsStorageConsentWeb: 'Granted', + eventMapping: '[]', + customVariables: '[]', + }, + reportService.cb, + true + ); + + var expectedDataLayer = [ + 'consent', + 'default', + { + ad_personalization: 'denied', // From User Consent State + ad_user_data: 'denied', // From User Consent State + ad_storage: 'granted', // From Consent Settings + analytics_storage: 'granted', // From Consent Settings + }, + ]; + + // Initial elements of Data Layer are setup for gtag. + // Consent state should be on the bottom + window.dataLayer.length.should.eql(4); + window.dataLayer[3][0].should.equal('consent'); + window.dataLayer[3][1].should.equal('default'); + window.dataLayer[3][2].should.deepEqual(expectedDataLayer[2]); + + done(); + }); + + it('should ignore Unspecified Consent Settings if NOT explicitely defined in Consent State', (done) => { + mParticle.forwarder.init( + { + conversionId: 'AW-123123123', + enableGtag: 'True', + consentMappingWeb: JSON.stringify(consentMap), + defaultAdUserDataConsent: 'Unspecified', + defaultAdPersonalizationConsent: 'Unspecified', // Will be overriden by User Consent State + defaultAdStorageConsentWeb: 'Unspecified', // Will be overriden by User Consent State + defaultAnalyticsStorageConsentWeb: 'Unspecified', + eventMapping: '[]', + customVariables: '[]', + }, + reportService.cb, + true + ); + + var expectedDataLayer = [ + 'consent', + 'default', + { + ad_personalization: 'denied', // From User Consent State + ad_user_data: 'denied', // From User Consent State + }, + ]; + + // Initial elements of Data Layer are setup for gtag. + // Consent state should be on the bottom + window.dataLayer.length.should.eql(4); + window.dataLayer[3][0].should.equal('consent'); + window.dataLayer[3][1].should.equal('default'); + window.dataLayer[3][2].should.deepEqual(expectedDataLayer[2]); + + done(); + }); + + it('should construct a Consent State Update Payload when consent changes', (done) => { + mParticle.forwarder.init( + { + conversionId: 'AW-123123123', + enableGtag: 'True', + consentMappingWeb: JSON.stringify(consentMap), + eventMapping: '[]', + customVariables: '[]', + }, + reportService.cb, + true + ); + + var expectedDataLayerBefore = [ + 'consent', + 'update', + { + ad_user_data: 'denied', + ad_personalization: 'denied', + }, + ]; + + // Initial elements of Data Layer are setup for gtag. + // Consent state should be on the bottom + window.dataLayer.length.should.eql(4); + window.dataLayer[3][0].should.equal('consent'); + window.dataLayer[3][1].should.equal('default'); + window.dataLayer[3][2].should.deepEqual(expectedDataLayerBefore[2]); + + mParticle.forwarder.process({ + EventName: 'Test Event', + EventDataType: MessageTypes.PageEvent, + EventCategory: mParticle.EventType.Unknown, + EventAttributes: {}, + ConsentState: { + getGDPRConsentState: function () { + return { + some_consent: { + Consented: true, + Timestamp: Date.now(), + Document: 'some_consent', + }, + ignored_consent: { + Consented: false, + Timestamp: Date.now(), + Document: 'ignored_consent', + }, + test_consent: { + Consented: true, + Timestamp: Date.now(), + Document: 'test_consent', + }, + }; + }, + + getCCPAConsentState: function () { + return { + data_sale_opt_out: { + Consented: false, + Timestamp: Date.now(), + Document: 'some_consent', + }, + }; + }, + }, + }); + + var expectedDataLayerAfter = [ + 'consent', + 'update', + { + ad_user_data: 'granted', + ad_personalization: 'granted', + }, + ]; + + // Initial elements of Data Layer are setup for gtag. + // Consent Default is index 3 + // Consent Update is index 4 + window.dataLayer.length.should.eql(5); + window.dataLayer[4][0].should.equal('consent'); + window.dataLayer[4][1].should.equal('update'); + window.dataLayer[4][2].should.deepEqual(expectedDataLayerAfter[2]); + + mParticle.forwarder.process({ + EventName: 'Test Event', + EventDataType: MessageTypes.PageEvent, + EventCategory: mParticle.EventType.Unknown, + EventAttributes: { + showcase: 'something', + test: 'thisoneshouldgetmapped', + mp: 'rock', + }, + CustomFlags: { + 'DoubleClick.Counter': 'per_session', + }, + ConsentState: { + getGDPRConsentState: function () { + return { + some_consent: { + Consented: true, + Timestamp: Date.now(), + Document: 'some_consent', + }, + ignored_consent: { + Consented: false, + Timestamp: Date.now(), + Document: 'ignored_consent', + }, + test_consent: { + Consented: true, + Timestamp: Date.now(), + Document: 'test_consent', + }, + other_test_consent: { + Consented: true, + Timestamp: Date.now(), + Document: 'other_test_consent', + }, + storage_consent: { + Consented: false, + Timestamp: Date.now(), + Document: 'storage_consent', + }, + }; + }, + + getCCPAConsentState: function () { + return { + data_sale_opt_out: { + Consented: false, + Timestamp: Date.now(), + Document: 'data_sale_opt_out', + }, + }; + }, + }, + }); + + var expectedDataLayerFinal = [ + 'consent', + 'update', + { + ad_personalization: 'granted', + ad_storage: 'granted', + ad_user_data: 'granted', + analytics_storage: 'denied', + }, + ]; + + // Initial elements of Data Layer are setup for gtag. + // Consent Default is index 3 + // Consent Update is index 4 + // Consent Update #2 is index 5 + window.dataLayer.length.should.eql(6); + window.dataLayer[5][0].should.equal('consent'); + window.dataLayer[5][1].should.equal('update'); + window.dataLayer[5][2].should.deepEqual(expectedDataLayerFinal[2]); + + done(); + }); + + it('should construct a Consent State Update Payload with Consent Setting Defaults when consent changes', (done) => { + mParticle.forwarder.init( + { + conversionId: 'AW-123123123', + enableGtag: 'True', + consentMappingWeb: JSON.stringify(consentMap), + defaultAdUserDataConsent: 'Granted', // Will be overriden by User Consent State + defaultAdPersonalizationConsent: 'Granted', // Will be overriden by User Consent State + defaultAdStorageConsentWeb: 'Granted', + defaultAnalyticsStorageConsentWeb: 'Granted', + eventMapping: '[]', + customVariables: '[]', + }, + reportService.cb, + true + ); + + var expectedDataLayerBefore = [ + 'consent', + 'update', + { + ad_personalization: 'denied', // From User Consent State + ad_user_data: 'denied', // From User Consent State + ad_storage: 'granted', // From Consent Settings + analytics_storage: 'granted', // From Consent Settings + }, + ]; + + // Initial elements of Data Layer are setup for gtag. + // Consent state should be on the bottom + window.dataLayer.length.should.eql(4); + window.dataLayer[3][0].should.equal('consent'); + window.dataLayer[3][1].should.equal('default'); + window.dataLayer[3][2].should.deepEqual(expectedDataLayerBefore[2]); + + mParticle.forwarder.process({ + EventName: 'Test Event', + EventDataType: MessageTypes.PageEvent, + EventCategory: mParticle.EventType.Unknown, + EventAttributes: {}, + ConsentState: { + getGDPRConsentState: function () { + return { + some_consent: { + Consented: true, + Timestamp: Date.now(), + Document: 'some_consent', + }, + ignored_consent: { + Consented: false, + Timestamp: Date.now(), + Document: 'ignored_consent', + }, + test_consent: { + Consented: true, + Timestamp: Date.now(), + Document: 'test_consent', + }, + }; + }, + + getCCPAConsentState: function () { + return { + data_sale_opt_out: { + Consented: false, + Timestamp: Date.now(), + Document: 'data_sale_opt_out', + }, + }; + }, + }, + }); + + var expectedDataLayerAfter = [ + 'consent', + 'update', + { + ad_personalization: 'granted', // From Event Consent State Change + ad_user_data: 'granted', // From Event Consent State Change + ad_storage: 'granted', // From Consent Settings + analytics_storage: 'granted', // From Consent Settings + }, + ]; + + // Initial elements of Data Layer are setup for gtag. + // Consent Default is index 3 + // Consent Update is index 4 + window.dataLayer.length.should.eql(5); + window.dataLayer[4][0].should.equal('consent'); + window.dataLayer[4][1].should.equal('update'); + window.dataLayer[4][2].should.deepEqual(expectedDataLayerAfter[2]); + + mParticle.forwarder.process({ + EventName: 'Test Event', + EventDataType: MessageTypes.PageEvent, + EventCategory: mParticle.EventType.Unknown, + EventAttributes: { + showcase: 'something', + test: 'thisoneshouldgetmapped', + mp: 'rock', + }, + CustomFlags: { + 'DoubleClick.Counter': 'per_session', + }, + ConsentState: { + getGDPRConsentState: function () { + return { + some_consent: { + Consented: true, + Timestamp: Date.now(), + Document: 'some_consent', + }, + ignored_consent: { + Consented: false, + Timestamp: Date.now(), + Document: 'ignored_consent', + }, + test_consent: { + Consented: true, + Timestamp: Date.now(), + Document: 'test_consent', + }, + other_test_consent: { + Consented: true, + Timestamp: Date.now(), + Document: 'other_test_consent', + }, + storage_consent: { + Consented: false, + Timestamp: Date.now(), + Document: 'storage_consent', + }, + }; + }, + + getCCPAConsentState: function () { + return { + data_sale_opt_out: { + Consented: false, + Timestamp: Date.now(), + Document: 'data_sale_opt_out', + }, + }; + }, + }, + }); + + var expectedDataLayerFinal = [ + 'consent', + 'update', + { + ad_personalization: 'granted', // From Previous Event State Change + ad_storage: 'granted', // From Previous Event State Change + ad_user_data: 'granted', // From Consent Settings + analytics_storage: 'denied', // From FinalEvent Consent State Change + }, + ]; + // Initial elements of Data Layer are setup for gtag. + // Consent Default is index 3 + // Consent Update is index 4 + // Consent Update #2 is index 5 + window.dataLayer.length.should.eql(6); + window.dataLayer[5][0].should.equal('consent'); + window.dataLayer[5][1].should.equal('update'); + window.dataLayer[5][2].should.deepEqual(expectedDataLayerFinal[2]); + done(); + }); + + it('should NOT construct a Consent State Update Payload if consent DOES NOT change', (done) => { + mParticle.forwarder.init( + { + conversionId: 'AW-123123123', + enableGtag: 'True', + consentMappingWeb: JSON.stringify(consentMap), + eventMapping: '[]', + customVariables: '[]', + }, + reportService.cb, + true + ); + + var expectedDataLayerBefore = [ + 'consent', + 'update', + { + ad_user_data: 'denied', + ad_personalization: 'denied', + }, + ]; + + // Initial elements of Data Layer are setup for gtag. + // Consent state should be on the bottom + window.dataLayer.length.should.eql(4); + window.dataLayer[3][0].should.equal('consent'); + window.dataLayer[3][1].should.equal('default'); + window.dataLayer[3][2].should.deepEqual(expectedDataLayerBefore[2]); + + mParticle.forwarder.process({ + EventName: 'Homepage', + EventDataType: MessageTypes.PageEvent, + EventCategory: EventType.Navigation, + EventAttributes: { + showcase: 'something', + test: 'thisoneshouldgetmapped', + mp: 'rock', + }, + ConsentState: { + getGDPRConsentState: function () { + return { + some_consent: { + Consented: false, + Timestamp: Date.now(), + Document: 'some_consent', + }, + test_consent: { + Consented: false, + Timestamp: Date.now(), + Document: 'test_consent', + }, + }; + }, + }, + }); + + // There should be no additional consent update events + window.dataLayer.length.should.eql(4); + window.dataLayer[3][0].should.equal('consent'); + window.dataLayer[3][1].should.equal('default'); + window.dataLayer[3][2].should.deepEqual(expectedDataLayerBefore[2]); + + done(); + }); + + it('should NOT construct any Consent State Payload if consent mappings and settings are undefined', (done) => { + mParticle.forwarder.init( + { + conversionId: 'AW-123123123', + enableGtag: 'True', + eventMapping: '[]', + customVariables: '[]', + }, + reportService.cb, + true + ); + + // Initial elements of Data Layer are setup for gtag. + // Consent state should be on the bottom + window.dataLayer.length.should.eql(3); + + mParticle.forwarder.process({ + EventName: 'Homepage', + EventDataType: MessageTypes.PageEvent, + EventCategory: EventType.Navigation, + EventAttributes: { + showcase: 'something', + test: 'thisoneshouldgetmapped', + mp: 'rock', + }, + ConsentState: { + getGDPRConsentState: function () { + return { + some_consent: { + Consented: true, + Timestamp: Date.now(), + Document: 'some_consent', + }, + ignored_consent: { + Consented: false, + Timestamp: Date.now(), + Document: 'ignored_consent', + }, + test_consent: { + Consented: true, + Timestamp: Date.now(), + Document: 'test_consent', + }, + other_test_consent: { + Consented: true, + Timestamp: Date.now(), + Document: 'other_test_consent', + }, + storage_consent: { + Consented: false, + Timestamp: Date.now(), + Document: 'storage_consent', + }, + }; + }, + + getCCPAConsentState: function () { + return { + data_sale_opt_out: { + Consented: false, + Timestamp: Date.now(), + Document: 'data_sale_opt_out', + }, + }; + }, + }, + }); + + // There should be no additional consent update events + // as the consent state is not mapped to any gtag consent settings + window.dataLayer.length.should.eql(3); + + done(); + }); + + it('should construct Consent State Payloads if consent mappings is undefined but settings defaults are defined', (done) => { + mParticle.forwarder.init( + { + conversionId: 'AW-123123123', + enableGtag: 'True', + defaultAdUserDataConsent: 'Granted', + defaultAdPersonalizationConsent: 'Denied', + defaultAdStorageConsentWeb: 'Granted', + defaultAnalyticsStorageConsentWeb: 'Denied', + eventMapping: '[]', + customVariables: '[]', + }, + reportService.cb, + true + ); + + var expectedDataLayerBefore = [ + 'consent', + 'default', + { + ad_user_data: 'granted', + ad_personalization: 'denied', + ad_storage: 'granted', + analytics_storage: 'denied', + }, + ]; + + // Initial elements of Data Layer are setup for gtag. + // Consent state should be on the bottom + window.dataLayer.length.should.eql(4); + window.dataLayer[3][0].should.equal('consent'); + window.dataLayer[3][1].should.equal('default'); + window.dataLayer[3][2].should.deepEqual(expectedDataLayerBefore[2]); + + mParticle.forwarder.process({ + EventName: 'Homepage', + EventDataType: MessageTypes.PageEvent, + EventCategory: EventType.Navigation, + EventAttributes: { + showcase: 'something', + test: 'thisoneshouldgetmapped', + mp: 'rock', + }, + ConsentState: { + getGDPRConsentState: function () { + return { + some_consent: { + Consented: true, + Timestamp: Date.now(), + Document: 'some_consent', + }, + ignored_consent: { + Consented: false, + Timestamp: Date.now(), + Document: 'ignored_consent', + }, + test_consent: { + Consented: true, + Timestamp: Date.now(), + Document: 'test_consent', + }, + }; + }, + + getCCPAConsentState: function () { + return { + data_sale_opt_out: { + Consented: false, + Timestamp: Date.now(), + Document: 'data_sale_opt_out', + }, + }; + }, + }, + }); + + // There should be no additional consent update events + // as the consent state is not mapped to any gtag consent settings + window.dataLayer.length.should.eql(4); + window.dataLayer[3][0].should.equal('consent'); + window.dataLayer[3][1].should.equal('default'); + window.dataLayer[3][2].should.deepEqual(expectedDataLayerBefore[2]); + + done(); + }); + }); });