Skip to content

feat: send the mParticle device id as a selectPlacements attribute - #121

Merged
jamesnrokt merged 2 commits into
developmentfrom
feature/mparticle-device-id-attribute
Aug 19, 2026
Merged

feat: send the mParticle device id as a selectPlacements attribute#121
jamesnrokt merged 2 commits into
developmentfrom
feature/mparticle-device-id-attribute

Conversation

@jamesnrokt

@jamesnrokt jamesnrokt commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Summary

The kit already sends mparticle_session_id on every selectPlacements call, but the session id is short-lived: core mints a new one after sessionTimeout (30 minutes by default), when another tab starts a session, and on an explicit endSession(). That makes it a poor key for anything that needs to span a visit.

mParticle also carries a device application stamp (das, exposed publicly as mParticle.getDeviceId()), which is strictly stickier. Verified against mparticle-web-sdk:

  • Generated once and then restored from persistence — priority is config.deviceIdgs.das in storage → new GUID (persistence.js:135-137, persistence.js:175-178).
  • Written to both localStorage and cookie on every save (persistence.js:294). Cookie TTL is cookieExpiration, 365 days by default (constants.ts:118); localStorage has no expiry.
  • Survives session rotation. nullifySession() clears sessionId, sessionStartDate, dateLastEventSent and session attributes but never deviceId (store.ts:699-706) — and that is the function called on timeout, cross-tab rotation, and endSession() (sessionManager.ts:250-256).
  • Survives login / logout / modify — the identity flows only read _Store.deviceId to send as device_application_stamp (identity.js:265, 360, 450); nothing regenerates it.

It changes only on explicit setDeviceId() / config.deviceId, mParticle.reset(), or the user clearing browser storage.

This PR sends it as the mparticle_device_id attribute on each selectPlacements call:

  • Adds readMpDeviceId(), which reads mParticle.getDeviceId() per call rather than pinning it at launcher creation, so a partner's setDeviceId() reassignment is picked up on the next call.
  • Spreads the value into selectPlacementsAttributes next to mparticle_session_id, omitted entirely when absent or empty rather than sent as an empty string.
  • Types getDeviceId as optional on the internal MParticleExtended interface, so a core that predates it simply omits the attribute instead of throwing.

No public API change: readMpDeviceId is private and no new kit setting is introduced.

Known caveat

Under noFunctional consent, setCookie and setLocalStorage bail early (persistence.js:234, 407), so nothing persists and the device id regenerates per page load. The session id isn't persisted in that mode either, so the device id is never less sticky than what we already send.

Open question for review

The attribute is named mparticle_device_id to match the public accessor (getDeviceId()) and to read naturally beside mparticle_session_id. mParticle's own canonical wire name is device_application_stamp / das. If the ingestion contract expects that spelling, it's a one-line change to the MPARTICLE_DEVICE_ID_KEY constant.

Screenshot

Screenshot 2026-08-18 at 10 58 58 AM

Testing Plan

  • npm test — 311/311 pass (307 existing + 4 new). npm run lint clean. npm run build succeeds.
  • The build emits 7 error TS diagnostics from vite-plugin-dts. These are pre-existing (IMParticleUser missing getMPID / getUserIdentities); diffed against a baseline build of the branch point, the set is identical with only line numbers shifted.
  • New unit tests cover:
    • the device id holding steady across a session id rotation between two selectPlacements calls — the core claim of this PR;
    • the device id current at the time of each call, asserted across a reassignment;
    • the attribute being omitted when getDeviceId is unavailable;
    • the attribute being omitted when getDeviceId returns nothing.
  • 35 existing full-payload toEqual assertions updated, since the attribute is now present on every call. getDeviceId added to the mParticle mocks in test/vitest.setup.ts and the #selectPlacements beforeEach, so tests that delete it don't leak into later ones.
  • dist/ is intentionally not included — the release workflow regenerates the bundle after merge.
  • Additional testing worth doing: on a real page, confirm the same mparticle_device_id arrives across a session timeout and across a logout/login, and that it differs between two browser profiles.

🤖 Generated with Claude Code

The kit already sends mparticle_session_id, but the session id is short-lived:
core mints a new one after sessionTimeout (30 minutes by default), when another
tab starts a session, and on an explicit endSession(). That makes it a poor key
for anything that needs to span a visit.

The device application stamp (das, exposed as mParticle.getDeviceId()) is
strictly stickier. nullifySession() clears the session id and its dates but
never deviceId, and that is the function called on timeout, cross-tab rotation
and endSession(). It also survives login/logout — the identity flows only read
_Store.deviceId — and persists in localStorage/cookie for cookieExpiration days
(365 by default).

Send it as the mparticle_device_id attribute on each selectPlacements call.
Reading it per call rather than pinning it at launcher creation means a
partner's setDeviceId() reassignment is picked up on the next call.

getDeviceId is typed optional on MParticleExtended, so a core that predates it
omits the attribute instead of throwing. The attribute is also omitted when the
accessor returns nothing, rather than sent as an empty string.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread src/Rokt-Kit.ts Outdated
The aliased-reference-plus-.call() pattern was copied from
readMpSessionId, where it is load-bearing: that helper picks between
getSessionId and getSession, so it needs a local alias and an explicit
receiver. readMpDeviceId has a single accessor, so optional chaining
does the same job — ?.() still covers a core that predates getDeviceId,
and the call keeps its natural mParticle receiver.

No behaviour change; the empty-string-to-undefined normalization stays.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jamesnrokt
jamesnrokt merged commit bb754ca into development Aug 19, 2026
3 of 5 checks passed
github-actions Bot pushed a commit that referenced this pull request Aug 19, 2026
# [1.34.0](v1.33.4...v1.34.0) (2026-08-19)

### Features

* send the mParticle device id as a selectPlacements attribute ([#121](#121)) ([bb754ca](bb754ca))
@mparticle-automation

Copy link
Copy Markdown
Collaborator

🎉 This PR is included in version 1.34.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants