@mparticle/web-sdk 2.18.0 → 2.18.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.
@@ -1,47 +1,230 @@
1
+ // TODO: This file is no longer the server model because the web SDK payload
2
+ // is now the server-to-server model. We should rename this to
3
+ // something more appropriate.
4
+
1
5
  import Types from './types';
2
6
  import Constants from './constants';
7
+ import {
8
+ BaseEvent,
9
+ MParticleUser,
10
+ MParticleWebSDK,
11
+ SDKConsentState,
12
+ SDKEvent,
13
+ SDKGeoLocation,
14
+ SDKProduct,
15
+ SDKUserIdentity,
16
+ } from './sdkRuntimeModels';
17
+ import { parseNumber, parseStringOrNumber, Dictionary } from './utils';
18
+ import { ServerSettings } from './store';
19
+ import { MPID } from '@mparticle/web-sdk';
20
+
21
+ const MessageType = Types.MessageType;
22
+ const ApplicationTransitionType = Types.ApplicationTransitionType;
23
+
24
+ // TODO: Move to Consent Module
25
+ export interface IPrivacyV2DTO {
26
+ c: boolean; // Consented
27
+ ts: number; // Timestamp
28
+ d: string; // Document
29
+ l: string; // Location
30
+ h: string; // HardwareID
31
+ }
32
+
33
+ // TODO: Move to Consent Module
34
+ export interface IGDPRConsentStateDTO {
35
+ [key: string]: IPrivacyV2DTO;
36
+ }
3
37
 
4
- var MessageType = Types.MessageType,
5
- ApplicationTransitionType = Types.ApplicationTransitionType;
38
+ // TODO: Move to Consent Module
39
+ export interface ICCPAConsentStateDTO {
40
+ data_sale_opt_out: IPrivacyV2DTO;
41
+ }
6
42
 
