@mparticle/web-sdk 3.23.0 → 3.25.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.
Files changed (39) hide show
  1. package/dist/mparticle.common.js +1 -1688
  2. package/dist/mparticle.esm.js +1 -1688
  3. package/dist/mparticle.js +1015 -253
  4. package/dist/types/src/buttonTracking/buttonClickEvent.d.ts +7 -0
  5. package/dist/types/src/buttonTracking/buttonDescription.d.ts +10 -3
  6. package/dist/types/src/buttonTracking/buttonTracker.d.ts +8 -0
  7. package/dist/types/src/buttonTracking/checkoutIntent.d.ts +8 -3
  8. package/dist/types/src/buttonTracking/controls.d.ts +8 -4
  9. package/dist/types/src/buttonTracking/labelMasking.d.ts +1 -0
  10. package/dist/types/src/configAPIClient.d.ts +1 -0
  11. package/dist/types/src/constants.d.ts +1 -0
  12. package/dist/types/src/cookieConsentManager.d.ts +17 -3
  13. package/dist/types/src/forwarder-utils.d.ts +3 -0
  14. package/dist/types/src/pageViewTracker.d.ts +19 -3
  15. package/dist/types/src/roktLauncherOptions.d.ts +1 -0
  16. package/dist/types/src/roktManager.d.ts +13 -2
  17. package/dist/types/src/sdkRuntimeModels.d.ts +1 -0
  18. package/dist/types/src/store.d.ts +3 -0
  19. package/package.json +21 -14
  20. package/src/buttonTracking/buttonClickEvent.ts +126 -8
  21. package/src/buttonTracking/buttonDescription.ts +336 -53
  22. package/src/buttonTracking/buttonTracker.ts +231 -19
  23. package/src/buttonTracking/checkoutIntent.ts +117 -23
  24. package/src/buttonTracking/controls.ts +169 -55
  25. package/src/buttonTracking/labelMasking.ts +4 -1
  26. package/src/configAPIClient.ts +1 -0
  27. package/src/constants.ts +1 -0
  28. package/src/cookieConsentManager.ts +23 -3
  29. package/src/cookieSyncManager.ts +11 -3
  30. package/src/events.ts +9 -3
  31. package/src/forwarder-utils.ts +38 -0
  32. package/src/forwarders.ts +2 -28
  33. package/src/mp-instance.ts +41 -7
  34. package/src/pageUrl.ts +43 -2
  35. package/src/pageViewTracker.ts +193 -111
  36. package/src/roktLauncherOptions.ts +1 -0
  37. package/src/roktManager.ts +66 -11
  38. package/src/sdkRuntimeModels.ts +1 -0
  39. package/src/store.ts +17 -0
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.23.0";
207
+ var version = "3.25.0";
208
208
 
