From f3912e64c0a30bad3759abec2c61ac90e832c3ad Mon Sep 17 00:00:00 2001 From: Robert Ing Date: Wed, 19 Nov 2025 21:47:27 -0500 Subject: [PATCH 1/5] fix: Ignore falsey identity values --- src/Rokt-Kit.js | 4 +- test/src/tests.js | 300 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 302 insertions(+), 2 deletions(-) diff --git a/src/Rokt-Kit.js b/src/Rokt-Kit.js index f59bcf2..d63f0e6 100644 --- a/src/Rokt-Kit.js +++ b/src/Rokt-Kit.js @@ -185,11 +185,11 @@ var constructor = function () { function replaceOtherIdentityWithEmailsha256(userIdentities) { var newUserIdentities = mergeObjects({}, userIdentities || {}); - if (userIdentities.hasOwnProperty(mappedEmailSha256Key)) { + if (userIdentities[mappedEmailSha256Key]) { newUserIdentities[EMAIL_SHA256_KEY] = userIdentities[mappedEmailSha256Key]; - delete newUserIdentities[mappedEmailSha256Key]; } + delete newUserIdentities[mappedEmailSha256Key]; return newUserIdentities; } diff --git a/test/src/tests.js b/test/src/tests.js index a96375b..e38aeab 100644 --- a/test/src/tests.js +++ b/test/src/tests.js @@ -1692,6 +1692,306 @@ describe('Rokt Forwarder', () => { } ); }); + + it('should NOT map other userIdentities to emailsha256 when the value is an empty string', async () => { + window.mParticle.Rokt.filters = { + userAttributeFilters: [], + filterUserAttributes: function () { + return {}; + }, + filteredUser: { + getMPID: function () { + return '234'; + }, + getUserIdentities: function () { + return { + userIdentities: { + email: 'test@gmail.com', + other: '', // Empty string + }, + }; + }, + }, + }; + + // Set up the createLauncher to properly resolve asynchronously + window.Rokt.createLauncher = async function () { + return Promise.resolve({ + selectPlacements: function (options) { + window.mParticle.Rokt.selectPlacementsOptions = + options; + window.mParticle.Rokt.selectPlacementsCalled = true; + }, + }); + }; + await window.mParticle.forwarder.init( + { + accountId: '123456', + hashedEmailUserIdentityType: 'Other', + }, + reportService.cb, + true, + null, + {} + ); + // Wait for initialization to complete (after launcher is created) + await waitForCondition(() => { + return window.mParticle.forwarder.isInitialized; + }); + debugger + await window.mParticle.forwarder.selectPlacements({ + identifier: 'test-placement', + attributes: { }, + }); + + // Should NOT include emailsha256 since the other identity value was empty + window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( + { + email: 'test@gmail.com', + mpid: '234' + } + ); + }); + + it('should NOT map other userIdentities to emailsha256 when the value is null', async () => { + window.mParticle.Rokt.filters = { + userAttributeFilters: [], + filterUserAttributes: function () { + return {}; + }, + filteredUser: { + getMPID: function () { + return '345'; + }, + getUserIdentities: function () { + return { + userIdentities: { + email: 'test@gmail.com', + other: null, // Null value + }, + }; + }, + }, + }; + + // Set up the createLauncher to properly resolve asynchronously + window.Rokt.createLauncher = async function () { + return Promise.resolve({ + selectPlacements: function (options) { + window.mParticle.Rokt.selectPlacementsOptions = + options; + window.mParticle.Rokt.selectPlacementsCalled = true; + }, + }); + }; + await window.mParticle.forwarder.init( + { + accountId: '123456', + hashedEmailUserIdentityType: 'Other', + }, + reportService.cb, + true, + null, + {} + ); + // Wait for initialization to complete (after launcher is created) + await waitForCondition(() => { + return window.mParticle.forwarder.isInitialized; + }); + + await window.mParticle.forwarder.selectPlacements({ + identifier: 'test-placement', + attributes: {}, + }); + + // Should NOT include emailsha256 since the other identity value was null + window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( + { + email: 'test@gmail.com', + mpid: '345', + } + ); + }); + + it('should NOT map other userIdentities to emailsha256 when the value is undefined', async () => { + window.mParticle.Rokt.filters = { + userAttributeFilters: [], + filterUserAttributes: function () { + return {}; + }, + filteredUser: { + getMPID: function () { + return '456'; + }, + getUserIdentities: function () { + return { + userIdentities: { + email: 'test@gmail.com', + other: undefined, // Undefined value + }, + }; + }, + }, + }; + + // Set up the createLauncher to properly resolve asynchronously + window.Rokt.createLauncher = async function () { + return Promise.resolve({ + selectPlacements: function (options) { + window.mParticle.Rokt.selectPlacementsOptions = + options; + window.mParticle.Rokt.selectPlacementsCalled = true; + }, + }); + }; + await window.mParticle.forwarder.init( + { + accountId: '123456', + hashedEmailUserIdentityType: 'Other', + }, + reportService.cb, + true, + null, + {} + ); + // Wait for initialization to complete (after launcher is created) + await waitForCondition(() => { + return window.mParticle.forwarder.isInitialized; + }); + + await window.mParticle.forwarder.selectPlacements({ + identifier: 'test-placement', + attributes: {}, + }); + + // Should NOT include emailsha256 since the other identity value was undefined + window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( + { + email: 'test@gmail.com', + mpid: '456', + } + ); + }); + + it('should NOT map other userIdentities to emailsha256 when the value is 0', async () => { + window.mParticle.Rokt.filters = { + userAttributeFilters: [], + filterUserAttributes: function () { + return {}; + }, + filteredUser: { + getMPID: function () { + return '567'; + }, + getUserIdentities: function () { + return { + userIdentities: { + email: 'test@gmail.com', + other: 0, // Zero value + }, + }; + }, + }, + }; + + // Set up the createLauncher to properly resolve asynchronously + window.Rokt.createLauncher = async function () { + return Promise.resolve({ + selectPlacements: function (options) { + window.mParticle.Rokt.selectPlacementsOptions = + options; + window.mParticle.Rokt.selectPlacementsCalled = true; + }, + }); + }; + await window.mParticle.forwarder.init( + { + accountId: '123456', + hashedEmailUserIdentityType: 'Other', + }, + reportService.cb, + true, + null, + {} + ); + // Wait for initialization to complete (after launcher is created) + await waitForCondition(() => { + return window.mParticle.forwarder.isInitialized; + }); + + await window.mParticle.forwarder.selectPlacements({ + identifier: 'test-placement', + attributes: {}, + }); + + // Should NOT include emailsha256 since the other identity value was 0 + window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( + { + email: 'test@gmail.com', + mpid: '567', + } + ); + }); + + it('should NOT map other userIdentities to emailsha256 when the value is false', async () => { + window.mParticle.Rokt.filters = { + userAttributeFilters: [], + filterUserAttributes: function () { + return {}; + }, + filteredUser: { + getMPID: function () { + return '678'; + }, + getUserIdentities: function () { + return { + userIdentities: { + email: 'test@gmail.com', + other: false, // False value + }, + }; + }, + }, + }; + + // Set up the createLauncher to properly resolve asynchronously + window.Rokt.createLauncher = async function () { + return Promise.resolve({ + selectPlacements: function (options) { + window.mParticle.Rokt.selectPlacementsOptions = + options; + window.mParticle.Rokt.selectPlacementsCalled = true; + }, + }); + }; + await window.mParticle.forwarder.init( + { + accountId: '123456', + hashedEmailUserIdentityType: 'Other', + }, + reportService.cb, + true, + null, + {} + ); + // Wait for initialization to complete (after launcher is created) + await waitForCondition(() => { + return window.mParticle.forwarder.isInitialized; + }); + + await window.mParticle.forwarder.selectPlacements({ + identifier: 'test-placement', + attributes: {}, + }); + + // Should NOT include emailsha256 since the other identity value was false + window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( + { + email: 'test@gmail.com', + mpid: '678', + } + ); + }); }); }); From 7a7f3a0e28c18556dd52d2777b835fb7a04d317c Mon Sep 17 00:00:00 2001 From: Robert Ing Date: Wed, 19 Nov 2025 21:57:07 -0500 Subject: [PATCH 2/5] remove debugger --- test/src/tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/tests.js b/test/src/tests.js index e38aeab..6965d9c 100644 --- a/test/src/tests.js +++ b/test/src/tests.js @@ -1738,7 +1738,7 @@ describe('Rokt Forwarder', () => { await waitForCondition(() => { return window.mParticle.forwarder.isInitialized; }); - debugger + await window.mParticle.forwarder.selectPlacements({ identifier: 'test-placement', attributes: { }, From ee2746554d3ca84834556c5a3418725de9ecc62d Mon Sep 17 00:00:00 2001 From: Robert Ing Date: Thu, 20 Nov 2025 13:24:09 -0500 Subject: [PATCH 3/5] Update when emails and emailsha256 are included in final selectPlacements calls --- src/Rokt-Kit.js | 42 ++++- test/src/tests.js | 392 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 428 insertions(+), 6 deletions(-) diff --git a/src/Rokt-Kit.js b/src/Rokt-Kit.js index d63f0e6..51192c5 100644 --- a/src/Rokt-Kit.js +++ b/src/Rokt-Kit.js @@ -194,12 +194,42 @@ var constructor = function () { return newUserIdentities; } - function sanitizeEmailIdentities(_data) { - var data = mergeObjects({}, _data || {}); - if (_data.hasOwnProperty(EMAIL_SHA256_KEY)) { + /** + * Sanitizes email identities + * @param {Object} selectPlacementsAttributes - The merged attributes + * @param {Object} filteredUserIdentities - User identities + * @param {Object} filteredAttributes - Developer-provided attributes + * @returns {Object} Sanitized attributes + */ + function sanitizeEmailIdentities( + selectPlacementsAttributes, + filteredUserIdentities, + filteredAttributes + ) { + var data = mergeObjects({}, selectPlacementsAttributes || {}); + + // Remove email if: + // - emailsha256 was explicitly passed by developer AND + // - email came from filteredUserIdentities (not explicitly passed by developer) + if ( + filteredAttributes.hasOwnProperty(EMAIL_SHA256_KEY) && + filteredUserIdentities.hasOwnProperty(EMAIL_KEY) && + !filteredAttributes.hasOwnProperty(EMAIL_KEY) + ) { delete data[EMAIL_KEY]; } + // Remove emailsha256 if: + // - email was explicitly passed by developer AND + // - emailsha256 came from filteredUserIdentities (not explicitly passed by developer) + if ( + filteredAttributes.hasOwnProperty(EMAIL_KEY) && + filteredUserIdentities.hasOwnProperty(EMAIL_SHA256_KEY) && + !filteredAttributes.hasOwnProperty(EMAIL_SHA256_KEY) + ) { + delete data[EMAIL_SHA256_KEY]; + } + return data; } @@ -262,7 +292,11 @@ var constructor = function () { ); var selectPlacementsOptions = mergeObjects(options, { - attributes: sanitizeEmailIdentities(selectPlacementsAttributes), + attributes: sanitizeEmailIdentities( + selectPlacementsAttributes, + filteredUserIdentities, + filteredAttributes + ), }); return self.launcher.selectPlacements(selectPlacementsOptions); diff --git a/test/src/tests.js b/test/src/tests.js index 6965d9c..11f1807 100644 --- a/test/src/tests.js +++ b/test/src/tests.js @@ -1693,6 +1693,394 @@ describe('Rokt Forwarder', () => { ); }); + it('should keep both email and emailsha256 when both are passed through selectPlacements', async () => { + window.mParticle.Rokt.filters = { + userAttributeFilters: [], + filterUserAttributes: function (attributes) { + return attributes; + }, + filteredUser: { + getMPID: function () { + return '789'; + }, + getUserIdentities: function () { + return { + userIdentities: { + email: 'identity-email@example.com', + }, + }; + }, + }, + }; + + // Set up the createLauncher to properly resolve asynchronously + window.Rokt.createLauncher = async function () { + return Promise.resolve({ + selectPlacements: function (options) { + window.mParticle.Rokt.selectPlacementsOptions = + options; + window.mParticle.Rokt.selectPlacementsCalled = true; + }, + }); + }; + + await window.mParticle.forwarder.init( + { + accountId: '123456', + }, + reportService.cb, + true, + null, + {} + ); + + // Wait for initialization to complete (after launcher is created) + await waitForCondition(() => { + return window.mParticle.forwarder.isInitialized; + }); + + await window.mParticle.forwarder.selectPlacements({ + identifier: 'test-placement', + attributes: { + email: 'developer-email@example.com', + emailsha256: 'hashed-email-value', + }, + }); + + // Should keep both email and emailsha256 since developer explicitly passed both + window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( + { + email: 'developer-email@example.com', + emailsha256: 'hashed-email-value', + mpid: '789', + } + ); + }); + + // it('should remove email from userIdentities but keep emailsha256 from selectPlacements', async () => { + it('should only have emailsha256 in kit.selectPlacemenets call if no email is passed, even if email exists on userIdentities', async () => { + window.mParticle.Rokt.filters = { + userAttributeFilters: [], + filterUserAttributes: function (attributes) { + return attributes; + }, + filteredUser: { + getMPID: function () { + return '890'; + }, + getUserIdentities: function () { + return { + userIdentities: { + email: 'identity-email@example.com', + customerid: 'customer123', + }, + }; + }, + }, + }; + + // Set up the createLauncher to properly resolve asynchronously + window.Rokt.createLauncher = async function () { + return Promise.resolve({ + selectPlacements: function (options) { + window.mParticle.Rokt.selectPlacementsOptions = + options; + window.mParticle.Rokt.selectPlacementsCalled = true; + }, + }); + }; + + await window.mParticle.forwarder.init( + { + accountId: '123456', + }, + reportService.cb, + true, + null, + {} + ); + + // Wait for initialization to complete (after launcher is created) + await waitForCondition(() => { + return window.mParticle.forwarder.isInitialized; + }); + + await window.mParticle.forwarder.selectPlacements({ + identifier: 'test-placement', + attributes: { + emailsha256: 'hashed-email-value', + }, + }); + + // Should remove email from userIdentities since emailsha256 exists and email was not explicitly passed + window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( + { + customerid: 'customer123', + emailsha256: 'hashed-email-value', + mpid: '890', + } + ); + }); + + it('should include email in kit.selectPlacements call if not passed, and email exists in userIdentities', async () => { + window.mParticle.Rokt.filters = { + userAttributeFilters: [], + filterUserAttributes: function (attributes) { + return attributes; + }, + filteredUser: { + getMPID: function () { + return '901'; + }, + getUserIdentities: function () { + return { + userIdentities: { + email: 'identity-email@example.com', + customerid: 'customer456', + }, + }; + }, + }, + }; + + // Set up the createLauncher to properly resolve asynchronously + window.Rokt.createLauncher = async function () { + return Promise.resolve({ + selectPlacements: function (options) { + window.mParticle.Rokt.selectPlacementsOptions = + options; + window.mParticle.Rokt.selectPlacementsCalled = true; + }, + }); + }; + + await window.mParticle.forwarder.init( + { + accountId: '123456', + }, + reportService.cb, + true, + null, + {} + ); + + // Wait for initialization to complete (after launcher is created) + await waitForCondition(() => { + return window.mParticle.forwarder.isInitialized; + }); + + await window.mParticle.forwarder.selectPlacements({ + identifier: 'test-placement', + attributes: { + someAttribute: 'someValue', + }, + }); + + // Should keep email from userIdentities since emailsha256 does not exist + window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( + { + email: 'identity-email@example.com', + customerid: 'customer456', + someAttribute: 'someValue', + mpid: '901', + } + ); + }); + // it('should include email in kit.selectPlacements call if no attribuets are passed, and email exists in userIdentities', async () => { + it('should have both email and emailsha256 in kit.selectPlacements call if both exist on userIdentities, and neither is passed through selectPlacements', async () => { + window.mParticle.Rokt.filters = { + userAttributeFilters: [], + filterUserAttributes: function (attributes) { + return attributes; + }, + filteredUser: { + getMPID: function () { + return '912'; + }, + getUserIdentities: function () { + return { + userIdentities: { + email: 'identity-email@example.com', + other: 'hashed-from-other', + customerid: 'customer789', + }, + }; + }, + }, + }; + + // Set up the createLauncher to properly resolve asynchronously + window.Rokt.createLauncher = async function () { + return Promise.resolve({ + selectPlacements: function (options) { + window.mParticle.Rokt.selectPlacementsOptions = + options; + window.mParticle.Rokt.selectPlacementsCalled = true; + }, + }); + }; + + await window.mParticle.forwarder.init( + { + accountId: '123456', + hashedEmailUserIdentityType: 'Other', + }, + reportService.cb, + true, + null, + {} + ); + + // Wait for initialization to complete (after launcher is created) + await waitForCondition(() => { + return window.mParticle.forwarder.isInitialized; + }); + + await window.mParticle.forwarder.selectPlacements({ + identifier: 'test-placement', + attributes: {}, + }); + + // Should keep both email and emailsha256 since emailsha256 was mapped from other identity (not explicitly passed) + window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( + { + email: 'identity-email@example.com', + emailsha256: 'hashed-from-other', + customerid: 'customer789', + mpid: '912', + } + ); + }); + + it('should only have email in kit.selectPlacements call when developer passes only email, even if emailsha256 exists in userIdentities', async () => { + window.mParticle.Rokt.filters = { + userAttributeFilters: [], + filterUserAttributes: function (attributes) { + return attributes; + }, + filteredUser: { + getMPID: function () { + return '923'; + }, + getUserIdentities: function () { + return { + userIdentities: { + email: 'identity-email@example.com', + other: 'hashed-from-other', + customerid: 'customer101', + }, + }; + }, + }, + }; + + // Set up the createLauncher to properly resolve asynchronously + window.Rokt.createLauncher = async function () { + return Promise.resolve({ + selectPlacements: function (options) { + window.mParticle.Rokt.selectPlacementsOptions = + options; + window.mParticle.Rokt.selectPlacementsCalled = true; + }, + }); + }; + + await window.mParticle.forwarder.init( + { + accountId: '123456', + hashedEmailUserIdentityType: 'Other', + }, + reportService.cb, + true, + null, + {} + ); + + // Wait for initialization to complete (after launcher is created) + await waitForCondition(() => { + return window.mParticle.forwarder.isInitialized; + }); + + await window.mParticle.forwarder.selectPlacements({ + identifier: 'test-placement', + attributes: { + email: 'developer-email@example.com', + }, + }); + + // Should only have email since developer explicitly passed email (emailsha256 from userIdentities should be removed) + window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( + { + email: 'developer-email@example.com', + customerid: 'customer101', + mpid: '923', + } + ); + }); + + it('should keep only email from selectPlacements when no emailsha256 exists', async () => { + window.mParticle.Rokt.filters = { + userAttributeFilters: [], + filterUserAttributes: function (attributes) { + return attributes; + }, + filteredUser: { + getMPID: function () { + return '934'; + }, + getUserIdentities: function () { + return { + userIdentities: { + customerid: 'customer202', + }, + }; + }, + }, + }; + + // Set up the createLauncher to properly resolve asynchronously + window.Rokt.createLauncher = async function () { + return Promise.resolve({ + selectPlacements: function (options) { + window.mParticle.Rokt.selectPlacementsOptions = + options; + window.mParticle.Rokt.selectPlacementsCalled = true; + }, + }); + }; + + await window.mParticle.forwarder.init( + { + accountId: '123456', + }, + reportService.cb, + true, + null, + {} + ); + + // Wait for initialization to complete (after launcher is created) + await waitForCondition(() => { + return window.mParticle.forwarder.isInitialized; + }); + + await window.mParticle.forwarder.selectPlacements({ + identifier: 'test-placement', + attributes: { + email: 'developer-email@example.com', + }, + }); + + // Should keep email from selectPlacements since no emailsha256 exists + window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( + { + email: 'developer-email@example.com', + customerid: 'customer202', + mpid: '934', + } + ); + }); + it('should NOT map other userIdentities to emailsha256 when the value is an empty string', async () => { window.mParticle.Rokt.filters = { userAttributeFilters: [], @@ -1741,14 +2129,14 @@ describe('Rokt Forwarder', () => { await window.mParticle.forwarder.selectPlacements({ identifier: 'test-placement', - attributes: { }, + attributes: {}, }); // Should NOT include emailsha256 since the other identity value was empty window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( { email: 'test@gmail.com', - mpid: '234' + mpid: '234', } ); }); From 8cbc451e8e3539ad45591d09eb5f05cc12017007 Mon Sep 17 00:00:00 2001 From: Robert Ing Date: Thu, 20 Nov 2025 13:28:47 -0500 Subject: [PATCH 4/5] add additional tests --- test/src/tests.js | 430 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 430 insertions(+) diff --git a/test/src/tests.js b/test/src/tests.js index 11f1807..7acd36b 100644 --- a/test/src/tests.js +++ b/test/src/tests.js @@ -2081,6 +2081,436 @@ describe('Rokt Forwarder', () => { ); }); + it('should keep only emailsha256 from selectPlacements when no email exists in userIdentities', async () => { + window.mParticle.Rokt.filters = { + userAttributeFilters: [], + filterUserAttributes: function (attributes) { + return attributes; + }, + filteredUser: { + getMPID: function () { + return '945'; + }, + getUserIdentities: function () { + return { + userIdentities: { + customerid: 'customer303', + }, + }; + }, + }, + }; + + window.Rokt.createLauncher = async function () { + return Promise.resolve({ + selectPlacements: function (options) { + window.mParticle.Rokt.selectPlacementsOptions = + options; + window.mParticle.Rokt.selectPlacementsCalled = true; + }, + }); + }; + + await window.mParticle.forwarder.init( + { + accountId: '123456', + }, + reportService.cb, + true, + null, + {} + ); + + await waitForCondition(() => { + return window.mParticle.forwarder.isInitialized; + }); + + await window.mParticle.forwarder.selectPlacements({ + identifier: 'test-placement', + attributes: { + emailsha256: 'developer-hashed-email', + }, + }); + + window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( + { + emailsha256: 'developer-hashed-email', + customerid: 'customer303', + mpid: '945', + } + ); + }); + + it('should keep both when developer passes both email and emailsha256, even if neither exists in userIdentities', async () => { + window.mParticle.Rokt.filters = { + userAttributeFilters: [], + filterUserAttributes: function (attributes) { + return attributes; + }, + filteredUser: { + getMPID: function () { + return '956'; + }, + getUserIdentities: function () { + return { + userIdentities: { + customerid: 'customer404', + }, + }; + }, + }, + }; + + window.Rokt.createLauncher = async function () { + return Promise.resolve({ + selectPlacements: function (options) { + window.mParticle.Rokt.selectPlacementsOptions = + options; + window.mParticle.Rokt.selectPlacementsCalled = true; + }, + }); + }; + + await window.mParticle.forwarder.init( + { + accountId: '123456', + }, + reportService.cb, + true, + null, + {} + ); + + await waitForCondition(() => { + return window.mParticle.forwarder.isInitialized; + }); + + await window.mParticle.forwarder.selectPlacements({ + identifier: 'test-placement', + attributes: { + email: 'developer-email@example.com', + emailsha256: 'developer-hashed-email', + }, + }); + + window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( + { + email: 'developer-email@example.com', + emailsha256: 'developer-hashed-email', + customerid: 'customer404', + mpid: '956', + } + ); + }); + + it('should have nothing when neither email nor emailsha256 exist anywhere', async () => { + window.mParticle.Rokt.filters = { + userAttributeFilters: [], + filterUserAttributes: function (attributes) { + return attributes; + }, + filteredUser: { + getMPID: function () { + return '967'; + }, + getUserIdentities: function () { + return { + userIdentities: { + customerid: 'customer505', + }, + }; + }, + }, + }; + + window.Rokt.createLauncher = async function () { + return Promise.resolve({ + selectPlacements: function (options) { + window.mParticle.Rokt.selectPlacementsOptions = + options; + window.mParticle.Rokt.selectPlacementsCalled = true; + }, + }); + }; + + await window.mParticle.forwarder.init( + { + accountId: '123456', + }, + reportService.cb, + true, + null, + {} + ); + + await waitForCondition(() => { + return window.mParticle.forwarder.isInitialized; + }); + + await window.mParticle.forwarder.selectPlacements({ + identifier: 'test-placement', + attributes: {}, + }); + + window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( + { + customerid: 'customer505', + mpid: '967', + } + ); + }); + + it('should keep only emailsha256 from userIdentities when email is not in userIdentities and developer passes nothing', async () => { + window.mParticle.Rokt.filters = { + userAttributeFilters: [], + filterUserAttributes: function (attributes) { + return attributes; + }, + filteredUser: { + getMPID: function () { + return '978'; + }, + getUserIdentities: function () { + return { + userIdentities: { + other: 'hashed-from-useridentities', + customerid: 'customer606', + }, + }; + }, + }, + }; + + window.Rokt.createLauncher = async function () { + return Promise.resolve({ + selectPlacements: function (options) { + window.mParticle.Rokt.selectPlacementsOptions = + options; + window.mParticle.Rokt.selectPlacementsCalled = true; + }, + }); + }; + + await window.mParticle.forwarder.init( + { + accountId: '123456', + hashedEmailUserIdentityType: 'Other', + }, + reportService.cb, + true, + null, + {} + ); + + await waitForCondition(() => { + return window.mParticle.forwarder.isInitialized; + }); + + await window.mParticle.forwarder.selectPlacements({ + identifier: 'test-placement', + attributes: {}, + }); + + window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( + { + emailsha256: 'hashed-from-useridentities', + customerid: 'customer606', + mpid: '978', + } + ); + }); + + it('should keep only developer-passed emailsha256 when email exists in userIdentities but emailsha256 does not', async () => { + window.mParticle.Rokt.filters = { + userAttributeFilters: [], + filterUserAttributes: function (attributes) { + return attributes; + }, + filteredUser: { + getMPID: function () { + return '989'; + }, + getUserIdentities: function () { + return { + userIdentities: { + email: 'useridentity-email@example.com', + customerid: 'customer707', + }, + }; + }, + }, + }; + + window.Rokt.createLauncher = async function () { + return Promise.resolve({ + selectPlacements: function (options) { + window.mParticle.Rokt.selectPlacementsOptions = + options; + window.mParticle.Rokt.selectPlacementsCalled = true; + }, + }); + }; + + await window.mParticle.forwarder.init( + { + accountId: '123456', + }, + reportService.cb, + true, + null, + {} + ); + + await waitForCondition(() => { + return window.mParticle.forwarder.isInitialized; + }); + + await window.mParticle.forwarder.selectPlacements({ + identifier: 'test-placement', + attributes: { + emailsha256: 'developer-hashed-email', + }, + }); + + // Should remove email from userIdentities since developer passed emailsha256 + window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( + { + emailsha256: 'developer-hashed-email', + customerid: 'customer707', + mpid: '989', + } + ); + }); + + it('should keep only developer-passed email when emailsha256 exists in userIdentities but email does not', async () => { + window.mParticle.Rokt.filters = { + userAttributeFilters: [], + filterUserAttributes: function (attributes) { + return attributes; + }, + filteredUser: { + getMPID: function () { + return '991'; + }, + getUserIdentities: function () { + return { + userIdentities: { + other: 'hashed-from-useridentities', + customerid: 'customer808', + }, + }; + }, + }, + }; + + window.Rokt.createLauncher = async function () { + return Promise.resolve({ + selectPlacements: function (options) { + window.mParticle.Rokt.selectPlacementsOptions = + options; + window.mParticle.Rokt.selectPlacementsCalled = true; + }, + }); + }; + + await window.mParticle.forwarder.init( + { + accountId: '123456', + hashedEmailUserIdentityType: 'Other', + }, + reportService.cb, + true, + null, + {} + ); + + await waitForCondition(() => { + return window.mParticle.forwarder.isInitialized; + }); + + await window.mParticle.forwarder.selectPlacements({ + identifier: 'test-placement', + attributes: { + email: 'developer-email@example.com', + }, + }); + + // Should remove emailsha256 from userIdentities since developer passed email + window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( + { + email: 'developer-email@example.com', + customerid: 'customer808', + mpid: '991', + } + ); + }); + + it('should keep both when developer passes both and both exist in userIdentities', async () => { + window.mParticle.Rokt.filters = { + userAttributeFilters: [], + filterUserAttributes: function (attributes) { + return attributes; + }, + filteredUser: { + getMPID: function () { + return '992'; + }, + getUserIdentities: function () { + return { + userIdentities: { + email: 'useridentity-email@example.com', + other: 'hashed-from-useridentities', + customerid: 'customer909', + }, + }; + }, + }, + }; + + window.Rokt.createLauncher = async function () { + return Promise.resolve({ + selectPlacements: function (options) { + window.mParticle.Rokt.selectPlacementsOptions = + options; + window.mParticle.Rokt.selectPlacementsCalled = true; + }, + }); + }; + + await window.mParticle.forwarder.init( + { + accountId: '123456', + hashedEmailUserIdentityType: 'Other', + }, + reportService.cb, + true, + null, + {} + ); + + await waitForCondition(() => { + return window.mParticle.forwarder.isInitialized; + }); + + await window.mParticle.forwarder.selectPlacements({ + identifier: 'test-placement', + attributes: { + email: 'developer-email@example.com', + emailsha256: 'developer-hashed-email', + }, + }); + + // Should use developer-passed values for both + window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( + { + email: 'developer-email@example.com', + emailsha256: 'developer-hashed-email', + customerid: 'customer909', + mpid: '992', + } + ); + }); + it('should NOT map other userIdentities to emailsha256 when the value is an empty string', async () => { window.mParticle.Rokt.filters = { userAttributeFilters: [], From 3077b1d845bc5e27b16b712f5ccc30b579099ef8 Mon Sep 17 00:00:00 2001 From: Robert Ing Date: Thu, 20 Nov 2025 16:43:01 -0500 Subject: [PATCH 5/5] Remove sanitizeEmail, pass email and emailsha256 to selectPlacements regardless of where they are provided --- src/Rokt-Kit.js | 46 +------ test/src/tests.js | 324 +--------------------------------------------- 2 files changed, 5 insertions(+), 365 deletions(-) diff --git a/src/Rokt-Kit.js b/src/Rokt-Kit.js index 51192c5..1e11709 100644 --- a/src/Rokt-Kit.js +++ b/src/Rokt-Kit.js @@ -23,7 +23,6 @@ var constructor = function () { }; var EMAIL_SHA256_KEY = 'emailsha256'; - var EMAIL_KEY = 'email'; // Dynamic identity type for Rokt's emailsha256 identity value which MP doesn't natively support - will be set during initialization var mappedEmailSha256Key; @@ -194,45 +193,6 @@ var constructor = function () { return newUserIdentities; } - /** - * Sanitizes email identities - * @param {Object} selectPlacementsAttributes - The merged attributes - * @param {Object} filteredUserIdentities - User identities - * @param {Object} filteredAttributes - Developer-provided attributes - * @returns {Object} Sanitized attributes - */ - function sanitizeEmailIdentities( - selectPlacementsAttributes, - filteredUserIdentities, - filteredAttributes - ) { - var data = mergeObjects({}, selectPlacementsAttributes || {}); - - // Remove email if: - // - emailsha256 was explicitly passed by developer AND - // - email came from filteredUserIdentities (not explicitly passed by developer) - if ( - filteredAttributes.hasOwnProperty(EMAIL_SHA256_KEY) && - filteredUserIdentities.hasOwnProperty(EMAIL_KEY) && - !filteredAttributes.hasOwnProperty(EMAIL_KEY) - ) { - delete data[EMAIL_KEY]; - } - - // Remove emailsha256 if: - // - email was explicitly passed by developer AND - // - emailsha256 came from filteredUserIdentities (not explicitly passed by developer) - if ( - filteredAttributes.hasOwnProperty(EMAIL_KEY) && - filteredUserIdentities.hasOwnProperty(EMAIL_SHA256_KEY) && - !filteredAttributes.hasOwnProperty(EMAIL_SHA256_KEY) - ) { - delete data[EMAIL_SHA256_KEY]; - } - - return data; - } - /** * Selects placements for Rokt Web SDK with merged attributes, filters, and experimentation options * @see https://docs.rokt.com/developers/integration-guides/web/library/select-placements-options/ @@ -292,11 +252,7 @@ var constructor = function () { ); var selectPlacementsOptions = mergeObjects(options, { - attributes: sanitizeEmailIdentities( - selectPlacementsAttributes, - filteredUserIdentities, - filteredAttributes - ), + attributes: selectPlacementsAttributes, }); return self.launcher.selectPlacements(selectPlacementsOptions); diff --git a/test/src/tests.js b/test/src/tests.js index 7acd36b..0b75846 100644 --- a/test/src/tests.js +++ b/test/src/tests.js @@ -1632,7 +1632,7 @@ describe('Rokt Forwarder', () => { ); }); - it('should remove email identity if emailsha256 is passed through selectPlacements', async () => { + it('should keep both email and emailsha256 when emailsha256 is passed through selectPlacements and email exists in userIdentities', async () => { window.mParticle.Rokt.filters = { userAttributeFilters: [], filterUserAttributes: function (attributes) { @@ -1685,8 +1685,10 @@ describe('Rokt Forwarder', () => { }, }); + // Should keep both email from userIdentities and emailsha256 from selectPlacements window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( { + email: 'test@example.com', emailsha256: 'hashed-email-value', mpid: '456', } @@ -1757,71 +1759,6 @@ describe('Rokt Forwarder', () => { ); }); - // it('should remove email from userIdentities but keep emailsha256 from selectPlacements', async () => { - it('should only have emailsha256 in kit.selectPlacemenets call if no email is passed, even if email exists on userIdentities', async () => { - window.mParticle.Rokt.filters = { - userAttributeFilters: [], - filterUserAttributes: function (attributes) { - return attributes; - }, - filteredUser: { - getMPID: function () { - return '890'; - }, - getUserIdentities: function () { - return { - userIdentities: { - email: 'identity-email@example.com', - customerid: 'customer123', - }, - }; - }, - }, - }; - - // Set up the createLauncher to properly resolve asynchronously - window.Rokt.createLauncher = async function () { - return Promise.resolve({ - selectPlacements: function (options) { - window.mParticle.Rokt.selectPlacementsOptions = - options; - window.mParticle.Rokt.selectPlacementsCalled = true; - }, - }); - }; - - await window.mParticle.forwarder.init( - { - accountId: '123456', - }, - reportService.cb, - true, - null, - {} - ); - - // Wait for initialization to complete (after launcher is created) - await waitForCondition(() => { - return window.mParticle.forwarder.isInitialized; - }); - - await window.mParticle.forwarder.selectPlacements({ - identifier: 'test-placement', - attributes: { - emailsha256: 'hashed-email-value', - }, - }); - - // Should remove email from userIdentities since emailsha256 exists and email was not explicitly passed - window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( - { - customerid: 'customer123', - emailsha256: 'hashed-email-value', - mpid: '890', - } - ); - }); - it('should include email in kit.selectPlacements call if not passed, and email exists in userIdentities', async () => { window.mParticle.Rokt.filters = { userAttributeFilters: [], @@ -1886,7 +1823,7 @@ describe('Rokt Forwarder', () => { } ); }); - // it('should include email in kit.selectPlacements call if no attribuets are passed, and email exists in userIdentities', async () => { + it('should have both email and emailsha256 in kit.selectPlacements call if both exist on userIdentities, and neither is passed through selectPlacements', async () => { window.mParticle.Rokt.filters = { userAttributeFilters: [], @@ -1952,72 +1889,6 @@ describe('Rokt Forwarder', () => { ); }); - it('should only have email in kit.selectPlacements call when developer passes only email, even if emailsha256 exists in userIdentities', async () => { - window.mParticle.Rokt.filters = { - userAttributeFilters: [], - filterUserAttributes: function (attributes) { - return attributes; - }, - filteredUser: { - getMPID: function () { - return '923'; - }, - getUserIdentities: function () { - return { - userIdentities: { - email: 'identity-email@example.com', - other: 'hashed-from-other', - customerid: 'customer101', - }, - }; - }, - }, - }; - - // Set up the createLauncher to properly resolve asynchronously - window.Rokt.createLauncher = async function () { - return Promise.resolve({ - selectPlacements: function (options) { - window.mParticle.Rokt.selectPlacementsOptions = - options; - window.mParticle.Rokt.selectPlacementsCalled = true; - }, - }); - }; - - await window.mParticle.forwarder.init( - { - accountId: '123456', - hashedEmailUserIdentityType: 'Other', - }, - reportService.cb, - true, - null, - {} - ); - - // Wait for initialization to complete (after launcher is created) - await waitForCondition(() => { - return window.mParticle.forwarder.isInitialized; - }); - - await window.mParticle.forwarder.selectPlacements({ - identifier: 'test-placement', - attributes: { - email: 'developer-email@example.com', - }, - }); - - // Should only have email since developer explicitly passed email (emailsha256 from userIdentities should be removed) - window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( - { - email: 'developer-email@example.com', - customerid: 'customer101', - mpid: '923', - } - ); - }); - it('should keep only email from selectPlacements when no emailsha256 exists', async () => { window.mParticle.Rokt.filters = { userAttributeFilters: [], @@ -2141,68 +2012,6 @@ describe('Rokt Forwarder', () => { ); }); - it('should keep both when developer passes both email and emailsha256, even if neither exists in userIdentities', async () => { - window.mParticle.Rokt.filters = { - userAttributeFilters: [], - filterUserAttributes: function (attributes) { - return attributes; - }, - filteredUser: { - getMPID: function () { - return '956'; - }, - getUserIdentities: function () { - return { - userIdentities: { - customerid: 'customer404', - }, - }; - }, - }, - }; - - window.Rokt.createLauncher = async function () { - return Promise.resolve({ - selectPlacements: function (options) { - window.mParticle.Rokt.selectPlacementsOptions = - options; - window.mParticle.Rokt.selectPlacementsCalled = true; - }, - }); - }; - - await window.mParticle.forwarder.init( - { - accountId: '123456', - }, - reportService.cb, - true, - null, - {} - ); - - await waitForCondition(() => { - return window.mParticle.forwarder.isInitialized; - }); - - await window.mParticle.forwarder.selectPlacements({ - identifier: 'test-placement', - attributes: { - email: 'developer-email@example.com', - emailsha256: 'developer-hashed-email', - }, - }); - - window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( - { - email: 'developer-email@example.com', - emailsha256: 'developer-hashed-email', - customerid: 'customer404', - mpid: '956', - } - ); - }); - it('should have nothing when neither email nor emailsha256 exist anywhere', async () => { window.mParticle.Rokt.filters = { userAttributeFilters: [], @@ -2320,131 +2129,6 @@ describe('Rokt Forwarder', () => { ); }); - it('should keep only developer-passed emailsha256 when email exists in userIdentities but emailsha256 does not', async () => { - window.mParticle.Rokt.filters = { - userAttributeFilters: [], - filterUserAttributes: function (attributes) { - return attributes; - }, - filteredUser: { - getMPID: function () { - return '989'; - }, - getUserIdentities: function () { - return { - userIdentities: { - email: 'useridentity-email@example.com', - customerid: 'customer707', - }, - }; - }, - }, - }; - - window.Rokt.createLauncher = async function () { - return Promise.resolve({ - selectPlacements: function (options) { - window.mParticle.Rokt.selectPlacementsOptions = - options; - window.mParticle.Rokt.selectPlacementsCalled = true; - }, - }); - }; - - await window.mParticle.forwarder.init( - { - accountId: '123456', - }, - reportService.cb, - true, - null, - {} - ); - - await waitForCondition(() => { - return window.mParticle.forwarder.isInitialized; - }); - - await window.mParticle.forwarder.selectPlacements({ - identifier: 'test-placement', - attributes: { - emailsha256: 'developer-hashed-email', - }, - }); - - // Should remove email from userIdentities since developer passed emailsha256 - window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( - { - emailsha256: 'developer-hashed-email', - customerid: 'customer707', - mpid: '989', - } - ); - }); - - it('should keep only developer-passed email when emailsha256 exists in userIdentities but email does not', async () => { - window.mParticle.Rokt.filters = { - userAttributeFilters: [], - filterUserAttributes: function (attributes) { - return attributes; - }, - filteredUser: { - getMPID: function () { - return '991'; - }, - getUserIdentities: function () { - return { - userIdentities: { - other: 'hashed-from-useridentities', - customerid: 'customer808', - }, - }; - }, - }, - }; - - window.Rokt.createLauncher = async function () { - return Promise.resolve({ - selectPlacements: function (options) { - window.mParticle.Rokt.selectPlacementsOptions = - options; - window.mParticle.Rokt.selectPlacementsCalled = true; - }, - }); - }; - - await window.mParticle.forwarder.init( - { - accountId: '123456', - hashedEmailUserIdentityType: 'Other', - }, - reportService.cb, - true, - null, - {} - ); - - await waitForCondition(() => { - return window.mParticle.forwarder.isInitialized; - }); - - await window.mParticle.forwarder.selectPlacements({ - identifier: 'test-placement', - attributes: { - email: 'developer-email@example.com', - }, - }); - - // Should remove emailsha256 from userIdentities since developer passed email - window.Rokt.selectPlacementsOptions.attributes.should.deepEqual( - { - email: 'developer-email@example.com', - customerid: 'customer808', - mpid: '991', - } - ); - }); - it('should keep both when developer passes both and both exist in userIdentities', async () => { window.mParticle.Rokt.filters = { userAttributeFilters: [],