feat: capture page views and surface as page_events in selectPlacements - #109
Merged
Conversation
…eventAttributes per page view
…ents Derive a flat page_events array from stored page views at selectPlacements time. Each view's eventAttributes are exploded into attr_-namespaced keys; the title attribute is surfaced as page_name and EventName as event_name. The raw nested mpPageViews store is stripped so only the flattened copy is sent. Relax returnLocalSessionAttributes to no longer require a placement mapping. Restore the isKitReady guard in process() and remove debug logging.
Compute a timeOnPage field for each page_events entry at read time in buildPageEvents as the diff of consecutive activeTimeOnSite values — the active time a page was viewed before the next page view was logged. Emitted only when it is a genuine, non-negative number; omitted for the still-open last entry, negative diffs (clock skew, reset, out-of-order), and non-numeric activeTimeOnSite. Nothing new is persisted; StoredPageView is unchanged.
…iews # Conflicts: # dist/Rokt-Kit.common.js # dist/Rokt-Kit.common.js.map # dist/Rokt-Kit.esm.js.map # dist/Rokt-Kit.iife.js # dist/Rokt-Kit.iife.js.map
Add an explicit PageEvent return type for buildPageEvents (with timeOnPage optional) instead of Record<string, unknown>[]. Revert the legacy #processEvent attribute-mapping tests back to MessageType.PageView and strip the captured mpPageViews list from their exact-shape assertions via a helper, since processing a PageView now legitimately captures a page-view record.
alexs-mparticle
commented
Jul 31, 2026
…st test asserts - Split mpPageViews off the session attributes via destructuring instead of delete, so the store returned by getLocalSessionAttributes is not mutated. - Rename buildPageEvents map params pv/i to pageView/index. - Drop the mappedSessionAttributes masking helper; the attribute-mapping tests now assert only their own mapped keys directly and leave mpPageViews to the page-view capture tests.
alexs-mparticle
commented
Jul 31, 2026
rmi22186
reviewed
Aug 1, 2026
rmi22186
reviewed
Aug 1, 2026
rmi22186
reviewed
Aug 1, 2026
rmi22186
reviewed
Aug 1, 2026
rmi22186
reviewed
Aug 1, 2026
…ey, report capture failures - Store page views as a JSON string via setLocalSessionAttribute to respect the primitive-only AttributeValue contract; parse on read (rmi22186) - Rename PAGE_VIEWS_KEY -> LS_PAGE_VIEWS_KEY to distinguish the persistence key from the PAGE_EVENTS_KEY wire shape (rmi22186) - Report page-view capture failures via errorReportingService instead of console.error for observability (rmi22186) - Fold page_name assignment into the eventAttributes guard to drop the optional chain (rmi22186)
jamesnrokt
requested changes
Aug 3, 2026
jamesnrokt
left a comment
Contributor
There was a problem hiding this comment.
Requesting changes as we definitely need the cleansing of the URL in before go live
alexs-mparticle
commented
Aug 3, 2026
- sanitizeUrl now strips the query string (commonly carries PII) before a page-view URL is persisted and sent to Rokt - add rationale comment for MAX_PAGE_VIEWS cap - rename buildPageEvents var flat -> pageEvent - drop redundant typeof guards on activeTimeOnSite (already typed number; diff >= 0 check handles NaN) - add test covering query-param stripping
This flow targets auto page views, which carry no useful EventName or EventAttributes, so remove event_name, page_name, and the attr_-namespaced event-attribute explosion from both the stored and wire shapes. Simplifies StoredPageView/PageEvent to url, sourceMessageId, timestamp, activeTimeOnSite (+ derived timeOnPage).
page_events is an array of objects, but the Rokt attribute contract only permits primitives and arrays of primitives. Passing raw objects to the launcher is undefined behaviour, so JSON-stringify it at the call site and JSON.parse in the tests that assert on it.
- Resolve pageUrl as the first step inside capturePageView's try and reuse it in the failure log, so an undefined URL signals failure at/before URL construction and the log is never lost. - Collapse StoredPageView and PageEvent into a single PageEvent interface; timeOnPage is optional and derived at transmission.
Replace the arbitrary MAX_PAGE_VIEWS=25 count cap with a byte-budget cap resolved from mParticle's storage backend: 128 KB in localStorage mode, 1/3 of maxCookieSize in cookie mode, with a safe cookie-default fallback when SDK internals are unavailable. Evicts oldest page views first until the serialized blob fits the budget, always retaining the current view.
Persist captured page views in the kit's own localStorage instead of mParticle's local session attributes, so page-view capture no longer touches mParticle persistence or cookie sync. Replace the storage-backend byte-budget calculator with a fixed 25-record cap (oldest evicted), and drop the SDK-internal _Store/SDKConfig typing it depended on. Capture now runs independently of setLocalSessionAttribute availability, and localStorage read/write is guarded so a storage failure surfaces a single WARNING without throwing out of the forwarder.
jamesnrokt
reviewed
Aug 3, 2026
jamesnrokt
left a comment
Contributor
There was a problem hiding this comment.
Final remaining comment I know you're already addressing is clearing out storage on session
A localStorage JSON round-trip can yield undefined/NaN for a mangled or partially-written record, so activeTimeOnSite is no longer guaranteed to be a number. Type it as optional and guard the timeOnPage subtraction in buildPageEvents against non-number values. Addresses review feedback on PR #109.
Restore the isKitReady guard in process() that was removed in 329efb3, but place it after kit-owned page-view capture so capture still runs before the launcher attaches while the core SDK regains the not-ready signal for the forwarding path. Also gate page-view capture and session-end cleanup on the noTargeting launcher option: page views are behavioral targeting signals and must not be collected when the partner has opted out of targeting. Addresses review feedback on PR #109.
A finite number round-trips losslessly through JSON, and SDKEvent types ActiveTimeOnSite as a non-optional number, so buildPageEvents never sees a non-number in the normal flow. Drop the per-field typeof guard and realign PageEvent.activeTimeOnSite with the source type.
A NaN/Infinity source serializes to "null" via JSON.stringify and reads back as a non-number, which is what made the stored type look inconsistent in review. Normalizing at capture guarantees only finite numbers enter storage, so PageEvent.activeTimeOnSite stays an honest non-optional number with no per-field guard needed on read.
jamesnrokt
reviewed
Aug 4, 2026
…ting disabled Revert PageEvent.activeTimeOnSite to optional and omit it at capture when non-finite rather than coercing to 0, which would be diffed against the next record in buildPageEvents and fabricate a dwell time. Guard the timeOnPage diff on both records carrying a finite value so "unknown" stays distinguishable from a genuine zero. Clear the kit-owned page-view store once on init when targeting is disabled, so a later re-enable starts fresh without reading storage on every event or dispatch. A clear failure surfaces as a PAGE_VIEW_CAPTURE_FAILED warning.
rmi22186
approved these changes
Aug 4, 2026
rmi22186
left a comment
Collaborator
There was a problem hiding this comment.
I noticed that Event.EventAttributes were removed between commits at some point. If that is the intent, then this LGTM
jamesnrokt
approved these changes
Aug 4, 2026
jamesnrokt
left a comment
Contributor
There was a problem hiding this comment.
Thanks for your efforts on this - apologies for the many questions
github-actions Bot
pushed a commit
that referenced
this pull request
Aug 4, 2026
# [1.30.0](v1.29.0...v1.30.0) (2026-08-04) ### Features * capture page views and surface as page_events in selectPlacements ([#109](#109)) ([098aabe](098aabe))
Collaborator
|
🎉 This PR is included in version 1.30.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Captures page-view events as they are logged, persists them in the kit's own localStorage (separate from mParticle persistence and cookie sync), and surfaces them as a stringified
page_eventsarray on the nextselectPlacementscall as targeting context.What's included
process→capturePageView): page views (EventDataType === 3) are appended to a kit-ownedmpPageViewslist in localStorage. Each record storespageUrl(fromwindow.location.href, query string stripped),sourceMessageId,timestamp, andactiveTimeOnSite. Capture runs independently ofsetLocalSessionAttributeavailability, since the kit controls its own storage.buildPageEvents): each entry carries a derivedtimeOnPage— the active time a page was viewed before the next page view was logged (diff of consecutiveactiveTimeOnSite). Emitted only when it's a genuine non-negative number; omitted for the still-open last entry, negative diffs, and non-numeric values. Derived at read time — nothing extra persisted.buildPageEvents, and sent as a stringifiedpage_eventsarray alongside the other placement attributes.Storage & error handling
The kit owns its localStorage directly rather than writing through mParticle's local-session-attribute store, so page-view capture no longer touches mParticle persistence or cookie sync.
readPageViewsStorage): guarded — returns an empty array when nothing is stored, the value can't be parsed, or localStorage is unavailable (Safari private mode, storage disabled).writePageViewsStorage): a failure (quota exceeded, storage disabled) is caught incapturePageViewand surfaced as aPAGE_VIEW_CAPTURE_FAILEDWARNING, bounded by the reporting service's per-severity rate limiter, without ever throwing out of the forwarder.Testing
Navigate through pages on an MPA, then inspect the kit-owned store in the dev console:
Expected result:
Verify the expected payload when making a selectPlacements call: