@mparticle/web-sdk 2.18.0 → 2.18.2

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
@@ -7,12 +7,12 @@ import {
7
7
  IdentityCallback,
8
8
  SDKEventCustomFlags,
9
9
  } from '@mparticle/web-sdk';
10
+ import { IKitConfigs } from './configAPIClient';
10
11
  import Constants from './constants';
11
12
  import {
12
13
  DataPlanResult,
13
14
  Kit,
14
15
  KitBlockerOptions,
15
- KitConfigs,
16
16
  LogLevelType,
17
17
  MParticleWebSDK,
18
18
  MPForwarder,
@@ -45,7 +45,7 @@ export interface SDKConfig {
45
45
  appVersion?: string;
46
46
  package?: string;
47
47
  flags?: { [key: string]: string | number | boolean };
48
- kitConfigs: KitConfigs[];
48
+ kitConfigs: IKitConfigs[];
49
49
  kits: Dictionary<Kit>;
50
50
  logLevel?: LogLevelType;
51
51
  cookieDomain?: string;
@@ -102,6 +102,13 @@ export type PixelConfiguration = Dictionary;
102
102
  export type MigrationData = Dictionary;
103
103
  export type ServerSettings = Dictionary;
104
104
 
105
+ type WrapperSDKTypes = 'flutter' | 'none';
106
+ interface WrapperSDKInfo {
107
+ name: WrapperSDKTypes;
108
+ version: string | null;
109
+ isInfoSet: boolean;
110
+ }
111
+
105
112
  // Temporary Interface until Store can be refactored as a class
106
113
  export interface IStore {
107
114
  isEnabled: boolean;
@@ -115,8 +122,8 @@ export interface IStore {
115
122
  devToken: string | null;
116
123
  migrationData: MigrationData;
117
124
  serverSettings: ServerSettings;
118
- dateLastEventSent: number | null;
119
- sessionStartDate: number | null;
125
+ dateLastEventSent: Date;
126
+ sessionStartDate: Date;
120
127
  currentPosition: SDKGeoLocation | null;
121
128
  isTracking: boolean;
122
129
  watchPositionId: number | null;
@@ -143,8 +150,11 @@ export interface IStore {
143
150
  configuredForwarders: MPForwarder[];
144
151
  pixelConfigurations: PixelConfiguration[];
145
152
  integrationDelayTimeoutStart: number; // UNIX Timestamp
153
+ webviewBridgeEnabled?: boolean;
154
+ wrapperSDKInfo: WrapperSDKInfo;
146
155
  }
147
156
 
157
+ // TODO: Merge this with SDKStoreApi in sdkRuntimeModels
148
158
  export default function Store(
149
159
  this: IStore,
150
160
  config: SDKInitConfig,
@@ -189,6 +199,11 @@ export default function Store(
189
199
  kits: {},
190
200
  configuredForwarders: [],
191
201
  pixelConfigurations: [],
202
+ wrapperSDKInfo: {
203
+ name: 'none',
204
+ version: null,
205
+ isInfoSet: false,
206
+ },
192
207
  };
193
208
 
194
209
  for (var key in defaultStore) {
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
  },
@@ -1,67 +0,0 @@
1
- export default function ConfigAPIClient() {
2
- this.getSDKConfiguration = function(
3
- apiKey,
4
- config,
5
- completeSDKInitialization,
6
- mpInstance
7
- ) {
8
- var url;
9
- try {
10
- var xhrCallback = function() {
11
- if (xhr.readyState === 4) {
12
- // when a 200 returns, merge current config with what comes back from config, prioritizing user inputted config
13
- if (xhr.status === 200) {
14
- config = mpInstance._Helpers.extend(
15
- {},
16
- config,
17
- JSON.parse(xhr.responseText)
18
- );
19
- completeSDKInitialization(apiKey, config, mpInstance);
20
- mpInstance.Logger.verbose(
21
- 'Successfully received configuration from server'
22
- );
23
- } else {
24
- // if for some reason a 200 doesn't return, then we initialize with the just the passed through config
25
- completeSDKInitialization(apiKey, config, mpInstance);
26
- mpInstance.Logger.verbose(
27
- 'Issue with receiving configuration from server, received HTTP Code of ' +
28
- xhr.status
29
- );
30
- }
31
- }
32
- };
33
-
34
- var xhr = mpInstance._Helpers.createXHR(xhrCallback);
35
- url =
36
- 'https://' +
37
- mpInstance._Store.SDKConfig.configUrl +
38
- apiKey +
39
- '/config?env=';
40
- if (config.isDevelopmentMode) {
41
- url = url + '1';
42
- } else {
43
- url = url + '0';
44
- }
45
-
46
- var dataPlan = config.dataPlan;
47
- if (dataPlan) {
48
- if (dataPlan.planId) {
49
- url = url + '&plan_id=' + dataPlan.planId || '';
50
- }
51
- if (dataPlan.planVersion) {
52
- url = url + '&plan_version=' + dataPlan.planVersion || '';
53
- }
54
- }
55
-
56
- if (xhr) {
57
- xhr.open('get', url);
58
- xhr.send(null);
59
- }
60
- } catch (e) {
61
- completeSDKInitialization(apiKey, config, mpInstance);
62
- mpInstance.Logger.error(
63
- 'Error getting forwarder configuration from mParticle servers.'
64
- );
65
- }
66
- };
67
- }