209
209
  var Constants = {
210
210
  sdkVersion: version,
@@ -315,7 +315,6 @@ var mParticle = (function () {
315
315
  aliasMaxWindow: 90,
316
316
  uploadInterval: 0 // Maximum milliseconds in between batch uploads, below 500 will mean immediate upload. The server returns this as a string, but we are using it as a number internally
317
317
  },
318
-
319
318
  DefaultBaseUrls: {
320
319
  v1SecureServiceUrl: 'jssdks.mparticle.com/v1/JS/',
321
320
  v2SecureServiceUrl: 'jssdks.mparticle.com/v2/JS/',
@@ -380,7 +379,8 @@ var mParticle = (function () {
380
379
  CaptureIntegrationSpecificIdsV2: 'captureIntegrationSpecificIds.V2',
381
380
  AstBackgroundEvents: 'astBackgroundEvents',
382
381
  AutoLogPageView: 'autoLogPageView',
383
- AutoButtonTracking: 'autoButtonTracking'
382
+ AutoButtonTracking: 'autoButtonTracking',
383
+ AutoLinkTracking: 'autoLinkTracking'
384
384
  },
385
385
  DefaultInstance: 'default_instance',
386
386
  CCPAPurpose: 'data_sale_opt_out',
@@ -601,14 +601,13 @@ var mParticle = (function () {
601
601
  // @ts-ignore
602
602
  randomValue = window.crypto.getRandomValues(new Uint8Array(1)); // eslint-disable-line no-undef
603
603
  }
604
-
605
604
  if (randomValue) {
606
605
  // @ts-ignore
607
606
  return (a ^ randomValue[0] % 16 >> a / 4).toString(16);
608
607
  }
609
608
  return (a ^ Math.random() * 16 >> a / 4).toString(16);
610
609
  };
611
- var generateUniqueId = function generateUniqueId(a) {
610
+ var _generateUniqueId = function generateUniqueId(a) {
612
611
  // https://gist.github.com/jed/982883
613
612
  // Added support for crypto for better random
614
613
  if (a === void 0) {
@@ -624,7 +623,7 @@ var mParticle = (function () {
624
623
  // -1e11 -> //-100000000000,
625
624
  "".concat(1e7, "-").concat(1e3, "-").concat(4e3, "-").concat(8e3, "-").concat(1e11).replace(/[018]/g,
626
625
  // zeroes, ones, and eights with
627
- generateUniqueId // random hex digits
626
+ _generateUniqueId // random hex digits
628
627
  );
629
628
  };
630
629
  /**
@@ -930,7 +929,7 @@ var mParticle = (function () {
930
929
  * obfuscateData({ tags: ['premium', 'verified'] })
931
930
  * // Returns: { tags: ['string', 'string'] }
932
931
  */
933
- var obfuscateData = function obfuscateData(value) {
932
+ var _obfuscateData = function obfuscateData(value) {
934
933
  // Preserve null and undefined
935
934
  if (value === null || value === undefined) {
936
935
  return value;
@@ -938,7 +937,7 @@ var mParticle = (function () {
938
937
  // Handle arrays - recursively obfuscate each element
939
938
  if (Array.isArray(value)) {
940
939
  return value.map(function (item) {
941
- return obfuscateData(item);
940
+ return _obfuscateData(item);
942
941
  });
943
942
  }
944
943
  // Handle objects - recursively obfuscate each property
@@ -946,7 +945,7 @@ var mParticle = (function () {
946
945
  var obfuscated = {};
947
946
  for (var key in value) {
948
947
  if (value.hasOwnProperty(key)) {
949
- obfuscated[key] = obfuscateData(value[key]);
948
+ obfuscated[key] = _obfuscateData(value[key]);
950
949
  }
951
950
  }
952
951
  return obfuscated;
@@ -970,7 +969,7 @@ var mParticle = (function () {
970
969
  * // Returns: { email: 'string' }
971
970
  */
972
971
  var obfuscateDevData = function obfuscateDevData(data, isDevelopmentMode) {
973
- return isDevelopmentMode ? data : obfuscateData(data);
972
+ return isDevelopmentMode ? data : _obfuscateData(data);
974
973
  };
975
974
  // Standalone version of jQuery.extend, from https://github.com/dansdom/extend
976
975
  // https://go.mparticle.com/work/SQDSDKS-6047
@@ -2183,7 +2182,7 @@ var mParticle = (function () {
2183
2182
  application_info: {
2184
2183
  application_version: lastEvent.AppVersion,
2185
2184
  application_name: lastEvent.AppName,
2186
- "package": lastEvent.Package,
2185
+ package: lastEvent.Package,
2187
2186
  sideloaded_kits_count: mpInstance._Store.sideloadedKitsCount
2188
2187
  },
2189
2188
  device_info: {
@@ -2523,7 +2522,6 @@ var mParticle = (function () {
2523
2522
  //note: External Events DTO does not support the session mpids array as of this time.
2524
2523
  //spanning_mpids: sdkEvent.SessionMpids
2525
2524
  };
2526
-
2527
2525
  sessionEndEventData = Object.assign(sessionEndEventData, commonEventData);
2528
2526
  return {
2529
2527
  event_type: dist.EventTypeEnum.sessionEnd,
@@ -2652,7 +2650,7 @@ var mParticle = (function () {
2652
2650
  var commonEventData = convertBaseEventData(sdkEvent);
2653
2651
  var userAttributeChangeEvent = {
2654
2652
  user_attribute_name: sdkEvent.UserAttributeChanges.UserAttributeName,
2655
- "new": sdkEvent.UserAttributeChanges.New,
2653
+ new: sdkEvent.UserAttributeChanges.New,
2656
2654
  old: sdkEvent.UserAttributeChanges.Old,
2657
2655
  deleted: sdkEvent.UserAttributeChanges.Deleted,
2658
2656
  is_new_attribute: sdkEvent.UserAttributeChanges.IsNewAttribute
@@ -2666,7 +2664,7 @@ var mParticle = (function () {
2666
2664
  function convertUserIdentityChangeEvent(sdkEvent) {
2667
2665
  var commonEventData = convertBaseEventData(sdkEvent);
2668
2666
  var userIdentityChangeEvent = {
2669
- "new": {
2667
+ new: {
2670
2668
  identity_type: convertUserIdentityTypeToServerIdentityType(sdkEvent.UserIdentityChanges.New.IdentityType),
2671
2669
  identity: sdkEvent.UserIdentityChanges.New.Identity || null,
2672
2670
  timestamp_unixtime_ms: sdkEvent.Timestamp,
@@ -2886,23 +2884,9 @@ var mParticle = (function () {
2886
2884
  // Nothing has to be deleted, and there is no ordering in which half the state
2887
2885
  // survives the reset.
2888
2886
  var WIN_APV_KEY = '__mpApv__';
2889
- // The query params an auto page view may carry. An allowlist, not a denylist:
2890
- // partner URLs routinely hold order ids, email addresses and session tokens, and
2891
- // none of those should reach the event stream because someone forgot to exclude
2892
- // them. Anything absent from this list is dropped.
2893
- //
2894
- // SECURITY: `code`, `state` and `nonce` are the OAuth 2.0 / OIDC authorization
2895
- // code and the CSRF/replay tokens. They are credentials until redeemed, and
2896
- // attaching them here persists them in the event store and forwards them to
2897
- // every configured kit. They are on the list by explicit product decision \u2014
2898
- // deleting that line is the whole of the fix if that decision is revisited.
2887
+ // Parameters that identify which page a view is of. Authorization-flow and campaign
2888
+ // attribution parameters are deliberately absent; an input can add them explicitly.
2899
2889
  var ALLOWED_QUERY_PARAMS = [
2900
- // Campaign attribution
2901
- 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'utm_id', 'utm_source_platform', 'utm_creative_format', 'utm_marketing_tactic', 'gad_source', 'gad_campaignid', 'mc_cid',
2902
- // Ad-network click ids
2903
- 'gclid', 'gbraid', 'wbraid', 'fbclid', 'msclkid', 'ttclid', 'twclid', 'li_fat_id', 'dclid', 'srsltid', 'epik', 'ScCid', 'irclickid', 'rtid', 'rclid',
2904
- // OAuth / OIDC \u2014 see the SECURITY note above
2905
- 'client_id', 'redirect_uri', 'response_type', 'scope', 'state', 'code', 'nonce',
2906
2890
  // Pagination
2907
2891
  'page', 'limit', 'offset', 'cursor', 'per_page',
2908
2892
  // Site search term, as the common platforms spell it. Matching is
@@ -2916,6 +2900,22 @@ var mParticle = (function () {
2916
2900
  'category',
2917
2901
  // Referral
2918
2902
  'ref', 'referrer'];
2903
+ var AUTO_PAGE_VIEW_ATTRIBUTE = 'is_auto_page_view';
2904
+ var CORE_EVENT_FIELDS = ['hostname', 'title', 'path', AUTO_PAGE_VIEW_ATTRIBUTE];
2905
+ var PROTOTYPE_MEMBER_NAMES = ['constructor', '__proto__', 'prototype'];
2906
+ // Server flag values are the strings 'True' and 'False', so these get typed here by habit.
2907
+ var FLAG_VALUE_NAMES = ['true', 'false'];
2908
+ var RESERVED_QUERY_PARAMS = CORE_EVENT_FIELDS.concat(PROTOTYPE_MEMBER_NAMES, FLAG_VALUE_NAMES);
2909
+ var BUILT_IN_NAMES = ALLOWED_QUERY_PARAMS.map(function (name) {
2910
+ return name.toLowerCase();
2911
+ });
2912
+ var isBuiltIn = function isBuiltIn(name) {
2913
+ return BUILT_IN_NAMES.indexOf(name.toLowerCase()) !== -1;
2914
+ };
2915
+ // Excludes `&`, `=`, `%` and whitespace, which would distort the dedup key.
2916
+ var QUERY_PARAM_NAME = /^[a-z0-9_][a-z0-9_.-]{0,63}$/;
2917
+ var MAX_CUSTOM_QUERY_PARAMS = 25;
2918
+ var MAX_CUSTOM_QUERY_PARAM_VALUE_LENGTH = 512;
2919
2919
  // Params matched by name prefix, for platforms that put a per-product id in the
2920
2920
  // name. Salesforce Commerce Cloud spells a variant choice
2921
2921
  // `dwvar_<productId>_<attribute>`, e.g. `dwvar_12345_color=navy`.
@@ -2925,34 +2925,113 @@ var mParticle = (function () {
2925
2925
  return name.indexOf(prefix) === 0;
2926
2926
  });
2927
2927
  };
2928
- var ALLOWED_QUERY_PARAMS_LOWER = ALLOWED_QUERY_PARAMS.map(function (name) {
2929
- return name.toLowerCase();
2930
- });
2931
- // Whether a query param name, in any case, is one an auto page view captures.
2928
+ // Whether a query param name, in any case, is one an auto page view captures by default.
2932
2929
  var isAllowedQueryParam = function isAllowedQueryParam(name) {
2933
- var lower = name.toLowerCase();
2934
- return ALLOWED_QUERY_PARAMS_LOWER.indexOf(lower) !== -1 || hasAllowedPrefix(lower);
2930
+ return isBuiltIn(name) || hasAllowedPrefix(name.toLowerCase());
2935
2931
  };
2936
2932
  // ---------------------------------------------------------------------------
2937
2933
  // Pure helpers. No `this`, no globals, no side effects \u2014 callable and assertable
2938
2934
  // on their own, which is where the interesting rules live.
2939
2935
  // ---------------------------------------------------------------------------
2940
- // Pulls the allowlisted query params off a URL. Delegates to the SDK's own
2941
- // parser, which lowercases keys (so `?UTM_Source=` and `?utm_source=` land on one
2942
- // attribute), drops empty values, and carries the fallback for browsers without
2943
- // URLSearchParams. Tolerates an empty href, so SSR yields no params rather than
2944
- // throwing.
2945
- var allowedQueryParams = function allowedQueryParams(href) {
2946
- var params = queryStringParser(href, ALLOWED_QUERY_PARAMS);
2936
+ // Validated here as well as in the dashboard, because remote config is untrusted input.
2937
+ var parseQueryParamAllowlist = function parseQueryParamAllowlist(configured) {
2938
+ var allowed = [];
2939
+ var rejectedPositions = [];
2940
+ var overLimit = 0;
2941
+ if (!configured) {
2942
+ return {
2943
+ allowed: allowed,
2944
+ rejectedPositions: rejectedPositions,
2945
+ overLimit: overLimit
2946
+ };
2947
+ }
2948
+ var entries = Array.isArray(configured) ? configured : String(configured).split(',');
2949
+ entries.forEach(function (entry, index) {
2950
+ var position = index + 1;
2951
+ var name = String(entry).trim().toLowerCase();
2952
+ if (!name) {
2953
+ return;
2954
+ }
2955
+ if (!QUERY_PARAM_NAME.test(name) || RESERVED_QUERY_PARAMS.indexOf(name) !== -1) {
2956
+ rejectedPositions.push(position);
2957
+ return;
2958
+ }
2959
+ if (isBuiltIn(name) || allowed.indexOf(name) !== -1) {
2960
+ return;
2961
+ }
2962
+ if (allowed.length >= MAX_CUSTOM_QUERY_PARAMS) {
2963
+ overLimit++;
2964
+ return;
2965
+ }
2966
+ allowed.push(name);
2967
+ });
2968
+ return {
2969
+ allowed: allowed,
2970
+ rejectedPositions: rejectedPositions,
2971
+ overLimit: overLimit
2972
+ };
2973
+ };
2974
+ // Not localeCompare: this order feeds the dedup key, so it must be identical in every
2975
+ // browser, and localeCompare is locale-dependent.
2976
+ var byName = function byName(a, b) {
2977
+ return a < b ? -1 : a > b ? 1 : 0;
2978
+ };
2979
+ // Built-ins keep their positions and additions sort after them, so a dedup key depends on
2980
+ // which params are configured, not the order they were typed.
2981
+ var effectiveAllowlist = function effectiveAllowlist(extras) {
2982
+ if (extras === void 0) {
2983
+ extras = [];
2984
+ }
2985
+ return ALLOWED_QUERY_PARAMS.concat(extras.filter(function (name) {
2986
+ return !isBuiltIn(name);
2987
+ }).sort(byName));
2988
+ };
2989
+ // Over-long custom values are dropped rather than truncated: two truncated values could
2990
+ // share a dedup key and swallow a real page view.
2991
+ var isWithinValueLimit = function isWithinValueLimit(name, value) {
2992
+ return isBuiltIn(name) || value.length <= MAX_CUSTOM_QUERY_PARAM_VALUE_LENGTH;
2993
+ };
2994
+ // A prefix match is a platform name, not a configured extra, so the custom
2995
+ // value limit does not apply. A name already on the allowlist keeps that
2996
+ // position instead of being appended again.
2997
+ var prefixMatchedParams = function prefixMatchedParams(href, allowlist) {
2947
2998
  var allParams = queryStringParser(href);
2948
- Object.keys(allParams).filter(function (name) {
2949
- return hasAllowedPrefix(name) && allParams[name];
2950
- }).forEach(function (name) {
2951
- params[name] = allParams[name];
2999
+ return Object.keys(allParams).filter(function (name) {
3000
+ return hasAllowedPrefix(name) && allParams[name] && allowlist.indexOf(name) === -1;
3001
+ }).sort(byName).map(function (name) {
3002
+ return {
3003
+ name: name,
3004
+ value: allParams[name]
3005
+ };
2952
3006
  });
2953
- return params;
2954
3007
  };
2955
- var AUTO_PAGE_VIEW_ATTRIBUTE = 'is_auto_page_view';
3008
+ var allowedQueryParams = function allowedQueryParams(href, extras) {
3009
+ if (extras === void 0) {
3010
+ extras = [];
3011
+ }
3012
+ var allowlist = effectiveAllowlist(extras);
3013
+ var found = queryStringParser(href, allowlist);
3014
+ var listed = allowlist.filter(function (name) {
3015
+ return hasOwnProp(found, name);
3016
+ }).filter(function (name) {
3017
+ return isWithinValueLimit(name, found[name]);
3018
+ }).map(function (name) {
3019
+ return {
3020
+ name: name,
3021
+ value: found[name]
3022
+ };
3023
+ });
3024
+ return listed.concat(prefixMatchedParams(href, allowlist));
3025
+ };
3026
+ var paramsToAttributes = function paramsToAttributes(params) {
3027
+ var attributes = {};
3028
+ params.forEach(function (_a) {
3029
+ var name = _a.name,
3030
+ value = _a.value;
3031
+ attributes[name] = value;
3032
+ });
3033
+ return attributes;
3034
+ };
2956
3035
  // Absent rather than false on a manual page view, so the attribute is only ever
2957
3036
  // added by the automatic emitters. Shared by both of them so the key and that
2958
3037
  // rule have one definition instead of two that can drift.
@@ -2960,33 +3039,13 @@ var mParticle = (function () {
2960
3039
  var _a;
2961
3040
  return isAutoPageView ? (_a = {}, _a[AUTO_PAGE_VIEW_ATTRIBUTE] = true, _a) : {};
2962
3041
  };
2963
- // The captured params, in allowlist order, then prefix-matched names sorted.
2964
- // Neither depends on the object's insertion order, so reordering the query
2965
- // string cannot produce a different key.
2966
- //
2967
- // Membership is an own-property check, not `in`. `in` walks the prototype
2968
- // chain, so a name matching an Object.prototype member reports as present on
2969
- // any plain object. ALLOWED_QUERY_PARAMS contains no such name, which was the
2970
- // only thing making `in` safe here \u2014 and it stops being a safe assumption the
2971
- // moment this list can be extended from configuration.
2972
- var capturedNames = function capturedNames(params) {
2973
- return ALLOWED_QUERY_PARAMS.filter(function (name) {
2974
- return hasOwnProp(params, name);
2975
- }).concat(Object.keys(params).filter(hasAllowedPrefix).sort());
2976
- };
2977
- // The dedup key: pathname plus the allowlisted params in a fixed order, so that
2978
- // reordering the query string is not a new page. Params outside the allowlist
2979
- // never make it into `page.params` and so cannot key a view \u2014 nor can the hash.
2980
- //
2981
- // Values are re-encoded because queryStringParser hands them back DECODED. A
2982
- // value holding the pair delimiters would otherwise serialize exactly like two
2983
- // separate params \u2014 `{q: 'a&search=b'}` and `{q: 'a', search: 'b'}` both becoming
2984
- // `q=a&search=b` \u2014 and dedup would treat a real navigation between them as the
2985
- // same page and drop the view. `q`, `search` and `redirect_uri` carry `&` and `=`
2986
- // routinely, so this is reachable rather than theoretical.
3042
+ // Values are re-encoded because they arrive decoded: `{q: 'a&search=b'}` would otherwise
3043
+ // key the same as `{q: 'a', search: 'b'}` and dedup a real navigation away.
2987
3044
  var pageKey = function pageKey(page) {
2988
- var query = capturedNames(page.params).map(function (name) {
2989
- return "".concat(name, "=").concat(encodeURIComponent(page.params[name]));
3045
+ var query = page.params.map(function (_a) {
3046
+ var name = _a.name,
3047
+ value = _a.value;
3048
+ return "".concat(name, "=").concat(encodeURIComponent(value));
2990
3049
  }).join('&');
2991
3050
  return query ? "".concat(page.path, "?").concat(query) : page.path;
2992
3051
  };
@@ -3011,7 +3070,7 @@ var mParticle = (function () {
3011
3070
  // Params spread first, then the core fields by name, so a core field always
3012
3071
  // wins. No allowlist entry collides with hostname/title/path today; naming
3013
3072
  // them here is what keeps a later addition from silently overwriting one.
3014
- data: __assign(__assign(__assign({}, params), {
3073
+ data: __assign(__assign(__assign({}, paramsToAttributes(params)), {
3015
3074
  hostname: hostname,
3016
3075
  title: title,
3017
3076
  path: path
@@ -3090,10 +3149,10 @@ var mParticle = (function () {
3090
3149
  state.tracker = undefined;
3091
3150
  }
3092
3151
  };
3093
- var currentPage = function currentPage() {
3152
+ var currentPage = function currentPage(extras) {
3094
3153
  return {
3095
3154
  path: window.location.pathname,
3096
- params: allowedQueryParams(getHref())
3155
+ params: allowedQueryParams(getHref(), extras)
3097
3156
  };
3098
3157
  };
3099
3158
  // Log-safe description of a page: the path, plus the NAMES of the captured
@@ -3106,7 +3165,10 @@ var mParticle = (function () {
3106
3165
  if (!page) {
3107
3166
  return '';
3108
3167
  }
3109
- var names = capturedNames(page.params);
3168
+ var names = page.params.map(function (_a) {
3169
+ var name = _a.name;
3170
+ return name;
3171
+ });
3110
3172
  return names.length ? "".concat(page.path, " (params: ").concat(names.join(), ")") : page.path;
3111
3173
  };
3112
3174
  // ---------------------------------------------------------------------------
@@ -3119,6 +3181,7 @@ var mParticle = (function () {
3119
3181
  function PageViewTracker(mpInstance, options) {
3120
3182
  this.lastPage = null;
3121
3183
  this.active = false;
3184
+ this.customQueryParams = [];
3122
3185
  this.pendingNavigations = [];
3123
3186
  this.undoHistoryPatch = null;
3124
3187
  this.mpInstance = mpInstance;
@@ -3153,7 +3216,8 @@ var mParticle = (function () {
3153
3216
  inheritedPages = inheritedPages.concat(this.retire(this));
3154
3217
  }
3155
3218
  this.active = true;
3156
- this.lastPage = currentPage();
3219
+ this.customQueryParams = this.readCustomQueryParams();
3220
+ this.lastPage = currentPage(this.customQueryParams);
3157
3221
  this.log("[init] seeded lastPage: ".concat(describePage(this.lastPage)));
3158
3222
  this.undoHistoryPatch = subscribeToRouteChange('pageView', function (source) {
3159
3223
  return _this.safeHandleNavigation(source);
@@ -3178,6 +3242,25 @@ var mParticle = (function () {
3178
3242
  this.active = false;
3179
3243
  clearActiveTracker(this);
3180
3244
  };
3245
+ PageViewTracker.prototype.readCustomQueryParams = function () {
3246
+ var _a = this.mpInstance._Store.SDKConfig.autoLogPageViewQueryParams || {},
3247
+ _b = _a.allowed,
3248
+ allowed = _b === void 0 ? [] : _b,
3249
+ _c = _a.rejectedPositions,
3250
+ rejectedPositions = _c === void 0 ? [] : _c,
3251
+ _d = _a.overLimit,
3252
+ overLimit = _d === void 0 ? 0 : _d;
3253
+ if (rejectedPositions.length) {
3254
+ this.mpInstance.Logger.warning('mParticle APV: ignoring invalid additional page view query ' + "parameters at positions ".concat(rejectedPositions.join(', ')));
3255
+ }
3256
+ if (overLimit) {
3257
+ this.mpInstance.Logger.warning("mParticle APV: ignoring ".concat(overLimit, " additional page view ") + "query parameters beyond the limit of " + "".concat(MAX_CUSTOM_QUERY_PARAMS));
3258
+ }
3259
+ if (allowed.length) {
3260
+ this.log("[init] additional query params: ".concat(allowed.join()));
3261
+ }
3262
+ return allowed;
3263
+ };
3181
3264
  // Stops the outgoing tracker and returns the pages it had queued so this
3182
3265
  // tracker can re-schedule them once it is active. Private, but reachable
3183
3266
  // across instances: the handoff protocol stays internal to the class.
@@ -3219,7 +3302,7 @@ var mParticle = (function () {
3219
3302
  }
3220
3303
  };
3221
3304
  PageViewTracker.prototype.handleNavigation = function (source) {
3222
- var candidate = currentPage();
3305
+ var candidate = currentPage(this.customQueryParams);
3223
3306
  var lastKey = this.lastPage ? pageKey(this.lastPage) : null;
3224
3307
  this.log("[detect] navigation signal (source: ".concat(source, ", candidate: ").concat(describePage(candidate), ", last: ").concat(describePage(this.lastPage), ")"));
3225
3308
  if (!isNewPage(lastKey, pageKey(candidate))) {
@@ -3271,11 +3354,21 @@ var mParticle = (function () {
3271
3354
 
3272
3355
  // The one rule for a URL that leaves the SDK, shared with kits through
3273
3356
  // `mParticle._cleanUrl`. `path` keeps scheme, host and path. `full` adds the
3274
- // query params an auto page view may capture, then the fragment.
3357
+ // query params an auto page view captures by default and the campaign
3358
+ // attribution params, then the fragment.
3275
3359
  var UrlMode = {
3276
3360
  Path: 'path',
3277
3361
  Full: 'full'
3278
3362
  };
3363
+ // Campaign attribution and ad-network click ids. URLs keep them; an auto page view
3364
+ // carries one as an attribute only when an input configures it.
3365
+ var URL_ATTRIBUTION_QUERY_PARAMS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'utm_id', 'utm_source_platform', 'utm_creative_format', 'utm_marketing_tactic', 'gad_source', 'gad_campaignid', 'mc_cid', 'gclid', 'gbraid', 'wbraid', 'fbclid', 'msclkid', 'ttclid', 'twclid', 'li_fat_id', 'dclid', 'srsltid', 'epik', 'ScCid', 'irclickid', 'rtid', 'rclid'];
3366
+ var URL_ATTRIBUTION_QUERY_PARAMS_LOWER = URL_ATTRIBUTION_QUERY_PARAMS.map(function (name) {
3367
+ return name.toLowerCase();
3368
+ });
3369
+ var isUrlQueryParam = function isUrlQueryParam(name) {
3370
+ return isAllowedQueryParam(name) || URL_ATTRIBUTION_QUERY_PARAMS_LOWER.indexOf(name.toLowerCase()) !== -1;
3371
+ };
3279
3372
  // OAuth 2.0 / OIDC authorization-flow params. They stay off URLs even where the
3280
3373
  // page view allowlist admits them, because a URL travels on every event.
3281
3374
  var EXCLUDED_URL_QUERY_PARAMS = ['client_id', 'redirect_uri', 'response_type', 'scope', 'state', 'code', 'nonce'];
@@ -3297,7 +3390,7 @@ var mParticle = (function () {
3297
3390
  return null;
3298
3391
  }
3299
3392
  var lowerName = name.toLowerCase();
3300
- if (!name || !isAllowedQueryParam(name) || EXCLUDED_URL_QUERY_PARAMS.indexOf(lowerName) !== -1) {
3393
+ if (!name || !isUrlQueryParam(name) || EXCLUDED_URL_QUERY_PARAMS.indexOf(lowerName) !== -1) {
3301
3394
  return null;
3302
3395
  }
3303
3396
  if (separator === -1 || URL_VALUED_QUERY_PARAMS.indexOf(lowerName) === -1) {
@@ -3745,7 +3838,7 @@ var mParticle = (function () {
3745
3838
  var event = {
3746
3839
  AppName: SDKConfig.appName,
3747
3840
  AppVersion: SDKConfig.appVersion,
3748
- Package: SDKConfig["package"],
3841
+ Package: SDKConfig.package,
3749
3842
  EventDataType: MessageType$1.AppStateTransition,
3750
3843
  Timestamp: now,
3751
3844
  SessionId: sessionId,
@@ -3976,7 +4069,7 @@ var mParticle = (function () {
3976
4069
  });
3977
4070
  batchesToUpload.forEach(function (batch) {
3978
4071
  if (retriedIds.indexOf(batch.source_request_id) === -1) {
3979
- _this_1.batchOrder["delete"](batch.source_request_id);
4072
+ _this_1.batchOrder.delete(batch.source_request_id);
3980
4073
  }
3981
4074
  });
3982
4075
  // Batches that do not successfully upload are added back to the process queue
@@ -4003,7 +4096,6 @@ var mParticle = (function () {
4003
4096
  });
4004
4097
  });
4005
4098
  };
4006
-
4007
4099
  BatchUploader.prototype.storeBatchesQueuedForProcessing = function () {
4008
4100
  var _this_1 = this;
4009
4101
  var _a;
@@ -4271,7 +4363,6 @@ var mParticle = (function () {
4271
4363
  logger.error('search called without a callback function; skipping request.');
4272
4364
  return [2 /*return*/];
4273
4365
  }
4274
-
4275
4366
  safeInvoke = function safeInvoke(result) {
4276
4367
  try {
4277
4368
  callback(result);
@@ -4301,7 +4392,6 @@ var mParticle = (function () {
4301
4392
  });
4302
4393
  return [2 /*return*/];
4303
4394
  }
4304
-
4305
4395
  _a.label = 1;
4306
4396
  case 1:
4307
4397
  _a.trys.push([1, 9,, 10]);
@@ -4604,7 +4694,7 @@ var mParticle = (function () {
4604
4694
  sdk_version: Constants.sdkVersion
4605
4695
  },
4606
4696
  environment: environment,
4607
- request_id: generateUniqueId(),
4697
+ request_id: _generateUniqueId(),
4608
4698
  request_timestamp_ms: Date.now()
4609
4699
  };
4610
4700
  };
@@ -5058,7 +5148,7 @@ var mParticle = (function () {
5058
5148
  this.decoded = decoded;
5059
5149
  this.parseStringOrNumber = parseStringOrNumber;
5060
5150
  this.generateHash = generateHash;
5061
- this.generateUniqueId = generateUniqueId;
5151
+ this.generateUniqueId = _generateUniqueId;
5062
5152
  // Imported Validators
5063
5153
  this.Validators = Validators;
5064
5154
  }
@@ -5260,9 +5350,13 @@ var mParticle = (function () {
5260
5350
  if (requiresConsent && mpidIsNotInCookies) {
5261
5351
  return;
5262
5352
  }
5263
- // For Rokt, block cookie sync when noTargeting privacy flag is true
5264
- if (moduleId === PARTNER_MODULE_IDS.Rokt && mpInstance._CookieConsentManager.getNoTargeting()) {
5265
- return;
5353
+ // For Rokt, block cookie sync under the noTargeting or doNotShareOrSell privacy flag,
5354
+ // or when the browser sends Global Privacy Control
5355
+ if (moduleId === PARTNER_MODULE_IDS.Rokt) {
5356
+ var nav = typeof navigator === 'undefined' ? undefined : navigator;
5357
+ if (mpInstance._CookieConsentManager.getNoTargeting() || mpInstance._CookieConsentManager.getDoNotShareOrSell() || !!nav && nav.globalPrivacyControl === true) {
5358
+ return;
5359
+ }
5266
5360
  }
5267
5361
  var isEnabledForUserConsent = mpInstance._Consent.isEnabledForUserConsent;
5268
5362
  if (!isEnabledForUserConsent(filteringConsentRuleValues, mpInstance.Identity.getCurrentUser())) {
@@ -6144,6 +6238,7 @@ var mParticle = (function () {
6144
6238
  this.SDKConfig.flags = {};
6145
6239
  }
6146
6240
  this.SDKConfig.flags = processFlags(config);
6241
+ this.SDKConfig.autoLogPageViewQueryParams = parseQueryParamAllowlist(config.autoLogPageViewQueryParams);
6147
6242
  if (config.deviceId) {
6148
6243
  this.deviceId = config.deviceId;
6149
6244
  }
@@ -6178,7 +6273,7 @@ var mParticle = (function () {
6178
6273
  this.SDKConfig.appName = config.appName;
6179
6274
  }
6180
6275
  if (config.hasOwnProperty('package')) {
6181
- this.SDKConfig["package"] = config["package"];
6276
+ this.SDKConfig.package = config.package;
6182
6277
  }
6183
6278
  if (config.hasOwnProperty('integrationDelayTimeout')) {
6184
6279
  this.SDKConfig.integrationDelayTimeout = config.integrationDelayTimeout;
@@ -6420,6 +6515,9 @@ var mParticle = (function () {
6420
6515
  if (config.flags) {
6421
6516
  _this.SDKConfig.flags = processFlags(config);
6422
6517
  }
6518
+ // Stored whole: the tracker reports the rejections, and cannot recover them by
6519
+ // re-parsing a list that has already had them removed.
6520
+ _this.SDKConfig.autoLogPageViewQueryParams = parseQueryParamAllowlist(config.autoLogPageViewQueryParams);
6423
6521
  var baseUrls = processBaseUrls(config, _this.SDKConfig.flags, apiKey);
6424
6522
  for (var baseUrlKeys in baseUrls) {
6425
6523
  _this.SDKConfig[baseUrlKeys] = baseUrls[baseUrlKeys];
@@ -6452,7 +6550,8 @@ var mParticle = (function () {
6452
6550
  CaptureIntegrationSpecificIdsV2 = _a.CaptureIntegrationSpecificIdsV2,
6453
6551
  AstBackgroundEvents = _a.AstBackgroundEvents,
6454
6552
  AutoLogPageView = _a.AutoLogPageView,
6455
- AutoButtonTracking = _a.AutoButtonTracking;
6553
+ AutoButtonTracking = _a.AutoButtonTracking,
6554
+ AutoLinkTracking = _a.AutoLinkTracking;
6456
6555
  if (!config.flags) {
6457
6556
  return {};
6458
6557
  }
@@ -6470,6 +6569,7 @@ var mParticle = (function () {
6470
6569
  flags[AstBackgroundEvents] = config.flags[AstBackgroundEvents] === 'True';
6471
6570
  flags[AutoLogPageView] = config.flags[AutoLogPageView] === 'True';
6472
6571
  flags[AutoButtonTracking] = config.flags[AutoButtonTracking] === 'True';
6572
+ flags[AutoLinkTracking] = config.flags[AutoLinkTracking] === 'True';
6473
6573
  return flags;
6474
6574
  }
6475
6575
  function processBaseUrls(config, flags, apiKey) {
@@ -7438,13 +7538,13 @@ var mParticle = (function () {
7438
7538
  };
7439
7539
  // The auto page view for the landing page. The SPA navigations that follow it
7440
7540
  // come from PageViewTracker instead, so both emitters attach the same
7441
- // allowlisted query params \u2014 and this is the one that matters for campaign
7442
- // attribution, since utm_*/gclid live on the entry URL.
7541
+ // allowlisted query params.
7443
7542
  this.logPageView = function (options) {
7543
+ var _a;
7444
7544
  self.logEvent({
7445
7545
  messageType: Types.MessageType.PageView,
7446
7546
  name: 'PageView',
7447
- data: __assign(__assign(__assign({}, allowedQueryParams(getHref())), {
7547
+ data: __assign(__assign(__assign({}, paramsToAttributes(allowedQueryParams(getHref(), (_a = mpInstance._Store.SDKConfig.autoLogPageViewQueryParams) === null || _a === void 0 ? void 0 : _a.allowed))), {
7448
7548
  hostname: window.location.hostname,
7449
7549
  title: window.document.title
7450
7550
  }), autoPageViewAttribute(options && options.isAutoPageView)),
@@ -7738,6 +7838,19 @@ var mParticle = (function () {
7738
7838
  }
7739
7839
  return false;
7740
7840
  }
7841
+ // sendEventToForwarders passes no kitBlocker: apiClient has already applied
7842
+ // the data plan by then.
7843
+ function isEventForwarded(event, forwarder, kitBlocker) {
7844
+ var plannedEvent = kitBlocker && kitBlocker.kitBlockingEnabled ? kitBlocker.createBlockedEvent(event) : event;
7845
+ if (!plannedEvent) {
7846
+ return false;
7847
+ }
7848
+ if (isBlockedByForwardingRule(plannedEvent.EventDataType, plannedEvent.EventAttributes, forwarder)) {
7849
+ return false;
7850
+ }
7851
+ var eventCategory = plannedEvent.EventCategory;
7852
+ return !isBlockedByEventFilter(plannedEvent.EventDataType, KitFilterHelper.hashEventName(plannedEvent.EventName, eventCategory), KitFilterHelper.hashEventType(eventCategory), forwarder);
7853
+ }
7741
7854
  function filterEventAttributes(messageType, eventCategory, eventName, attributes, forwarder) {
7742
7855
  if (!attributes) {
7743
7856
  return attributes;
@@ -7888,17 +8001,12 @@ var mParticle = (function () {
7888
8001
  if (mpInstance._Store.webviewBridgeEnabled || !mpInstance._Store.activeForwarders) {
7889
8002
  return;
7890
8003
  }
7891
- var hashedEventName = KitFilterHelper.hashEventName(event.EventName, event.EventCategory);
7892
- var hashedEventType = KitFilterHelper.hashEventType(event.EventCategory);
7893
8004
  var forwardEvent = function forwardEvent(forwarder) {
7894
- if (isBlockedByForwardingRule(event.EventDataType, event.EventAttributes, forwarder)) {
8005
+ if (!isEventForwarded(event, forwarder)) {
7895
8006
  return;
7896
8007
  }
7897
8008
  // Clone the event object, as we could be sending different attributes to each forwarder
7898
8009
  var clonedEvent = extend(true, {}, event);
7899
- if (isBlockedByEventFilter(event.EventDataType, hashedEventName, hashedEventType, forwarder)) {
7900
- return;
7901
- }
7902
8010
  clonedEvent.EventAttributes = filterEventAttributes(event.EventDataType, event.EventCategory, event.EventName, clonedEvent.EventAttributes, forwarder);
7903
8011
  // Check user identity filtering rules
7904
8012
  clonedEvent.UserIdentities = filterUserIdentities(clonedEvent.UserIdentities, forwarder.userIdentityFilters);
@@ -8346,7 +8454,7 @@ var mParticle = (function () {
8346
8454
  ExpandedEventCount: 0,
8347
8455
  AppVersion: mpInstance.getAppVersion(),
8348
8456
  AppName: mpInstance.getAppName(),
8349
- Package: mpInstance._Store.SDKConfig["package"],
8457
+ Package: mpInstance._Store.SDKConfig.package,
8350
8458
  ClientGeneratedId: mpInstance._Store.clientId,
8351
8459
  DeviceId: mpInstance._Store.deviceId,
8352
8460
  IntegrationAttributes: integrationAttributes,
@@ -8575,7 +8683,6 @@ var mParticle = (function () {
8575
8683
  });
8576
8684
  });
8577
8685
  };
8578
-
8579
8686
  return AudienceManager;
8580
8687
  }();
8581
8688
 
@@ -10614,7 +10721,6 @@ var mParticle = (function () {
10614
10721
  });
10615
10722
  });
10616
10723
  };
10617
-
10618
10724
  this.sendIdentityRequest = function (identityApiRequest, method, callback, originalIdentityApiData, parseIdentityResponse, mpid, knownIdentities) {
10619
10725
  var _a, _b, _c, _d;
10620
10726
  return __awaiter(this, void 0, void 0, function () {
@@ -10634,13 +10740,11 @@ var mParticle = (function () {
10634
10740
  Logger.error(Messages$1.ErrorMessages.APIRequestEmpty);
10635
10741
  return [2 /*return*/];
10636
10742
  }
10637
-
10638
10743
  Logger.verbose(Messages$1.InformationMessages.SendIdentityHttp);
10639
10744
  if (mpInstance._Store.identityCallInFlight) {
10640
10745
  invokeCallback(callback, HTTPCodes$1.activeIdentityRequest, 'There is currently an Identity request processing. Please wait for this to return before requesting again');
10641
10746
  return [2 /*return*/];
10642
10747
  }
10643
-
10644
10748
  previousMPID = mpid || null;
10645
10749
  uploadUrl = this.getUploadUrl(method, mpid);
10646
10750
  uploader = window.fetch ? new FetchUploader(uploadUrl) : new XHRUploader(uploadUrl);
@@ -10698,7 +10802,7 @@ var mParticle = (function () {
10698
10802
  responseToLog = responseText;
10699
10803
  if (!isDevelopmentMode && (responseText === null || responseText === void 0 ? void 0 : responseText.matched_identities)) {
10700
10804
  responseToLog = __assign(__assign({}, responseText), {
10701
- matched_identities: obfuscateData(responseText.matched_identities)
10805
+ matched_identities: _obfuscateData(responseText.matched_identities)
10702
10806
  });
10703
10807
  }
10704
10808
  message = 'Received Identity Response from server: ' + JSON.stringify(responseToLog);
@@ -10753,7 +10857,6 @@ var mParticle = (function () {
10753
10857
  });
10754
10858
  });
10755
10859
  };
10756
-
10757
10860
  this.getUploadUrl = function (method, mpid) {
10758
10861
  var uploadServiceUrl = mpInstance._Helpers.createServiceUrl(mpInstance._Store.SDKConfig.identityUrl);
10759
10862
  var uploadUrl = method === Modify ? uploadServiceUrl + mpid + '/' + method : uploadServiceUrl + method;
@@ -10778,7 +10881,8 @@ var mParticle = (function () {
10778
10881
  }
10779
10882
 
10780
10883
  var PASSBACK_CONVERSION_TRACKING_ID = 'passbackconversiontrackingid';
10781
- var CHECKOUT_INTENT_BUFFER_LIMIT = 4;
10884
+ // One signal per kind (commit, wallet, checkout_step, frame and cart), so a new kind never evicts another.
10885
+ var CHECKOUT_INTENT_BUFFER_LIMIT = 5;
10782
10886
  var CHECKOUT_INTENT_MAX_AGE_MS = 30000;
10783
10887
  var ON_SHOPPABLE_ADS_READY_METHOD = 'onShoppableAdsReady';
10784
10888
  // The purpose of this class is to create a link between the Core mParticle SDK and the
@@ -10807,6 +10911,8 @@ var mParticle = (function () {
10807
10911
  this.stopRouteChangeWatch = null;
10808
10912
  // Checkout intent raised before the kit attached: latest per kind.
10809
10913
  this.pendingCheckoutIntents = [];
10914
+ // Off until SDK init finds the Rokt connection receives auto button clicks.
10915
+ this.autoButtonClicksForwarded = false;
10810
10916
  }
10811
10917
  /**
10812
10918
  * Sets a callback to be invoked when RoktManager becomes ready
@@ -10933,21 +11039,31 @@ var mParticle = (function () {
10933
11039
  (_c = this.logger) === null || _c === void 0 ? void 0 : _c.error("RoktManager: Error in onRouteChange: ".concat(getErrorMessage(e)));
10934
11040
  }
10935
11041
  };
10936
- // Checkout intent is off unless the flag is on, and under opt-out, any Rokt
10937
- // privacy launcher option, or Global Privacy Control.
10938
- RoktManager.prototype.isCheckoutIntentSuppressed = function () {
10939
- var _a, _b;
10940
- var flags = (_b = (_a = this.store) === null || _a === void 0 ? void 0 : _a.SDKConfig) === null || _b === void 0 ? void 0 : _b.flags;
10941
- if (!flags || flags[Constants.FeatureFlags.AutoButtonTracking] !== true || !this.store.isEnabled) {
10942
- return true;
10943
- }
11042
+ RoktManager.prototype.setAutoButtonClicksForwarded = function (forwarded) {
11043
+ this.autoButtonClicksForwarded = forwarded;
11044
+ };
11045
+ // noTargeting (which noDeviceId implies), doNotShareOrSell or Global Privacy
11046
+ // Control. Unlike noFunctional, these also stop auto button and link clicks
11047
+ // from being logged.
11048
+ RoktManager.prototype.hasTargetingOptOut = function () {
10944
11049
  var options = this.launcherOptions || {};
10945
- if (options.noFunctional || options.noTargeting || options.doNotShareOrSell) {
11050
+ if (options.noTargeting || options.doNotShareOrSell) {
10946
11051
  return true;
10947
11052
  }
10948
11053
  var nav = typeof navigator === 'undefined' ? undefined : navigator;
10949
11054
  return !!nav && nav.globalPrivacyControl === true;
10950
11055
  };
11056
+ // Checkout intent is off unless the flag is on, and under opt-out, any Rokt
11057
+ // privacy launcher option, Global Privacy Control, or a Rokt connection that
11058
+ // does not receive auto button clicks.
11059
+ RoktManager.prototype.isCheckoutIntentSuppressed = function () {
11060
+ var _a, _b, _c;
11061
+ var flags = (_b = (_a = this.store) === null || _a === void 0 ? void 0 : _a.SDKConfig) === null || _b === void 0 ? void 0 : _b.flags;
11062
+ if (!flags || flags[Constants.FeatureFlags.AutoButtonTracking] !== true || !this.store.isEnabled || !this.autoButtonClicksForwarded) {
11063
+ return true;
11064
+ }
11065
+ return !!((_c = this.launcherOptions) === null || _c === void 0 ? void 0 : _c.noFunctional) || this.hasTargetingOptOut();
11066
+ };
10951
11067
  // True when the kit can wait for the shopper's confirm click: signals are not
10952
11068
  // suppressed and the browser exposes the composed path the detector reads.
10953
11069
  // Decided from config alone, so tracker setup and teardown never change it.
@@ -10988,6 +11104,23 @@ var mParticle = (function () {
10988
11104
  RoktManager.prototype.discardCheckoutIntents = function () {
10989
11105
  this.pendingCheckoutIntents = [];
10990
11106
  };
11107
+ // Under the same gates as checkout intent. A pick made before a kit
11108
+ // attaches is not kept: the shopper is still choosing, and the next pick
11109
+ // replaces it.
11110
+ RoktManager.prototype.notifyPaymentPick = function (signal) {
11111
+ var _a;
11112
+ if (this.isCheckoutIntentSuppressed()) {
11113
+ return;
11114
+ }
11115
+ var kit = this.kit;
11116
+ try {
11117
+ if (kit && isFunction(kit.onPaymentPick)) {
11118
+ kit.onPaymentPick(signal);
11119
+ }
11120
+ } catch (e) {
11121
+ (_a = this.logger) === null || _a === void 0 ? void 0 : _a.error("RoktManager: Error in onPaymentPick: ".concat(getErrorMessage(e)));
11122
+ }
11123
+ };
10991
11124
  // Replays recent buffered signals raised on the current page, oldest first.
10992
11125
  RoktManager.prototype.flushCheckoutIntents = function () {
10993
11126
  var _this = this;
@@ -11006,6 +11139,8 @@ var mParticle = (function () {
11006
11139
  return _this.notifyCheckoutIntent(signal);
11007
11140
  });
11008
11141
  };
11142
+ // The Rokt kit reads this to tell core's page views, whose title and utm
11143
+ // attributes a filter may have removed, from ones logged by hand.
11009
11144
  RoktManager.prototype.isAutoLogPageViewEnabled = function () {
11010
11145
  var _a, _b, _c;
11011
11146
  return ((_c = (_b = (_a = this.store) === null || _a === void 0 ? void 0 : _a.SDKConfig) === null || _b === void 0 ? void 0 : _b.flags) === null || _c === void 0 ? void 0 : _c[Constants.FeatureFlags.AutoLogPageView]) === true;
@@ -11153,7 +11288,6 @@ var mParticle = (function () {
11153
11288
  });
11154
11289
  });
11155
11290
  };
11156
-
11157
11291
  RoktManager.prototype.logFilteredIdentity = function (identityType) {
11158
11292
  var _a;
11159
11293
  (_a = this.logger) === null || _a === void 0 ? void 0 : _a.verbose("mParticle.Rokt selectPlacements did not add the ".concat(identityType, " user identity to the attributes because the Rokt connection does not allow it"));
@@ -11223,7 +11357,6 @@ var mParticle = (function () {
11223
11357
  });
11224
11358
  });
11225
11359
  };
11226
-
11227
11360
  RoktManager.prototype.setExtensionData = function (extensionData) {
11228
11361
  if (!this.isReady()) {
11229
11362
  this.deferredCall('setExtensionData', extensionData);
@@ -11272,12 +11405,10 @@ var mParticle = (function () {
11272
11405
  (_a = this.logger) === null || _a === void 0 ? void 0 : _a.verbose('mParticle.Rokt.terminate called before the Rokt kit was ready. Nothing to terminate.');
11273
11406
  return [2 /*return*/];
11274
11407
  }
11275
-
11276
11408
  if (!isFunction(this.kit.terminate)) {
11277
11409
  (_b = this.logger) === null || _b === void 0 ? void 0 : _b.error('mParticle.Rokt.terminate is not supported by the attached Rokt Kit version.');
11278
11410
  return [2 /*return*/];
11279
11411
  }
11280
-
11281
11412
  _d.label = 1;
11282
11413
  case 1:
11283
11414
  _d.trys.push([1, 3,, 4]);
@@ -11295,7 +11426,6 @@ var mParticle = (function () {
11295
11426
  });
11296
11427
  });
11297
11428
  };
11298
-
11299
11429
  RoktManager.prototype.onShoppableAdsReady = function (callback) {
11300
11430
  if (!this.kit || !this.isShoppableAdsLoaded) {
11301
11431
  this.deferredCall('onShoppableAdsReady', callback);
@@ -11318,7 +11448,7 @@ var mParticle = (function () {
11318
11448
  } catch (e) {
11319
11449
  (_a = _this.logger) === null || _a === void 0 ? void 0 : _a.error('RoktManager: Error flushing onShoppableAdsReady: ' + e);
11320
11450
  }
11321
- _this.messageQueue["delete"](key);
11451
+ _this.messageQueue.delete(key);
11322
11452
  }
11323
11453
  });
11324
11454
  this.isShoppableAdsLoaded = true;
@@ -11357,7 +11487,6 @@ var mParticle = (function () {
11357
11487
  });
11358
11488
  });
11359
11489
  };
11360
-
11361
11490
  RoktManager.prototype.getLocalSessionAttributes = function () {
11362
11491
  return this.store.getLocalSessionAttributes();
11363
11492
  };
@@ -11463,7 +11592,7 @@ var mParticle = (function () {
11463
11592
  if (resolve) {
11464
11593
  resolve(resolvedResult);
11465
11594
  }
11466
- })["catch"](handleError);
11595
+ }).catch(handleError);
11467
11596
  } catch (error) {
11468
11597
  handleError(error);
11469
11598
  }
@@ -11475,7 +11604,7 @@ var mParticle = (function () {
11475
11604
  RoktManager.prototype.deferredCall = function (methodName, payload) {
11476
11605
  var _this = this;
11477
11606
  return new Promise(function (resolve, reject) {
11478
- var messageId = "".concat(methodName, "_").concat(generateUniqueId());
11607
+ var messageId = "".concat(methodName, "_").concat(_generateUniqueId());
11479
11608
  _this.queueMessage({
11480
11609
  messageId: messageId,
11481
11610
  methodName: methodName,
@@ -11538,24 +11667,21 @@ var mParticle = (function () {
11538
11667
  if (!currentValue) {
11539
11668
  return true; // New value exists but no current value
11540
11669
  }
11541
-
11542
11670
  if (currentValue !== newValue) {
11543
11671
  return true; // Values are different
11544
11672
  }
11545
-
11546
11673
  return false; // Values are the same
11547
11674
  };
11548
-
11549
11675
  return RoktManager;
11550
11676
  }();
11551
11677
 
11552
11678
  /**
11553
- * CookieConsentManager handles storage and access of consent flags (noFunctional, noTargeting)
11554
- * that are passed via launcherOptions during SDK initialization.
11679
+ * CookieConsentManager handles storage and access of consent flags (noFunctional, noTargeting,
11680
+ * doNotShareOrSell) that are passed via launcherOptions during SDK initialization.
11555
11681
  *
11556
11682
  * These flags allow Rokt integration to respect user privacy choices.
11557
11683
  *
11558
- * Default behavior: Both flags default to false, meaning all features are allowed
11684
+ * Default behavior: All flags default to false, meaning all features are allowed
11559
11685
  * unless explicitly opted out by the user.
11560
11686
  */
11561
11687
  var CookieConsentManager = /** @class */function () {
@@ -11563,7 +11689,8 @@ var mParticle = (function () {
11563
11689
  this.flags = flags;
11564
11690
  this.flags = {
11565
11691
  noFunctional: flags.noFunctional === true,
11566
- noTargeting: flags.noTargeting === true
11692
+ noTargeting: flags.noTargeting === true,
11693
+ doNotShareOrSell: flags.doNotShareOrSell === true
11567
11694
  };
11568
11695
  }
11569
11696
  /**
@@ -11580,6 +11707,13 @@ var mParticle = (function () {
11580
11707
  CookieConsentManager.prototype.getNoFunctional = function () {
11581
11708
  return this.flags.noFunctional;
11582
11709
  };
11710
+ /**
11711
+ * Returns true if the user has opted out of the sale or sharing of their data.
11712
+ * Sharing is allowed when doNotShareOrSell is false (default).
11713
+ */
11714
+ CookieConsentManager.prototype.getDoNotShareOrSell = function () {
11715
+ return this.flags.doNotShareOrSell;
11716
+ };
11583
11717
  CookieConsentManager.prototype.syncNoTargetingAttribute = function (user) {
11584
11718
  if (!user) return;
11585
11719
  if (this.flags.noTargeting) {
@@ -11637,19 +11771,20 @@ var mParticle = (function () {
11637
11771
  var AUTO_BUTTON_ATTRIBUTE = 'is_auto_button_click';
11638
11772
  // Specific enough not to merge with a customer's own 'Button Click' event.
11639
11773
  var BUTTON_CLICK_EVENT_NAME = 'Auto Button Click';
11640
- // One custom event per click, logged like logLink() with the auto marker and the
11641
- // page fields that auto page views carry. No intent: classifying clicks is left
11642
- // to whoever reads the data.
11643
- var buildButtonClickEvent = function buildButtonClickEvent(click, pathname) {
11644
- var _a;
11645
- var data = (_a = {
11774
+ // Marks an automatically logged click on a link that leaves the page.
11775
+ var AUTO_LINK_ATTRIBUTE = 'is_auto_link_click';
11776
+ var LINK_CLICK_EVENT_NAME = 'Auto Link Click';
11777
+ // The attributes both click events carry, with the page fields that auto page
11778
+ // views carry. No intent: classifying clicks is left to whoever reads the data.
11779
+ var clickAttributes = function clickAttributes(click, pathname) {
11780
+ var data = {
11646
11781
  label_source: click.labelSource,
11647
11782
  element: click.element,
11648
11783
  type: click.type,
11649
11784
  path: pathname,
11650
11785
  hostname: window.location.hostname
11651
- }, _a[AUTO_BUTTON_ATTRIBUTE] = true, _a);
11652
- // Optional fields are left out when the button has no safe value for them.
11786
+ };
11787
+ // Optional fields are left out when the control has no safe value for them.
11653
11788
  var optional = {
11654
11789
  label: click.label,
11655
11790
  aria_label: click.ariaLabel,
@@ -11663,8 +11798,8 @@ var mParticle = (function () {
11663
11798
  link_domain: click.linkDomain,
11664
11799
  file_extension: click.fileExtension
11665
11800
  };
11666
- for (var _i = 0, _b = Object.keys(optional); _i < _b.length; _i++) {
11667
- var key = _b[_i];
11801
+ for (var _i = 0, _a = Object.keys(optional); _i < _a.length; _i++) {
11802
+ var key = _a[_i];
11668
11803
  if (optional[key]) {
11669
11804
  data[key] = optional[key];
11670
11805
  }
@@ -11672,6 +11807,12 @@ var mParticle = (function () {
11672
11807
  if (click.checked !== undefined) {
11673
11808
  data.checked = click.checked;
11674
11809
  }
11810
+ return data;
11811
+ };
11812
+ // One custom event per button click, logged like logLink() with the auto marker.
11813
+ var buildButtonClickEvent = function buildButtonClickEvent(click, pathname) {
11814
+ var data = clickAttributes(click, pathname);
11815
+ data[AUTO_BUTTON_ATTRIBUTE] = true;
11675
11816
  return {
11676
11817
  messageType: MessageType$1.PageEvent,
11677
11818
  name: BUTTON_CLICK_EVENT_NAME,
@@ -11679,23 +11820,95 @@ var mParticle = (function () {
11679
11820
  eventType: EventType.Other
11680
11821
  };
11681
11822
  };
11823
+ // One custom event per click on a link that leaves the page. Typed Other, not
11824
+ // Navigation, so no kit reads it as a page change. A plain link to another page
11825
+ // of the site goes without its text, which can name the shopper ("Hi, Jane");
11826
+ // a button or wallet acting as a link keeps its name.
11827
+ var buildLinkClickEvent = function buildLinkClickEvent(click, pathname) {
11828
+ var data = clickAttributes(click.linkType === 'internal' && click.element === 'link' ? __assign(__assign({}, click), {
11829
+ label: undefined,
11830
+ labelSource: 'none',
11831
+ ariaLabel: undefined
11832
+ }) : click, pathname);
11833
+ data[AUTO_LINK_ATTRIBUTE] = true;
11834
+ return {
11835
+ messageType: MessageType$1.PageEvent,
11836
+ name: LINK_CLICK_EVENT_NAME,
11837
+ data: data,
11838
+ eventType: EventType.Other
11839
+ };
11840
+ };
11841
+ // Every attribute buildButtonClickEvent can set apart from the marker and the
11842
+ // hostname, which is the same on every click on this page. A spec checks the
11843
+ // two stay in step.
11844
+ var CLICK_ATTRIBUTES = ['label_source', 'element', 'type', 'path', 'label', 'aria_label', 'icon', 'element_id', 'element_name', 'test_id', 'controls', 'form', 'link_type', 'link_domain', 'file_extension', 'checked'];
11845
+ // Which check keeps some or all auto button clicks, or auto link clicks with
11846
+ // `link`, from a kit, or undefined when the kit receives every one. A forwarding
11847
+ // rule on an attribute whose value differs between clicks lets some through and
11848
+ // not others. Each other check goes through isEventForwarded on its own, so
11849
+ // together they decide as it does for each click.
11850
+ var getButtonClickBlocker = function getButtonClickBlocker(forwarder, kitBlocker, link) {
11851
+ if (link === void 0) {
11852
+ link = false;
11853
+ }
11854
+ var rule = forwarder.filteringEventAttributeValue;
11855
+ if (rule && rule.eventAttributeValue && CLICK_ATTRIBUTES.some(function (name) {
11856
+ return KitFilterHelper.hashAttributeConditionalForwarding(name) === rule.eventAttributeName;
11857
+ })) {
11858
+ return 'forwarding rule';
11859
+ }
11860
+ var attributes = {
11861
+ hostname: window.location.hostname
11862
+ };
11863
+ attributes[link ? AUTO_LINK_ATTRIBUTE : AUTO_BUTTON_ATTRIBUTE] = true;
11864
+ var click = {
11865
+ EventName: link ? LINK_CLICK_EVENT_NAME : BUTTON_CLICK_EVENT_NAME,
11866
+ EventCategory: EventType.Other,
11867
+ EventDataType: MessageType$1.PageEvent,
11868
+ EventAttributes: attributes
11869
+ };
11870
+ var checks = [['data plan', {}], ['forwarding rule', {
11871
+ filteringEventAttributeValue: rule
11872
+ }], ['event name filter', {
11873
+ eventNameFilters: forwarder.eventNameFilters
11874
+ }], ['event type filter', {
11875
+ eventTypeFilters: forwarder.eventTypeFilters
11876
+ }]];
11877
+ var failed = checks.find(function (_a) {
11878
+ var filters = _a[1];
11879
+ return !isEventForwarded(click, filters, kitBlocker);
11880
+ });
11881
+ return failed ? failed[0] : undefined;
11882
+ };
11682
11883
 
11683
11884
  // Finds the control a click landed on, for both checkout intent and button
11684
11885
  // descriptions. Rokt's own elements, disabled controls and anything under
11685
11886
  // data-mp-no-track are never returned.
11686
11887
  var CONTROL_SELECTOR = 'button, input[type="submit"], input[type="button"], input[type="image"], [role="button"]';
11687
- // closest() over the composed tree: slots, parents, then shadow hosts.
11688
- function hasComposedAncestor(element, selector) {
11888
+ var EDITABLE = '[contenteditable]:not([contenteditable="false"])';
11889
+ // One step up the composed tree: the slot, the parent, then the shadow host.
11890
+ function composedParent(node) {
11891
+ var root = isFunction(node.getRootNode) ? node.getRootNode() : null;
11892
+ return node.assignedSlot || node.parentElement || root && root.host || null;
11893
+ }
11894
+ // Tests the element and its ancestors over the composed tree: slots, parents,
11895
+ // then shadow hosts. With `until`, stops before reaching that ancestor.
11896
+ function someComposedAncestor(element, test, until) {
11689
11897
  var node = element;
11690
- while (node) {
11691
- if (node.matches(selector)) {
11898
+ while (node && node !== until) {
11899
+ if (test(node)) {
11692
11900
  return true;
11693
11901
  }
11694
- var root = isFunction(node.getRootNode) ? node.getRootNode() : null;
11695
- node = node.assignedSlot || node.parentElement || root && root.host || null;
11902
+ node = composedParent(node);
11696
11903
  }
11697
11904
  return false;
11698
11905
  }
11906
+ // closest() over the composed tree, starting at the element itself.
11907
+ function hasComposedAncestor(element, selector) {
11908
+ return someComposedAncestor(element, function (node) {
11909
+ return node.matches(selector);
11910
+ });
11911
+ }
11699
11912
  var ROKT_SELECTOR = '[data-core-type], #rokt-controller-frame';
11700
11913
  // A Rokt frame or script is served from a /wsdk/ path.
11701
11914
  function hasRoktSrc(element) {
@@ -11741,8 +11954,27 @@ var mParticle = (function () {
11741
11954
  }
11742
11955
  return host.matches(CARD_FORM_BUTTON) ? 'checkout_step' : 'wallet';
11743
11956
  }
11957
+ // A merchant-built wallet button that names its wallet in an id or test id, such
11958
+ // as `venmo-pay-btn` or `applePayButton`. The name must also say button and not
11959
+ // name a part or a wrapper of one, so a wallet's logo, label or container never
11960
+ // counts, nor a button that only chooses a wallet. Affirm is also an English
11961
+ // verb, so it counts only beside a payment word. Only the element's own
11962
+ // attributes are read.
11963
+ var WALLET_IN_NAME = /(?:^| )(?:apple ?pay|google ?pay|g ?pay|amazon ?pay|cash ?app|venmo|pay ?pal|shop ?pay|klarna|after ?pay|clear ?pay|sezzle|samsung ?pay)(?= |$)/;
11964
+ var AFFIRM_IN_NAME = /(?:^| )affirm(?= |$)/;
11965
+ var PAYMENT_IN_NAME = /(?:^| )(?:pay|payment|checkout)(?= |$)/;
11966
+ var BUTTON_IN_NAME = /(?:^| )(?:btn|button)(?= |$)/;
11967
+ var PART_IN_NAME = /(?:^| )(?:logo|icon|image|img|label|text|caption|message|messaging|info|tooltip|help|learn|terms|promo|banner|container|wrapper|wrap|placeholder|form|section|panel|modal|list|group|select|option|tab|toggle|radio|choice|method)(?= |$)/;
11968
+ function nameWords(value) {
11969
+ return (value || '').slice(0, 64).replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/[^A-Za-z0-9]+/g, ' ').trim().toLowerCase();
11970
+ }
11971
+ function isWalletNamed(element) {
11972
+ return [element.getAttribute('id'), element.getAttribute('data-testid')].map(nameWords).some(function (words) {
11973
+ return (WALLET_IN_NAME.test(words) || AFFIRM_IN_NAME.test(words) && PAYMENT_IN_NAME.test(words)) && BUTTON_IN_NAME.test(words) && !PART_IN_NAME.test(words);
11974
+ });
11975
+ }
11744
11976
  function isWalletElement(element) {
11745
- return vendorButtonKind(element) === 'wallet';
11977
+ return vendorButtonKind(element) === 'wallet' || isWalletNamedControl(element);
11746
11978
  }
11747
11979
  // Pickers and toggles choose a payment method or open a panel; they never pay.
11748
11980
  var PICKER = 'input[type="radio"], input[type="checkbox"], label, [role="radio"], [role="tab"], [role="option"], ' + '[role="checkbox"], [role="switch"], [role="menuitemradio"], [role="menuitemcheckbox"], [aria-checked], [aria-selected]';
@@ -11750,25 +11982,53 @@ var mParticle = (function () {
11750
11982
  // Pay-later and financing messages link to details rather than pay.
11751
11983
  var MESSAGING = 'klarna-placement, afterpay-placement, square-placement, .affirm-as-low-as, .affirm-site-modal, ' + '.affirm-product-modal, [data-pp-message]';
11752
11984
  // A control that is still logged as a click but never counts as checkout intent.
11753
- function isQuietIntentControl(control, isVendorWallet) {
11985
+ // A cart button can open a flyout, so aria-expanded alone never quiets one.
11986
+ function isQuietIntentControl(control, isVendorWallet, isCart) {
11987
+ if (isCart === void 0) {
11988
+ isCart = false;
11989
+ }
11754
11990
  if (control.matches(PICKER)) {
11755
11991
  return true;
11756
11992
  }
11757
11993
  if (hasComposedAncestor(control, PICKER_GROUP)) {
11758
11994
  return true;
11759
11995
  }
11760
- if (!isVendorWallet && (control.hasAttribute('aria-expanded') || control.hasAttribute('aria-pressed'))) {
11996
+ if (!isVendorWallet && (!isCart && control.hasAttribute('aria-expanded') || control.hasAttribute('aria-pressed'))) {
11761
11997
  return true;
11762
11998
  }
11763
11999
  return hasComposedAncestor(control, MESSAGING);
11764
12000
  }
11765
12001
  // A checkbox or radio is a control of its own only when no button or link
11766
- // around it takes the click, so a tappable row keeps its own name.
12002
+ // around it takes the click, so a tappable row stays the control.
11767
12003
  var OPTION_INPUT = 'input[type="checkbox"], input[type="radio"]';
11768
- // Walks at most 12 entries of an event's composed path for the nearest control.
11769
- // One composed walk from the target rules out Rokt elements and anything under
11770
- // data-mp-no-track; disabled controls are skipped.
11771
- function findControl(path) {
12004
+ // Anything that chooses, toggles or takes typing. One between a click's target
12005
+ // and a wallet-named element, or inside that element, keeps it from becoming
12006
+ // the tap. Hidden inputs and button-like inputs are not fields.
12007
+ var NAME_BARRIER = PICKER + ', ' + PICKER_GROUP + ', input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="image"]), ' + 'select, textarea, option, optgroup, datalist, [aria-expanded], [aria-pressed], ' + EDITABLE;
12008
+ // What owns a click on anything inside it: a picker, a real control or link,
12009
+ // a toggle, or an editable region.
12010
+ var NAME_HOLDER = PICKER + ', ' + CONTROL_SELECTOR + ', a[href], ' + VENDOR_BUTTON + ', [aria-expanded], [aria-pressed], ' + EDITABLE;
12011
+ // A wallet-named element that may stand as a control by its name alone: not
12012
+ // itself a picker, toggle or form field; not inside a picker, a real button or
12013
+ // link, a toggle, an editable region or another wallet-named element, which own
12014
+ // the click with their own state; and holding no picker or field, in its open
12015
+ // shadow root either, so a payment-method row with its own radio or select
12016
+ // never becomes a wallet tap.
12017
+ function isWalletNamedControl(element) {
12018
+ if (!isWalletNamed(element) || element.matches(NAME_BARRIER)) {
12019
+ return false;
12020
+ }
12021
+ for (var node = composedParent(element); node; node = composedParent(node)) {
12022
+ if (node.matches(NAME_HOLDER) || isWalletNamed(node)) {
12023
+ return false;
12024
+ }
12025
+ }
12026
+ var shadow = element.shadowRoot;
12027
+ return !element.querySelector(NAME_BARRIER) && !(shadow && shadow.querySelector(NAME_BARRIER));
12028
+ }
12029
+ // Whether a click is never tracked: it has no element, or lands on a Rokt
12030
+ // element or under data-mp-no-track. Reads at most 12 entries of the path.
12031
+ function isExcludedPath(path) {
11772
12032
  var entries = path.slice(0, 12);
11773
12033
  var first;
11774
12034
  for (var _i = 0, entries_1 = entries; _i < entries_1.length; _i++) {
@@ -11779,40 +12039,63 @@ var mParticle = (function () {
11779
12039
  }
11780
12040
  }
11781
12041
  if (!first || hasComposedAncestor(first, ROKT_SELECTOR + ', [data-mp-no-track]')) {
12042
+ return true;
12043
+ }
12044
+ for (var _a = 0, entries_2 = entries; _a < entries_2.length; _a++) {
12045
+ var target = entries_2[_a];
12046
+ if (target instanceof Element && hasRoktSrc(target)) {
12047
+ return true;
12048
+ }
12049
+ }
12050
+ return false;
12051
+ }
12052
+ // Walks at most 12 entries of an event's composed path for the nearest control.
12053
+ // A wallet-named element counts as one unless a picker, toggle or form field
12054
+ // lies between it and the target or a picker holds it, so choosing an option in
12055
+ // or around it never becomes its tap. Excluded clicks give nothing; disabled controls are skipped, a
12056
+ // wallet-named one also by its `disabled` class. A caller that has already
12057
+ // found the path not excluded passes `included` so the walk runs once.
12058
+ function findControl(path, included) {
12059
+ if (included === void 0) {
12060
+ included = false;
12061
+ }
12062
+ if (!included && isExcludedPath(path)) {
11782
12063
  return undefined;
11783
12064
  }
11784
12065
  var control;
11785
12066
  var option;
11786
- for (var _a = 0, entries_2 = entries; _a < entries_2.length; _a++) {
11787
- var target = entries_2[_a];
12067
+ var picked = false;
12068
+ for (var _i = 0, _a = path.slice(0, 12); _i < _a.length; _i++) {
12069
+ var target = _a[_i];
12070
+ if (control) {
12071
+ break;
12072
+ }
11788
12073
  if (!(target instanceof Element)) {
11789
12074
  continue;
11790
12075
  }
11791
- if (hasRoktSrc(target)) {
11792
- return undefined;
11793
- }
11794
- if (!control && target.matches(CONTROL_SELECTOR + ', a[href], ' + VENDOR_BUTTON)) {
12076
+ if (target.matches(CONTROL_SELECTOR + ', a[href], ' + VENDOR_BUTTON) || !picked && isWalletNamedControl(target)) {
11795
12077
  control = target;
11796
- } else if (!control && !option && target.matches(OPTION_INPUT)) {
11797
- option = target;
12078
+ } else {
12079
+ if (!option && target.matches(OPTION_INPUT)) {
12080
+ option = target;
12081
+ }
12082
+ picked = picked || target.matches(NAME_BARRIER);
11798
12083
  }
11799
12084
  }
11800
12085
  control = control || option;
11801
- if (!control || control.matches(':disabled, [aria-disabled="true"]')) {
12086
+ if (!control || control.matches(':disabled, [aria-disabled="true"]') || control.classList.contains('disabled') && isWalletNamed(control)) {
11802
12087
  return undefined;
11803
12088
  }
11804
12089
  return control;
11805
12090
  }
11806
- var EDITABLE = '[contenteditable]:not([contenteditable="false"])';
11807
12091
  // Text that may hold what a shopper typed or what the page opted out: form
11808
- // fields, editable regions, and anything under data-mp-no-track.
11809
- function isUnsafeTextSource(element) {
11810
- 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);
11811
- }
11812
- // A label around a checkbox or radio holds no typed text, so only the other
11813
- // fields rule its text out.
11814
- function isUnsafeLabelSource(label) {
11815
- return !!label.querySelector('input:not([type="checkbox"]):not([type="radio"]), select, textarea, [data-mp-no-track], ' + EDITABLE) || hasComposedAncestor(label, 'select, datalist, textarea, [data-mp-no-track], ' + EDITABLE);
12092
+ // fields, editable regions, and anything under data-mp-no-track. `own` is a
12093
+ // box the element labels, which holds no typing of its own.
12094
+ function isUnsafeTextSource(element, own) {
12095
+ var inside = 'input, select, textarea, [data-mp-no-track], ' + EDITABLE;
12096
+ return element.matches('input, select, textarea, option, optgroup, datalist, ' + EDITABLE) || (own ? Array.from(element.querySelectorAll(inside)).some(function (found) {
12097
+ return found !== own;
12098
+ }) : !!element.querySelector(inside)) || hasComposedAncestor(element, 'select, datalist, textarea, [data-mp-no-track], ' + EDITABLE);
11816
12099
  }
11817
12100
 
11818
12101
  // Masks a button label before it is logged. Button text can carry personal data,
@@ -11830,10 +12113,12 @@ var mParticle = (function () {
11830
12113
  }
11831
12114
  }
11832
12115
  var digitPatterns = createDigitPatterns();
12116
+ // Longer raw labels are dropped, not cut short.
12117
+ var MAX_RAW_LABEL_LENGTH = 128;
11833
12118
  // The label as logged: lower case, at most 64 characters, digits and amounts
11834
12119
  // masked. Dropped when it looks like an email, phone or card number.
11835
12120
  function maskLabel(raw) {
11836
- if (!raw || raw.length > 128 || !digitPatterns) {
12121
+ if (!raw || raw.length > MAX_RAW_LABEL_LENGTH || !digitPatterns) {
11837
12122
  return undefined;
11838
12123
  }
11839
12124
  var text = raw.replace(/\s+/g, ' ').trim().toLowerCase();
@@ -11855,24 +12140,139 @@ var mParticle = (function () {
11855
12140
  }
11856
12141
  return control.matches('a[href]:not([role="button"])') ? 'link' : 'role';
11857
12142
  }
12143
+ var NOT_RENDERED = 'script, style, template, [hidden], [aria-hidden="true"], [inert]';
12144
+ // The most nodes read for one label source, and images looked at for one
12145
+ // button. Each costs a style lookup in the click handler.
12146
+ var MAX_READS = 64;
12147
+ function assignedNodes(element) {
12148
+ var slot = element;
12149
+ return isFunction(slot.assignedNodes) ? slot.assignedNodes() : [];
12150
+ }
12151
+ // Left out of what is drawn by its parent: a light child of an open shadow host
12152
+ // that no slot takes, or a slot's own child while nodes are assigned to the
12153
+ // slot. A closed shadow root can't be seen, so its host's children count.
12154
+ function isUnslotted(node) {
12155
+ var parent = node.parentElement;
12156
+ if (!parent) {
12157
+ return false;
12158
+ }
12159
+ return parent.shadowRoot ? !node.assignedSlot : assignedNodes(parent).length > 0;
12160
+ }
12161
+ // Hides the element and all it holds, whatever their own style says.
12162
+ // Screen-reader-only text is clipped rather than hidden, so it is not. An SVG
12163
+ // title is never drawn but names its icon, so it is not either.
12164
+ function hidesSubtree(element) {
12165
+ if (element.matches(NOT_RENDERED) || isUnslotted(element)) {
12166
+ return true;
12167
+ }
12168
+ return !(element instanceof SVGTitleElement) && window.getComputedStyle(element).display === 'none';
12169
+ }
12170
+ // Visibility is inherited and a child can set it back to visible, so only the
12171
+ // element's own counts.
12172
+ function isVisible(element) {
12173
+ var visibility = window.getComputedStyle(element).visibility;
12174
+ return visibility !== 'hidden' && visibility !== 'collapse';
12175
+ }
12176
+ function isShown(element, until) {
12177
+ return !someComposedAncestor(element, hidesSubtree, until);
12178
+ }
12179
+ // A shadow host shows a light child only through a slot, and only while
12180
+ // nothing from that slot up to the host hides it.
12181
+ function isShownThroughSlot(node, host) {
12182
+ var slot = node.assignedSlot;
12183
+ return !!slot && isShown(slot, host);
12184
+ }
12185
+ // Whitespace hides nothing, so a node holding only whitespace is taken as it
12186
+ // is, without a style lookup: an icon's paths cost nothing. A slot's text can
12187
+ // sit in the nodes assigned to it, so a slot is never blank.
12188
+ function isBlank(node) {
12189
+ return !/\S/.test(node.textContent || '') && !(node instanceof Element && (node.matches('slot') || !!node.querySelector('slot')));
12190
+ }
12191
+ // The text a sighted user or a screen reader gets from an element, whitespace
12192
+ // collapsed. Undefined when that runs past what maskLabel keeps, takes more
12193
+ // than MAX_READS nodes to read, or takes in a form field through a slot, so a
12194
+ // label is never cut short at a node boundary.
12195
+ function renderedText(root) {
12196
+ var text = '';
12197
+ var reads = 0;
12198
+ var append = function append(value) {
12199
+ text = (text + value).replace(/\s+/g, ' ');
12200
+ };
12201
+ var _read = function read(element) {
12202
+ if (hidesSubtree(element)) {
12203
+ return true;
12204
+ }
12205
+ var assigned = assignedNodes(element);
12206
+ var nodes = assigned.length ? assigned : element.childNodes;
12207
+ for (var i = 0; i < nodes.length; i++) {
12208
+ var node = nodes[i];
12209
+ var isText = node.nodeType === Node.TEXT_NODE;
12210
+ if (!isText && !(node instanceof Element)) {
12211
+ continue;
12212
+ }
12213
+ if (isBlank(node)) {
12214
+ append(node.textContent);
12215
+ continue;
12216
+ }
12217
+ if (++reads > MAX_READS) {
12218
+ return false;
12219
+ }
12220
+ if (element.shadowRoot && !isShownThroughSlot(node, element)) {
12221
+ continue;
12222
+ }
12223
+ if (isText) {
12224
+ if (isVisible(node.assignedSlot || element)) {
12225
+ append(node.nodeValue);
12226
+ }
12227
+ } else if (
12228
+ // Assigned nodes sit outside the label source, where its own
12229
+ // form field check never looked.
12230
+ assigned.length && isUnsafeTextSource(node) || !_read(node)) {
12231
+ return false;
12232
+ }
12233
+ if (text.trim().length > MAX_RAW_LABEL_LENGTH) {
12234
+ return false;
12235
+ }
12236
+ }
12237
+ return true;
12238
+ };
12239
+ return _read(root) ? text.trim() : undefined;
12240
+ }
12241
+ // '' when the element can't name the button, undefined when its text can't be
12242
+ // read in full.
11858
12243
  function textOf(element) {
11859
- return element && !isUnsafeTextSource(element) && element.textContent ? element.textContent.trim() : '';
12244
+ return element && !isUnsafeTextSource(element) && isShown(element) ? renderedText(element) : '';
12245
+ }
12246
+ // The control itself is already known to be shown.
12247
+ function shownImageAlt(control) {
12248
+ var images = control.querySelectorAll('img[alt]');
12249
+ for (var i = 0; i < images.length && i < MAX_READS; i++) {
12250
+ if (isShown(images[i], control) && isVisible(images[i])) {
12251
+ return nonBlank(images[i].getAttribute('alt'));
12252
+ }
12253
+ }
12254
+ return undefined;
11860
12255
  }
11861
12256
  function nonBlank(value) {
11862
12257
  return value && value.trim() ? value : undefined;
11863
12258
  }
11864
12259
  // The button's name as a screen reader would read it, simplified: the first of
11865
- // aria-labelledby, aria-label, own text (or an inner image's alt), title, for
11866
- // inputs their value, and for checkboxes and radios their label's text. The text
11867
- // sources are skipped for controls that hold form fields or more than four
11868
- // children, so a typed value is never read.
12260
+ // aria-labelledby, aria-label, own text (or an inner image's alt), title, and
12261
+ // for inputs their value. The text sources are skipped for controls that hold
12262
+ // form fields or more than four children, so a typed value is never read.
11869
12263
  function accessibleName(control) {
11870
12264
  var labelledBy = control.getAttribute('aria-labelledby');
11871
12265
  if (labelledBy) {
11872
12266
  var root_1 = isFunction(control.getRootNode) ? control.getRootNode() : document;
11873
- var text = labelledBy.split(/\s+/).map(function (id) {
12267
+ var parts = labelledBy.split(/\s+/).map(function (id) {
11874
12268
  return textOf(root_1.getElementById ? root_1.getElementById(id) : null);
11875
- }).filter(function (part) {
12269
+ });
12270
+ if (parts.indexOf(undefined) !== -1) {
12271
+ return {
12272
+ source: 'none'
12273
+ };
12274
+ }
12275
+ var text = parts.filter(function (part) {
11876
12276
  return !!part;
11877
12277
  }).join(' ');
11878
12278
  if (text) {
@@ -11916,27 +12316,21 @@ var mParticle = (function () {
11916
12316
  source: 'value'
11917
12317
  };
11918
12318
  }
11919
- } else if (type === 'checkbox' || type === 'radio') {
11920
- // The label the browser ties to the box, never the box's value.
11921
- var label = control.labels ? control.labels[0] : undefined;
11922
- var text = label && !isUnsafeLabelSource(label) && label.textContent ? label.textContent.trim() : '';
11923
- if (text) {
11924
- return {
11925
- raw: text,
11926
- source: 'label'
11927
- };
11928
- }
11929
12319
  }
11930
- } else if (readable) {
11931
- var text = textOf(control);
12320
+ } else if (readable && isShown(control)) {
12321
+ var text = renderedText(control);
12322
+ if (text === undefined) {
12323
+ return {
12324
+ source: 'none'
12325
+ };
12326
+ }
11932
12327
  if (text) {
11933
12328
  return {
11934
12329
  raw: text,
11935
12330
  source: 'text'
11936
12331
  };
11937
12332
  }
11938
- var image = control.querySelector('img[alt]');
11939
- var alt = image ? nonBlank(image.getAttribute('alt')) : undefined;
12333
+ var alt = shownImageAlt(control);
11940
12334
  if (alt) {
11941
12335
  return {
11942
12336
  raw: alt,
@@ -12034,13 +12428,16 @@ var mParticle = (function () {
12034
12428
  return form ? stableIdentifier(form.getAttribute('id')) : undefined;
12035
12429
  }
12036
12430
  // The browser's normalised type: a button defaults to 'submit' and an unknown
12037
- // input type reads as 'text'. Wallet and role buttons are just 'button'.
12038
- function controlType(control, element) {
12039
- return element === 'button' || element === 'input' ? control.type : 'button';
12431
+ // input type reads as 'text'. A native button or input reports its own type, a
12432
+ // wallet-named one included; any other control, such as a role button or a
12433
+ // vendor's wallet element, reads as 'button'.
12434
+ function controlType(control) {
12435
+ var tag = control.tagName.toLowerCase();
12436
+ return tag === 'button' || tag === 'input' ? control.type : 'button';
12040
12437
  }
12041
12438
  // Reads the link's own resolved URL parts, never its full href: only the hostname of
12042
- // a link that leaves the site and the extension of a file it downloads travel. Navigation
12043
- // within the site is left to page views, and mailto:, tel: and other schemes are skipped.
12439
+ // a link that leaves the site and the extension of a file it downloads travel.
12440
+ // mailto:, tel: and other schemes give undefined.
12044
12441
  function describeLink(link) {
12045
12442
  var href = (link.getAttribute('href') || '').trim();
12046
12443
  if (!href || href.charAt(0) === '#') {
@@ -12077,43 +12474,149 @@ var mParticle = (function () {
12077
12474
  }
12078
12475
  return link.pathname === window.location.pathname && link.search === window.location.search ? {
12079
12476
  type: 'same_page'
12080
- } : undefined;
12477
+ } : {
12478
+ type: 'internal'
12479
+ };
12480
+ }
12481
+ // Whether a described click is on a link that leaves the page, logged as a link
12482
+ // click rather than a button click.
12483
+ function isLinkClick(click) {
12484
+ return !!click.linkType && click.linkType !== 'same_page';
12081
12485
  }
12082
- // Describes a clicked control for auto button tracking. A link counts only when page
12083
- // views can't see the click, and carries its link type.
12084
- function describeButtonClick(control) {
12486
+ // Checkboxes and radios, and the roles that act as them. A role can list
12487
+ // fallbacks, so any of its tokens counts.
12488
+ var CHOICE = 'input[type="checkbox"], input[type="radio"], [role~="checkbox"], [role~="radio"], ' + '[role~="switch"], [role~="menuitemcheckbox"], [role~="menuitemradio"]';
12489
+ var RADIO_ROLE = '[role~="radio"], [role~="menuitemradio"]';
12490
+ function boxOf(element) {
12491
+ var box = element instanceof HTMLLabelElement ? element.control : element;
12492
+ return box && box.matches(CHOICE) ? box : null;
12493
+ }
12494
+ function isRadio(choice) {
12495
+ return choice instanceof HTMLInputElement ? choice.type === 'radio' : !!choice && choice.matches(RADIO_ROLE);
12496
+ }
12497
+ // The box a click on this control picks. One the click passed through below
12498
+ // the control comes first: querySelector can't reach a slotted box, a box in a
12499
+ // shadow root or the box of a label inside the control, and a row can hold
12500
+ // more than one box. Else the control itself, the box it labels, or the first
12501
+ // one it wraps, as on an option row whose name repeats the option's text.
12502
+ function choiceOf(control, path) {
12503
+ for (var _i = 0, path_1 = path; _i < path_1.length; _i++) {
12504
+ var target = path_1[_i];
12505
+ if (target === control) {
12506
+ break;
12507
+ }
12508
+ var box = target instanceof Element ? boxOf(target) : null;
12509
+ if (box) {
12510
+ return box;
12511
+ }
12512
+ }
12513
+ return boxOf(control) || control.querySelector(CHOICE);
12514
+ }
12515
+ // Describes a clicked control for auto button and link tracking. A link carries its
12516
+ // link type, and one to another scheme is skipped. A role="button" anchor carries one
12517
+ // only when it leaves the page; otherwise it stays a plain button.
12518
+ function describeButtonClick(control, path) {
12519
+ if (path === void 0) {
12520
+ path = [];
12521
+ }
12085
12522
  var link;
12086
12523
  if (control.matches('a[href]:not([role="button"])')) {
12087
12524
  link = describeLink(control);
12088
12525
  if (!link) {
12089
12526
  return undefined;
12090
12527
  }
12528
+ } else if (control.matches('a[href]')) {
12529
+ link = describeLink(control);
12530
+ if (link && link.type === 'same_page') {
12531
+ link = undefined;
12532
+ }
12091
12533
  }
12092
12534
  var element = elementKind(control);
12093
- var name = accessibleName(control);
12094
- var label = maskLabel(name.raw);
12535
+ var box = control instanceof HTMLInputElement && (control.type === 'checkbox' || control.type === 'radio') ? control : undefined;
12536
+ // A box's text sent with its state is the shopper's answer, and the text
12537
+ // can be a name, an address or a sensitive choice, so a box goes by its
12538
+ // hand-written identifiers alone. So does a control acting as one or
12539
+ // wrapping one, and a label, whose click the browser passes on to its box
12540
+ // as a second event.
12541
+ var choice = box || choiceOf(control, path);
12542
+ // A checkbox's identifiers name its question, which its state answers. A
12543
+ // radio's id, test id and the panel it opens name the option picked, so
12544
+ // only the name of its input, the question, goes with it. A wrapper's own
12545
+ // name, or a role radio's, can name the option instead.
12546
+ var radio = isRadio(choice);
12547
+ var named = !radio ? control : choice instanceof HTMLInputElement ? choice : null;
12548
+ var name = choice ? undefined : accessibleName(control);
12549
+ var label = name ? maskLabel(name.raw) : undefined;
12095
12550
  return {
12096
12551
  label: label,
12097
12552
  labelSource: label ? name.source : 'none',
12098
12553
  // Kept on its own as well: it often says more than the visible text,
12099
12554
  // such as which item a remove button removes.
12100
- ariaLabel: maskLabel(control.getAttribute('aria-label')),
12101
- icon: iconHint(control),
12102
- elementId: stableIdentifier(control.getAttribute('id')),
12103
- elementName: stableIdentifier(control.getAttribute('name')),
12104
- testId: firstIdentifier(control, TEST_ID_ATTRIBUTES),
12105
- controls: controlsIdentifier(control),
12555
+ ariaLabel: choice ? undefined : maskLabel(control.getAttribute('aria-label')),
12556
+ icon: choice ? undefined : iconHint(control),
12557
+ elementId: radio ? undefined : stableIdentifier(control.getAttribute('id')),
12558
+ elementName: stableIdentifier(named && named.getAttribute('name')),
12559
+ testId: radio ? undefined : firstIdentifier(control, TEST_ID_ATTRIBUTES),
12560
+ controls: radio ? undefined : controlsIdentifier(control),
12106
12561
  form: formIdentifier(control),
12107
12562
  element: element,
12108
- type: controlType(control, element),
12563
+ type: controlType(control),
12109
12564
  linkType: link ? link.type : undefined,
12110
12565
  linkDomain: link ? link.domain : undefined,
12111
12566
  fileExtension: link ? link.fileExtension : undefined,
12112
12567
  // The box as the click left it: the browser toggles it before the click
12113
12568
  // reaches listeners, and puts it back if the click is cancelled.
12114
- checked: control instanceof HTMLInputElement && (control.type === 'checkbox' || control.type === 'radio') ? control.checked : undefined
12569
+ checked: box ? box.checked : undefined
12115
12570
  };
12116
12571
  }
12572
+ // A question about payment, read from the names its author gave it, so
12573
+ // "checkout[payment_gateway]", "paymentMethod" and "tender_type" all count and
12574
+ // "payee" does not. A radio's input name is its question; its group or
12575
+ // fieldset speaks only for a radio with no name, as a role radio usually is.
12576
+ var PAYMENT_QUESTION = /(?:^| )(?:pay|payment\w*|tenders?|gateways?)(?: |$)/;
12577
+ var QUESTION_GROUP = '[role="radiogroup"], fieldset';
12578
+ function isPaymentQuestion(radio) {
12579
+ var name = radio.getAttribute('name');
12580
+ var group = name ? null : radio.closest(QUESTION_GROUP);
12581
+ var question = group ? [group.getAttribute('id'), group.getAttribute('name'), group.getAttribute('aria-label')] : [name];
12582
+ return question.some(function (words) {
12583
+ return PAYMENT_QUESTION.test(nameWords(words));
12584
+ });
12585
+ }
12586
+ // What names a payment option: the radio's accessible name, the text and
12587
+ // image of each label, and its hand-written ids, text masked like a label.
12588
+ function optionNames(radio) {
12589
+ var names = [maskLabel(accessibleName(radio).raw)];
12590
+ var labels = radio instanceof HTMLInputElement && radio.labels ? Array.from(radio.labels).slice(0, 2) : [];
12591
+ labels.forEach(function (label) {
12592
+ if (isShown(label) && !isUnsafeTextSource(label, radio)) {
12593
+ names.push(maskLabel(renderedText(label)));
12594
+ names.push(maskLabel(shownImageAlt(label)));
12595
+ }
12596
+ });
12597
+ names.push(stableIdentifier(radio.getAttribute('id')), firstIdentifier(radio, TEST_ID_ATTRIBUTES));
12598
+ return names.filter(function (name, index) {
12599
+ return !!name && names.indexOf(name) === index;
12600
+ });
12601
+ }
12602
+ function describePaymentChoice(control, path) {
12603
+ if (path === void 0) {
12604
+ path = [];
12605
+ }
12606
+ var choice = choiceOf(control, path);
12607
+ if (!isRadio(choice) || hasComposedAncestor(choice, '[data-mp-no-track]') || !isPaymentQuestion(choice)) {
12608
+ return undefined;
12609
+ }
12610
+ var names = optionNames(choice);
12611
+ return names.length ? {
12612
+ radio: choice,
12613
+ names: names
12614
+ } : undefined;
12615
+ }
12616
+ // Read once the page has handled the click, which may have cancelled it.
12617
+ function isPicked(radio) {
12618
+ return radio instanceof HTMLInputElement ? radio.checked : radio.getAttribute('aria-checked') === 'true';
12619
+ }
12117
12620
 
12118
12621
  var checkoutPath = /(?:^|\/)(?:checkouts?|payment|order|kasse|paiement|commander?|pago)(?:\/|$)/i;
12119
12622
  // English look-alikes are whole words; the other languages stay substring
@@ -12128,6 +12631,12 @@ var mParticle = (function () {
12128
12631
  var linkCommits = new RegExp('^(?:(?:place|submit|complete)(?!(?: [^ ]+){0,2} (?:another|new|next)(?: |$))(?: [^ ]+){0,2} (?:order|purchase)|' + otherCommits + ')$');
12129
12632
  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)$');
12130
12633
  var steps = new RegExp('^(?:' + ["(?:let's |lets |go to |proceed to |secure )?(?:check ?out|checkout)(?: now| securely| as guest)?", '(?:continue|proceed) to (?:payment|review|billing|shipping|delivery|order review)|review (?:my |your )?order', 'guest checkout|(?:pay|continue|check ?out) (?:with|by) (?:card|credit card|debit card|credit or debit card)', '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('|') + ')$');
12634
+ var CART_NOUN = '(?:(?:my|your|the) )?(?:shopping )?(?:cart|bag|basket)';
12635
+ // "Add to bag" and "Add 2 to cart": a click that puts something in the cart.
12636
+ var addToCartLabels = new RegExp('^add(?: [^ ]+){0,5} to ' + CART_NOUN + '$');
12637
+ // "View cart", "Shopping bag", "Bag", and a count badge such as "1 item in bag",
12638
+ // which reads "item in bag" once digits are stripped: a click that opens the cart.
12639
+ var openCartLabels = new RegExp('^(?:(?:(?:view|open|go to) )?' + CART_NOUN + '(?: items?)?' + '|items? in ' + CART_NOUN + ')$');
12131
12640
  var WALLET_NAMES = 'apple pay|google pay|paypal|venmo|amazon pay|shop pay|klarna|afterpay|clearpay|cash app(?: pay)?|affirm|sezzle|samsung pay';
12132
12641
  var WALLET_VERBS = 'pay|buy|purchase|order|book|donate|subscribe|check ?out|checkout|express checkout|continue|proceed|' + 'complete purchase|place (?:my |your )?order';
12133
12642
  // "Pay with Klarna", "Buy with GPay", "Continue to PayPal".
@@ -12135,14 +12644,27 @@ var mParticle = (function () {
12135
12644
  // A wallet's own name on a button: "PayPal", "Klarna checkout", "Apple Pay logo".
12136
12645
  var walletBrands = new RegExp('^(?:' + WALLET_NAMES + ')(?: (?:checkout|check out|express(?: checkout)?|express pay|buy now|pay now|pay|now)' + '| (?:logo|icon|mark|button)(?: (?:logo|icon|mark|button))*)?$');
12137
12646
  // Exact HTTPS hosts of hosted wallet button frames.
12138
- var walletFrameHosts = new Set(['pay.google.com', 'applepay.cdn-apple.com', 'payments.amazon.com', 'pay.shopify.com']);
12647
+ var walletFrameFamilies = {
12648
+ 'pay.google.com': 'google_pay',
12649
+ 'applepay.cdn-apple.com': 'apple_pay',
12650
+ 'payments.amazon.com': 'amazon_pay',
12651
+ 'pay.shopify.com': 'shop_pay'
12652
+ };
12139
12653
  // PayPal serves its buttons and its card fields and pay-later messages from
12140
12654
  // the same hosts; only the button frames are wallet taps.
12141
12655
  var paypalFrameHosts = new Set(['www.paypal.com', 'www.sandbox.paypal.com']);
12142
12656
  var PAYPAL_BUTTON_FRAME = /^\/smart\/(?:buttons|menu)(?:\/|$)/;
12657
+ var PAYPAL_CARD_FRAME = /^\/smart\/card-fields?(?:\/|$)/;
12658
+ // PayPal's JS SDK, used directly or wrapped by a processor, leaves its frames
12659
+ // without a src and names them __zoid__paypal_<component>__ instead.
12660
+ var PAYPAL_SDK_FRAME = /^__zoid__paypal_/;
12661
+ var PAYPAL_SDK_BUTTON_FRAME = /^__zoid__paypal_(?:buttons|menu)__/;
12143
12662
  // Stripe's quick-pay button frames; its other frames are card and form fields.
12144
12663
  var STRIPE_WALLET_FRAME = /\/elements-inner-(?:payment-request|express-checkout)-/;
12664
+ var STRIPE_CARD_FRAME = /\/elements-inner-card\b/;
12145
12665
  // Hosted card-entry and fingerprint frames: focus there is typing card details.
12666
+ // Some of these hosts also serve gift card and bank account fields, so none
12667
+ // names a family.
12146
12668
  var CARD_FRAME_DOMAINS = ['adyen.com', 'braintreegateway.com', 'cybersource.com', 'squarecdn.com', 'checkout.com'];
12147
12669
  // Keep these classes ASCII-only in source bytes to avoid mojibake parse failures
12148
12670
  // when a host page forces non-UTF-8 decoding before JavaScript parse.
@@ -12203,6 +12725,22 @@ var mParticle = (function () {
12203
12725
  return !!label;
12204
12726
  });
12205
12727
  }
12728
+ // A cart button is often a bare icon, so its title names it when nothing else
12729
+ // does. Only the cart rule reads the title. Opening the cart counts only for a
12730
+ // control that opens it on the page, such as a flyout: one that leads to the cart
12731
+ // page unloads before a call made on the click can return.
12732
+ function isCartControl(control, cores) {
12733
+ var title = cores.length ? undefined : normalize(control.getAttribute('title'));
12734
+ var texts = title ? [coreLabel(title)] : cores;
12735
+ if (texts.some(function (text) {
12736
+ return addToCartLabels.test(text);
12737
+ })) {
12738
+ return true;
12739
+ }
12740
+ return (control.hasAttribute('aria-expanded') || control.hasAttribute('aria-haspopup')) && texts.some(function (text) {
12741
+ return openCartLabels.test(text);
12742
+ });
12743
+ }
12206
12744
  // "Buy now" and "Purchase" count only on a checkout route, and never on a plain
12207
12745
  // link, which is navigation.
12208
12746
  function isCommitLabel(text, isLink, pathname) {
@@ -12214,20 +12752,33 @@ var mParticle = (function () {
12214
12752
  // Classifies a clicked or submitting control found by findControl, in order:
12215
12753
  // pickers, toggles and pay-later messages never count; a vendor's own wallet
12216
12754
  // button counts before any label rule; a confirm label on any source counts
12217
- // before the look-alike exclusions veto; then wallet phrases and steps.
12755
+ // before the look-alike exclusions veto; then a wallet-named control, wallet
12756
+ // phrases and steps. An add-to-cart label, links included, or a control that
12757
+ // opens the cart on the page counts only where none of those do.
12218
12758
  function classifyIntent(control, pathname) {
12219
12759
  var vendor = vendorButtonKind(control);
12220
- if (isQuietIntentControl(control, vendor === 'wallet') || vendor === 'none') {
12760
+ if (vendor === 'none') {
12221
12761
  return undefined;
12222
12762
  }
12223
12763
  if (vendor) {
12224
- return {
12764
+ return isQuietIntentControl(control, vendor === 'wallet') ? undefined : {
12225
12765
  kind: vendor,
12226
12766
  source: 'vendor'
12227
12767
  };
12228
12768
  }
12229
12769
  var texts = labels(control);
12230
12770
  var cores = texts.map(coreLabel);
12771
+ var cart = isCartControl(control, cores) ? {
12772
+ kind: 'cart',
12773
+ source: 'label'
12774
+ } : undefined;
12775
+ if (isQuietIntentControl(control, false, !!cart)) {
12776
+ return undefined;
12777
+ }
12778
+ // A bag button that opens a flyout counts only as a cart click.
12779
+ if (cart && control.hasAttribute('aria-expanded')) {
12780
+ return cart;
12781
+ }
12231
12782
  var isLink = control.matches('a[href]:not([role="button"])');
12232
12783
  if (cores.some(function (text) {
12233
12784
  return isCommitLabel(text, isLink, pathname);
@@ -12240,7 +12791,13 @@ var mParticle = (function () {
12240
12791
  if (texts.concat(cores).some(function (text) {
12241
12792
  return englishExclusions.test(text) || otherExclusions.test(text);
12242
12793
  })) {
12243
- return undefined;
12794
+ return cart;
12795
+ }
12796
+ if (isWalletNamedControl(control)) {
12797
+ return {
12798
+ kind: 'wallet',
12799
+ source: 'name'
12800
+ };
12244
12801
  }
12245
12802
  var isButton = !isLink && control.matches(CONTROL_SELECTOR);
12246
12803
  for (var _i = 0, cores_1 = cores; _i < cores_1.length; _i++) {
@@ -12264,37 +12821,73 @@ var mParticle = (function () {
12264
12821
  };
12265
12822
  }
12266
12823
  }
12267
- return undefined;
12824
+ return cart;
12268
12825
  }
12269
12826
  // The host is the domain or one of its subdomains, never a look-alike suffix.
12270
12827
  function isOnDomain(host, domain) {
12271
12828
  return host === domain || host.length > domain.length && host.slice(host.length - domain.length - 1) === '.' + domain;
12272
12829
  }
12273
12830
  // Focus moving into a visible hosted wallet button counts as a wallet click;
12274
- // focus into hosted card fields or messages is only a frame.
12275
- function classifyIntentFrame(target, win) {
12831
+ // focus into hosted card fields or messages is only a frame. A family comes
12832
+ // only from a frame that can draw no other method: PayPal's button frame also
12833
+ // draws Pay Later, Venmo and its card form, Stripe's quick-pay frame draws
12834
+ // Apple Pay or Google Pay by browser, and its payment form holds several.
12835
+ function classifyPaymentFrame(target, win) {
12276
12836
  if (win.document.visibilityState !== 'visible' || !(target instanceof HTMLIFrameElement)) {
12277
12837
  return undefined;
12278
12838
  }
12279
12839
  if (isRoktElement(target) || hasComposedAncestor(target, '[data-mp-no-track]')) {
12280
12840
  return undefined;
12281
12841
  }
12842
+ // A frame that has its own src is judged by that address, never by its name.
12843
+ if ((!target.src || target.src === 'about:blank') && PAYPAL_SDK_FRAME.test(target.name)) {
12844
+ return PAYPAL_SDK_BUTTON_FRAME.test(target.name) ? {
12845
+ kind: 'wallet'
12846
+ } : {
12847
+ kind: 'frame'
12848
+ };
12849
+ }
12282
12850
  try {
12283
12851
  var url = new URL(target.src);
12284
12852
  var host_1 = url.protocol === 'https:' ? url.hostname : '';
12285
12853
  if (paypalFrameHosts.has(host_1)) {
12286
- return PAYPAL_BUTTON_FRAME.test(url.pathname) ? 'wallet' : 'frame';
12854
+ if (PAYPAL_BUTTON_FRAME.test(url.pathname)) {
12855
+ return {
12856
+ kind: 'wallet'
12857
+ };
12858
+ }
12859
+ return PAYPAL_CARD_FRAME.test(url.pathname) ? {
12860
+ kind: 'frame',
12861
+ family: 'card'
12862
+ } : {
12863
+ kind: 'frame'
12864
+ };
12287
12865
  }
12288
- if (walletFrameHosts.has(host_1)) {
12289
- return 'wallet';
12866
+ if (Object.prototype.hasOwnProperty.call(walletFrameFamilies, host_1)) {
12867
+ return {
12868
+ kind: 'wallet',
12869
+ family: walletFrameFamilies[host_1]
12870
+ };
12290
12871
  }
12291
12872
  if (host_1 === 'js.stripe.com') {
12292
- return STRIPE_WALLET_FRAME.test(url.pathname) ? 'wallet' : 'frame';
12873
+ if (STRIPE_WALLET_FRAME.test(url.pathname)) {
12874
+ return {
12875
+ kind: 'wallet'
12876
+ };
12877
+ }
12878
+ return STRIPE_CARD_FRAME.test(url.pathname) ? {
12879
+ kind: 'frame',
12880
+ family: 'card'
12881
+ } : {
12882
+ kind: 'frame'
12883
+ };
12293
12884
  }
12294
12885
  if (CARD_FRAME_DOMAINS.some(function (domain) {
12295
12886
  return isOnDomain(host_1, domain);
12296
12887
  })) {
12297
- return 'frame';
12888
+ return {
12889
+ kind: 'frame'
12890
+ };
12298
12891
  }
12299
12892
  } catch (e) {
12300
12893
  // An unparseable frame src is not a wallet frame.
@@ -12307,6 +12900,8 @@ var mParticle = (function () {
12307
12900
  var WIN_BTN_KEY = '__mpBtn__';
12308
12901
  var THROTTLE_MS = 2000;
12309
12902
  var SUBMIT_AFTER_COMMIT_CLICK_MS = 1000;
12903
+ // Apple Pay must begin inside the tap's own handler, so the tap is this recent.
12904
+ var APPLE_PAY_AFTER_CLICK_MS = 1000;
12310
12905
  var PATH_LIMIT = 12;
12311
12906
  // A ceiling, not a dedupe: a held Enter key or browser automation fires a
12312
12907
  // trusted click per repeat, and each one is forwarded to every kit.
@@ -12345,6 +12940,63 @@ var mParticle = (function () {
12345
12940
  setTimeout(fn, 0);
12346
12941
  }
12347
12942
  };
12943
+ // Apple Pay's session opens only from a shopper's tap, whatever button the page
12944
+ // draws for it, so its opening is a wallet tap no label or name rule can miss.
12945
+ // The wrapper keeps the page's own begin under this key, to put it back.
12946
+ var APPLE_PAY_HOOK = '__mpApplePayHook__';
12947
+ var applePayPrototype = function applePayPrototype(win) {
12948
+ var session = win['ApplePaySession'];
12949
+ return session && session.prototype;
12950
+ };
12951
+ // Wraps ApplePaySession.prototype.begin once per window, so the active tracker
12952
+ // hears that a session began. The wrapper reads no arguments and no payment
12953
+ // data, and returns or throws exactly what the original does. Nothing here can
12954
+ // throw into SDK init: a throwing getter or a frozen prototype leaves Apple Pay
12955
+ // as it was.
12956
+ var hookApplePay = function hookApplePay(win) {
12957
+ try {
12958
+ var prototype = applePayPrototype(win);
12959
+ var begin_1 = prototype && prototype.begin;
12960
+ if (!isFunction(begin_1) || begin_1[APPLE_PAY_HOOK]) {
12961
+ return;
12962
+ }
12963
+ var opened = function opened() {
12964
+ var args = [];
12965
+ for (var _i = 0; _i < arguments.length; _i++) {
12966
+ args[_i] = arguments[_i];
12967
+ }
12968
+ var result = begin_1.apply(this, args);
12969
+ try {
12970
+ var tracker = getActiveButtonTracker();
12971
+ if (tracker) {
12972
+ tracker.onApplePayBegin();
12973
+ }
12974
+ } catch (e) {
12975
+ // A session the page began is never disturbed by tracking.
12976
+ }
12977
+ return result;
12978
+ };
12979
+ // Not enumerable, so the marker doesn't show among the method's own keys.
12980
+ Object.defineProperty(opened, APPLE_PAY_HOOK, {
12981
+ value: begin_1
12982
+ });
12983
+ prototype.begin = opened;
12984
+ } catch (e) {
12985
+ // Apple Pay stays as the page set it up.
12986
+ }
12987
+ };
12988
+ // Puts the page's own begin back, unless another script has wrapped it since.
12989
+ var unhookApplePay = function unhookApplePay(win) {
12990
+ try {
12991
+ var prototype = applePayPrototype(win);
12992
+ var begin = prototype && prototype.begin;
12993
+ if (isFunction(begin) && isFunction(begin[APPLE_PAY_HOOK])) {
12994
+ prototype.begin = begin[APPLE_PAY_HOOK];
12995
+ }
12996
+ } catch (e) {
12997
+ // Apple Pay stays as it is.
12998
+ }
12999
+ };
12348
13000
  var CAPTURE_PASSIVE = {
12349
13001
  capture: true,
12350
13002
  passive: true
@@ -12352,16 +13004,22 @@ var mParticle = (function () {
12352
13004
  var PASSIVE = {
12353
13005
  passive: true
12354
13006
  };
12355
- // Logs every button click as an mParticle event, and hands checkout intent to the
12356
- // Rokt kit as a frozen {kind, t, source, pathname}. Submits and wallet frame focus feed the kit only.
13007
+ // Logs button clicks and clicks on links that leave the page as mParticle events, and hands checkout
13008
+ // intent to the Rokt kit as a frozen {kind, t, source, pathname}. Submits and wallet frame focus feed the kit only,
13009
+ // and so do payment picks: a payment radio's option names, or the payment family of a focused frame.
12357
13010
  var ButtonTracker = /** @class */function () {
12358
13011
  function ButtonTracker(mpInstance) {
12359
13012
  var _this = this;
12360
13013
  this.active = false;
12361
13014
  this.hasRoktKit = false;
13015
+ this.logButtons = false;
13016
+ this.logLinks = false;
12362
13017
  // Bumped on reset so classification queued before it is dropped.
12363
13018
  this.generation = 0;
12364
13019
  this.lastCommitClick = -Infinity;
13020
+ // The latest trusted click: whether the page excluded it, and the intent it
13021
+ // already carries to the kit, with its source, if any.
13022
+ this.lastClick = null;
12365
13023
  this.lastSent = {};
12366
13024
  this.pagePath = null;
12367
13025
  this.pageEvents = 0;
@@ -12429,7 +13087,9 @@ var mParticle = (function () {
12429
13087
  this.teardown();
12430
13088
  }
12431
13089
  this.hasRoktKit = options.hasRoktKit;
12432
- if (!this.eventsEnabled() && this.isKitSuppressed()) {
13090
+ this.logButtons = options.logButtons;
13091
+ this.logLinks = options.logLinks;
13092
+ if ((!(this.logButtons || this.logLinks) || !this.eventsEnabled()) && this.isKitSuppressed()) {
12433
13093
  this.log('opted out, not starting');
12434
13094
  return;
12435
13095
  }
@@ -12445,8 +13105,40 @@ var mParticle = (function () {
12445
13105
  win[WIN_BTN_KEY] = {};
12446
13106
  }
12447
13107
  win[WIN_BTN_KEY].tracker = this;
13108
+ // Only where checkout intent can reach the kit; a shopper who opts back in mid-page gets no session
13109
+ // signal until the next page load.
13110
+ if (!this.isKitSuppressed()) {
13111
+ hookApplePay(win);
13112
+ }
12448
13113
  this.log('listening');
12449
13114
  };
13115
+ // An Apple Pay session begun right after a tracked tap counts as a wallet
13116
+ // tap for the kit; one begun with no such tap, or from a tap the page
13117
+ // excluded, does not. A tap that reads as a confirm tap reaches the kit as
13118
+ // one. It is delivered on a timer, not after the next paint, since the
13119
+ // payment sheet may hold painting back: a tap that already reads as a
13120
+ // wallet goes now under its own rule, and the throttle drops the copy
13121
+ // queued for the paint.
13122
+ ButtonTracker.prototype.onApplePayBegin = function () {
13123
+ var _this = this;
13124
+ var t = Date.now();
13125
+ var click = this.lastClick;
13126
+ if (!this.active || !click || click.excluded || click.kind === 'commit' || t - click.t > APPLE_PAY_AFTER_CLICK_MS || this.isKitSuppressed()) {
13127
+ return;
13128
+ }
13129
+ var pathname = window.location.pathname;
13130
+ var generation = this.generation;
13131
+ setTimeout(function () {
13132
+ if (generation !== _this.generation) {
13133
+ return;
13134
+ }
13135
+ try {
13136
+ _this.emit('wallet', click.kind === 'wallet' ? click.source : 'api', 'apple_pay', t, pathname);
13137
+ } catch (e) {
13138
+ _this.log('[error] Apple Pay delivery threw: ' + e);
13139
+ }
13140
+ }, 0);
13141
+ };
12450
13142
  ButtonTracker.prototype.teardown = function () {
12451
13143
  var win = btnWindow();
12452
13144
  if (win) {
@@ -12460,6 +13152,7 @@ var mParticle = (function () {
12460
13152
  var state = win[WIN_BTN_KEY];
12461
13153
  if (state && state.tracker === this) {
12462
13154
  state.tracker = undefined;
13155
+ unhookApplePay(win);
12463
13156
  }
12464
13157
  }
12465
13158
  this.active = false;
@@ -12479,6 +13172,7 @@ var mParticle = (function () {
12479
13172
  this.generation++;
12480
13173
  this.pending = [];
12481
13174
  this.lastCommitClick = -Infinity;
13175
+ this.lastClick = null;
12482
13176
  this.lastSent = {};
12483
13177
  this.pagePath = null;
12484
13178
  this.pageEvents = 0;
@@ -12496,7 +13190,7 @@ var mParticle = (function () {
12496
13190
  return;
12497
13191
  }
12498
13192
  var layer = event.type;
12499
- var logsEvents = layer === 'click' && this.eventsEnabled();
13193
+ var logsEvents = layer === 'click' && (this.logButtons || this.logLinks) && this.eventsEnabled() && !this.hasTargetingOptOut();
12500
13194
  var signalsKit = !this.isKitSuppressed();
12501
13195
  if (!logsEvents && !signalsKit) {
12502
13196
  return;
@@ -12508,15 +13202,31 @@ var mParticle = (function () {
12508
13202
  // button that disables or relabels itself is seen as it was clicked.
12509
13203
  // Logging and the kit hand-off wait for the next paint.
12510
13204
  var click;
13205
+ var asLink = false;
12511
13206
  var box;
12512
13207
  var kind;
12513
13208
  var source;
13209
+ var choice;
13210
+ var family;
12514
13211
  var unconfirmedCommit = false;
12515
13212
  if (layer === 'click') {
12516
13213
  // The composed path is only populated during dispatch.
12517
- var control = findControl(isFunction(event.composedPath) ? event.composedPath().slice(0, PATH_LIMIT) : []);
13214
+ var path = isFunction(event.composedPath) ? event.composedPath().slice(0, PATH_LIMIT) : [];
13215
+ var excluded = isExcludedPath(path);
13216
+ var control = excluded ? undefined : findControl(path, true);
12518
13217
  if (control) {
12519
- click = logsEvents ? describeButtonClick(control) : undefined;
13218
+ // With links alone, only an anchor can be logged.
13219
+ click = logsEvents && (this.logButtons || control.matches('a[href]')) ? describeButtonClick(control, path) : undefined;
13220
+ if (click) {
13221
+ asLink = this.logLinks && isLinkClick(click);
13222
+ // Without link tracking, a link that leaves the page stays a
13223
+ // button click, except a plain link to another page of the
13224
+ // site, which button tracking never logged.
13225
+ var asButton = this.logButtons && !asLink && !(click.linkType === 'internal' && control.matches('a[href]:not([role="button"])'));
13226
+ if (!asLink && !asButton) {
13227
+ click = undefined;
13228
+ }
13229
+ }
12520
13230
  if (click && click.checked !== undefined) {
12521
13231
  box = control;
12522
13232
  }
@@ -12524,7 +13234,14 @@ var mParticle = (function () {
12524
13234
  var intent = signalsKit ? classifyIntent(control, pathname) : undefined;
12525
13235
  kind = intent ? intent.kind : undefined;
12526
13236
  source = intent ? intent.source : undefined;
13237
+ choice = signalsKit ? describePaymentChoice(control, path) : undefined;
12527
13238
  }
13239
+ this.lastClick = {
13240
+ t: t,
13241
+ excluded: excluded,
13242
+ kind: kind,
13243
+ source: source
13244
+ };
12528
13245
  } else if (layer === 'submit') {
12529
13246
  var submitter = event.submitter;
12530
13247
  var control = submitter ? findControl([submitter]) : undefined;
@@ -12549,7 +13266,9 @@ var mParticle = (function () {
12549
13266
  if (layer === 'blur') {
12550
13267
  // Read after the blur settles: some browsers have not moved
12551
13268
  // focus into the frame yet when the window blurs.
12552
- kind = classifyIntentFrame(document.activeElement, window);
13269
+ var frame = classifyPaymentFrame(document.activeElement, window);
13270
+ kind = frame ? frame.kind : undefined;
13271
+ family = frame ? frame.family : undefined;
12553
13272
  source = 'frame';
12554
13273
  } else if (unconfirmedCommit && t - _this.lastCommitClick > SUBMIT_AFTER_COMMIT_CLICK_MS) {
12555
13274
  kind = undefined;
@@ -12563,7 +13282,14 @@ var mParticle = (function () {
12563
13282
  if (box && event.defaultPrevented) {
12564
13283
  click.checked = box.type === 'checkbox' ? !click.checked : box.checked;
12565
13284
  }
12566
- _this.logClick(click, pathname);
13285
+ _this.logClick(click, pathname, asLink);
13286
+ }
13287
+ // Ahead of the intent, so a pre-selection it fires goes out
13288
+ // with this pick.
13289
+ if (choice && isPicked(choice.radio)) {
13290
+ _this.pickPayment(t, pathname, choice.names);
13291
+ } else if (family) {
13292
+ _this.pickPayment(t, pathname, undefined, family);
12567
13293
  }
12568
13294
  if (kind) {
12569
13295
  _this.emit(kind, source, layer, t, pathname);
@@ -12574,8 +13300,8 @@ var mParticle = (function () {
12574
13300
  });
12575
13301
  };
12576
13302
  // Every real click is one event, repeats included, up to a ceiling per page.
12577
- ButtonTracker.prototype.logClick = function (click, pathname) {
12578
- if (!this.eventsEnabled()) {
13303
+ ButtonTracker.prototype.logClick = function (click, pathname, asLink) {
13304
+ if (!this.eventsEnabled() || this.hasTargetingOptOut()) {
12579
13305
  return;
12580
13306
  }
12581
13307
  if (pathname !== this.pagePath) {
@@ -12589,7 +13315,7 @@ var mParticle = (function () {
12589
13315
  // Restarts an expired session, as public logEvent() and page views do.
12590
13316
  this.mpInstance._SessionManager.resetSessionTimer();
12591
13317
  var queued = this.queuedForUpload();
12592
- this.mpInstance._Events.logEvent(buildButtonClickEvent(click, pathname));
13318
+ this.mpInstance._Events.logEvent(asLink ? buildLinkClickEvent(click, pathname) : buildButtonClickEvent(click, pathname));
12593
13319
  // Only a click waiting for the batch interval is sent early: a held
12594
13320
  // one goes out when released, and with batching off it already has.
12595
13321
  // A checkbox or radio waits for the batch, since choosing an option
@@ -12651,11 +13377,29 @@ var mParticle = (function () {
12651
13377
  pathname: pathname
12652
13378
  }), pathname);
12653
13379
  };
13380
+ ButtonTracker.prototype.pickPayment = function (t, pathname, names, family) {
13381
+ if (this.isKitSuppressed()) {
13382
+ return;
13383
+ }
13384
+ this.mpInstance._RoktManager.notifyPaymentPick(Object.freeze({
13385
+ t: t,
13386
+ pathname: pathname,
13387
+ names: names ? Object.freeze(names.slice()) : undefined,
13388
+ family: family
13389
+ }));
13390
+ };
12654
13391
  // Events follow the SDK's opt-out like any other event.
12655
13392
  ButtonTracker.prototype.eventsEnabled = function () {
12656
13393
  var store = this.mpInstance._Store;
12657
13394
  return !!store && store.isEnabled;
12658
13395
  };
13396
+ // Read per click, since the launcher options can arrive after the tracker
13397
+ // starts. Only where this init configures a Rokt kit, so Global Privacy
13398
+ // Control alone doesn't stop click logging elsewhere.
13399
+ ButtonTracker.prototype.hasTargetingOptOut = function () {
13400
+ var roktManager = this.mpInstance._RoktManager;
13401
+ return this.hasRoktKit && !!roktManager && roktManager.hasTargetingOptOut();
13402
+ };
12659
13403
  // The Rokt manager owns the kit's privacy gates: flag, opt-out, launcher
12660
13404
  // options and Global Privacy Control.
12661
13405
  ButtonTracker.prototype.isKitSuppressed = function () {
@@ -13600,7 +14344,8 @@ var mParticle = (function () {
13600
14344
  var getFeatureFlag = mpInstance._Helpers.getFeatureFlag;
13601
14345
  var _e = Constants.FeatureFlags,
13602
14346
  AutoLogPageView = _e.AutoLogPageView,
13603
- AutoButtonTracking = _e.AutoButtonTracking;
14347
+ AutoButtonTracking = _e.AutoButtonTracking,
14348
+ AutoLinkTracking = _e.AutoLinkTracking;
13604
14349
  // Destroy previous batch uploader to prevent leaked timers and event listeners
13605
14350
  if ((_a = mpInstance._APIClient) === null || _a === void 0 ? void 0 : _a.uploader) {
13606
14351
  mpInstance._APIClient.uploader.destroy();
@@ -13618,6 +14363,7 @@ var mParticle = (function () {
13618
14363
  mpInstance._NativeSdkHelpers.initializeSessionAttributes(apiKey);
13619
14364
  // Button tracking never runs as a native passthrough.
13620
14365
  resetButtonTracking();
14366
+ mpInstance._RoktManager.setAutoButtonClicksForwarded(false);
13621
14367
  } else {
13622
14368
  // Main SDK initialization flow
13623
14369
  // Load any settings/identities/attributes from cookie or localStorage
@@ -13662,6 +14408,20 @@ var mParticle = (function () {
13662
14408
  // https://go.mparticle.com/work/SQDSDKS-7339
13663
14409
  mpInstance._RoktManager.init(roktConfig_1, createRoktFilteredUser(currentUserMPID), mpInstance.Identity, mpInstance._Store, mpInstance.Logger, roktOptions, mpInstance.captureTiming, mpInstance._ErrorReportingDispatcher, mpInstance._LoggingDispatcher, mpInstance._IntegrationCapture, createRoktFilteredUser);
13664
14410
  }
14411
+ // Checkout intent comes from the shopper's clicks, so it goes to the
14412
+ // Rokt kit only when that kit receives them. Set before
14413
+ // processForwarders: the kit can read it during this init, from its own
14414
+ // init or from a returning shopper's landing page view.
14415
+ var logButtons = !!getFeatureFlag(AutoButtonTracking);
14416
+ var logLinks = !!getFeatureFlag(AutoLinkTracking);
14417
+ // Links feed intent too, so with link tracking on the kit must also
14418
+ // receive Auto Link Click events.
14419
+ var buttonClickBlocker = roktConfig_1 ? getButtonClickBlocker(roktConfig_1, kitBlocker) : undefined;
14420
+ var linkClickBlocker = roktConfig_1 && logLinks ? getButtonClickBlocker(roktConfig_1, kitBlocker, true) : undefined;
14421
+ if (logButtons && (buttonClickBlocker || linkClickBlocker)) {
14422
+ mpInstance.Logger.warning(buttonClickBlocker ? "Checkout intent is off: the ".concat(buttonClickBlocker, " keeps some or all Auto Button Click events from the Rokt kit.") : "Checkout intent is off: the ".concat(linkClickBlocker, " keeps some or all Auto Link Click events from the Rokt kit."));
14423
+ }
14424
+ mpInstance._RoktManager.setAutoButtonClicksForwarded(!!roktConfig_1 && !buttonClickBlocker && !linkClickBlocker);
13665
14425
  mpInstance._Forwarders.processForwarders(config, mpInstance._APIClient.prepareForwardingStats);
13666
14426
  mpInstance._Forwarders.processPixelConfigs(config);
13667
14427
  // Checks if session is created, resumed, or needs to be ended
@@ -13696,14 +14456,14 @@ var mParticle = (function () {
13696
14456
  // so it stops listening. Re-created if the flag is re-enabled later.
13697
14457
  mpInstance._PageViewTracker.teardown();
13698
14458
  }
13699
- // Button clicks are logged for any workspace that opts in. Checkout
13700
- // intent also goes to the Rokt kit when this init configures one.
13701
- if (getFeatureFlag(AutoButtonTracking)) {
14459
+ if (logButtons || logLinks) {
13702
14460
  if (!mpInstance._ButtonTracker) {
13703
14461
  mpInstance._ButtonTracker = new ButtonTracker(mpInstance);
13704
14462
  }
13705
14463
  mpInstance._ButtonTracker.init({
13706
- hasRoktKit: !!roktConfig_1
14464
+ hasRoktKit: !!roktConfig_1,
14465
+ logButtons: logButtons,
14466
+ logLinks: logLinks
13707
14467
  });
13708
14468
  } else {
13709
14469
  // Also retires a tracker left by an earlier bundle load. Like APV,
@@ -13809,10 +14569,12 @@ var mParticle = (function () {
13809
14569
  // Initialize CookieConsentManager with privacy flags from launcherOptions
13810
14570
  var _a = normalizeRoktLauncherOptions(config === null || config === void 0 ? void 0 : config.launcherOptions),
13811
14571
  noFunctional = _a.noFunctional,
13812
- noTargeting = _a.noTargeting;
14572
+ noTargeting = _a.noTargeting,
14573
+ doNotShareOrSell = _a.doNotShareOrSell;
13813
14574
  mpInstance._CookieConsentManager = new CookieConsentManager({
13814
14575
  noFunctional: noFunctional,
13815
- noTargeting: noTargeting
14576
+ noTargeting: noTargeting,
14577
+ doNotShareOrSell: doNotShareOrSell
13816
14578
  });
13817
14579
  // Check to see if localStorage is available before main configuration runs
13818
14580
  // since we will need this for the current implementation of user persistence
@@ -14308,8 +15070,8 @@ var mParticle = (function () {
14308
15070
  this.configurePixel = function (settings) {
14309
15071
  self.getInstance().configurePixel(settings);
14310
15072
  };
14311
- this._setIntegrationDelay = function (moduleId, _boolean) {
14312
- self.getInstance()._setIntegrationDelay(moduleId, _boolean);
15073
+ this._setIntegrationDelay = function (moduleId, boolean) {
15074
+ self.getInstance()._setIntegrationDelay(moduleId, boolean);
14313
15075
  };
14314
15076
  this._getIntegrationDelays = function () {
14315
15077
  return self.getInstance()._getIntegrationDelays();