Skip to content

Commit 97dd88a

Browse files
authored
feat: added noFunctional in batchUploader to disable offline storage (#1063)
1 parent 7548f0b commit 97dd88a

5 files changed

Lines changed: 201 additions & 6 deletions

File tree

src/batchUploader.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ export class BatchUploader {
7272

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

7779
if (this.offlineStorageEnabled) {
7880
this.eventVault = new SessionStorageVault<SDKEvent[]>(

src/constants.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,15 @@ export const HTTP_UNAUTHORIZED = 401 as const;
229229
export const HTTP_FORBIDDEN = 403 as const;
230230
export const HTTP_NOT_FOUND = 404 as const;
231231
export const HTTP_SERVER_ERROR = 500 as const;
232+
233+
export type PrivacyControl = 'functional' | 'targeting';
234+
235+
export type StorageTypes = 'SDKState' | 'Products' | 'OfflineEvents' | 'IdentityCache' | 'TimeOnSite';
236+
237+
export const StoragePrivacyMap: Record<StorageTypes, PrivacyControl> = {
238+
SDKState: 'functional',
239+
Products: 'targeting',
240+
OfflineEvents: 'functional',
241+
IdentityCache: 'functional',
242+
TimeOnSite: 'targeting',
243+
};

src/store.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
UserIdentities,
99
} from '@mparticle/web-sdk';
1010
import { IKitConfigs } from './configAPIClient';
11-
import Constants from './constants';
11+
import Constants, { PrivacyControl, StoragePrivacyMap, StorageTypes } from './constants';
1212
import {
1313
DataPlanResult,
1414
KitBlockerOptions,
@@ -216,6 +216,7 @@ export interface IStore {
216216
setNoFunctional?(noFunctional: boolean): void;
217217
getNoTargeting?(): boolean;
218218
setNoTargeting?(noTargeting: boolean): void;
219+
getPrivacyFlag?(storageType: StorageTypes): boolean;
219220

220221
getDeviceId?(): string;
221222
setDeviceId?(deviceId: string): void;
@@ -608,6 +609,17 @@ export default function Store(
608609
this.noTargeting = noTargeting;
609610
};
610611

612+
this.getPrivacyFlag = (storageType: StorageTypes): boolean => {
613+
const privacyControl: PrivacyControl = StoragePrivacyMap[storageType];
614+
if (privacyControl === 'functional') {
615+
return this.getNoFunctional();
616+
}
617+
if (privacyControl === 'targeting') {
618+
return this.getNoTargeting();
619+
}
620+
return false;
621+
};
622+
611623
this.getDeviceId = () => this.deviceId;
612624
this.setDeviceId = (deviceId: string) => {
613625
this.deviceId = deviceId;

test/jest/batchUploader.spec.ts

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,59 @@
11
import { BatchUploader } from '../../src/batchUploader';
22
import { IMParticleWebSDKInstance } from '../../src/mp-instance';
3+
import { IStore } from '../../src/store';
4+
import { StoragePrivacyMap, StorageTypes } from '../../src/constants';
5+
import { SDKEvent } from '../../src/sdkRuntimeModels';
36

47
describe('BatchUploader', () => {
58
let batchUploader: BatchUploader;
69
let mockMPInstance: IMParticleWebSDKInstance;
10+
let originalFetch: typeof global.fetch;
711

812
beforeEach(() => {
913
const now = Date.now();
1014
jest.useFakeTimers({
1115
now: now,
1216
advanceTimers: true // This improves the performance of nested timers, equivalent to Sinon's shouldAdvanceTime
1317
});
14-
18+
originalFetch = global.fetch;
19+
global.fetch = jest.fn().mockResolvedValue({ ok: true, status: 200 });
20+
1521
// Create a mock mParticle instance with mocked methods for instantiating a BatchUploader
1622
mockMPInstance = {
1723
_Store: {
24+
storageName: 'mprtcl-v4_abcdef',
25+
getNoFunctional: function(this: IStore) { return this.noFunctional; },
26+
getNoTargeting: function(this: IStore) { return this.noTargeting; },
27+
getPrivacyFlag: function(this: IStore, storageType: StorageTypes) {
28+
const privacyControl = StoragePrivacyMap[storageType];
29+
if (privacyControl === 'functional') {
30+
return this.getNoFunctional();
31+
}
32+
if (privacyControl === 'targeting') {
33+
return this.getNoTargeting();
34+
}
35+
return false;
36+
},
37+
deviceId: 'device-1',
1838
SDKConfig: {
1939
flags: {}
2040
}
2141
},
2242
_Helpers: {
23-
getFeatureFlag: jest.fn().mockReturnValue(false),
24-
createServiceUrl: jest.fn().mockReturnValue('https://mock-url.com')
43+
getFeatureFlag: jest.fn().mockReturnValue('100'),
44+
createServiceUrl: jest.fn().mockReturnValue('https://mock-url.com'),
45+
generateUniqueId: jest.fn().mockReturnValue('req-1'),
2546
},
2647
Identity: {
2748
getCurrentUser: jest.fn().mockReturnValue({
28-
getMPID: () => 'test-mpid'
49+
getMPID: () => 'test-mpid',
50+
getConsentState: jest.fn().mockReturnValue(null),
2951
})
52+
},
53+
Logger: {
54+
verbose: jest.fn(),
55+
error: jest.fn(),
56+
warning: jest.fn(),
3057
}
3158
} as unknown as IMParticleWebSDKInstance;
3259

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

3663
afterEach(() => {
3764
jest.useRealTimers();
65+
global.fetch = originalFetch;
3866
});
3967

4068
describe('shouldDebounceAST', () => {
@@ -104,4 +132,50 @@ describe('BatchUploader', () => {
104132
expect(secondCallTime).toBe(firstCallTime);
105133
});
106134
});
135+
136+
describe('noFunctional', () => {
137+
beforeEach(() => {
138+
localStorage.clear();
139+
sessionStorage.clear();
140+
});
141+
142+
it('should disable offline storage when noFunctional is true', () => {
143+
mockMPInstance._Store.noFunctional = true;
144+
145+
const uploader = new BatchUploader(mockMPInstance, 1000);
146+
expect(uploader['offlineStorageEnabled']).toBe(false);
147+
148+
uploader.queueEvent({ EventDataType: 4 } as SDKEvent);
149+
expect(sessionStorage.getItem('mprtcl-v4_abcdef-events')).toBeNull();
150+
expect(localStorage.getItem('mprtcl-v4_abcdef-batches')).toBeNull();
151+
});
152+
153+
it('should enable offline storage when noFunctional is false by default', async () => {
154+
const uploader = new BatchUploader(mockMPInstance, 1000);
155+
156+
expect(uploader['offlineStorageEnabled']).toBe(true);
157+
158+
uploader.queueEvent({ EventDataType: 4 } as SDKEvent);
159+
expect(sessionStorage.getItem('mprtcl-v4_abcdef-events')).not.toBeNull();
160+
161+
jest.advanceTimersByTime(1000);
162+
await Promise.resolve();
163+
164+
expect(localStorage.getItem('mprtcl-v4_abcdef-batches')).not.toBeNull();
165+
});
166+
167+
it('should enable offline storage when noFunctional is false', async () => {
168+
mockMPInstance._Store.noFunctional = false;
169+
170+
const uploader = new BatchUploader(mockMPInstance, 1000);
171+
172+
uploader.queueEvent({ EventDataType: 4 } as SDKEvent);
173+
expect(sessionStorage.getItem('mprtcl-v4_abcdef-events')).not.toBeNull();
174+
175+
jest.advanceTimersByTime(1000);
176+
await Promise.resolve();
177+
178+
expect(localStorage.getItem('mprtcl-v4_abcdef-batches')).not.toBeNull();
179+
});
180+
});
107181
});

test/src/tests-batchUploader.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,7 @@ describe('batch uploader', () => {
12021202
});
12031203

12041204
window.sessionStorage.clear();
1205+
window.localStorage.clear();
12051206
});
12061207

12071208
afterEach(() => {
@@ -1715,5 +1716,99 @@ describe('batch uploader', () => {
17151716
.catch((e) => {
17161717
})
17171718
});
1719+
1720+
describe('noFunctional', () => {
1721+
const eventStorageKey = 'mprtcl-v4_abcdef-events';
1722+
const batchStorageKey = 'mprtcl-v4_abcdef-batches';
1723+
beforeEach(() => {
1724+
window.mParticle._resetForTests(MPConfig);
1725+
window.localStorage.clear();
1726+
window.sessionStorage.clear();
1727+
});
1728+
1729+
it('should store batches in session storage when noFunctional is false by default', async () => {
1730+
window.mParticle.config.flags = {
1731+
offlineStorage: '100',
1732+
...enableBatchingConfigFlags,
1733+
};
1734+
window.mParticle.init(apiKey, window.mParticle.config);
1735+
await waitForCondition(hasIdentifyReturned);
1736+
const mpInstance = window.mParticle.getInstance();
1737+
const uploader = mpInstance._APIClient.uploader;
1738+
uploader.queueEvent(event0);
1739+
expect(window.sessionStorage.getItem(eventStorageKey)).to.not.equal(null);
1740+
});
1741+
1742+
it('should NOT store events in session storage when noFunctional is true', async () => {
1743+
window.mParticle.config.flags = {
1744+
offlineStorage: '100',
1745+
...enableBatchingConfigFlags,
1746+
};
1747+
window.mParticle.config.noFunctional = true;
1748+
window.mParticle.init(apiKey, window.mParticle.config);
1749+
await waitForCondition(hasIdentifyReturned);
1750+
const mpInstance = window.mParticle.getInstance();
1751+
const uploader = mpInstance._APIClient.uploader;
1752+
uploader.queueEvent(event0);
1753+
expect(window.sessionStorage.getItem(eventStorageKey)).to.equal(null);
1754+
});
1755+
1756+
it('should store events in session storage when noFunctional is false', async () => {
1757+
window.mParticle.config.flags = {
1758+
offlineStorage: '100',
1759+
...enableBatchingConfigFlags,
1760+
};
1761+
window.mParticle.config.noFunctional = false;
1762+
window.mParticle.init(apiKey, window.mParticle.config);
1763+
await waitForCondition(hasIdentifyReturned);
1764+
const mpInstance = window.mParticle.getInstance();
1765+
const uploader = mpInstance._APIClient.uploader;
1766+
uploader.queueEvent(event0);
1767+
expect(window.sessionStorage.getItem(eventStorageKey)).to.not.equal(null);
1768+
});
1769+
1770+
it('should store batches in local storage when noFunctional is false by default', async () => {
1771+
window.mParticle.init(apiKey, window.mParticle.config);
1772+
await waitForCondition(hasIdentifyReturned);
1773+
const mpInstance = window.mParticle.getInstance();
1774+
const uploader = mpInstance._APIClient.uploader;
1775+
fetchMock.post(urls.events, 500, { overwriteRoutes: true });
1776+
uploader.queueEvent(event0);
1777+
await window.mParticle.getInstance()._APIClient.uploader.prepareAndUpload();
1778+
expect(window.localStorage.getItem(batchStorageKey)).to.not.equal(null);
1779+
});
1780+
1781+
it('should NOT store batches in local storage when noFunctional is true', async () => {
1782+
window.mParticle.config.flags = {
1783+
offlineStorage: '100',
1784+
...enableBatchingConfigFlags,
1785+
};
1786+
window.mParticle.config.noFunctional = true;
1787+
window.mParticle.init(apiKey, window.mParticle.config);
1788+
await waitForCondition(hasIdentifyReturned);
1789+
const mpInstance = window.mParticle.getInstance();
1790+
const uploader = mpInstance._APIClient.uploader;
1791+
uploader.queueEvent(event0);
1792+
await window.mParticle.getInstance()._APIClient.uploader.prepareAndUpload();
1793+
expect(window.localStorage.getItem(batchStorageKey)).to.equal(null);
1794+
});
1795+
1796+
it('should store batches in local storage when noFunctional is false', async () => {
1797+
window.mParticle.config.flags = {
1798+
offlineStorage: '100',
1799+
...enableBatchingConfigFlags,
1800+
};
1801+
window.mParticle.config.noFunctional = false;
1802+
window.mParticle.init(apiKey, window.mParticle.config);
1803+
await waitForCondition(hasIdentifyReturned);
1804+
const mpInstance = window.mParticle.getInstance();
1805+
const uploader = mpInstance._APIClient.uploader;
1806+
fetchMock.post(urls.events, 500, { overwriteRoutes: true });
1807+
uploader.queueEvent(event0);
1808+
await window.mParticle.getInstance()._APIClient.uploader.prepareAndUpload();
1809+
expect(window.localStorage.getItem(batchStorageKey)).to.not.equal(null);
1810+
});
1811+
1812+
});
17181813
});
17191814
});

0 commit comments

Comments
 (0)