@mparticle/web-sdk 3.17.0 → 3.19.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.
@@ -66,10 +66,7 @@ import {
66
66
  PageViewTracker,
67
67
  resetPageViewTracking,
68
68
  } from './pageViewTracker';
69
- import {
70
- CheckoutIntentTracker,
71
- resetCheckoutIntentTracking,
72
- } from './checkoutIntentTracker';
69
+ import { ButtonTracker, resetButtonTracking } from './buttonTracking/buttonTracker';
73
70
 
74
71
  export interface IErrorLogMessage {
75
72
  message?: string;
@@ -103,7 +100,7 @@ export interface IMParticleWebSDKInstance extends MParticleWebSDK {
103
100
  _IntegrationCapture: IntegrationCapture;
104
101
  _NativeSdkHelpers: INativeSdkHelpers;
105
102
  _PageViewTracker?: PageViewTracker;
106
- _CheckoutIntentTracker?: CheckoutIntentTracker;
103
+ _ButtonTracker?: ButtonTracker;
107
104
  _Persistence: IPersistence;
108
105
  _CookieConsentManager: ICookieConsentManager;
109
106
  _ErrorReportingDispatcher: ErrorReportingDispatcher;
@@ -291,10 +288,10 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan
291
288
  this.reset = function(instance) {
292
289
  try {
293
290
  instance._Persistence.resetPersistence();
294
- // The Rokt manager keeps its own store reference, so stop checkout
295
- // intent here rather than relying on the deleted store.
296
- resetCheckoutIntentTracking();
297
- instance._CheckoutIntentTracker = undefined;
291
+ // The Rokt manager keeps its own store reference, so stop button
292
+ // tracking here rather than relying on the deleted store.
293
+ resetButtonTracking();
294
+ instance._ButtonTracker = undefined;
298
295
  if (instance._Store) {
299
296
  delete instance._Store;
300
297
  }
@@ -320,8 +317,8 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan
320
317
 
321
318
  resetPageViewTracking();
322
319
  instance._PageViewTracker = undefined;
323
- resetCheckoutIntentTracking();
324
- instance._CheckoutIntentTracker = undefined;
320
+ resetButtonTracking();
321
+ instance._ButtonTracker = undefined;
325
322
 
326
323
  if (!keepPersistence) {
327
324
  instance._Persistence.resetPersistence();
@@ -1145,8 +1142,8 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan
1145
1142
  self._Store.isEnabled = !isOptingOut;
1146
1143
 
1147
1144
  if (isOptingOut) {
1148
- if (self._CheckoutIntentTracker) {
1149
- self._CheckoutIntentTracker.clearPending();
1145
+ if (self._ButtonTracker) {
1146
+ self._ButtonTracker.clearPending();
1150
1147
  }
1151
1148
  self._RoktManager.discardCheckoutIntents();
1152
1149
  }
@@ -1365,7 +1362,7 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan
1365
1362
  function completeSDKInitialization(apiKey, config, mpInstance) {
1366
1363
  const kitBlocker = createKitBlocker(config, mpInstance);
1367
1364
  const { getFeatureFlag } = mpInstance._Helpers;
1368
- const { AutoLogPageView, CheckoutIntent } = Constants.FeatureFlags;
1365
+ const { AutoLogPageView, AutoButtonTracking } = Constants.FeatureFlags;
1369
1366
 
1370
1367
  // Destroy previous batch uploader to prevent leaked timers and event listeners
1371
1368
  if (mpInstance._APIClient?.uploader) {
@@ -1385,8 +1382,8 @@ function completeSDKInitialization(apiKey, config, mpInstance) {
1385
1382
  // persistence or Identity directly.
1386
1383
  if (mpInstance._Store.webviewBridgeEnabled) {
1387
1384
  mpInstance._NativeSdkHelpers.initializeSessionAttributes(apiKey);
1388
- // Checkout intent never runs as a native passthrough.
1389
- resetCheckoutIntentTracking();
1385
+ // Button tracking never runs as a native passthrough.
1386
+ resetButtonTracking();
1390
1387
  } else {
1391
1388
  // Main SDK initialization flow
1392
1389
 
@@ -1497,19 +1494,17 @@ function completeSDKInitialization(apiKey, config, mpInstance) {
1497
1494
  mpInstance._PageViewTracker.teardown();
1498
1495
  }
1499
1496
 
1500
- // Checkout intent only feeds the Rokt kit, so it listens only when the
1501
- // workspace opts in and this init configures a Rokt kit.
1502
- if (getFeatureFlag(CheckoutIntent) && roktConfig) {
1503
- if (!mpInstance._CheckoutIntentTracker) {
1504
- mpInstance._CheckoutIntentTracker = new CheckoutIntentTracker(
1505
- mpInstance
1506
- );
1497
+ // Button clicks are logged for any workspace that opts in. Checkout
1498
+ // intent also goes to the Rokt kit when this init configures one.
1499
+ if (getFeatureFlag(AutoButtonTracking)) {
1500
+ if (!mpInstance._ButtonTracker) {
1501
+ mpInstance._ButtonTracker = new ButtonTracker(mpInstance);
1507
1502
  }
1508
- mpInstance._CheckoutIntentTracker.init();
1503
+ mpInstance._ButtonTracker.init({ hasRoktKit: !!roktConfig });
1509
1504
  } else {
1510
1505
  // Also retires a tracker left by an earlier bundle load. Like APV,
1511
1506
  // this assumes a single active SDK instance.
1512
- resetCheckoutIntentTracking();
1507
+ resetButtonTracking();
1513
1508
  }
1514
1509
 
1515
1510
  processIdentityCallback(
@@ -1631,8 +1626,8 @@ function runPreConfigFetchInitialization(mpInstance, apiKey, config) {
1631
1626
  mpInstance._LoggingDispatcher.logger = mpInstance.Logger;
1632
1627
  mpInstance._Store = new Store(config, mpInstance, apiKey);
1633
1628
  window.mParticle.Store = mpInstance._Store;
1634
- // Checkout intent stops until this init's config decides whether it runs.
1635
- resetCheckoutIntentTracking();
1629
+ // Button tracking stops until this init's config decides whether it runs.
1630
+ resetButtonTracking();
1636
1631
  mpInstance.Logger.verbose(StartingInitialization);
1637
1632
 
1638
1633
  // Initialize CookieConsentManager with privacy flags from launcherOptions
@@ -23,7 +23,7 @@ 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
+ import type { CheckoutIntentKind } from "./buttonTracking/checkoutIntent";
27
27
 
28
28
  const PASSBACK_CONVERSION_TRACKING_ID = 'passbackconversiontrackingid';
29
29
  const CHECKOUT_INTENT_BUFFER_LIMIT = 4;
@@ -303,7 +303,7 @@ export default class RoktManager {
303
303
  // privacy launcher option, or Global Privacy Control.
304
304
  public isCheckoutIntentSuppressed(): boolean {
305
305
  const flags = this.store?.SDKConfig?.flags;
306
- if (!flags || flags[Constants.FeatureFlags.CheckoutIntent] !== true || !this.store.isEnabled) {
306
+ if (!flags || flags[Constants.FeatureFlags.AutoButtonTracking] !== true || !this.store.isEnabled) {
307
307
  return true;
308
308
  }
309
309
  const options = this.launcherOptions || {};
package/src/store.ts CHANGED
@@ -153,7 +153,7 @@ export interface IFeatureFlags {
153
153
  'captureIntegrationSpecificIds.V2'?: string;
154
154
  astBackgroundEvents?: boolean;
155
155
  autoLogPageView?: boolean;
156
- checkoutIntent?: boolean;
156
+ autoButtonTracking?: boolean;
157
157
  }
158
158
 
159
159
  // Temporary Interface until Store can be refactored as a class
@@ -778,7 +778,7 @@ export function processFlags(config: SDKInitConfig): IFeatureFlags {
778
778
  CaptureIntegrationSpecificIdsV2,
779
779
  AstBackgroundEvents,
780
780
  AutoLogPageView,
781
- CheckoutIntent,
781
+ AutoButtonTracking,
782
782
  } = Constants.FeatureFlags;
783
783
 
784
784
  if (!config.flags) {
@@ -800,7 +800,7 @@ export function processFlags(config: SDKInitConfig): IFeatureFlags {
800
800
  flags[CaptureIntegrationSpecificIdsV2] = (config.flags[CaptureIntegrationSpecificIdsV2] || 'none');
801
801
  flags[AstBackgroundEvents] = config.flags[AstBackgroundEvents] === 'True';
802
802
  flags[AutoLogPageView] = config.flags[AutoLogPageView] === 'True';
803
- flags[CheckoutIntent] = config.flags[CheckoutIntent] === 'True';
803
+ flags[AutoButtonTracking] = config.flags[AutoButtonTracking] === 'True';
804
804
  return flags;
805
805
  }
806
806
 
@@ -1,4 +0,0 @@
1
- export type CheckoutIntentKind = 'commit' | 'wallet' | 'checkout_step' | 'frame';
2
- export declare function getIntentRouteClass(pathname: string): 'checkout_path' | 'other';
3
- export declare function classifyIntentControl(path: Array<EventTarget | null>, pathname: string): CheckoutIntentKind | undefined;
4
- export declare function classifyIntentFrame(target: EventTarget | null, win: Window): 'frame' | undefined;
@@ -1,23 +0,0 @@
1
- import { IMParticleWebSDKInstance } from './mp-instance';
2
- export declare const WIN_CIT_KEY = "__mpCit__";
3
- export declare const getActiveCheckoutIntentTracker: () => CheckoutIntentTracker | undefined;
4
- export declare const resetCheckoutIntentTracking: () => void;
5
- export declare class CheckoutIntentTracker {
6
- private readonly mpInstance;
7
- private active;
8
- private generation;
9
- private lastCommitClick;
10
- private lastSent;
11
- constructor(mpInstance: IMParticleWebSDKInstance);
12
- get isActive(): boolean;
13
- init(): void;
14
- teardown(): void;
15
- clearPending(): void;
16
- private reset;
17
- private readonly onPageShow;
18
- private readonly onEvent;
19
- private observe;
20
- private emit;
21
- private isSuppressed;
22
- private log;
23
- }
@@ -1,244 +0,0 @@
1
- import { IMParticleWebSDKInstance } from './mp-instance';
2
- import {
3
- CheckoutIntentKind,
4
- classifyIntentControl,
5
- classifyIntentFrame,
6
- } from './checkoutIntentClassifier';
7
-
8
- // State lives on `window`, like APV, so a re-executed bundle never doubles the
9
- // listeners. Inspect `window.__mpCit__` in a console to see if one is live.
10
- export const WIN_CIT_KEY = '__mpCit__';
11
-
12
- const THROTTLE_MS = 2000;
13
- const SUBMIT_AFTER_COMMIT_CLICK_MS = 1000;
14
- const PATH_LIMIT = 12;
15
-
16
- interface ICitState {
17
- tracker?: CheckoutIntentTracker;
18
- }
19
-
20
- type WindowWithCit = Window & {
21
- [WIN_CIT_KEY]?: ICitState;
22
- };
23
-
24
- const citWindow = (): WindowWithCit | null =>
25
- typeof window === 'undefined' ? null : (window as WindowWithCit);
26
-
27
- export const getActiveCheckoutIntentTracker = ():
28
- | CheckoutIntentTracker
29
- | undefined => {
30
- const win = citWindow();
31
- const state = win ? win[WIN_CIT_KEY] : undefined;
32
- return state ? state.tracker : undefined;
33
- };
34
-
35
- // Stops whichever tracker is active, including one from an earlier bundle load.
36
- export const resetCheckoutIntentTracking = (): void => {
37
- const win = citWindow();
38
- if (!win) {
39
- return;
40
- }
41
- const active = getActiveCheckoutIntentTracker();
42
- if (active) {
43
- active.teardown();
44
- }
45
- win[WIN_CIT_KEY] = {};
46
- };
47
-
48
- const CAPTURE_PASSIVE: AddEventListenerOptions = {
49
- capture: true,
50
- passive: true,
51
- };
52
- const PASSIVE: AddEventListenerOptions = { passive: true };
53
-
54
- // Listens for checkout clicks, submits and wallet frame focus, and hands a
55
- // frozen {kind, t} to the Rokt kit. No storage, no network, no event logging.
56
- export class CheckoutIntentTracker {
57
- private readonly mpInstance: IMParticleWebSDKInstance;
58
- private active = false;
59
- // Bumped on reset so classification queued before it is dropped.
60
- private generation = 0;
61
- private lastCommitClick = -Infinity;
62
- private lastSent: { [kind: string]: { t: number; pathname: string } } = {};
63
-
64
- constructor(mpInstance: IMParticleWebSDKInstance) {
65
- this.mpInstance = mpInstance;
66
- }
67
-
68
- public get isActive(): boolean {
69
- return this.active;
70
- }
71
-
72
- public init(): void {
73
- const win = citWindow();
74
- if (!win) {
75
- return;
76
- }
77
-
78
- const previous = getActiveCheckoutIntentTracker();
79
- if (previous) {
80
- previous.teardown();
81
- }
82
- if (this.active) {
83
- this.teardown();
84
- }
85
-
86
- if (this.isSuppressed()) {
87
- this.log('privacy settings suppress checkout intent, not starting');
88
- return;
89
- }
90
-
91
- win.document.addEventListener('click', this.onEvent, CAPTURE_PASSIVE);
92
- win.document.addEventListener('submit', this.onEvent, CAPTURE_PASSIVE);
93
- win.addEventListener('blur', this.onEvent, PASSIVE);
94
- win.addEventListener('pageshow', this.onPageShow, PASSIVE);
95
-
96
- this.active = true;
97
- if (!win[WIN_CIT_KEY]) {
98
- win[WIN_CIT_KEY] = {};
99
- }
100
- win[WIN_CIT_KEY].tracker = this;
101
- this.log('listening');
102
- }
103
-
104
- public teardown(): void {
105
- const win = citWindow();
106
- if (win) {
107
- win.document.removeEventListener('click', this.onEvent, true);
108
- win.document.removeEventListener('submit', this.onEvent, true);
109
- win.removeEventListener('blur', this.onEvent);
110
- win.removeEventListener('pageshow', this.onPageShow);
111
-
112
- const state = win[WIN_CIT_KEY];
113
- if (state && state.tracker === this) {
114
- state.tracker = undefined;
115
- }
116
- }
117
- this.active = false;
118
- // Signals not yet handed to a kit go with the tracker.
119
- this.clearPending();
120
- }
121
-
122
- // Drops queued classification, commit timing and buffered signals, so
123
- // nothing captured before an opt-out is delivered after an opt-in.
124
- public clearPending(): void {
125
- this.reset();
126
- const roktManager = this.mpInstance._RoktManager;
127
- if (roktManager) {
128
- roktManager.discardCheckoutIntents();
129
- }
130
- }
131
-
132
- private reset(): void {
133
- this.generation++;
134
- this.lastCommitClick = -Infinity;
135
- this.lastSent = {};
136
- }
137
-
138
- private readonly onPageShow = (event: PageTransitionEvent): void => {
139
- // A page restored from the back-forward cache starts fresh.
140
- if (event.persisted) {
141
- this.reset();
142
- }
143
- };
144
-
145
- private readonly onEvent = (event: Event): void => {
146
- try {
147
- this.observe(event);
148
- } catch (e) {
149
- this.log('[error] event handler threw: ' + e);
150
- }
151
- };
152
-
153
- private observe(event: Event): void {
154
- if (!this.active || !event.isTrusted || this.isSuppressed()) {
155
- return;
156
- }
157
-
158
- const t = Date.now();
159
- const pathname = window.location.pathname;
160
- const layer = event.type;
161
- // The composed path is only populated during dispatch, so copy it now.
162
- const path =
163
- layer === 'click' && typeof event.composedPath === 'function'
164
- ? event.composedPath().slice(0, PATH_LIMIT)
165
- : [];
166
- let target: EventTarget | null;
167
- if (layer === 'submit') {
168
- target = (event as Event & { submitter?: Element | null })
169
- .submitter;
170
- } else if (layer === 'blur') {
171
- // Read after the blur settles: some browsers have not moved focus
172
- // into the frame yet when the window blurs.
173
- target = null;
174
- } else {
175
- target = event.target;
176
- }
177
-
178
- const generation = this.generation;
179
- setTimeout(() => {
180
- if (generation !== this.generation) {
181
- return;
182
- }
183
- try {
184
- let kind: CheckoutIntentKind | undefined;
185
- if (layer === 'blur') {
186
- kind = classifyIntentFrame(document.activeElement, window);
187
- } else if (layer === 'submit' && target === null) {
188
- // A form submitted without a submitter only counts right
189
- // after a commit click.
190
- kind =
191
- t - this.lastCommitClick <= SUBMIT_AFTER_COMMIT_CLICK_MS
192
- ? 'commit'
193
- : undefined;
194
- } else {
195
- kind = classifyIntentControl(
196
- layer === 'submit' ? [target] : path,
197
- pathname
198
- );
199
- }
200
- if (kind) {
201
- this.emit(kind, layer, t, pathname);
202
- }
203
- } catch (e) {
204
- this.log('[error] classification threw: ' + e);
205
- }
206
- }, 0);
207
- }
208
-
209
- private emit(
210
- kind: CheckoutIntentKind,
211
- layer: string,
212
- t: number,
213
- pathname: string
214
- ): void {
215
- if (this.isSuppressed()) {
216
- return;
217
- }
218
- if (kind === 'commit' && layer === 'click') {
219
- this.lastCommitClick = t;
220
- }
221
-
222
- const last = this.lastSent[kind];
223
- if (last && last.pathname === pathname && t - last.t < THROTTLE_MS) {
224
- return;
225
- }
226
- this.lastSent[kind] = { t, pathname };
227
-
228
- this.mpInstance._RoktManager.notifyCheckoutIntent(
229
- Object.freeze({ kind, t }),
230
- pathname
231
- );
232
- }
233
-
234
- // The Rokt manager owns the privacy gates: flag, opt-out, launcher options
235
- // and Global Privacy Control.
236
- private isSuppressed(): boolean {
237
- const roktManager = this.mpInstance._RoktManager;
238
- return !roktManager || roktManager.isCheckoutIntentSuppressed();
239
- }
240
-
241
- private log(message: string): void {
242
- this.mpInstance.Logger.verbose('mParticle checkout intent: ' + message);
243
- }
244
- }