Skip to content

feat: capture page views and surface as page_events in selectPlacements - #109

Open
alexs-mparticle wants to merge 14 commits into
developmentfrom
feat/capture-page-views
Open

feat: capture page views and surface as page_events in selectPlacements#109
alexs-mparticle wants to merge 14 commits into
developmentfrom
feat/capture-page-views

Conversation

@alexs-mparticle

@alexs-mparticle alexs-mparticle commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Summary

Records page-view events as they are logged, persists them durably to the Rokt local-session-attribute store, and surfaces them as a flat page_events array on the next selectPlacements call as targeting context.

What's included

  • Capture (process): page views (EventDataType === 3) are appended to a persisted mpPageViews list (max 25, oldest evicted), guarded so a malformed event can never throw out of the forwarder. Captures EventName, full pageUrl (window.location.href), SourceMessageId, timestamp, ActiveTimeOnSite, and EventAttributes.
  • Flatten (buildPageEvents): each stored view is flattened into a page_events entry — event_name, page_name (from the title attribute), and eventAttributes exploded into attr_-namespaced keys so they can't clobber base fields. The raw nested mpPageViews store is stripped so only the flat copy is sent.
  • timeOnPage: each entry carries a derived timeOnPage — the active time a page was viewed before the next page view was logged (diff of consecutive activeTimeOnSite). 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.
  • Feeding selectPlacements: relaxed returnLocalSessionAttributes() so the store flows through whenever it's available and populated.

Testing

  • Navigate through a page with an MPA and then triggermParticle.getInstance()._Store.getLocalSessionAttributes() in the dev console.

Expected result:

[
    {
        "attr_hostname": "www.domain.com",
        "event_name": "PageView",
        "page_name": "Online Store",
        "pageUrl": "https://www.domain.com/",
        "sourceMessageId": "80588459-ea2c-491a-9f41-2acc15b8cfb8",
        "timestamp": 1785526522349,
        "activeTimeOnSite": 328892,
        "timeOnPage": 35935
    },
    {
        "attr_hostname": "www.domain.com",
        "event_name": "PageView",
        "page_name": "Online Store | Some Page",
        "pageUrl": "https://www.domain.com/page_1",
        "sourceMessageId": "88a1ef32-83fd-41a8-03d8-04c4c67c879d",
        "timestamp": 1785526728989,
        "activeTimeOnSite": 364827,
        "timeOnPage": 11964
    },
    {
        "attr_hostname": "www.domain.com",
        "event_name": "PageView",
        "page_name": "Online Store | Next Page",
        "pageUrl": "https://www.domain.com/page_2",
        "sourceMessageId": "3c755531-1785-4b23-bf6c-fee3a93cc38e",
        "timestamp": 1785526922622,
        "activeTimeOnSite": 376791
    }
]

Verify the expected payload when making a select placements call:

{
  "active_time_on_site_ms": "427361",
  "page_events": "[ /* stringified array of events with timeOnPage value */ ]",
  // other placement attributes
}

alexs-mparticle and others added 11 commits July 31, 2026 10:44
…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.
Comment thread src/Rokt-Kit.ts Outdated
// raw nested mpPageViews so Rokt receives only the flattened copy.
const rawPageViews = localSessionAttributes[PAGE_VIEWS_KEY];
const pageEvents = Array.isArray(rawPageViews) ? this.buildPageEvents(rawPageViews as StoredPageView[]) : [];
delete localSessionAttributes[PAGE_VIEWS_KEY];

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not be mutating this.

Comment thread src/Rokt-Kit.ts Outdated
}

private buildPageEvents(pageViews: StoredPageView[]): PageEvent[] {
return pageViews.map((pv, i) => {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Use pageview and index, rather than abbreviations.

…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.
Comment thread src/Rokt-Kit.ts Outdated
Comment thread src/Rokt-Kit.ts

mp().Rokt.setLocalSessionAttribute?.(PAGE_VIEWS_KEY, pageViews);
} catch (err) {
console.error('Rokt Kit: Failed to capture page view', err);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of console.erroring, we should increase observability by sending to data dog

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or, in addition to i should say

Comment thread src/Rokt-Kit.ts
const filteredUserIdentities = this.returnUserIdentities(filteredUser);

const localSessionAttributes = this.returnLocalSessionAttributes();
const { [PAGE_VIEWS_KEY]: rawPageViews, ...sessionAttributes } = this.returnLocalSessionAttributes();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was conflating PAGE_VIEWS_KEY and PAGE_EVENTS_KEY. Maybe LS_Page_VIEWS_KEY is more clear to stated it's the key for the persistence?

Comment thread src/Rokt-Kit.ts
Comment on lines +937 to +942
const flat: PageEvent = {
event_name: pageView.event_name,
pageUrl: pageView.pageUrl,
sourceMessageId: pageView.sourceMessageId,
timestamp: pageView.timestamp,
activeTimeOnSite: pageView.activeTimeOnSite,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Any reason we are doing 2 transforms? Saving to LS as a PageView, where we do some mapping, then turning a PageView into a PageEvent. Could we just save it as a PageEvent to reduce the number of schemas to keep track of?

Comment thread src/Rokt-Kit.ts
pageViews.shift();
}

mp().Rokt.setLocalSessionAttribute?.(PAGE_VIEWS_KEY, pageViews);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setLocalSessionAttribute only accepts primitive AttributeValues (string | number | boolean | null | undefined) — same as every existing LSA usage (boolean flags). Passing an array of objects here works at runtime only because the setter doesn't validate, but it violates the core SDK contract.

Prefer JSON.stringify on write and JSON.parse on read so the stored value stays a string.

Comment thread src/Rokt-Kit.ts
flat[`${PAGE_EVENT_ATTR_PREFIX}${key}`] = value;
}
}
flat.page_name = pageView.eventAttributes?.[PAGE_TITLE_ATTRIBUTE];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can remove the ? in eventAttributes? here by moving this up one line to be embedded in the if clause directly above

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants