Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions kits/rokt/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
## [1.30.2](https://github.com/mparticle-integrations/mparticle-javascript-integration-rokt/compare/v1.30.1...v1.30.2) (2026-08-05)


### Bug Fixes

* lower PAGE_VIEW_CAPTURE_FAILED severity from WARNING to INFO ([#111](https://github.com/mparticle-integrations/mparticle-javascript-integration-rokt/issues/111)) ([12e179d](https://github.com/mparticle-integrations/mparticle-javascript-integration-rokt/commit/12e179d2ae13c58927917be4f7685217666a2a52))

## [1.30.1](https://github.com/mparticle-integrations/mparticle-javascript-integration-rokt/compare/v1.30.0...v1.30.1) (2026-08-05)

# [1.30.0](https://github.com/mparticle-integrations/mparticle-javascript-integration-rokt/compare/v1.29.0...v1.30.0) (2026-08-04)


### Features

* capture page views and surface as page_events in selectPlacements ([#109](https://github.com/mparticle-integrations/mparticle-javascript-integration-rokt/issues/109)) ([098aabe](https://github.com/mparticle-integrations/mparticle-javascript-integration-rokt/commit/098aabe86d13b5f9a83d46885d8ed83f09bf3122))

# [1.29.0](https://github.com/mparticle-integrations/mparticle-javascript-integration-rokt/compare/v1.28.3...v1.29.0) (2026-07-31)


### Features

* prevent caching of active_time_on_site_ms attribute ([#106](https://github.com/mparticle-integrations/mparticle-javascript-integration-rokt/issues/106)) ([315c4f9](https://github.com/mparticle-integrations/mparticle-javascript-integration-rokt/commit/315c4f9016a31076a53b8fa97a9e35bf31b5c953))

## [1.28.3](https://github.com/mparticle-integrations/mparticle-javascript-integration-rokt/compare/v1.28.2...v1.28.3) (2026-07-01)


Expand Down
4 changes: 2 additions & 2 deletions kits/rokt/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions kits/rokt/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mparticle/web-rokt-kit",
"version": "1.28.3",
"version": "1.30.2",
"description": "mParticle integration kit for Rokt",
"main": "dist/Rokt-Kit.common.js",
"module": "dist/Rokt-Kit.esm.js",
Expand All @@ -18,7 +18,7 @@
"dist/Rokt-Kit.iife.js",
"dist/Rokt-Kit.d.ts"
],
"repository": "https://github.com/mparticle-integrations/mparticle-javascript-integration-rokt",
"repository": "https://github.com/mParticle/mparticle-web-sdk",
"scripts": {
"build": "vite build",
"build:watch": "vite build --watch",
Expand All @@ -30,7 +30,6 @@
},
"publishConfig": {
"access": "public",
"provenance": true,
"registry": "https://registry.npmjs.org"
},
"peerDependencies": {
Expand Down
171 changes: 166 additions & 5 deletions kits/rokt/src/Rokt-Kit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,17 @@
value: string;
}

interface PageEvent {
pageUrl: string;
sourceMessageId: string;
timestamp: number;
activeTimeOnSite?: number;
// Derived at transmission not at capture based
// on the next page view's activeTimeOnSite,
// so it is absent on stored records.
activeTimeOnPage?: number;
}

interface RoktSelection {
context?: {
sessionId?: Promise<string>;
Expand Down Expand Up @@ -244,6 +255,19 @@
const ROKT_THANK_YOU_ELEMENT_SCRIPT_ID = 'rokt-thank-you-element';
const USER_IDENTIFIED_IN_WORKSPACE_KEY = 'userIdentifiedInWorkspace';

const MESSAGE_TYPE_PAGE_VIEW = 3; // mParticle MessageType.PageView
const MESSAGE_TYPE_SESSION_END = 2; // mParticle MessageType.SessionEnd
// localStorage key under which captured page views are persisted (as a JSON
// string). The kit owns this storage directly — separate from mParticle's
// cookie/localStorage — so page-view capture does not affect mParticle
// persistence or cookie sync. Distinct from PAGE_EVENTS_KEY, which is the
// flattened wire shape sent to Rokt on selectPlacements.
const LS_PAGE_VIEWS_KEY = 'mpPageViews';
// Fixed cap on the number of persisted page views (oldest evicted first). Code
// constant, not a kit setting — change it here.
const PAGE_VIEWS_MAX_COUNT = 25;
const PAGE_EVENTS_KEY = 'page_events';

// Bound on how long selectPlacements will wait for an in-flight Workspace
// IDSync search before proceeding without the userIdentifiedInWorkspace flag.
// Long enough to cover the typical /v1/search round-trip (~50ms); short enough that a
Expand Down Expand Up @@ -287,6 +311,27 @@
// Module-level utility functions
// ============================================================

function readPageViewsStorage(): PageEvent[] {
try {
const stored = window.localStorage.getItem(LS_PAGE_VIEWS_KEY);
if (stored === null) {
return [];
}
const parsed = JSON.parse(stored);
return Array.isArray(parsed) ? (parsed as PageEvent[]) : [];
} catch {
return [];
}
}

function writePageViewsStorage(pageViews: PageEvent[]): void {
window.localStorage.setItem(LS_PAGE_VIEWS_KEY, JSON.stringify(pageViews));
}

function clearPageViewsStorage(): void {
window.localStorage.removeItem(LS_PAGE_VIEWS_KEY);
}

function generateLauncherScript(domain: string | undefined, extensions: string[]): string {
const launcherPath = '/wsdk/integrations/launcher.js';
const baseUrl = [generateBaseUrl(domain), launcherPath].join('');
Expand Down Expand Up @@ -450,6 +495,19 @@
return typeof value === 'string';
}

// Strips the query string from a page-view URL before it is persisted and sent
// to Rokt, since query params commonly carry PII (emails, tokens, order refs).
// Returns the input unchanged if it can't be parsed as a URL.
function sanitizeUrl(href: string): string {
try {
const url = new URL(href);
url.search = '';
return url.toString();
} catch {
return href;
}
}

function generateIntegrationName(customIntegrationName?: string): string {
const coreSdkVersion = mp().getVersion();
const kitVersion = process.env.PACKAGE_VERSION;
Expand Down Expand Up @@ -845,6 +903,41 @@
}
}

private capturePageView(event: SDKEvent): void {
let pageUrl: string | undefined;

try {
pageUrl = sanitizeUrl(window.location.href);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Page URL captured after delay

Medium Severity

capturePageView records window.location.href at process time, while other fields come from the event snapshot. When the core SDK queues events (no MPID, integration delay, or config load), navigation before flush stores the wrong pageUrl in page_events sent to Rokt. SDKEvent already carries PageUrl from event creation.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 41cb8dc. Configure here.


const pageViews = readPageViewsStorage();

const pageView: PageEvent = {
pageUrl,
sourceMessageId: event.SourceMessageId,
timestamp: event.Timestamp,
};

if (Number.isFinite(event.ActiveTimeOnSite)) {
pageView.activeTimeOnSite = event.ActiveTimeOnSite;
}

pageViews.push(pageView);

while (pageViews.length > PAGE_VIEWS_MAX_COUNT) {
pageViews.shift();
}

writePageViewsStorage(pageViews);
} catch (err) {
this.errorReportingService?.report({
message: `Rokt Kit: Failed to capture page view for ${pageUrl}`,
code: 'PAGE_VIEW_CAPTURE_FAILED',
severity: WSDKErrorSeverity.INFO,
stackTrace: err instanceof Error ? err.stack : undefined,
});
}
}

private isLauncherReadyToAttach(): boolean {
return !!window.Rokt && typeof window.Rokt.createLauncher === 'function';
}
Expand All @@ -866,12 +959,37 @@
if (!mp().Rokt || typeof mp().Rokt.getLocalSessionAttributes !== 'function') {
return {};
}
if (isEmpty(this.placementEventMappingLookup) && isEmpty(this.placementEventAttributeMappingLookup)) {
return {};
}
return mp().Rokt.getLocalSessionAttributes!();
}

private buildPageEvents(pageViews: PageEvent[]): PageEvent[] {
return pageViews.map((pageView, index) => {
const pageEvent: PageEvent = {
pageUrl: pageView.pageUrl,
sourceMessageId: pageView.sourceMessageId,
timestamp: pageView.timestamp,
};

const activeTimeOnSite = pageView.activeTimeOnSite;
const hasActiveTime = activeTimeOnSite !== undefined && Number.isFinite(activeTimeOnSite);
if (hasActiveTime) {
pageEvent.activeTimeOnSite = activeTimeOnSite;
}

const next = pageViews[index + 1];
const nextActiveTimeOnSite = next?.activeTimeOnSite;
const hasNextActiveTimeOnSite = nextActiveTimeOnSite !== undefined && Number.isFinite(nextActiveTimeOnSite);

if (hasActiveTime && hasNextActiveTimeOnSite) {
const diff = nextActiveTimeOnSite - activeTimeOnSite;
if (diff >= 0) {
pageEvent.activeTimeOnPage = diff;
}
}
return pageEvent;
});
}

private replaceOtherIdentityWithEmailsha256(userIdentities: IUserIdentities): Record<string, string> {
const newUserIdentities: Record<string, string> = { ...(userIdentities || {}) };
const key = this._mappedEmailSha256Key;
Expand Down Expand Up @@ -1007,6 +1125,12 @@
return !!(this.isInitialized && this.launcher);
}

// When the partner has opted out of targeting (noTargeting launcher option),
// the kit must not collect behavioral targeting signals such as page views.
private isTargetingDisabled(): boolean {
return (mp().Rokt?.launcherOptions as Record<string, unknown> | undefined)?.noTargeting === true;
}

private isPartnerInLocalLauncherTestGroup(): boolean {
return !!(mp().config && mp().config!.isLocalLauncherEnabled && this.isAssignedToSampleGroup());
}
Expand Down Expand Up @@ -1091,6 +1215,19 @@
this.errorReportingService = errorReportingService;
this.loggingService = loggingService;

if (this.isTargetingDisabled()) {
try {
clearPageViewsStorage();
} catch (err) {
this.errorReportingService?.report({
message: 'Rokt Kit: Failed to clear page views when targeting is disabled',
code: 'PAGE_VIEW_CAPTURE_FAILED',
severity: WSDKErrorSeverity.INFO,
stackTrace: err instanceof Error ? err.stack : undefined,
});
}
}

if (mp()._registerErrorReportingService) {
mp()._registerErrorReportingService!(errorReportingService);
}
Expand Down Expand Up @@ -1161,10 +1298,32 @@
return 'Successfully initialized: ' + name;
}

public process(event: SDKEvent): string {

Check failure on line 1301 in kits/rokt/src/Rokt-Kit.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 21 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=mParticle_mparticle-web-sdk&issues=AZ_YZzf5eNDEvhtsjbDr&open=AZ_YZzf5eNDEvhtsjbDr&pullRequest=1313
if (!this.isTargetingDisabled()) {
if (event.EventDataType === MESSAGE_TYPE_PAGE_VIEW) {
this.capturePageView(event);
}

if (event.EventDataType === MESSAGE_TYPE_SESSION_END) {
try {
clearPageViewsStorage();
} catch (err) {
this.errorReportingService?.report({
message: 'Rokt Kit: Failed to clear page views on session end',
code: 'PAGE_VIEW_CAPTURE_FAILED',
severity: WSDKErrorSeverity.INFO,
stackTrace: err instanceof Error ? err.stack : undefined,
});
}
}
}

// The forwarding work below (LSA mapping) depends on the launcher, so guard
// it here and surface the not-ready signal to the core SDK.
if (!this.isKitReady()) {
return 'Kit not ready for forwarder: ' + name;
}

if (typeof mp().Rokt?.setLocalSessionAttribute === 'function') {
if (!isEmpty(this.placementEventAttributeMappingLookup)) {
this.applyPlacementEventAttributeMapping(event);
Expand Down Expand Up @@ -1373,13 +1532,15 @@

const filteredUserIdentities = this.returnUserIdentities(filteredUser);

const localSessionAttributes = this.returnLocalSessionAttributes();
const sessionAttributes = this.returnLocalSessionAttributes();
const pageEvents = this.buildPageEvents(readPageViewsStorage());

const selectPlacementsAttributes: Record<string, unknown> = {
...(filteredUserIdentities as Record<string, unknown>),
...filteredAttributes,
...optimizelyAttributes,
...localSessionAttributes,
...sessionAttributes,
...(pageEvents.length ? { [PAGE_EVENTS_KEY]: JSON.stringify(pageEvents) } : {}),
...(this.userIdentifiedInWorkspace ? { [USER_IDENTIFIED_IN_WORKSPACE_KEY]: true } : {}),
mpid,
};
Expand Down
1 change: 1 addition & 0 deletions kits/rokt/src/selectPlacementsAttributePersistence.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const SELECT_PLACEMENTS_ATTRIBUTE_PERSISTENCE_DENY_LIST = [
'active_time_on_site_ms',
'billingaddress1',
'billingaddress2',
'billingcity',
Expand Down
Loading