@mparticle/web-sdk 2.20.2 → 2.21.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mparticle/web-sdk",
3
- "version": "2.20.2",
3
+ "version": "2.21.0",
4
4
  "description": "mParticle core SDK for web applications",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -78,7 +78,7 @@
78
78
  "@mparticle/event-models": "^1.1.6",
79
79
  "@rollup/plugin-json": "^4.1.0",
80
80
  "@rollup/plugin-typescript": "^8.3.3",
81
- "@rollup/plugin-babel": "5.3.1",
81
+ "@rollup/plugin-babel": "6.0.3",
82
82
  "@rollup/plugin-commonjs": "22.0.2",
83
83
  "@rollup/plugin-node-resolve": "13.3.0",
84
84
  "@semantic-release/changelog": "^5.0.1",
@@ -93,8 +93,8 @@
93
93
  "browserify": "^17.0.0",
94
94
  "chai": "^4.2.0",
95
95
  "cross-env": "^7.0.3",
96
- "eslint": "^7.32.0",
97
- "eslint-config-prettier": "8.3.0",
96
+ "eslint": "^8.36.0",
97
+ "eslint-config-prettier": "8.8.0",
98
98
  "eslint-plugin-prettier": "3.4.1",
99
99
  "fetch-mock": "^7.5.1",
100
100
  "gts": "^3.1.0",
@@ -116,7 +116,7 @@
116
116
  "should": "^7.1.0",
117
117
  "sinon": "^11.0.0",
118
118
  "tslib": "^2.1.0",
119
- "typescript": "^4.2.4",
119
+ "typescript": "^5.0.2",
120
120
  "uglify-js": "^3.4.9",
121
121
  "webpack": "^5.36.2",
122
122
  "webpack-cli": "^4.6.0"
package/src/apiClient.ts CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  SDKEvent,
9
9
  } from './sdkRuntimeModels';
10
10
  import KitBlocker from './kitBlocking';
11
- import { Dictionary } from './utils';
11
+ import { Dictionary, getRampNumber } from './utils';
12
12
  import { IUploadObject } from './serverModel';
13
13
 
14
14
  const Messages = Constants.Messages;
@@ -67,9 +67,7 @@ export default function APIClient(
67
67
  return false;
68
68
  }
69
69
 
70
- const rampNumber = mpInstance._Helpers.getRampNumber(
71
- mpInstance._Store.deviceId
72
- );
70
+ const rampNumber = getRampNumber(mpInstance._Store.deviceId);
73
71
  return eventsV3Percentage >= rampNumber;
74
72
  };
75
73
 
@@ -1,4 +1,5 @@
1
1
  import { Batch } from '@mparticle/event-models';
