@mparticle/web-sdk 2.18.2 → 2.19.0
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/dist/mparticle.common.js +30 -12
- package/dist/mparticle.esm.js +30 -12
- package/dist/mparticle.js +318 -280
- package/package.json +1 -1
- package/src/{apiClient.js → apiClient.ts} +60 -20
- package/src/batchUploader.ts +146 -109
- package/src/consent.interfaces.ts +89 -0
- package/src/mockBatchCreator.ts +9 -4
- package/src/sdkRuntimeModels.ts +24 -28
- package/src/sdkToEventsApiConverter.ts +64 -59
- package/src/serverModel.ts +6 -26
- package/src/store.ts +1 -1
- package/src/vault.ts +141 -0
package/package.json
CHANGED
|
@@ -1,15 +1,51 @@
|
|
|
1
1
|
import Constants from './constants';
|
|
2
2
|
import Types from './types';
|
|
3
3
|
import { BatchUploader } from './batchUploader';
|
|
4
|
+
import {
|
|
5
|
+
MParticleUser,
|
|
6
|
+
MParticleWebSDK,
|
|
7
|
+
MPForwarder,
|
|
8
|
+
SDKEvent,
|
|
9
|
+
} from './sdkRuntimeModels';
|
|
10
|
+
import KitBlocker from './kitBlocking';
|
|
11
|
+
import { Dictionary } from './utils';
|
|
12
|
+
import { IUploadObject } from './serverModel';
|
|
4
13
|
|
|
5
|
-
|
|
14
|
+
const Messages = Constants.Messages;
|
|
6
15
|
|
|
7
|
-
export
|
|
16
|
+
export type ForwardingStatsData = Dictionary<any>;
|
|
17
|
+
|
|
18
|
+
export interface IAPIClient {
|
|
19
|
+
uploader: BatchUploader | null;
|
|
20
|
+
queueEventForBatchUpload: (event: SDKEvent) => void;
|
|
21
|
+
shouldEnableBatching: () => boolean;
|
|
22
|
+
processQueuedEvents: () => void;
|
|
23
|
+
appendUserInfoToEvents: (user: MParticleUser, events: SDKEvent[]) => void;
|
|
24
|
+
sendEventToServer: (event: SDKEvent, _options?: Dictionary<any>) => void;
|
|
25
|
+
sendSingleEventToServer: (event: SDKEvent) => void;
|
|
26
|
+
sendBatchForwardingStatsToServer: (
|
|
27
|
+
forwardingStatsData: ForwardingStatsData,
|
|
28
|
+
xhr: XMLHttpRequest
|
|
29
|
+
) => void;
|
|
30
|
+
sendSingleForwardingStatsToServer: (
|
|
31
|
+
forwardingStatsData: ForwardingStatsData
|
|
32
|
+
) => void;
|
|
33
|
+
prepareForwardingStats: (
|
|
34
|
+
forwarder: MPForwarder,
|
|
35
|
+
event: IUploadObject
|
|
36
|
+
) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default function APIClient(
|
|
40
|
+
this: IAPIClient,
|
|
41
|
+
mpInstance: MParticleWebSDK,
|
|
42
|
+
kitBlocker: KitBlocker
|
|
43
|
+
) {
|
|
8
44
|
this.uploader = null;
|
|
9
|
-
|
|
10
|
-
this.queueEventForBatchUpload = function(event) {
|
|
45
|
+
const self = this;
|
|
46
|
+
this.queueEventForBatchUpload = function(event: SDKEvent) {
|
|
11
47
|
if (!this.uploader) {
|
|
12
|
-
|
|
48
|
+
const millis = mpInstance._Helpers.getFeatureFlag(
|
|
13
49
|
Constants.FeatureFlags.EventBatchingIntervalMillis
|
|
14
50
|
);
|
|
15
51
|
this.uploader = new BatchUploader(mpInstance, millis);
|
|
@@ -22,7 +58,7 @@ export default function APIClient(mpInstance, kitBlocker) {
|
|
|
22
58
|
this.shouldEnableBatching = function() {
|
|
23
59
|
// Returns a string of a number that must be parsed
|
|
24
60
|
// Invalid strings will be parsed to NaN which is falsey
|
|
25
|
-
|
|
61
|
+
const eventsV3Percentage = parseInt(
|
|
26
62
|
mpInstance._Helpers.getFeatureFlag(Constants.FeatureFlags.EventsV3),
|
|
27
63
|
10
|
|
28
64
|
);
|
|
@@ -31,20 +67,20 @@ export default function APIClient(mpInstance, kitBlocker) {
|
|
|
31
67
|
return false;
|
|
32
68
|
}
|
|
33
69
|
|
|
34
|
-
|
|
70
|
+
const rampNumber = mpInstance._Helpers.getRampNumber(
|
|
35
71
|
mpInstance._Store.deviceId
|
|
36
72
|
);
|
|
37
73
|
return eventsV3Percentage >= rampNumber;
|
|
38
74
|
};
|
|
39
75
|
|
|
40
76
|
this.processQueuedEvents = function() {
|
|
41
|
-
|
|
77
|
+
let mpid,
|
|
42
78
|
currentUser = mpInstance.Identity.getCurrentUser();
|
|
43
79
|
if (currentUser) {
|
|
44
80
|
mpid = currentUser.getMPID();
|
|
45
81
|
}
|
|
46
82
|
if (mpInstance._Store.eventQueue.length && mpid) {
|
|
47
|
-
|
|
83
|
+
const localQueueCopy = mpInstance._Store.eventQueue;
|
|
48
84
|
mpInstance._Store.eventQueue = [];
|
|
49
85
|
this.appendUserInfoToEvents(currentUser, localQueueCopy);
|
|
50
86
|
localQueueCopy.forEach(function(event) {
|
|
@@ -62,10 +98,10 @@ export default function APIClient(mpInstance, kitBlocker) {
|
|
|
62
98
|
};
|
|
63
99
|
|
|
64
100
|
this.sendEventToServer = function(event, _options) {
|
|
65
|
-
|
|
101
|
+
const defaultOptions = {
|
|
66
102
|
shouldUploadEvent: true,
|
|
67
103
|
};
|
|
68
|
-
|
|
104
|
+
const options = mpInstance._Helpers.extend(defaultOptions, _options);
|
|
69
105
|
|
|
70
106
|
if (mpInstance._Store.webviewBridgeEnabled) {
|
|
71
107
|
mpInstance._NativeSdkHelpers.sendToNative(
|
|
@@ -75,7 +111,7 @@ export default function APIClient(mpInstance, kitBlocker) {
|
|
|
75
111
|
return;
|
|
76
112
|
}
|
|
77
113
|
|
|
78
|
-
|
|
114
|
+
let mpid,
|
|
79
115
|
currentUser = mpInstance.Identity.getCurrentUser();
|
|
80
116
|
if (currentUser) {
|
|
81
117
|
mpid = currentUser.getMPID();
|
|
@@ -126,7 +162,7 @@ export default function APIClient(mpInstance, kitBlocker) {
|
|
|
126
162
|
if (event.EventDataType === Types.MessageType.Media) {
|
|
127
163
|
return;
|
|
128
164
|
}
|
|
129
|
-
|
|
165
|
+
let xhr,
|
|
130
166
|
xhrCallback = function() {
|
|
131
167
|
if (xhr.readyState === 4) {
|
|
132
168
|
mpInstance.Logger.verbose(
|
|
@@ -153,7 +189,9 @@ export default function APIClient(mpInstance, kitBlocker) {
|
|
|
153
189
|
);
|
|
154
190
|
xhr.send(
|
|
155
191
|
JSON.stringify(
|
|
156
|
-
mpInstance._ServerModel.convertEventToDTO(
|
|
192
|
+
mpInstance._ServerModel.convertEventToDTO(
|
|
193
|
+
event as IUploadObject
|
|
194
|
+
)
|
|
157
195
|
)
|
|
158
196
|
);
|
|
159
197
|
} catch (e) {
|
|
@@ -165,7 +203,8 @@ export default function APIClient(mpInstance, kitBlocker) {
|
|
|
165
203
|
};
|
|
166
204
|
|
|
167
205
|
this.sendBatchForwardingStatsToServer = function(forwardingStatsData, xhr) {
|
|
168
|
-
|
|
206
|
+
let url;
|
|
207
|
+
let data;
|
|
169
208
|
try {
|
|
170
209
|
url = mpInstance._Helpers.createServiceUrl(
|
|
171
210
|
mpInstance._Store.SDKConfig.v2SecureServiceUrl,
|
|
@@ -188,9 +227,10 @@ export default function APIClient(mpInstance, kitBlocker) {
|
|
|
188
227
|
};
|
|
189
228
|
|
|
190
229
|
this.sendSingleForwardingStatsToServer = function(forwardingStatsData) {
|
|
191
|
-
|
|
230
|
+
let url;
|
|
231
|
+
let data;
|
|
192
232
|
try {
|
|
193
|
-
|
|
233
|
+
const xhrCallback = function() {
|
|
194
234
|
if (xhr.readyState === 4) {
|
|
195
235
|
if (xhr.status === 202) {
|
|
196
236
|
mpInstance.Logger.verbose(
|
|
@@ -201,7 +241,7 @@ export default function APIClient(mpInstance, kitBlocker) {
|
|
|
201
241
|
}
|
|
202
242
|
}
|
|
203
243
|
};
|
|
204
|
-
|
|
244
|
+
const xhr = mpInstance._Helpers.createXHR(xhrCallback);
|
|
205
245
|
url = mpInstance._Helpers.createServiceUrl(
|
|
206
246
|
mpInstance._Store.SDKConfig.v1SecureServiceUrl,
|
|
207
247
|
mpInstance._Store.devToken
|
|
@@ -220,8 +260,8 @@ export default function APIClient(mpInstance, kitBlocker) {
|
|
|
220
260
|
};
|
|
221
261
|
|
|
222
262
|
this.prepareForwardingStats = function(forwarder, event) {
|
|
223
|
-
|
|
224
|
-
|
|
263
|
+
let forwardingStatsData;
|
|
264
|
+
const queue = mpInstance._Forwarders.getForwarderStatsQueue();
|
|
225
265
|
|
|
226
266
|
if (forwarder && forwarder.isVisible) {
|
|
227
267
|
forwardingStatsData = {
|
package/src/batchUploader.ts
CHANGED
|
@@ -9,17 +9,35 @@ 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
|
+
|
|
12
24
|
export class BatchUploader {
|
|
13
|
-
//
|
|
25
|
+
// We upload JSON, but this content type is required to avoid a CORS preflight request
|
|
14
26
|
static readonly CONTENT_TYPE: string = 'text/plain;charset=UTF-8';
|
|
15
27
|
static readonly MINIMUM_INTERVAL_MILLIS: number = 500;
|
|
16
28
|
uploadIntervalMillis: number;
|
|
17
|
-
|
|
18
|
-
|
|
29
|
+
eventsQueuedForProcessing: SDKEvent[];
|
|
30
|
+
batchesQueuedForProcessing: Batch[];
|
|
19
31
|
mpInstance: MParticleWebSDK;
|
|
20
32
|
uploadUrl: string;
|
|
21
33
|
batchingEnabled: boolean;
|
|
34
|
+
private uploader: AsyncUploader;
|
|
22
35
|
|
|
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
|
+
*/
|
|
23
41
|
constructor(mpInstance: MParticleWebSDK, uploadInterval: number) {
|
|
24
42
|
this.mpInstance = mpInstance;
|
|
25
43
|
this.uploadIntervalMillis = uploadInterval;
|
|
@@ -28,8 +46,8 @@ export class BatchUploader {
|
|
|
28
46
|
if (this.uploadIntervalMillis < BatchUploader.MINIMUM_INTERVAL_MILLIS) {
|
|
29
47
|
this.uploadIntervalMillis = BatchUploader.MINIMUM_INTERVAL_MILLIS;
|
|
30
48
|
}
|
|
31
|
-
this.
|
|
32
|
-
this.
|
|
49
|
+
this.eventsQueuedForProcessing = [];
|
|
50
|
+
this.batchesQueuedForProcessing = [];
|
|
33
51
|
|
|
34
52
|
const { SDKConfig, devToken } = this.mpInstance._Store;
|
|
35
53
|
const baseUrl = this.mpInstance._Helpers.createServiceUrl(
|
|
@@ -38,15 +56,20 @@ export class BatchUploader {
|
|
|
38
56
|
);
|
|
39
57
|
this.uploadUrl = `${baseUrl}/events`;
|
|
40
58
|
|
|
41
|
-
|
|
42
|
-
this.
|
|
43
|
-
|
|
59
|
+
this.uploader = window.fetch
|
|
60
|
+
? new FetchUploader(this.uploadUrl)
|
|
61
|
+
: new XHRUploader(this.uploadUrl);
|
|
62
|
+
|
|
63
|
+
this.triggerUploadInterval(true, false);
|
|
44
64
|
this.addEventListeners();
|
|
45
65
|
}
|
|
46
66
|
|
|
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
|
|
47
69
|
private addEventListeners() {
|
|
48
70
|
const _this = this;
|
|
49
71
|
|
|
72
|
+
// visibility change is a document property, not window
|
|
50
73
|
document.addEventListener('visibilitychange', () => {
|
|
51
74
|
_this.prepareAndUpload(false, _this.isBeaconAvailable());
|
|
52
75
|
});
|
|
@@ -65,16 +88,32 @@ export class BatchUploader {
|
|
|
65
88
|
return false;
|
|
66
89
|
}
|
|
67
90
|
|
|
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
|
+
*/
|
|
68
105
|
queueEvent(event: SDKEvent): void {
|
|
69
106
|
if (!isEmpty(event)) {
|
|
70
|
-
this.
|
|
107
|
+
this.eventsQueuedForProcessing.push(event);
|
|
71
108
|
this.mpInstance.Logger.verbose(
|
|
72
109
|
`Queuing event: ${JSON.stringify(event)}`
|
|
73
110
|
);
|
|
74
111
|
this.mpInstance.Logger.verbose(
|
|
75
|
-
`Queued event count: ${this.
|
|
112
|
+
`Queued event count: ${this.eventsQueuedForProcessing.length}`
|
|
76
113
|
);
|
|
77
114
|
|
|
115
|
+
// TODO: Remove this check once the v2 code path is removed
|
|
116
|
+
// https://go.mparticle.com/work/SQDSDKS-3720
|
|
78
117
|
if (
|
|
79
118
|
!this.batchingEnabled ||
|
|
80
119
|
Types.TriggerUploadType[event.EventDataType]
|
|
@@ -93,7 +132,7 @@ export class BatchUploader {
|
|
|
93
132
|
* @param sdkEvents current pending events
|
|
94
133
|
* @param defaultUser the user to reference for events that are missing data
|
|
95
134
|
*/
|
|
96
|
-
private static
|
|
135
|
+
private static createNewBatches(
|
|
97
136
|
sdkEvents: SDKEvent[],
|
|
98
137
|
defaultUser: MParticleUser,
|
|
99
138
|
mpInstance: MParticleWebSDK
|
|
@@ -141,9 +180,8 @@ export class BatchUploader {
|
|
|
141
180
|
mpInstance._Store.SDKConfig.onCreateBatch;
|
|
142
181
|
|
|
143
182
|
if (onCreateBatchCallback) {
|
|
144
|
-
uploadBatchObject =
|
|
145
|
-
uploadBatchObject
|
|
146
|
-
);
|
|
183
|
+
uploadBatchObject =
|
|
184
|
+
onCreateBatchCallback(uploadBatchObject);
|
|
147
185
|
if (uploadBatchObject) {
|
|
148
186
|
uploadBatchObject.modified = true;
|
|
149
187
|
} else {
|
|
@@ -169,112 +207,122 @@ export class BatchUploader {
|
|
|
169
207
|
* @param triggerFuture whether to trigger the loop again - for manual/forced uploads this should be false
|
|
170
208
|
* @param useBeacon whether to use the beacon API - used when the page is being unloaded
|
|
171
209
|
*/
|
|
172
|
-
private async prepareAndUpload(
|
|
210
|
+
private async prepareAndUpload(
|
|
211
|
+
triggerFuture: boolean,
|
|
212
|
+
useBeacon: boolean
|
|
213
|
+
): Promise<void> {
|
|
173
214
|
const currentUser = this.mpInstance.Identity.getCurrentUser();
|
|
174
215
|
|
|
175
|
-
const currentEvents = this.
|
|
176
|
-
this.
|
|
177
|
-
|
|
216
|
+
const currentEvents = this.eventsQueuedForProcessing;
|
|
217
|
+
this.eventsQueuedForProcessing = [];
|
|
218
|
+
|
|
219
|
+
const newBatches = BatchUploader.createNewBatches(
|
|
178
220
|
currentEvents,
|
|
179
221
|
currentUser,
|
|
180
222
|
this.mpInstance
|
|
181
223
|
);
|
|
182
|
-
if (
|
|
183
|
-
this.
|
|
224
|
+
if (!isEmpty(newBatches)) {
|
|
225
|
+
this.batchesQueuedForProcessing.push(...newBatches);
|
|
184
226
|
}
|
|
185
227
|
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
+
});
|
|
195
261
|
}
|
|
196
262
|
|
|
197
263
|
if (triggerFuture) {
|
|
198
|
-
|
|
199
|
-
this.prepareAndUpload(true, false);
|
|
200
|
-
}, this.uploadIntervalMillis);
|
|
264
|
+
this.triggerUploadInterval(triggerFuture, false);
|
|
201
265
|
}
|
|
202
266
|
}
|
|
203
267
|
|
|
204
268
|
private async upload(
|
|
205
269
|
logger: SDKLoggerApi,
|
|
206
|
-
|
|
270
|
+
batch: Batch,
|
|
207
271
|
useBeacon: boolean
|
|
208
|
-
): Promise<Batch
|
|
209
|
-
|
|
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)) {
|
|
272
|
+
): Promise<Batch | null> {
|
|
273
|
+
if (isEmpty(batch) || isEmpty(batch.events)) {
|
|
215
274
|
return null;
|
|
216
275
|
}
|
|
217
276
|
|
|
218
|
-
logger.verbose(`Uploading batches: ${JSON.stringify(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
+
);
|
|
230
304
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
|
305
|
+
return null;
|
|
306
|
+
} else if (response.status >= 500 || response.status === 429) {
|
|
307
|
+
logger.error(
|
|
308
|
+
`HTTP error status ${response.status} received`
|
|
250
309
|
);
|
|
251
310
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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) {
|
|
311
|
+
// Server error, return current batch and try again later
|
|
312
|
+
return batch;
|
|
313
|
+
} else if (response.status >= 401) {
|
|
273
314
|
logger.error(
|
|
274
|
-
`
|
|
315
|
+
`HTTP error status ${response.status} while uploading - please verify your API key.`
|
|
275
316
|
);
|
|
276
|
-
|
|
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;
|
|
277
320
|
}
|
|
321
|
+
} catch (error) {
|
|
322
|
+
logger.error(
|
|
323
|
+
`Error sending event to mParticle servers. ${error}`
|
|
324
|
+
);
|
|
325
|
+
return batch;
|
|
278
326
|
}
|
|
279
327
|
}
|
|
280
328
|
return null;
|
|
@@ -283,34 +331,24 @@ export class BatchUploader {
|
|
|
283
331
|
|
|
284
332
|
abstract class AsyncUploader {
|
|
285
333
|
url: string;
|
|
286
|
-
|
|
334
|
+
public abstract upload(fetchPayload: fetchPayload): Promise<XHRResponse>;
|
|
287
335
|
|
|
288
|
-
constructor(url: string
|
|
336
|
+
constructor(url: string) {
|
|
289
337
|
this.url = url;
|
|
290
|
-
this.logger = logger;
|
|
291
338
|
}
|
|
292
339
|
}
|
|
293
340
|
|
|
294
341
|
class FetchUploader extends AsyncUploader {
|
|
295
|
-
|
|
296
|
-
fetchPayload: fetchPayload,
|
|
297
|
-
uploads: Batch[],
|
|
298
|
-
i: number
|
|
299
|
-
) {
|
|
342
|
+
public async upload(fetchPayload: fetchPayload): Promise<XHRResponse> {
|
|
300
343
|
const response: XHRResponse = await fetch(this.url, fetchPayload);
|
|
301
344
|
return response;
|
|
302
345
|
}
|
|
303
346
|
}
|
|
304
347
|
|
|
305
348
|
class XHRUploader extends AsyncUploader {
|
|
306
|
-
|
|
307
|
-
fetchPayload: fetchPayload,
|
|
308
|
-
uploads: Batch[],
|
|
309
|
-
i: number
|
|
310
|
-
) {
|
|
349
|
+
public async upload(fetchPayload: fetchPayload): Promise<XHRResponse> {
|
|
311
350
|
const response: XHRResponse = await this.makeRequest(
|
|
312
351
|
this.url,
|
|
313
|
-
this.logger,
|
|
314
352
|
fetchPayload.body
|
|
315
353
|
);
|
|
316
354
|
return response;
|
|
@@ -318,7 +356,6 @@ class XHRUploader extends AsyncUploader {
|
|
|
318
356
|
|
|
319
357
|
private async makeRequest(
|
|
320
358
|
url: string,
|
|
321
|
-
logger: SDKLoggerApi,
|
|
322
359
|
data: string
|
|
323
360
|
): Promise<XMLHttpRequest> {
|
|
324
361
|
const xhr: XMLHttpRequest = new XMLHttpRequest();
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// TODO: Migrate this to Consent.ts when that is created
|
|
2
|
+
import { ConsentState, PrivacyConsentState } from '@mparticle/web-sdk';
|
|
3
|
+
|
|
4
|
+
export interface IGDPRConsentObject {
|
|
5
|
+
purpose: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ICCPAConsentObject {
|
|
9
|
+
purpose: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface IConsentJSONObject {
|
|
13
|
+
gdpr?: Partial<IGDPRConsentObject>;
|
|
14
|
+
ccpa?: Partial<ICCPAConsentObject>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ICreatePrivacyConsentFunction {
|
|
18
|
+
(
|
|
19
|
+
consented: boolean,
|
|
20
|
+
timestamp?: number,
|
|
21
|
+
consentDocument?: string,
|
|
22
|
+
location?: string,
|
|
23
|
+
hardwareId?: string
|
|
24
|
+
): PrivacyConsentState | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// TODO: Refactor this with Consent Namespace in @types/mparticle-web-sdk
|
|
28
|
+
// https://go.mparticle.com/work/SQDSDKS-5009
|
|
29
|
+
export interface SDKConsentApi {
|
|
30
|
+
createGDPRConsent: ICreatePrivacyConsentFunction;
|
|
31
|
+
createCCPAConsent: ICreatePrivacyConsentFunction;
|
|
32
|
+
createConsentState: (consentState?: ConsentState) => ConsentState;
|
|
33
|
+
ConsentSerialization: IConsentSerialization;
|
|
34
|
+
createPrivacyConsent: ICreatePrivacyConsentFunction;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface IConsentSerialization {
|
|
38
|
+
toMinifiedJsonObject: (state: ConsentState) => IConsentJSONObject;
|
|
39
|
+
fromMinifiedJsonObject: (json: IConsentJSONObject) => ConsentState;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// TODO: Resolve discrepency between ConsentState and SDKConsentState
|
|
43
|
+
// https://go.mparticle.com/work/SQDSDKS-5009
|
|
44
|
+
export interface SDKConsentState
|
|
45
|
+
extends Omit<ConsentState, 'getGDPRConsentState' | 'getCCPAConsentState'> {
|
|
46
|
+
getGDPRConsentState(): SDKGDPRConsentState;
|
|
47
|
+
getCCPAConsentState(): SDKCCPAConsentState;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface SDKGDPRConsentState {
|
|
51
|
+
[key: string]: SDKConsentStateData;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// TODO: Resolve discrepency between ConsentState and SDKConsentState
|
|
55
|
+
// https://go.mparticle.com/work/SQDSDKS-5009
|
|
56
|
+
// Specifically PrivacyConsentState, GDPRConsentState and CCPAConsentState
|
|
57
|
+
// Treat all attributes as required, but we had to override this
|
|
58
|
+
// to be optional for a bugfix:
|
|
59
|
+
// https://github.com/mParticle/mparticle-web-sdk/commit/3b11ead79f25b417737442a4fabd6435750f46b8
|
|
60
|
+
export interface SDKConsentStateData {
|
|
61
|
+
Consented: boolean;
|
|
62
|
+
Timestamp?: number;
|
|
63
|
+
ConsentDocument?: string;
|
|
64
|
+
Location?: string;
|
|
65
|
+
HardwareId?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface SDKCCPAConsentState extends SDKConsentStateData {}
|
|
69
|
+
|
|
70
|
+
export interface IPrivacyV2DTO {
|
|
71
|
+
c: boolean; // Consented
|
|
72
|
+
ts: number; // Timestamp
|
|
73
|
+
d: string; // Document
|
|
74
|
+
l: string; // Location
|
|
75
|
+
h: string; // HardwareID
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface IGDPRConsentStateDTO {
|
|
79
|
+
[key: string]: IPrivacyV2DTO;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface ICCPAConsentStateDTO {
|
|
83
|
+
data_sale_opt_out: IPrivacyV2DTO;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface IConsentStateDTO {
|
|
87
|
+
gdpr?: IGDPRConsentStateDTO;
|
|
88
|
+
ccpa?: ICCPAConsentStateDTO;
|
|
89
|
+
}
|
package/src/mockBatchCreator.ts
CHANGED
|
@@ -7,15 +7,15 @@ import * as EventsApi from '@mparticle/event-models';
|
|
|
7
7
|
|
|
8
8
|
const mockFunction = function () {
|
|
9
9
|
return null;
|
|
10
|
-
}
|
|
10
|
+
};
|
|
11
11
|
export default class _BatchValidator {
|
|
12
12
|
private getMPInstance() {
|
|
13
13
|
return {
|
|
14
14
|
// Certain Helper, Store, and Identity properties need to be mocked to be used in the `returnBatch` method
|
|
15
15
|
_Helpers: {
|
|
16
|
-
sanitizeAttributes:
|
|
17
|
-
.sanitizeAttributes,
|
|
18
|
-
generateUniqueId: function() {
|
|
16
|
+
sanitizeAttributes:
|
|
17
|
+
window.mParticle.getInstance()._Helpers.sanitizeAttributes,
|
|
18
|
+
generateUniqueId: function () {
|
|
19
19
|
return 'mockId';
|
|
20
20
|
},
|
|
21
21
|
extend: window.mParticle.getInstance()._Helpers.extend,
|
|
@@ -28,6 +28,11 @@ export default class _BatchValidator {
|
|
|
28
28
|
_resetForTests: mockFunction,
|
|
29
29
|
_APIClient: null,
|
|
30
30
|
_Consent: null,
|
|
31
|
+
_Forwarders: null,
|
|
32
|
+
_NativeSdkHelpers: null,
|
|
33
|
+
_Persistence: null,
|
|
34
|
+
_preInit: null,
|
|
35
|
+
Consent: null,
|
|
31
36
|
_ServerModel: null,
|
|
32
37
|
_SessionManager: null,
|
|
33
38
|
_Store: {
|