@mparticle/web-sdk 3.20.0 → 3.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mparticle.common.js +91 -38
- package/dist/mparticle.esm.js +91 -38
- package/dist/mparticle.js +425 -79
- package/dist/types/src/batchUploader.d.ts +2 -0
- package/dist/types/src/buttonTracking/buttonDescription.d.ts +7 -2
- package/dist/types/src/buttonTracking/buttonTracker.d.ts +6 -0
- package/dist/types/src/buttonTracking/checkoutIntent.d.ts +7 -1
- package/dist/types/src/buttonTracking/controls.d.ts +3 -0
- package/dist/types/src/roktManager.d.ts +4 -1
- package/package.json +1 -1
- package/src/batchUploader.ts +33 -1
- package/src/buttonTracking/buttonClickEvent.ts +6 -0
- package/src/buttonTracking/buttonDescription.ts +92 -8
- package/src/buttonTracking/buttonTracker.ts +92 -8
- package/src/buttonTracking/checkoutIntent.ts +199 -66
- package/src/buttonTracking/controls.ts +150 -32
- package/src/pageViewTracker.ts +7 -1
- package/src/roktManager.ts +16 -1
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.
|
|
207
|
+
var version = "3.22.0";
|
|
208
208
|
|
|
209
209
|
var Constants = {
|
|
210
210
|
sdkVersion: version,
|
|
@@ -3014,6 +3014,11 @@ var mParticle = (function () {
|
|
|
3014
3014
|
this.exitHandler = null;
|
|
3015
3015
|
this.visibilityChangeHandler = null;
|
|
3016
3016
|
this.destroyed = false;
|
|
3017
|
+
// The order this page's uploads first picked each batch up in, by request
|
|
3018
|
+
// id. Batches enter uploads in creation order, so this keeps that order for
|
|
3019
|
+
// batches that share a millisecond.
|
|
3020
|
+
this.batchOrder = new Map();
|
|
3021
|
+
this.nextBatchOrder = 0;
|
|
3017
3022
|
this.mpInstance = mpInstance;
|
|
3018
3023
|
this.uploadIntervalMillis = uploadInterval;
|
|
3019
3024
|
this.batchingEnabled = uploadInterval >= BatchUploader.MINIMUM_INTERVAL_MILLIS;
|
|
@@ -3299,8 +3304,9 @@ var mParticle = (function () {
|
|
|
3299
3304
|
useBeacon = false;
|
|
3300
3305
|
}
|
|
3301
3306
|
return __awaiter(this, void 0, void 0, function () {
|
|
3302
|
-
var currentUser, currentEvents, newBatches, batchesToUpload, batchesThatDidNotUpload;
|
|
3307
|
+
var currentUser, currentEvents, newBatches, batchesToUpload, batchesThatDidNotUpload, retriedIds;
|
|
3303
3308
|
var _b, _c, _d;
|
|
3309
|
+
var _this_1 = this;
|
|
3304
3310
|
return __generator(this, function (_e) {
|
|
3305
3311
|
switch (_e.label) {
|
|
3306
3312
|
case 0:
|
|
@@ -3326,9 +3332,22 @@ var mParticle = (function () {
|
|
|
3326
3332
|
}
|
|
3327
3333
|
batchesToUpload = this.batchesQueuedForProcessing;
|
|
3328
3334
|
this.batchesQueuedForProcessing = [];
|
|
3335
|
+
batchesToUpload.forEach(function (batch) {
|
|
3336
|
+
if (!_this_1.batchOrder.has(batch.source_request_id)) {
|
|
3337
|
+
_this_1.batchOrder.set(batch.source_request_id, _this_1.nextBatchOrder++);
|
|
3338
|
+
}
|
|
3339
|
+
});
|
|
3329
3340
|
return [4 /*yield*/, this.uploadBatches(batchesToUpload, useBeacon)];
|
|
3330
3341
|
case 1:
|
|
3331
3342
|
batchesThatDidNotUpload = _e.sent();
|
|
3343
|
+
retriedIds = (batchesThatDidNotUpload || []).map(function (batch) {
|
|
3344
|
+
return batch.source_request_id;
|
|
3345
|
+
});
|
|
3346
|
+
batchesToUpload.forEach(function (batch) {
|
|
3347
|
+
if (retriedIds.indexOf(batch.source_request_id) === -1) {
|
|
3348
|
+
_this_1.batchOrder["delete"](batch.source_request_id);
|
|
3349
|
+
}
|
|
3350
|
+
});
|
|
3332
3351
|
// Batches that do not successfully upload are added back to the process queue
|
|
3333
3352
|
// in the order they were created so that we can attempt re-transmission in
|
|
3334
3353
|
// the same sequence. This is to prevent any potential data corruption.
|
|
@@ -3355,7 +3374,19 @@ var mParticle = (function () {
|
|
|
3355
3374
|
};
|
|
3356
3375
|
|
|
3357
3376
|
BatchUploader.prototype.storeBatchesQueuedForProcessing = function () {
|
|
3358
|
-
var
|
|
3377
|
+
var _this_1 = this;
|
|
3378
|
+
var _a;
|
|
3379
|
+
// Another upload may have stored its failed batches while this one was
|
|
3380
|
+
// in flight. Merge them in creation order, whichever upload finished
|
|
3381
|
+
// first, so they are not overwritten and quota trimming drops the oldest.
|
|
3382
|
+
var orderOf = function orderOf(batch) {
|
|
3383
|
+
var _a;
|
|
3384
|
+
return (_a = _this_1.batchOrder.get(batch.source_request_id)) !== null && _a !== void 0 ? _a : -1;
|
|
3385
|
+
};
|
|
3386
|
+
var batches = __spreadArray(__spreadArray([], this.batchesQueuedForProcessing, true), (_a = this.batchVault.retrieve()) !== null && _a !== void 0 ? _a : [], true).sort(function (a, b) {
|
|
3387
|
+
var _a, _b;
|
|
3388
|
+
return orderOf(a) - orderOf(b) || ((_a = a.timestamp_unixtime_ms) !== null && _a !== void 0 ? _a : 0) - ((_b = b.timestamp_unixtime_ms) !== null && _b !== void 0 ? _b : 0);
|
|
3389
|
+
});
|
|
3359
3390
|
// Nothing to persist: reset offline storage to an empty state.
|
|
3360
3391
|
if (isEmpty(batches)) {
|
|
3361
3392
|
this.batchVault.store([]);
|
|
@@ -6869,11 +6900,12 @@ var mParticle = (function () {
|
|
|
6869
6900
|
// Pagination
|
|
6870
6901
|
'page', 'limit', 'offset', 'cursor', 'per_page',
|
|
6871
6902
|
// Site search term, as the common platforms spell it. Matching is
|
|
6872
|
-
// case-insensitive
|
|
6873
|
-
|
|
6903
|
+
// case-insensitive. `s` is WordPress and WooCommerce search; other
|
|
6904
|
+
// single-letter names are too generic. `oq` is a storefront search term.
|
|
6905
|
+
'q', 's', 'oq', 'search', 'query', 'keyword', 'keywords', 'term', 'text', 'searchTerm', 'searchText', 'search_query', 'search_text', 'Ntt',
|
|
6874
6906
|
// Product and variant identifiers. `cid` is not a category: it is a
|
|
6875
6907
|
// customer id on many sites.
|
|
6876
|
-
'pid', 'productId', 'sku', 'skuId', 'variant',
|
|
6908
|
+
'pid', 'productId', 'sku', 'skuId', 'variant', 'color', 'colour', 'size',
|
|
6877
6909
|
// Category
|
|
6878
6910
|
'category',
|
|
6879
6911
|
// Referral
|
|
@@ -10795,6 +10827,15 @@ var mParticle = (function () {
|
|
|
10795
10827
|
var nav = typeof navigator === 'undefined' ? undefined : navigator;
|
|
10796
10828
|
return !!nav && nav.globalPrivacyControl === true;
|
|
10797
10829
|
};
|
|
10830
|
+
// True when the kit can wait for the shopper's confirm click: signals are not
|
|
10831
|
+
// suppressed and the browser exposes the composed path the detector reads.
|
|
10832
|
+
// Decided from config alone, so tracker setup and teardown never change it.
|
|
10833
|
+
RoktManager.prototype.isCheckoutIntentLive = function () {
|
|
10834
|
+
if (this.isCheckoutIntentSuppressed()) {
|
|
10835
|
+
return false;
|
|
10836
|
+
}
|
|
10837
|
+
return typeof Event !== 'undefined' && isFunction(Event.prototype.composedPath);
|
|
10838
|
+
};
|
|
10798
10839
|
// Reads `this.kit` at call time; before a kit attaches the signal is buffered
|
|
10799
10840
|
// with the path it was raised on.
|
|
10800
10841
|
RoktManager.prototype.notifyCheckoutIntent = function (signal, pathname) {
|
|
@@ -11484,7 +11525,10 @@ var mParticle = (function () {
|
|
|
11484
11525
|
element_name: click.elementName,
|
|
11485
11526
|
test_id: click.testId,
|
|
11486
11527
|
controls: click.controls,
|
|
11487
|
-
form: click.form
|
|
11528
|
+
form: click.form,
|
|
11529
|
+
link_type: click.linkType,
|
|
11530
|
+
link_domain: click.linkDomain,
|
|
11531
|
+
file_extension: click.fileExtension
|
|
11488
11532
|
};
|
|
11489
11533
|
for (var _i = 0, _b = Object.keys(optional); _i < _b.length; _i++) {
|
|
11490
11534
|
var key = _b[_i];
|
|
@@ -11492,6 +11536,9 @@ var mParticle = (function () {
|
|
|
11492
11536
|
data[key] = optional[key];
|
|
11493
11537
|
}
|
|
11494
11538
|
}
|
|
11539
|
+
if (click.checked !== undefined) {
|
|
11540
|
+
data.checked = click.checked;
|
|
11541
|
+
}
|
|
11495
11542
|
return {
|
|
11496
11543
|
messageType: MessageType$1.PageEvent,
|
|
11497
11544
|
name: BUTTON_CLICK_EVENT_NAME,
|
|
@@ -11516,43 +11563,109 @@ var mParticle = (function () {
|
|
|
11516
11563
|
}
|
|
11517
11564
|
return false;
|
|
11518
11565
|
}
|
|
11566
|
+
var ROKT_SELECTOR = '[data-core-type], #rokt-controller-frame';
|
|
11567
|
+
// A Rokt frame or script is served from a /wsdk/ path.
|
|
11568
|
+
function hasRoktSrc(element) {
|
|
11569
|
+
var src = element.getAttribute('src');
|
|
11570
|
+
if (!src) {
|
|
11571
|
+
return false;
|
|
11572
|
+
}
|
|
11573
|
+
try {
|
|
11574
|
+
return new URL(src, 'https://example.test').pathname.indexOf('/wsdk/') !== -1;
|
|
11575
|
+
} catch (e) {
|
|
11576
|
+
// An unparseable src is not a Rokt element.
|
|
11577
|
+
return false;
|
|
11578
|
+
}
|
|
11579
|
+
}
|
|
11519
11580
|
// Rokt's own placements and frames never count as partner checkout controls.
|
|
11520
11581
|
function isRoktElement(element) {
|
|
11521
|
-
|
|
11522
|
-
|
|
11582
|
+
return hasComposedAncestor(element, ROKT_SELECTOR) || hasRoktSrc(element);
|
|
11583
|
+
}
|
|
11584
|
+
// Wallet and quick-pay buttons as their vendors render them: official custom
|
|
11585
|
+
// elements and documented classes, ids and attributes.
|
|
11586
|
+
var WALLET_SIGNATURE = ['apple-pay-button', '.apple-pay-button', '.gpay-button', '.gpay-card-info-container', '#gpay-button-online-api-id', 'paypal-button', 'venmo-button', 'paypal-pay-later-button', 'paypal-credit-button', 'shop-pay-button', 'shop-pay-wallet-button', 'shopify-accelerated-checkout', 'shopify-accelerated-checkout-cart', 'shopify-apple-pay-button', 'shopify-google-pay-button', 'shopify-paypal-button', 'shopify-amazon-pay-button', '.amazonpay-button-focusable', '#AmazonPayButton', '[data-amazon-pay-button]', '[data-afterpay-entry-point]'].join(', ');
|
|
11587
|
+
// PayPal's card button opens a card form, so it is a step rather than a wallet.
|
|
11588
|
+
var CARD_FORM_BUTTON = 'paypal-basic-card-button';
|
|
11589
|
+
var VENDOR_BUTTON = WALLET_SIGNATURE + ', ' + CARD_FORM_BUTTON;
|
|
11590
|
+
// Apple's set-up and top-up buttons manage a wallet rather than pay with it.
|
|
11591
|
+
var NOT_A_PAYMENT = ['set-up', 'add-money', 'reload', 'top-up'].map(function (type) {
|
|
11592
|
+
return 'apple-pay-button[type="' + type + '"]';
|
|
11593
|
+
}).join(', ');
|
|
11594
|
+
// What a vendor's own button markup says about a control. Only the control or
|
|
11595
|
+
// the shadow host that owns it counts, so a merchant wrapper reusing a vendor
|
|
11596
|
+
// class never turns its children into wallet buttons.
|
|
11597
|
+
function vendorButtonKind(control) {
|
|
11598
|
+
var host = control.matches(VENDOR_BUTTON) ? control : null;
|
|
11599
|
+
if (!host) {
|
|
11600
|
+
var root = isFunction(control.getRootNode) ? control.getRootNode() : null;
|
|
11601
|
+
host = root && root.host && root.host.matches(VENDOR_BUTTON) ? root.host : null;
|
|
11602
|
+
}
|
|
11603
|
+
if (!host) {
|
|
11604
|
+
return undefined;
|
|
11523
11605
|
}
|
|
11524
|
-
|
|
11525
|
-
|
|
11526
|
-
try {
|
|
11527
|
-
if (new URL(src, 'https://example.test').pathname.indexOf('/wsdk/') !== -1) {
|
|
11528
|
-
return true;
|
|
11529
|
-
}
|
|
11530
|
-
} catch (e) {
|
|
11531
|
-
// An unparseable src is not a Rokt element.
|
|
11532
|
-
}
|
|
11606
|
+
if (host.matches(NOT_A_PAYMENT)) {
|
|
11607
|
+
return 'none';
|
|
11533
11608
|
}
|
|
11534
|
-
return
|
|
11609
|
+
return host.matches(CARD_FORM_BUTTON) ? 'checkout_step' : 'wallet';
|
|
11535
11610
|
}
|
|
11536
11611
|
function isWalletElement(element) {
|
|
11537
|
-
return element
|
|
11612
|
+
return vendorButtonKind(element) === 'wallet';
|
|
11613
|
+
}
|
|
11614
|
+
// Pickers and toggles choose a payment method or open a panel; they never pay.
|
|
11615
|
+
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]';
|
|
11616
|
+
var PICKER_GROUP = '[role="radiogroup"], [role="tablist"], [role="listbox"]';
|
|
11617
|
+
// Pay-later and financing messages link to details rather than pay.
|
|
11618
|
+
var MESSAGING = 'klarna-placement, afterpay-placement, square-placement, .affirm-as-low-as, .affirm-site-modal, ' + '.affirm-product-modal, [data-pp-message]';
|
|
11619
|
+
// A control that is still logged as a click but never counts as checkout intent.
|
|
11620
|
+
function isQuietIntentControl(control, isVendorWallet) {
|
|
11621
|
+
if (control.matches(PICKER)) {
|
|
11622
|
+
return true;
|
|
11623
|
+
}
|
|
11624
|
+
if (hasComposedAncestor(control, PICKER_GROUP)) {
|
|
11625
|
+
return true;
|
|
11626
|
+
}
|
|
11627
|
+
if (!isVendorWallet && (control.hasAttribute('aria-expanded') || control.hasAttribute('aria-pressed'))) {
|
|
11628
|
+
return true;
|
|
11629
|
+
}
|
|
11630
|
+
return hasComposedAncestor(control, MESSAGING);
|
|
11538
11631
|
}
|
|
11632
|
+
// A checkbox or radio is a control of its own only when no button or link
|
|
11633
|
+
// around it takes the click, so a tappable row keeps its own name.
|
|
11634
|
+
var OPTION_INPUT = 'input[type="checkbox"], input[type="radio"]';
|
|
11539
11635
|
// Walks at most 12 entries of an event's composed path for the nearest control.
|
|
11540
|
-
// Rokt elements
|
|
11636
|
+
// One composed walk from the target rules out Rokt elements and anything under
|
|
11637
|
+
// data-mp-no-track; disabled controls are skipped.
|
|
11541
11638
|
function findControl(path) {
|
|
11639
|
+
var entries = path.slice(0, 12);
|
|
11640
|
+
var first;
|
|
11641
|
+
for (var _i = 0, entries_1 = entries; _i < entries_1.length; _i++) {
|
|
11642
|
+
var target = entries_1[_i];
|
|
11643
|
+
if (target instanceof Element) {
|
|
11644
|
+
first = target;
|
|
11645
|
+
break;
|
|
11646
|
+
}
|
|
11647
|
+
}
|
|
11648
|
+
if (!first || hasComposedAncestor(first, ROKT_SELECTOR + ', [data-mp-no-track]')) {
|
|
11649
|
+
return undefined;
|
|
11650
|
+
}
|
|
11542
11651
|
var control;
|
|
11543
|
-
|
|
11544
|
-
|
|
11652
|
+
var option;
|
|
11653
|
+
for (var _a = 0, entries_2 = entries; _a < entries_2.length; _a++) {
|
|
11654
|
+
var target = entries_2[_a];
|
|
11545
11655
|
if (!(target instanceof Element)) {
|
|
11546
11656
|
continue;
|
|
11547
11657
|
}
|
|
11548
|
-
if (
|
|
11658
|
+
if (hasRoktSrc(target)) {
|
|
11549
11659
|
return undefined;
|
|
11550
11660
|
}
|
|
11551
|
-
if (!control &&
|
|
11661
|
+
if (!control && target.matches(CONTROL_SELECTOR + ', a[href], ' + VENDOR_BUTTON)) {
|
|
11552
11662
|
control = target;
|
|
11663
|
+
} else if (!control && !option && target.matches(OPTION_INPUT)) {
|
|
11664
|
+
option = target;
|
|
11553
11665
|
}
|
|
11554
11666
|
}
|
|
11555
|
-
|
|
11667
|
+
control = control || option;
|
|
11668
|
+
if (!control || control.matches(':disabled, [aria-disabled="true"]')) {
|
|
11556
11669
|
return undefined;
|
|
11557
11670
|
}
|
|
11558
11671
|
return control;
|
|
@@ -11563,6 +11676,11 @@ var mParticle = (function () {
|
|
|
11563
11676
|
function isUnsafeTextSource(element) {
|
|
11564
11677
|
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);
|
|
11565
11678
|
}
|
|
11679
|
+
// A label around a checkbox or radio holds no typed text, so only the other
|
|
11680
|
+
// fields rule its text out.
|
|
11681
|
+
function isUnsafeLabelSource(label) {
|
|
11682
|
+
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);
|
|
11683
|
+
}
|
|
11566
11684
|
|
|
11567
11685
|
// Masks a button label before it is logged. Button text can carry personal data,
|
|
11568
11686
|
// so this is the privacy boundary for every label and aria-label.
|
|
@@ -11593,12 +11711,16 @@ var mParticle = (function () {
|
|
|
11593
11711
|
return masked || undefined;
|
|
11594
11712
|
}
|
|
11595
11713
|
|
|
11714
|
+
var DOWNLOAD_EXTENSIONS = /\.(pdf|zip|csv|txt|ics|docx?|xlsx?|pptx?|dmg|exe|apk|mp3|mp4)$/i;
|
|
11596
11715
|
function elementKind(control) {
|
|
11597
11716
|
if (isWalletElement(control)) {
|
|
11598
11717
|
return 'wallet';
|
|
11599
11718
|
}
|
|
11600
11719
|
var tag = control.tagName.toLowerCase();
|
|
11601
|
-
|
|
11720
|
+
if (tag === 'button' || tag === 'input') {
|
|
11721
|
+
return tag;
|
|
11722
|
+
}
|
|
11723
|
+
return control.matches('a[href]:not([role="button"])') ? 'link' : 'role';
|
|
11602
11724
|
}
|
|
11603
11725
|
function textOf(element) {
|
|
11604
11726
|
return element && !isUnsafeTextSource(element) && element.textContent ? element.textContent.trim() : '';
|
|
@@ -11607,9 +11729,10 @@ var mParticle = (function () {
|
|
|
11607
11729
|
return value && value.trim() ? value : undefined;
|
|
11608
11730
|
}
|
|
11609
11731
|
// The button's name as a screen reader would read it, simplified: the first of
|
|
11610
|
-
// aria-labelledby, aria-label, own text (or an inner image's alt), title,
|
|
11611
|
-
// inputs their value
|
|
11612
|
-
//
|
|
11732
|
+
// aria-labelledby, aria-label, own text (or an inner image's alt), title, for
|
|
11733
|
+
// inputs their value, and for checkboxes and radios their label's text. The text
|
|
11734
|
+
// sources are skipped for controls that hold form fields or more than four
|
|
11735
|
+
// children, so a typed value is never read.
|
|
11613
11736
|
function accessibleName(control) {
|
|
11614
11737
|
var labelledBy = control.getAttribute('aria-labelledby');
|
|
11615
11738
|
if (labelledBy) {
|
|
@@ -11660,6 +11783,16 @@ var mParticle = (function () {
|
|
|
11660
11783
|
source: 'value'
|
|
11661
11784
|
};
|
|
11662
11785
|
}
|
|
11786
|
+
} else if (type === 'checkbox' || type === 'radio') {
|
|
11787
|
+
// The label the browser ties to the box, never the box's value.
|
|
11788
|
+
var label = control.labels ? control.labels[0] : undefined;
|
|
11789
|
+
var text = label && !isUnsafeLabelSource(label) && label.textContent ? label.textContent.trim() : '';
|
|
11790
|
+
if (text) {
|
|
11791
|
+
return {
|
|
11792
|
+
raw: text,
|
|
11793
|
+
source: 'label'
|
|
11794
|
+
};
|
|
11795
|
+
}
|
|
11663
11796
|
}
|
|
11664
11797
|
} else if (readable) {
|
|
11665
11798
|
var text = textOf(control);
|
|
@@ -11772,11 +11905,56 @@ var mParticle = (function () {
|
|
|
11772
11905
|
function controlType(control, element) {
|
|
11773
11906
|
return element === 'button' || element === 'input' ? control.type : 'button';
|
|
11774
11907
|
}
|
|
11775
|
-
//
|
|
11776
|
-
//
|
|
11908
|
+
// Reads the link's own resolved URL parts, never its full href: only the hostname of
|
|
11909
|
+
// a link that leaves the site and the extension of a file it downloads travel. Navigation
|
|
11910
|
+
// within the site is left to page views, and mailto:, tel: and other schemes are skipped.
|
|
11911
|
+
function describeLink(link) {
|
|
11912
|
+
var href = (link.getAttribute('href') || '').trim();
|
|
11913
|
+
if (!href || href.charAt(0) === '#') {
|
|
11914
|
+
return {
|
|
11915
|
+
type: 'same_page'
|
|
11916
|
+
};
|
|
11917
|
+
}
|
|
11918
|
+
if (!(link instanceof HTMLAnchorElement)) {
|
|
11919
|
+
return undefined;
|
|
11920
|
+
}
|
|
11921
|
+
var protocol = link.protocol.toLowerCase();
|
|
11922
|
+
if (protocol === 'javascript:') {
|
|
11923
|
+
return {
|
|
11924
|
+
type: 'same_page'
|
|
11925
|
+
};
|
|
11926
|
+
}
|
|
11927
|
+
if (protocol !== 'http:' && protocol !== 'https:') {
|
|
11928
|
+
return undefined;
|
|
11929
|
+
}
|
|
11930
|
+
var domain = link.hostname !== window.location.hostname ? link.hostname.toLowerCase() : undefined;
|
|
11931
|
+
var extension = DOWNLOAD_EXTENSIONS.exec(link.pathname);
|
|
11932
|
+
if (link.hasAttribute('download') || extension) {
|
|
11933
|
+
return {
|
|
11934
|
+
type: 'download',
|
|
11935
|
+
domain: domain,
|
|
11936
|
+
fileExtension: extension ? extension[1].toLowerCase() : undefined
|
|
11937
|
+
};
|
|
11938
|
+
}
|
|
11939
|
+
if (domain) {
|
|
11940
|
+
return {
|
|
11941
|
+
type: 'outbound',
|
|
11942
|
+
domain: domain
|
|
11943
|
+
};
|
|
11944
|
+
}
|
|
11945
|
+
return link.pathname === window.location.pathname && link.search === window.location.search ? {
|
|
11946
|
+
type: 'same_page'
|
|
11947
|
+
} : undefined;
|
|
11948
|
+
}
|
|
11949
|
+
// Describes a clicked control for auto button tracking. A link counts only when page
|
|
11950
|
+
// views can't see the click, and carries its link type.
|
|
11777
11951
|
function describeButtonClick(control) {
|
|
11952
|
+
var link;
|
|
11778
11953
|
if (control.matches('a[href]:not([role="button"])')) {
|
|
11779
|
-
|
|
11954
|
+
link = describeLink(control);
|
|
11955
|
+
if (!link) {
|
|
11956
|
+
return undefined;
|
|
11957
|
+
}
|
|
11780
11958
|
}
|
|
11781
11959
|
var element = elementKind(control);
|
|
11782
11960
|
var name = accessibleName(control);
|
|
@@ -11794,34 +11972,83 @@ var mParticle = (function () {
|
|
|
11794
11972
|
controls: controlsIdentifier(control),
|
|
11795
11973
|
form: formIdentifier(control),
|
|
11796
11974
|
element: element,
|
|
11797
|
-
type: controlType(control, element)
|
|
11975
|
+
type: controlType(control, element),
|
|
11976
|
+
linkType: link ? link.type : undefined,
|
|
11977
|
+
linkDomain: link ? link.domain : undefined,
|
|
11978
|
+
fileExtension: link ? link.fileExtension : undefined,
|
|
11979
|
+
// The box as the click left it: the browser toggles it before the click
|
|
11980
|
+
// reaches listeners, and puts it back if the click is cancelled.
|
|
11981
|
+
checked: control instanceof HTMLInputElement && (control.type === 'checkbox' || control.type === 'radio') ? control.checked : undefined
|
|
11798
11982
|
};
|
|
11799
11983
|
}
|
|
11800
11984
|
|
|
11801
11985
|
var checkoutPath = /(?:^|\/)(?:checkouts?|payment|order|kasse|paiement|commander?|pago)(?:\/|$)/i;
|
|
11802
|
-
|
|
11803
|
-
|
|
11986
|
+
// English look-alikes are whole words; the other languages stay substring
|
|
11987
|
+
// matches, since several of them do not separate words with spaces.
|
|
11988
|
+
var englishExclusions = new RegExp('\\b(?:coupon|promo(?: code)?|newsletter|sign in|sign up|log in|login|add to (?:cart|bag|basket)|' + 'gift card balance|(?:add|apply|redeem|check|enter|use) (?:a |an |your )?gift card|pay later|' + 'interest free|as low as|learn more|prequalify|apply (?:now|for)|payment plan|available|accepted|' + 'we accept|set up|add money|reload|top up|add to (?:apple|google) wallet|connect|link your|unlink|' + 'remove|delete|edit|change|manage|use a different|switch)\\b|^pay in$|\\bpay in (?:installments|instalments)\\b');
|
|
11989
|
+
var otherExclusions = new RegExp(['gutschein|anmelden|warenkorb|bon cadeau|connexion|panier|cup\u00F3n|iniciar sesi\u00F3n|carrito', 'carrello|codice sconto|accedi|buono regalo|winkelwagen|kortingscode|inloggen|cadeaubon|achteraf betalen', '\u30AB\u30FC\u30C8|\u30AF\u30FC\u30DD\u30F3|\u30ED\u30B0\u30A4\u30F3|\u4F1A\u54E1\u767B\u9332|\u30AE\u30D5\u30C8|\u5F8C\u6255\u3044|\u30E1\u30EB\u30DE\u30AC'].join('|'));
|
|
11990
|
+
var englishCommits = '(?:(?:agree|accept|review) and )?(?:place|submit|complete|confirm|finali[sz]e)' + '(?!(?: [^ ]+){0,2} (?:another|new|next)(?: |$))(?! (?:[^ ]+ )?gift card)' + '(?: [^ ]+){0,2} (?:order|purchase|booking|reservation|payment|checkout|transaction)' + '(?: now| securely| and pay| (?:with|using) [^ ]+(?: [^ ]+)?)?(?: (?:total|subtotal|items?|tickets?))?' + '|pay(?: securely)?(?: now)?|(?:confirm|agree|accept|submit|order|book) and pay' + '|pay and place (?:my |your )?order|order with obligation to pay';
|
|
11991
|
+
var otherCommits = ['(?:jetzt )?(?:zahlungs|kosten)pflichtig (?:bestellen|buchen)|(?:jetzt )?bezahlen|kauf abschlie\u00DFen|bestellung abschlie\u00DFen', 'commander et payer|payer(?: maintenant)?|(?:confirmer|finaliser) (?:ma |la )?commande', "valider et payer|confirmer et payer|commande avec obligation de paiement|finaliser l'achat", '(?:realizar|confirmar|completar) (?:el |mi |la )?(?:pedido|compra|reserva)|finalizar (?:el |mi )?pedido|pagar(?: ahora)?', 'pedido con obligaci\u00F3n de pago', "(?:effettua|conferma|concludi|completa) (?:l'|il )?(?:ordine|acquisto)|ordine con obbligo di pagamento", 'paga(?: ora| adesso)?|ordina e paga', '(?:nu )?betalen|bestellen en betalen|bestelling plaatsen|plaats bestelling|bestelling afronden', 'bestelling met betalingsverplichting', '(?:\u3053\u306E\u5185\u5BB9\u3067|\u4E0A\u8A18\u306E?\u5185\u5BB9\u3067)?\u3054?(?:\u6CE8\u6587|\u8CFC\u5165|\u4E88\u7D04|\u6C7A\u6E08|\u304A?\u652F\u6255\u3044?)\u3092?(?:\u78BA\u5B9A|\u5B8C\u4E86)(?:\u3059\u308B)?', '(?:\u3053\u306E\u5185\u5BB9\u3067|\u4E0A\u8A18\u306E?\u5185\u5BB9\u3067)\u3054?(?:\u6CE8\u6587|\u8CFC\u5165|\u4E88\u7D04)\u3059\u308B|\u3054?\u6CE8\u6587\u3059\u308B|\u6C7A\u6E08\u3059\u308B|(?:\u4ECA\u3059\u3050|\u3092)?\u652F\u6255\u3046'].join('|');
|
|
11992
|
+
var commits = new RegExp('^(?:' + englishCommits + '|' + otherCommits + ')$');
|
|
11993
|
+
// A plain link is navigation, so in English only an order or purchase it
|
|
11994
|
+
// places, submits or completes counts as the confirm click.
|
|
11995
|
+
var linkCommits = new RegExp('^(?:(?:place|submit|complete)(?!(?: [^ ]+){0,2} (?:another|new|next)(?: |$))(?: [^ ]+){0,2} (?:order|purchase)|' + otherCommits + ')$');
|
|
11804
11996
|
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)$');
|
|
11805
|
-
var steps = new RegExp('^(?:' + ['
|
|
11806
|
-
var
|
|
11807
|
-
|
|
11808
|
-
|
|
11997
|
+
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('|') + ')$');
|
|
11998
|
+
var WALLET_NAMES = 'apple pay|google pay|paypal|venmo|amazon pay|shop pay|klarna|afterpay|clearpay|cash app(?: pay)?|affirm|sezzle|samsung pay';
|
|
11999
|
+
var WALLET_VERBS = 'pay|buy|purchase|order|book|donate|subscribe|check ?out|checkout|express checkout|continue|proceed|' + 'complete purchase|place (?:my |your )?order';
|
|
12000
|
+
// "Pay with Klarna", "Buy with GPay", "Continue to PayPal".
|
|
12001
|
+
var walletPhrases = new RegExp('^(?:(?:' + WALLET_VERBS + ')(?: now| securely)? (?:with|using|via|through|by) (?:' + WALLET_NAMES + ')(?: express(?: checkout)?| checkout)?|(?:continue|proceed|go) (?:to|with) (?:' + WALLET_NAMES + '))$');
|
|
12002
|
+
// A wallet's own name on a button: "PayPal", "Klarna checkout", "Apple Pay logo".
|
|
12003
|
+
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))*)?$');
|
|
12004
|
+
// Exact HTTPS hosts of hosted wallet button frames.
|
|
12005
|
+
var walletFrameHosts = new Set(['pay.google.com', 'applepay.cdn-apple.com', 'payments.amazon.com', 'pay.shopify.com']);
|
|
12006
|
+
// PayPal serves its buttons and its card fields and pay-later messages from
|
|
12007
|
+
// the same hosts; only the button frames are wallet taps.
|
|
12008
|
+
var paypalFrameHosts = new Set(['www.paypal.com', 'www.sandbox.paypal.com']);
|
|
12009
|
+
var PAYPAL_BUTTON_FRAME = /^\/smart\/(?:buttons|menu)(?:\/|$)/;
|
|
12010
|
+
// Stripe's quick-pay button frames; its other frames are card and form fields.
|
|
12011
|
+
var STRIPE_WALLET_FRAME = /\/elements-inner-(?:payment-request|express-checkout)-/;
|
|
12012
|
+
// Hosted card-entry and fingerprint frames: focus there is typing card details.
|
|
12013
|
+
var CARD_FRAME_DOMAINS = ['adyen.com', 'braintreegateway.com', 'cybersource.com', 'squarecdn.com', 'checkout.com'];
|
|
11809
12014
|
// Keep these classes ASCII-only in source bytes to avoid mojibake parse failures
|
|
11810
12015
|
// when a host page forces non-UTF-8 decoding before JavaScript parse.
|
|
11811
12016
|
var TYPOGRAPHIC_APOSTROPHES = /[\u2019\u2018]/g;
|
|
11812
12017
|
var CURRENCY_AND_NUMBER_CHARS = /[0-9\uFF10-\uFF19$\u20AC\u00A3\u00A5\uFFE5\u20B9\u5186.,\uFF0C]/g;
|
|
12018
|
+
var BRACKETED = /\([^()]*\)|\[[^[\]]*\]|\uFF08[^\uFF08\uFF09]*\uFF09|\uFF3B[^\uFF3B\uFF3D]*\uFF3D|\u3010[^\u3010\u3011]*\u3011/g;
|
|
12019
|
+
// Spaces, punctuation, separators, arrows and symbols, CJK and full-width forms
|
|
12020
|
+
// included, plus emoji halves and variation selectors.
|
|
12021
|
+
var EDGE = "[\\s!-/:-@\\[-`{-~\\u00A1-\\u00BF\\u00D7\\u00F7\\u2000-\\u206F\\u20A0-\\u20CF\\u2190-\\u2BFF" + "\\u3000-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u30FB\\uD800-\\uDFFF\\uFE00-\\uFE0F" + "\\uFF01-\\uFF0F\\uFF1A-\\uFF20\\uFF3B-\\uFF40\\uFF5B-\\uFF65]+";
|
|
12022
|
+
var EDGE_SYMBOLS = new RegExp('^' + EDGE + '|' + EDGE + '$', 'g');
|
|
11813
12023
|
function getIntentRouteClass(pathname) {
|
|
11814
12024
|
return checkoutPath.test(pathname) ? 'checkout_path' : 'other';
|
|
11815
12025
|
}
|
|
12026
|
+
var SEPARATORS = /[-_|\u00B7\u2022]/g;
|
|
12027
|
+
var CURRENCY_CODES = /(?:^| )(?:usd|aud|cad|nzd|gbp|eur|chf|sgd|hkd|jpy|inr|mxn)(?= |$)|\b(?:a|au|ca|c|us|nz|hk|s)\$/g;
|
|
12028
|
+
var WALLET_ALIASES = [[/\bapplepay\b/g, 'apple pay'], [/\bg ?pay\b/g, 'google pay'], [/\bamazonpay\b/g, 'amazon pay'], [/\bshoppay\b/g, 'shop pay'], [/\bcashapp\b/g, 'cash app']];
|
|
12029
|
+
// Labels longer than this are sentences: they neither match nor veto intent.
|
|
12030
|
+
var MAX_LABEL_WORDS = 8;
|
|
11816
12031
|
function normalize(text) {
|
|
11817
12032
|
if (!text || text.length > 128) {
|
|
11818
12033
|
return undefined;
|
|
11819
12034
|
}
|
|
11820
|
-
|
|
12035
|
+
var label = text.toLowerCase().replace(TYPOGRAPHIC_APOSTROPHES, "'").replace(/[&+]/g, ' and ').replace(SEPARATORS, ' ').replace(CURRENCY_CODES, ' ').replace(CURRENCY_AND_NUMBER_CHARS, '').replace(/\s+/g, ' ').trim();
|
|
12036
|
+
WALLET_ALIASES.forEach(function (_a) {
|
|
12037
|
+
var alias = _a[0],
|
|
12038
|
+
name = _a[1];
|
|
12039
|
+
label = label.replace(alias, name);
|
|
12040
|
+
});
|
|
12041
|
+
return label.split(' ').length > MAX_LABEL_WORDS ? undefined : label;
|
|
12042
|
+
}
|
|
12043
|
+
// The label without bracketed asides or the separators and symbols left at its
|
|
12044
|
+
// edges, so "place order -" and "place order ( items)" both read "place order".
|
|
12045
|
+
function coreLabel(text) {
|
|
12046
|
+
var core = text.replace(BRACKETED, ' ').replace(/\s+/g, ' ').replace(EDGE_SYMBOLS, '');
|
|
12047
|
+
return core || text.replace(EDGE_SYMBOLS, '');
|
|
11821
12048
|
}
|
|
11822
12049
|
// Labels come from aria-label then visible text. Only submit and button inputs
|
|
11823
|
-
// have their value read, so a typed field value is never touched.
|
|
11824
|
-
//
|
|
12050
|
+
// have their value read, so a typed field value is never touched. A control
|
|
12051
|
+
// holding form fields or more than four children gives only its aria-label.
|
|
11825
12052
|
function labelSources(element) {
|
|
11826
12053
|
if (element instanceof HTMLInputElement) {
|
|
11827
12054
|
var type = element.type;
|
|
@@ -11834,7 +12061,7 @@ var mParticle = (function () {
|
|
|
11834
12061
|
return [element.getAttribute('aria-label'), text];
|
|
11835
12062
|
}
|
|
11836
12063
|
if (element.children.length > 4 || isUnsafeTextSource(element)) {
|
|
11837
|
-
return [];
|
|
12064
|
+
return [element.getAttribute('aria-label')];
|
|
11838
12065
|
}
|
|
11839
12066
|
return [element.getAttribute('aria-label'), element.textContent];
|
|
11840
12067
|
}
|
|
@@ -11843,53 +12070,97 @@ var mParticle = (function () {
|
|
|
11843
12070
|
return !!label;
|
|
11844
12071
|
});
|
|
11845
12072
|
}
|
|
11846
|
-
|
|
11847
|
-
|
|
11848
|
-
|
|
12073
|
+
// "Buy now" and "Purchase" count only on a checkout route, and never on a plain
|
|
12074
|
+
// link, which is navigation.
|
|
12075
|
+
function isCommitLabel(text, isLink, pathname) {
|
|
12076
|
+
if (isLink) {
|
|
12077
|
+
return linkCommits.test(text);
|
|
11849
12078
|
}
|
|
11850
|
-
|
|
11851
|
-
|
|
12079
|
+
return commits.test(text) || routeGatedCommits.test(text) && getIntentRouteClass(pathname) === 'checkout_path';
|
|
12080
|
+
}
|
|
12081
|
+
// Classifies a clicked or submitting control found by findControl, in order:
|
|
12082
|
+
// pickers, toggles and pay-later messages never count; a vendor's own wallet
|
|
12083
|
+
// button counts before any label rule; a confirm label on any source counts
|
|
12084
|
+
// before the look-alike exclusions veto; then wallet phrases and steps.
|
|
12085
|
+
function classifyIntent(control, pathname) {
|
|
12086
|
+
var vendor = vendorButtonKind(control);
|
|
12087
|
+
if (isQuietIntentControl(control, vendor === 'wallet') || vendor === 'none') {
|
|
12088
|
+
return undefined;
|
|
11852
12089
|
}
|
|
11853
|
-
if (
|
|
11854
|
-
return
|
|
12090
|
+
if (vendor) {
|
|
12091
|
+
return {
|
|
12092
|
+
kind: vendor,
|
|
12093
|
+
source: 'vendor'
|
|
12094
|
+
};
|
|
11855
12095
|
}
|
|
11856
|
-
return undefined;
|
|
11857
|
-
}
|
|
11858
|
-
// Classifies a clicked or submitting control found by findControl.
|
|
11859
|
-
function classifyIntentControl(control, pathname) {
|
|
11860
12096
|
var texts = labels(control);
|
|
11861
|
-
|
|
11862
|
-
|
|
12097
|
+
var cores = texts.map(coreLabel);
|
|
12098
|
+
var isLink = control.matches('a[href]:not([role="button"])');
|
|
12099
|
+
if (cores.some(function (text) {
|
|
12100
|
+
return isCommitLabel(text, isLink, pathname);
|
|
11863
12101
|
})) {
|
|
11864
|
-
return
|
|
11865
|
-
|
|
11866
|
-
|
|
11867
|
-
|
|
11868
|
-
button = control.matches(CONTROL_SELECTOR) || isWalletElement(control);
|
|
12102
|
+
return {
|
|
12103
|
+
kind: 'commit',
|
|
12104
|
+
source: 'label'
|
|
12105
|
+
};
|
|
11869
12106
|
}
|
|
11870
|
-
if (
|
|
11871
|
-
return
|
|
12107
|
+
if (texts.concat(cores).some(function (text) {
|
|
12108
|
+
return englishExclusions.test(text) || otherExclusions.test(text);
|
|
12109
|
+
})) {
|
|
12110
|
+
return undefined;
|
|
11872
12111
|
}
|
|
11873
|
-
|
|
11874
|
-
|
|
11875
|
-
var
|
|
11876
|
-
if (
|
|
11877
|
-
return
|
|
12112
|
+
var isButton = !isLink && control.matches(CONTROL_SELECTOR);
|
|
12113
|
+
for (var _i = 0, cores_1 = cores; _i < cores_1.length; _i++) {
|
|
12114
|
+
var text = cores_1[_i];
|
|
12115
|
+
if (isButton && walletPhrases.test(text)) {
|
|
12116
|
+
return {
|
|
12117
|
+
kind: 'wallet',
|
|
12118
|
+
source: 'label'
|
|
12119
|
+
};
|
|
12120
|
+
}
|
|
12121
|
+
if (isButton && walletBrands.test(text)) {
|
|
12122
|
+
return {
|
|
12123
|
+
kind: 'wallet',
|
|
12124
|
+
source: 'brand'
|
|
12125
|
+
};
|
|
12126
|
+
}
|
|
12127
|
+
if (steps.test(text)) {
|
|
12128
|
+
return {
|
|
12129
|
+
kind: 'checkout_step',
|
|
12130
|
+
source: 'label'
|
|
12131
|
+
};
|
|
11878
12132
|
}
|
|
11879
12133
|
}
|
|
11880
12134
|
return undefined;
|
|
11881
12135
|
}
|
|
11882
|
-
//
|
|
12136
|
+
// The host is the domain or one of its subdomains, never a look-alike suffix.
|
|
12137
|
+
function isOnDomain(host, domain) {
|
|
12138
|
+
return host === domain || host.length > domain.length && host.slice(host.length - domain.length - 1) === '.' + domain;
|
|
12139
|
+
}
|
|
12140
|
+
// Focus moving into a visible hosted wallet button counts as a wallet click;
|
|
12141
|
+
// focus into hosted card fields or messages is only a frame.
|
|
11883
12142
|
function classifyIntentFrame(target, win) {
|
|
11884
12143
|
if (win.document.visibilityState !== 'visible' || !(target instanceof HTMLIFrameElement)) {
|
|
11885
12144
|
return undefined;
|
|
11886
12145
|
}
|
|
11887
|
-
if (isRoktElement(target)) {
|
|
12146
|
+
if (isRoktElement(target) || hasComposedAncestor(target, '[data-mp-no-track]')) {
|
|
11888
12147
|
return undefined;
|
|
11889
12148
|
}
|
|
11890
12149
|
try {
|
|
11891
12150
|
var url = new URL(target.src);
|
|
11892
|
-
|
|
12151
|
+
var host_1 = url.protocol === 'https:' ? url.hostname : '';
|
|
12152
|
+
if (paypalFrameHosts.has(host_1)) {
|
|
12153
|
+
return PAYPAL_BUTTON_FRAME.test(url.pathname) ? 'wallet' : 'frame';
|
|
12154
|
+
}
|
|
12155
|
+
if (walletFrameHosts.has(host_1)) {
|
|
12156
|
+
return 'wallet';
|
|
12157
|
+
}
|
|
12158
|
+
if (host_1 === 'js.stripe.com') {
|
|
12159
|
+
return STRIPE_WALLET_FRAME.test(url.pathname) ? 'wallet' : 'frame';
|
|
12160
|
+
}
|
|
12161
|
+
if (CARD_FRAME_DOMAINS.some(function (domain) {
|
|
12162
|
+
return isOnDomain(host_1, domain);
|
|
12163
|
+
})) {
|
|
11893
12164
|
return 'frame';
|
|
11894
12165
|
}
|
|
11895
12166
|
} catch (e) {
|
|
@@ -11907,6 +12178,9 @@ var mParticle = (function () {
|
|
|
11907
12178
|
// A ceiling, not a dedupe: a held Enter key or browser automation fires a
|
|
11908
12179
|
// trusted click per repeat, and each one is forwarded to every kit.
|
|
11909
12180
|
var PAGE_EVENT_LIMIT = 100;
|
|
12181
|
+
// A click uploads at once. Taps that follow it this quickly are held and share
|
|
12182
|
+
// one upload once they stop.
|
|
12183
|
+
var UPLOAD_DEBOUNCE_MS = 100;
|
|
11910
12184
|
var btnWindow = function btnWindow() {
|
|
11911
12185
|
return typeof window === 'undefined' ? null : window;
|
|
11912
12186
|
};
|
|
@@ -11946,7 +12220,7 @@ var mParticle = (function () {
|
|
|
11946
12220
|
passive: true
|
|
11947
12221
|
};
|
|
11948
12222
|
// Logs every button click as an mParticle event, and hands checkout intent to the
|
|
11949
|
-
// Rokt kit as a frozen {kind, t}. Submits and wallet frame focus feed the kit only.
|
|
12223
|
+
// Rokt kit as a frozen {kind, t, source, pathname}. Submits and wallet frame focus feed the kit only.
|
|
11950
12224
|
var ButtonTracker = /** @class */function () {
|
|
11951
12225
|
function ButtonTracker(mpInstance) {
|
|
11952
12226
|
var _this = this;
|
|
@@ -11960,6 +12234,10 @@ var mParticle = (function () {
|
|
|
11960
12234
|
this.pageEvents = 0;
|
|
11961
12235
|
// Clicks read at dispatch and waiting for the next paint.
|
|
11962
12236
|
this.pending = [];
|
|
12237
|
+
this.uploadTimerId = null;
|
|
12238
|
+
this.uploadHeld = false;
|
|
12239
|
+
// Set while the page is left, when the SDK's exit upload sends the queue.
|
|
12240
|
+
this.leaving = false;
|
|
11963
12241
|
this.drain = function () {
|
|
11964
12242
|
var queue = _this.pending;
|
|
11965
12243
|
_this.pending = [];
|
|
@@ -11973,7 +12251,14 @@ var mParticle = (function () {
|
|
|
11973
12251
|
// beforeunload, the first of them.
|
|
11974
12252
|
this.onHide = function (event) {
|
|
11975
12253
|
if (event.type === 'beforeunload' || event.type === 'pagehide' || document.visibilityState === 'hidden') {
|
|
11976
|
-
_this.
|
|
12254
|
+
_this.leaving = true;
|
|
12255
|
+
try {
|
|
12256
|
+
_this.drain();
|
|
12257
|
+
} finally {
|
|
12258
|
+
_this.leaving = false;
|
|
12259
|
+
}
|
|
12260
|
+
// The SDK's exit upload sends the queue, held taps included.
|
|
12261
|
+
_this.cancelUpload();
|
|
11977
12262
|
}
|
|
11978
12263
|
};
|
|
11979
12264
|
this.onPageShow = function (event) {
|
|
@@ -12064,6 +12349,7 @@ var mParticle = (function () {
|
|
|
12064
12349
|
this.lastSent = {};
|
|
12065
12350
|
this.pagePath = null;
|
|
12066
12351
|
this.pageEvents = 0;
|
|
12352
|
+
this.cancelUpload();
|
|
12067
12353
|
};
|
|
12068
12354
|
ButtonTracker.prototype.enqueue = function (delivery) {
|
|
12069
12355
|
this.pending.push(delivery);
|
|
@@ -12089,15 +12375,22 @@ var mParticle = (function () {
|
|
|
12089
12375
|
// button that disables or relabels itself is seen as it was clicked.
|
|
12090
12376
|
// Logging and the kit hand-off wait for the next paint.
|
|
12091
12377
|
var click;
|
|
12378
|
+
var box;
|
|
12092
12379
|
var kind;
|
|
12380
|
+
var source;
|
|
12093
12381
|
var unconfirmedCommit = false;
|
|
12094
12382
|
if (layer === 'click') {
|
|
12095
12383
|
// The composed path is only populated during dispatch.
|
|
12096
12384
|
var control = findControl(isFunction(event.composedPath) ? event.composedPath().slice(0, PATH_LIMIT) : []);
|
|
12097
12385
|
if (control) {
|
|
12098
12386
|
click = logsEvents ? describeButtonClick(control) : undefined;
|
|
12387
|
+
if (click && click.checked !== undefined) {
|
|
12388
|
+
box = control;
|
|
12389
|
+
}
|
|
12099
12390
|
// Checkout intent is only worked out when the kit can use it.
|
|
12100
|
-
|
|
12391
|
+
var intent = signalsKit ? classifyIntent(control, pathname) : undefined;
|
|
12392
|
+
kind = intent ? intent.kind : undefined;
|
|
12393
|
+
source = intent ? intent.source : undefined;
|
|
12101
12394
|
}
|
|
12102
12395
|
} else if (layer === 'submit') {
|
|
12103
12396
|
var submitter = event.submitter;
|
|
@@ -12106,8 +12399,11 @@ var mParticle = (function () {
|
|
|
12106
12399
|
// commit click, which is known once that click is delivered.
|
|
12107
12400
|
if (!submitter) {
|
|
12108
12401
|
kind = 'commit';
|
|
12402
|
+
source = 'submit';
|
|
12109
12403
|
} else if (control) {
|
|
12110
|
-
|
|
12404
|
+
var intent = classifyIntent(control, pathname);
|
|
12405
|
+
kind = intent ? intent.kind : undefined;
|
|
12406
|
+
source = intent ? intent.source : undefined;
|
|
12111
12407
|
}
|
|
12112
12408
|
unconfirmedCommit = !submitter;
|
|
12113
12409
|
}
|
|
@@ -12121,14 +12417,23 @@ var mParticle = (function () {
|
|
|
12121
12417
|
// Read after the blur settles: some browsers have not moved
|
|
12122
12418
|
// focus into the frame yet when the window blurs.
|
|
12123
12419
|
kind = classifyIntentFrame(document.activeElement, window);
|
|
12420
|
+
source = 'frame';
|
|
12124
12421
|
} else if (unconfirmedCommit && t - _this.lastCommitClick > SUBMIT_AFTER_COMMIT_CLICK_MS) {
|
|
12125
12422
|
kind = undefined;
|
|
12126
12423
|
}
|
|
12127
12424
|
if (click && logsEvents) {
|
|
12425
|
+
// The box as this click left it. The browser toggles it
|
|
12426
|
+
// before dispatch and puts it back once a cancelled click
|
|
12427
|
+
// is over, so only a cancelled click changes the reading
|
|
12428
|
+
// taken at dispatch: a checkbox to the other state, a
|
|
12429
|
+
// radio to however it was restored.
|
|
12430
|
+
if (box && event.defaultPrevented) {
|
|
12431
|
+
click.checked = box.type === 'checkbox' ? !click.checked : box.checked;
|
|
12432
|
+
}
|
|
12128
12433
|
_this.logClick(click, pathname);
|
|
12129
12434
|
}
|
|
12130
12435
|
if (kind) {
|
|
12131
|
-
_this.emit(kind, layer, t, pathname);
|
|
12436
|
+
_this.emit(kind, source, layer, t, pathname);
|
|
12132
12437
|
}
|
|
12133
12438
|
} catch (e) {
|
|
12134
12439
|
_this.log('[error] delivery threw: ' + e);
|
|
@@ -12150,9 +12455,48 @@ var mParticle = (function () {
|
|
|
12150
12455
|
this.pageEvents++;
|
|
12151
12456
|
// Restarts an expired session, as public logEvent() and page views do.
|
|
12152
12457
|
this.mpInstance._SessionManager.resetSessionTimer();
|
|
12458
|
+
var queued = this.queuedForUpload();
|
|
12153
12459
|
this.mpInstance._Events.logEvent(buildButtonClickEvent(click, pathname));
|
|
12460
|
+
// Only a click waiting for the batch interval is sent early: a held
|
|
12461
|
+
// one goes out when released, and with batching off it already has.
|
|
12462
|
+
// A checkbox or radio waits for the batch, since choosing an option
|
|
12463
|
+
// doesn't leave the page the way a confirm click can.
|
|
12464
|
+
if (!this.leaving && click.checked === undefined && this.queuedForUpload() > queued) {
|
|
12465
|
+
this.uploadNow();
|
|
12466
|
+
}
|
|
12467
|
+
};
|
|
12468
|
+
// Events waiting in the upload queue for the next batch interval.
|
|
12469
|
+
ButtonTracker.prototype.queuedForUpload = function () {
|
|
12470
|
+
var uploader = this.mpInstance._APIClient.uploader;
|
|
12471
|
+
return uploader ? uploader.eventsQueuedForProcessing.length : 0;
|
|
12472
|
+
};
|
|
12473
|
+
// Uploads a click without waiting for the batch interval. A tap inside the
|
|
12474
|
+
// wait after the last one is held, and each restarts the wait, so a burst
|
|
12475
|
+
// after the first click goes out in one request once the taps stop.
|
|
12476
|
+
ButtonTracker.prototype.uploadNow = function () {
|
|
12477
|
+
var _this = this;
|
|
12478
|
+
if (this.uploadTimerId === null) {
|
|
12479
|
+
this.mpInstance.upload();
|
|
12480
|
+
} else {
|
|
12481
|
+
clearTimeout(this.uploadTimerId);
|
|
12482
|
+
this.uploadHeld = true;
|
|
12483
|
+
}
|
|
12484
|
+
this.uploadTimerId = setTimeout(function () {
|
|
12485
|
+
_this.uploadTimerId = null;
|
|
12486
|
+
if (_this.uploadHeld) {
|
|
12487
|
+
_this.uploadHeld = false;
|
|
12488
|
+
_this.mpInstance.upload();
|
|
12489
|
+
}
|
|
12490
|
+
}, UPLOAD_DEBOUNCE_MS);
|
|
12154
12491
|
};
|
|
12155
|
-
ButtonTracker.prototype.
|
|
12492
|
+
ButtonTracker.prototype.cancelUpload = function () {
|
|
12493
|
+
if (this.uploadTimerId !== null) {
|
|
12494
|
+
clearTimeout(this.uploadTimerId);
|
|
12495
|
+
this.uploadTimerId = null;
|
|
12496
|
+
}
|
|
12497
|
+
this.uploadHeld = false;
|
|
12498
|
+
};
|
|
12499
|
+
ButtonTracker.prototype.emit = function (kind, source, layer, t, pathname) {
|
|
12156
12500
|
if (this.isKitSuppressed()) {
|
|
12157
12501
|
return;
|
|
12158
12502
|
}
|
|
@@ -12169,7 +12513,9 @@ var mParticle = (function () {
|
|
|
12169
12513
|
};
|
|
12170
12514
|
this.mpInstance._RoktManager.notifyCheckoutIntent(Object.freeze({
|
|
12171
12515
|
kind: kind,
|
|
12172
|
-
t: t
|
|
12516
|
+
t: t,
|
|
12517
|
+
source: source,
|
|
12518
|
+
pathname: pathname
|
|
12173
12519
|
}), pathname);
|
|
12174
12520
|
};
|
|
12175
12521
|
// Events follow the SDK's opt-out like any other event.
|