7
- export default function ServerModel(mpInstance) {
8
- var self = this;
9
- function convertCustomFlags(event, dto) {
10
- var valueArray = [];
11
- dto.flags = {};
43
+ // TODO: Break this up into GDPR and CCPA interfaces
44
+ export interface IConsentStateDTO {
45
+ gdpr?: IGDPRConsentStateDTO;
46
+ ccpa?: ICCPAConsentStateDTO;
47
+ }
12
48
 
13
- for (var prop in event.CustomFlags) {
14
- valueArray = [];
49
+ // TODO: Confirm which attributes are optional
50
+ export interface IServerDTO {
51
+ id: string | number;
52
+ nm: string | number;
53
+ pr: number;
54
+ qt: number;
55
+ br: string | number;
56
+ va: string | number;
57
+ ca: string | number;
58
+ ps: number;
59
+ cc: string | number;
60
+ attrs: Record<string, string> | null;
61
+ dp_id?: string;
62
+ dp_v?: number;
63
+ n?: string;
64
+ et?: number;
65
+ ua?: Dictionary<string | string[]>;
66
+ ui?: SDKUserIdentity[];
67
+ ia?: Dictionary<Dictionary<string>>;
68
+ str?: ServerSettings;
69
+ sdk?: string;
70
+ sid?: string;
71
+ sl?: number;
72
+ ssd?: number;
73
+ dt?: number;
74
+ dbg?: boolean;
75
+ ct?: number;
76
+ lc?: SDKGeoLocation;
77
+ o?: boolean;
78
+ eec?: number;
79
+ av?: string;
80
+ cgid?: string;
81
+ das?: string;
82
+ mpid?: MPID;
83
+ smpids?: MPID[];
84
+ con?: IConsentStateDTO;
85
+ fr?: boolean;
86
+ iu?: boolean; // isUpgrade
87
+ at?: number;
88
+ lr?: string;
89
+ flags?: Dictionary<string[]>;
90
+ cu?: string;
91
+ sc?: {
92
+ pl: IProductDTO[];
93
+ };
94
+ pd?: {
95
+ an: number;
96
+ cs: number;
97
+ co: string;
98
+ pl: IProductDTO[];
99
+ ta: string;
100
+ ti: string;
101
+ tcc: string;
102
+ tr: number;
103
+ ts: number;
104
+ tt: number;
105
+ };
106
+ pm?: {
107
+ an: string;
108
+ pl: IPromotionDTO[];
109
+ };
110
+ pi?: IProductImpressionDTO[];
111
+ pet?: number;
112
+ }
15
113
 
16
- if (event.CustomFlags.hasOwnProperty(prop)) {
17
- if (Array.isArray(event.CustomFlags[prop])) {
18
- event.CustomFlags[prop].forEach(function(
19
- customFlagProperty
114
+ export interface IProductDTO {
115
+ id: string | number;
116
+ nm: string | number;
117
+ pr: number;
118
+ qt: number;
119
+ br: string | number;
120
+ va: string | number;
121
+ ca: string | number;
122
+ ps: number;
123
+ cc: string | number;
124
+ tpa: number;
125
+ attrs: Record<string, string> | null;
126
+ }
127
+
128
+ export interface IPromotionDTO {
129
+ id: string | number;
130
+ nm: string | number;
131
+ ps: string | number;
132
+ cr: string;
133
+ }
134
+
135
+ export interface IProductImpressionDTO {
136
+ pil: string;
137
+ pl: IProductDTO[];
138
+ }
139
+
140
+ export interface IUploadObject extends SDKEvent {
141
+ // TODO: References to `ClientGeneratedId` can be removed when we remove the
142
+ // V2 event upload path because it does not exist in the V3 payload
143
+ ClientGeneratedId: string;
144
+ // FIXME: This reference to Store is misleading and can be removed whenever
145
+ // we deprecate v2 related code
146
+ Store: ServerSettings;
147
+ ExpandedEventCount: number;
148
+ ProfileMessageType: number;
149
+ }
150
+
151
+ export interface IServerModel {
152
+ convertEventToDTO: (event: IUploadObject) => IServerDTO;
153
+ createEventObject: (event: BaseEvent, user?: MParticleUser) => SDKEvent;
154
+ convertToConsentStateDTO: (state: SDKConsentState) => IConsentStateDTO;
155
+ appendUserInfo: (user: MParticleUser, event: SDKEvent) => void;
156
+ }
157
+
158
+ // TODO: Make this a pure function that returns a new object
159
+ function convertCustomFlags(event: SDKEvent, dto: IServerDTO) {
160
+ var valueArray: string[] = [];
161
+ dto.flags = {};
162
+
163
+ for (var prop in event.CustomFlags) {
164
+ valueArray = [];
165
+
166
+ if (event.CustomFlags.hasOwnProperty(prop)) {
167
+ if (Array.isArray(event.CustomFlags[prop])) {
168
+ event.CustomFlags[prop].forEach(function(customFlagProperty) {
169
+ // TODO: Can we use our utility functions here?
170
+ if (
171
+ typeof customFlagProperty === 'number' ||
172
+ typeof customFlagProperty === 'string' ||
173
+ typeof customFlagProperty === 'boolean'
20
174
  ) {
21
- if (
22
- typeof customFlagProperty === 'number' ||
23
- typeof customFlagProperty === 'string' ||
24
- typeof customFlagProperty === 'boolean'
25
- ) {
26
- valueArray.push(customFlagProperty.toString());
27
- }
28
- });
29
- } else if (
30
- typeof event.CustomFlags[prop] === 'number' ||
31
- typeof event.CustomFlags[prop] === 'string' ||
32
- typeof event.CustomFlags[prop] === 'boolean'
33
- ) {
34
- valueArray.push(event.CustomFlags[prop].toString());
35
- }
175
+ valueArray.push(customFlagProperty.toString());
176
+ }
177
+ });
178
+ } else if (
179
+ // TODO: Can we use our utility functions here?
180
+ typeof event.CustomFlags[prop] === 'number' ||
181
+ typeof event.CustomFlags[prop] === 'string' ||
182
+ typeof event.CustomFlags[prop] === 'boolean'
183
+ ) {
184
+ valueArray.push(event.CustomFlags[prop].toString());
185
+ }
36
186
 
37
- if (valueArray.length) {
38
- dto.flags[prop] = valueArray;
39
- }
187
+ if (valueArray.length) {
188
+ dto.flags[prop] = valueArray;
40
189
  }
41
190
  }
42
191
  }
192
+ }
193
+
194
+ function convertProductToDTO(product: SDKProduct): IProductDTO {
195
+ return {
196
+ id: parseStringOrNumber(product.Sku),
197
+ nm: parseStringOrNumber(product.Name),
198
+ pr: parseNumber(product.Price),
199
+ qt: parseNumber(product.Quantity),
200
+ br: parseStringOrNumber(product.Brand),
201
+ va: parseStringOrNumber(product.Variant),
202
+ ca: parseStringOrNumber(product.Category),
203
+ ps: parseNumber(product.Position),
204
+ cc: parseStringOrNumber(product.CouponCode),
205
+ tpa: parseNumber(product.TotalAmount),
206
+ attrs: product.Attributes,
207
+ };
208
+ }
209
+
210
+ function convertProductListToDTO(productList: SDKProduct[]): IProductDTO[] {
211
+ if (!productList) {
212
+ return [];
213
+ }
43
214
 
44
- this.appendUserInfo = function(user, event) {
215
+ return productList.map(function(product) {
216
+ return convertProductToDTO(product);
217
+ });
218
+ }
219
+
220
+ export default function ServerModel(
221
+ this: IServerModel,
222
+ mpInstance: MParticleWebSDK
223
+ ) {
224
+ var self = this;
225
+
226
+ // TODO: Can we refactor this to not mutate the event?
227
+ this.appendUserInfo = function(user: MParticleUser, event: SDKEvent): void {
45
228
  if (!event) {
46
229
  return;
47
230
  }
@@ -72,7 +255,7 @@ export default function ServerModel(mpInstance) {
72
255
  if (mpInstance._Helpers.isObject(dtoUserIdentities)) {
73
256
  if (Object.keys(dtoUserIdentities).length) {
74
257
  for (var key in dtoUserIdentities) {
75
- var userIdentity = {};
258
+ var userIdentity: Partial<SDKUserIdentity> = {};
76
259
  userIdentity.Identity = dtoUserIdentities[key];
77
260
  userIdentity.Type = mpInstance._Helpers.parseNumber(key);
78
261
  validUserIdentities.push(userIdentity);
@@ -82,45 +265,21 @@ export default function ServerModel(mpInstance) {
82
265
  event.UserIdentities = validUserIdentities;
83
266
  };
84
267
 
85
- function convertProductListToDTO(productList) {
86
- if (!productList) {
87
- return [];
88
- }
89
-
90
- return productList.map(function(product) {
91
- return convertProductToDTO(product);
92
- });
93
- }
94
-
95
- function convertProductToDTO(product) {
96
- return {
97
- id: mpInstance._Helpers.parseStringOrNumber(product.Sku),
98
- nm: mpInstance._Helpers.parseStringOrNumber(product.Name),
99
- pr: mpInstance._Helpers.parseNumber(product.Price),
100
- qt: mpInstance._Helpers.parseNumber(product.Quantity),
101
- br: mpInstance._Helpers.parseStringOrNumber(product.Brand),
102
- va: mpInstance._Helpers.parseStringOrNumber(product.Variant),
103
- ca: mpInstance._Helpers.parseStringOrNumber(product.Category),
104
- ps: mpInstance._Helpers.parseNumber(product.Position),
105
- cc: mpInstance._Helpers.parseStringOrNumber(product.CouponCode),
106
- tpa: mpInstance._Helpers.parseNumber(product.TotalAmount),
107
- attrs: product.Attributes,
108
- };
109
- }
110
-
111
- this.convertToConsentStateDTO = function(state) {
268
+ this.convertToConsentStateDTO = function(
269
+ state: SDKConsentState
270
+ ): IConsentStateDTO {
112
271
  if (!state) {
113
272
  return null;
114
273
  }
115
- var jsonObject = {};
274
+ var jsonObject: IConsentStateDTO = {};
116
275
  var gdprConsentState = state.getGDPRConsentState();
117
276
  if (gdprConsentState) {
118
- var gdpr = {};
277
+ var gdpr: IGDPRConsentStateDTO = {};
119
278
  jsonObject.gdpr = gdpr;
120
279
  for (var purpose in gdprConsentState) {
121
280
  if (gdprConsentState.hasOwnProperty(purpose)) {
122
281
  var gdprConsent = gdprConsentState[purpose];
123
- jsonObject.gdpr[purpose] = {};
282
+ jsonObject.gdpr[purpose] = {} as IPrivacyV2DTO;
124
283
  if (typeof gdprConsent.Consented === 'boolean') {
125
284
  gdpr[purpose].c = gdprConsent.Consented;
126
285
  }
@@ -153,28 +312,41 @@ export default function ServerModel(mpInstance) {
153
312
  };
154
313
  }
155
314
 
156
- return jsonObject;
315
+ return jsonObject as IConsentStateDTO;
157
316
  };
158
317
 
159
- this.createEventObject = function(event, user) {
160
- var uploadObject = {};
161
- var eventObject = {};
162
-
318
+ this.createEventObject = function(
319
+ event: BaseEvent,
320
+ user?: MParticleUser
321
+ ): SDKEvent | IUploadObject {
322
+ var uploadObject: Partial<IUploadObject> = {};
323
+ var eventObject: Partial<SDKEvent> = {};
324
+
325
+ // The `optOut` variable is passed later in this method to the `uploadObject`
326
+ // so that it can be used to denote whether or not a user has "opted out" of being
327
+ // tracked. If this is an `optOut` Event, we set `optOut` to the inverse of the SDK's
328
+ // `isEnabled` boolean which is controlled via `MPInstanceManager.setOptOut`.
163
329
  var optOut =
164
330
  event.messageType === Types.MessageType.OptOut
165
331
  ? !mpInstance._Store.isEnabled
166
332
  : null;
167
333
 
334
+ // TODO: Why is Webview Bridge Enabled or Opt Out necessary here?
168
335
  if (
169
336
  mpInstance._Store.sessionId ||
170
- event.messageType == Types.MessageType.OptOut ||
337
+ event.messageType === Types.MessageType.OptOut ||
171
338
  mpInstance._Store.webviewBridgeEnabled
172
339
  ) {
173
340
  if (event.hasOwnProperty('toEventAPIObject')) {
174
341
  eventObject = event.toEventAPIObject();
175
342
  } else {
176
343
  eventObject = {
177
- EventName: event.name || event.messageType,
344
+ // This is an artifact from v2 events where SessionStart/End and AST event
345
+ // names are numbers (1, 2, or 10), but going forward with v3, these lifecycle
346
+ // events do not have names, but are denoted by their `event_type`
347
+ EventName:
348
+ event.name ||
349
+ ((event.messageType as unknown) as string),
178
350
  EventCategory: event.eventType,
179
351
  EventAttributes: mpInstance._Helpers.sanitizeAttributes(
180
352
  event.data,
@@ -190,11 +362,13 @@ export default function ServerModel(mpInstance) {
190
362
  };
191
363
  }
192
364
 
365
+ // TODO: Should we move this side effect outside of this method?
193
366
  if (event.messageType !== Types.MessageType.SessionEnd) {
194
367
  mpInstance._Store.dateLastEventSent = new Date();
195
368
  }
196
369
 
197
370
  uploadObject = {
371
+ // FIXME: Deprecate when we get rid of V2
198
372
  Store: mpInstance._Store.serverSettings,
199
373
  SDKVersion: Constants.sdkVersion,
200
374
  SessionId: mpInstance._Store.sessionId,
@@ -222,9 +396,10 @@ export default function ServerModel(mpInstance) {
222
396
  eventObject.LaunchReferral = window.location.href || null;
223
397
  }
224
398
 
399
+ // FIXME: Remove duplicate occurence
225
400
  eventObject.CurrencyCode = mpInstance._Store.currencyCode;
226
401
  var currentUser = user || mpInstance.Identity.getCurrentUser();
227
- self.appendUserInfo(currentUser, eventObject);
402
+ self.appendUserInfo(currentUser, eventObject as SDKEvent);
228
403
 
229
404
  if (event.messageType === Types.MessageType.SessionEnd) {
230
405
  eventObject.SessionLength =
@@ -232,9 +407,15 @@ export default function ServerModel(mpInstance) {
232
407
  mpInstance._Store.sessionStartDate.getTime();
233
408
  eventObject.currentSessionMPIDs =
234
409
  mpInstance._Store.currentSessionMPIDs;
410
+
411
+ // Session attributes are assigned on a session level, but only uploaded
412
+ // when a session ends. As there is no way to attach event attributes to
413
+ // a `SessionEnd` event, we are uploading the session level attributes
414
+ // as event level attributes in a `SessionEnd` event.
235
415
  eventObject.EventAttributes =
236
416
  mpInstance._Store.sessionAttributes;
237
417
 
418
+ // TODO: We should move this out of here to avoid side effects
238
419
  mpInstance._Store.currentSessionMPIDs = [];
239
420
  mpInstance._Store.sessionStartDate = null;
240
421
  }
@@ -247,8 +428,8 @@ export default function ServerModel(mpInstance) {
247
428
  return null;
248
429
  };
249
430
 
250
- this.convertEventToDTO = function(event) {
251
- var dto = {
431
+ this.convertEventToDTO = function(event: IUploadObject): IServerDTO {
432
+ var dto: Partial<IServerDTO> = {
252
433
  n: event.EventName,
253
434
  et: event.EventCategory,
254
435
  ua: event.UserAttributes,
@@ -290,16 +471,20 @@ export default function ServerModel(mpInstance) {
290
471
  dto.iu = false;
291
472
  dto.at = ApplicationTransitionType.AppInit;
292
473
  dto.lr = event.LaunchReferral;
474
+
475
+ // Nullify Attributes in case AST Was logged manually or
476
+ // via logBaseEvent. AST should not have any attributes
293
477
  dto.attrs = null;
294
478
  }
295
479
 
296
480
  if (event.CustomFlags) {
297
- convertCustomFlags(event, dto);
481
+ convertCustomFlags(event, dto as IServerDTO);
298
482
  }
299
483
 
300
484
  if (event.EventDataType === MessageType.Commerce) {
301
485
  dto.cu = event.CurrencyCode;
302
486
 
487
+ // TODO: If Cart is deprecated, we should deprecate this too
303
488
  if (event.ShoppingCart) {
304
489
  dto.sc = {
305
490
  pl: convertProductListToDTO(event.ShoppingCart.ProductList),
@@ -355,6 +540,6 @@ export default function ServerModel(mpInstance) {
355
540
  dto.pet = event.ProfileMessageType;
356
541
  }
357
542
 
358
- return dto;
543
+ return dto as IServerDTO;
359
544
  };
360
545
  }
package/src/store.ts CHANGED
@@ -115,8 +115,8 @@ export interface IStore {
115
115
  devToken: string | null;
116
116
  migrationData: MigrationData;
117
117
  serverSettings: ServerSettings;
118
- dateLastEventSent: number | null;
119
- sessionStartDate: number | null;
118
+ dateLastEventSent: Date;
119
+ sessionStartDate: Date;
120
120
  currentPosition: SDKGeoLocation | null;
121
121
  isTracking: boolean;
122
122
  watchPositionId: number | null;
@@ -143,8 +143,10 @@ export interface IStore {
143
143
  configuredForwarders: MPForwarder[];
144
144
  pixelConfigurations: PixelConfiguration[];
145
145
  integrationDelayTimeoutStart: number; // UNIX Timestamp
146
+ webviewBridgeEnabled?: boolean;
146
147
  }
147
148
 
149
+ // TODO: Merge this with SDKStoreApi in sdkRuntimeModels
148
150
  export default function Store(
149
151
  this: IStore,
150
152
  config: SDKInitConfig,
package/src/utils.ts CHANGED
@@ -96,6 +96,9 @@ const isDataPlanSlug = (str: string): boolean =>
96
96
  const isStringOrNumber = (value: any): boolean =>
97
97
  isString(value) || isNumber(value);
98
98
 
99
+ const isEmpty = (value: Dictionary<any> | null | undefined): boolean =>
100
+ value == null || !(Object.keys(value) || value).length;
101
+
99
102
  export {
100
103
  valueof,
101
104
  converted,
@@ -111,4 +114,5 @@ export {
111
114
  isNumber,
112
115
  isFunction,
113
116
  isDataPlanSlug,
117
+ isEmpty,
114
118
  };
package/src/validators.ts CHANGED
@@ -17,6 +17,7 @@ const Validators = {
17
17
  isFunction,
18
18
  isStringOrNumber,
19
19
 
20
+ // Validator Functions
20
21
  isValidAttributeValue: function(value: any): boolean {
21
22
  return value !== undefined && !isObject(value) && !Array.isArray(value);
22
23
  },