feat: Block mp cookies when noFunctional is true#1167
feat: Block mp cookies when noFunctional is true#1167khushi1033 wants to merge 12 commits intodevelopmentfrom
Conversation
alexs-mparticle
left a comment
There was a problem hiding this comment.
@khushi1033 Great PR and I'm happy to see this coming together. I have a few comments but this looks pretty solid.
| data['newMPID'].csd.should.have.property(roktModuleId); | ||
| }); | ||
|
|
||
| it('should not block Rokt cookie sync when only noFunctional is true', async () => { |
There was a problem hiding this comment.
Why was this removed?
There was a problem hiding this comment.
With noFunctional blocking cookies now, this test fails since theres no mPId being saved. Planning to add a new test once we have confirmation of how we want cookie sync to behave under noFunctional
src/mp-instance.ts
Outdated
| // When noFunctional is true, we must still return a vault-shaped object because | ||
| // Identity expects mpInstance._Identity.idCache to always exist. A no-op vault | ||
| // ensures no identity response data is written to localStorage | ||
| if (mpInstance._CookieConsentManager?.getNoFunctional()) { | ||
| const vault = new LocalStorageVault(`${mpInstance._Store.storageName}-id-cache`, { | ||
| logger: mpInstance.Logger, | ||
| }); | ||
| vault.store = function() { | ||
| this.contents = arguments[0]; | ||
| mpInstance.Logger.verbose(`Storage disabled: not storing id-cache when noFunctional is true`); | ||
| }; | ||
| vault.retrieve = function() { | ||
| this.contents = null; | ||
| return null; | ||
| }; | ||
| vault.purge = function() { | ||
| this.contents = null; | ||
| }; | ||
| return vault; | ||
| } |
There was a problem hiding this comment.
I asked AI to instead of creating a no-op, to make idCache optional. Here are the pros/cons of optional idCache:
1. Explicit “no cache” instead of a fake object
optional idCache: The type is BaseVault<...> | null. “We are not caching” is visible in the type and in control flow.
No-op: You have an object that looks like a cache but isn’t. “No cache” is hidden behind an implementation that only exists to satisfy “something is always there.”
2. One concept instead of two
optional idCache: There’s a single idea: either we have a real cache or we don’t.
No-op: You maintain two concepts — “real cache” and “fake cache that implements the interface.” The no-op is there only to avoid null checks, which pushes the “no functional” concern into a dummy implementation.
3. Less code and no dummy behavior
optional idCache: No extra type, no store/retrieve/purge stubs, no allocation when noFunctional is true.
No-op: You have to implement and maintain those no-op methods and keep them in sync with the real vault behavior (e.g. logging) over time.
4. Honest API
optional idCache: The API says “identity cache may be absent.” Callers (or shared utils) handle that once at the boundary.
No-op: The API says “you always have a cache,” but one of the implementations is a lie. That can be surprising for anyone reading or debugging.
There was a problem hiding this comment.
I'd prefer the optional no cache
There was a problem hiding this comment.
Changed to use DisabledVault instead similar to the approach in this PR
Co-authored-by: Robert Ing <rmi22186@gmail.com>
| expect(hasExplicitIdentifier(store as IStore)).toBe(false); | ||
| }); | ||
|
|
||
| it('should return false when userIdentities object is empty', () => { |
There was a problem hiding this comment.
can you add one more test for if useIdentities object has an invalid user identity, like userIdentities = {email: ''} and also userIdentities = {email: null}?
rmi22186
left a comment
There was a problem hiding this comment.
Can you add tests to the forwarders test file that shows an integration test when we add noFunctional = true, and shows that forwarders are still being initialized, and the forwarder still receives an event even if MP SDK does not initialize?
| }); | ||
|
|
||
| describe('when MP SDK is not fully initialized', () => { | ||
| it('should allow Rokt Kit to initialize and selectPlacements', async () => { |
There was a problem hiding this comment.
What about this tests is showing that the MP SDK is not fully initialized? It seems to just be setting up the rokt manager manually.
Similar to the forwarders test, it'd be helpful to have a test (probably there), specifically for seeing if rokt is fully initialized even if the MP SDK isn't.
Something like:
call mParticle init with noFunctional = true
assert that the MP SDK is not initialized
assert that no identity calls were made
call mParticle.rokt.selectPlacements and assert that the kit called selectPlacements internally
There was a problem hiding this comment.
Yep you're right, I updated the test to better represent the full flow.
alexs-mparticle
left a comment
There was a problem hiding this comment.
LGTM. Once you resolve comments from @rmi22186, I think we can ship.
test/src/tests-forwarders.ts
Outdated
| delete window.mParticle.config.identifyRequest; | ||
| }); | ||
|
|
||
| it('should still initialize forwarders when noFunctional is true (even when SDK does not complete initialization)', () => { |
There was a problem hiding this comment.
Can we add a test that says the event is still sent to the forwarder here also?
It should be work both in this case when there is no identity passed, as well as the bottom case where an identity is passed,
There was a problem hiding this comment.
Done, added verification that events are still sent even without identity passed in
src/apiClient.ts
Outdated
| ); | ||
| mpInstance._Store.eventQueue.push(event); | ||
| // When noFunctional is true, mpid may never come (no identity), so still send to forwarders so kits receive the event. | ||
| if (mpInstance._CookieConsentManager?.getNoFunctional() && !isEmpty(event) && (event.EventName as unknown as number) !== Types.MessageType.AppStateTransition) { |
There was a problem hiding this comment.
It's actually not just events. You may want to ask claude or cursor to do an audit of where we call anything on the forwarder inside of the forwarders.js module, as an example forwarder.setUserAttribute. These will needs tests too.
There was a problem hiding this comment.
Is it ever possible for an event to be sent twice to a forwarder in this case?
If a partner passes an identity, and a customer is on a slow connection, it's possible an MPID hasn't come back yet. THe event gets queued and also immediately forwarded to the forwarder.
Then the MPID comes back, the queue gets processed, and in that code block, would it also then get sent to the server? Perhaps doing a check like you did elsewhere for hasExplicitIdentifier may help here?
There was a problem hiding this comment.
Got it, yeah I added a check for hasExplicitIdentifier to ensure the events aren't sent twice.
Also added tests for the following behavior under noFunctional = true:
forwarder.setSessionAttributestill works whether an identity is provided or isn't.forwarder.setUserAttributeandforwarder.removeUserAttributeonly work when an identity is provided, and are no-ops when it isn't sincegetCurrentUseris nullforwarder.onUserIdentifiedandforwarder.onIdentifyCompleteare only called when an identity is provided
| }); | ||
|
|
||
| expect(identifySpy).toHaveBeenCalledWith({ | ||
| userIdentities: { email: 'user@example.com' }, |
There was a problem hiding this comment.
One of the requirements is that once identify is called, mparticle should be fully initialized. I'd add a test for this. Also, have the SDK queue a few events before calling identify, confirm the queue is a certain length (if that is possible), then call identify, wait for it to return, and confirm logevent was called with the queued events.
There was a problem hiding this comment.
Added testing to tests-forwarders.ts to make sure mparticle is being intialized after identify is called and added a test to check logEvent is being called on queued events.
|


Background
Rokt supports disabling cookies via client-side flags (noFunctional, noTargeting). With the introduction of the joint Rokt/mParticle SDK, mP cookies are treated as strictly necessary — meaning they could not previously be disabled for Rokt-only clients. This created friction for clients who want a clear opt-out path for users or interpret mP cookies as non-essential when using the SDK solely for Rokt use cases (e.g., post-checkout ads).
However, simply blocking all mP cookies introduces a risk: without the mprtcl-v4-{workspace_token} cookie, every page load looks like a new user, causing identify to be called continuously and flooding the system with new profile creation.
What Has Changed
profile creation.
Screenshots/Video
Checklist
Additional Notes
Reference Issue (For employees only. Ignore if you are an outside contributor)