@mparticle/web-sdk
Advanced tools
Comparing version 2.20.3 to 2.21.0
{ | ||
"name": "@mparticle/web-sdk", | ||
"version": "2.20.3", | ||
"version": "2.21.0", | ||
"description": "mParticle core SDK for web applications", | ||
@@ -5,0 +5,0 @@ "license": "Apache-2.0", |
import { Batch } from '@mparticle/event-models'; | ||
import Constants from './constants'; | ||
import { | ||
@@ -10,8 +11,13 @@ SDKEvent, | ||
import Types from './types'; | ||
import { isEmpty } from './utils'; | ||
import { getRampNumber, isEmpty } from './utils'; | ||
import { SessionStorageVault, LocalStorageVault } from './vault'; | ||
/** | ||
* BatchUploader contains all the logic to upload batches to mParticle. | ||
* It queues events as they come in and at set intervals turns them into batches. | ||
* It then attempts to upload them to mParticle. | ||
* BatchUploader contains all the logic to store/retrieve events and batches | ||
* to/from persistence, and upload batches to mParticle. | ||
* It queues events as they come in, storing them in persistence, then at set | ||
* intervals turns them into batches and transfers between event and batch | ||
* persistence. | ||
* It then attempts to upload them to mParticle, purging batch persistence if | ||
* the upload is successful | ||
* | ||
@@ -35,2 +41,5 @@ * These uploads happen on an interval basis using window.fetch or XHR | ||
batchingEnabled: boolean; | ||
private eventVault: SessionStorageVault<SDKEvent[]>; | ||
private batchVault: LocalStorageVault<Batch[]>; | ||
private offlineStorageEnabled: boolean = false; | ||
private uploader: AsyncUploader; | ||
@@ -51,5 +60,34 @@ | ||
} | ||
// Events will be queued during `queueEvents` method | ||
this.eventsQueuedForProcessing = []; | ||
// Batch queue should start empty and will be populated during | ||
// `prepareAndUpload` method, either via Local Storage or after | ||
// new batches are created. | ||
this.batchesQueuedForProcessing = []; | ||
// Cache Offline Storage Availability boolean | ||
// so that we don't have to check it every time | ||
this.offlineStorageEnabled = this.isOfflineStorageAvailable(); | ||
if (this.offlineStorageEnabled) { | ||
this.eventVault = new SessionStorageVault<SDKEvent[]>( | ||
`${mpInstance._Store.storageName}-events`, | ||
{ | ||
logger: mpInstance.Logger, | ||
} | ||
); | ||
this.batchVault = new LocalStorageVault<Batch[]>( | ||
`${mpInstance._Store.storageName}-batches`, | ||
{ | ||
logger: mpInstance.Logger, | ||
} | ||
); | ||
// Load Events from Session Storage in case we have any in storage | ||
this.eventsQueuedForProcessing.push(...this.eventVault.retrieve()); | ||
} | ||
const { SDKConfig, devToken } = this.mpInstance._Store; | ||
@@ -70,2 +108,25 @@ const baseUrl = this.mpInstance._Helpers.createServiceUrl( | ||
private isOfflineStorageAvailable(): boolean { | ||
const { | ||
_Helpers: { getFeatureFlag }, | ||
_Store: { deviceId }, | ||
} = this.mpInstance; | ||
const offlineStorageFeatureFlagValue = getFeatureFlag( | ||
Constants.FeatureFlags.OfflineStorage | ||
); | ||
const offlineStoragePercentage = parseInt( | ||
offlineStorageFeatureFlagValue, | ||
10 | ||
); | ||
const rampNumber = getRampNumber(deviceId); | ||
// TODO: Handle cases where Local Storage is unavailable | ||
// Potentially shared between Vault and Persistence as well | ||
// https://go.mparticle.com/work/SQDSDKS-5022 | ||
return offlineStoragePercentage >= rampNumber; | ||
} | ||
// Adds listeners to be used trigger Navigator.sendBeacon if the browser | ||
@@ -109,5 +170,8 @@ // loses focus for any reason, such as closing browser tab or minimizing window | ||
*/ | ||
queueEvent(event: SDKEvent): void { | ||
public queueEvent(event: SDKEvent): void { | ||
if (!isEmpty(event)) { | ||
this.eventsQueuedForProcessing.push(event); | ||
if (this.offlineStorageEnabled && this.eventVault) { | ||
this.eventVault.store(this.eventsQueuedForProcessing); | ||
} | ||
this.mpInstance.Logger.verbose( | ||
@@ -222,11 +286,29 @@ `Queuing event: ${JSON.stringify(event)}` | ||
const currentEvents = this.eventsQueuedForProcessing; | ||
const currentEvents: SDKEvent[] = this.eventsQueuedForProcessing; | ||
this.eventsQueuedForProcessing = []; | ||
if (this.offlineStorageEnabled && this.eventVault) { | ||
this.eventVault.store([]); | ||
} | ||
const newBatches = BatchUploader.createNewBatches( | ||
currentEvents, | ||
currentUser, | ||
this.mpInstance | ||
); | ||
let newBatches: Batch[] = []; | ||
if (!isEmpty(currentEvents)) { | ||
newBatches = BatchUploader.createNewBatches( | ||
currentEvents, | ||
currentUser, | ||
this.mpInstance | ||
); | ||
} | ||
// Top Load any older Batches from Offline Storage so they go out first | ||
if (this.offlineStorageEnabled && this.batchVault) { | ||
this.batchesQueuedForProcessing.unshift( | ||
...this.batchVault.retrieve() | ||
); | ||
// Remove batches from local storage before transmit to | ||
// prevent duplication | ||
this.batchVault.purge(); | ||
} | ||
if (!isEmpty(newBatches)) { | ||
@@ -240,2 +322,9 @@ this.batchesQueuedForProcessing.push(...newBatches); | ||
// If `useBeacon` is true, the browser has been closed suddently | ||
// so we should save `batchesToUpload` to Offline Storage before | ||
// an upload is attempted. | ||
if (useBeacon && this.offlineStorageEnabled && this.batchVault) { | ||
this.batchVault.store(batchesToUpload); | ||
} | ||
const batchesThatDidNotUpload = await this.uploadBatches( | ||
@@ -255,2 +344,15 @@ this.mpInstance.Logger, | ||
// Update Offline Storage with current state of batch queue | ||
if (!useBeacon && this.offlineStorageEnabled && this.batchVault) { | ||
// Note: since beacon is "Fire and forget" it will empty `batchesThatDidNotUplod` | ||
// regardless of whether the batches were successfully uploaded or not. We should | ||
// therefore NOT overwrite Offline Storage when beacon returns, so that we can retry | ||
// uploading saved batches at a later time. Batches should only be removed from | ||
// Local Storage once we can confirm they are successfully uploaded. | ||
this.batchVault.store(this.batchesQueuedForProcessing); | ||
// Clear batch queue since everything should be in Offline Storage | ||
this.batchesQueuedForProcessing = []; | ||
} | ||
if (triggerFuture) { | ||
@@ -257,0 +359,0 @@ this.triggerUploadInterval(triggerFuture, false); |
@@ -164,2 +164,3 @@ import { version } from '../package.json'; | ||
EventBatchingIntervalMillis: 'eventBatchingIntervalMillis', | ||
OfflineStorage: 'offlineStorage', | ||
}, | ||
@@ -166,0 +167,0 @@ DefaultInstance: 'default_instance', |
@@ -7,2 +7,3 @@ // This file is used ONLY for the mParticle ESLint plugin. It should NOT be used otherwise! | ||
import * as EventsApi from '@mparticle/event-models'; | ||
import { Batch } from '@mparticle/event-models'; | ||
@@ -123,14 +124,17 @@ const mockFunction = function () { | ||
returnBatch(event: BaseEvent) { | ||
private createSDKEventFunction(event): SDKEvent { | ||
return new ServerModel(this.getMPInstance()).createEventObject(event); | ||
} | ||
public returnBatch(events: BaseEvent | BaseEvent[]): Batch | null { | ||
const mpInstance = this.getMPInstance(); | ||
const sdkEvent: SDKEvent = new ServerModel( | ||
mpInstance | ||
).createEventObject(event); | ||
const batch: EventsApi.Batch | null = convertEvents( | ||
'0', | ||
[sdkEvent], | ||
mpInstance as any | ||
); | ||
const sdkEvents: SDKEvent[] = Array.isArray(events) | ||
? events.map((event) => this.createSDKEventFunction(event)) | ||
: [this.createSDKEventFunction(events)]; | ||
const batch: Batch = convertEvents('0', sdkEvents, mpInstance as any); | ||
return batch; | ||
} | ||
} |
@@ -447,3 +447,10 @@ import { Batch } from '@mparticle/event-models'; | ||
} | ||
if ( | ||
!this.SDKConfig.flags.hasOwnProperty( | ||
Constants.FeatureFlags.OfflineStorage | ||
) | ||
) { | ||
this.SDKConfig.flags[Constants.FeatureFlags.OfflineStorage] = 0; | ||
} | ||
} | ||
} |
import { Logger } from '@mparticle/web-sdk'; | ||
import { Dictionary, isEmpty } from './utils'; | ||
import { isEmpty } from './utils'; | ||
interface IVaultOptions { | ||
export interface IVaultOptions { | ||
logger?: Logger; | ||
@@ -9,6 +9,7 @@ offlineStorageEnabled?: boolean; | ||
export default class Vault<StorableItem> { | ||
export abstract class BaseVault<StorableItem> { | ||
public contents: StorableItem; | ||
private readonly _storageKey: string; | ||
private logger?: Logger; | ||
protected readonly _storageKey: string; | ||
protected logger?: Logger; | ||
protected storageObject: Storage; | ||
@@ -18,8 +19,12 @@ /** | ||
* @param {string} storageKey the local storage key string | ||
* @param {string} itemKey an element within your StorableItem to use as a key | ||
* @param {Storage} Web API Storage object that is being used | ||
* @param {IVaultOptions} options A Dictionary of IVaultOptions | ||
*/ | ||
constructor(storageKey: string, options?: IVaultOptions) { | ||
constructor( | ||
storageKey: string, | ||
storageObject: Storage, | ||
options?: IVaultOptions | ||
) { | ||
this._storageKey = storageKey; | ||
this.contents = this.getFromLocalStorage(); | ||
this.storageObject = storageObject; | ||
@@ -32,7 +37,9 @@ // Add a fake logger in case one is not provided or needed | ||
}; | ||
this.contents = this.retrieve(); | ||
} | ||
/** | ||
* Stores a StorableItem to Local Storage | ||
* @method storeItem | ||
* Stores a StorableItem to Storage | ||
* @method store | ||
* @param item {StorableItem} | ||
@@ -43,15 +50,30 @@ */ | ||
this.logger.verbose(`Saved to local storage: ${item}`); | ||
const stringifiedItem = !isEmpty(item) ? JSON.stringify(item) : ''; | ||
this.saveToLocalStorage(this.contents); | ||
try { | ||
this.storageObject.setItem(this._storageKey, stringifiedItem); | ||
this.logger.verbose(`Saving item to Storage: ${stringifiedItem}`); | ||
} catch (error) { | ||
this.logger.error( | ||
`Cannot Save items to Storage: ${stringifiedItem}` | ||
); | ||
this.logger.error(error as string); | ||
} | ||
} | ||
/** | ||
* Retrieves all StorableItems from local storage as an array | ||
* @method retrieveItems | ||
* @returns {StorableItem[]} an array of Items | ||
* Retrieve StorableItem from Storage | ||
* @method retrieve | ||
* @returns {StorableItem} | ||
*/ | ||
public retrieve(): StorableItem { | ||
this.contents = this.getFromLocalStorage(); | ||
public retrieve(): StorableItem | null { | ||
// TODO: Handle cases where Local Storage is unavailable | ||
// https://go.mparticle.com/work/SQDSDKS-5022 | ||
const item: string = this.storageObject.getItem(this._storageKey); | ||
this.contents = item ? JSON.parse(item) : null; | ||
this.logger.verbose(`Retrieving item from Storage: ${item}`); | ||
return this.contents; | ||
@@ -61,33 +83,23 @@ } | ||
/** | ||
* Removes all persisted data from local storage based on this vault's `key` | ||
* Removes all persisted data from Storage based on this vault's `key` | ||
* Will remove storage key from Storage as well | ||
* @method purge | ||
*/ | ||
public purge(): void { | ||
this.logger.verbose('Purging Storage'); | ||
this.contents = null; | ||
this.removeFromLocalStorage(); | ||
this.storageObject.removeItem(this._storageKey); | ||
} | ||
} | ||
private saveToLocalStorage(items: StorableItem): void { | ||
try { | ||
window.localStorage.setItem( | ||
this._storageKey, | ||
!isEmpty(items) ? JSON.stringify(items) : '' | ||
); | ||
} catch (error) { | ||
this.logger.error(`Cannot Save items to Local Storage: ${items}`); | ||
this.logger.error(error as string); | ||
} | ||
export class LocalStorageVault<StorableItem> extends BaseVault<StorableItem> { | ||
constructor(storageKey: string, options?: IVaultOptions) { | ||
super(storageKey, window.localStorage, options); | ||
} | ||
} | ||
private getFromLocalStorage(): StorableItem | null { | ||
// TODO: Handle cases where Local Storage is unavailable | ||
// https://go.mparticle.com/work/SQDSDKS-5022 | ||
const item: string = window.localStorage.getItem(this._storageKey); | ||
return item ? JSON.parse(item) : null; | ||
export class SessionStorageVault<StorableItem> extends BaseVault<StorableItem> { | ||
constructor(storageKey: string, options?: IVaultOptions) { | ||
super(storageKey, window.sessionStorage, options); | ||
} | ||
private removeFromLocalStorage(): void { | ||
window.localStorage.removeItem(this._storageKey); | ||
} | ||
} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1488516
26429