@mparticle/web-sdk 3.16.1 → 3.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,8 +23,11 @@ import { PARTNER_MODULE_IDS } from "./cookieSyncManager";
23
23
  import IntegrationCapture from "./integrationCapture";
24
24
  import Constants from "./constants";
25
25
  import { subscribeToRouteChange } from "./routeChangeMonitor";
26
+ import type { CheckoutIntentKind } from "./checkoutIntentClassifier";
26
27
 
27
28
  const PASSBACK_CONVERSION_TRACKING_ID = 'passbackconversiontrackingid';
29
+ const CHECKOUT_INTENT_BUFFER_LIMIT = 4;
30
+ const CHECKOUT_INTENT_MAX_AGE_MS = 30000;
28
31
 
29
32
  type AttributeTargetUser = Pick<IMParticleUser, 'getUserIdentities' | 'setUserAttributes'>;
30
33
 
@@ -71,6 +74,11 @@ export interface RoktKitFilterSettings {
71
74
  filteredUser?: IMParticleUser | null;
72
75
  }
73
76
 
77
+ export interface ICheckoutIntentSignal {
78
+ kind: CheckoutIntentKind;
79
+ t: number;
80
+ }
81
+
74
82
  export interface IRoktKitSettings {
75
83
  accountId?: string;
76
84
  placementAttributesMapping?: string;
@@ -93,6 +101,8 @@ export interface IRoktKit {
93
101
  // Set by the kit only when it wants route changes; nothing subscribes otherwise. The
94
102
  // manager also calls it once on attach.
95
103
  onRouteChange?: () => void;
104
+ // Set by the kit only when it wants checkout intent signals.
105
+ onCheckoutIntent?: (signal: ICheckoutIntentSignal) => void;
96
106
  launcherOptions?: Dictionary<any>;
97
107
  settings?: IRoktKitSettings;
98
108
  integrationName?: string;
@@ -138,6 +148,8 @@ export default class RoktManager {
138
148
  private initialized: boolean = false;
139
149
  private isShoppableAdsLoaded: boolean = false;
140
150
  private stopRouteChangeWatch: (() => void) | null = null;
151
+ // Checkout intent raised before the kit attached: latest per kind.
152
+ private pendingCheckoutIntents: Array<{ signal: ICheckoutIntentSignal; pathname: string }> = [];
141
153
 
142
154
  // Keys this manager's route subscription, so a re-executed bundle's manager replaces
143
155
  // it while a second named instance keeps its own.
@@ -232,6 +244,7 @@ export default class RoktManager {
232
244
  this.kit = kit;
233
245
 
234
246
  this.watchRouteChanges();
247
+ this.flushCheckoutIntents();
235
248
 
236
249
  if (kit.settings?.accountId) {
237
250
  this.store.setRoktAccountId(kit.settings.accountId);
@@ -286,6 +299,69 @@ export default class RoktManager {
286
299
  }
287
300
  }
288
301
 
302
+ // Checkout intent is off unless the flag is on, and under opt-out, any Rokt
303
+ // privacy launcher option, or Global Privacy Control.
304
+ public isCheckoutIntentSuppressed(): boolean {
305
+ const flags = this.store?.SDKConfig?.flags;
306
+ if (!flags || flags[Constants.FeatureFlags.CheckoutIntent] !== true || !this.store.isEnabled) {
307
+ return true;
308
+ }
309
+ const options = this.launcherOptions || {};
310
+ if (options.noFunctional || options.noTargeting || options.doNotShareOrSell) {
311
+ return true;
312
+ }
313
+ const nav = typeof navigator === 'undefined' ? undefined : (navigator as Navigator & { globalPrivacyControl?: boolean });
314
+ return !!nav && nav.globalPrivacyControl === true;
315
+ }
316
+
317
+ // Reads `this.kit` at call time; before a kit attaches the signal is buffered
318
+ // with the path it was raised on.
319
+ public notifyCheckoutIntent(signal: ICheckoutIntentSignal, pathname: string = window.location.pathname): void {
320
+ if (this.isCheckoutIntentSuppressed()) {
321
+ return;
322
+ }
323
+
324
+ const kit = this.kit;
325
+ if (!kit) {
326
+ this.pendingCheckoutIntents = this.pendingCheckoutIntents
327
+ .filter(pending => pending.signal.kind !== signal.kind)
328
+ .concat({ signal, pathname })
329
+ .slice(-CHECKOUT_INTENT_BUFFER_LIMIT);
330
+ return;
331
+ }
332
+
333
+ try {
334
+ if (isFunction(kit.onCheckoutIntent)) {
335
+ kit.onCheckoutIntent(signal);
336
+ }
337
+ } catch (e) {
338
+ this.logger?.error(
339
+ `RoktManager: Error in onCheckoutIntent: ${getErrorMessage(e)}`
340
+ );
341
+ }
342
+ }
343
+
344
+ public discardCheckoutIntents(): void {
345
+ this.pendingCheckoutIntents = [];
346
+ }
347
+
348
+ // Replays recent buffered signals raised on the current page, oldest first.
349
+ private flushCheckoutIntents(): void {
350
+ const pending = this.pendingCheckoutIntents;
351
+ this.pendingCheckoutIntents = [];
352
+
353
+ const now = Date.now();
354
+ const pathname = window.location.pathname;
355
+ pending
356
+ .filter(
357
+ ({ signal, pathname: signalPathname }) =>
358
+ now - signal.t < CHECKOUT_INTENT_MAX_AGE_MS &&
359
+ signalPathname === pathname
360
+ )
361
+ .sort((a, b) => a.signal.t - b.signal.t)
362
+ .forEach(({ signal }) => this.notifyCheckoutIntent(signal));
363
+ }
364
+
289
365
  private isAutoLogPageViewEnabled(): boolean {
290
366
  return (
291
367
  this.store?.SDKConfig?.flags?.[
package/src/store.ts CHANGED
@@ -153,6 +153,7 @@ export interface IFeatureFlags {
153
153
  'captureIntegrationSpecificIds.V2'?: string;
154
154
  astBackgroundEvents?: boolean;
155
155
  autoLogPageView?: boolean;
156
+ checkoutIntent?: boolean;
156
157
  }
157
158
 
158
159
  // Temporary Interface until Store can be refactored as a class
@@ -776,7 +777,8 @@ export function processFlags(config: SDKInitConfig): IFeatureFlags {
776
777
  CaptureIntegrationSpecificIds,
777
778
  CaptureIntegrationSpecificIdsV2,
778
779
  AstBackgroundEvents,
779
- AutoLogPageView
780
+ AutoLogPageView,
781
+ CheckoutIntent,
780
782
  } = Constants.FeatureFlags;
781
783
 
782
784
  if (!config.flags) {
@@ -798,6 +800,7 @@ export function processFlags(config: SDKInitConfig): IFeatureFlags {
798
800
  flags[CaptureIntegrationSpecificIdsV2] = (config.flags[CaptureIntegrationSpecificIdsV2] || 'none');
799
801
  flags[AstBackgroundEvents] = config.flags[AstBackgroundEvents] === 'True';
800
802
  flags[AutoLogPageView] = config.flags[AutoLogPageView] === 'True';
803
+ flags[CheckoutIntent] = config.flags[CheckoutIntent] === 'True';
801
804
  return flags;
802
805
  }
803
806