@mparticle/web-sdk 2.32.2 → 2.32.4

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,21 +1,21 @@
1
1
  import { AllUserAttributes, MPID, Product, User } from '@mparticle/web-sdk';
2
2
  import { SDKIdentityTypeEnum } from './identity.interfaces';
3
- import { MessageType } from './types.interfaces';
4
- import { BaseEvent } from './sdkRuntimeModels';
3
+ import { MessageType } from './types';
4
+ import { BaseEvent, SDKProduct } from './sdkRuntimeModels';
5
5
  import Constants from './constants';
6
6
  const { HTTPCodes } = Constants;
7
7
 
8
8
  // Cart is Deprecated and private to mParticle user in @mparticle/web-sdk
9
9
  // but we need to expose it here for type safety in some of our tests
10
- interface Cart {
10
+ interface ICart {
11
11
  /**
12
12
  * @deprecated Cart persistence in mParticle has been deprecated. Please use mParticle.eCommerce.logProductAction(mParticle.ProductActionType.AddToCart, [products])
13
13
  */
14
- add: (product: Product, logEventBoolean?: boolean) => void;
14
+ add: (product: SDKProduct, logEventBoolean?: boolean) => void;
15
15
  /**
16
16
  * @deprecated Cart persistence in mParticle has been deprecated. Please use mParticle.eCommerce.logProductAction(mParticle.ProductActionType.RemoveFromCart, [products])
17
17
  */
18
- remove: (product: Product, logEventBoolean?: boolean) => void;
18
+ remove: (product: SDKProduct, logEventBoolean?: boolean) => void;
19
19
  /**
20
20
  * @deprecated Cart persistence in mParticle has been deprecated.
21
21
  */
@@ -23,7 +23,7 @@ interface Cart {
23
23
  /**
24
24
  * @deprecated Cart Products have been deprecated
25
25
  */
26
- getCartProducts: () => Product[];
26
+ getCartProducts: () => SDKProduct[];
27
27
  }
28
28
 
29
29
  // https://go.mparticle.com/work/SQDSDKS-5033
@@ -36,7 +36,7 @@ export interface IMParticleUser extends User {
36
36
  /*
37
37
  * @deprecated
38
38
  */
39
- getCart(): Cart;
39
+ getCart(): ICart;
40
40
  }
41
41
 
42
42
  export interface ISDKUserIdentity {
@@ -58,7 +58,7 @@ export interface ISDKUserIdentityChangeData {
58
58
  }
59
59
 
60
60
  export interface IUserIdentityChangeEvent extends BaseEvent {
61
- messageType: MessageType.UserIdentityChange;
61
+ messageType: typeof MessageType.UserIdentityChange;
62
62
  userIdentityChanges: ISDKUserIdentityChanges;
63
63
  }
64
64
 
@@ -75,7 +75,7 @@ export interface ISDKUserAttributeChangeData {
75
75
  }
76
76
 
77
77
  export interface IUserAttributeChangeEvent extends BaseEvent {
78
- messageType: MessageType.UserAttributeChange;
78
+ messageType: typeof MessageType.UserAttributeChange;
79
79
  userAttributeChanges: ISDKUserAttributeChangeData;
80
80
  }
81
81
 
@@ -115,10 +115,10 @@ export interface IdentityModifyResultBody {
115
115
  }
116
116
 
117
117
  export interface mParticleUserCart {
118
- add(product: Product, logEvent: boolean): void;
119
- remove(product: Product, logEvent: boolean): void;
118
+ add(product: SDKProduct | SDKProduct[], logEvent: boolean): void;
119
+ remove(product: SDKProduct | SDKProduct[], logEvent: boolean): void;
120
120
  clear(): void;
121
- getCartProducts(): Product[];
121
+ getCartProducts(): SDKProduct[];
122
122
  }
123
123
 
124
124
  // https://go.mparticle.com/work/SQDSDKS-5196
package/src/identity.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import Constants, { HTTP_OK } from './constants';
2
- import Types from './types';
2
+ import Types, { IdentityType } from './types';
3
3
  import {
4
4
  cacheOrClearIdCache,
5
5
  createKnownIdentities,
@@ -17,7 +17,6 @@ import {
17
17
  isObject,
18
18
  } from './utils';
19
19
  import { hasMPIDAndUserLoginChanged, hasMPIDChanged } from './user-utils';
20
- import { getNewIdentitiesByName } from './type-utils';
21
20
  import { processReadyQueue } from './pre-init-utils';
22
21
 
23
22
  export default function Identity(mpInstance) {
@@ -1619,7 +1618,7 @@ export default function Identity(mpInstance) {
1619
1618
  self.setForwarderCallbacks(newUser, method);
1620
1619
  }
1621
1620
 
1622
- const newIdentitiesByName = getNewIdentitiesByName(
1621
+ const newIdentitiesByName = IdentityType.getNewIdentitiesByName(
1623
1622
  newIdentitiesByType
1624
1623
  );
1625
1624
 
@@ -1,4 +1,4 @@
1
- import Constants, { HTTP_ACCEPTED, HTTP_OK } from './constants';
1
+ import Constants, { HTTP_ACCEPTED, HTTP_BAD_REQUEST, HTTP_OK } from './constants';
2
2
  import {
3
3
  AsyncUploader,
4
4
  FetchUploader,
@@ -6,7 +6,7 @@ import {
6
6
  IFetchPayload,
7
7
  } from './uploaders';
8
8
  import { CACHE_HEADER } from './identity-utils';
9
- import { parseNumber } from './utils';
9
+ import { parseNumber, valueof } from './utils';
10
10
  import {
11
11
  IAliasCallback,
12
12
  IAliasRequest,
@@ -15,7 +15,6 @@ import {
15
15
  IIdentityAPIRequestData,
16
16
  } from './identity.interfaces';
17
17
  import {
18
- Callback,
19
18
  IdentityApiData,
20
19
  MPID,
21
20
  UserIdentities,
@@ -25,7 +24,7 @@ import {
25
24
  IdentityResultBody,
26
25
  IIdentityResponse,
27
26
  } from './identity-user-interfaces';
28
- import { MParticleWebSDK } from './sdkRuntimeModels';
27
+ import { IMParticleWebSDKInstance } from './mp-instance';
29
28
 
30
29
  const { HTTPCodes, Messages, IdentityMethods } = Constants;
31
30
 
@@ -53,9 +52,8 @@ export interface IIdentityApiClient {
53
52
  getIdentityResponseFromXHR: (response: XMLHttpRequest) => IIdentityResponse;
54
53
  }
55
54
 
56
- export interface IAliasResponseBody {
57
- message?: string;
58
- }
55
+ // A successfull Alias request will return a 202 with no body
56
+ export interface IAliasResponseBody {}
59
57
 
60
58
  interface IdentityApiRequestPayload extends IFetchPayload {
61
59
  headers: {
@@ -65,9 +63,26 @@ interface IdentityApiRequestPayload extends IFetchPayload {
65
63
  };
66
64
  }
67
65
 
66
+ type HTTP_STATUS_CODES = typeof HTTP_OK | typeof HTTP_ACCEPTED;
67
+
68
+ interface IdentityApiError {
69
+ code: string;
70
+ message: string;
71
+ }
72
+
73
+ interface IdentityApiErrorResponse {
74
+ Errors: IdentityApiError[],
75
+ ErrorCode: string,
76
+ StatusCode: valueof<HTTP_STATUS_CODES>;
77
+ RequestId: string;
78
+ }
79
+
80
+ // All Identity Api Responses have the same structure, except for Alias
81
+ interface IAliasErrorResponse extends IdentityApiError {}
82
+
68
83
  export default function IdentityAPIClient(
69
84
  this: IIdentityApiClient,
70
- mpInstance: MParticleWebSDK
85
+ mpInstance: IMParticleWebSDKInstance
71
86
  ) {
72
87
  this.sendAliasRequest = async function(
73
88
  aliasRequest: IAliasRequest,
@@ -99,55 +114,72 @@ export default function IdentityAPIClient(
99
114
  try {
100
115
  const response: Response = await uploader.upload(uploadPayload);
101
116
 
102
- let message: string;
103
117
  let aliasResponseBody: IAliasResponseBody;
104
-
105
- // FetchUploader returns the response as a JSON object that we have to await
106
- if (response.json) {
107
- // HTTP responses of 202, 200, and 403 do not have a response. response.json will always exist on a fetch, but can only be await-ed when the response is not empty, otherwise it will throw an error.
108
- try {
109
- aliasResponseBody = await response.json();
110
- } catch (e) {
111
- verbose('The request has no response body');
112
- }
113
- } else {
114
- // https://go.mparticle.com/work/SQDSDKS-6568
115
- // XHRUploader returns the response as a string that we need to parse
116
- const xhrResponse = (response as unknown) as XMLHttpRequest;
117
-
118
- aliasResponseBody = xhrResponse.responseText
119
- ? JSON.parse(xhrResponse.responseText)
120
- : '';
121
- }
122
-
118
+ let message: string;
123
119
  let errorMessage: string;
124
120
 
125
121
  switch (response.status) {
126
- case HTTP_OK:
122
+ // A successfull Alias request will return without a body
127
123
  case HTTP_ACCEPTED:
124
+ case HTTP_OK:
128
125
  // https://go.mparticle.com/work/SQDSDKS-6670
129
- message =
130
- 'Successfully sent forwarding stats to mParticle Servers';
126
+ message = 'Received Alias Response from server: ' + JSON.stringify(response.status);
131
127
  break;
132
- default:
133
- // 400 has an error message, but 403 doesn't
134
- if (aliasResponseBody?.message) {
135
- errorMessage = aliasResponseBody.message;
128
+
129
+ // Our Alias Request API will 400 if there is an issue with the request body (ie timestamps are too far
130
+ // in the past or MPIDs don't exist).
131
+ // A 400 will return an error in the response body and will go through the happy path to report the error
132
+ case HTTP_BAD_REQUEST:
133
+ // response.json will always exist on a fetch, but can only be await-ed when the
134
+ // response is not empty, otherwise it will throw an error.
135
+ if (response.json) {
136
+ try {
137
+ aliasResponseBody = await response.json();
138
+ } catch (e) {
139
+ verbose('The request has no response body');
140
+ }
141
+ } else {
142
+ // https://go.mparticle.com/work/SQDSDKS-6568
143
+ // XHRUploader returns the response as a string that we need to parse
144
+ const xhrResponse = (response as unknown) as XMLHttpRequest;
145
+
146
+ aliasResponseBody = xhrResponse.responseText
147
+ ? JSON.parse(xhrResponse.responseText)
148
+ : '';
136
149
  }
150
+
151
+ const errorResponse: IAliasErrorResponse = aliasResponseBody as unknown as IAliasErrorResponse;
152
+
153
+ if (errorResponse?.message) {
154
+ errorMessage = errorResponse.message;
155
+ }
156
+
137
157
  message =
138
158
  'Issue with sending Alias Request to mParticle Servers, received HTTP Code of ' +
139
159
  response.status;
160
+
161
+ if (errorResponse?.code) {
162
+ message += ' - ' + errorResponse.code;
163
+ }
164
+
165
+ break;
166
+
167
+ // Any unhandled errors, such as 500 or 429, will be caught here as well
168
+ default: {
169
+ throw new Error('Received HTTP Code of ' + response.status);
170
+ }
171
+
140
172
  }
141
173
 
142
174
  verbose(message);
143
175
  invokeAliasCallback(aliasCallback, response.status, errorMessage);
144
176
  } catch (e) {
145
- const err = e as Error;
146
- error('Error sending alias request to mParticle servers. ' + err);
177
+ const errorMessage = (e as Error).message || e.toString();
178
+ error('Error sending alias request to mParticle servers. ' + errorMessage);
147
179
  invokeAliasCallback(
148
180
  aliasCallback,
149
181
  HTTPCodes.noHttpCoverage,
150
- err.message
182
+ errorMessage,
151
183
  );
152
184
  }
153
185
  };
@@ -197,33 +229,67 @@ export default function IdentityAPIClient(
197
229
  },
198
230
  body: JSON.stringify(identityApiRequest),
199
231
  };
232
+ mpInstance._Store.identityCallInFlight = true;
200
233
 
201
234
  try {
202
- mpInstance._Store.identityCallInFlight = true;
203
235
  const response: Response = await uploader.upload(fetchPayload);
204
236
 
205
237
  let identityResponse: IIdentityResponse;
238
+ let message: string;
239
+
240
+ switch (response.status) {
241
+ case HTTP_ACCEPTED:
242
+ case HTTP_OK:
243
+
244
+ // Our Identity API will return a 400 error if there is an issue with the requeest body
245
+ // such as if the body is empty or one of the attributes is missing or malformed
246
+ // A 400 will return an error in the response body and will go through the happy path to report the error
247
+ case HTTP_BAD_REQUEST:
248
+
249
+ // FetchUploader returns the response as a JSON object that we have to await
250
+ if (response.json) {
251
+ // https://go.mparticle.com/work/SQDSDKS-6568
252
+ // FetchUploader returns the response as a JSON object that we have to await
253
+ const responseBody: IdentityResultBody = await response.json();
254
+
255
+ identityResponse = this.getIdentityResponseFromFetch(
256
+ response,
257
+ responseBody
258
+ );
259
+ } else {
260
+ identityResponse = this.getIdentityResponseFromXHR(
261
+ (response as unknown) as XMLHttpRequest
262
+ );
263
+ }
206
264
 
207
- if (response.json) {
208
- // https://go.mparticle.com/work/SQDSDKS-6568
209
- // FetchUploader returns the response as a JSON object that we have to await
210
- const responseBody: IdentityResultBody = await response.json();
211
-
212
- identityResponse = this.getIdentityResponseFromFetch(
213
- response,
214
- responseBody
215
- );
216
- } else {
217
- identityResponse = this.getIdentityResponseFromXHR(
218
- (response as unknown) as XMLHttpRequest
219
- );
265
+ if (identityResponse.status === HTTP_BAD_REQUEST) {
266
+ const errorResponse: IdentityApiErrorResponse = identityResponse.responseText as unknown as IdentityApiErrorResponse;
267
+ message = 'Issue with sending Identity Request to mParticle Servers, received HTTP Code of ' + identityResponse.status;
268
+
269
+ if (errorResponse?.Errors) {
270
+ const errorMessage = errorResponse.Errors.map((error) => error.message).join(', ');
271
+ message += ' - ' + errorMessage;
272
+ }
273
+
274
+ } else {
275
+ message = 'Received Identity Response from server: ';
276
+ message += JSON.stringify(identityResponse.responseText);
277
+ }
278
+
279
+ break;
280
+
281
+ // Our Identity API will return:
282
+ // - 401 if the `x-mp-key` is incorrect or missing
283
+ // - 403 if the there is a permission or account issue related to the `x-mp-key`
284
+ // 401 and 403 have no response bodies and should be rejected outright
285
+ default: {
286
+ throw new Error('Received HTTP Code of ' + response.status);
287
+ }
220
288
  }
221
289
 
222
- verbose(
223
- 'Received Identity Response from server: ' +
224
- JSON.stringify(identityResponse.responseText)
225
- );
290
+ mpInstance._Store.identityCallInFlight = false;
226
291
 
292
+ verbose(message);
227
293
  parseIdentityResponse(
228
294
  identityResponse,
229
295
  previousMPID,
@@ -234,15 +300,16 @@ export default function IdentityAPIClient(
234
300
  false
235
301
  );
236
302
  } catch (err) {
303
+ mpInstance._Store.identityCallInFlight = false;
304
+
237
305
  const errorMessage = (err as Error).message || err.toString();
238
306
 
239
- mpInstance._Store.identityCallInFlight = false;
307
+ error('Error sending identity request to servers' + ' - ' + errorMessage);
240
308
  invokeCallback(
241
309
  callback,
242
310
  HTTPCodes.noHttpCoverage,
243
311
  errorMessage,
244
312
  );
245
- error('Error sending identity request to servers' + ' - ' + err);
246
313
  }
247
314
  };
248
315
 
@@ -190,10 +190,13 @@ export default class IntegrationCapture {
190
190
  return mappedClickIds;
191
191
  }
192
192
 
193
- for (const [key, value] of Object.entries(clickIds)) {
194
- const mappedKey = mappingList[key]?.mappedKey;
195
- if (!isEmpty(mappedKey)) {
196
- mappedClickIds[mappedKey] = value;
193
+ for (const key in clickIds) {
194
+ if (clickIds.hasOwnProperty(key)) {
195
+ const value = clickIds[key];
196
+ const mappedKey = mappingList[key]?.mappedKey;
197
+ if (!isEmpty(mappedKey)) {
198
+ mappedClickIds[mappedKey] = value;
199
+ }
197
200
  }
198
201
  }
199
202
 
@@ -207,12 +210,11 @@ export default class IntegrationCapture {
207
210
  ): Dictionary<string> {
208
211
  const processedClickIds: Dictionary<string> = {};
209
212
 
210
- for (const [key, value] of Object.entries(clickIds)) {
211
- const processor = integrationMapping[key]?.processor;
212
- if (processor) {
213
- processedClickIds[key] = processor(value, url, timestamp);
214
- } else {
215
- processedClickIds[key] = value;
213
+ for (const key in clickIds) {
214
+ if (clickIds.hasOwnProperty(key)) {
215
+ const value = clickIds[key];
216
+ const processor = integrationMapping[key]?.processor;
217
+ processedClickIds[key] = processor ? processor(value, url, timestamp) : value;
216
218
  }
217
219
  }
218
220
 
@@ -222,10 +224,12 @@ export default class IntegrationCapture {
222
224
  private filterMappings(
223
225
  outputType: valueof<typeof IntegrationOutputs>
224
226
  ): IntegrationIdMapping {
225
- return Object.fromEntries(
226
- Object.entries(integrationMapping).filter(
227
- ([, value]) => value.output === outputType
228
- )
229
- );
227
+ const filteredMappings: IntegrationIdMapping = {};
228
+ for (const key in integrationMapping) {
229
+ if (integrationMapping[key].output === outputType) {
230
+ filteredMappings[key] = integrationMapping[key];
231
+ }
232
+ }
233
+ return filteredMappings;
230
234
  }
231
235
  }
@@ -3,6 +3,7 @@ import { SDKEvent, MParticleWebSDK, KitBlockerDataPlan, SDKProduct } from './sdk
3
3
  import { BaseEvent, EventTypeEnum, CommerceEvent, ScreenViewEvent, CustomEvent } from '@mparticle/event-models';
4
4
  import Types from './types'
5
5
  import { DataPlanPoint } from '@mparticle/data-planning-models';
6
+ import { IMParticleWebSDKInstance } from './mp-instance';
6
7
 
7
8
  /*
8
9
  TODO: Including this as a workaround because attempting to import it from
@@ -41,9 +42,9 @@ export default class KitBlocker {
41
42
  blockUserAttributes = false;
42
43
  blockUserIdentities = false;
43
44
  kitBlockingEnabled = false;
44
- mpInstance: MParticleWebSDK;
45
+ mpInstance: IMParticleWebSDKInstance;
45
46
 
46
- constructor(dataPlan: KitBlockerDataPlan, mpInstance: MParticleWebSDK) {
47
+ constructor(dataPlan: KitBlockerDataPlan, mpInstance: IMParticleWebSDKInstance) {
47
48
  // if data plan is not requested, the data plan is {document: null}
48
49
  if (dataPlan && !dataPlan.document) {
49
50
  this.kitBlockingEnabled = false;
@@ -1,18 +1,17 @@
1
- import { generateHash } from "./utils";
1
+ import { generateHash, valueof } from "./utils";
2
2
  // TODO: https://mparticle-eng.atlassian.net/browse/SQDSDKS-5381
3
- import { EventTypeEnum, IdentityType } from "./types.interfaces";
4
- import Constants from './constants';
3
+ import { EventType, IdentityType } from "./types";
5
4
 
6
5
  export default class KitFilterHelper {
7
- static hashEventType(eventType: EventTypeEnum): number {
6
+ static hashEventType(eventType: valueof<typeof EventType>): number {
8
7
  return generateHash(eventType as unknown as string);
9
8
  };
10
9
 
11
- static hashEventName(eventName: string, eventType: EventTypeEnum): number {
10
+ static hashEventName(eventName: string, eventType: valueof<typeof EventType>): number {
12
11
  return generateHash(eventType + eventName);
13
12
  };
14
13
 
15
- static hashEventAttributeKey(eventType: EventTypeEnum, eventName: string, customAttributeName: string): number {
14
+ static hashEventAttributeKey(eventType: valueof<typeof EventType>, eventName: string, customAttributeName: string): number {
16
15
  return generateHash(eventType + eventName + customAttributeName);
17
16
  }
18
17
 
@@ -22,7 +21,7 @@ export default class KitFilterHelper {
22
21
 
23
22
  // User Identities are not actually hashed, this method is named this way to
24
23
  // be consistent with the filter class. UserIdentityType is also a number
25
- static hashUserIdentity(userIdentity: IdentityType): IdentityType {
24
+ static hashUserIdentity(userIdentity: typeof IdentityType): typeof IdentityType {
26
25
  return userIdentity;
27
26
  }
28
27
 
@@ -1,11 +1,10 @@
1
1
  // This file is used ONLY for the mParticle ESLint plugin. It should NOT be used otherwise!
2
2
 
3
3
  import ServerModel from './serverModel';
4
- import { SDKEvent, BaseEvent, MParticleWebSDK } from './sdkRuntimeModels';
4
+ import { SDKEvent, BaseEvent } from './sdkRuntimeModels';
5
5
  import { convertEvents } from './sdkToEventsApiConverter';
6
- import * as EventsApi from '@mparticle/event-models';
7
6
  import { Batch } from '@mparticle/event-models';
8
- import { IMPSideloadedKit } from './sideloadedKit';
7
+ import { IMParticleWebSDKInstance } from './mp-instance';
9
8
 
10
9
  const mockFunction = function() {
11
10
  return null;
@@ -15,7 +14,7 @@ export default class _BatchValidator {
15
14
  return ({
16
15
  // Certain Helper, Store, and Identity properties need to be mocked to be used in the `returnBatch` method
17
16
  _Helpers: {
18
- sanitizeAttributes: window.mParticle.getInstance()._Helpers
17
+ sanitizeAttributes: (window.mParticle.getInstance() as unknown as IMParticleWebSDKInstance)._Helpers
19
18
  .sanitizeAttributes,
20
19
  generateHash: function() {
21
20
  return 'mockHash';
@@ -23,7 +22,7 @@ export default class _BatchValidator {
23
22
  generateUniqueId: function() {
24
23
  return 'mockId';
25
24
  },
26
- extend: window.mParticle.getInstance()._Helpers.extend,
25
+ extend: (window.mParticle.getInstance() as unknown as IMParticleWebSDKInstance)._Helpers.extend,
27
26
  createServiceUrl: mockFunction,
28
27
  parseNumber: mockFunction,
29
28
  isObject: mockFunction,
@@ -118,7 +117,7 @@ export default class _BatchValidator {
118
117
  logLevel: 'none',
119
118
  setPosition: mockFunction,
120
119
  upload: mockFunction,
121
- } as unknown) as MParticleWebSDK;
120
+ } as unknown) as IMParticleWebSDKInstance;
122
121
  }
123
122
 
124
123
  private createSDKEventFunction(event): SDKEvent {