@mparticle/web-sdk 2.62.0 → 2.64.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/src/forwarders.js CHANGED
@@ -1,9 +1,17 @@
1
- import Types from './types';
2
1
  import filteredMparticleUser from './filteredMparticleUser';
3
- import { isEmpty } from './utils';
2
+ import { isEmpty, extend } from './utils';
4
3
  import KitFilterHelper from './kitFilterHelper';
5
4
  import Constants from './constants';
6
5
  import APIClient from './apiClient';
6
+ import {
7
+ isBlockedByForwardingRule,
8
+ isBlockedByEventFilter,
9
+ filterEventAttributes,
10
+ filterUserIdentities,
11
+ isBatchEventAllowed,
12
+ filterBatchEventAttributes,
13
+ filterBatchIdentities,
14
+ } from './forwarder-utils';
7
15
 
8
16
  const { Modify, Identify, Login, Logout } = Constants.IdentityMethods;
9
17
 
@@ -65,7 +73,7 @@ export default function Forwarders(mpInstance, kitBlocker) {
65
73
  userIdentities,
66
74
  forwarder.userIdentityFilters
67
75
  );
68
- var filteredUserAttributes = mpInstance._Helpers.filterUserAttributes(
76
+ const filteredUserAttributes = KitFilterHelper.filterUserAttributes(
69
77
  user ? user.getAllUserAttributes() : {},
70
78
  forwarder.userAttributeFilters
71
79
  );
@@ -177,64 +185,9 @@ export default function Forwarders(mpInstance, kitBlocker) {
177
185
  };
178
186
 
179
187
  this.sendEventToForwarders = function(event) {
180
- var clonedEvent,
181
- hashedEventName,
182
- hashedEventType,
183
- filterUserIdentities = function(event, filterList) {
184
- if (event.UserIdentities && event.UserIdentities.length) {
185
- event.UserIdentities.forEach(function(userIdentity, i) {
186
- if (
187
- mpInstance._Helpers.inArray(
188
- filterList,
189
- KitFilterHelper.hashUserIdentity(
190
- userIdentity.Type
191
- )
192
- )
193
- ) {
194
- event.UserIdentities.splice(i, 1);
195
-
196
- if (i > 0) {
197
- i--;
198
- }
199
- }
200
- });
201
- }
202
- },
203
- filterAttributes = function(event, filterList) {
204
- var hash;
205
-
206
- if (!filterList) {
207
- return;
208
- }
209
-
210
- for (var attrName in event.EventAttributes) {
211
- if (event.EventAttributes.hasOwnProperty(attrName)) {
212
- hash = KitFilterHelper.hashEventAttributeKey(
213
- event.EventCategory,
214
- event.EventName,
215
- attrName
216
- );
217
-
218
- if (mpInstance._Helpers.inArray(filterList, hash)) {
219
- delete event.EventAttributes[attrName];
220
- }
221
- }
222
- }
223
- },
224
- inFilteredList = function(filterList, hash) {
225
- if (filterList && filterList.length) {
226
- if (mpInstance._Helpers.inArray(filterList, hash)) {
227
- return true;
228
- }
229
- }
230
-
231
- return false;
232
- },
233
- forwardingRuleMessageTypes = [
234
- Types.MessageType.PageEvent,
235
- Types.MessageType.PageView,
236
- Types.MessageType.Commerce,
237
- ];
188
+ let clonedEvent;
189
+ let hashedEventName;
190
+ let hashedEventType;
238
191
 
239
192
  if (
240
193
  !mpInstance._Store.webviewBridgeEnabled &&
@@ -249,153 +202,61 @@ export default function Forwarders(mpInstance, kitBlocker) {
249
202
  );
250
203
 
251
204
  for (
252
- var i = 0;
205
+ let i = 0;
253
206
  i < mpInstance._Store.activeForwarders.length;
254
207
  i++
255
208
  ) {
256
- // Check attribute forwarding rule. This rule allows users to only forward an event if a
257
- // specific attribute exists and has a specific value. Alternatively, they can specify
258
- // that an event not be forwarded if the specified attribute name and value exists.
259
- // The two cases are controlled by the "includeOnMatch" boolean value.
260
- // Supported message types for attribute forwarding rules are defined in the forwardingRuleMessageTypes array
209
+ const forwarder = mpInstance._Store.activeForwarders[i];
261
210
 
262
211
  if (
263
- forwardingRuleMessageTypes.indexOf(event.EventDataType) >
264
- -1 &&
265
- mpInstance._Store.activeForwarders[i]
266
- .filteringEventAttributeValue &&
267
- mpInstance._Store.activeForwarders[i]
268
- .filteringEventAttributeValue.eventAttributeName &&
269
- mpInstance._Store.activeForwarders[i]
270
- .filteringEventAttributeValue.eventAttributeValue
212
+ isBlockedByForwardingRule(
213
+ event.EventDataType,
214
+ event.EventAttributes,
215
+ forwarder
216
+ )
271
217
  ) {
272
- var foundProp = null;
273
-
274
- // Attempt to find the attribute in the collection of event attributes
275
- if (event.EventAttributes) {
276
- for (var prop in event.EventAttributes) {
277
- var hashedEventAttributeName;
278
- hashedEventAttributeName = KitFilterHelper.hashAttributeConditionalForwarding(
279
- prop
280
- );
281
-
282
- if (
283
- hashedEventAttributeName ===
284
- mpInstance._Store.activeForwarders[i]
285
- .filteringEventAttributeValue
286
- .eventAttributeName
287
- ) {
288
- foundProp = {
289
- name: hashedEventAttributeName,
290
- value: KitFilterHelper.hashAttributeConditionalForwarding(
291
- event.EventAttributes[prop]
292
- ),
293
- };
294
- }
295
-
296
- if (foundProp) {
297
- break;
298
- }
299
- }
300
- }
301
-
302
- var isMatch =
303
- foundProp !== null &&
304
- foundProp.value ===
305
- mpInstance._Store.activeForwarders[i]
306
- .filteringEventAttributeValue
307
- .eventAttributeValue;
308
-
309
- var shouldInclude =
310
- mpInstance._Store.activeForwarders[i]
311
- .filteringEventAttributeValue.includeOnMatch ===
312
- true
313
- ? isMatch
314
- : !isMatch;
315
-
316
- if (!shouldInclude) {
317
- continue;
318
- }
218
+ continue;
319
219
  }
320
220
 
321
221
  // Clone the event object, as we could be sending different attributes to each forwarder
322
- clonedEvent = {};
323
- clonedEvent = mpInstance._Helpers.extend(
324
- true,
325
- clonedEvent,
326
- event
327
- );
328
- // Check event filtering rules
222
+ clonedEvent = extend(true, {}, event);
223
+
329
224
  if (
330
- event.EventDataType === Types.MessageType.PageEvent &&
331
- (inFilteredList(
332
- mpInstance._Store.activeForwarders[i].eventNameFilters,
333
- hashedEventName
334
- ) ||
335
- inFilteredList(
336
- mpInstance._Store.activeForwarders[i]
337
- .eventTypeFilters,
338
- hashedEventType
339
- ))
340
- ) {
341
- continue;
342
- } else if (
343
- event.EventDataType === Types.MessageType.Commerce &&
344
- inFilteredList(
345
- mpInstance._Store.activeForwarders[i].eventTypeFilters,
346
- hashedEventType
347
- )
348
- ) {
349
- continue;
350
- } else if (
351
- event.EventDataType === Types.MessageType.PageView &&
352
- inFilteredList(
353
- mpInstance._Store.activeForwarders[i].screenNameFilters,
354
- hashedEventName
225
+ isBlockedByEventFilter(
226
+ event.EventDataType,
227
+ hashedEventName,
228
+ hashedEventType,
229
+ forwarder
355
230
  )
356
231
  ) {
357
232
  continue;
358
233
  }
359
234
 
360
- // Check attribute filtering rules
361
- if (clonedEvent.EventAttributes) {
362
- if (event.EventDataType === Types.MessageType.PageEvent) {
363
- filterAttributes(
364
- clonedEvent,
365
- mpInstance._Store.activeForwarders[i]
366
- .attributeFilters
367
- );
368
- } else if (
369
- event.EventDataType === Types.MessageType.PageView
370
- ) {
371
- filterAttributes(
372
- clonedEvent,
373
- mpInstance._Store.activeForwarders[i]
374
- .screenAttributeFilters
375
- );
376
- }
377
- }
235
+ clonedEvent.EventAttributes = filterEventAttributes(
236
+ event.EventDataType,
237
+ event.EventCategory,
238
+ event.EventName,
239
+ clonedEvent.EventAttributes,
240
+ forwarder
241
+ );
378
242
 
379
243
  // Check user identity filtering rules
380
- filterUserIdentities(
381
- clonedEvent,
382
- mpInstance._Store.activeForwarders[i].userIdentityFilters
244
+ clonedEvent.UserIdentities = filterUserIdentities(
245
+ clonedEvent.UserIdentities,
246
+ forwarder.userIdentityFilters
383
247
  );
384
248
 
385
249
  // Check user attribute filtering rules
386
- clonedEvent.UserAttributes = mpInstance._Helpers.filterUserAttributes(
250
+ clonedEvent.UserAttributes = KitFilterHelper.filterUserAttributes(
387
251
  clonedEvent.UserAttributes,
388
- mpInstance._Store.activeForwarders[i].userAttributeFilters
252
+ forwarder.userAttributeFilters
389
253
  );
390
254
 
391
- if (mpInstance._Store.activeForwarders[i].process) {
255
+ if (forwarder.process) {
392
256
  mpInstance.Logger.verbose(
393
- 'Sending message to forwarder: ' +
394
- mpInstance._Store.activeForwarders[i].name
395
- );
396
- var result = mpInstance._Store.activeForwarders[i].process(
397
- clonedEvent
257
+ 'Sending message to forwarder: ' + forwarder.name
398
258
  );
259
+ const result = forwarder.process(clonedEvent);
399
260
 
400
261
  if (result) {
401
262
  mpInstance.Logger.verbose(result);
@@ -405,6 +266,61 @@ export default function Forwarders(mpInstance, kitBlocker) {
405
266
  }
406
267
  };
407
268
 
269
+ this.sendBatchToForwarders = function(batch) {
270
+ if (
271
+ mpInstance._Store.webviewBridgeEnabled ||
272
+ !mpInstance._Store.activeForwarders
273
+ ) {
274
+ return;
275
+ }
276
+
277
+ for (const forwarder of mpInstance._Store.activeForwarders) {
278
+ if (!forwarder.processBatch) {
279
+ continue;
280
+ }
281
+
282
+ try {
283
+ const batchCopy = extend(true, {}, batch);
284
+
285
+ if (batchCopy.events) {
286
+ batchCopy.events = batchCopy.events.filter(function(
287
+ batchEvent
288
+ ) {
289
+ return isBatchEventAllowed(batchEvent, forwarder);
290
+ });
291
+
292
+ batchCopy.events.forEach(function(batchEvent) {
293
+ filterBatchEventAttributes(batchEvent, forwarder);
294
+ });
295
+ }
296
+
297
+ batchCopy.user_identities = filterBatchIdentities(
298
+ batchCopy.user_identities,
299
+ forwarder.userIdentityFilters
300
+ );
301
+
302
+ if (batchCopy.user_attributes) {
303
+ batchCopy.user_attributes = KitFilterHelper.filterUserAttributes(
304
+ batchCopy.user_attributes,
305
+ forwarder.userAttributeFilters
306
+ );
307
+ }
308
+
309
+ if (!batchCopy.events || batchCopy.events.length === 0) {
310
+ continue;
311
+ }
312
+
313
+ const result = forwarder.processBatch(batchCopy);
314
+
315
+ if (result) {
316
+ mpInstance.Logger.verbose(result);
317
+ }
318
+ } catch (e) {
319
+ mpInstance.Logger.verbose(e);
320
+ }
321
+ }
322
+ };
323
+
408
324
  this.handleForwarderUserAttributes = function(functionNameKey, key, value) {
409
325
  if (
410
326
  (kitBlocker && kitBlocker.isAttributeKeyBlocked(key)) ||
@@ -747,7 +663,6 @@ export default function Forwarders(mpInstance, kitBlocker) {
747
663
  config.filteringConsentRuleValues || {};
748
664
  newForwarder.excludeAnonymousUser =
749
665
  config.excludeAnonymousUser || false;
750
-
751
666
  return newForwarder;
752
667
  };
753
668
 
package/src/helpers.js CHANGED
@@ -99,154 +99,7 @@ export default function Helpers(mpInstance) {
99
99
  }
100
100
  };
101
101
 
102
- // https://go.mparticle.com/work/SQDSDKS-6047
103
- // Standalone version of jQuery.extend, from https://github.com/dansdom/extend
104
- this.extend = function() {
105
- var options,
106
- name,
107
- src,
108
- copy,
109
- copyIsArray,
110
- clone,
111
- target = arguments[0] || {},
112
- i = 1,
113
- length = arguments.length,
114
- deep = false,
115
- // helper which replicates the jquery internal functions
116
- objectHelper = {
117
- hasOwn: Object.prototype.hasOwnProperty,
118
- class2type: {},
119
- type: function(obj) {
120
- return obj == null
121
- ? String(obj)
122
- : objectHelper.class2type[
123
- Object.prototype.toString.call(obj)
124
- ] || 'object';
125
- },
126
- isPlainObject: function(obj) {
127
- if (
128
- !obj ||
129
- objectHelper.type(obj) !== 'object' ||
130
- obj.nodeType ||
131
- objectHelper.isWindow(obj)
132
- ) {
133
- return false;
134
- }
135
-
136
- try {
137
- if (
138
- obj.constructor &&
139
- !objectHelper.hasOwn.call(obj, 'constructor') &&
140
- !objectHelper.hasOwn.call(
141
- obj.constructor.prototype,
142
- 'isPrototypeOf'
143
- )
144
- ) {
145
- return false;
146
- }
147
- } catch (e) {
148
- return false;
149
- }
150
-
151
- var key;
152
- for (key in obj) {
153
- } // eslint-disable-line no-empty
154
-
155
- return (
156
- key === undefined || objectHelper.hasOwn.call(obj, key)
157
- );
158
- },
159
- isArray:
160
- Array.isArray ||
161
- function(obj) {
162
- return objectHelper.type(obj) === 'array';
163
- },
164
- isFunction: function(obj) {
165
- return objectHelper.type(obj) === 'function';
166
- },
167
- isWindow: function(obj) {
168
- return obj != null && obj == obj.window;
169
- },
170
- }; // end of objectHelper
171
-
172
- // Handle a deep copy situation
173
- if (typeof target === 'boolean') {
174
- deep = target;
175
- target = arguments[1] || {};
176
- // skip the boolean and the target
177
- i = 2;
178
- }
179
-
180
- // Handle case when target is a string or something (possible in deep copy)
181
- if (typeof target !== 'object' && !objectHelper.isFunction(target)) {
182
- target = {};
183
- }
184
-
185
- // If no second argument is used then this can extend an object that is using this method
186
- if (length === i) {
187
- target = this;
188
- --i;
189
- }
190
-
191
- for (; i < length; i++) {
192
- // Only deal with non-null/undefined values
193
- if ((options = arguments[i]) != null) {
194
- // Extend the base object
195
- for (name in options) {
196
- // Prevent prototype pollution
197
- // https://github.com/advisories/GHSA-jf85-cpcp-j695
198
- if (
199
- name === '__proto__' ||
200
- name === 'constructor' ||
201
- name === 'prototype'
202
- ) {
203
- continue;
204
- }
205
-
206
- // Only copy own properties
207
- if (!Object.prototype.hasOwnProperty.call(options, name)) {
208
- continue;
209
- }
210
-
211
- src = target[name];
212
- copy = options[name];
213
-
214
- // Prevent never-ending loop
215
- if (target === copy) {
216
- continue;
217
- }
218
-
219
- // Recurse if we're merging plain objects or arrays
220
- if (
221
- deep &&
222
- copy &&
223
- (objectHelper.isPlainObject(copy) ||
224
- (copyIsArray = objectHelper.isArray(copy)))
225
- ) {
226
- if (copyIsArray) {
227
- copyIsArray = false;
228
- clone = src && objectHelper.isArray(src) ? src : [];
229
- } else {
230
- clone =
231
- src && objectHelper.isPlainObject(src)
232
- ? src
233
- : {};
234
- }
235
-
236
- // Never move original objects, clone them
237
- target[name] = self.extend(deep, clone, copy);
238
-
239
- // Don't bring in undefined values
240
- } else if (copy !== undefined) {
241
- target[name] = copy;
242
- }
243
- }
244
- }
245
- }
246
-
247
- // Return the modified object
248
- return target;
249
- };
102
+ this.extend = utils.extend;
250
103
 
251
104
  this.createServiceUrl = function(secureServiceUrl, devToken) {
252
105
  var serviceScheme =
@@ -5,6 +5,7 @@ import { SDKEvent, BaseEvent } from './sdkRuntimeModels';
5
5
  import { convertEvents } from './sdkToEventsApiConverter';
6
6
  import { Batch } from '@mparticle/event-models';
7
7
  import { IMParticleWebSDKInstance } from './mp-instance';
8
+ import { extend } from './utils';
8
9
 
9
10
  const mockFunction = function() {
10
11
  return null;
@@ -22,7 +23,7 @@ export default class _BatchValidator {
22
23
  generateUniqueId: function() {
23
24
  return 'mockId';
24
25
  },
25
- extend: (window.mParticle.getInstance() as unknown as IMParticleWebSDKInstance)._Helpers.extend,
26
+ extend,
26
27
  createServiceUrl: mockFunction,
27
28
  parseNumber: mockFunction,
28
29
  isObject: mockFunction,
@@ -36,7 +36,7 @@ import Consent, { IConsent } from './consent';
36
36
  import KitBlocker from './kitBlocking';
37
37
  import ConfigAPIClient, { IKitConfigs } from './configAPIClient';
38
38
  import IdentityAPIClient from './identityApiClient';
39
- import { isFunction, parseConfig, valueof, generateDeprecationMessage } from './utils';
39
+ import { isFunction, parseConfig, valueof, generateDeprecationMessage, extend } from './utils';
40
40
  import { DisabledVault, LocalStorageVault } from './vault';
41
41
  import { removeExpiredIdentityCacheDates, hasExplicitIdentifier } from './identity-utils';
42
42
  import IntegrationCapture from './integrationCapture';
@@ -228,7 +228,7 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan
228
228
  );
229
229
 
230
230
  configApiClient.getSDKConfiguration().then(result => {
231
- const mergedConfig = this._Helpers.extend(
231
+ const mergedConfig = extend(
232
232
  {},
233
233
  config,
234
234
  result
@@ -49,7 +49,7 @@ export default function _Persistence(mpInstance) {
49
49
  if (localStorageData) {
50
50
  if (cookies) {
51
51
  // https://go.mparticle.com/work/SQDSDKS-6047
52
- allData = mpInstance._Helpers.extend(
52
+ allData = Utils.extend(
53
53
  false,
54
54
  localStorageData,
55
55
  cookies
@@ -68,7 +68,7 @@ export default function _Persistence(mpInstance) {
68
68
  if (cookies) {
69
69
  if (localStorageData) {
70
70
  // https://go.mparticle.com/work/SQDSDKS-6047
71
- allData = mpInstance._Helpers.extend(
71
+ allData = Utils.extend(
72
72
  false,
73
73
  localStorageData,
74
74
  cookies
@@ -255,7 +255,7 @@ export default function _Persistence(mpInstance) {
255
255
  }
256
256
 
257
257
  if (Object.keys(mpInstance._Store.nonCurrentUserMPIDs).length) {
258
- localStorageData = mpInstance._Helpers.extend(
258
+ localStorageData = Utils.extend(
259
259
  {},
260
260
  localStorageData,
261
261
  mpInstance._Store.nonCurrentUserMPIDs
@@ -451,7 +451,7 @@ export default function _Persistence(mpInstance) {
451
451
  cookies = setGlobalStorageAttributes(cookies);
452
452
 
453
453
  if (Object.keys(mpInstance._Store.nonCurrentUserMPIDs).length) {
454
- cookies = mpInstance._Helpers.extend(
454
+ cookies = Utils.extend(
455
455
  {},
456
456
  cookies,
457
457
  mpInstance._Store.nonCurrentUserMPIDs
@@ -305,27 +305,39 @@ export default class RoktManager {
305
305
  }
306
306
 
307
307
  if (!isEmpty(newIdentities)) {
308
- // Call identify with the new user identities
308
+ // Fire-and-forget identify — best-effort, does not block selectPlacements
309
309
  try {
310
- await new Promise<void>((resolve, reject) => {
311
- this.identityService.identify({
310
+ this.identityService.identify(
311
+ {
312
312
  userIdentities: {
313
313
  ...currentUserIdentities,
314
- ...newIdentities
314
+ ...newIdentities,
315
+ },
316
+ },
317
+ (result) => {
318
+ const httpCode = Number(result?.httpCode);
319
+ if (httpCode && (httpCode >= 400 || httpCode < 0)) {
320
+ this.logger.error(
321
+ 'Background identify failed with HTTP ' + httpCode
322
+ );
315
323
  }
316
- }, () => {
317
- resolve();
318
- });
319
- });
324
+ // Drain any selectPlacements calls that were deferred while
325
+ // identify was in-flight. By the time this callback fires,
326
+ // identityCallInFlight has already been reset to false.
327
+ this.processMessageQueue();
328
+ }
329
+ );
320
330
  } catch (error) {
321
331
  const errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
322
- this.logger.error('Failed to identify user with updated identities: ' + errorMessage);
332
+ this.logger.error('Background identify threw an error: ' + errorMessage);
323
333
  }
324
334
  }
325
-
326
- // Refresh current user identities to ensure we have the latest values before building enrichedAttributes
327
- this.currentUser = this.identityService.getCurrentUser();
328
- const finalUserIdentities = this.currentUser?.getUserIdentities()?.userIdentities || {};
335
+
336
+ // Optimistic merge: apply the pending identity changes locally so
337
+ // enrichedAttributes are correct without waiting for the identify round-trip.
338
+ // The next call to selectPlacements re-fetches currentUser (see line above),
339
+ // so stale identity state does not persist beyond this invocation.
340
+ const finalUserIdentities = { ...currentUserIdentities, ...newIdentities };
329
341
 
330
342
  this.setUserAttributes(mappedAttributes);
331
343