@mparticle/web-sdk 3.6.2 → 3.8.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.
@@ -127,6 +127,13 @@ interface IPageSnapshot {
127
127
  interface IPageViewData extends IPageSnapshot {
128
128
  hostname: string;
129
129
  title: string;
130
+ isAutoPageView?: boolean;
131
+ }
132
+
133
+ // Options for the emitters the AutoLogPageView flag drives. Passing this is how
134
+ // an emitter is told it is automatic; it never reads the feature flag itself.
135
+ export interface IPageViewOptions {
136
+ isAutoPageView?: boolean;
130
137
  }
131
138
 
132
139
  interface IPendingNavigation {
@@ -147,6 +154,16 @@ interface IPendingNavigation {
147
154
  export const allowedQueryParams = (href: string): Dictionary<string> =>
148
155
  queryStringParser(href, ALLOWED_QUERY_PARAMS);
149
156
 
157
+ export const AUTO_PAGE_VIEW_ATTRIBUTE = 'is_auto_page_view';
158
+
159
+ // Absent rather than false on a manual page view, so the attribute is only ever
160
+ // added by the automatic emitters. Shared by both of them so the key and that
161
+ // rule have one definition instead of two that can drift.
162
+ export const autoPageViewAttribute = (
163
+ isAutoPageView?: boolean
164
+ ): Dictionary<boolean> =>
165
+ isAutoPageView ? { [AUTO_PAGE_VIEW_ATTRIBUTE]: true } : {};
166
+
150
167
  // The captured params, in allowlist order. Ordering comes from the constant
151
168
  // rather than a sort: it is deterministic without needing a comparator, and it
152
169
  // does not depend on the object's insertion order, so reordering the query string
@@ -200,6 +217,7 @@ export const buildPageViewEvent = ({
200
217
  hostname,
201
218
  title,
202
219
  path,
220
+ isAutoPageView,
203
221
  }: IPageViewData): BaseEvent => ({
204
222
  messageType: MessageType.PageView,
205
223
  name: 'PageView',
@@ -211,6 +229,7 @@ export const buildPageViewEvent = ({
211
229
  hostname,
212
230
  title,
213
231
  path,
232
+ ...autoPageViewAttribute(isAutoPageView),
214
233
  },
215
234
  eventType: EventType.Unknown,
216
235
  });
@@ -411,8 +430,14 @@ export class PageViewTracker {
411
430
  private undoHistoryPatch: (() => void) | null = null;
412
431
  private popStateListener: (() => void) | null = null;
413
432
 
414
- constructor(mpInstance: IMParticleWebSDKInstance) {
433
+ private readonly isAutoPageView: boolean;
434
+
435
+ constructor(
436
+ mpInstance: IMParticleWebSDKInstance,
437
+ options?: IPageViewOptions
438
+ ) {
415
439
  this.mpInstance = mpInstance;
440
+ this.isAutoPageView = !!(options && options.isAutoPageView);
416
441
  }
417
442
 
418
443
  // True while this tracker owns the history patch and is listening for
@@ -589,6 +614,7 @@ export class PageViewTracker {
589
614
  ...page,
590
615
  hostname: window.location.hostname,
591
616
  title,
617
+ isAutoPageView: this.isAutoPageView,
592
618
  });
593
619
 
594
620
  this.log(
@@ -1023,7 +1023,7 @@ export default function _Persistence(
1023
1023
  if (csd) {
1024
1024
  let persistence = self.getPersistence();
1025
1025
  if (persistence) {
1026
- if (persistence[mpid]) {
1026
+ if (mpInstance._Helpers.isObject(persistence[mpid])) {
1027
1027
  persistence[mpid].csd = csd;
1028
1028
  } else {
1029
1029
  persistence[mpid] = {
@@ -1140,7 +1140,7 @@ export default function _Persistence(
1140
1140
  }
1141
1141
  let persistence = self.getPersistence();
1142
1142
  if (persistence) {
1143
- if (!persistence[mpid]) {
1143
+ if (!mpInstance._Helpers.isObject(persistence[mpid])) {
1144
1144
  persistence[mpid] = {};
1145
1145
  }
1146
1146
  if (!persistence[mpid].fst) {
@@ -1180,7 +1180,7 @@ export default function _Persistence(
1180
1180
  time = new Date().getTime();
1181
1181
  }
1182
1182
  let persistence = self.getPersistence();
1183
- if (persistence && persistence[mpid]) {
1183
+ if (persistence && mpInstance._Helpers.isObject(persistence[mpid])) {
1184
1184
  persistence[mpid].lst = time;
1185
1185
  self.savePersistence(persistence);
1186
1186
  }
package/src/store.ts CHANGED
@@ -533,7 +533,7 @@ export default function Store(
533
533
  this.syncPersistenceData();
534
534
 
535
535
  if (this.persistenceData) {
536
- if (this.persistenceData[mpid]) {
536
+ if (isObject(this.persistenceData[mpid])) {
537
537
  this.persistenceData[mpid][key] = value;
538
538
  } else {
539
539
  this.persistenceData[mpid] = {
package/src/types.ts CHANGED
@@ -558,6 +558,20 @@ export function getEventCategoryFromCustomEventType(
558
558
  }
559
559
  }
560
560
 
561
+ // A stored `ui` key is the decimal string of an IdentityType; anything else was
562
+ // not written by this SDK. The round-trip comparison is load bearing: parseNumber
563
+ // returns 0 for any non-numeric key and IdentityType.Other is 0, so after parsing
564
+ // alone a stored "hasOwnProperty" is indistinguishable from a genuine Other entry.
565
+ export function getIdentityTypeFromStoredKey(key: string): number | null {
566
+ const identityType = parseNumber(key);
567
+
568
+ if (String(identityType) !== key || !IdentityType.isValid(identityType)) {
569
+ return null;
570
+ }
571
+
572
+ return identityType;
573
+ }
574
+
561
575
  export function getIdentityTypeFromBatchKey(
562
576
  key: string
563
577
  ): number {
package/src/utils.ts CHANGED
@@ -44,7 +44,7 @@ const findKeyInObject = (obj: any, key: string): string => {
44
44
  if (key && obj) {
45
45
  for (var prop in obj) {
46
46
  if (
47
- obj.hasOwnProperty(prop) &&
47
+ hasOwnProp(obj, prop) &&
48
48
  prop.toLowerCase() === key.toLowerCase()
49
49
  ) {
50
50
  return prop;
@@ -246,8 +246,13 @@ const WEB_SAFE_BASE64_REPLACEMENTS: Dictionary<string> = {
246
246
  '=': '',
247
247
  };
248
248
 
249
- const toWebSafeBase64 = (value: string): string =>
250
- btoa(value).replace(/[+/=]/g, c => WEB_SAFE_BASE64_REPLACEMENTS[c]);
249
+ // btoa is defined only over Latin-1 and throws InvalidCharacterError above U+00FF
250
+ const isLatin1 = (value: string): boolean => !/[^\u0000-\u00ff]/.test(value);
251
+
252
+ const toWebSafeBase64 = (value: string): string | undefined =>
253
+ isLatin1(value)
254
+ ? btoa(value).replace(/[+/=]/g, c => WEB_SAFE_BASE64_REPLACEMENTS[c])
255
+ : undefined;
251
256
 
252
257
  // FIXME: REFACTOR for V3
253
258
  // only used in store.js to sanitize server-side formatting of
@@ -316,9 +321,26 @@ const moveElementToEnd = <T>(array: T[], index: number): T[] =>
316
321
  // does not compile under this project's `lib: ["es5", "es6", "dom"]`; widening that
317
322
  // would emit a call absent from every browser before 2021, and this same file still
318
323
  // carries a fallback for browsers with no URLSearchParams.
319
- const hasOwnProp = (obj: object, key: string): boolean =>
324
+ const hasOwnProp = (obj: object, key: PropertyKey): boolean =>
320
325
  Object.prototype.hasOwnProperty.call(obj, key);
321
326
 
327
+ // Assigning `__proto__` replaces the target's prototype instead of adding an own
328
+ // property; `constructor` is reserved alongside it by the stored-record contract.
329
+ // `prototype` is deliberately absent: isValidKeyValue accepts it, so setUserAttribute
330
+ // persists it and the getters must hand it back.
331
+ const isReservedStoredPropertyName = (name: string): boolean =>
332
+ name === '__proto__' || name === 'constructor';
333
+
334
+ // extend's historical set, which additionally refuses `prototype` when merging.
335
+ const isUncopyableMergeName = (name: string): boolean =>
336
+ isReservedStoredPropertyName(name) || name === 'prototype';
337
+
338
+ // Kit-facing copies stay compatible with conventional own-property iteration, such as
339
+ // attributes.hasOwnProperty(key), which is the one read pattern an attribute of that name
340
+ // interferes with. The public getters keep the name; only this filter drops it.
341
+ const isReservedKitPropertyName = (name: string): boolean =>
342
+ isReservedStoredPropertyName(name) || name === 'hasOwnProperty';
343
+
322
344
  const queryStringParser = (
323
345
  url: string,
324
346
  keys: string[] = []
@@ -446,7 +468,7 @@ const filterDictionaryWithHash = <T>(
446
468
 
447
469
  if (!isEmpty(dictionary)) {
448
470
  for (const key in dictionary) {
449
- if (dictionary.hasOwnProperty(key)) {
471
+ if (hasOwnProp(dictionary, key) && !isReservedKitPropertyName(key)) {
450
472
  const hashedKey = hashFn(key);
451
473
  if (!inArray(filterList, hashedKey)) {
452
474
  filtered[key] = dictionary[key];
@@ -612,11 +634,7 @@ function extend(...args: any[]): any {
612
634
  for (; i < length; i++) {
613
635
  if ((options = args[i]) != null) {
614
636
  for (name in options) {
615
- if (
616
- name === '__proto__' ||
617
- name === 'constructor' ||
618
- name === 'prototype'
619
- ) {
637
+ if (isUncopyableMergeName(name)) {
620
638
  continue;
621
639
  }
622
640
 
@@ -721,6 +739,7 @@ export {
721
739
  moveElementToEnd,
722
740
  queryStringParser,
723
741
  hasOwnProp,
742
+ isReservedStoredPropertyName,
724
743
  getCookies,
725
744
  getHref,
726
745
  replaceMPID,