@mparticle/web-sdk 2.19.2 → 2.19.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.
- package/dist/mparticle.common.js +31 -14
- package/dist/mparticle.esm.js +31 -14
- package/dist/mparticle.js +132 -82
- package/package.json +3 -2
- package/src/batchUploader.ts +100 -63
- package/src/forwarders.js +40 -20
- package/src/helpers.js +5 -0
- package/src/identity.js +10 -6
- package/src/vault.ts +21 -74
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,47 +207,58 @@ 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> {
|
|
214
|
+
// Fetch current user so that events can be grouped by MPID
|
|
173
215
|
const currentUser = this.mpInstance.Identity.getCurrentUser();
|
|
174
216
|
|
|
175
|
-
const currentEvents = this.
|
|
176
|
-
this.
|
|
177
|
-
|
|
217
|
+
const currentEvents = this.eventsQueuedForProcessing;
|
|
218
|
+
this.eventsQueuedForProcessing = [];
|
|
219
|
+
|
|
220
|
+
const newBatches = BatchUploader.createNewBatches(
|
|
178
221
|
currentEvents,
|
|
179
222
|
currentUser,
|
|
180
223
|
this.mpInstance
|
|
181
224
|
);
|
|
182
|
-
|
|
183
|
-
|
|
225
|
+
|
|
226
|
+
if (!isEmpty(newBatches)) {
|
|
227
|
+
this.batchesQueuedForProcessing.push(...newBatches);
|
|
184
228
|
}
|
|
185
229
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
230
|
+
// Clear out pending batches to avoid any potential duplication
|
|
231
|
+
const batchesToUpload = this.batchesQueuedForProcessing;
|
|
232
|
+
this.batchesQueuedForProcessing = [];
|
|
233
|
+
|
|
234
|
+
const batchesThatDidNotUpload = await this.uploadBatches(
|
|
189
235
|
this.mpInstance.Logger,
|
|
190
|
-
|
|
236
|
+
batchesToUpload,
|
|
191
237
|
useBeacon
|
|
192
238
|
);
|
|
193
|
-
|
|
194
|
-
|
|
239
|
+
|
|
240
|
+
// Batches that do not successfully upload are added back to the process queue
|
|
241
|
+
// in the order they were created so that we can attempt re-transmission in
|
|
242
|
+
// the same sequence. This is to prevent any potential data corruption.
|
|
243
|
+
if (!isEmpty(batchesThatDidNotUpload)) {
|
|
244
|
+
// TODO: https://go.mparticle.com/work/SQDSDKS-5165
|
|
245
|
+
this.batchesQueuedForProcessing.unshift(...batchesThatDidNotUpload);
|
|
195
246
|
}
|
|
196
247
|
|
|
197
248
|
if (triggerFuture) {
|
|
198
|
-
|
|
199
|
-
this.prepareAndUpload(true, false);
|
|
200
|
-
}, this.uploadIntervalMillis);
|
|
249
|
+
this.triggerUploadInterval(triggerFuture, false);
|
|
201
250
|
}
|
|
202
251
|
}
|
|
203
252
|
|
|
204
|
-
|
|
253
|
+
// TODO: Refactor to use logger as a class method
|
|
254
|
+
// https://go.mparticle.com/work/SQDSDKS-5167
|
|
255
|
+
private async uploadBatches(
|
|
205
256
|
logger: SDKLoggerApi,
|
|
206
|
-
|
|
257
|
+
batches: Batch[],
|
|
207
258
|
useBeacon: boolean
|
|
208
|
-
): Promise<Batch[]> {
|
|
209
|
-
let uploader;
|
|
210
|
-
|
|
259
|
+
): Promise<Batch[] | null> {
|
|
211
260
|
// Filter out any batches that don't have events
|
|
212
|
-
const uploads =
|
|
261
|
+
const uploads = batches.filter((batch) => !isEmpty(batch.events));
|
|
213
262
|
|
|
214
263
|
if (isEmpty(uploads)) {
|
|
215
264
|
return null;
|
|
@@ -235,19 +284,8 @@ export class BatchUploader {
|
|
|
235
284
|
});
|
|
236
285
|
navigator.sendBeacon(this.uploadUrl, blob);
|
|
237
286
|
} 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
287
|
try {
|
|
246
|
-
const response = await uploader.upload(
|
|
247
|
-
fetchPayload,
|
|
248
|
-
uploads,
|
|
249
|
-
i
|
|
250
|
-
);
|
|
288
|
+
const response = await this.uploader.upload(fetchPayload);
|
|
251
289
|
|
|
252
290
|
if (response.status >= 200 && response.status < 300) {
|
|
253
291
|
logger.verbose(
|
|
@@ -260,7 +298,7 @@ export class BatchUploader {
|
|
|
260
298
|
logger.error(
|
|
261
299
|
`HTTP error status ${response.status} received`
|
|
262
300
|
);
|
|
263
|
-
//
|
|
301
|
+
// Server error, add back current batches and try again later
|
|
264
302
|
return uploads.slice(i, uploads.length);
|
|
265
303
|
} else if (response.status >= 401) {
|
|
266
304
|
logger.error(
|
|
@@ -268,6 +306,16 @@ export class BatchUploader {
|
|
|
268
306
|
);
|
|
269
307
|
//if we're getting a 401, assume we'll keep getting a 401 and clear the uploads.
|
|
270
308
|
return null;
|
|
309
|
+
} else {
|
|
310
|
+
// In case there is an HTTP error we did not anticipate.
|
|
311
|
+
console.error(
|
|
312
|
+
`HTTP error status ${response.status} while uploading events.`,
|
|
313
|
+
response
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
throw new Error(
|
|
317
|
+
`Uncaught HTTP Error ${response.status}. Batch upload will be re-attempted.`
|
|
318
|
+
);
|
|
271
319
|
}
|
|
272
320
|
} catch (e) {
|
|
273
321
|
logger.error(
|
|
@@ -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();
|
package/src/forwarders.js
CHANGED
|
@@ -3,6 +3,12 @@ import filteredMparticleUser from './filteredMparticleUser';
|
|
|
3
3
|
|
|
4
4
|
export default function Forwarders(mpInstance, kitBlocker) {
|
|
5
5
|
var self = this;
|
|
6
|
+
|
|
7
|
+
const UserAttributeActionTypes = {
|
|
8
|
+
setUserAttribute: 'setUserAttribute',
|
|
9
|
+
removeUserAttribute: 'removeUserAttribute',
|
|
10
|
+
};
|
|
11
|
+
|
|
6
12
|
this.initForwarders = function(userIdentities, forwardingStatsCallback) {
|
|
7
13
|
var user = mpInstance.Identity.getCurrentUser();
|
|
8
14
|
if (
|
|
@@ -387,33 +393,47 @@ export default function Forwarders(mpInstance, kitBlocker) {
|
|
|
387
393
|
}
|
|
388
394
|
};
|
|
389
395
|
|
|
390
|
-
this.
|
|
391
|
-
if (
|
|
396
|
+
this.handleForwarderUserAttributes = function(functionNameKey, key, value) {
|
|
397
|
+
if (
|
|
398
|
+
(kitBlocker && kitBlocker.isAttributeKeyBlocked(key)) ||
|
|
399
|
+
!mpInstance._Store.activeForwarders.length
|
|
400
|
+
) {
|
|
392
401
|
return;
|
|
393
402
|
}
|
|
394
403
|
|
|
395
|
-
|
|
396
|
-
|
|
404
|
+
mpInstance._Store.activeForwarders.forEach(function(forwarder) {
|
|
405
|
+
const forwarderFunction = forwarder[functionNameKey];
|
|
406
|
+
if (
|
|
407
|
+
!forwarderFunction ||
|
|
408
|
+
mpInstance._Helpers.isFilteredUserAttribute(
|
|
409
|
+
key,
|
|
410
|
+
forwarder.userAttributeFilters
|
|
411
|
+
)
|
|
412
|
+
) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
let result;
|
|
417
|
+
|
|
397
418
|
if (
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
!mpInstance._Helpers.inArray(
|
|
401
|
-
forwarder.userAttributeFilters,
|
|
402
|
-
mpInstance._Helpers.generateHash(key)
|
|
403
|
-
)
|
|
419
|
+
functionNameKey ===
|
|
420
|
+
UserAttributeActionTypes.setUserAttribute
|
|
404
421
|
) {
|
|
405
|
-
|
|
406
|
-
|
|
422
|
+
result = forwarder.setUserAttribute(key, value);
|
|
423
|
+
} else if (
|
|
424
|
+
functionNameKey ===
|
|
425
|
+
UserAttributeActionTypes.removeUserAttribute
|
|
426
|
+
) {
|
|
427
|
+
result = forwarder.removeUserAttribute(key);
|
|
428
|
+
}
|
|
407
429
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
} catch (e) {
|
|
412
|
-
mpInstance.Logger.error(e);
|
|
413
|
-
}
|
|
430
|
+
if (result) {
|
|
431
|
+
mpInstance.Logger.verbose(result);
|
|
414
432
|
}
|
|
415
|
-
})
|
|
416
|
-
|
|
433
|
+
} catch (e) {
|
|
434
|
+
mpInstance.Logger.error(e);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
417
437
|
};
|
|
418
438
|
|
|
419
439
|
this.setForwarderUserIdentities = function(userIdentities) {
|
package/src/helpers.js
CHANGED
|
@@ -387,6 +387,11 @@ export default function Helpers(mpInstance) {
|
|
|
387
387
|
return filteredUserAttributes;
|
|
388
388
|
};
|
|
389
389
|
|
|
390
|
+
this.isFilteredUserAttribute = function(userAttributeKey, filterList) {
|
|
391
|
+
const hashedUserAttribute = self.generateHash(userAttributeKey);
|
|
392
|
+
return filterList && self.inArray(filterList, hashedUserAttribute);
|
|
393
|
+
};
|
|
394
|
+
|
|
390
395
|
this.isEventType = function(type) {
|
|
391
396
|
for (var prop in Types.EventType) {
|
|
392
397
|
if (Types.EventType.hasOwnProperty(prop)) {
|
package/src/identity.js
CHANGED
|
@@ -904,7 +904,8 @@ export default function Identity(mpInstance) {
|
|
|
904
904
|
self.IdentityAPI.getCurrentUser().getUserIdentities(),
|
|
905
905
|
mpInstance._APIClient.prepareForwardingStats
|
|
906
906
|
);
|
|
907
|
-
mpInstance._Forwarders.
|
|
907
|
+
mpInstance._Forwarders.handleForwarderUserAttributes(
|
|
908
|
+
'setUserAttribute',
|
|
908
909
|
key,
|
|
909
910
|
newValue
|
|
910
911
|
);
|
|
@@ -990,9 +991,10 @@ export default function Identity(mpInstance) {
|
|
|
990
991
|
self.IdentityAPI.getCurrentUser().getUserIdentities(),
|
|
991
992
|
mpInstance._APIClient.prepareForwardingStats
|
|
992
993
|
);
|
|
993
|
-
mpInstance._Forwarders.
|
|
994
|
+
mpInstance._Forwarders.handleForwarderUserAttributes(
|
|
994
995
|
'removeUserAttribute',
|
|
995
|
-
key
|
|
996
|
+
key,
|
|
997
|
+
null
|
|
996
998
|
);
|
|
997
999
|
}
|
|
998
1000
|
},
|
|
@@ -1098,7 +1100,8 @@ export default function Identity(mpInstance) {
|
|
|
1098
1100
|
self.IdentityAPI.getCurrentUser().getUserIdentities(),
|
|
1099
1101
|
mpInstance._APIClient.prepareForwardingStats
|
|
1100
1102
|
);
|
|
1101
|
-
mpInstance._Forwarders.
|
|
1103
|
+
mpInstance._Forwarders.handleForwarderUserAttributes(
|
|
1104
|
+
'setUserAttribute',
|
|
1102
1105
|
key,
|
|
1103
1106
|
arrayCopy
|
|
1104
1107
|
);
|
|
@@ -1127,9 +1130,10 @@ export default function Identity(mpInstance) {
|
|
|
1127
1130
|
if (userAttributes) {
|
|
1128
1131
|
for (var prop in userAttributes) {
|
|
1129
1132
|
if (userAttributes.hasOwnProperty(prop)) {
|
|
1130
|
-
mpInstance._Forwarders.
|
|
1133
|
+
mpInstance._Forwarders.handleForwarderUserAttributes(
|
|
1131
1134
|
'removeUserAttribute',
|
|
1132
|
-
prop
|
|
1135
|
+
prop,
|
|
1136
|
+
null
|
|
1133
1137
|
);
|
|
1134
1138
|
}
|
|
1135
1139
|
this.removeUserAttribute(prop);
|
package/src/vault.ts
CHANGED
|
@@ -6,12 +6,10 @@ interface IVaultOptions {
|
|
|
6
6
|
offlineStorageEnabled?: boolean;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export default class Vault<StorableItem
|
|
10
|
-
public contents:
|
|
9
|
+
export default class Vault<StorableItem> {
|
|
10
|
+
public contents: StorableItem;
|
|
11
11
|
private readonly _storageKey: string;
|
|
12
|
-
private readonly _itemKey: keyof StorableItem;
|
|
13
12
|
private logger?: Logger;
|
|
14
|
-
private offlineStorageEnabled: boolean = false;
|
|
15
13
|
|
|
16
14
|
/**
|
|
17
15
|
*
|
|
@@ -19,16 +17,9 @@ export default class Vault<StorableItem extends Dictionary> {
|
|
|
19
17
|
* @param {string} itemKey an element within your StorableItem to use as a key
|
|
20
18
|
* @param {IVaultOptions} options A Dictionary of IVaultOptions
|
|
21
19
|
*/
|
|
22
|
-
constructor(
|
|
23
|
-
storageKey: string,
|
|
24
|
-
itemKey: keyof StorableItem,
|
|
25
|
-
options?: IVaultOptions
|
|
26
|
-
) {
|
|
20
|
+
constructor(storageKey: string, options?: IVaultOptions) {
|
|
27
21
|
this._storageKey = storageKey;
|
|
28
|
-
this.
|
|
29
|
-
this.contents = this.getItems() || {};
|
|
30
|
-
|
|
31
|
-
this.offlineStorageEnabled = options?.offlineStorageEnabled;
|
|
22
|
+
this.contents = this.getFromLocalStorage();
|
|
32
23
|
|
|
33
24
|
// Add a fake logger in case one is not provided or needed
|
|
34
25
|
this.logger = options?.logger || {
|
|
@@ -39,51 +30,16 @@ export default class Vault<StorableItem extends Dictionary> {
|
|
|
39
30
|
}
|
|
40
31
|
|
|
41
32
|
/**
|
|
42
|
-
* Stores a
|
|
33
|
+
* Stores a StorableItem to Local Storage
|
|
43
34
|
* @method storeItem
|
|
44
|
-
* @param item {StorableItem}
|
|
45
|
-
*/
|
|
46
|
-
public storeItem(item: StorableItem): void {
|
|
47
|
-
this.contents = this.getItems();
|
|
48
|
-
|
|
49
|
-
if (item[this._itemKey]) {
|
|
50
|
-
this.contents[item[this._itemKey]] = item;
|
|
51
|
-
this.logger.verbose(
|
|
52
|
-
`Saved items to vault with key: ${item[this._itemKey]}`
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
this.saveItems(this.contents);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Stores Items using `itemId` as an index
|
|
61
|
-
* @method storeItems
|
|
62
|
-
* @param {StorableItem[]} an Array of StorableItems
|
|
63
|
-
*/
|
|
64
|
-
public storeItems(items: StorableItem[]): void {
|
|
65
|
-
items.forEach((item) => this.storeItem(item));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Removes a single StorableItem based on `indexId`
|
|
70
|
-
* @method removeItem
|
|
71
|
-
* @param {String} indexId
|
|
35
|
+
* @param item {StorableItem}
|
|
72
36
|
*/
|
|
73
|
-
public
|
|
74
|
-
this.
|
|
75
|
-
this.contents = this.getItems() || {};
|
|
37
|
+
public store(item: StorableItem): void {
|
|
38
|
+
this.contents = item;
|
|
76
39
|
|
|
77
|
-
|
|
78
|
-
delete this.contents[indexId];
|
|
40
|
+
this.logger.verbose(`Saved to local storage: ${item}`);
|
|
79
41
|
|
|
80
|
-
|
|
81
|
-
} catch (error) {
|
|
82
|
-
this.logger.error(
|
|
83
|
-
`Unable to remove item without a matching ID ${indexId}`
|
|
84
|
-
);
|
|
85
|
-
this.logger.error(error as string);
|
|
86
|
-
}
|
|
42
|
+
this.saveToLocalStorage(this.contents);
|
|
87
43
|
}
|
|
88
44
|
|
|
89
45
|
/**
|
|
@@ -91,10 +47,10 @@ export default class Vault<StorableItem extends Dictionary> {
|
|
|
91
47
|
* @method retrieveItems
|
|
92
48
|
* @returns {StorableItem[]} an array of Items
|
|
93
49
|
*/
|
|
94
|
-
public
|
|
95
|
-
this.contents = this.
|
|
50
|
+
public retrieve(): StorableItem {
|
|
51
|
+
this.contents = this.getFromLocalStorage();
|
|
96
52
|
|
|
97
|
-
return
|
|
53
|
+
return this.contents;
|
|
98
54
|
}
|
|
99
55
|
|
|
100
56
|
/**
|
|
@@ -102,14 +58,11 @@ export default class Vault<StorableItem extends Dictionary> {
|
|
|
102
58
|
* @method purge
|
|
103
59
|
*/
|
|
104
60
|
public purge(): void {
|
|
105
|
-
this.contents =
|
|
106
|
-
this.
|
|
61
|
+
this.contents = null;
|
|
62
|
+
this.removeFromLocalStorage();
|
|
107
63
|
}
|
|
108
64
|
|
|
109
|
-
private
|
|
110
|
-
if (!this.offlineStorageEnabled) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
65
|
+
private saveToLocalStorage(items: StorableItem): void {
|
|
113
66
|
try {
|
|
114
67
|
window.localStorage.setItem(
|
|
115
68
|
this._storageKey,
|
|
@@ -121,21 +74,15 @@ export default class Vault<StorableItem extends Dictionary> {
|
|
|
121
74
|
}
|
|
122
75
|
}
|
|
123
76
|
|
|
124
|
-
private
|
|
125
|
-
if (!this.offlineStorageEnabled) {
|
|
126
|
-
return this.contents;
|
|
127
|
-
}
|
|
77
|
+
private getFromLocalStorage(): StorableItem | null {
|
|
128
78
|
// TODO: Handle cases where Local Storage is unavailable
|
|
129
79
|
// https://go.mparticle.com/work/SQDSDKS-5022
|
|
130
|
-
const
|
|
80
|
+
const item: string = window.localStorage.getItem(this._storageKey);
|
|
131
81
|
|
|
132
|
-
return
|
|
82
|
+
return item ? JSON.parse(item) : null;
|
|
133
83
|
}
|
|
134
84
|
|
|
135
|
-
private
|
|
136
|
-
if (!this.offlineStorageEnabled) {
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
85
|
+
private removeFromLocalStorage(): void {
|
|
139
86
|
window.localStorage.removeItem(this._storageKey);
|
|
140
87
|
}
|
|
141
|
-
}
|
|
88
|
+
}
|