@mparticle/web-sdk 3.17.0 → 3.18.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.
package/dist/mparticle.js CHANGED
@@ -204,7 +204,7 @@ var mParticle = (function () {
204
204
  Base64: Base64$1
205
205
  };
206
206
 
207
- var version = "3.17.0";
207
+ var version = "3.18.0";
208
208
 
209
209
  var Constants = {
210
210
  sdkVersion: version,
@@ -370,17 +370,17 @@ var mParticle = (function () {
370
370
  CacheIdentity: 'cacheIdentity',
371
371
  AudienceAPI: 'audienceAPI',
372
372
  // CaptureIntegrationSpecificIds (legacy): boolean flag from server/UI
373
- // - 'True' → capture all integration-specific IDs
374
- // - 'False' → capture none
373
+ // - 'True' \u2192 capture all integration-specific IDs
374
+ // - 'False' \u2192 capture none
375
375
  CaptureIntegrationSpecificIds: 'captureIntegrationSpecificIds',
376
376
  // CaptureIntegrationSpecificIdsV2 (new): string mode from server
377
- // - 'all' → capture all IDs
378
- // - 'none' → capture none
379
- // - 'roktonly' → capture only Rokt-related IDs
377
+ // - 'all' \u2192 capture all IDs
378
+ // - 'none' \u2192 capture none
379
+ // - 'roktonly' \u2192 capture only Rokt-related IDs
380
380
  CaptureIntegrationSpecificIdsV2: 'captureIntegrationSpecificIds.V2',
381
381
  AstBackgroundEvents: 'astBackgroundEvents',
382
382
  AutoLogPageView: 'autoLogPageView',
383
- CheckoutIntent: 'checkoutIntent'
383
+ AutoButtonTracking: 'autoButtonTracking'
384
384
  },
385
385
  DefaultInstance: 'default_instance',
386
386
  CCPAPurpose: 'data_sale_opt_out',
@@ -697,7 +697,7 @@ var mParticle = (function () {
697
697
  return fullUrl;
698
698
  };
699
699
  // Google's cookie matching service requires `google_hm` values to be web-safe
700
- // base64 (RFC 4648 §5) without padding, otherwise the write to the hosted
700
+ // base64 (RFC 4648 \u00A75) without padding, otherwise the write to the hosted
701
701
  // match table fails: https://developers.google.com/authorized-buyers/rtb/cookie-guide
702
702
  // (String#replaceAll is not available in our ES5 browser targets.)
703
703
  var WEB_SAFE_BASE64_REPLACEMENTS = {
@@ -3975,7 +3975,7 @@ var mParticle = (function () {
3975
3975
  }
3976
3976
  // The Search endpoint is colocated with /v1/identify under
3977
3977
  // identityUrl, so we reuse the same service URL builder. We do
3978
- // NOT append the apiKey to the URL — auth is done via x-mp-key.
3978
+ // NOT append the apiKey to the URL \u2014 auth is done via x-mp-key.
3979
3979
  var serviceUrl = _Helpers.createServiceUrl(identityUrl);
3980
3980
  var searchUrl = serviceUrl + 'search?cb=1';
3981
3981
  var environment = isDevelopmentMode ? 'development' : 'production';
@@ -4617,7 +4617,7 @@ var mParticle = (function () {
4617
4617
  var domain = moduleId === PARTNER_MODULE_IDS.TradeDesk ? window.location.hostname : undefined;
4618
4618
  // Google Marketing Platform accepts a web-safe base64-encoded MPID via the
4619
4619
  // `google_hm` query parameter, but only for accounts provisioned for Google
4620
- // Hosted Matching — Google returns errors otherwise. It is therefore gated
4620
+ // Hosted Matching \u2014 Google returns errors otherwise. It is therefore gated
4621
4621
  // behind the opt-in `enableHmTag` setting (off by default), which the server
4622
4622
  // delivers as the string 'True' when the UI toggle is enabled.
4623
4623
  var enableHmTag = ((_c = settings === null || settings === void 0 ? void 0 : settings.enableHmTag) === null || _c === void 0 ? void 0 : _c.toLowerCase()) === 'true';
@@ -5790,7 +5790,7 @@ var mParticle = (function () {
5790
5790
  CaptureIntegrationSpecificIdsV2 = _a.CaptureIntegrationSpecificIdsV2,
5791
5791
  AstBackgroundEvents = _a.AstBackgroundEvents,
5792
5792
  AutoLogPageView = _a.AutoLogPageView,
5793
- CheckoutIntent = _a.CheckoutIntent;
5793
+ AutoButtonTracking = _a.AutoButtonTracking;
5794
5794
  if (!config.flags) {
5795
5795
  return {};
5796
5796
  }
@@ -5807,7 +5807,7 @@ var mParticle = (function () {
5807
5807
  flags[CaptureIntegrationSpecificIdsV2] = config.flags[CaptureIntegrationSpecificIdsV2] || 'none';
5808
5808
  flags[AstBackgroundEvents] = config.flags[AstBackgroundEvents] === 'True';
5809
5809
  flags[AutoLogPageView] = config.flags[AutoLogPageView] === 'True';
5810
- flags[CheckoutIntent] = config.flags[CheckoutIntent] === 'True';
5810
+ flags[AutoButtonTracking] = config.flags[AutoButtonTracking] === 'True';
5811
5811
  return flags;
5812
5812
  }
5813
5813
  function processBaseUrls(config, flags, apiKey) {
@@ -6703,14 +6703,14 @@ var mParticle = (function () {
6703
6703
  return !!win && win.history !== undefined && typeof win.history.pushState === 'function' && typeof win.addEventListener === 'function';
6704
6704
  };
6705
6705
  // Wraps pushState/replaceState so `onNavigate` runs after the real method, and
6706
- // returns the single function that undoes it — or null when nothing was
6706
+ // returns the single function that undoes it \u2014 or null when nothing was
6707
6707
  // installed (history is already wrapped, or a frozen/sealed History rejected the
6708
6708
  // assignment). Handing back one undo closure keeps the wrapper and original
6709
6709
  // references out of the tracker entirely: there is no half-patched state for a
6710
6710
  // caller to inspect, and no way to restore the wrong pair.
6711
6711
  var patchHistory = function patchHistory(onNavigate, log) {
6712
6712
  if (window.history.pushState[WRAPPED_MARKER]) {
6713
- log('[patch] history already wrapped, skipping to avoid double-wrap — STACKED WRAPPER DETECTED');
6713
+ log('[patch] history already wrapped, skipping to avoid double-wrap \u2014 STACKED WRAPPER DETECTED');
6714
6714
  return null;
6715
6715
  }
6716
6716
  var originals = {};
@@ -6857,14 +6857,14 @@ var mParticle = (function () {
6857
6857
  // SECURITY: `code`, `state` and `nonce` are the OAuth 2.0 / OIDC authorization
6858
6858
  // code and the CSRF/replay tokens. They are credentials until redeemed, and
6859
6859
  // attaching them here persists them in the event store and forwards them to
6860
- // every configured kit. They are on the list by explicit product decision —
6860
+ // every configured kit. They are on the list by explicit product decision \u2014
6861
6861
  // deleting that line is the whole of the fix if that decision is revisited.
6862
6862
  var ALLOWED_QUERY_PARAMS = [
6863
6863
  // Campaign attribution
6864
6864
  'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'utm_id',
6865
6865
  // Ad-network click ids
6866
6866
  'gclid', 'gbraid', 'wbraid', 'fbclid', 'msclkid', 'ttclid', 'twclid', 'li_fat_id', 'dclid',
6867
- // OAuth / OIDC — see the SECURITY note above
6867
+ // OAuth / OIDC \u2014 see the SECURITY note above
6868
6868
  'client_id', 'redirect_uri', 'response_type', 'scope', 'state', 'code', 'nonce',
6869
6869
  // Pagination
6870
6870
  'page', 'limit', 'offset', 'cursor', 'per_page',
@@ -6879,7 +6879,7 @@ var mParticle = (function () {
6879
6879
  // Referral
6880
6880
  'ref', 'referrer'];
6881
6881
  // ---------------------------------------------------------------------------
6882
- // Pure helpers. No `this`, no globals, no side effects — callable and assertable
6882
+ // Pure helpers. No `this`, no globals, no side effects \u2014 callable and assertable
6883
6883
  // on their own, which is where the interesting rules live.
6884
6884
  // ---------------------------------------------------------------------------
6885
6885
  // Pulls the allowlisted query params off a URL. Delegates to the SDK's own
@@ -6906,7 +6906,7 @@ var mParticle = (function () {
6906
6906
  // Membership is an own-property check, not `in`. `in` walks the prototype
6907
6907
  // chain, so a name matching an Object.prototype member reports as present on
6908
6908
  // any plain object. ALLOWED_QUERY_PARAMS contains no such name, which was the
6909
- // only thing making `in` safe here — and it stops being a safe assumption the
6909
+ // only thing making `in` safe here \u2014 and it stops being a safe assumption the
6910
6910
  // moment this list can be extended from configuration.
6911
6911
  var capturedNames = function capturedNames(params) {
6912
6912
  return ALLOWED_QUERY_PARAMS.filter(function (name) {
@@ -6915,12 +6915,12 @@ var mParticle = (function () {
6915
6915
  };
6916
6916
  // The dedup key: pathname plus the allowlisted params in a fixed order, so that
6917
6917
  // reordering the query string is not a new page. Params outside the allowlist
6918
- // never make it into `page.params` and so cannot key a view — nor can the hash.
6918
+ // never make it into `page.params` and so cannot key a view \u2014 nor can the hash.
6919
6919
  //
6920
6920
  // Values are re-encoded because queryStringParser hands them back DECODED. A
6921
6921
  // value holding the pair delimiters would otherwise serialize exactly like two
6922
- // separate params — `{q: 'a&search=b'}` and `{q: 'a', search: 'b'}` both becoming
6923
- // `q=a&search=b` — and dedup would treat a real navigation between them as the
6922
+ // separate params \u2014 `{q: 'a&search=b'}` and `{q: 'a', search: 'b'}` both becoming
6923
+ // `q=a&search=b` \u2014 and dedup would treat a real navigation between them as the
6924
6924
  // same page and drop the view. `q`, `search` and `redirect_uri` carry `&` and `=`
6925
6925
  // routinely, so this is reachable rather than theoretical.
6926
6926
  var pageKey = function pageKey(page) {
@@ -7036,7 +7036,7 @@ var mParticle = (function () {
7036
7036
  };
7037
7037
  };
7038
7038
  // Log-safe description of a page: the path, plus the NAMES of the captured
7039
- // params. Values are deliberately omitted — verbose logging goes to the console,
7039
+ // params. Values are deliberately omitted \u2014 verbose logging goes to the console,
7040
7040
  // and session-replay tooling ships console output off-domain, which is not a
7041
7041
  // place for an OAuth code or a consumer's search terms.
7042
7042
  // Only reachable before init() assigns lastPage, which is ahead of any navigation
@@ -7084,7 +7084,7 @@ var mParticle = (function () {
7084
7084
  // Take over from whichever tracker is currently active: one left behind by
7085
7085
  // a previous module load, or this same tracker on a repeat
7086
7086
  // mParticle.init(). Their pending navigations are transferred rather than
7087
- // dropped — a route change may have queued a deferred fire that has not
7087
+ // dropped \u2014 a route change may have queued a deferred fire that has not
7088
7088
  // flushed yet, and dropping it loses a page view entirely.
7089
7089
  var active = getActiveTracker();
7090
7090
  var inheritedPages = this.retire(active);
@@ -7124,7 +7124,7 @@ var mParticle = (function () {
7124
7124
  if (!outgoing) {
7125
7125
  return [];
7126
7126
  }
7127
- this.log(outgoing === this ? '[init] retiring this tracker for a repeat mParticle.init() — transferring pending navigations and tearing down' : '[init] retiring a tracker from a previous module load — transferring pending navigations and tearing down');
7127
+ this.log(outgoing === this ? '[init] retiring this tracker for a repeat mParticle.init() \u2014 transferring pending navigations and tearing down' : '[init] retiring a tracker from a previous module load \u2014 transferring pending navigations and tearing down');
7128
7128
  var pages = outgoing.takePendingNavigations();
7129
7129
  outgoing.teardown();
7130
7130
  return pages;
@@ -7287,7 +7287,7 @@ var mParticle = (function () {
7287
7287
  };
7288
7288
  // The auto page view for the landing page. The SPA navigations that follow it
7289
7289
  // come from PageViewTracker instead, so both emitters attach the same
7290
- // allowlisted query params — and this is the one that matters for campaign
7290
+ // allowlisted query params \u2014 and this is the one that matters for campaign
7291
7291
  // attribution, since utm_*/gclid live on the entry URL.
7292
7292
  this.logPageView = function (options) {
7293
7293
  self.logEvent({
@@ -8432,8 +8432,8 @@ var mParticle = (function () {
8432
8432
  if (isEmpty(readyQueue)) {
8433
8433
  return [];
8434
8434
  }
8435
- // Without draining first, sync re-entry (cache-hit identify →
8436
- // parseIdentityResponse → processReadyQueue) re-iterates the same array:
8435
+ // Without draining first, sync re-entry (cache-hit identify \u2192
8436
+ // parseIdentityResponse \u2192 processReadyQueue) re-iterates the same array:
8437
8437
  // callers only replace `_preInit.readyQueue` with [] after we return, so the
8438
8438
  // nested call still sees every entry. That duplicates Identity/event calls
8439
8439
  // and can nest "Unable to compute proper mParticle function" wraps.
@@ -8511,7 +8511,7 @@ var mParticle = (function () {
8511
8511
  mpInstance.Logger.verbose(Messages$2.InformationMessages.StartingLogEvent + ': ' + method);
8512
8512
  // First, remove any falsy identity values and warn about them
8513
8513
  var removedFalsyIdentityData = mpInstance._Helpers.Validators.removeFalsyIdentityValues(identityApiData, mpInstance.Logger);
8514
- // Normalize convenience aliases (email_sha256 → other, mobile_sha256 → other2)
8514
+ // Normalize convenience aliases (email_sha256 \u2192 other, mobile_sha256 \u2192 other2)
8515
8515
  var cleanedIdentityApiData = (removedFalsyIdentityData === null || removedFalsyIdentityData === void 0 ? void 0 : removedFalsyIdentityData.userIdentities) ? __assign(__assign({}, removedFalsyIdentityData), {
8516
8516
  userIdentities: normalizeUserIdentityKeys(removedFalsyIdentityData.userIdentities)
8517
8517
  }) : removedFalsyIdentityData;
@@ -9694,7 +9694,7 @@ var mParticle = (function () {
9694
9694
  * Add a GDPR Consent State to the consent state object
9695
9695
  *
9696
9696
  * @method addGDPRConsentState
9697
- * @param purpose [String] Data processing purpose that describes the type of processing done on the data subject’s data
9697
+ * @param purpose [String] Data processing purpose that describes the type of processing done on the data subject\u2019s data
9698
9698
  * @param gdprConsent [Object] A GDPR consent object created via mParticle.Consent.createGDPRConsent(...)
9699
9699
  */
9700
9700
  function addGDPRConsentState(purpose, gdprConsent) {
@@ -9730,7 +9730,7 @@ var mParticle = (function () {
9730
9730
  * Remove a GDPR Consent State to the consent state object
9731
9731
  *
9732
9732
  * @method removeGDPRConsentState
9733
- * @param purpose [String] Data processing purpose that describes the type of processing done on the data subject’s data
9733
+ * @param purpose [String] Data processing purpose that describes the type of processing done on the data subject\u2019s data
9734
9734
  */
9735
9735
  function removeGDPRConsentState(purpose) {
9736
9736
  var normalizedPurpose = canonicalizeForDeduplication(purpose);
@@ -10785,7 +10785,7 @@ var mParticle = (function () {
10785
10785
  RoktManager.prototype.isCheckoutIntentSuppressed = function () {
10786
10786
  var _a, _b;
10787
10787
  var flags = (_b = (_a = this.store) === null || _a === void 0 ? void 0 : _a.SDKConfig) === null || _b === void 0 ? void 0 : _b.flags;
10788
- if (!flags || flags[Constants.FeatureFlags.CheckoutIntent] !== true || !this.store.isEnabled) {
10788
+ if (!flags || flags[Constants.FeatureFlags.AutoButtonTracking] !== true || !this.store.isEnabled) {
10789
10789
  return true;
10790
10790
  }
10791
10791
  var options = this.launcherOptions || {};
@@ -10922,7 +10922,7 @@ var mParticle = (function () {
10922
10922
  if (isEmpty(newIdentities_1)) {
10923
10923
  this.setUserAttributes(this.currentUser, userAttributes_1);
10924
10924
  } else {
10925
- // Fire-and-forget identify — best-effort, does not block selectPlacements
10925
+ // Fire-and-forget identify \u2014 best-effort, does not block selectPlacements
10926
10926
  try {
10927
10927
  this.identityService.identify({
10928
10928
  userIdentities: __assign(__assign({}, currentUserIdentities), newIdentities_1)
@@ -11074,7 +11074,7 @@ var mParticle = (function () {
11074
11074
  /**
11075
11075
  * Tears down the current Rokt launcher and removes the placements it rendered.
11076
11076
  *
11077
- * Intended for hosts that cannot simply destroy the container element —
11077
+ * Intended for hosts that cannot simply destroy the container element \u2014
11078
11078
  * SPA route changes and confirmation pages, for example.
11079
11079
  *
11080
11080
  * Unlike the other proxied methods, a call made before the kit is ready is
@@ -11459,21 +11459,66 @@ var mParticle = (function () {
11459
11459
  return LoggingDispatcher;
11460
11460
  }();
11461
11461
 
11462
- var checkoutPath = /(?:^|\/)(?:checkouts?|payment|order|kasse|paiement|commander?|pago)(?:\/|$)/i;
11463
- var exclusions = new RegExp(['coupon|promo|newsletter|sign in|add to cart|gift card|pay later|pay in|payment plan|learn more', 'gutschein|anmelden|warenkorb|bon cadeau|connexion|panier|cupón|iniciar sesión|carrito', 'carrello|codice sconto|accedi|buono regalo|winkelwagen|kortingscode|inloggen|cadeaubon|achteraf betalen', 'カート|クーポン|ログイン|会員登録|ギフト|後払い|メルマガ'].join('|'));
11464
- var commits = new RegExp('^(?:' + ['(?:place|submit|complete|confirm|finali[sz]e)(?!(?: [^ ]+){0,2} (?:another|new|next)(?: |$))(?: [^ ]+){0,2} (?:order|purchase|booking|reservation|payment|checkout)', 'pay(?: securely)?(?: now)?|confirm and pay|order and pay|order with obligation to pay', '(?:jetzt )?(?:zahlungs|kosten)pflichtig (?:bestellen|buchen)|(?:jetzt )?bezahlen|kauf abschließen|bestellung abschließen', 'commander et payer|payer(?: maintenant)?|(?:confirmer|finaliser) (?:ma |la )?commande', "valider et payer|confirmer et payer|commande avec obligation de paiement|finaliser l'achat", '(?:realizar|confirmar|completar) (?:el |mi |la )?(?:pedido|compra|reserva)|finalizar (?:el |mi )?pedido|pagar(?: ahora)?', 'pedido con obligación de pago', "(?:effettua|conferma|concludi|completa) (?:l'|il )?(?:ordine|acquisto)|ordine con obbligo di pagamento", 'paga(?: ora| adesso)?|ordina e paga', '(?:nu )?betalen|bestellen en betalen|bestelling plaatsen|plaats bestelling|bestelling afronden', 'bestelling met betalingsverplichting', '(?:この内容で|上記の?内容で)?ご?(?:注文|購入|予約|決済|お?支払い?)を?(?:確定|完了)(?:する)?', '(?:この内容で|上記の?内容で)ご?(?:注文|購入|予約)する|ご?注文する|決済する|(?:今すぐ|を)?支払う'].join('|') + ')$');
11465
- var routeGatedCommits = new RegExp('^(?:buy now|purchase|jetzt kaufen|kaufen|commander|(?:passer|valider) (?:la |ma )?commande|finalizar (?:la |mi )?compra|' + 'comprar ahora|acquista ora|nu kopen|今すぐ購入|購入する|この内容で申し込む)$');
11466
- var steps = new RegExp('^(?:' + ['checkout|check out|check-out|continue to payment|proceed to checkout', 'zur kasse|zur kasse gehen|auschecken|weiter zur zahlung', 'passer à la caisse|continuer vers le paiement|procéder au paiement', 'ir a caja|continuar al pago|tramitar pedido', "procedi al checkout|procedi all'acquisto|procedi al pagamento|vai alla cassa", 'afrekenen|verder naar betalen|doorgaan naar betalen', 'レジに進む|ご?(?:購入|注文)手続き(?:へ|に)(?:進む)?|お?支払い(?:へ|に)進む|(?:ご注文内容の)?確認画面へ(?:進む)?'].join('|') + ')$');
11467
- var wallets = /^(?:apple pay|google pay|paypal|venmo|amazon pay|shop pay|klarna|afterpay|affirm)$/;
11468
- // Exact HTTPS hosts of hosted wallet and payment frames.
11469
- var frameHosts = new Set(['www.paypal.com', 'www.sandbox.paypal.com', 'pay.google.com', 'applepay.cdn-apple.com', 'js.stripe.com', 'checkoutshopper-live.adyen.com', 'payments.amazon.com', 'pay.shopify.com']);
11462
+ // Marks an automatically logged button click, like `is_auto_page_view`.
11463
+ var AUTO_BUTTON_ATTRIBUTE = 'is_auto_button_click';
11464
+ var BUTTON_CLICK_EVENT_NAME = 'Button Click';
11465
+ // One custom event per click, logged like logLink() with the auto marker and the
11466
+ // page fields that auto page views carry. No intent: classifying clicks is left
11467
+ // to whoever reads the data.
11468
+ var buildButtonClickEvent = function buildButtonClickEvent(click, pathname) {
11469
+ var _a;
11470
+ var data = (_a = {
11471
+ label_source: click.labelSource,
11472
+ element: click.element,
11473
+ type: click.type,
11474
+ path: pathname,
11475
+ hostname: window.location.hostname
11476
+ }, _a[AUTO_BUTTON_ATTRIBUTE] = true, _a);
11477
+ if (click.label) {
11478
+ data.label = click.label;
11479
+ }
11480
+ if (click.ariaLabel) {
11481
+ data.aria_label = click.ariaLabel;
11482
+ }
11483
+ if (click.icon) {
11484
+ data.icon = click.icon;
11485
+ }
11486
+ if (click.elementId) {
11487
+ data.element_id = click.elementId;
11488
+ }
11489
+ if (click.elementName) {
11490
+ data.element_name = click.elementName;
11491
+ }
11492
+ if (click.testId) {
11493
+ data.test_id = click.testId;
11494
+ }
11495
+ return {
11496
+ messageType: MessageType$1.PageEvent,
11497
+ name: BUTTON_CLICK_EVENT_NAME,
11498
+ data: data,
11499
+ eventType: EventType.Other
11500
+ };
11501
+ };
11502
+
11503
+ // Finds the control a click landed on, for both checkout intent and button
11504
+ // descriptions. Rokt's own elements, disabled controls and anything under
11505
+ // data-mp-no-track are never returned.
11470
11506
  var CONTROL_SELECTOR = 'button, input[type="submit"], input[type="button"], input[type="image"], [role="button"]';
11471
- function getIntentRouteClass(pathname) {
11472
- return checkoutPath.test(pathname) ? 'checkout_path' : 'other';
11507
+ // closest() over the composed tree: slots, parents, then shadow hosts.
11508
+ function hasComposedAncestor(element, selector) {
11509
+ var node = element;
11510
+ while (node) {
11511
+ if (node.matches(selector)) {
11512
+ return true;
11513
+ }
11514
+ var root = isFunction(node.getRootNode) ? node.getRootNode() : null;
11515
+ node = node.assignedSlot || node.parentElement || root && root.host || null;
11516
+ }
11517
+ return false;
11473
11518
  }
11474
11519
  // Rokt's own placements and frames never count as partner checkout controls.
11475
11520
  function isRoktElement(element) {
11476
- if (element.closest('[data-core-type], #rokt-controller-frame')) {
11521
+ if (hasComposedAncestor(element, '[data-core-type], #rokt-controller-frame')) {
11477
11522
  return true;
11478
11523
  }
11479
11524
  var src = element.getAttribute('src');
@@ -11491,22 +11536,283 @@ var mParticle = (function () {
11491
11536
  function isWalletElement(element) {
11492
11537
  return element.matches('apple-pay-button, shop-pay-button, .gpay-button, #AmazonPayButton, [data-amazon-pay-button]');
11493
11538
  }
11539
+ // Walks at most 12 entries of an event's composed path for the nearest control.
11540
+ // Rokt elements, disabled controls and anything under data-mp-no-track are skipped.
11541
+ function findControl(path) {
11542
+ var control;
11543
+ for (var _i = 0, _a = path.slice(0, 12); _i < _a.length; _i++) {
11544
+ var target = _a[_i];
11545
+ if (!(target instanceof Element)) {
11546
+ continue;
11547
+ }
11548
+ if (isRoktElement(target) || target.matches('[data-mp-no-track]')) {
11549
+ return undefined;
11550
+ }
11551
+ if (!control && (target.matches(CONTROL_SELECTOR + ', a[href]') || isWalletElement(target))) {
11552
+ control = target;
11553
+ }
11554
+ }
11555
+ if (!control || isRoktElement(control) || hasComposedAncestor(control, '[data-mp-no-track]') || control.matches(':disabled, [aria-disabled="true"]')) {
11556
+ return undefined;
11557
+ }
11558
+ return control;
11559
+ }
11560
+
11561
+ // Masks a button label before it is logged. Button text can carry personal data,
11562
+ // so this is the privacy boundary for every label and aria-label.
11563
+ // Masking needs \p{Nd} to see every script's digits. Browsers without Unicode
11564
+ // property escapes send no labels at all.
11565
+ function createDigitPatterns() {
11566
+ try {
11567
+ return {
11568
+ run: new RegExp('\\p{Nd}{6,}', 'u'),
11569
+ number: new RegExp('\\p{Nd}+(?:[.,\u060C\u066B\u066C]\\p{Nd}+)*', 'gu')
11570
+ };
11571
+ } catch (e) {
11572
+ return undefined;
11573
+ }
11574
+ }
11575
+ var digitPatterns = createDigitPatterns();
11576
+ // The label as logged: lower case, at most 64 characters, digits and amounts
11577
+ // masked. Dropped when it looks like an email, phone or card number.
11578
+ function maskLabel(raw) {
11579
+ if (!raw || raw.length > 128 || !digitPatterns) {
11580
+ return undefined;
11581
+ }
11582
+ var text = raw.replace(/\s+/g, ' ').trim().toLowerCase();
11583
+ if (!text || text.indexOf('@') !== -1 || digitPatterns.run.test(text.replace(/[\s()\-\u2010-\u2015]/g, ''))) {
11584
+ return undefined;
11585
+ }
11586
+ var masked = text.replace(digitPatterns.number, '#').slice(0, 64).trim();
11587
+ return masked || undefined;
11588
+ }
11589
+
11590
+ function elementKind(control) {
11591
+ if (isWalletElement(control)) {
11592
+ return 'wallet';
11593
+ }
11594
+ var tag = control.tagName.toLowerCase();
11595
+ return tag === 'button' || tag === 'input' ? tag : 'role';
11596
+ }
11597
+ var EDITABLE = '[contenteditable]:not([contenteditable="false"])';
11598
+ // Text that may hold what a shopper typed or what the page opted out: form
11599
+ // fields, editable regions, and anything under data-mp-no-track.
11600
+ function isUnsafeTextSource(element) {
11601
+ return element.matches('input, select, textarea, option, optgroup, datalist, ' + EDITABLE) || !!element.querySelector('input, select, textarea, [data-mp-no-track], ' + EDITABLE) || hasComposedAncestor(element, 'select, datalist, textarea, [data-mp-no-track], ' + EDITABLE);
11602
+ }
11603
+ function textOf(element) {
11604
+ return element && !isUnsafeTextSource(element) && element.textContent ? element.textContent.trim() : '';
11605
+ }
11606
+ function nonBlank(value) {
11607
+ return value && value.trim() ? value : undefined;
11608
+ }
11609
+ // The button's name as a screen reader would read it, simplified: the first of
11610
+ // aria-labelledby, aria-label, own text (or an inner image's alt), title, and for
11611
+ // inputs their value. The text sources are skipped for controls that hold form
11612
+ // fields or more than four children, so a typed value is never read.
11613
+ function accessibleName(control) {
11614
+ var labelledBy = control.getAttribute('aria-labelledby');
11615
+ if (labelledBy) {
11616
+ var root_1 = isFunction(control.getRootNode) ? control.getRootNode() : document;
11617
+ var text = labelledBy.split(/\s+/).map(function (id) {
11618
+ return textOf(root_1.getElementById ? root_1.getElementById(id) : null);
11619
+ }).filter(function (part) {
11620
+ return !!part;
11621
+ }).join(' ');
11622
+ if (text) {
11623
+ return {
11624
+ raw: text,
11625
+ source: 'labelledby'
11626
+ };
11627
+ }
11628
+ }
11629
+ var aria = nonBlank(control.getAttribute('aria-label'));
11630
+ if (aria) {
11631
+ return {
11632
+ raw: aria,
11633
+ source: 'aria'
11634
+ };
11635
+ }
11636
+ var readable = control.children.length <= 4 && !isUnsafeTextSource(control);
11637
+ if (control instanceof HTMLInputElement) {
11638
+ // The browser's normalised type: an unknown value reads as 'text'.
11639
+ var type = control.type;
11640
+ if (type === 'image') {
11641
+ var alt = nonBlank(control.getAttribute('alt'));
11642
+ if (alt) {
11643
+ return {
11644
+ raw: alt,
11645
+ source: 'alt'
11646
+ };
11647
+ }
11648
+ } else if (type === 'submit' || type === 'button' || type === 'reset') {
11649
+ var value = nonBlank(control.value);
11650
+ if (value) {
11651
+ return {
11652
+ raw: value,
11653
+ source: 'value'
11654
+ };
11655
+ }
11656
+ if (type !== 'button') {
11657
+ // What the browser shows on an input without a value.
11658
+ return {
11659
+ raw: type,
11660
+ source: 'value'
11661
+ };
11662
+ }
11663
+ }
11664
+ } else if (readable) {
11665
+ var text = textOf(control);
11666
+ if (text) {
11667
+ return {
11668
+ raw: text,
11669
+ source: 'text'
11670
+ };
11671
+ }
11672
+ var image = control.querySelector('img[alt]');
11673
+ var alt = image ? nonBlank(image.getAttribute('alt')) : undefined;
11674
+ if (alt) {
11675
+ return {
11676
+ raw: alt,
11677
+ source: 'alt'
11678
+ };
11679
+ }
11680
+ }
11681
+ var title = nonBlank(control.getAttribute('title'));
11682
+ if (title) {
11683
+ return {
11684
+ raw: title,
11685
+ source: 'title'
11686
+ };
11687
+ }
11688
+ return {
11689
+ source: 'none'
11690
+ };
11691
+ }
11692
+ var ICON_CLASS = /^(?:fa|fas|far|fal|icon|bi|glyphicon|ti|mdi|ri|la|lucide|ion|feather|ph)-([a-z][a-z-]{1,31})$/;
11693
+ // Size, style and animation classes that share the icon prefixes.
11694
+ var ICON_MODIFIER = /^(?:solid|regular|light|thin|duotone|brands|sharp|fw|lg|xs|sm|xl|spin|pulse|border|inverse|stack|flip(?:-[a-z]+)?|rotate(?:-[a-z]+)?|pull-[a-z]+)$/;
11695
+ function iconToken(candidate) {
11696
+ if (!candidate) {
11697
+ return undefined;
11698
+ }
11699
+ var token = candidate.toLowerCase().replace(/^icon-/, '');
11700
+ return /^[a-z][a-z-]{1,31}$/.test(token) ? token : undefined;
11701
+ }
11702
+ // An icon name from an SVG sprite reference or an icon-font class, for buttons
11703
+ // whose only content is an icon. Letters and hyphens only.
11704
+ function iconHint(control) {
11705
+ var allowed = function allowed(element) {
11706
+ return !hasComposedAncestor(element, '[data-mp-no-track]');
11707
+ };
11708
+ var use = Array.from(control.querySelectorAll('use')).filter(allowed)[0];
11709
+ if (use) {
11710
+ var href = use.getAttribute('href') || use.getAttribute('xlink:href') || '';
11711
+ var hash = href.indexOf('#');
11712
+ var sprite = iconToken(hash === -1 ? null : href.slice(hash + 1));
11713
+ if (sprite) {
11714
+ return sprite;
11715
+ }
11716
+ }
11717
+ var candidates = [control].concat(Array.from(control.querySelectorAll('i, span, svg'))).filter(allowed).slice(0, 5);
11718
+ for (var _i = 0, candidates_1 = candidates; _i < candidates_1.length; _i++) {
11719
+ var element = candidates_1[_i];
11720
+ var classes = (element.getAttribute('class') || '').split(/\s+/);
11721
+ for (var _a = 0, classes_1 = classes; _a < classes_1.length; _a++) {
11722
+ var name_1 = classes_1[_a];
11723
+ var match = ICON_CLASS.exec(name_1.toLowerCase());
11724
+ if (match && !ICON_MODIFIER.test(match[1])) {
11725
+ return match[1];
11726
+ }
11727
+ }
11728
+ }
11729
+ return undefined;
11730
+ }
11731
+ // Words of letters, each optionally ending in up to three digits, joined by
11732
+ // hyphens or underscores: `add-to-cart`, `btnCheckout`, `step_2`. Generated ids
11733
+ // (`ember1234`, `a8f3e2c1`, `radix-:r1:`) and long numbers don't match.
11734
+ var HAND_WRITTEN_ID = /^[A-Za-z]+[0-9]{0,3}(?:[-_](?:[A-Za-z]+[0-9]{0,3}|[0-9]{1,3}))*$/;
11735
+ // A developer-written identifier, or nothing.
11736
+ function stableIdentifier(value) {
11737
+ if (!value) {
11738
+ return undefined;
11739
+ }
11740
+ var id = value.trim();
11741
+ // At most three digits in all, so numbers split into groups don't pass.
11742
+ var digits = id.replace(/[^0-9]/g, '').length;
11743
+ return id.length <= 64 && digits <= 3 && HAND_WRITTEN_ID.test(id) ? id : undefined;
11744
+ }
11745
+ var TEST_ID_ATTRIBUTES = ['data-testid', 'data-test-id', 'data-test', 'data-qa', 'data-cy'];
11746
+ function testIdentifier(control) {
11747
+ for (var _i = 0, TEST_ID_ATTRIBUTES_1 = TEST_ID_ATTRIBUTES; _i < TEST_ID_ATTRIBUTES_1.length; _i++) {
11748
+ var attribute = TEST_ID_ATTRIBUTES_1[_i];
11749
+ var id = stableIdentifier(control.getAttribute(attribute));
11750
+ if (id) {
11751
+ return id;
11752
+ }
11753
+ }
11754
+ return undefined;
11755
+ }
11756
+ // The browser's normalised type: a button defaults to 'submit' and an unknown
11757
+ // input type reads as 'text'. Wallet and role buttons are just 'button'.
11758
+ function controlType(control, element) {
11759
+ return element === 'button' || element === 'input' ? control.type : 'button';
11760
+ }
11761
+ // Describes a clicked button for auto button tracking. Links are left to page
11762
+ // views, so a link click is not described.
11763
+ function describeButtonClick(path) {
11764
+ var control = findControl(path);
11765
+ if (!control || control.matches('a[href]:not([role="button"])')) {
11766
+ return undefined;
11767
+ }
11768
+ var element = elementKind(control);
11769
+ var name = accessibleName(control);
11770
+ var label = maskLabel(name.raw);
11771
+ return {
11772
+ label: label,
11773
+ labelSource: label ? name.source : 'none',
11774
+ // Kept on its own as well: it often says more than the visible text,
11775
+ // such as which item a remove button removes.
11776
+ ariaLabel: maskLabel(control.getAttribute('aria-label')),
11777
+ icon: iconHint(control),
11778
+ elementId: stableIdentifier(control.getAttribute('id')),
11779
+ elementName: stableIdentifier(control.getAttribute('name')),
11780
+ testId: testIdentifier(control),
11781
+ element: element,
11782
+ type: controlType(control, element)
11783
+ };
11784
+ }
11785
+
11786
+ var checkoutPath = /(?:^|\/)(?:checkouts?|payment|order|kasse|paiement|commander?|pago)(?:\/|$)/i;
11787
+ var exclusions = new RegExp(['coupon|promo|newsletter|sign in|add to cart|gift card|pay later|pay in|payment plan|learn more', 'gutschein|anmelden|warenkorb|bon cadeau|connexion|panier|cup\u00F3n|iniciar sesi\u00F3n|carrito', 'carrello|codice sconto|accedi|buono regalo|winkelwagen|kortingscode|inloggen|cadeaubon|achteraf betalen', '\u30AB\u30FC\u30C8|\u30AF\u30FC\u30DD\u30F3|\u30ED\u30B0\u30A4\u30F3|\u4F1A\u54E1\u767B\u9332|\u30AE\u30D5\u30C8|\u5F8C\u6255\u3044|\u30E1\u30EB\u30DE\u30AC'].join('|'));
11788
+ var commits = new RegExp('^(?:' + ['(?:place|submit|complete|confirm|finali[sz]e)(?!(?: [^ ]+){0,2} (?:another|new|next)(?: |$))(?: [^ ]+){0,2} (?:order|purchase|booking|reservation|payment|checkout)', 'pay(?: securely)?(?: now)?|confirm and pay|order and pay|order with obligation to pay', '(?:jetzt )?(?:zahlungs|kosten)pflichtig (?:bestellen|buchen)|(?:jetzt )?bezahlen|kauf abschlie\u00DFen|bestellung abschlie\u00DFen', 'commander et payer|payer(?: maintenant)?|(?:confirmer|finaliser) (?:ma |la )?commande', "valider et payer|confirmer et payer|commande avec obligation de paiement|finaliser l'achat", '(?:realizar|confirmar|completar) (?:el |mi |la )?(?:pedido|compra|reserva)|finalizar (?:el |mi )?pedido|pagar(?: ahora)?', 'pedido con obligaci\u00F3n de pago', "(?:effettua|conferma|concludi|completa) (?:l'|il )?(?:ordine|acquisto)|ordine con obbligo di pagamento", 'paga(?: ora| adesso)?|ordina e paga', '(?:nu )?betalen|bestellen en betalen|bestelling plaatsen|plaats bestelling|bestelling afronden', 'bestelling met betalingsverplichting', '(?:\u3053\u306E\u5185\u5BB9\u3067|\u4E0A\u8A18\u306E?\u5185\u5BB9\u3067)?\u3054?(?:\u6CE8\u6587|\u8CFC\u5165|\u4E88\u7D04|\u6C7A\u6E08|\u304A?\u652F\u6255\u3044?)\u3092?(?:\u78BA\u5B9A|\u5B8C\u4E86)(?:\u3059\u308B)?', '(?:\u3053\u306E\u5185\u5BB9\u3067|\u4E0A\u8A18\u306E?\u5185\u5BB9\u3067)\u3054?(?:\u6CE8\u6587|\u8CFC\u5165|\u4E88\u7D04)\u3059\u308B|\u3054?\u6CE8\u6587\u3059\u308B|\u6C7A\u6E08\u3059\u308B|(?:\u4ECA\u3059\u3050|\u3092)?\u652F\u6255\u3046'].join('|') + ')$');
11789
+ var routeGatedCommits = new RegExp('^(?:buy now|purchase|jetzt kaufen|kaufen|commander|(?:passer|valider) (?:la |ma )?commande|finalizar (?:la |mi )?compra|' + 'comprar ahora|acquista ora|nu kopen|\u4ECA\u3059\u3050\u8CFC\u5165|\u8CFC\u5165\u3059\u308B|\u3053\u306E\u5185\u5BB9\u3067\u7533\u3057\u8FBC\u3080)$');
11790
+ var steps = new RegExp('^(?:' + ['checkout|check out|check-out|continue to payment|proceed to checkout', 'zur kasse|zur kasse gehen|auschecken|weiter zur zahlung', 'passer \u00E0 la caisse|continuer vers le paiement|proc\u00E9der au paiement', 'ir a caja|continuar al pago|tramitar pedido', "procedi al checkout|procedi all'acquisto|procedi al pagamento|vai alla cassa", 'afrekenen|verder naar betalen|doorgaan naar betalen', '\u30EC\u30B8\u306B\u9032\u3080|\u3054?(?:\u8CFC\u5165|\u6CE8\u6587)\u624B\u7D9A\u304D(?:\u3078|\u306B)(?:\u9032\u3080)?|\u304A?\u652F\u6255\u3044(?:\u3078|\u306B)\u9032\u3080|(?:\u3054\u6CE8\u6587\u5185\u5BB9\u306E)?\u78BA\u8A8D\u753B\u9762\u3078(?:\u9032\u3080)?'].join('|') + ')$');
11791
+ var wallets = /^(?:apple pay|google pay|paypal|venmo|amazon pay|shop pay|klarna|afterpay|affirm)$/;
11792
+ // Exact HTTPS hosts of hosted wallet and payment frames.
11793
+ var frameHosts = new Set(['www.paypal.com', 'www.sandbox.paypal.com', 'pay.google.com', 'applepay.cdn-apple.com', 'js.stripe.com', 'checkoutshopper-live.adyen.com', 'payments.amazon.com', 'pay.shopify.com']);
11794
+ // Keep these classes ASCII-only in source bytes to avoid mojibake parse failures
11795
+ // when a host page forces non-UTF-8 decoding before JavaScript parse.
11796
+ var TYPOGRAPHIC_APOSTROPHES = /[\u2019\u2018]/g;
11797
+ var CURRENCY_AND_NUMBER_CHARS = /[0-9\uFF10-\uFF19$\u20AC\u00A3\u00A5\uFFE5\u20B9\u5186.,\uFF0C]/g;
11798
+ function getIntentRouteClass(pathname) {
11799
+ return checkoutPath.test(pathname) ? 'checkout_path' : 'other';
11800
+ }
11494
11801
  function normalize(text) {
11495
11802
  if (!text || text.length > 128) {
11496
11803
  return undefined;
11497
11804
  }
11498
- return text.toLowerCase().replace(/[’‘]/g, "'").replace(/[0-90-9$€£¥¥₹円.,,]/g, '').replace(/\s+/g, ' ').trim();
11805
+ return text.toLowerCase().replace(TYPOGRAPHIC_APOSTROPHES, "'").replace(CURRENCY_AND_NUMBER_CHARS, '').replace(/\s+/g, ' ').trim();
11499
11806
  }
11500
11807
  // Labels come from aria-label then visible text. Only submit and button inputs
11501
11808
  // have their value read, so a typed field value is never touched.
11502
- function labels(element) {
11503
- if (element.children.length > 4 || element.querySelector('input, select, textarea')) {
11809
+ function labelSources(element) {
11810
+ if (element.children.length > 4 || element.querySelector('input, select, textarea, [data-mp-no-track]')) {
11504
11811
  return [];
11505
11812
  }
11506
11813
  var text = null;
11507
- if (element.tagName.toLowerCase() === 'input') {
11508
- var typeAttr = element.getAttribute('type');
11509
- var type = typeAttr ? typeAttr.toLowerCase() : typeAttr;
11814
+ if (element instanceof HTMLInputElement) {
11815
+ var type = element.type;
11510
11816
  if (type === 'submit' || type === 'button') {
11511
11817
  text = element.value;
11512
11818
  } else if (type === 'image') {
@@ -11515,7 +11821,10 @@ var mParticle = (function () {
11515
11821
  } else {
11516
11822
  text = element.textContent;
11517
11823
  }
11518
- return [normalize(element.getAttribute('aria-label')), normalize(text)].filter(function (label) {
11824
+ return [element.getAttribute('aria-label'), text];
11825
+ }
11826
+ function labels(element) {
11827
+ return labelSources(element).map(normalize).filter(function (label) {
11519
11828
  return !!label;
11520
11829
  });
11521
11830
  }
@@ -11531,24 +11840,11 @@ var mParticle = (function () {
11531
11840
  }
11532
11841
  return undefined;
11533
11842
  }
11534
- // Walks at most 12 entries of an event's composed path for the nearest control.
11535
11843
  function classifyIntentControl(path, pathname) {
11536
- var control;
11537
- for (var _i = 0, _a = path.slice(0, 12); _i < _a.length; _i++) {
11538
- var target = _a[_i];
11539
- if (!(target instanceof Element)) {
11540
- continue;
11541
- }
11542
- if (isRoktElement(target)) {
11543
- return undefined;
11544
- }
11545
- if (!control && (target.matches(CONTROL_SELECTOR + ', a[href]') || isWalletElement(target))) {
11546
- control = target;
11547
- }
11548
- }
11549
- if (!control || isRoktElement(control)) {
11550
- return undefined;
11551
- }
11844
+ var control = findControl(path);
11845
+ return control ? classifyControl(control, pathname) : undefined;
11846
+ }
11847
+ function classifyControl(control, pathname) {
11552
11848
  var texts = labels(control);
11553
11849
  if (texts.some(function (text) {
11554
11850
  return exclusions.test(text);
@@ -11562,8 +11858,8 @@ var mParticle = (function () {
11562
11858
  if (button && isWalletElement(control)) {
11563
11859
  return 'wallet';
11564
11860
  }
11565
- for (var _b = 0, texts_1 = texts; _b < texts_1.length; _b++) {
11566
- var text = texts_1[_b];
11861
+ for (var _i = 0, texts_1 = texts; _i < texts_1.length; _i++) {
11862
+ var text = texts_1[_i];
11567
11863
  var kind = classifyLabel(text, button, pathname);
11568
11864
  if (kind) {
11569
11865
  return kind;
@@ -11591,30 +11887,43 @@ var mParticle = (function () {
11591
11887
  }
11592
11888
 
11593
11889
  // State lives on `window`, like APV, so a re-executed bundle never doubles the
11594
- // listeners. Inspect `window.__mpCit__` in a console to see if one is live.
11595
- var WIN_CIT_KEY = '__mpCit__';
11890
+ // listeners. Inspect `window.__mpBtn__` in a console to see if one is live.
11891
+ var WIN_BTN_KEY = '__mpBtn__';
11596
11892
  var THROTTLE_MS = 2000;
11597
11893
  var SUBMIT_AFTER_COMMIT_CLICK_MS = 1000;
11598
11894
  var PATH_LIMIT = 12;
11599
- var citWindow = function citWindow() {
11895
+ var EVENT_DEDUP_MS = 1000;
11896
+ var PAGE_EVENT_LIMIT = 50;
11897
+ var btnWindow = function btnWindow() {
11600
11898
  return typeof window === 'undefined' ? null : window;
11601
11899
  };
11602
- var getActiveCheckoutIntentTracker = function getActiveCheckoutIntentTracker() {
11603
- var win = citWindow();
11604
- var state = win ? win[WIN_CIT_KEY] : undefined;
11900
+ var getActiveButtonTracker = function getActiveButtonTracker() {
11901
+ var win = btnWindow();
11902
+ var state = win ? win[WIN_BTN_KEY] : undefined;
11605
11903
  return state ? state.tracker : undefined;
11606
11904
  };
11607
11905
  // Stops whichever tracker is active, including one from an earlier bundle load.
11608
- var resetCheckoutIntentTracking = function resetCheckoutIntentTracking() {
11609
- var win = citWindow();
11906
+ var resetButtonTracking = function resetButtonTracking() {
11907
+ var win = btnWindow();
11610
11908
  if (!win) {
11611
11909
  return;
11612
11910
  }
11613
- var active = getActiveCheckoutIntentTracker();
11911
+ var active = getActiveButtonTracker();
11614
11912
  if (active) {
11615
11913
  active.teardown();
11616
11914
  }
11617
- win[WIN_CIT_KEY] = {};
11915
+ win[WIN_BTN_KEY] = {};
11916
+ };
11917
+ // Runs once the browser has painted the page's response to the interaction, so
11918
+ // describing and logging a click never delays that paint.
11919
+ var afterNextPaint = function afterNextPaint(fn) {
11920
+ if (isFunction(window.requestAnimationFrame) && document.visibilityState !== 'hidden') {
11921
+ requestAnimationFrame(function () {
11922
+ setTimeout(fn, 0);
11923
+ });
11924
+ } else {
11925
+ setTimeout(fn, 0);
11926
+ }
11618
11927
  };
11619
11928
  var CAPTURE_PASSIVE = {
11620
11929
  capture: true,
@@ -11623,16 +11932,38 @@ var mParticle = (function () {
11623
11932
  var PASSIVE = {
11624
11933
  passive: true
11625
11934
  };
11626
- // Listens for checkout clicks, submits and wallet frame focus, and hands a
11627
- // frozen {kind, t} to the Rokt kit. No storage, no network, no event logging.
11628
- var CheckoutIntentTracker = /** @class */function () {
11629
- function CheckoutIntentTracker(mpInstance) {
11935
+ // Logs every button click as an mParticle event, and hands checkout intent to the
11936
+ // Rokt kit as a frozen {kind, t}. Submits and wallet frame focus feed the kit only.
11937
+ var ButtonTracker = /** @class */function () {
11938
+ function ButtonTracker(mpInstance) {
11630
11939
  var _this = this;
11631
11940
  this.active = false;
11941
+ this.hasRoktKit = false;
11632
11942
  // Bumped on reset so classification queued before it is dropped.
11633
11943
  this.generation = 0;
11634
11944
  this.lastCommitClick = -Infinity;
11635
11945
  this.lastSent = {};
11946
+ this.pagePath = null;
11947
+ this.pageEvents = 0;
11948
+ this.lastEvent = {};
11949
+ // Clicks read at dispatch and waiting for the next paint.
11950
+ this.pending = [];
11951
+ this.drain = function () {
11952
+ var queue = _this.pending;
11953
+ _this.pending = [];
11954
+ queue.forEach(function (delivery) {
11955
+ return delivery();
11956
+ });
11957
+ };
11958
+ // A click that navigates away can unload the page before the next paint, so
11959
+ // anything waiting is delivered as the page is hidden or left. The capture
11960
+ // listeners run before the SDK's own upload on the same events, including
11961
+ // beforeunload, the first of them.
11962
+ this.onHide = function (event) {
11963
+ if (event.type === 'beforeunload' || event.type === 'pagehide' || document.visibilityState === 'hidden') {
11964
+ _this.drain();
11965
+ }
11966
+ };
11636
11967
  this.onPageShow = function (event) {
11637
11968
  // A page restored from the back-forward cache starts fresh.
11638
11969
  if (event.persisted) {
@@ -11648,48 +11979,55 @@ var mParticle = (function () {
11648
11979
  };
11649
11980
  this.mpInstance = mpInstance;
11650
11981
  }
11651
- Object.defineProperty(CheckoutIntentTracker.prototype, "isActive", {
11982
+ Object.defineProperty(ButtonTracker.prototype, "isActive", {
11652
11983
  get: function get() {
11653
11984
  return this.active;
11654
11985
  },
11655
11986
  enumerable: false,
11656
11987
  configurable: true
11657
11988
  });
11658
- CheckoutIntentTracker.prototype.init = function () {
11659
- var win = citWindow();
11989
+ ButtonTracker.prototype.init = function (options) {
11990
+ var win = btnWindow();
11660
11991
  if (!win) {
11661
11992
  return;
11662
11993
  }
11663
- var previous = getActiveCheckoutIntentTracker();
11994
+ var previous = getActiveButtonTracker();
11664
11995
  if (previous) {
11665
11996
  previous.teardown();
11666
11997
  }
11667
11998
  if (this.active) {
11668
11999
  this.teardown();
11669
12000
  }
11670
- if (this.isSuppressed()) {
11671
- this.log('privacy settings suppress checkout intent, not starting');
12001
+ this.hasRoktKit = options.hasRoktKit;
12002
+ if (!this.eventsEnabled() && this.isKitSuppressed()) {
12003
+ this.log('opted out, not starting');
11672
12004
  return;
11673
12005
  }
11674
12006
  win.document.addEventListener('click', this.onEvent, CAPTURE_PASSIVE);
11675
12007
  win.document.addEventListener('submit', this.onEvent, CAPTURE_PASSIVE);
11676
12008
  win.addEventListener('blur', this.onEvent, PASSIVE);
11677
12009
  win.addEventListener('pageshow', this.onPageShow, PASSIVE);
12010
+ win.addEventListener('beforeunload', this.onHide, CAPTURE_PASSIVE);
12011
+ win.addEventListener('pagehide', this.onHide, CAPTURE_PASSIVE);
12012
+ win.document.addEventListener('visibilitychange', this.onHide, CAPTURE_PASSIVE);
11678
12013
  this.active = true;
11679
- if (!win[WIN_CIT_KEY]) {
11680
- win[WIN_CIT_KEY] = {};
12014
+ if (!win[WIN_BTN_KEY]) {
12015
+ win[WIN_BTN_KEY] = {};
11681
12016
  }
11682
- win[WIN_CIT_KEY].tracker = this;
12017
+ win[WIN_BTN_KEY].tracker = this;
11683
12018
  this.log('listening');
11684
12019
  };
11685
- CheckoutIntentTracker.prototype.teardown = function () {
11686
- var win = citWindow();
12020
+ ButtonTracker.prototype.teardown = function () {
12021
+ var win = btnWindow();
11687
12022
  if (win) {
11688
12023
  win.document.removeEventListener('click', this.onEvent, true);
11689
12024
  win.document.removeEventListener('submit', this.onEvent, true);
11690
12025
  win.removeEventListener('blur', this.onEvent);
11691
12026
  win.removeEventListener('pageshow', this.onPageShow);
11692
- var state = win[WIN_CIT_KEY];
12027
+ win.removeEventListener('beforeunload', this.onHide, true);
12028
+ win.removeEventListener('pagehide', this.onHide, true);
12029
+ win.document.removeEventListener('visibilitychange', this.onHide, true);
12030
+ var state = win[WIN_BTN_KEY];
11693
12031
  if (state && state.tracker === this) {
11694
12032
  state.tracker = undefined;
11695
12033
  }
@@ -11700,64 +12038,109 @@ var mParticle = (function () {
11700
12038
  };
11701
12039
  // Drops queued classification, commit timing and buffered signals, so
11702
12040
  // nothing captured before an opt-out is delivered after an opt-in.
11703
- CheckoutIntentTracker.prototype.clearPending = function () {
12041
+ ButtonTracker.prototype.clearPending = function () {
11704
12042
  this.reset();
11705
12043
  var roktManager = this.mpInstance._RoktManager;
11706
12044
  if (roktManager) {
11707
12045
  roktManager.discardCheckoutIntents();
11708
12046
  }
11709
12047
  };
11710
- CheckoutIntentTracker.prototype.reset = function () {
12048
+ ButtonTracker.prototype.reset = function () {
11711
12049
  this.generation++;
12050
+ this.pending = [];
11712
12051
  this.lastCommitClick = -Infinity;
11713
12052
  this.lastSent = {};
12053
+ this.pagePath = null;
12054
+ this.pageEvents = 0;
12055
+ this.lastEvent = {};
11714
12056
  };
11715
- CheckoutIntentTracker.prototype.observe = function (event) {
12057
+ ButtonTracker.prototype.enqueue = function (delivery) {
12058
+ this.pending.push(delivery);
12059
+ if (this.pending.length === 1) {
12060
+ afterNextPaint(this.drain);
12061
+ }
12062
+ };
12063
+ ButtonTracker.prototype.observe = function (event) {
11716
12064
  var _this = this;
11717
- if (!this.active || !event.isTrusted || this.isSuppressed()) {
12065
+ if (!this.active || !event.isTrusted) {
12066
+ return;
12067
+ }
12068
+ var layer = event.type;
12069
+ var logsEvents = layer === 'click' && this.eventsEnabled();
12070
+ if (!logsEvents && this.isKitSuppressed()) {
11718
12071
  return;
11719
12072
  }
11720
12073
  var t = Date.now();
11721
12074
  var pathname = window.location.pathname;
11722
- var layer = event.type;
11723
- // The composed path is only populated during dispatch, so copy it now.
11724
- var path = layer === 'click' && typeof event.composedPath === 'function' ? event.composedPath().slice(0, PATH_LIMIT) : [];
11725
- var target;
11726
- if (layer === 'submit') {
11727
- target = event.submitter;
11728
- } else if (layer === 'blur') {
11729
- // Read after the blur settles: some browsers have not moved focus
11730
- // into the frame yet when the window blurs.
11731
- target = null;
11732
- } else {
11733
- target = event.target;
12075
+ // The button is read now, while the click is still being dispatched:
12076
+ // this capture listener runs before the page's own handlers, so a
12077
+ // button that disables or relabels itself is seen as it was clicked.
12078
+ // Logging and the kit hand-off wait for the next paint.
12079
+ var click;
12080
+ var kind;
12081
+ var unconfirmedCommit = false;
12082
+ if (layer === 'click') {
12083
+ // The composed path is only populated during dispatch.
12084
+ var path = isFunction(event.composedPath) ? event.composedPath().slice(0, PATH_LIMIT) : [];
12085
+ click = logsEvents ? describeButtonClick(path) : undefined;
12086
+ kind = classifyIntentControl(path, pathname);
12087
+ } else if (layer === 'submit') {
12088
+ var submitter = event.submitter;
12089
+ // A form submitted without a submitter only counts right after a
12090
+ // commit click, which is known once that click is delivered.
12091
+ kind = submitter ? classifyIntentControl([submitter], pathname) : 'commit';
12092
+ unconfirmedCommit = !submitter;
11734
12093
  }
11735
12094
  var generation = this.generation;
11736
- setTimeout(function () {
12095
+ this.enqueue(function () {
11737
12096
  if (generation !== _this.generation) {
11738
12097
  return;
11739
12098
  }
11740
12099
  try {
11741
- var kind = void 0;
11742
12100
  if (layer === 'blur') {
12101
+ // Read after the blur settles: some browsers have not moved
12102
+ // focus into the frame yet when the window blurs.
11743
12103
  kind = classifyIntentFrame(document.activeElement, window);
11744
- } else if (layer === 'submit' && target === null) {
11745
- // A form submitted without a submitter only counts right
11746
- // after a commit click.
11747
- kind = t - _this.lastCommitClick <= SUBMIT_AFTER_COMMIT_CLICK_MS ? 'commit' : undefined;
11748
- } else {
11749
- kind = classifyIntentControl(layer === 'submit' ? [target] : path, pathname);
12104
+ } else if (unconfirmedCommit && t - _this.lastCommitClick > SUBMIT_AFTER_COMMIT_CLICK_MS) {
12105
+ kind = undefined;
12106
+ }
12107
+ if (click && logsEvents) {
12108
+ _this.logClick(click, t, pathname);
11750
12109
  }
11751
12110
  if (kind) {
11752
12111
  _this.emit(kind, layer, t, pathname);
11753
12112
  }
11754
12113
  } catch (e) {
11755
- _this.log('[error] classification threw: ' + e);
12114
+ _this.log('[error] delivery threw: ' + e);
11756
12115
  }
11757
- }, 0);
12116
+ });
11758
12117
  };
11759
- CheckoutIntentTracker.prototype.emit = function (kind, layer, t, pathname) {
11760
- if (this.isSuppressed()) {
12118
+ // One event per identical button every second, and at most 50 per page.
12119
+ ButtonTracker.prototype.logClick = function (click, t, pathname) {
12120
+ if (!this.eventsEnabled()) {
12121
+ return;
12122
+ }
12123
+ if (pathname !== this.pagePath) {
12124
+ this.pagePath = pathname;
12125
+ this.pageEvents = 0;
12126
+ this.lastEvent = {};
12127
+ }
12128
+ if (this.pageEvents >= PAGE_EVENT_LIMIT) {
12129
+ return;
12130
+ }
12131
+ var key = [click.label, click.ariaLabel, click.icon, click.elementId, click.elementName, click.testId, click.element, click.type].join('|');
12132
+ var last = this.lastEvent[key];
12133
+ if (last !== undefined && t - last < EVENT_DEDUP_MS) {
12134
+ return;
12135
+ }
12136
+ this.lastEvent[key] = t;
12137
+ this.pageEvents++;
12138
+ // Restarts an expired session, as public logEvent() and page views do.
12139
+ this.mpInstance._SessionManager.resetSessionTimer();
12140
+ this.mpInstance._Events.logEvent(buildButtonClickEvent(click, pathname));
12141
+ };
12142
+ ButtonTracker.prototype.emit = function (kind, layer, t, pathname) {
12143
+ if (this.isKitSuppressed()) {
11761
12144
  return;
11762
12145
  }
11763
12146
  if (kind === 'commit' && layer === 'click') {
@@ -11776,16 +12159,21 @@ var mParticle = (function () {
11776
12159
  t: t
11777
12160
  }), pathname);
11778
12161
  };
11779
- // The Rokt manager owns the privacy gates: flag, opt-out, launcher options
11780
- // and Global Privacy Control.
11781
- CheckoutIntentTracker.prototype.isSuppressed = function () {
12162
+ // Events follow the SDK's opt-out like any other event.
12163
+ ButtonTracker.prototype.eventsEnabled = function () {
12164
+ var store = this.mpInstance._Store;
12165
+ return !!store && store.isEnabled;
12166
+ };
12167
+ // The Rokt manager owns the kit's privacy gates: flag, opt-out, launcher
12168
+ // options and Global Privacy Control.
12169
+ ButtonTracker.prototype.isKitSuppressed = function () {
11782
12170
  var roktManager = this.mpInstance._RoktManager;
11783
- return !roktManager || roktManager.isCheckoutIntentSuppressed();
12171
+ return !this.hasRoktKit || !roktManager || roktManager.isCheckoutIntentSuppressed();
11784
12172
  };
11785
- CheckoutIntentTracker.prototype.log = function (message) {
11786
- this.mpInstance.Logger.verbose('mParticle checkout intent: ' + message);
12173
+ ButtonTracker.prototype.log = function (message) {
12174
+ this.mpInstance.Logger.verbose('mParticle button tracking: ' + message);
11787
12175
  };
11788
- return CheckoutIntentTracker;
12176
+ return ButtonTracker;
11789
12177
  }();
11790
12178
 
11791
12179
  var Messages = Constants.Messages,
@@ -11921,10 +12309,10 @@ var mParticle = (function () {
11921
12309
  this.reset = function (instance) {
11922
12310
  try {
11923
12311
  instance._Persistence.resetPersistence();
11924
- // The Rokt manager keeps its own store reference, so stop checkout
11925
- // intent here rather than relying on the deleted store.
11926
- resetCheckoutIntentTracking();
11927
- instance._CheckoutIntentTracker = undefined;
12312
+ // The Rokt manager keeps its own store reference, so stop button
12313
+ // tracking here rather than relying on the deleted store.
12314
+ resetButtonTracking();
12315
+ instance._ButtonTracker = undefined;
11928
12316
  if (instance._Store) {
11929
12317
  delete instance._Store;
11930
12318
  }
@@ -11946,8 +12334,8 @@ var mParticle = (function () {
11946
12334
  instance._Events.stopTracking();
11947
12335
  resetPageViewTracking();
11948
12336
  instance._PageViewTracker = undefined;
11949
- resetCheckoutIntentTracking();
11950
- instance._CheckoutIntentTracker = undefined;
12337
+ resetButtonTracking();
12338
+ instance._ButtonTracker = undefined;
11951
12339
  if (!keepPersistence) {
11952
12340
  instance._Persistence.resetPersistence();
11953
12341
  }
@@ -12547,8 +12935,8 @@ var mParticle = (function () {
12547
12935
  self._SessionManager.resetSessionTimer();
12548
12936
  self._Store.isEnabled = !isOptingOut;
12549
12937
  if (isOptingOut) {
12550
- if (self._CheckoutIntentTracker) {
12551
- self._CheckoutIntentTracker.clearPending();
12938
+ if (self._ButtonTracker) {
12939
+ self._ButtonTracker.clearPending();
12552
12940
  }
12553
12941
  self._RoktManager.discardCheckoutIntents();
12554
12942
  }
@@ -12720,7 +13108,7 @@ var mParticle = (function () {
12720
13108
  var getFeatureFlag = mpInstance._Helpers.getFeatureFlag;
12721
13109
  var _e = Constants.FeatureFlags,
12722
13110
  AutoLogPageView = _e.AutoLogPageView,
12723
- CheckoutIntent = _e.CheckoutIntent;
13111
+ AutoButtonTracking = _e.AutoButtonTracking;
12724
13112
  // Destroy previous batch uploader to prevent leaked timers and event listeners
12725
13113
  if ((_a = mpInstance._APIClient) === null || _a === void 0 ? void 0 : _a.uploader) {
12726
13114
  mpInstance._APIClient.uploader.destroy();
@@ -12736,8 +13124,8 @@ var mParticle = (function () {
12736
13124
  // persistence or Identity directly.
12737
13125
  if (mpInstance._Store.webviewBridgeEnabled) {
12738
13126
  mpInstance._NativeSdkHelpers.initializeSessionAttributes(apiKey);
12739
- // Checkout intent never runs as a native passthrough.
12740
- resetCheckoutIntentTracking();
13127
+ // Button tracking never runs as a native passthrough.
13128
+ resetButtonTracking();
12741
13129
  } else {
12742
13130
  // Main SDK initialization flow
12743
13131
  // Load any settings/identities/attributes from cookie or localStorage
@@ -12817,17 +13205,19 @@ var mParticle = (function () {
12817
13205
  // so it stops listening. Re-created if the flag is re-enabled later.
12818
13206
  mpInstance._PageViewTracker.teardown();
12819
13207
  }
12820
- // Checkout intent only feeds the Rokt kit, so it listens only when the
12821
- // workspace opts in and this init configures a Rokt kit.
12822
- if (getFeatureFlag(CheckoutIntent) && roktConfig) {
12823
- if (!mpInstance._CheckoutIntentTracker) {
12824
- mpInstance._CheckoutIntentTracker = new CheckoutIntentTracker(mpInstance);
13208
+ // Button clicks are logged for any workspace that opts in. Checkout
13209
+ // intent also goes to the Rokt kit when this init configures one.
13210
+ if (getFeatureFlag(AutoButtonTracking)) {
13211
+ if (!mpInstance._ButtonTracker) {
13212
+ mpInstance._ButtonTracker = new ButtonTracker(mpInstance);
12825
13213
  }
12826
- mpInstance._CheckoutIntentTracker.init();
13214
+ mpInstance._ButtonTracker.init({
13215
+ hasRoktKit: !!roktConfig
13216
+ });
12827
13217
  } else {
12828
13218
  // Also retires a tracker left by an earlier bundle load. Like APV,
12829
13219
  // this assumes a single active SDK instance.
12830
- resetCheckoutIntentTracking();
13220
+ resetButtonTracking();
12831
13221
  }
12832
13222
  processIdentityCallback(mpInstance, currentUser, currentUserMPID, currentUserIdentities);
12833
13223
  }
@@ -12922,8 +13312,8 @@ var mParticle = (function () {
12922
13312
  mpInstance._LoggingDispatcher.logger = mpInstance.Logger;
12923
13313
  mpInstance._Store = new Store(config, mpInstance, apiKey);
12924
13314
  window.mParticle.Store = mpInstance._Store;
12925
- // Checkout intent stops until this init's config decides whether it runs.
12926
- resetCheckoutIntentTracking();
13315
+ // Button tracking stops until this init's config decides whether it runs.
13316
+ resetButtonTracking();
12927
13317
  mpInstance.Logger.verbose(StartingInitialization);
12928
13318
  // Initialize CookieConsentManager with privacy flags from launcherOptions
12929
13319
  var _a = normalizeRoktLauncherOptions(config === null || config === void 0 ? void 0 : config.launcherOptions),