@mparticle/web-sdk 3.28.0 → 3.29.1
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/README.md +65 -34
- package/dist/mparticle.common.js +1 -1
- package/dist/mparticle.esm.js +1 -1
- package/dist/mparticle.js +324 -55
- package/dist/types/src/buttonTracking/buttonDescription.d.ts +2 -0
- package/dist/types/src/buttonTracking/buttonTracker.d.ts +1 -0
- package/dist/types/src/buttonTracking/checkoutIntent.d.ts +2 -0
- package/dist/types/src/roktManager.d.ts +3 -0
- package/package.json +1 -1
- package/src/buttonTracking/buttonDescription.ts +397 -61
- package/src/buttonTracking/buttonTracker.ts +49 -7
- package/src/buttonTracking/checkoutIntent.ts +36 -0
- package/src/mp-instance.ts +8 -4
- package/src/roktManager.ts +13 -0
|
@@ -3,6 +3,7 @@ import { isFunction } from '../utils';
|
|
|
3
3
|
import { buildButtonClickEvent, buildLinkClickEvent } from './buttonClickEvent';
|
|
4
4
|
import {
|
|
5
5
|
describeButtonClick,
|
|
6
|
+
describeCheckedPaymentChoice,
|
|
6
7
|
describePaymentChoice,
|
|
7
8
|
IButtonClick,
|
|
8
9
|
IPaymentChoice,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
CheckoutIntentSource,
|
|
15
16
|
classifyIntent,
|
|
16
17
|
classifyPaymentFrame,
|
|
18
|
+
namesWallet,
|
|
17
19
|
PaymentFrameFamily,
|
|
18
20
|
} from './checkoutIntent';
|
|
19
21
|
import { findControl, findRoleControl, isExcludedPath } from './controls';
|
|
@@ -160,8 +162,9 @@ export class ButtonTracker {
|
|
|
160
162
|
// Bumped on reset so classification queued before it is dropped.
|
|
161
163
|
private generation = 0;
|
|
162
164
|
private lastCommitClick = -Infinity;
|
|
163
|
-
// The latest trusted click: whether the page excluded it
|
|
164
|
-
// already carries to the kit, with its
|
|
165
|
+
// The latest trusted click: whether the page excluded it or it was withheld
|
|
166
|
+
// from the kit, and the intent it already carries to the kit, with its
|
|
167
|
+
// source, if any.
|
|
165
168
|
private lastClick: {
|
|
166
169
|
t: number;
|
|
167
170
|
excluded: boolean;
|
|
@@ -390,6 +393,7 @@ export class ButtonTracker {
|
|
|
390
393
|
let source: CheckoutIntentSource | undefined;
|
|
391
394
|
let adds: true | undefined;
|
|
392
395
|
let choice: IPaymentChoice | undefined;
|
|
396
|
+
let checkedNames: string[] | undefined;
|
|
393
397
|
let family: PaymentFrameFamily | undefined;
|
|
394
398
|
let unconfirmedCommit = false;
|
|
395
399
|
if (layer === 'click') {
|
|
@@ -405,15 +409,26 @@ export class ButtonTracker {
|
|
|
405
409
|
? undefined
|
|
406
410
|
: findControl(path, true, PATH_LIMIT) ||
|
|
407
411
|
findRoleControl(path, PATH_LIMIT));
|
|
412
|
+
// A click that link tracking logs as Auto Link Click gives the kit no
|
|
413
|
+
// intent when the Rokt connection does not receive those events.
|
|
414
|
+
let withheld = false;
|
|
408
415
|
if (logged) {
|
|
409
416
|
// With links alone, only an anchor can be logged.
|
|
410
|
-
|
|
417
|
+
const logsThis =
|
|
411
418
|
logsEvents &&
|
|
412
|
-
(this.logButtons || logged.matches('a[href]'))
|
|
419
|
+
(this.logButtons || logged.matches('a[href]'));
|
|
420
|
+
const linksWithheld =
|
|
421
|
+
signalsKit && this.logLinks && !this.linkClicksReachKit();
|
|
422
|
+
const described =
|
|
423
|
+
logsThis || linksWithheld
|
|
413
424
|
? describeButtonClick(logged, path)
|
|
414
425
|
: undefined;
|
|
426
|
+
const linkClick =
|
|
427
|
+
this.logLinks && !!described && isLinkClick(described);
|
|
428
|
+
withheld = linksWithheld && linkClick;
|
|
429
|
+
click = logsThis ? described : undefined;
|
|
415
430
|
if (click) {
|
|
416
|
-
asLink =
|
|
431
|
+
asLink = linkClick;
|
|
417
432
|
// Without link tracking, a link that leaves the page stays a
|
|
418
433
|
// button click, except a plain link to another page of the
|
|
419
434
|
// site, which button tracking never logged.
|
|
@@ -432,7 +447,7 @@ export class ButtonTracker {
|
|
|
432
447
|
box = logged as HTMLInputElement;
|
|
433
448
|
}
|
|
434
449
|
}
|
|
435
|
-
if (control) {
|
|
450
|
+
if (control && !withheld) {
|
|
436
451
|
// Checkout intent is only worked out when the kit can use it.
|
|
437
452
|
const intent = signalsKit
|
|
438
453
|
? classifyIntent(control, pathname)
|
|
@@ -443,8 +458,28 @@ export class ButtonTracker {
|
|
|
443
458
|
choice = signalsKit
|
|
444
459
|
? describePaymentChoice(control, path)
|
|
445
460
|
: undefined;
|
|
461
|
+
// A confirm or checkout-step click picks no radio itself, so it
|
|
462
|
+
// carries the one the page holds checked as read here. It is not
|
|
463
|
+
// read again at delivery: the page's own handlers may clear or
|
|
464
|
+
// replace it by then. A wallet tap names its own method, and
|
|
465
|
+
// so does a confirm that names a wallet.
|
|
466
|
+
if (
|
|
467
|
+
!choice &&
|
|
468
|
+
(kind === 'commit' || kind === 'checkout_step') &&
|
|
469
|
+
!namesWallet(control, click)
|
|
470
|
+
) {
|
|
471
|
+
const checked = describeCheckedPaymentChoice();
|
|
472
|
+
checkedNames = checked ? checked.names : undefined;
|
|
473
|
+
}
|
|
446
474
|
}
|
|
447
|
-
|
|
475
|
+
// A withheld click cannot start an Apple Pay session for the kit
|
|
476
|
+
// either.
|
|
477
|
+
this.lastClick = {
|
|
478
|
+
t,
|
|
479
|
+
excluded: excluded || withheld,
|
|
480
|
+
kind,
|
|
481
|
+
source,
|
|
482
|
+
};
|
|
448
483
|
} else if (layer === 'submit') {
|
|
449
484
|
const submitter = (event as Event & { submitter?: Element | null })
|
|
450
485
|
.submitter;
|
|
@@ -503,6 +538,8 @@ export class ButtonTracker {
|
|
|
503
538
|
// with this pick.
|
|
504
539
|
if (choice && isPicked(choice.radio)) {
|
|
505
540
|
this.pickPayment(t, pathname, choice.names);
|
|
541
|
+
} else if (checkedNames) {
|
|
542
|
+
this.pickPayment(t, pathname, checkedNames);
|
|
506
543
|
} else if (family) {
|
|
507
544
|
this.pickPayment(t, pathname, undefined, family);
|
|
508
545
|
}
|
|
@@ -625,6 +662,11 @@ export class ButtonTracker {
|
|
|
625
662
|
);
|
|
626
663
|
}
|
|
627
664
|
|
|
665
|
+
private linkClicksReachKit(): boolean {
|
|
666
|
+
const roktManager = this.mpInstance._RoktManager;
|
|
667
|
+
return !!roktManager && roktManager.areAutoLinkClicksForwarded();
|
|
668
|
+
}
|
|
669
|
+
|
|
628
670
|
private log(message: string): void {
|
|
629
671
|
this.mpInstance.Logger.verbose('mParticle button tracking: ' + message);
|
|
630
672
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
isWalletNamedControl,
|
|
8
8
|
vendorButtonKind,
|
|
9
9
|
} from './controls';
|
|
10
|
+
import { IButtonClick, TEST_ID_ATTRIBUTES } from './buttonDescription';
|
|
10
11
|
|
|
11
12
|
// Classifies a click, submit or frame focus as checkout intent for the Rokt kit.
|
|
12
13
|
// Reads only short control labels, never form field values, and stores nothing.
|
|
@@ -126,6 +127,9 @@ const walletBrands = new RegExp(
|
|
|
126
127
|
'^(?:' + WALLET_NAMES + ')(?: (?:checkout|check out|express(?: checkout)?|express pay|buy now|pay now|pay|now)' +
|
|
127
128
|
'| (?:logo|icon|mark|button)(?: (?:logo|icon|mark|button))*)?$'
|
|
128
129
|
);
|
|
130
|
+
// The wallets the Rokt kit reads from a click's label or identifiers, as words:
|
|
131
|
+
// "Place order with PayPal!" and "paypal-pay-button" both name PayPal.
|
|
132
|
+
const KIT_WALLET_WORDS = /(?:^| )(?:apple ?pay|google ?pay|g ?pay|android ?pay|venmo|pay ?pal|amazon ?pay|shop ?pay|klarna|after ?pay|clear ?pay|affirm|cash ?app|sezzle|samsung ?pay)(?= |$)/;
|
|
129
133
|
// The payment method a frame's focus picks, in the Rokt kit's family names.
|
|
130
134
|
export type PaymentFrameFamily =
|
|
131
135
|
| 'apple_pay'
|
|
@@ -393,6 +397,38 @@ export function classifyIntent(
|
|
|
393
397
|
return cart;
|
|
394
398
|
}
|
|
395
399
|
|
|
400
|
+
// The Rokt kit's own word split, so "PayPal!" and "paypal2" both read as
|
|
401
|
+
// "paypal" here as they do there.
|
|
402
|
+
function kitWords(value: string | null | undefined): string {
|
|
403
|
+
return (value || '')
|
|
404
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
405
|
+
.toLowerCase()
|
|
406
|
+
.replace(/[^a-z]+/g, ' ')
|
|
407
|
+
.trim();
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// A confirm control may name its wallet, so "Place order with PayPal" and a
|
|
411
|
+
// "Place order" button with id paypal-pay-button are confirm clicks that still
|
|
412
|
+
// name their own method. A logged click is judged on the fields the kit reads
|
|
413
|
+
// a method from; a click that isn't logged, on the control's own label and
|
|
414
|
+
// identifiers.
|
|
415
|
+
export function namesWallet(control: Element, click?: IButtonClick): boolean {
|
|
416
|
+
const values = click
|
|
417
|
+
? [
|
|
418
|
+
click.label,
|
|
419
|
+
click.ariaLabel,
|
|
420
|
+
click.elementId,
|
|
421
|
+
click.elementName,
|
|
422
|
+
click.testId,
|
|
423
|
+
]
|
|
424
|
+
: labelSources(control).concat(
|
|
425
|
+
['id', 'name']
|
|
426
|
+
.concat(TEST_ID_ATTRIBUTES)
|
|
427
|
+
.map(attribute => control.getAttribute(attribute))
|
|
428
|
+
);
|
|
429
|
+
return values.some(value => KIT_WALLET_WORDS.test(kitWords(value)));
|
|
430
|
+
}
|
|
431
|
+
|
|
396
432
|
export function classifyIntentControl(
|
|
397
433
|
control: Element,
|
|
398
434
|
pathname: string
|
package/src/mp-instance.ts
CHANGED
|
@@ -1390,6 +1390,7 @@ function completeSDKInitialization(apiKey, config, mpInstance) {
|
|
|
1390
1390
|
// Button tracking never runs as a native passthrough.
|
|
1391
1391
|
resetButtonTracking();
|
|
1392
1392
|
mpInstance._RoktManager.setAutoButtonClicksForwarded(false);
|
|
1393
|
+
mpInstance._RoktManager.setAutoLinkClicksForwarded(false);
|
|
1393
1394
|
} else {
|
|
1394
1395
|
// Main SDK initialization flow
|
|
1395
1396
|
|
|
@@ -1459,8 +1460,8 @@ function completeSDKInitialization(apiKey, config, mpInstance) {
|
|
|
1459
1460
|
// init or from a returning shopper's landing page view.
|
|
1460
1461
|
const logButtons = !!getFeatureFlag(AutoButtonTracking);
|
|
1461
1462
|
const logLinks = !!getFeatureFlag(AutoLinkTracking);
|
|
1462
|
-
//
|
|
1463
|
-
//
|
|
1463
|
+
// With link tracking on, a click logged as Auto Link Click feeds intent
|
|
1464
|
+
// only when the kit also receives those events; button clicks still do.
|
|
1464
1465
|
const buttonClickBlocker = roktConfig
|
|
1465
1466
|
? getButtonClickBlocker(roktConfig, kitBlocker)
|
|
1466
1467
|
: undefined;
|
|
@@ -1472,11 +1473,14 @@ function completeSDKInitialization(apiKey, config, mpInstance) {
|
|
|
1472
1473
|
mpInstance.Logger.warning(
|
|
1473
1474
|
buttonClickBlocker
|
|
1474
1475
|
? `Checkout intent is off: the ${buttonClickBlocker} keeps some or all Auto Button Click events from the Rokt kit.`
|
|
1475
|
-
: `Checkout intent
|
|
1476
|
+
: `Checkout intent skips link clicks: the ${linkClickBlocker} keeps some or all Auto Link Click events from the Rokt kit.`
|
|
1476
1477
|
);
|
|
1477
1478
|
}
|
|
1478
1479
|
mpInstance._RoktManager.setAutoButtonClicksForwarded(
|
|
1479
|
-
!!roktConfig && !buttonClickBlocker
|
|
1480
|
+
!!roktConfig && !buttonClickBlocker
|
|
1481
|
+
);
|
|
1482
|
+
mpInstance._RoktManager.setAutoLinkClicksForwarded(
|
|
1483
|
+
!!roktConfig && !linkClickBlocker
|
|
1480
1484
|
);
|
|
1481
1485
|
|
|
1482
1486
|
mpInstance._Forwarders.processForwarders(
|
package/src/roktManager.ts
CHANGED
|
@@ -175,6 +175,9 @@ export default class RoktManager {
|
|
|
175
175
|
private pendingCheckoutIntents: Array<{ signal: ICheckoutIntentSignal; pathname: string }> = [];
|
|
176
176
|
// Off until SDK init finds the Rokt connection receives auto button clicks.
|
|
177
177
|
private autoButtonClicksForwarded = false;
|
|
178
|
+
// Off until SDK init finds the Rokt connection receives auto link clicks, or
|
|
179
|
+
// link tracking is off. Only link clicks lose their intent without it.
|
|
180
|
+
private autoLinkClicksForwarded = false;
|
|
178
181
|
|
|
179
182
|
// Keys this manager's route subscription, so a re-executed bundle's manager replaces
|
|
180
183
|
// it while a second named instance keeps its own.
|
|
@@ -331,6 +334,16 @@ export default class RoktManager {
|
|
|
331
334
|
this.autoButtonClicksForwarded = forwarded;
|
|
332
335
|
}
|
|
333
336
|
|
|
337
|
+
public setAutoLinkClicksForwarded(forwarded: boolean): void {
|
|
338
|
+
this.autoLinkClicksForwarded = forwarded;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// The Rokt kit reads this too: while false, its page views keep
|
|
342
|
+
// dispatchDelayMs, since a checkout link could never release the wait.
|
|
343
|
+
public areAutoLinkClicksForwarded(): boolean {
|
|
344
|
+
return this.autoLinkClicksForwarded;
|
|
345
|
+
}
|
|
346
|
+
|
|
334
347
|
// noTargeting (which noDeviceId implies), doNotShareOrSell or Global Privacy
|
|
335
348
|
// Control. Unlike noFunctional, these also stop auto button and link clicks
|
|
336
349
|
// from being logged.
|