From eddcbd43d7cd5cfc64e4a2746997e960a701aed8 Mon Sep 17 00:00:00 2001 From: Matt Bodle Date: Tue, 24 Mar 2026 22:18:32 -0400 Subject: [PATCH 1/5] feat: enrich event stream with Kit userAttributes Events sent to Rokt's __event_stream__ now include the Kit's current userAttributes as the UserAttributes property. This ensures user attributes are always fresh (sourced from the Kit's own state rather than potentially stale per-event data) and present even for queued events that were created before user identification. Co-Authored-By: Claude Opus 4.6 (1M context) --- dist/Rokt-Kit.common.js | 5 +- dist/Rokt-Kit.iife.js | 5 +- src/Rokt-Kit.js | 5 +- test/src/tests.js | 137 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 148 insertions(+), 4 deletions(-) diff --git a/dist/Rokt-Kit.common.js b/dist/Rokt-Kit.common.js index 04f0178..4665772 100644 --- a/dist/Rokt-Kit.common.js +++ b/dist/Rokt-Kit.common.js @@ -525,7 +525,10 @@ var constructor = function () { function _sendEventStream(event) { if (window.Rokt && typeof window.Rokt.__event_stream__ === 'function') { - window.Rokt.__event_stream__(event); + var enrichedEvent = mergeObjects({}, event, { + UserAttributes: self.userAttributes, + }); + window.Rokt.__event_stream__(enrichedEvent); } } diff --git a/dist/Rokt-Kit.iife.js b/dist/Rokt-Kit.iife.js index d357be0..cd8de26 100644 --- a/dist/Rokt-Kit.iife.js +++ b/dist/Rokt-Kit.iife.js @@ -524,7 +524,10 @@ var RoktKit = (function (exports) { function _sendEventStream(event) { if (window.Rokt && typeof window.Rokt.__event_stream__ === 'function') { - window.Rokt.__event_stream__(event); + var enrichedEvent = mergeObjects({}, event, { + UserAttributes: self.userAttributes, + }); + window.Rokt.__event_stream__(enrichedEvent); } } diff --git a/src/Rokt-Kit.js b/src/Rokt-Kit.js index b1574fd..f14541b 100644 --- a/src/Rokt-Kit.js +++ b/src/Rokt-Kit.js @@ -521,7 +521,10 @@ var constructor = function () { function _sendEventStream(event) { if (window.Rokt && typeof window.Rokt.__event_stream__ === 'function') { - window.Rokt.__event_stream__(event); + var enrichedEvent = mergeObjects({}, event, { + UserAttributes: self.userAttributes, + }); + window.Rokt.__event_stream__(enrichedEvent); } } diff --git a/test/src/tests.js b/test/src/tests.js index 2d05873..158ce1c 100644 --- a/test/src/tests.js +++ b/test/src/tests.js @@ -4393,7 +4393,10 @@ describe('Rokt Forwarder', () => { window.mParticle.forwarder.process(testEvent); receivedEvents.length.should.equal(1); - receivedEvents[0].should.deepEqual(testEvent); + receivedEvents[0].EventName.should.equal('Test Event'); + receivedEvents[0].EventCategory.should.equal(EventType.Other); + receivedEvents[0].EventDataType.should.equal(MessageType.PageEvent); + receivedEvents[0].UserAttributes.should.deepEqual({}); }); it('should not throw when window.Rokt.__event_stream__ is not defined', async () => { @@ -4579,6 +4582,138 @@ describe('Rokt Forwarder', () => { 'foo-mapped-flag': true, }); }); + + it('should enrich event with Kit userAttributes before sending to event stream', async () => { + var receivedEvents = []; + window.Rokt.__event_stream__ = function (event) { + receivedEvents.push(event); + }; + + await window.mParticle.forwarder.init( + { accountId: '123456' }, + reportService.cb, + true, + null, + {} + ); + + await waitForCondition(() => window.mParticle.Rokt.attachKitCalled); + + window.mParticle.forwarder.userAttributes = { + firstName: 'John', + lastName: 'Doe', + }; + + window.mParticle.forwarder.process({ + EventName: 'Test Event', + EventCategory: EventType.Other, + EventDataType: MessageType.PageEvent, + }); + + receivedEvents.length.should.equal(1); + receivedEvents[0].UserAttributes.should.deepEqual({ + firstName: 'John', + lastName: 'Doe', + }); + }); + + it('should override event UserAttributes with Kit userAttributes', async () => { + var receivedEvents = []; + window.Rokt.__event_stream__ = function (event) { + receivedEvents.push(event); + }; + + await window.mParticle.forwarder.init( + { accountId: '123456' }, + reportService.cb, + true, + null, + {} + ); + + await waitForCondition(() => window.mParticle.Rokt.attachKitCalled); + + window.mParticle.forwarder.userAttributes = { + firstName: 'Jane', + }; + + window.mParticle.forwarder.process({ + EventName: 'Test Event', + EventCategory: EventType.Other, + EventDataType: MessageType.PageEvent, + UserAttributes: { + firstName: 'Stale', + obsoleteAttr: 'should-not-appear', + }, + }); + + receivedEvents.length.should.equal(1); + receivedEvents[0].UserAttributes.should.deepEqual({ + firstName: 'Jane', + }); + }); + + it('should not mutate the original event when enriching with userAttributes', async () => { + var receivedEvents = []; + window.Rokt.__event_stream__ = function (event) { + receivedEvents.push(event); + }; + + await window.mParticle.forwarder.init( + { accountId: '123456' }, + reportService.cb, + true, + null, + {} + ); + + await waitForCondition(() => window.mParticle.Rokt.attachKitCalled); + + window.mParticle.forwarder.userAttributes = { + firstName: 'John', + }; + + var originalEvent = { + EventName: 'Test Event', + EventCategory: EventType.Other, + EventDataType: MessageType.PageEvent, + }; + + window.mParticle.forwarder.process(originalEvent); + + should.not.exist(originalEvent.UserAttributes); + receivedEvents[0].UserAttributes.should.deepEqual({ + firstName: 'John', + }); + }); + + it('should send empty UserAttributes when Kit has no userAttributes', async () => { + var receivedEvents = []; + window.Rokt.__event_stream__ = function (event) { + receivedEvents.push(event); + }; + + await window.mParticle.forwarder.init( + { accountId: '123456' }, + reportService.cb, + true, + null, + {} + ); + + await waitForCondition(() => window.mParticle.Rokt.attachKitCalled); + + window.mParticle.forwarder.userAttributes = {}; + + window.mParticle.forwarder.process({ + EventName: 'Test Event', + EventCategory: EventType.Other, + EventDataType: MessageType.PageEvent, + }); + + receivedEvents.length.should.equal(1); + receivedEvents[0].UserAttributes.should.deepEqual({}); + }); }); describe('#parseSettingsString', () => { From bd92fc5bd06be5d28d4cb4e41e026aa93692aa48 Mon Sep 17 00:00:00 2001 From: James Newman Date: Wed, 25 Mar 2026 09:55:57 -0400 Subject: [PATCH 2/5] Revert changes to dist files --- dist/Rokt-Kit.common.js | 5 +---- dist/Rokt-Kit.iife.js | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/dist/Rokt-Kit.common.js b/dist/Rokt-Kit.common.js index 4665772..04f0178 100644 --- a/dist/Rokt-Kit.common.js +++ b/dist/Rokt-Kit.common.js @@ -525,10 +525,7 @@ var constructor = function () { function _sendEventStream(event) { if (window.Rokt && typeof window.Rokt.__event_stream__ === 'function') { - var enrichedEvent = mergeObjects({}, event, { - UserAttributes: self.userAttributes, - }); - window.Rokt.__event_stream__(enrichedEvent); + window.Rokt.__event_stream__(event); } } diff --git a/dist/Rokt-Kit.iife.js b/dist/Rokt-Kit.iife.js index cd8de26..d357be0 100644 --- a/dist/Rokt-Kit.iife.js +++ b/dist/Rokt-Kit.iife.js @@ -524,10 +524,7 @@ var RoktKit = (function (exports) { function _sendEventStream(event) { if (window.Rokt && typeof window.Rokt.__event_stream__ === 'function') { - var enrichedEvent = mergeObjects({}, event, { - UserAttributes: self.userAttributes, - }); - window.Rokt.__event_stream__(enrichedEvent); + window.Rokt.__event_stream__(event); } } From 2cae9a7f9c0447e670d67b4b695ed4b43bce2d46 Mon Sep 17 00:00:00 2001 From: James Newman Date: Wed, 25 Mar 2026 10:02:03 -0400 Subject: [PATCH 3/5] Update tests.js --- 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 158ce1c..efa8567 100644 --- a/test/src/tests.js +++ b/test/src/tests.js @@ -4681,7 +4681,7 @@ describe('Rokt Forwarder', () => { window.mParticle.forwarder.process(originalEvent); - should.not.exist(originalEvent.UserAttributes); + originalEvent.should.not.have.property('UserAttributes'); receivedEvents[0].UserAttributes.should.deepEqual({ firstName: 'John', }); From 0dbe2002dbf68ffe876b3f096046551aa35b135b Mon Sep 17 00:00:00 2001 From: Alexander Sapountzis Date: Wed, 25 Mar 2026 10:16:31 -0400 Subject: [PATCH 4/5] fix: revert dist files from merge commit Dist files are generated by CI, not committed manually. --- dist/Rokt-Kit.common.js | 87 +++-------------------------------------- dist/Rokt-Kit.iife.js | 87 +++-------------------------------------- 2 files changed, 12 insertions(+), 162 deletions(-) diff --git a/dist/Rokt-Kit.common.js b/dist/Rokt-Kit.common.js index 01ca87d..04f0178 100644 --- a/dist/Rokt-Kit.common.js +++ b/dist/Rokt-Kit.common.js @@ -43,7 +43,6 @@ var constructor = function () { self.placementEventMappingLookup = {}; self.placementEventAttributeMappingLookup = {}; self.eventQueue = []; - self.eventStreamQueue = []; self.integrationName = null; function getEventAttributeValue(event, eventAttributeKey) { @@ -417,42 +416,10 @@ var constructor = function () { attributes: selectPlacementsAttributes, }); - var selection = self.launcher.selectPlacements(selectPlacementsOptions); - - // After selection resolves, sync the Rokt session ID back to mParticle - // as an integration attribute so server-side integrations can link events. - // We log the custom event AFTER setting the attribute because - // setIntegrationAttribute alone doesn't fire a network request — - // if the user closes the page before another event fires, the server - // would never receive the session ID. - if (selection && typeof selection.then === 'function') { - selection - .then(function (sel) { - if (sel && sel.context && sel.context.sessionId) { - sel.context.sessionId - .then(function (sessionId) { - _setRoktSessionId(sessionId); - logSelectPlacementsEvent( - selectPlacementsAttributes - ); - }) - .catch(function () { - logSelectPlacementsEvent( - selectPlacementsAttributes - ); - }); - } else { - logSelectPlacementsEvent(selectPlacementsAttributes); - } - }) - .catch(function () { - logSelectPlacementsEvent(selectPlacementsAttributes); - }); - } else { - logSelectPlacementsEvent(selectPlacementsAttributes); - } + // Log custom event for selectPlacements call + logSelectPlacementsEvent(selectPlacementsAttributes); - return selection; + return self.launcher.selectPlacements(selectPlacementsOptions); } /** @@ -556,43 +523,9 @@ var constructor = function () { } } - function _enrichEvent(event) { - return mergeObjects({}, event, { - UserAttributes: self.userAttributes, - }); - } - function _sendEventStream(event) { if (window.Rokt && typeof window.Rokt.__event_stream__ === 'function') { - if (self.eventStreamQueue.length) { - var queuedEvents = self.eventStreamQueue; - self.eventStreamQueue = []; - for (var i = 0; i < queuedEvents.length; i++) { - window.Rokt.__event_stream__(_enrichEvent(queuedEvents[i])); - } - } - window.Rokt.__event_stream__(_enrichEvent(event)); - } else { - self.eventStreamQueue.push(event); - } - } - - function _setRoktSessionId(sessionId) { - if (!sessionId || typeof sessionId !== 'string') { - return; - } - try { - var mpInstance = window.mParticle.getInstance(); - if ( - mpInstance && - typeof mpInstance.setIntegrationAttribute === 'function' - ) { - mpInstance.setIntegrationAttribute(moduleId, { - roktSessionId: sessionId, - }); - } - } catch (e) { - // Best effort — never let this break the partner page + window.Rokt.__event_stream__(event); } } @@ -610,19 +543,11 @@ var constructor = function () { } function attachLauncher(accountId, launcherOptions) { - var mpSessionId = - window.mParticle && - window.mParticle.sessionManager && - typeof window.mParticle.sessionManager.getSession === 'function' - ? window.mParticle.sessionManager.getSession() - : undefined; - var options = mergeObjects( { accountId: accountId, }, - launcherOptions || {}, - mpSessionId ? { mpSessionId: mpSessionId } : {} + launcherOptions || {} ); if (isPartnerInLocalLauncherTestGroup()) { @@ -824,7 +749,7 @@ var constructor = function () { function generateIntegrationName(customIntegrationName) { var coreSdkVersion = window.mParticle.getVersion(); - var kitVersion = "1.18.1"; + var kitVersion = "1.17.0"; var name = 'mParticle_' + 'wsdkv_' + coreSdkVersion + '_kitv_' + kitVersion; if (customIntegrationName) { diff --git a/dist/Rokt-Kit.iife.js b/dist/Rokt-Kit.iife.js index 948c494..d357be0 100644 --- a/dist/Rokt-Kit.iife.js +++ b/dist/Rokt-Kit.iife.js @@ -42,7 +42,6 @@ var RoktKit = (function (exports) { self.placementEventMappingLookup = {}; self.placementEventAttributeMappingLookup = {}; self.eventQueue = []; - self.eventStreamQueue = []; self.integrationName = null; function getEventAttributeValue(event, eventAttributeKey) { @@ -416,42 +415,10 @@ var RoktKit = (function (exports) { attributes: selectPlacementsAttributes, }); - var selection = self.launcher.selectPlacements(selectPlacementsOptions); - - // After selection resolves, sync the Rokt session ID back to mParticle - // as an integration attribute so server-side integrations can link events. - // We log the custom event AFTER setting the attribute because - // setIntegrationAttribute alone doesn't fire a network request — - // if the user closes the page before another event fires, the server - // would never receive the session ID. - if (selection && typeof selection.then === 'function') { - selection - .then(function (sel) { - if (sel && sel.context && sel.context.sessionId) { - sel.context.sessionId - .then(function (sessionId) { - _setRoktSessionId(sessionId); - logSelectPlacementsEvent( - selectPlacementsAttributes - ); - }) - .catch(function () { - logSelectPlacementsEvent( - selectPlacementsAttributes - ); - }); - } else { - logSelectPlacementsEvent(selectPlacementsAttributes); - } - }) - .catch(function () { - logSelectPlacementsEvent(selectPlacementsAttributes); - }); - } else { - logSelectPlacementsEvent(selectPlacementsAttributes); - } + // Log custom event for selectPlacements call + logSelectPlacementsEvent(selectPlacementsAttributes); - return selection; + return self.launcher.selectPlacements(selectPlacementsOptions); } /** @@ -555,43 +522,9 @@ var RoktKit = (function (exports) { } } - function _enrichEvent(event) { - return mergeObjects({}, event, { - UserAttributes: self.userAttributes, - }); - } - function _sendEventStream(event) { if (window.Rokt && typeof window.Rokt.__event_stream__ === 'function') { - if (self.eventStreamQueue.length) { - var queuedEvents = self.eventStreamQueue; - self.eventStreamQueue = []; - for (var i = 0; i < queuedEvents.length; i++) { - window.Rokt.__event_stream__(_enrichEvent(queuedEvents[i])); - } - } - window.Rokt.__event_stream__(_enrichEvent(event)); - } else { - self.eventStreamQueue.push(event); - } - } - - function _setRoktSessionId(sessionId) { - if (!sessionId || typeof sessionId !== 'string') { - return; - } - try { - var mpInstance = window.mParticle.getInstance(); - if ( - mpInstance && - typeof mpInstance.setIntegrationAttribute === 'function' - ) { - mpInstance.setIntegrationAttribute(moduleId, { - roktSessionId: sessionId, - }); - } - } catch (e) { - // Best effort — never let this break the partner page + window.Rokt.__event_stream__(event); } } @@ -609,19 +542,11 @@ var RoktKit = (function (exports) { } function attachLauncher(accountId, launcherOptions) { - var mpSessionId = - window.mParticle && - window.mParticle.sessionManager && - typeof window.mParticle.sessionManager.getSession === 'function' - ? window.mParticle.sessionManager.getSession() - : undefined; - var options = mergeObjects( { accountId: accountId, }, - launcherOptions || {}, - mpSessionId ? { mpSessionId: mpSessionId } : {} + launcherOptions || {} ); if (isPartnerInLocalLauncherTestGroup()) { @@ -823,7 +748,7 @@ var RoktKit = (function (exports) { function generateIntegrationName(customIntegrationName) { var coreSdkVersion = window.mParticle.getVersion(); - var kitVersion = "1.18.1"; + var kitVersion = "1.17.0"; var name = 'mParticle_' + 'wsdkv_' + coreSdkVersion + '_kitv_' + kitVersion; if (customIntegrationName) { From be547804682b3bebd1658e0b6faf0af394a2093f Mon Sep 17 00:00:00 2001 From: Alexander Sapountzis Date: Wed, 25 Mar 2026 10:18:17 -0400 Subject: [PATCH 5/5] fix: reset dist files to match development branch --- dist/Rokt-Kit.common.js | 69 ++++++++++++++++++++++++++++++++++++++--- dist/Rokt-Kit.iife.js | 69 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 128 insertions(+), 10 deletions(-) diff --git a/dist/Rokt-Kit.common.js b/dist/Rokt-Kit.common.js index 04f0178..d002220 100644 --- a/dist/Rokt-Kit.common.js +++ b/dist/Rokt-Kit.common.js @@ -416,10 +416,42 @@ var constructor = function () { attributes: selectPlacementsAttributes, }); - // Log custom event for selectPlacements call - logSelectPlacementsEvent(selectPlacementsAttributes); + var selection = self.launcher.selectPlacements(selectPlacementsOptions); + + // After selection resolves, sync the Rokt session ID back to mParticle + // as an integration attribute so server-side integrations can link events. + // We log the custom event AFTER setting the attribute because + // setIntegrationAttribute alone doesn't fire a network request — + // if the user closes the page before another event fires, the server + // would never receive the session ID. + if (selection && typeof selection.then === 'function') { + selection + .then(function (sel) { + if (sel && sel.context && sel.context.sessionId) { + sel.context.sessionId + .then(function (sessionId) { + _setRoktSessionId(sessionId); + logSelectPlacementsEvent( + selectPlacementsAttributes + ); + }) + .catch(function () { + logSelectPlacementsEvent( + selectPlacementsAttributes + ); + }); + } else { + logSelectPlacementsEvent(selectPlacementsAttributes); + } + }) + .catch(function () { + logSelectPlacementsEvent(selectPlacementsAttributes); + }); + } else { + logSelectPlacementsEvent(selectPlacementsAttributes); + } - return self.launcher.selectPlacements(selectPlacementsOptions); + return selection; } /** @@ -529,6 +561,25 @@ var constructor = function () { } } + function _setRoktSessionId(sessionId) { + if (!sessionId || typeof sessionId !== 'string') { + return; + } + try { + var mpInstance = window.mParticle.getInstance(); + if ( + mpInstance && + typeof mpInstance.setIntegrationAttribute === 'function' + ) { + mpInstance.setIntegrationAttribute(moduleId, { + roktSessionId: sessionId, + }); + } + } catch (e) { + // Best effort — never let this break the partner page + } + } + function onUserIdentified(filteredUser) { self.filters.filteredUser = filteredUser; self.userAttributes = filteredUser.getAllUserAttributes(); @@ -543,11 +594,19 @@ var constructor = function () { } function attachLauncher(accountId, launcherOptions) { + var mpSessionId = + window.mParticle && + window.mParticle.sessionManager && + typeof window.mParticle.sessionManager.getSession === 'function' + ? window.mParticle.sessionManager.getSession() + : undefined; + var options = mergeObjects( { accountId: accountId, }, - launcherOptions || {} + launcherOptions || {}, + mpSessionId ? { mpSessionId: mpSessionId } : {} ); if (isPartnerInLocalLauncherTestGroup()) { @@ -749,7 +808,7 @@ var constructor = function () { function generateIntegrationName(customIntegrationName) { var coreSdkVersion = window.mParticle.getVersion(); - var kitVersion = "1.17.0"; + var kitVersion = "1.18.1"; var name = 'mParticle_' + 'wsdkv_' + coreSdkVersion + '_kitv_' + kitVersion; if (customIntegrationName) { diff --git a/dist/Rokt-Kit.iife.js b/dist/Rokt-Kit.iife.js index d357be0..9c5f68f 100644 --- a/dist/Rokt-Kit.iife.js +++ b/dist/Rokt-Kit.iife.js @@ -415,10 +415,42 @@ var RoktKit = (function (exports) { attributes: selectPlacementsAttributes, }); - // Log custom event for selectPlacements call - logSelectPlacementsEvent(selectPlacementsAttributes); + var selection = self.launcher.selectPlacements(selectPlacementsOptions); + + // After selection resolves, sync the Rokt session ID back to mParticle + // as an integration attribute so server-side integrations can link events. + // We log the custom event AFTER setting the attribute because + // setIntegrationAttribute alone doesn't fire a network request — + // if the user closes the page before another event fires, the server + // would never receive the session ID. + if (selection && typeof selection.then === 'function') { + selection + .then(function (sel) { + if (sel && sel.context && sel.context.sessionId) { + sel.context.sessionId + .then(function (sessionId) { + _setRoktSessionId(sessionId); + logSelectPlacementsEvent( + selectPlacementsAttributes + ); + }) + .catch(function () { + logSelectPlacementsEvent( + selectPlacementsAttributes + ); + }); + } else { + logSelectPlacementsEvent(selectPlacementsAttributes); + } + }) + .catch(function () { + logSelectPlacementsEvent(selectPlacementsAttributes); + }); + } else { + logSelectPlacementsEvent(selectPlacementsAttributes); + } - return self.launcher.selectPlacements(selectPlacementsOptions); + return selection; } /** @@ -528,6 +560,25 @@ var RoktKit = (function (exports) { } } + function _setRoktSessionId(sessionId) { + if (!sessionId || typeof sessionId !== 'string') { + return; + } + try { + var mpInstance = window.mParticle.getInstance(); + if ( + mpInstance && + typeof mpInstance.setIntegrationAttribute === 'function' + ) { + mpInstance.setIntegrationAttribute(moduleId, { + roktSessionId: sessionId, + }); + } + } catch (e) { + // Best effort — never let this break the partner page + } + } + function onUserIdentified(filteredUser) { self.filters.filteredUser = filteredUser; self.userAttributes = filteredUser.getAllUserAttributes(); @@ -542,11 +593,19 @@ var RoktKit = (function (exports) { } function attachLauncher(accountId, launcherOptions) { + var mpSessionId = + window.mParticle && + window.mParticle.sessionManager && + typeof window.mParticle.sessionManager.getSession === 'function' + ? window.mParticle.sessionManager.getSession() + : undefined; + var options = mergeObjects( { accountId: accountId, }, - launcherOptions || {} + launcherOptions || {}, + mpSessionId ? { mpSessionId: mpSessionId } : {} ); if (isPartnerInLocalLauncherTestGroup()) { @@ -748,7 +807,7 @@ var RoktKit = (function (exports) { function generateIntegrationName(customIntegrationName) { var coreSdkVersion = window.mParticle.getVersion(); - var kitVersion = "1.17.0"; + var kitVersion = "1.18.1"; var name = 'mParticle_' + 'wsdkv_' + coreSdkVersion + '_kitv_' + kitVersion; if (customIntegrationName) {