@mparticle/web-sdk 3.22.0 → 3.24.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 +1 -1669
- package/dist/mparticle.esm.js +1 -1669
- package/dist/mparticle.js +4827 -4184
- package/dist/types/src/buttonTracking/buttonClickEvent.d.ts +4 -0
- package/dist/types/src/buttonTracking/buttonDescription.d.ts +2 -2
- package/dist/types/src/buttonTracking/buttonTracker.d.ts +2 -0
- package/dist/types/src/buttonTracking/checkoutIntent.d.ts +2 -2
- package/dist/types/src/buttonTracking/controls.d.ts +6 -3
- package/dist/types/src/buttonTracking/labelMasking.d.ts +1 -0
- package/dist/types/src/configAPIClient.d.ts +1 -0
- package/dist/types/src/forwarder-utils.d.ts +3 -0
- package/dist/types/src/pageUrl.d.ts +10 -0
- package/dist/types/src/pageViewTracker.d.ts +21 -3
- package/dist/types/src/roktManager.d.ts +8 -2
- package/dist/types/src/sdkRuntimeModels.d.ts +3 -0
- package/dist/types/src/store.d.ts +2 -0
- package/package.json +21 -14
- package/src/batchUploader.ts +3 -2
- package/src/buttonTracking/buttonClickEvent.ts +76 -1
- package/src/buttonTracking/buttonDescription.ts +236 -46
- package/src/buttonTracking/buttonTracker.ts +122 -7
- package/src/buttonTracking/checkoutIntent.ts +51 -9
- package/src/buttonTracking/controls.ts +160 -50
- package/src/buttonTracking/labelMasking.ts +4 -1
- package/src/configAPIClient.ts +1 -0
- package/src/events.ts +9 -3
- package/src/forwarder-utils.ts +38 -0
- package/src/forwarders.ts +2 -28
- package/src/mp-instance.ts +23 -10
- package/src/mparticle-instance-manager.ts +3 -0
- package/src/pageUrl.ts +191 -0
- package/src/pageViewTracker.ts +206 -78
- package/src/roktManager.ts +53 -20
- package/src/sdkRuntimeModels.ts +3 -0
- package/src/serverModel.ts +3 -3
- package/src/store.ts +14 -0
- package/src/utils.ts +1 -1
|
@@ -6,29 +6,46 @@ import { isFunction } from '../utils';
|
|
|
6
6
|
export const CONTROL_SELECTOR =
|
|
7
7
|
'button, input[type="submit"], input[type="button"], input[type="image"], [role="button"]';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const EDITABLE = '[contenteditable]:not([contenteditable="false"])';
|
|
10
|
+
|
|
11
|
+
// One step up the composed tree: the slot, the parent, then the shadow host.
|
|
12
|
+
function composedParent(node: Element): Element | null {
|
|
13
|
+
const root = isFunction(node.getRootNode)
|
|
14
|
+
? (node.getRootNode() as ShadowRoot)
|
|
15
|
+
: null;
|
|
16
|
+
return (
|
|
17
|
+
(node as Element & { assignedSlot?: Element | null }).assignedSlot ||
|
|
18
|
+
node.parentElement ||
|
|
19
|
+
(root && root.host) ||
|
|
20
|
+
null
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Tests the element and its ancestors over the composed tree: slots, parents,
|
|
25
|
+
// then shadow hosts. With `until`, stops before reaching that ancestor.
|
|
26
|
+
export function someComposedAncestor(
|
|
11
27
|
element: Element,
|
|
12
|
-
|
|
28
|
+
test: (node: Element) => boolean,
|
|
29
|
+
until?: Element
|
|
13
30
|
): boolean {
|
|
14
31
|
let node: Element | null = element;
|
|
15
|
-
while (node) {
|
|
16
|
-
if (node
|
|
32
|
+
while (node && node !== until) {
|
|
33
|
+
if (test(node)) {
|
|
17
34
|
return true;
|
|
18
35
|
}
|
|
19
|
-
|
|
20
|
-
? (node.getRootNode() as ShadowRoot)
|
|
21
|
-
: null;
|
|
22
|
-
node =
|
|
23
|
-
(node as Element & { assignedSlot?: Element | null })
|
|
24
|
-
.assignedSlot ||
|
|
25
|
-
node.parentElement ||
|
|
26
|
-
(root && root.host) ||
|
|
27
|
-
null;
|
|
36
|
+
node = composedParent(node);
|
|
28
37
|
}
|
|
29
38
|
return false;
|
|
30
39
|
}
|
|
31
40
|
|
|
41
|
+
// closest() over the composed tree, starting at the element itself.
|
|
42
|
+
export function hasComposedAncestor(
|
|
43
|
+
element: Element,
|
|
44
|
+
selector: string
|
|
45
|
+
): boolean {
|
|
46
|
+
return someComposedAncestor(element, node => node.matches(selector));
|
|
47
|
+
}
|
|
48
|
+
|
|
32
49
|
const ROKT_SELECTOR = '[data-core-type], #rokt-controller-frame';
|
|
33
50
|
|
|
34
51
|
// A Rokt frame or script is served from a /wsdk/ path.
|
|
@@ -110,8 +127,44 @@ export function vendorButtonKind(
|
|
|
110
127
|
return host.matches(CARD_FORM_BUTTON) ? 'checkout_step' : 'wallet';
|
|
111
128
|
}
|
|
112
129
|
|
|
130
|
+
// A merchant-built wallet button that names its wallet in an id or test id, such
|
|
131
|
+
// as `venmo-pay-btn` or `applePayButton`. The name must also say button and not
|
|
132
|
+
// name a part or a wrapper of one, so a wallet's logo, label or container never
|
|
133
|
+
// counts, nor a button that only chooses a wallet. Affirm is also an English
|
|
134
|
+
// verb, so it counts only beside a payment word. Only the element's own
|
|
135
|
+
// attributes are read.
|
|
136
|
+
const 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)(?= |$)/;
|
|
137
|
+
const AFFIRM_IN_NAME = /(?:^| )affirm(?= |$)/;
|
|
138
|
+
const PAYMENT_IN_NAME = /(?:^| )(?:pay|payment|checkout)(?= |$)/;
|
|
139
|
+
const BUTTON_IN_NAME = /(?:^| )(?:btn|button)(?= |$)/;
|
|
140
|
+
const 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)(?= |$)/;
|
|
141
|
+
|
|
142
|
+
function nameWords(value: string | null): string {
|
|
143
|
+
return (value || '')
|
|
144
|
+
.slice(0, 64)
|
|
145
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
146
|
+
.replace(/[^A-Za-z0-9]+/g, ' ')
|
|
147
|
+
.trim()
|
|
148
|
+
.toLowerCase();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function isWalletNamed(element: Element): boolean {
|
|
152
|
+
return [element.getAttribute('id'), element.getAttribute('data-testid')]
|
|
153
|
+
.map(nameWords)
|
|
154
|
+
.some(
|
|
155
|
+
words =>
|
|
156
|
+
(WALLET_IN_NAME.test(words) ||
|
|
157
|
+
(AFFIRM_IN_NAME.test(words) &&
|
|
158
|
+
PAYMENT_IN_NAME.test(words))) &&
|
|
159
|
+
BUTTON_IN_NAME.test(words) &&
|
|
160
|
+
!PART_IN_NAME.test(words)
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
113
164
|
export function isWalletElement(element: Element): boolean {
|
|
114
|
-
return
|
|
165
|
+
return (
|
|
166
|
+
vendorButtonKind(element) === 'wallet' || isWalletNamedControl(element)
|
|
167
|
+
);
|
|
115
168
|
}
|
|
116
169
|
|
|
117
170
|
// Pickers and toggles choose a payment method or open a panel; they never pay.
|
|
@@ -125,9 +178,11 @@ const MESSAGING =
|
|
|
125
178
|
'.affirm-product-modal, [data-pp-message]';
|
|
126
179
|
|
|
127
180
|
// A control that is still logged as a click but never counts as checkout intent.
|
|
181
|
+
// A cart button can open a flyout, so aria-expanded alone never quiets one.
|
|
128
182
|
export function isQuietIntentControl(
|
|
129
183
|
control: Element,
|
|
130
|
-
isVendorWallet: boolean
|
|
184
|
+
isVendorWallet: boolean,
|
|
185
|
+
isCart = false
|
|
131
186
|
): boolean {
|
|
132
187
|
if (control.matches(PICKER)) {
|
|
133
188
|
return true;
|
|
@@ -137,7 +192,7 @@ export function isQuietIntentControl(
|
|
|
137
192
|
}
|
|
138
193
|
if (
|
|
139
194
|
!isVendorWallet &&
|
|
140
|
-
(control.hasAttribute('aria-expanded') ||
|
|
195
|
+
((!isCart && control.hasAttribute('aria-expanded')) ||
|
|
141
196
|
control.hasAttribute('aria-pressed'))
|
|
142
197
|
) {
|
|
143
198
|
return true;
|
|
@@ -146,15 +201,58 @@ export function isQuietIntentControl(
|
|
|
146
201
|
}
|
|
147
202
|
|
|
148
203
|
// A checkbox or radio is a control of its own only when no button or link
|
|
149
|
-
// around it takes the click, so a tappable row
|
|
204
|
+
// around it takes the click, so a tappable row stays the control.
|
|
150
205
|
const OPTION_INPUT = 'input[type="checkbox"], input[type="radio"]';
|
|
206
|
+
// Anything that chooses, toggles or takes typing. One between a click's target
|
|
207
|
+
// and a wallet-named element, or inside that element, keeps it from becoming
|
|
208
|
+
// the tap. Hidden inputs and button-like inputs are not fields.
|
|
209
|
+
const NAME_BARRIER =
|
|
210
|
+
PICKER +
|
|
211
|
+
', ' +
|
|
212
|
+
PICKER_GROUP +
|
|
213
|
+
', input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="image"]), ' +
|
|
214
|
+
'select, textarea, option, optgroup, datalist, [aria-expanded], [aria-pressed], ' +
|
|
215
|
+
EDITABLE;
|
|
216
|
+
// What owns a click on anything inside it: a picker, a real control or link,
|
|
217
|
+
// a toggle, or an editable region.
|
|
218
|
+
const NAME_HOLDER =
|
|
219
|
+
PICKER +
|
|
220
|
+
', ' +
|
|
221
|
+
CONTROL_SELECTOR +
|
|
222
|
+
', a[href], ' +
|
|
223
|
+
VENDOR_BUTTON +
|
|
224
|
+
', [aria-expanded], [aria-pressed], ' +
|
|
225
|
+
EDITABLE;
|
|
151
226
|
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
227
|
+
// A wallet-named element that may stand as a control by its name alone: not
|
|
228
|
+
// itself a picker, toggle or form field; not inside a picker, a real button or
|
|
229
|
+
// link, a toggle, an editable region or another wallet-named element, which own
|
|
230
|
+
// the click with their own state; and holding no picker or field, in its open
|
|
231
|
+
// shadow root either, so a payment-method row with its own radio or select
|
|
232
|
+
// never becomes a wallet tap.
|
|
233
|
+
export function isWalletNamedControl(element: Element): boolean {
|
|
234
|
+
if (!isWalletNamed(element) || element.matches(NAME_BARRIER)) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
for (
|
|
238
|
+
let node = composedParent(element);
|
|
239
|
+
node;
|
|
240
|
+
node = composedParent(node)
|
|
241
|
+
) {
|
|
242
|
+
if (node.matches(NAME_HOLDER) || isWalletNamed(node)) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const shadow = element.shadowRoot;
|
|
247
|
+
return (
|
|
248
|
+
!element.querySelector(NAME_BARRIER) &&
|
|
249
|
+
!(shadow && shadow.querySelector(NAME_BARRIER))
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Whether a click is never tracked: it has no element, or lands on a Rokt
|
|
254
|
+
// element or under data-mp-no-track. Reads at most 12 entries of the path.
|
|
255
|
+
export function isExcludedPath(path: Array<EventTarget | null>): boolean {
|
|
158
256
|
const entries = path.slice(0, 12);
|
|
159
257
|
let first: Element | undefined;
|
|
160
258
|
for (const target of entries) {
|
|
@@ -167,35 +265,62 @@ export function findControl(
|
|
|
167
265
|
!first ||
|
|
168
266
|
hasComposedAncestor(first, ROKT_SELECTOR + ', [data-mp-no-track]')
|
|
169
267
|
) {
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
for (const target of entries) {
|
|
271
|
+
if (target instanceof Element && hasRoktSrc(target)) {
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Walks at most 12 entries of an event's composed path for the nearest control.
|
|
279
|
+
// A wallet-named element counts as one unless a picker, toggle or form field
|
|
280
|
+
// lies between it and the target or a picker holds it, so choosing an option in
|
|
281
|
+
// or around it never becomes its tap. Excluded clicks give nothing; disabled controls are skipped, a
|
|
282
|
+
// wallet-named one also by its `disabled` class. A caller that has already
|
|
283
|
+
// found the path not excluded passes `included` so the walk runs once.
|
|
284
|
+
export function findControl(
|
|
285
|
+
path: Array<EventTarget | null>,
|
|
286
|
+
included = false
|
|
287
|
+
): Element | undefined {
|
|
288
|
+
if (!included && isExcludedPath(path)) {
|
|
170
289
|
return undefined;
|
|
171
290
|
}
|
|
172
291
|
let control: Element | undefined;
|
|
173
292
|
let option: Element | undefined;
|
|
174
|
-
|
|
293
|
+
let picked = false;
|
|
294
|
+
for (const target of path.slice(0, 12)) {
|
|
295
|
+
if (control) {
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
175
298
|
if (!(target instanceof Element)) {
|
|
176
299
|
continue;
|
|
177
300
|
}
|
|
178
|
-
if (hasRoktSrc(target)) {
|
|
179
|
-
return undefined;
|
|
180
|
-
}
|
|
181
301
|
if (
|
|
182
|
-
|
|
183
|
-
|
|
302
|
+
target.matches(CONTROL_SELECTOR + ', a[href], ' + VENDOR_BUTTON) ||
|
|
303
|
+
(!picked && isWalletNamedControl(target))
|
|
184
304
|
) {
|
|
185
305
|
control = target;
|
|
186
|
-
} else
|
|
187
|
-
option
|
|
306
|
+
} else {
|
|
307
|
+
if (!option && target.matches(OPTION_INPUT)) {
|
|
308
|
+
option = target;
|
|
309
|
+
}
|
|
310
|
+
picked = picked || target.matches(NAME_BARRIER);
|
|
188
311
|
}
|
|
189
312
|
}
|
|
190
313
|
control = control || option;
|
|
191
|
-
if (
|
|
314
|
+
if (
|
|
315
|
+
!control ||
|
|
316
|
+
control.matches(':disabled, [aria-disabled="true"]') ||
|
|
317
|
+
(control.classList.contains('disabled') && isWalletNamed(control))
|
|
318
|
+
) {
|
|
192
319
|
return undefined;
|
|
193
320
|
}
|
|
194
321
|
return control;
|
|
195
322
|
}
|
|
196
323
|
|
|
197
|
-
const EDITABLE = '[contenteditable]:not([contenteditable="false"])';
|
|
198
|
-
|
|
199
324
|
// Text that may hold what a shopper typed or what the page opted out: form
|
|
200
325
|
// fields, editable regions, and anything under data-mp-no-track.
|
|
201
326
|
export function isUnsafeTextSource(element: Element): boolean {
|
|
@@ -212,18 +337,3 @@ export function isUnsafeTextSource(element: Element): boolean {
|
|
|
212
337
|
)
|
|
213
338
|
);
|
|
214
339
|
}
|
|
215
|
-
|
|
216
|
-
// A label around a checkbox or radio holds no typed text, so only the other
|
|
217
|
-
// fields rule its text out.
|
|
218
|
-
export function isUnsafeLabelSource(label: Element): boolean {
|
|
219
|
-
return (
|
|
220
|
-
!!label.querySelector(
|
|
221
|
-
'input:not([type="checkbox"]):not([type="radio"]), select, textarea, [data-mp-no-track], ' +
|
|
222
|
-
EDITABLE
|
|
223
|
-
) ||
|
|
224
|
-
hasComposedAncestor(
|
|
225
|
-
label,
|
|
226
|
-
'select, datalist, textarea, [data-mp-no-track], ' + EDITABLE
|
|
227
|
-
)
|
|
228
|
-
);
|
|
229
|
-
}
|
|
@@ -16,10 +16,13 @@ function createDigitPatterns(): { run: RegExp; number: RegExp } | undefined {
|
|
|
16
16
|
|
|
17
17
|
const digitPatterns = createDigitPatterns();
|
|
18
18
|
|
|
19
|
+
// Longer raw labels are dropped, not cut short.
|
|
20
|
+
export const MAX_RAW_LABEL_LENGTH = 128;
|
|
21
|
+
|
|
19
22
|
// The label as logged: lower case, at most 64 characters, digits and amounts
|
|
20
23
|
// masked. Dropped when it looks like an email, phone or card number.
|
|
21
24
|
export function maskLabel(raw: string | null | undefined): string | undefined {
|
|
22
|
-
if (!raw || raw.length >
|
|
25
|
+
if (!raw || raw.length > MAX_RAW_LABEL_LENGTH || !digitPatterns) {
|
|
23
26
|
return undefined;
|
|
24
27
|
}
|
|
25
28
|
const text = raw
|
package/src/configAPIClient.ts
CHANGED
package/src/events.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
allowedQueryParams,
|
|
26
26
|
autoPageViewAttribute,
|
|
27
27
|
IPageViewOptions,
|
|
28
|
+
paramsToAttributes,
|
|
28
29
|
} from './pageViewTracker';
|
|
29
30
|
|
|
30
31
|
interface DOMHandlerElement extends HTMLElement {
|
|
@@ -151,8 +152,7 @@ export default function Events(
|
|
|
151
152
|
|
|
152
153
|
// The auto page view for the landing page. The SPA navigations that follow it
|
|
153
154
|
// come from PageViewTracker instead, so both emitters attach the same
|
|
154
|
-
// allowlisted query params
|
|
155
|
-
// attribution, since utm_*/gclid live on the entry URL.
|
|
155
|
+
// allowlisted query params.
|
|
156
156
|
this.logPageView = function(options?: IPageViewOptions): void {
|
|
157
157
|
self.logEvent({
|
|
158
158
|
messageType: Types.MessageType.PageView,
|
|
@@ -160,7 +160,13 @@ export default function Events(
|
|
|
160
160
|
data: {
|
|
161
161
|
// Params first so the core fields always win. See
|
|
162
162
|
// buildPageViewEvent, which does the same for SPA views.
|
|
163
|
-
...
|
|
163
|
+
...paramsToAttributes(
|
|
164
|
+
allowedQueryParams(
|
|
165
|
+
getHref(),
|
|
166
|
+
mpInstance._Store.SDKConfig.autoLogPageViewQueryParams
|
|
167
|
+
?.allowed
|
|
168
|
+
)
|
|
169
|
+
),
|
|
164
170
|
hostname: window.location.hostname,
|
|
165
171
|
title: window.document.title,
|
|
166
172
|
...autoPageViewAttribute(options && options.isAutoPageView),
|
package/src/forwarder-utils.ts
CHANGED
|
@@ -7,6 +7,8 @@ import { inArray, valueof } from './utils';
|
|
|
7
7
|
import KitFilterHelper from './kitFilterHelper';
|
|
8
8
|
import * as EventsApi from '@mparticle/event-models';
|
|
9
9
|
import { IKitFilterSettings } from './configAPIClient';
|
|
10
|
+
import type { SDKEvent } from './sdkRuntimeModels';
|
|
11
|
+
import type KitBlocker from './kitBlocking';
|
|
10
12
|
import {
|
|
11
13
|
getEventNameFromBatchEvent,
|
|
12
14
|
getEventCategoryFromBatchEvent,
|
|
@@ -117,6 +119,42 @@ export function isBlockedByEventFilter(
|
|
|
117
119
|
return false;
|
|
118
120
|
}
|
|
119
121
|
|
|
122
|
+
// sendEventToForwarders passes no kitBlocker: apiClient has already applied
|
|
123
|
+
// the data plan by then.
|
|
124
|
+
export function isEventForwarded(
|
|
125
|
+
event: SDKEvent,
|
|
126
|
+
forwarder: ForwarderFilterConfig,
|
|
127
|
+
kitBlocker?: KitBlocker
|
|
128
|
+
): boolean {
|
|
129
|
+
const plannedEvent =
|
|
130
|
+
kitBlocker && kitBlocker.kitBlockingEnabled
|
|
131
|
+
? kitBlocker.createBlockedEvent(event)
|
|
132
|
+
: event;
|
|
133
|
+
if (!plannedEvent) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (
|
|
138
|
+
isBlockedByForwardingRule(
|
|
139
|
+
plannedEvent.EventDataType,
|
|
140
|
+
plannedEvent.EventAttributes,
|
|
141
|
+
forwarder
|
|
142
|
+
)
|
|
143
|
+
) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const eventCategory = plannedEvent.EventCategory as valueof<
|
|
148
|
+
typeof EventType
|
|
149
|
+
>;
|
|
150
|
+
return !isBlockedByEventFilter(
|
|
151
|
+
plannedEvent.EventDataType,
|
|
152
|
+
KitFilterHelper.hashEventName(plannedEvent.EventName, eventCategory),
|
|
153
|
+
KitFilterHelper.hashEventType(eventCategory),
|
|
154
|
+
forwarder
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
120
158
|
export function filterEventAttributes(
|
|
121
159
|
messageType: number,
|
|
122
160
|
eventCategory: number,
|
package/src/forwarders.ts
CHANGED
|
@@ -6,8 +6,7 @@ import KitFilterHelper from './kitFilterHelper';
|
|
|
6
6
|
import Constants from './constants';
|
|
7
7
|
import APIClient, { IForwardingStatsData } from './apiClient';
|
|
8
8
|
import {
|
|
9
|
-
|
|
10
|
-
isBlockedByEventFilter,
|
|
9
|
+
isEventForwarded,
|
|
11
10
|
filterEventAttributes,
|
|
12
11
|
filterUserIdentities,
|
|
13
12
|
} from './forwarder-utils';
|
|
@@ -266,39 +265,14 @@ export default function Forwarders(
|
|
|
266
265
|
return;
|
|
267
266
|
}
|
|
268
267
|
|
|
269
|
-
const hashedEventName = KitFilterHelper.hashEventName(
|
|
270
|
-
event.EventName,
|
|
271
|
-
event.EventCategory as any
|
|
272
|
-
);
|
|
273
|
-
const hashedEventType = KitFilterHelper.hashEventType(
|
|
274
|
-
event.EventCategory as any
|
|
275
|
-
);
|
|
276
|
-
|
|
277
268
|
const forwardEvent = function(forwarder: ConfiguredKit): void {
|
|
278
|
-
if (
|
|
279
|
-
isBlockedByForwardingRule(
|
|
280
|
-
event.EventDataType,
|
|
281
|
-
event.EventAttributes,
|
|
282
|
-
forwarder
|
|
283
|
-
)
|
|
284
|
-
) {
|
|
269
|
+
if (!isEventForwarded(event, forwarder)) {
|
|
285
270
|
return;
|
|
286
271
|
}
|
|
287
272
|
|
|
288
273
|
// Clone the event object, as we could be sending different attributes to each forwarder
|
|
289
274
|
const clonedEvent = extend(true, {}, event);
|
|
290
275
|
|
|
291
|
-
if (
|
|
292
|
-
isBlockedByEventFilter(
|
|
293
|
-
event.EventDataType,
|
|
294
|
-
hashedEventName,
|
|
295
|
-
hashedEventType,
|
|
296
|
-
forwarder
|
|
297
|
-
)
|
|
298
|
-
) {
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
276
|
clonedEvent.EventAttributes = filterEventAttributes(
|
|
303
277
|
event.EventDataType,
|
|
304
278
|
event.EventCategory,
|
package/src/mp-instance.ts
CHANGED
|
@@ -45,7 +45,7 @@ import { removeExpiredIdentityCacheDates, hasExplicitIdentifier } from './identi
|
|
|
45
45
|
import IntegrationCapture, { isIntegrationCaptureEnabled } from './integrationCapture';
|
|
46
46
|
import { IPreInit, processReadyQueue } from './pre-init-utils';
|
|
47
47
|
import { BaseEvent, MParticleWebSDK, SDKHelpersApi } from './sdkRuntimeModels';
|
|
48
|
-
import { Dictionary, SDKEventAttrs } from '@mparticle/web-sdk';
|
|
48
|
+
import { Dictionary, MPID, SDKEventAttrs } from '@mparticle/web-sdk';
|
|
49
49
|
import { IIdentity } from './identity.interfaces';
|
|
50
50
|
import { IEvents } from './events.interfaces';
|
|
51
51
|
import { IECommerce } from './ecommerce.interfaces';
|
|
@@ -67,6 +67,7 @@ import {
|
|
|
67
67
|
resetPageViewTracking,
|
|
68
68
|
} from './pageViewTracker';
|
|
69
69
|
import { ButtonTracker, resetButtonTracking } from './buttonTracking/buttonTracker';
|
|
70
|
+
import { getButtonClickBlocker } from './buttonTracking/buttonClickEvent';
|
|
70
71
|
|
|
71
72
|
export interface IErrorLogMessage {
|
|
72
73
|
message?: string;
|
|
@@ -1384,6 +1385,7 @@ function completeSDKInitialization(apiKey, config, mpInstance) {
|
|
|
1384
1385
|
mpInstance._NativeSdkHelpers.initializeSessionAttributes(apiKey);
|
|
1385
1386
|
// Button tracking never runs as a native passthrough.
|
|
1386
1387
|
resetButtonTracking();
|
|
1388
|
+
mpInstance._RoktManager.setAutoButtonClicksForwarded(false);
|
|
1387
1389
|
} else {
|
|
1388
1390
|
// Main SDK initialization flow
|
|
1389
1391
|
|
|
@@ -1424,12 +1426,8 @@ function completeSDKInitialization(apiKey, config, mpInstance) {
|
|
|
1424
1426
|
if (roktConfig) {
|
|
1425
1427
|
const accountId = roktConfig.settings?.accountId;
|
|
1426
1428
|
mpInstance._Store.setRoktAccountId(accountId);
|
|
1427
|
-
const
|
|
1428
|
-
|
|
1429
|
-
currentUserMPID,
|
|
1430
|
-
{ userAttributeFilters },
|
|
1431
|
-
mpInstance
|
|
1432
|
-
);
|
|
1429
|
+
const createRoktFilteredUser = (mpid: MPID) =>
|
|
1430
|
+
filteredMparticleUser(mpid, roktConfig, mpInstance, kitBlocker);
|
|
1433
1431
|
const roktOptions: IRoktOptions = {
|
|
1434
1432
|
sandbox: config?.isDevelopmentMode,
|
|
1435
1433
|
launcherOptions: config?.launcherOptions,
|
|
@@ -1438,7 +1436,7 @@ function completeSDKInitialization(apiKey, config, mpInstance) {
|
|
|
1438
1436
|
// https://go.mparticle.com/work/SQDSDKS-7339
|
|
1439
1437
|
mpInstance._RoktManager.init(
|
|
1440
1438
|
roktConfig,
|
|
1441
|
-
|
|
1439
|
+
createRoktFilteredUser(currentUserMPID),
|
|
1442
1440
|
mpInstance.Identity,
|
|
1443
1441
|
mpInstance._Store,
|
|
1444
1442
|
mpInstance.Logger,
|
|
@@ -1447,9 +1445,26 @@ function completeSDKInitialization(apiKey, config, mpInstance) {
|
|
|
1447
1445
|
mpInstance._ErrorReportingDispatcher,
|
|
1448
1446
|
mpInstance._LoggingDispatcher,
|
|
1449
1447
|
mpInstance._IntegrationCapture,
|
|
1448
|
+
createRoktFilteredUser,
|
|
1450
1449
|
);
|
|
1451
1450
|
}
|
|
1452
1451
|
|
|
1452
|
+
// Checkout intent comes from the shopper's clicks, so it goes to the
|
|
1453
|
+
// Rokt kit only when that kit receives them. Set before
|
|
1454
|
+
// processForwarders: the kit can read it during this init, from its own
|
|
1455
|
+
// init or from a returning shopper's landing page view.
|
|
1456
|
+
const buttonClickBlocker = roktConfig
|
|
1457
|
+
? getButtonClickBlocker(roktConfig, kitBlocker)
|
|
1458
|
+
: undefined;
|
|
1459
|
+
if (buttonClickBlocker && getFeatureFlag(AutoButtonTracking)) {
|
|
1460
|
+
mpInstance.Logger.warning(
|
|
1461
|
+
`Checkout intent is off: the ${buttonClickBlocker} keeps some or all Auto Button Click events from the Rokt kit.`
|
|
1462
|
+
);
|
|
1463
|
+
}
|
|
1464
|
+
mpInstance._RoktManager.setAutoButtonClicksForwarded(
|
|
1465
|
+
!!roktConfig && !buttonClickBlocker
|
|
1466
|
+
);
|
|
1467
|
+
|
|
1453
1468
|
mpInstance._Forwarders.processForwarders(
|
|
1454
1469
|
config,
|
|
1455
1470
|
mpInstance._APIClient.prepareForwardingStats
|
|
@@ -1494,8 +1509,6 @@ function completeSDKInitialization(apiKey, config, mpInstance) {
|
|
|
1494
1509
|
mpInstance._PageViewTracker.teardown();
|
|
1495
1510
|
}
|
|
1496
1511
|
|
|
1497
|
-
// Button clicks are logged for any workspace that opts in. Checkout
|
|
1498
|
-
// intent also goes to the Rokt kit when this init configures one.
|
|
1499
1512
|
if (getFeatureFlag(AutoButtonTracking)) {
|
|
1500
1513
|
if (!mpInstance._ButtonTracker) {
|
|
1501
1514
|
mpInstance._ButtonTracker = new ButtonTracker(mpInstance);
|
|
@@ -7,6 +7,7 @@ import MPSideloadedKit from './sideloadedKit';
|
|
|
7
7
|
import { IMParticleInstanceManager } from './sdkRuntimeModels';
|
|
8
8
|
import { IStore } from './store';
|
|
9
9
|
import { Dictionary } from '@mparticle/web-sdk';
|
|
10
|
+
import { cleanUrl } from './pageUrl';
|
|
10
11
|
|
|
11
12
|
if (!Array.prototype.forEach) {
|
|
12
13
|
Array.prototype.forEach = Polyfill.forEach;
|
|
@@ -449,6 +450,8 @@ function mParticleInstanceManager(this: IMParticleInstanceManager) {
|
|
|
449
450
|
this.generateHash = function(string) {
|
|
450
451
|
return self.getInstance().generateHash(string);
|
|
451
452
|
};
|
|
453
|
+
// Kits clean every URL they send with the same rule as core events.
|
|
454
|
+
this._cleanUrl = cleanUrl;
|
|
452
455
|
this.addForwarder = function(forwarder) {
|
|
453
456
|
self.getInstance().addForwarder(forwarder);
|
|
454
457
|
};
|