Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/batchUploader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ export class BatchUploader {

// Cache Offline Storage Availability boolean
// so that we don't have to check it every time
this.offlineStorageEnabled = this.isOfflineStorageAvailable();
this.offlineStorageEnabled =
this.isOfflineStorageAvailable() &&
!mpInstance._Store.getPrivacyFlag('OfflineEvents');

if (this.offlineStorageEnabled) {
this.eventVault = new SessionStorageVault<SDKEvent[]>(
Expand Down
12 changes: 12 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,15 @@ export const HTTP_UNAUTHORIZED = 401 as const;
export const HTTP_FORBIDDEN = 403 as const;
export const HTTP_NOT_FOUND = 404 as const;
export const HTTP_SERVER_ERROR = 500 as const;

export type PrivacyControl = 'functional' | 'targeting';

export type StorageTypes = 'SDKState' | 'Products' | 'OfflineEvents' | 'IdentityCache' | 'TimeOnSite';

export const StoragePrivacyMap: Record<StorageTypes, PrivacyControl> = {
SDKState: 'functional',
Products: 'targeting',
OfflineEvents: 'functional',
IdentityCache: 'functional',
TimeOnSite: 'targeting',
};
14 changes: 13 additions & 1 deletion src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
UserIdentities,
} from '@mparticle/web-sdk';
import { IKitConfigs } from './configAPIClient';
import Constants from './constants';
import Constants, { PrivacyControl, StoragePrivacyMap, StorageTypes } from './constants';
import {
DataPlanResult,
KitBlockerOptions,
Expand Down Expand Up @@ -216,6 +216,7 @@ export interface IStore {
setNoFunctional?(noFunctional: boolean): void;
getNoTargeting?(): boolean;
setNoTargeting?(noTargeting: boolean): void;
getPrivacyFlag?(storageType: StorageTypes): boolean;

getDeviceId?(): string;
setDeviceId?(deviceId: string): void;
Expand Down Expand Up @@ -608,6 +609,17 @@ export default function Store(
this.noTargeting = noTargeting;
};

this.getPrivacyFlag = (storageType: StorageTypes): boolean => {
const privacyControl: PrivacyControl = StoragePrivacyMap[storageType];
if (privacyControl === 'functional') {
return this.getNoFunctional();
}
if (privacyControl === 'targeting') {
return this.getNoTargeting();
}
return false;
};

this.getDeviceId = () => this.deviceId;
this.setDeviceId = (deviceId: string) => {
this.deviceId = deviceId;
Expand Down
82 changes: 78 additions & 4 deletions test/jest/batchUploader.spec.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,59 @@
import { BatchUploader } from '../../src/batchUploader';
import { IMParticleWebSDKInstance } from '../../src/mp-instance';
import { IStore } from '../../src/store';
import { StoragePrivacyMap, StorageTypes } from '../../src/constants';
import { SDKEvent } from '../../src/sdkRuntimeModels';

describe('BatchUploader', () => {
let batchUploader: BatchUploader;
let mockMPInstance: IMParticleWebSDKInstance;
let originalFetch: typeof global.fetch;

beforeEach(() => {
const now = Date.now();
jest.useFakeTimers({
now: now,
advanceTimers: true // This improves the performance of nested timers, equivalent to Sinon's shouldAdvanceTime
});

originalFetch = global.fetch;
global.fetch = jest.fn().mockResolvedValue({ ok: true, status: 200 });
Comment thread
rmi22186 marked this conversation as resolved.

// Create a mock mParticle instance with mocked methods for instantiating a BatchUploader
mockMPInstance = {
_Store: {
storageName: 'mprtcl-v4_abcdef',
getNoFunctional: function(this: IStore) { return this.noFunctional; },
getNoTargeting: function(this: IStore) { return this.noTargeting; },
getPrivacyFlag: function(this: IStore, storageType: StorageTypes) {
const privacyControl = StoragePrivacyMap[storageType];
if (privacyControl === 'functional') {
return this.getNoFunctional();
}
if (privacyControl === 'targeting') {
return this.getNoTargeting();
}
return false;
},
deviceId: 'device-1',
SDKConfig: {
flags: {}
}
},
_Helpers: {
getFeatureFlag: jest.fn().mockReturnValue(false),
createServiceUrl: jest.fn().mockReturnValue('https://mock-url.com')
getFeatureFlag: jest.fn().mockReturnValue('100'),
createServiceUrl: jest.fn().mockReturnValue('https://mock-url.com'),
generateUniqueId: jest.fn().mockReturnValue('req-1'),
},
Identity: {
getCurrentUser: jest.fn().mockReturnValue({
getMPID: () => 'test-mpid'
getMPID: () => 'test-mpid',
getConsentState: jest.fn().mockReturnValue(null),
})
},
Logger: {
verbose: jest.fn(),
error: jest.fn(),
warning: jest.fn(),
}
} as unknown as IMParticleWebSDKInstance;

Expand All @@ -35,6 +62,7 @@ describe('BatchUploader', () => {

afterEach(() => {
jest.useRealTimers();
global.fetch = originalFetch;
});

describe('shouldDebounceAST', () => {
Expand Down Expand Up @@ -104,4 +132,50 @@ describe('BatchUploader', () => {
expect(secondCallTime).toBe(firstCallTime);
});
});

describe('noFunctional', () => {
beforeEach(() => {
localStorage.clear();
sessionStorage.clear();
});

it('should disable offline storage when noFunctional is true', () => {
mockMPInstance._Store.noFunctional = true;

const uploader = new BatchUploader(mockMPInstance, 1000);
expect(uploader['offlineStorageEnabled']).toBe(false);

uploader.queueEvent({ EventDataType: 4 } as SDKEvent);
expect(sessionStorage.getItem('mprtcl-v4_abcdef-events')).toBeNull();
expect(localStorage.getItem('mprtcl-v4_abcdef-batches')).toBeNull();
});

it('should enable offline storage when noFunctional is false by default', async () => {
const uploader = new BatchUploader(mockMPInstance, 1000);

expect(uploader['offlineStorageEnabled']).toBe(true);

uploader.queueEvent({ EventDataType: 4 } as SDKEvent);
expect(sessionStorage.getItem('mprtcl-v4_abcdef-events')).not.toBeNull();
Comment thread
jaissica12 marked this conversation as resolved.

jest.advanceTimersByTime(1000);
await Promise.resolve();

expect(localStorage.getItem('mprtcl-v4_abcdef-batches')).not.toBeNull();
});

it('should enable offline storage when noFunctional is false', async () => {
mockMPInstance._Store.noFunctional = false;

const uploader = new BatchUploader(mockMPInstance, 1000);

uploader.queueEvent({ EventDataType: 4 } as SDKEvent);
expect(sessionStorage.getItem('mprtcl-v4_abcdef-events')).not.toBeNull();
Comment thread
jaissica12 marked this conversation as resolved.

jest.advanceTimersByTime(1000);
await Promise.resolve();

expect(localStorage.getItem('mprtcl-v4_abcdef-batches')).not.toBeNull();
});
});
});
95 changes: 95 additions & 0 deletions test/src/tests-batchUploader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,7 @@ describe('batch uploader', () => {
});

window.sessionStorage.clear();
window.localStorage.clear();
});

afterEach(() => {
Expand Down Expand Up @@ -1715,5 +1716,99 @@ describe('batch uploader', () => {
.catch((e) => {
})
});

describe('noFunctional', () => {
const eventStorageKey = 'mprtcl-v4_abcdef-events';
const batchStorageKey = 'mprtcl-v4_abcdef-batches';
beforeEach(() => {
window.mParticle._resetForTests(MPConfig);
window.localStorage.clear();
window.sessionStorage.clear();
});

it('should store batches in session storage when noFunctional is false by default', async () => {
window.mParticle.config.flags = {
offlineStorage: '100',
...enableBatchingConfigFlags,
};
window.mParticle.init(apiKey, window.mParticle.config);
await waitForCondition(hasIdentifyReturned);
const mpInstance = window.mParticle.getInstance();
const uploader = mpInstance._APIClient.uploader;
uploader.queueEvent(event0);
expect(window.sessionStorage.getItem(eventStorageKey)).to.not.equal(null);
});

it('should NOT store events in session storage when noFunctional is true', async () => {
window.mParticle.config.flags = {
offlineStorage: '100',
...enableBatchingConfigFlags,
};
window.mParticle.config.noFunctional = true;
window.mParticle.init(apiKey, window.mParticle.config);
await waitForCondition(hasIdentifyReturned);
const mpInstance = window.mParticle.getInstance();
const uploader = mpInstance._APIClient.uploader;
uploader.queueEvent(event0);
expect(window.sessionStorage.getItem(eventStorageKey)).to.equal(null);
});

it('should store events in session storage when noFunctional is false', async () => {
window.mParticle.config.flags = {
offlineStorage: '100',
...enableBatchingConfigFlags,
};
window.mParticle.config.noFunctional = false;
window.mParticle.init(apiKey, window.mParticle.config);
await waitForCondition(hasIdentifyReturned);
const mpInstance = window.mParticle.getInstance();
const uploader = mpInstance._APIClient.uploader;
uploader.queueEvent(event0);
expect(window.sessionStorage.getItem(eventStorageKey)).to.not.equal(null);
});

it('should store batches in local storage when noFunctional is false by default', async () => {
window.mParticle.init(apiKey, window.mParticle.config);
await waitForCondition(hasIdentifyReturned);
const mpInstance = window.mParticle.getInstance();
const uploader = mpInstance._APIClient.uploader;
fetchMock.post(urls.events, 500, { overwriteRoutes: true });
Comment thread
rmi22186 marked this conversation as resolved.
uploader.queueEvent(event0);
await window.mParticle.getInstance()._APIClient.uploader.prepareAndUpload();
Comment thread
rmi22186 marked this conversation as resolved.
expect(window.localStorage.getItem(batchStorageKey)).to.not.equal(null);
});

it('should NOT store batches in local storage when noFunctional is true', async () => {
window.mParticle.config.flags = {
offlineStorage: '100',
...enableBatchingConfigFlags,
};
window.mParticle.config.noFunctional = true;
window.mParticle.init(apiKey, window.mParticle.config);
await waitForCondition(hasIdentifyReturned);
const mpInstance = window.mParticle.getInstance();
const uploader = mpInstance._APIClient.uploader;
uploader.queueEvent(event0);
await window.mParticle.getInstance()._APIClient.uploader.prepareAndUpload();
Comment thread
rmi22186 marked this conversation as resolved.
expect(window.localStorage.getItem(batchStorageKey)).to.equal(null);
});

it('should store batches in local storage when noFunctional is false', async () => {
window.mParticle.config.flags = {
offlineStorage: '100',
...enableBatchingConfigFlags,
};
window.mParticle.config.noFunctional = false;
window.mParticle.init(apiKey, window.mParticle.config);
await waitForCondition(hasIdentifyReturned);
const mpInstance = window.mParticle.getInstance();
const uploader = mpInstance._APIClient.uploader;
fetchMock.post(urls.events, 500, { overwriteRoutes: true });
Comment thread
rmi22186 marked this conversation as resolved.
uploader.queueEvent(event0);
await window.mParticle.getInstance()._APIClient.uploader.prepareAndUpload();
Comment thread
rmi22186 marked this conversation as resolved.
expect(window.localStorage.getItem(batchStorageKey)).to.not.equal(null);
});

});
});
});
Loading