Skip to content
Closed
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
119 changes: 116 additions & 3 deletions src/Rokt-Kit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,32 @@ interface RoktExtensionEntry {
value: string;
}

// A captured page view, persisted (newest last) under PAGE_VIEWS_KEY.
// pageUrl and eventAttributes are stored verbatim and may contain PII; they are
// persisted to browser storage and sent to Rokt on the next selectPlacements call.
interface StoredPageView {
event_name: string;
pageUrl: string;
sourceMessageId: string;
timestamp: number;
activeTimeOnSite: number;
eventAttributes?: { [key: string]: string };
}

// A page view flattened for the outgoing page_events array. Event attributes are
// exploded onto attr_-namespaced keys (the index signature), and timeOnPage is
// derived at read time so it is omitted for the still-open last view.
interface PageEvent {
event_name: string;
page_name?: string;
pageUrl: string;
sourceMessageId: string;
timestamp: number;
activeTimeOnSite: number;
timeOnPage?: number;
[attr: string]: unknown;
}

interface RoktSelection {
context?: {
sessionId?: Promise<string>;
Expand Down Expand Up @@ -244,6 +270,15 @@ const ROKT_INTEGRATION_SCRIPT_ID = 'rokt-launcher';
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 PAGE_VIEWS_KEY = 'mpPageViews';
const MAX_PAGE_VIEWS = 25;
const PAGE_EVENTS_KEY = 'page_events';
const PAGE_EVENT_ATTR_PREFIX = 'attr_';
// The page-view event attribute surfaced as the dedicated page_name field rather
// than an attr_-namespaced key.
const PAGE_TITLE_ATTRIBUTE = 'title';

// 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 @@ -450,6 +485,12 @@ function isString(value: unknown): value is string {
return typeof value === 'string';
}

// Isolates page-view URL handling. Returns the URL verbatim for now; tightening
// to strip query/fragment (which may carry PII) later is a one-line change here.
function sanitizeUrl(href: string): string {
return href;
}

function generateIntegrationName(customIntegrationName?: string): string {
const coreSdkVersion = mp().getVersion();
const kitVersion = process.env.PACKAGE_VERSION;
Expand Down Expand Up @@ -845,6 +886,34 @@ class RoktKit implements KitInterface {
}
}

// Appends a page-view record to the persisted list under PAGE_VIEWS_KEY,
// capping at MAX_PAGE_VIEWS (oldest evicted). Wrapped so a malformed event
// can never throw out of the forwarder. Callers must confirm the event is a
// page view and that setLocalSessionAttribute is available.
private capturePageView(event: SDKEvent): void {
try {
const existing = mp().Rokt.getLocalSessionAttributes?.()?.[PAGE_VIEWS_KEY];
const pageViews: StoredPageView[] = Array.isArray(existing) ? (existing as StoredPageView[]) : [];

pageViews.push({
event_name: event.EventName,
pageUrl: sanitizeUrl(window.location.href),
sourceMessageId: event.SourceMessageId,
timestamp: event.Timestamp,
activeTimeOnSite: event.ActiveTimeOnSite,
eventAttributes: event.EventAttributes,
});

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

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

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

private buildPageEvents(pageViews: StoredPageView[]): PageEvent[] {
return pageViews.map((pv, i) => {
const flat: PageEvent = {
event_name: pv.event_name,
pageUrl: pv.pageUrl,
sourceMessageId: pv.sourceMessageId,
timestamp: pv.timestamp,
activeTimeOnSite: pv.activeTimeOnSite,
};
if (pv.eventAttributes) {
for (const [key, value] of Object.entries(pv.eventAttributes)) {
// `title` is surfaced as the dedicated page_name field below, so it is
// not also emitted as an attr_-namespaced key.
if (key === PAGE_TITLE_ATTRIBUTE) {
continue;
}
flat[`${PAGE_EVENT_ATTR_PREFIX}${key}`] = value;
}
}
flat.page_name = pv.eventAttributes?.[PAGE_TITLE_ATTRIBUTE];

// Active time on this page = diff to the next view's activeTimeOnSite.
// Omitted for the still-open last view and for negative diffs (clock skew,
// reset, out-of-order) rather than surfacing a misleading value.
const next = pageViews[i + 1];
if (next && typeof next.activeTimeOnSite === 'number' && typeof pv.activeTimeOnSite === 'number') {
const diff = next.activeTimeOnSite - pv.activeTimeOnSite;
if (diff >= 0) {
flat.timeOnPage = diff;
}
}
return flat;
});
}

private replaceOtherIdentityWithEmailsha256(userIdentities: IUserIdentities): Record<string, string> {
const newUserIdentities: Record<string, string> = { ...(userIdentities || {}) };
const key = this._mappedEmailSha256Key;
Expand Down Expand Up @@ -1165,7 +1266,12 @@ class RoktKit implements KitInterface {
if (!this.isKitReady()) {
return 'Kit not ready for forwarder: ' + name;
}

if (typeof mp().Rokt?.setLocalSessionAttribute === 'function') {
if (event.EventDataType === MESSAGE_TYPE_PAGE_VIEW) {
this.capturePageView(event);
}

if (!isEmpty(this.placementEventAttributeMappingLookup)) {
this.applyPlacementEventAttributeMapping(event);
}
Expand Down Expand Up @@ -1375,11 +1481,18 @@ class RoktKit implements KitInterface {

const localSessionAttributes = this.returnLocalSessionAttributes();

// Derive the flat page_events array from the stored page views, then drop the
// 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];

const selectPlacementsAttributes: Record<string, unknown> = {
...(filteredUserIdentities as Record<string, unknown>),
...filteredAttributes,
...optimizelyAttributes,
...localSessionAttributes,
...(pageEvents.length ? { [PAGE_EVENTS_KEY]: pageEvents } : {}),
...(this.userIdentifiedInWorkspace ? { [USER_IDENTIFIED_IN_WORKSPACE_KEY]: true } : {}),
mpid,
};
Expand Down
Loading
Loading