@mparticle/web-sdk 2.32.2 → 2.32.3

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,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,
@@ -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,6 +63,23 @@ 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
85
  mpInstance: MParticleWebSDK
@@ -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
 
@@ -222,10 +222,12 @@ export default class IntegrationCapture {
222
222
  private filterMappings(
223
223
  outputType: valueof<typeof IntegrationOutputs>
224
224
  ): IntegrationIdMapping {
225
- return Object.fromEntries(
226
- Object.entries(integrationMapping).filter(
227
- ([, value]) => value.output === outputType
228
- )
229
- );
225
+ const filteredMappings: IntegrationIdMapping = {};
226
+ for (const key in integrationMapping) {
227
+ if (integrationMapping[key].output === outputType) {
228
+ filteredMappings[key] = integrationMapping[key];
229
+ }
230
+ }
231
+ return filteredMappings;
230
232
  }
231
233
  }
@@ -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
 
@@ -212,8 +212,8 @@ export default function mParticleInstance(instanceName) {
212
212
  */
213
213
  this.getEnvironment = function() {
214
214
  return self._Store.SDKConfig.isDevelopmentMode
215
- ? Types.Environment.Development
216
- : Types.Environment.Production;
215
+ ? Constants.Environment.Development
216
+ : Constants.Environment.Production;
217
217
  };
218
218
  /**
219
219
  * Returns the mParticle SDK version number
@@ -824,7 +824,7 @@ export default function mParticleInstance(instanceName) {
824
824
  * @for mParticle.eCommerce
825
825
  * @method logCheckout
826
826
  * @param {Number} step checkout step number
827
- * @param {String} option
827
+ * @param {String} checkout option string
828
828
  * @param {Object} attrs
829
829
  * @param {Object} [customFlags] Custom flags for the event
830
830
  * @deprecated
@@ -4,10 +4,12 @@ import {
4
4
  MPConfiguration,
5
5
  MPID,
6
6
  IdentityApiData,
7
+ SDKEventOptions,
8
+ SDKEventAttrs,
7
9
  } from '@mparticle/web-sdk';
8
10
  import { IStore } from './store';
9
11
  import Validators from './validators';
10
- import { Dictionary } from './utils';
12
+ import { Dictionary, valueof } from './utils';
11
13
  import { IServerModel } from './serverModel';
12
14
  import { IKitConfigs } from './configAPIClient';
13
15
  import { SDKConsentApi, SDKConsentState } from './consent';
@@ -28,10 +30,17 @@ import {
28
30
  IdentityCallback,
29
31
  ISDKUserAttributes,
30
32
  } from './identity-user-interfaces';
31
- import { IIdentityType } from './types.interfaces';
33
+ import {
34
+ CommerceEventType,
35
+ EventType,
36
+ IdentityType,
37
+ PromotionActionType,
38
+ } from './types';
32
39
  import IntegrationCapture from './integrationCapture';
33
40
  import { INativeSdkHelpers } from './nativeSdkHelpers.interfaces';
34
41
  import { ICookieSyncManager, IPixelConfiguration } from './cookieSyncManager';
42
+ import { IEvents } from './events.interfaces';
43
+ import { IECommerce, SDKECommerceAPI } from './ecommerce.interfaces';
35
44
 
36
45
  // TODO: Resolve this with version in @mparticle/web-sdk
37
46
  export type SDKEventCustomFlags = Dictionary<any>;
@@ -99,6 +108,11 @@ export interface SDKPromotion {
99
108
  Position?: string;
100
109
  }
101
110
 
111
+ export interface SDKImpression {
112
+ Name: string;
113
+ Product: SDKProduct;
114
+ }
115
+
102
116
  export interface SDKProductImpression {
103
117
  ProductImpressionList?: string;
104
118
  ProductList?: SDKProduct[];
@@ -142,18 +156,18 @@ export interface SDKProduct {
142
156
  Position?: number;
143
157
  CouponCode?: string;
144
158
  TotalAmount?: number;
145
- Attributes?: { [key: string]: string };
146
- }
147
159
 
148
- // Temporary Interfaces for Events Module
149
- interface IEvents {
150
- logEvent?(event: BaseEvent): void;
160
+ // https://go.mparticle.com/work/SQDSDKS-4801
161
+ Attributes?: Record<string, unknown> | null;
151
162
  }
152
163
 
153
164
  export interface MParticleWebSDK {
154
165
  addForwarder(mockForwarder: MPForwarder): void;
155
166
  _IntegrationCapture: IntegrationCapture;
156
- IdentityType: IIdentityType;
167
+ IdentityType: valueof<typeof IdentityType>;
168
+ CommerceEventType: valueof<typeof CommerceEventType>;
169
+ EventType: valueof<typeof EventType>;
170
+ PromotionType: valueof<typeof PromotionActionType>;
157
171
  _Identity: IIdentity;
158
172
  Identity: SDKIdentityApi;
159
173
  Logger: SDKLoggerApi;
@@ -164,6 +178,7 @@ export interface MParticleWebSDK {
164
178
  _Forwarders: any;
165
179
  _Helpers: SDKHelpersApi;
166
180
  _Events: IEvents;
181
+ _Ecommerce: IECommerce;
167
182
  config: SDKInitConfig;
168
183
  _ServerModel: IServerModel;
169
184
  _SessionManager: ISessionManager;
@@ -188,7 +203,7 @@ export interface MParticleWebSDK {
188
203
  getDeviceId(): string;
189
204
  setDeviceId(deviceId: string): void;
190
205
  setSessionAttribute(key: string, value: string): void;
191
- getInstance(): MParticleWebSDK; // TODO: Create a new type for MParticleWebSDKInstance
206
+ getInstance(instanceName?: string): MParticleWebSDK; // https://go.mparticle.com/work/SQDSDKS-4804
192
207
  ServerModel();
193
208
  upload();
194
209
  setLogLevel(logLevel: LogLevelType): void;
@@ -196,16 +211,18 @@ export interface MParticleWebSDK {
196
211
  startNewSession(): void;
197
212
  logEvent(
198
213
  eventName: string,
199
- eventType?: number,
200
- attrs?: { [key: string]: string },
201
- customFlags?: SDKEventCustomFlags
214
+ eventType?: valueof<typeof EventType>,
215
+ attrs?: SDKEventAttrs,
216
+ customFlags?: SDKEventCustomFlags,
217
+ eventOptions?: SDKEventOptions
202
218
  ): void;
203
- logBaseEvent(event: any): void;
204
- eCommerce: any;
219
+ logBaseEvent(event: BaseEvent, eventOptions?: SDKEventOptions): void;
220
+ eCommerce: SDKECommerceAPI;
205
221
  logLevel: string;
206
222
  ProductActionType: SDKProductActionType;
207
223
  generateHash(value: string): string;
208
224
  isIOS?: boolean;
225
+ sessionManager: Pick<ISessionManager, 'getSession'>; // https://go.mparticle.com/work/SQDSDKS-6949
209
226
  }
210
227
 
211
228
  // Used in cases where server requires booleans as strings
@@ -284,6 +301,7 @@ export interface SDKHelpersApi {
284
301
  timeoutStart: number,
285
302
  now: number
286
303
  ): boolean;
304
+ isEventType?(type: valueof<typeof EventType>): boolean;
287
305
  isObject?(item: any);
288
306
  invokeCallback?(
289
307
  callback: IdentityCallback,
@@ -373,7 +373,7 @@ export function convertProducts(
373
373
  price: sdkProduct.Price,
374
374
  quantity: sdkProduct.Quantity,
375
375
  coupon_code: sdkProduct.CouponCode,
376
- custom_attributes: sdkProduct.Attributes,
376
+ custom_attributes: sdkProduct.Attributes as Record<string, string>,
377
377
  };
378
378
  products.push(product);
379
379
  }
@@ -110,7 +110,7 @@ export interface IProductV2DTO {
110
110
  ps: number;
111
111
  cc: string | number;
112
112
  tpa: number;
113
- attrs: Record<string, string> | null;
113
+ attrs: Record<string, unknown> | undefined;
114
114
  }
115
115
 
116
116
  export interface IPromotionV2DTO {
@@ -6,22 +6,23 @@ import {
6
6
  IKitFilterSettings,
7
7
  } from './configAPIClient';
8
8
  import { UnregisteredKit } from './forwarders.interfaces';
9
- import { EventTypeEnum, IdentityType } from './types.interfaces';
9
+ import { EventType, IdentityType } from './types';
10
+ import { valueof } from './utils';
10
11
 
11
12
  export interface IMPSideloadedKit {
12
13
  kitInstance: UnregisteredKit;
13
14
  filterDictionary: IKitFilterSettings;
14
15
 
15
- addEventTypeFilter(eventType: EventTypeEnum): void;
16
- addEventNameFilter(eventType: EventTypeEnum, eventName: string): void;
16
+ addEventTypeFilter(eventType: valueof<typeof EventType>): void;
17
+ addEventNameFilter(eventType: valueof<typeof EventType>, eventName: string): void;
17
18
  addEventAttributeFilter(
18
- eventType: EventTypeEnum,
19
+ eventType: valueof<typeof EventType>,
19
20
  eventName: string,
20
21
  customAttributeKey: string
21
22
  ): void;
22
23
  addScreenNameFilter(screenName: string): void;
23
24
  addScreenAttributeFilter(screenName: string, screenAttribute: string): void;
24
- addUserIdentityFilter(userIdentity: IdentityType): void;
25
+ addUserIdentityFilter(userIdentity: typeof IdentityType): void;
25
26
  addUserAttributeFilter(userAttributeKey: string): void;
26
27
  }
27
28
 
@@ -57,13 +58,13 @@ export default class MPSideloadedKit implements IMPSideloadedKit{
57
58
  this.kitInstance = unregisteredKitInstance;
58
59
  }
59
60
 
60
- public addEventTypeFilter(eventType: EventTypeEnum): void {
61
+ public addEventTypeFilter(eventType: valueof<typeof EventType>): void {
61
62
  const hashedEventType = KitFilterHelper.hashEventType(eventType);
62
63
  this.filterDictionary.eventTypeFilters.push(hashedEventType);
63
64
  }
64
65
 
65
66
  public addEventNameFilter(
66
- eventType: EventTypeEnum,
67
+ eventType: valueof<typeof EventType>,
67
68
  eventName: string
68
69
  ): void {
69
70
  const hashedEventName = KitFilterHelper.hashEventName(
@@ -74,7 +75,7 @@ export default class MPSideloadedKit implements IMPSideloadedKit{
74
75
  }
75
76
 
76
77
  public addEventAttributeFilter(
77
- eventType: EventTypeEnum,
78
+ eventType: valueof<typeof EventType>,
78
79
  eventName: string,
79
80
  customAttributeKey: string
80
81
  ): void {
@@ -89,7 +90,7 @@ export default class MPSideloadedKit implements IMPSideloadedKit{
89
90
  public addScreenNameFilter(screenName: string): void {
90
91
  const hashedScreenName = KitFilterHelper.hashEventName(
91
92
  screenName,
92
- EventTypeEnum.Unknown
93
+ EventType.Unknown,
93
94
  );
94
95
  this.filterDictionary.screenNameFilters.push(hashedScreenName);
95
96
  }
@@ -99,7 +100,7 @@ export default class MPSideloadedKit implements IMPSideloadedKit{
99
100
  screenAttribute: string
100
101
  ): void {
101
102
  const hashedScreenAttribute = KitFilterHelper.hashEventAttributeKey(
102
- EventTypeEnum.Unknown,
103
+ EventType.Unknown,
103
104
  screenName,
104
105
  screenAttribute
105
106
  );
@@ -108,7 +109,7 @@ export default class MPSideloadedKit implements IMPSideloadedKit{
108
109
  );
109
110
  }
110
111
 
111
- public addUserIdentityFilter(userIdentity: IdentityType): void {
112
+ public addUserIdentityFilter(userIdentity: typeof IdentityType): void {
112
113
  const hashedIdentityType = KitFilterHelper.hashUserIdentity(
113
114
  userIdentity
114
115
  );