@mparticle/web-sdk 2.19.0 → 2.19.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mparticle/web-sdk",
3
- "version": "2.19.0",
3
+ "version": "2.19.2",
4
4
  "description": "mParticle core SDK for web applications",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
package/src/apiClient.ts CHANGED
@@ -189,7 +189,7 @@ export default function APIClient(
189
189
  );
190
190
  xhr.send(
191
191
  JSON.stringify(
192
- mpInstance._ServerModel.convertEventToDTO(
192
+ mpInstance._ServerModel.convertEventToV2DTO(
193
193
  event as IUploadObject
194
194
  )
195
195
  )
@@ -9,35 +9,17 @@ import { convertEvents } from './sdkToEventsApiConverter';
9
9
  import Types from './types';
10
10
  import { isEmpty } from './utils';
11
11
 
12
- /**
13
- * BatchUploader contains all the logic to upload batches to mParticle.
14
- * It queues events as they come in and at set intervals turns them into batches.
15
- * It then attempts to upload them to mParticle.
16
- *
17
- * These uploads happen on an interval basis using window.fetch or XHR
18
- * requests, depending on what is available in the browser.
19
- *
20
- * Uploads can also be triggered on browser visibility/focus changes via an
21
- * event listener, which then uploads to mPartice via the browser's Beacon API.
22
- */
23
-
24
12
  export class BatchUploader {
25
- // We upload JSON, but this content type is required to avoid a CORS preflight request
13
+ //we upload JSON, but this content type is required to avoid a CORS preflight request
26
14
  static readonly CONTENT_TYPE: string = 'text/plain;charset=UTF-8';
27
15
  static readonly MINIMUM_INTERVAL_MILLIS: number = 500;
28
16
  uploadIntervalMillis: number;
29
- eventsQueuedForProcessing: SDKEvent[];
30
- batchesQueuedForProcessing: Batch[];
17
+ pendingEvents: SDKEvent[];
18
+ pendingUploads: Batch[];
31
19
  mpInstance: MParticleWebSDK;
32
20
  uploadUrl: string;
33
21
  batchingEnabled: boolean;
34
- private uploader: AsyncUploader;
35
22
 
36
- /**
37
- * Creates an instance of a BatchUploader
38
- * @param {MParticleWebSDK} mpInstance - the mParticle SDK instance
39
- * @param {number} uploadInterval - the desired upload interval in milliseconds
40
- */
41
23
  constructor(mpInstance: MParticleWebSDK, uploadInterval: number) {
42
24
  this.mpInstance = mpInstance;
43
25
  this.uploadIntervalMillis = uploadInterval;
@@ -46,8 +28,8 @@ export class BatchUploader {
46
28
  if (this.uploadIntervalMillis < BatchUploader.MINIMUM_INTERVAL_MILLIS) {
47
29
  this.uploadIntervalMillis = BatchUploader.MINIMUM_INTERVAL_MILLIS;
48
30
  }
49
- this.eventsQueuedForProcessing = [];
50
- this.batchesQueuedForProcessing = [];
31
+ this.pendingEvents = [];
32
+ this.pendingUploads = [];
51
33
 
52
34
  const { SDKConfig, devToken } = this.mpInstance._Store;
53
35
  const baseUrl = this.mpInstance._Helpers.createServiceUrl(
@@ -56,20 +38,15 @@ export class BatchUploader {
56
38
  );
57
39
  this.uploadUrl = `${baseUrl}/events`;
58
40
 
59
- this.uploader = window.fetch
60
- ? new FetchUploader(this.uploadUrl)
61
- : new XHRUploader(this.uploadUrl);
62
-
63
- this.triggerUploadInterval(true, false);
41
+ setTimeout(() => {
42
+ this.prepareAndUpload(true, false);
43
+ }, this.uploadIntervalMillis);
64
44
  this.addEventListeners();
65
45
  }
66
46
 
67
- // Adds listeners to be used trigger Navigator.sendBeacon if the browser
68
- // loses focus for any reason, such as closing browser tab or minimizing window
69
47
  private addEventListeners() {
70
48
  const _this = this;
71
49
 
72
- // visibility change is a document property, not window
73
50
  document.addEventListener('visibilitychange', () => {
74
51
  _this.prepareAndUpload(false, _this.isBeaconAvailable());
75
52
  });
@@ -88,32 +65,16 @@ export class BatchUploader {
88
65
  return false;
89
66
  }
90
67
 
91
- // Triggers a setTimeout for prepareAndUpload
92
- private triggerUploadInterval(
93
- triggerFuture: boolean = false,
94
- useBeacon: boolean = false
95
- ): void {
96
- setTimeout(() => {
97
- this.prepareAndUpload(triggerFuture, useBeacon);
98
- }, this.uploadIntervalMillis);
99
- }
100
-
101
- /**
102
- * This method will queue a single Event which will eventually be processed into a Batch
103
- * @param event event that should be queued
104
- */
105
68
  queueEvent(event: SDKEvent): void {
106
69
  if (!isEmpty(event)) {
107
- this.eventsQueuedForProcessing.push(event);
70
+ this.pendingEvents.push(event);
108
71
  this.mpInstance.Logger.verbose(
109
72
  `Queuing event: ${JSON.stringify(event)}`
110
73
  );
111
74
  this.mpInstance.Logger.verbose(
112
- `Queued event count: ${this.eventsQueuedForProcessing.length}`
75
+ `Queued event count: ${this.pendingEvents.length}`
113
76
  );
114
77
 
115
- // TODO: Remove this check once the v2 code path is removed
116
- // https://go.mparticle.com/work/SQDSDKS-3720
117
78
  if (
118
79
  !this.batchingEnabled ||
119
80
  Types.TriggerUploadType[event.EventDataType]
@@ -132,7 +93,7 @@ export class BatchUploader {
132
93
  * @param sdkEvents current pending events
133
94
  * @param defaultUser the user to reference for events that are missing data
134
95
  */
135
- private static createNewBatches(
96
+ private static createNewUploads(
136
97
  sdkEvents: SDKEvent[],
137
98
  defaultUser: MParticleUser,
138
99
  mpInstance: MParticleWebSDK
@@ -180,8 +141,9 @@ export class BatchUploader {
180
141
  mpInstance._Store.SDKConfig.onCreateBatch;
181
142
 
182
143
  if (onCreateBatchCallback) {
183
- uploadBatchObject =
184
- onCreateBatchCallback(uploadBatchObject);
144
+ uploadBatchObject = onCreateBatchCallback(
145
+ uploadBatchObject
146
+ );
185
147
  if (uploadBatchObject) {
186
148
  uploadBatchObject.modified = true;
187
149
  } else {
@@ -207,122 +169,112 @@ export class BatchUploader {
207
169
  * @param triggerFuture whether to trigger the loop again - for manual/forced uploads this should be false
208
170
  * @param useBeacon whether to use the beacon API - used when the page is being unloaded
209
171
  */
210
- private async prepareAndUpload(
211
- triggerFuture: boolean,
212
- useBeacon: boolean
213
- ): Promise<void> {
172
+ private async prepareAndUpload(triggerFuture: boolean, useBeacon: boolean) {
214
173
  const currentUser = this.mpInstance.Identity.getCurrentUser();
215
174
 
216
- const currentEvents = this.eventsQueuedForProcessing;
217
- this.eventsQueuedForProcessing = [];
218
-
219
- const newBatches = BatchUploader.createNewBatches(
175
+ const currentEvents = this.pendingEvents;
176
+ this.pendingEvents = [];
177
+ const newUploads = BatchUploader.createNewUploads(
220
178
  currentEvents,
221
179
  currentUser,
222
180
  this.mpInstance
223
181
  );
224
- if (!isEmpty(newBatches)) {
225
- this.batchesQueuedForProcessing.push(...newBatches);
182
+ if (newUploads && newUploads.length) {
183
+ this.pendingUploads.push(...newUploads);
226
184
  }
227
185
 
228
- const batchesToUpload = this.batchesQueuedForProcessing;
229
- const batchesThatDidNotUpload: Batch[] = [];
230
- this.batchesQueuedForProcessing = [];
231
-
232
- // Create an array of promises as we try to upload each batch indvidually
233
- const promises: Promise<Batch>[] = batchesToUpload.map((upload) => {
234
- return this.upload(this.mpInstance.Logger, upload, useBeacon);
235
- });
236
-
237
- // Iterate through fulfilled promises and store any remaining batches
238
- // for future re-transmission attempts
239
- if (!isEmpty(promises)) {
240
- Promise.all(promises)
241
- .then((batchResponses) => {
242
- batchResponses.forEach((batch) =>
243
- !isEmpty(batch)
244
- ? batchesThatDidNotUpload.push(batch)
245
- : null
246
- );
247
- })
248
- .catch((error) => {
249
- this.mpInstance.Logger.error(
250
- `Error processing batches during upload attempt: ${error}`
251
- );
252
- })
253
- .finally(() => {
254
- // Any batches that did not upload should be put back into the queue for processing
255
- if (!isEmpty(batchesThatDidNotUpload)) {
256
- this.batchesQueuedForProcessing.unshift(
257
- ...batchesThatDidNotUpload
258
- );
259
- }
260
- });
186
+ const currentUploads = this.pendingUploads;
187
+ this.pendingUploads = [];
188
+ const remainingUploads: Batch[] = await this.upload(
189
+ this.mpInstance.Logger,
190
+ currentUploads,
191
+ useBeacon
192
+ );
193
+ if (remainingUploads && remainingUploads.length) {
194
+ this.pendingUploads.unshift(...remainingUploads);
261
195
  }
262
196
 
263
197
  if (triggerFuture) {
264
- this.triggerUploadInterval(triggerFuture, false);
198
+ setTimeout(() => {
199
+ this.prepareAndUpload(true, false);
200
+ }, this.uploadIntervalMillis);
265
201
  }
266
202
  }
267
203
 
268
204
  private async upload(
269
205
  logger: SDKLoggerApi,
270
- batch: Batch,
206
+ _uploads: Batch[],
271
207
  useBeacon: boolean
272
- ): Promise<Batch | null> {
273
- if (isEmpty(batch) || isEmpty(batch.events)) {
208
+ ): Promise<Batch[]> {
209
+ let uploader;
210
+
211
+ // Filter out any batches that don't have events
212
+ const uploads = _uploads.filter(upload => !isEmpty(upload.events));
213
+
214
+ if (isEmpty(uploads)) {
274
215
  return null;
275
216
  }
276
217
 
277
- logger.verbose(`Uploading batches: ${JSON.stringify(batch)}`);
278
-
279
- const fetchPayload: fetchPayload = {
280
- method: 'POST',
281
- headers: {
282
- Accept: BatchUploader.CONTENT_TYPE,
283
- 'Content-Type': 'text/plain;charset=UTF-8',
284
- },
285
- body: JSON.stringify(batch),
286
- };
287
-
288
- // TODO: Make beacon its own function
289
- // beacon is only used on onbeforeunload onpagehide events
290
- if (useBeacon && this.isBeaconAvailable()) {
291
- let blob = new Blob([fetchPayload.body], {
292
- type: 'text/plain;charset=UTF-8',
293
- });
294
- navigator.sendBeacon(this.uploadUrl, blob);
295
- } else {
296
- try {
297
- const response = await this.uploader.upload(fetchPayload);
298
-
299
- // TODO: Should we make this a switch statement instead?
300
- if (response.status >= 200 && response.status < 300) {
301
- logger.verbose(
302
- `Upload success for request ID: ${batch.source_request_id}`
303
- );
218
+ logger.verbose(`Uploading batches: ${JSON.stringify(uploads)}`);
219
+ logger.verbose(`Batch count: ${uploads.length}`);
220
+
221
+ for (let i = 0; i < uploads.length; i++) {
222
+ const fetchPayload: fetchPayload = {
223
+ method: 'POST',
224
+ headers: {
225
+ Accept: BatchUploader.CONTENT_TYPE,
226
+ 'Content-Type': 'text/plain;charset=UTF-8',
227
+ },
228
+ body: JSON.stringify(uploads[i]),
229
+ };
304
230
 
305
- return null;
306
- } else if (response.status >= 500 || response.status === 429) {
307
- logger.error(
308
- `HTTP error status ${response.status} received`
231
+ // beacon is only used on onbeforeunload onpagehide events
232
+ if (useBeacon && this.isBeaconAvailable()) {
233
+ let blob = new Blob([fetchPayload.body], {
234
+ type: 'text/plain;charset=UTF-8',
235
+ });
236
+ navigator.sendBeacon(this.uploadUrl, blob);
237
+ } else {
238
+ if (!uploader) {
239
+ if (window.fetch) {
240
+ uploader = new FetchUploader(this.uploadUrl, logger);
241
+ } else {
242
+ uploader = new XHRUploader(this.uploadUrl, logger);
243
+ }
244
+ }
245
+ try {
246
+ const response = await uploader.upload(
247
+ fetchPayload,
248
+ uploads,
249
+ i
309
250
  );
310
251
 
311
- // Server error, return current batch and try again later
312
- return batch;
313
- } else if (response.status >= 401) {
252
+ if (response.status >= 200 && response.status < 300) {
253
+ logger.verbose(
254
+ `Upload success for request ID: ${uploads[i].source_request_id}`
255
+ );
256
+ } else if (
257
+ response.status >= 500 ||
258
+ response.status === 429
259
+ ) {
260
+ logger.error(
261
+ `HTTP error status ${response.status} received`
262
+ );
263
+ //server error, add back current events and try again later
264
+ return uploads.slice(i, uploads.length);
265
+ } else if (response.status >= 401) {
266
+ logger.error(
267
+ `HTTP error status ${response.status} while uploading - please verify your API key.`
268
+ );
269
+ //if we're getting a 401, assume we'll keep getting a 401 and clear the uploads.
270
+ return null;
271
+ }
272
+ } catch (e) {
314
273
  logger.error(
315
- `HTTP error status ${response.status} while uploading - please verify your API key.`
274
+ `Error sending event to mParticle servers. ${e}`
316
275
  );
317
- // if we're getting a 401, assume we'll keep getting a 401
318
- // so return the upload so it can be stored for later use
319
- return batch;
276
+ return uploads.slice(i, uploads.length);
320
277
  }
321
- } catch (error) {
322
- logger.error(
323
- `Error sending event to mParticle servers. ${error}`
324
- );
325
- return batch;
326
278
  }
327
279
  }
328
280
  return null;
@@ -331,24 +283,34 @@ export class BatchUploader {
331
283
 
332
284
  abstract class AsyncUploader {
333
285
  url: string;
334
- public abstract upload(fetchPayload: fetchPayload): Promise<XHRResponse>;
286
+ logger: SDKLoggerApi;
335
287
 
336
- constructor(url: string) {
288
+ constructor(url: string, logger: SDKLoggerApi) {
337
289
  this.url = url;
290
+ this.logger = logger;
338
291
  }
339
292
  }
340
293
 
341
294
  class FetchUploader extends AsyncUploader {
342
- public async upload(fetchPayload: fetchPayload): Promise<XHRResponse> {
295
+ private async upload(
296
+ fetchPayload: fetchPayload,
297
+ uploads: Batch[],
298
+ i: number
299
+ ) {
343
300
  const response: XHRResponse = await fetch(this.url, fetchPayload);
344
301
  return response;
345
302
  }
346
303
  }
347
304
 
348
305
  class XHRUploader extends AsyncUploader {
349
- public async upload(fetchPayload: fetchPayload): Promise<XHRResponse> {
306
+ private async upload(
307
+ fetchPayload: fetchPayload,
308
+ uploads: Batch[],
309
+ i: number
310
+ ) {
350
311
  const response: XHRResponse = await this.makeRequest(
351
312
  this.url,
313
+ this.logger,
352
314
  fetchPayload.body
353
315
  );
354
316
  return response;
@@ -356,6 +318,7 @@ class XHRUploader extends AsyncUploader {
356
318
 
357
319
  private async makeRequest(
358
320
  url: string,
321
+ logger: SDKLoggerApi,
359
322
  data: string
360
323
  ): Promise<XMLHttpRequest> {
361
324
  const xhr: XMLHttpRequest = new XMLHttpRequest();