2
+ import Constants from './constants';
2
3
  import {
3
4
  SDKEvent,
4
5
  MParticleUser,
@@ -7,12 +8,17 @@ import {
7
8
  } from './sdkRuntimeModels';
8
9
  import { convertEvents } from './sdkToEventsApiConverter';
9
10
  import Types from './types';
10
- import { isEmpty } from './utils';
11
+ import { getRampNumber, isEmpty } from './utils';
12
+ import { SessionStorageVault, LocalStorageVault } from './vault';
11
13
 
12
14
  /**
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.
15
+ * BatchUploader contains all the logic to store/retrieve events and batches
16
+ * to/from persistence, and upload batches to mParticle.
17
+ * It queues events as they come in, storing them in persistence, then at set
18
+ * intervals turns them into batches and transfers between event and batch
19
+ * persistence.
20
+ * It then attempts to upload them to mParticle, purging batch persistence if
21
+ * the upload is successful
16
22
  *
17
23
  * These uploads happen on an interval basis using window.fetch or XHR
18
24
  * requests, depending on what is available in the browser.
@@ -31,6 +37,9 @@ export class BatchUploader {
31
37
  mpInstance: MParticleWebSDK;
32
38
  uploadUrl: string;
33
39
  batchingEnabled: boolean;
40
+ private eventVault: SessionStorageVault<SDKEvent[]>;
41
+ private batchVault: LocalStorageVault<Batch[]>;
42
+ private offlineStorageEnabled: boolean = false;
34
43
  private uploader: AsyncUploader;
35
44
 
36
45
  /**
@@ -46,9 +55,38 @@ export class BatchUploader {
46
55
  if (this.uploadIntervalMillis < BatchUploader.MINIMUM_INTERVAL_MILLIS) {
47
56
  this.uploadIntervalMillis = BatchUploader.MINIMUM_INTERVAL_MILLIS;
48
57
  }
58
+
59
+ // Events will be queued during `queueEvents` method
49
60
  this.eventsQueuedForProcessing = [];
61
+
62
+ // Batch queue should start empty and will be populated during
63
+ // `prepareAndUpload` method, either via Local Storage or after
64
+ // new batches are created.
50
65
  this.batchesQueuedForProcessing = [];
51
66
 
67
+ // Cache Offline Storage Availability boolean
68
+ // so that we don't have to check it every time
69
+ this.offlineStorageEnabled = this.isOfflineStorageAvailable();
70
+
71
+ if (this.offlineStorageEnabled) {
72
+ this.eventVault = new SessionStorageVault<SDKEvent[]>(
73
+ `${mpInstance._Store.storageName}-events`,
74
+ {
75
+ logger: mpInstance.Logger,
76
+ }
77
+ );
78
+
79
+ this.batchVault = new LocalStorageVault<Batch[]>(
80
+ `${mpInstance._Store.storageName}-batches`,
81
+ {
82
+ logger: mpInstance.Logger,
83
+ }
84
+ );
85
+
86
+ // Load Events from Session Storage in case we have any in storage
87
+ this.eventsQueuedForProcessing.push(...this.eventVault.retrieve());
88
+ }
89
+
52
90
  const { SDKConfig, devToken } = this.mpInstance._Store;
53
91
  const baseUrl = this.mpInstance._Helpers.createServiceUrl(
54
92
  SDKConfig.v3SecureServiceUrl,
@@ -64,6 +102,29 @@ export class BatchUploader {
64
102
  this.addEventListeners();
65
103
  }
66
104
 
105
+ private isOfflineStorageAvailable(): boolean {
106
+ const {
107
+ _Helpers: { getFeatureFlag },
108
+ _Store: { deviceId },
109
+ } = this.mpInstance;
110
+
111
+ const offlineStorageFeatureFlagValue = getFeatureFlag(
112
+ Constants.FeatureFlags.OfflineStorage
113
+ );
114
+
115
+ const offlineStoragePercentage = parseInt(
116
+ offlineStorageFeatureFlagValue,
117
+ 10
118
+ );
119
+
120
+ const rampNumber = getRampNumber(deviceId);
121
+
122
+ // TODO: Handle cases where Local Storage is unavailable
123
+ // Potentially shared between Vault and Persistence as well
124
+ // https://go.mparticle.com/work/SQDSDKS-5022
125
+ return offlineStoragePercentage >= rampNumber;
126
+ }
127
+
67
128
  // Adds listeners to be used trigger Navigator.sendBeacon if the browser
68
129
  // loses focus for any reason, such as closing browser tab or minimizing window
69
130
  private addEventListeners() {
@@ -102,9 +163,12 @@ export class BatchUploader {
102
163
  * This method will queue a single Event which will eventually be processed into a Batch
103
164
  * @param event event that should be queued
104
165
  */
105
- queueEvent(event: SDKEvent): void {
166
+ public queueEvent(event: SDKEvent): void {
106
167
  if (!isEmpty(event)) {
107
168
  this.eventsQueuedForProcessing.push(event);
169
+ if (this.offlineStorageEnabled && this.eventVault) {
170
+ this.eventVault.store(this.eventsQueuedForProcessing);
171
+ }
108
172
  this.mpInstance.Logger.verbose(
109
173
  `Queuing event: ${JSON.stringify(event)}`
110
174
  );
@@ -214,14 +278,32 @@ export class BatchUploader {
214
278
  // Fetch current user so that events can be grouped by MPID
215
279
  const currentUser = this.mpInstance.Identity.getCurrentUser();
216
280
 
217
- const currentEvents = this.eventsQueuedForProcessing;
281
+ const currentEvents: SDKEvent[] = this.eventsQueuedForProcessing;
282
+
218
283
  this.eventsQueuedForProcessing = [];
284
+ if (this.offlineStorageEnabled && this.eventVault) {
285
+ this.eventVault.store([]);
286
+ }
219
287
 
220
- const newBatches = BatchUploader.createNewBatches(
221
- currentEvents,
222
- currentUser,
223
- this.mpInstance
224
- );
288
+ let newBatches: Batch[] = [];
289
+ if (!isEmpty(currentEvents)) {
290
+ newBatches = BatchUploader.createNewBatches(
291
+ currentEvents,
292
+ currentUser,
293
+ this.mpInstance
294
+ );
295
+ }
296
+
297
+ // Top Load any older Batches from Offline Storage so they go out first
298
+ if (this.offlineStorageEnabled && this.batchVault) {
299
+ this.batchesQueuedForProcessing.unshift(
300
+ ...this.batchVault.retrieve()
301
+ );
302
+
303
+ // Remove batches from local storage before transmit to
304
+ // prevent duplication
305
+ this.batchVault.purge();
306
+ }
225
307
 
226
308
  if (!isEmpty(newBatches)) {
227
309
  this.batchesQueuedForProcessing.push(...newBatches);
@@ -231,6 +313,13 @@ export class BatchUploader {
231
313
  const batchesToUpload = this.batchesQueuedForProcessing;
232
314
  this.batchesQueuedForProcessing = [];
233
315
 
316
+ // If `useBeacon` is true, the browser has been closed suddently
317
+ // so we should save `batchesToUpload` to Offline Storage before
318
+ // an upload is attempted.
319
+ if (useBeacon && this.offlineStorageEnabled && this.batchVault) {
320
+ this.batchVault.store(batchesToUpload);
321
+ }
322
+
234
323
  const batchesThatDidNotUpload = await this.uploadBatches(
235
324
  this.mpInstance.Logger,
236
325
  batchesToUpload,
@@ -245,6 +334,19 @@ export class BatchUploader {
245
334
  this.batchesQueuedForProcessing.unshift(...batchesThatDidNotUpload);
246
335
  }
247
336
 
337
+ // Update Offline Storage with current state of batch queue
338
+ if (!useBeacon && this.offlineStorageEnabled && this.batchVault) {
339
+ // Note: since beacon is "Fire and forget" it will empty `batchesThatDidNotUplod`
340
+ // regardless of whether the batches were successfully uploaded or not. We should
341
+ // therefore NOT overwrite Offline Storage when beacon returns, so that we can retry
342
+ // uploading saved batches at a later time. Batches should only be removed from
343
+ // Local Storage once we can confirm they are successfully uploaded.
344
+ this.batchVault.store(this.batchesQueuedForProcessing);
345
+
346
+ // Clear batch queue since everything should be in Offline Storage
347
+ this.batchesQueuedForProcessing = [];
348
+ }
349
+
248
350
  if (triggerFuture) {
249
351
  this.triggerUploadInterval(triggerFuture, false);
250
352
  }
@@ -29,8 +29,9 @@ export interface IKitConfigs {
29
29
  eventNameFilters: number[];
30
30
  eventTypeFilters: number[];
31
31
  attributeFilters: number[];
32
- filteringEventAttributeValue: Dictionary;
33
- filteringConsentRuleValues: Dictionary;
32
+ filteringEventAttributeValue: IFilteringAttributeValue;
33
+ filteringUserAttributeValue: IFilteringAttributeValue;
34
+ filteringConsentRuleValues: IFilteringConsentRulesValue;
34
35
  consentRegulationFilters: number[];
35
36
  consentRegulationPurposeFilters: number[];
36
37
  messageTypeFilters: number[];
@@ -39,6 +40,22 @@ export interface IKitConfigs {
39
40
  excludeAnonymousUser: boolean;
40
41
  }
41
42
 
43
+ export interface IFilteringAttributeValue {
44
+ eventAttributeName: string;
45
+ eventAttributeValue: string;
46
+ includeOnMatch: boolean
47
+ }
48
+ export interface IFilteringConsentRulesValue {
49
+ includeOnMatch: boolean;
50
+ values: IConsentRuleValue[];
51
+ }
52
+
53
+ export interface IConsentRuleValue {
54
+ consentPurpose: string;
55
+ hasConsented: boolean
56
+ }
57
+
58
+
42
59
  export interface IPixelConfig {
43
60
  name: string;
44
61
  moduleId: number;
package/src/constants.ts CHANGED
@@ -161,6 +161,7 @@ const Constants = {
161
161
  ReportBatching: 'reportBatching',
162
162
  EventsV3: 'eventsV3',
163
163
  EventBatchingIntervalMillis: 'eventBatchingIntervalMillis',
164
+ OfflineStorage: 'offlineStorage',
164
165
  },
165
166
  DefaultInstance: 'default_instance',
166
167
  } as const;
@@ -0,0 +1,61 @@
1
+ import { MParticleUser, SDKEvent, SDKEventCustomFlags, SDKUserIdentity } from './sdkRuntimeModels';
2
+ import { Dictionary } from './utils';
3
+ import { IKitConfigs } from './configAPIClient';
4
+ import { UserAttributes } from './persistence.interfaces';
5
+ import { IdentityApiData } from '@mparticle/web-sdk';
6
+
7
+ // The state of the kit when accessed via window.KitName via CDN
8
+ // or imported as an NPM package
9
+ export interface UnregisteredKit {
10
+ register(config): void;
11
+ }
12
+
13
+ // The state of the kit after being added to forwarderConstructors in the CDN
14
+ // or after registered to SDKConfig.kits via NPM
15
+ export interface RegisteredKit {
16
+ constructor(): void;
17
+ name: string;
18
+ getId(): number;
19
+ }
20
+
21
+ // The state of the kit after being configured. This is what the kit looks like when acted on.
22
+ export interface ConfiguredKit
23
+ extends Omit<IKitConfigs, 'isDebugString' | 'hasDebugString'> {
24
+ common: Dictionary<unknown>;
25
+ init(
26
+ settings: Dictionary<unknown>,
27
+ service: forwardingStatsCallback,
28
+ testMode: boolean,
29
+ trackerId: string | null,
30
+ userAttributes: UserAttributes,
31
+ userIdentities: SDKUserIdentity,
32
+ appVersion: string,
33
+ appName: string,
34
+ customFlags: SDKEventCustomFlags,
35
+ clientId: string): string;
36
+ onIdentifyComplete(user: MParticleUser, filteredIdentityRequest: IdentityApiData): string | KitMappedMethodFailure;
37
+ onLoginComplete(user: MParticleUser, filteredIdentityRequest: IdentityApiData): string | KitMappedMethodFailure;
38
+ onLogoutComplete(user: MParticleUser, filteredIdentityRequest: IdentityApiData): string | KitMappedMethodFailure;
39
+ onModifyComplete(user: MParticleUser, filteredIdentityRequest: IdentityApiData): string | KitMappedMethodFailure;
40
+ onUserIdentified(user: MParticleUser): string | KitMappedMethodFailure;
41
+ process(event: SDKEvent): string;
42
+ setOptOut(isOptingOut: boolean): string | KitMappedMethodFailure;
43
+ removeUserAttribute(key: string): string;
44
+ setUserAttribute(key: string, value:string): string;
45
+
46
+ // TODO: Convert type to enum during Identity migration
47
+ // https://go.mparticle.com/work/SQDSDKS-5218
48
+ setUserIdentity(id: UserIdentityId, type: UserIdentityType): void;
49
+
50
+ // TODO: https://go.mparticle.com/work/SQDSDKS-5156
51
+ isSandbox: boolean;
52
+ hasSandbox: boolean;
53
+ }
54
+ export interface KitMappedMethodFailure {
55
+ error: string
56
+ }
57
+
58
+ export type UserIdentityId = string;
59
+ export type UserIdentityType = number;
60
+
61
+ export type forwardingStatsCallback = (forwarder: ConfiguredKit, event: SDKEvent) => void;
package/src/forwarders.js CHANGED
@@ -650,7 +650,7 @@ export default function Forwarders(mpInstance, kitBlocker) {
650
650
  newForwarder.id = config.moduleId;
651
651
 
652
652
  // TODO: isSandbox, hasSandbox is never used in any kit or in core SDK.
653
- // isVisibleInvestigate is only used in 1 place. It is always true if
653
+ // isVisible. Investigate is only used in 1 place. It is always true if
654
654
  // it is sent to JS. Investigate further to determine if these can be removed.
655
655
  // https://go.mparticle.com/work/SQDSDKS-5156
656
656
  newForwarder.isSandbox = config.isDebug || config.isSandbox;
package/src/helpers.js CHANGED
@@ -26,17 +26,6 @@ export default function Helpers(mpInstance) {
26
26
  return null;
27
27
  };
28
28
 
29
- /**
30
- * Returns a value between 1-100 inclusive.
31
- */
32
- this.getRampNumber = function(deviceId) {
33
- if (!deviceId) {
34
- return 100;
35
- }
36
- var hash = self.generateHash(deviceId);
37
- return Math.abs(hash % 100) + 1;
38
- };
39
-
40
29
  this.invokeCallback = function(
41
30
  callback,
42
31
  code,
@@ -284,39 +273,6 @@ export default function Helpers(mpInstance) {
284
273
  return xhr;
285
274
  };
286
275
 
287
- function generateRandomValue(a) {
288
- var randomValue;
289
- if (window.crypto && window.crypto.getRandomValues) {
290
- randomValue = window.crypto.getRandomValues(new Uint8Array(1)); // eslint-disable-line no-undef
291
- }
292
- if (randomValue) {
293
- return (a ^ (randomValue[0] % 16 >> (a / 4))).toString(16);
294
- }
295
-
296
- return (a ^ ((Math.random() * 16) >> (a / 4))).toString(16);
297
- }
298
-
299
- this.generateUniqueId = function(a) {
300
- // https://gist.github.com/jed/982883
301
- // Added support for crypto for better random
302
-
303
- return a // if the placeholder was passed, return
304
- ? generateRandomValue(a) // a random number
305
- : // or otherwise a concatenated string:
306
- (
307
- [1e7] + // 10000000 +
308
- -1e3 + // -1000 +
309
- -4e3 + // -4000 +
310
- -8e3 + // -80000000 +
311
- -1e11
312
- ) // -100000000000,
313
- .replace(
314
- // replacing
315
- /[018]/g, // zeroes, ones, and eights with
316
- self.generateUniqueId // random hex digits
317
- );
318
- };
319
-
320
276
  this.filterUserIdentities = function(userIdentitiesObject, filterList) {
321
277
  var filteredUserIdentities = [];
322
278
 
@@ -403,37 +359,6 @@ export default function Helpers(mpInstance) {
403
359
  return false;
404
360
  };
405
361
 
406
- this.generateHash = function(name) {
407
- var hash = 0,
408
- i = 0,
409
- character;
410
-
411
- if (name === undefined || name === null) {
412
- return 0;
413
- }
414
-
415
- name = name.toString().toLowerCase();
416
-
417
- if (Array.prototype.reduce) {
418
- return name.split('').reduce(function(a, b) {
419
- a = (a << 5) - a + b.charCodeAt(0);
420
- return a & a;
421
- }, 0);
422
- }
423
-
424
- if (name.length === 0) {
425
- return hash;
426
- }
427
-
428
- for (i = 0; i < name.length; i++) {
429
- character = name.charCodeAt(i);
430
- hash = (hash << 5) - hash + character;
431
- hash = hash & hash;
432
- }
433
-
434
- return hash;
435
- };
436
-
437
362
  this.sanitizeAttributes = function(attrs, name) {
438
363
  if (!attrs || !self.isObject(attrs)) {
439
364
  return null;
@@ -501,6 +426,8 @@ export default function Helpers(mpInstance) {
501
426
  }
502
427
  };
503
428
 
429
+ // TODO: Refactor SDK to directly use these methods
430
+ // https://go.mparticle.com/work/SQDSDKS-5239
504
431
  // Utility Functions
505
432
  this.converted = utils.converted;
506
433
  this.findKeyInObject = utils.findKeyInObject;
@@ -510,6 +437,8 @@ export default function Helpers(mpInstance) {
510
437
  this.decoded = utils.decoded;
511
438
  this.returnConvertedBoolean = utils.returnConvertedBoolean;
512
439
  this.parseStringOrNumber = utils.parseStringOrNumber;
440
+ this.generateHash = utils.generateHash;
441
+ this.generateUniqueId = utils.generateUniqueId;
513
442
 
514
443
  // Imported Validators
515
444
  this.Validators = Validators;
@@ -4,6 +4,7 @@ import ServerModel from './serverModel';
4
4
  import { SDKEvent, BaseEvent, MParticleWebSDK } from './sdkRuntimeModels';
5
5
  import { convertEvents } from './sdkToEventsApiConverter';
6
6
  import * as EventsApi from '@mparticle/event-models';
7
+ import { Batch } from '@mparticle/event-models';
7
8
 
8
9
  const mockFunction = function () {
9
10
  return null;
@@ -119,16 +120,19 @@ export default class _BatchValidator {
119
120
  } as MParticleWebSDK;
120
121
  }
121
122
 
122
- returnBatch(event: BaseEvent) {
123
+ private createSDKEventFunction(event): SDKEvent {
124
+ return new ServerModel(this.getMPInstance()).createEventObject(event);
125
+ }
126
+
127
+ public returnBatch(events: BaseEvent | BaseEvent[]): Batch | null {
123
128
  const mpInstance = this.getMPInstance();
124
- const sdkEvent: SDKEvent = new ServerModel(
125
- mpInstance
126
- ).createEventObject(event);
127
- const batch: EventsApi.Batch | null = convertEvents(
128
- '0',
129
- [sdkEvent],
130
- mpInstance as any
131
- );
129
+
130
+ const sdkEvents: SDKEvent[] = Array.isArray(events)
131
+ ? events.map((event) => this.createSDKEventFunction(event))
132
+ : [this.createSDKEventFunction(events)];
133
+
134
+ const batch: Batch = convertEvents('0', sdkEvents, mpInstance as any);
135
+
132
136
  return batch;
133
137
  }
134
138
  }
@@ -232,7 +232,6 @@ export interface SDKHelpersApi {
232
232
  generateUniqueId();
233
233
  generateHash?(value: string): string;
234
234
  getFeatureFlag?(feature: string); // TODO: Feature Constants should be converted to enum
235
- getRampNumber?(deviceId: string): number;
236
235
  isDelayedByIntegration?(
237
236
  delayedIntegrations: Dictionary<boolean>,
238
237
  timeoutStart: number,
package/src/store.ts CHANGED
@@ -444,5 +444,12 @@ export default function Store(
444
444
  ) {
445
445
  this.SDKConfig.flags[Constants.FeatureFlags.ReportBatching] = false;
446
446
  }
447
+ if (
448
+ !this.SDKConfig.flags.hasOwnProperty(
449
+ Constants.FeatureFlags.OfflineStorage
450
+ )
451
+ ) {
452
+ this.SDKConfig.flags[Constants.FeatureFlags.OfflineStorage] = 0;
453
+ }
447
454
  }
448
455
  }
package/src/utils.ts CHANGED
@@ -36,6 +36,81 @@ const findKeyInObject = (obj: any, key: string): string => {
36
36
  return null;
37
37
  };
38
38
 
39
+ function generateHash(name: string): number {
40
+ let hash: number = 0;
41
+ let character: number;
42
+
43
+ if (name === undefined || name === null) {
44
+ return 0;
45
+ }
46
+
47
+ name = name.toString().toLowerCase();
48
+
49
+ if (Array.prototype.reduce) {
50
+ return name.split('').reduce(function (a: number, b: string) {
51
+ a = (a << 5) - a + b.charCodeAt(0);
52
+ return a & a;
53
+ }, 0);
54
+ }
55
+
56
+ if (name.length === 0) {
57
+ return hash;
58
+ }
59
+
60
+ for (let i = 0; i < name.length; i++) {
61
+ character = name.charCodeAt(i);
62
+ hash = (hash << 5) - hash + character;
63
+ hash = hash & hash;
64
+ }
65
+
66
+ return hash;
67
+ }
68
+
69
+ const generateRandomValue = (value?: string): string => {
70
+ let randomValue: string;
71
+ let a: number;
72
+
73
+ if (window.crypto && window.crypto.getRandomValues) {
74
+ // @ts-ignore
75
+ randomValue = window.crypto.getRandomValues(new Uint8Array(1)); // eslint-disable-line no-undef
76
+ }
77
+ if (randomValue) {
78
+ // @ts-ignore
79
+ return (a ^ (randomValue[0] % 16 >> (a / 4))).toString(16);
80
+ }
81
+
82
+ return (a ^ ((Math.random() * 16) >> (a / 4))).toString(16);
83
+ };
84
+
85
+ const generateUniqueId = (a: string = ''): string =>
86
+ // https://gist.github.com/jed/982883
87
+ // Added support for crypto for better random
88
+
89
+ a // if the placeholder was passed, return
90
+ ? generateRandomValue(a) // if the placeholder was passed, return
91
+ : // [1e7] -> // 10000000 +
92
+ // -1e3 -> // -1000 +
93
+ // -4e3 -> // -4000 +
94
+ // -8e3 -> // -80000000 +
95
+ // -1e11 -> //-100000000000,
96
+ `${1e7}-${1e3}-${4e3}-${8e3}-${1e11}`.replace(
97
+ /[018]/g, // zeroes, ones, and eights with
98
+ generateUniqueId // random hex digits
99
+ );
100
+
101
+ /**
102
+ * Returns a value between 1-100 inclusive.
103
+ */
104
+ const getRampNumber = (value?: string): number => {
105
+ if (!value) {
106
+ return 100;
107
+ }
108
+
109
+ const hash = generateHash(value);
110
+
111
+ return Math.abs(hash % 100) + 1;
112
+ };
113
+
39
114
  const isObject = (value: any): boolean => {
40
115
  var objType = Object.prototype.toString.call(value);
41
116
  return objType === '[object Object]' || objType === '[object Error]';
@@ -76,10 +151,7 @@ const decoded = (s: string): string =>
76
151
 
77
152
  const converted = (s: string): string => {
78
153
  if (s.indexOf('"') === 0) {
79
- s = s
80
- .slice(1, -1)
81
- .replace(/\\"/g, '"')
82
- .replace(/\\\\/g, '\\');
154
+ s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
83
155
  }
84
156
 
85
157
  return s;
@@ -104,6 +176,9 @@ export {
104
176
  converted,
105
177
  decoded,
106
178
  findKeyInObject,
179
+ generateHash,
180
+ generateUniqueId,
181
+ getRampNumber,
107
182
  inArray,
108
183
  isObject,
109
184
  isStringOrNumber,